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.shared.utils.logging.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         public String format(long size, ScaleUnit unit, boolean omitSymbol) {
140             if (size < 0L) {
141                 throw new IllegalArgumentException("file size cannot be negative: " + size);
142             }
143 
144             if (unit == null) {
145                 unit = ScaleUnit.getScaleUnit(size);
146             }
147 
148             double scaledSize = (double) size / unit.bytes();
149             String scaledSymbol = " " + unit.symbol();
150 
151             if (omitSymbol) {
152                 scaledSymbol = "";
153             }
154 
155             if (unit == ScaleUnit.BYTE) {
156                 return largeFormat.format(size) + scaledSymbol;
157             }
158 
159             if (scaledSize < 0.05 || scaledSize >= 10.0) {
160                 return largeFormat.format(scaledSize) + scaledSymbol;
161             } else {
162                 return smallFormat.format(scaledSize) + scaledSymbol;
163             }
164         }
165 
166         public String formatProgress(long progressedSize, long size) {
167             if (progressedSize < 0L) {
168                 throw new IllegalArgumentException("progressed file size cannot be negative: " + progressedSize);
169             }
170             if (size >= 0L && progressedSize > size) {
171                 throw new IllegalArgumentException(
172                         "progressed file size cannot be greater than size: " + progressedSize + " > " + size);
173             }
174 
175             if (size >= 0L && progressedSize != size) {
176                 ScaleUnit unit = ScaleUnit.getScaleUnit(size);
177                 String formattedProgressedSize = format(progressedSize, unit, true);
178                 String formattedSize = format(size, unit);
179 
180                 return formattedProgressedSize + "/" + formattedSize;
181             } else {
182                 return format(progressedSize);
183             }
184         }
185     }
186 
187     protected PrintStream out;
188 
189     protected AbstractMavenTransferListener(PrintStream out) {
190         this.out = out;
191     }
192 
193     @Override
194     public void transferInitiated(TransferEvent event) {
195         String darkOn = MessageUtils.isColorEnabled() ? ANSI_DARK_SET : "";
196         String darkOff = MessageUtils.isColorEnabled() ? ANSI_DARK_RESET : "";
197 
198         String action = event.getRequestType() == TransferEvent.RequestType.PUT ? "Uploading" : "Downloading";
199         String direction = event.getRequestType() == TransferEvent.RequestType.PUT ? "to" : "from";
200 
201         TransferResource resource = event.getResource();
202         StringBuilder message = new StringBuilder();
203         message.append(darkOn).append(action).append(' ').append(direction).append(' ');
204         message.append(darkOff).append(resource.getRepositoryId());
205         message.append(darkOn).append(": ").append(resource.getRepositoryUrl());
206         message.append(darkOff).append(resource.getResourceName());
207 
208         out.println(message.toString());
209     }
210 
211     @Override
212     public void transferCorrupted(TransferEvent event) throws TransferCancelledException {
213         TransferResource resource = event.getResource();
214         // TODO This needs to be colorized
215         out.println("[WARNING] " + event.getException().getMessage() + " from " + resource.getRepositoryId() + " for "
216                 + resource.getRepositoryUrl() + resource.getResourceName());
217     }
218 
219     @Override
220     public void transferSucceeded(TransferEvent event) {
221         String darkOn = MessageUtils.isColorEnabled() ? ANSI_DARK_SET : "";
222         String darkOff = MessageUtils.isColorEnabled() ? ANSI_DARK_RESET : "";
223 
224         String action = (event.getRequestType() == TransferEvent.RequestType.PUT ? "Uploaded" : "Downloaded");
225         String direction = event.getRequestType() == TransferEvent.RequestType.PUT ? "to" : "from";
226 
227         TransferResource resource = event.getResource();
228         long contentLength = event.getTransferredBytes();
229         FileSizeFormat format = new FileSizeFormat(Locale.ENGLISH);
230 
231         StringBuilder message = new StringBuilder();
232         message.append(action).append(darkOn).append(' ').append(direction).append(' ');
233         message.append(darkOff).append(resource.getRepositoryId());
234         message.append(darkOn).append(": ").append(resource.getRepositoryUrl());
235         message.append(darkOff).append(resource.getResourceName());
236         message.append(darkOn).append(" (").append(format.format(contentLength));
237 
238         long duration = System.currentTimeMillis() - resource.getTransferStartTime();
239         if (duration > 0L) {
240             double bytesPerSecond = contentLength / (duration / 1000.0);
241             message.append(" at ").append(format.format((long) bytesPerSecond)).append("/s");
242         }
243 
244         message.append(')').append(darkOff);
245         out.println(message.toString());
246     }
247 }