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