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.eclipse.aether.transfer.AbstractTransferListener;
28  import org.eclipse.aether.transfer.TransferCancelledException;
29  import org.eclipse.aether.transfer.TransferEvent;
30  import org.eclipse.aether.transfer.TransferResource;
31  
32  /**
33   * AbstractMavenTransferListener
34   */
35  public abstract class AbstractMavenTransferListener extends AbstractTransferListener {
36  
37      // CHECKSTYLE_OFF: LineLength
38      /**
39       * Formats file size with the associated <a href="https://en.wikipedia.org/wiki/Metric_prefix">SI</a> prefix
40       * (GB, MB, kB) and using the patterns <code>#0.0</code> for numbers between 1 and 10
41       * and <code>###0</code> for numbers between 10 and 1000+ by default.
42       *
43       * @see <a href="https://en.wikipedia.org/wiki/Metric_prefix">https://en.wikipedia.org/wiki/Metric_prefix</a>
44       * @see <a href="https://en.wikipedia.org/wiki/Binary_prefix">https://en.wikipedia.org/wiki/Binary_prefix</a>
45       * @see <a
46       *      href="https://en.wikipedia.org/wiki/Octet_%28computing%29">https://en.wikipedia.org/wiki/Octet_(computing)</a>
47       */
48      // CHECKSTYLE_ON: LineLength
49      // TODO Move me to Maven Shared Utils
50      static class FileSizeFormat {
51          enum ScaleUnit {
52              BYTE {
53                  @Override
54                  public long bytes() {
55                      return 1L;
56                  }
57  
58                  @Override
59                  public String symbol() {
60                      return "B";
61                  }
62              },
63              KILOBYTE {
64                  @Override
65                  public long bytes() {
66                      return 1000L;
67                  }
68  
69                  @Override
70                  public String symbol() {
71                      return "kB";
72                  }
73              },
74              MEGABYTE {
75                  @Override
76                  public long bytes() {
77                      return KILOBYTE.bytes() * KILOBYTE.bytes();
78                  }
79  
80                  @Override
81                  public String symbol() {
82                      return "MB";
83                  }
84              },
85              GIGABYTE {
86                  @Override
87                  public long bytes() {
88                      return MEGABYTE.bytes() * KILOBYTE.bytes();
89                  }
90                  ;
91  
92                  @Override
93                  public String symbol() {
94                      return "GB";
95                  }
96              };
97  
98              public abstract long bytes();
99  
100             public abstract String symbol();
101 
102             public static ScaleUnit getScaleUnit(long size) {
103                 Validate.isTrue(size >= 0L, "file size cannot be negative: %s", size);
104 
105                 if (size >= GIGABYTE.bytes()) {
106                     return GIGABYTE;
107                 } else if (size >= MEGABYTE.bytes()) {
108                     return MEGABYTE;
109                 } else if (size >= KILOBYTE.bytes()) {
110                     return KILOBYTE;
111                 } else {
112                     return BYTE;
113                 }
114             }
115         }
116 
117         private DecimalFormat smallFormat;
118         private DecimalFormat largeFormat;
119 
120         FileSizeFormat(Locale locale) {
121             smallFormat = new DecimalFormat("#0.0", new DecimalFormatSymbols(locale));
122             largeFormat = new DecimalFormat("###0", new DecimalFormatSymbols(locale));
123         }
124 
125         public String format(long size) {
126             return format(size, null);
127         }
128 
129         public String format(long size, ScaleUnit unit) {
130             return format(size, unit, false);
131         }
132 
133         @SuppressWarnings("checkstyle:magicnumber")
134         public String format(long size, ScaleUnit unit, boolean omitSymbol) {
135             Validate.isTrue(size >= 0L, "file size cannot be negative: %s", size);
136 
137             if (unit == null) {
138                 unit = ScaleUnit.getScaleUnit(size);
139             }
140 
141             double scaledSize = (double) size / unit.bytes();
142             String scaledSymbol = " " + unit.symbol();
143 
144             if (omitSymbol) {
145                 scaledSymbol = "";
146             }
147 
148             if (unit == ScaleUnit.BYTE) {
149                 return largeFormat.format(size) + scaledSymbol;
150             }
151 
152             if (scaledSize < 0.05 || scaledSize >= 10.0) {
153                 return largeFormat.format(scaledSize) + scaledSymbol;
154             } else {
155                 return smallFormat.format(scaledSize) + scaledSymbol;
156             }
157         }
158 
159         public String formatProgress(long progressedSize, long size) {
160             Validate.isTrue(progressedSize >= 0L, "progressed file size cannot be negative: %s", progressedSize);
161             Validate.isTrue(
162                     size < 0L || progressedSize <= size,
163                     "progressed file size cannot be greater than size: %s > %s",
164                     progressedSize,
165                     size);
166 
167             if (size >= 0L && progressedSize != size) {
168                 ScaleUnit unit = ScaleUnit.getScaleUnit(size);
169                 String formattedProgressedSize = format(progressedSize, unit, true);
170                 String formattedSize = format(size, unit);
171 
172                 return formattedProgressedSize + "/" + formattedSize;
173             } else {
174                 return format(progressedSize);
175             }
176         }
177     }
178 
179     protected PrintStream out;
180 
181     protected AbstractMavenTransferListener(PrintStream out) {
182         this.out = out;
183     }
184 
185     @Override
186     public void transferInitiated(TransferEvent event) {
187         String action = event.getRequestType() == TransferEvent.RequestType.PUT ? "Uploading" : "Downloading";
188         String direction = event.getRequestType() == TransferEvent.RequestType.PUT ? "to" : "from";
189 
190         TransferResource resource = event.getResource();
191         StringBuilder message = new StringBuilder();
192         message.append(action).append(' ').append(direction).append(' ').append(resource.getRepositoryId());
193         message.append(": ");
194         message.append(resource.getRepositoryUrl()).append(resource.getResourceName());
195 
196         out.println(message.toString());
197     }
198 
199     @Override
200     public void transferCorrupted(TransferEvent event) throws TransferCancelledException {
201         TransferResource resource = event.getResource();
202         // TODO This needs to be colorized
203         out.println("[WARNING] " + event.getException().getMessage() + " from " + resource.getRepositoryId() + " for "
204                 + resource.getRepositoryUrl() + resource.getResourceName());
205     }
206 
207     @Override
208     public void transferSucceeded(TransferEvent event) {
209         String action = (event.getRequestType() == TransferEvent.RequestType.PUT ? "Uploaded" : "Downloaded");
210         String direction = event.getRequestType() == TransferEvent.RequestType.PUT ? "to" : "from";
211 
212         TransferResource resource = event.getResource();
213         long contentLength = event.getTransferredBytes();
214         FileSizeFormat format = new FileSizeFormat(Locale.ENGLISH);
215 
216         StringBuilder message = new StringBuilder();
217         message.append(action).append(' ').append(direction).append(' ').append(resource.getRepositoryId());
218         message.append(": ");
219         message.append(resource.getRepositoryUrl()).append(resource.getResourceName());
220         message.append(" (").append(format.format(contentLength));
221 
222         long duration = System.currentTimeMillis() - resource.getTransferStartTime();
223         if (duration > 0L) {
224             double bytesPerSecond = contentLength / (duration / 1000.0);
225             message.append(" at ").append(format.format((long) bytesPerSecond)).append("/s");
226         }
227 
228         message.append(')');
229         out.println(message.toString());
230     }
231 }