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