View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.maven.cli.transfer;
20  
21  import java.io.PrintStream;
22  import java.text.DecimalFormat;
23  import java.text.DecimalFormatSymbols;
24  import java.util.Locale;
25  
26  import org.apache.commons.lang3.Validate;
27  import org.apache.maven.shared.utils.logging.MessageUtils;
28  import org.eclipse.aether.transfer.AbstractTransferListener;
29  import org.eclipse.aether.transfer.TransferCancelledException;
30  import org.eclipse.aether.transfer.TransferEvent;
31  import org.eclipse.aether.transfer.TransferResource;
32  
33  /**
34   * AbstractMavenTransferListener
35   */
36  public abstract class AbstractMavenTransferListener extends AbstractTransferListener {
37  
38      private static final String ESC = "\u001B";
39      private static final String ANSI_DARK_SET = ESC + "[90m";
40      private static final String ANSI_DARK_RESET = ESC + "[0m";
41  
42      // CHECKSTYLE_OFF: LineLength
43      /**
44       * Formats file size with the associated <a href="https://en.wikipedia.org/wiki/Metric_prefix">SI</a> prefix
45       * (GB, MB, kB) and using the patterns <code>#0.0</code> for numbers between 1 and 10
46       * and <code>###0</code> for numbers between 10 and 1000+ by default.
47       *
48       * @see <a href="https://en.wikipedia.org/wiki/Metric_prefix">https://en.wikipedia.org/wiki/Metric_prefix</a>
49       * @see <a href="https://en.wikipedia.org/wiki/Binary_prefix">https://en.wikipedia.org/wiki/Binary_prefix</a>
50       * @see <a
51       *      href="https://en.wikipedia.org/wiki/Octet_%28computing%29">https://en.wikipedia.org/wiki/Octet_(computing)</a>
52       */
53      // CHECKSTYLE_ON: LineLength
54      // TODO Move me to Maven Shared Utils
55      static class FileSizeFormat {
56          enum ScaleUnit {
57              BYTE {
58                  @Override
59                  public long bytes() {
60                      return 1L;
61                  }
62  
63                  @Override
64                  public String symbol() {
65                      return "B";
66                  }
67              },
68              KILOBYTE {
69                  @Override
70                  public long bytes() {
71                      return 1000L;
72                  }
73  
74                  @Override
75                  public String symbol() {
76                      return "kB";
77                  }
78              },
79              MEGABYTE {
80                  @Override
81                  public long bytes() {
82                      return KILOBYTE.bytes() * KILOBYTE.bytes();
83                  }
84  
85                  @Override
86                  public String symbol() {
87                      return "MB";
88                  }
89              },
90              GIGABYTE {
91                  @Override
92                  public long bytes() {
93                      return MEGABYTE.bytes() * KILOBYTE.bytes();
94                  }
95                  ;
96  
97                  @Override
98                  public String symbol() {
99                      return "GB";
100                 }
101             };
102 
103             public abstract long bytes();
104 
105             public abstract String symbol();
106 
107             public static ScaleUnit getScaleUnit(long size) {
108                 Validate.isTrue(size >= 0L, "file size cannot be negative: %s", size);
109 
110                 if (size >= GIGABYTE.bytes()) {
111                     return GIGABYTE;
112                 } else if (size >= MEGABYTE.bytes()) {
113                     return MEGABYTE;
114                 } else if (size >= KILOBYTE.bytes()) {
115                     return KILOBYTE;
116                 } else {
117                     return BYTE;
118                 }
119             }
120         }
121 
122         private DecimalFormat smallFormat;
123         private DecimalFormat largeFormat;
124 
125         FileSizeFormat(Locale locale) {
126             smallFormat = new DecimalFormat("#0.0", new DecimalFormatSymbols(locale));
127             largeFormat = new DecimalFormat("###0", new DecimalFormatSymbols(locale));
128         }
129 
130         public String format(long size) {
131             return format(size, null);
132         }
133 
134         public String format(long size, ScaleUnit unit) {
135             return format(size, unit, false);
136         }
137 
138         @SuppressWarnings("checkstyle:magicnumber")
139         public String format(long size, ScaleUnit unit, boolean omitSymbol) {
140             Validate.isTrue(size >= 0L, "file size cannot be negative: %s", size);
141 
142             if (unit == null) {
143                 unit = ScaleUnit.getScaleUnit(size);
144             }
145 
146             double scaledSize = (double) size / unit.bytes();
147             String scaledSymbol = " " + unit.symbol();
148 
149             if (omitSymbol) {
150                 scaledSymbol = "";
151             }
152 
153             if (unit == ScaleUnit.BYTE) {
154                 return largeFormat.format(size) + scaledSymbol;
155             }
156 
157             if (scaledSize < 0.05 || scaledSize >= 10.0) {
158                 return largeFormat.format(scaledSize) + scaledSymbol;
159             } else {
160                 return smallFormat.format(scaledSize) + scaledSymbol;
161             }
162         }
163 
164         public String formatProgress(long progressedSize, long size) {
165             Validate.isTrue(progressedSize >= 0L, "progressed file size cannot be negative: %s", progressedSize);
166             Validate.isTrue(
167                     size >= 0L && progressedSize <= size || size < 0L,
168                     "progressed file size cannot be greater than size: %s > %s",
169                     progressedSize,
170                     size);
171 
172             if (size >= 0L && progressedSize != size) {
173                 ScaleUnit unit = ScaleUnit.getScaleUnit(size);
174                 String formattedProgressedSize = format(progressedSize, unit, true);
175                 String formattedSize = format(size, unit);
176 
177                 return formattedProgressedSize + "/" + formattedSize;
178             } else {
179                 return format(progressedSize);
180             }
181         }
182     }
183 
184     protected PrintStream out;
185 
186     protected AbstractMavenTransferListener(PrintStream out) {
187         this.out = out;
188     }
189 
190     @Override
191     public void transferInitiated(TransferEvent event) {
192         String darkOn = MessageUtils.isColorEnabled() ? ANSI_DARK_SET : "";
193         String darkOff = MessageUtils.isColorEnabled() ? ANSI_DARK_RESET : "";
194 
195         String action = event.getRequestType() == TransferEvent.RequestType.PUT ? "Uploading" : "Downloading";
196         String direction = event.getRequestType() == TransferEvent.RequestType.PUT ? "to" : "from";
197 
198         TransferResource resource = event.getResource();
199         StringBuilder message = new StringBuilder();
200         message.append(darkOn).append(action).append(' ').append(direction).append(' ');
201         message.append(darkOff).append(resource.getRepositoryId());
202         message.append(darkOn).append(": ").append(resource.getRepositoryUrl());
203         message.append(darkOff).append(resource.getResourceName());
204 
205         out.println(message.toString());
206     }
207 
208     @Override
209     public void transferCorrupted(TransferEvent event) throws TransferCancelledException {
210         TransferResource resource = event.getResource();
211         // TODO This needs to be colorized
212         out.println("[WARNING] " + event.getException().getMessage() + " from " + resource.getRepositoryId() + " for "
213                 + resource.getRepositoryUrl() + resource.getResourceName());
214     }
215 
216     @Override
217     public void transferSucceeded(TransferEvent event) {
218         String darkOn = MessageUtils.isColorEnabled() ? ANSI_DARK_SET : "";
219         String darkOff = MessageUtils.isColorEnabled() ? ANSI_DARK_RESET : "";
220 
221         String action = (event.getRequestType() == TransferEvent.RequestType.PUT ? "Uploaded" : "Downloaded");
222         String direction = event.getRequestType() == TransferEvent.RequestType.PUT ? "to" : "from";
223 
224         TransferResource resource = event.getResource();
225         long contentLength = event.getTransferredBytes();
226         FileSizeFormat format = new FileSizeFormat(Locale.ENGLISH);
227 
228         StringBuilder message = new StringBuilder();
229         message.append(action).append(darkOn).append(' ').append(direction).append(' ');
230         message.append(darkOff).append(resource.getRepositoryId());
231         message.append(darkOn).append(": ").append(resource.getRepositoryUrl());
232         message.append(darkOff).append(resource.getResourceName());
233         message.append(darkOn).append(" (").append(format.format(contentLength));
234 
235         long duration = System.currentTimeMillis() - resource.getTransferStartTime();
236         if (duration > 0L) {
237             double bytesPerSecond = contentLength / (duration / 1000.0);
238             message.append(" at ").append(format.format((long) bytesPerSecond)).append("/s");
239         }
240 
241         message.append(')').append(darkOff);
242         out.println(message.toString());
243     }
244 }