001    package org.codehaus.mojo.wagon.shared;
002    
003    import static java.lang.String.format;
004    import static java.lang.System.currentTimeMillis;
005    import static org.apache.commons.io.FileUtils.touch;
006    import static org.apache.commons.lang3.StringUtils.leftPad;
007    import static org.kuali.common.util.FormatUtils.getCount;
008    import static org.kuali.common.util.FormatUtils.getRate;
009    import static org.kuali.common.util.FormatUtils.getSize;
010    import static org.kuali.common.util.FormatUtils.getTime;
011    import static org.kuali.common.util.base.Exceptions.illegalState;
012    import static org.kuali.common.util.log.Loggers.newLogger;
013    
014    import java.io.File;
015    import java.text.NumberFormat;
016    
017    import javax.validation.constraints.Min;
018    
019    import org.apache.maven.wagon.Wagon;
020    import org.kuali.common.core.build.ValidatingBuilder;
021    import org.kuali.common.core.validate.annotation.IdiotProofImmutable;
022    import org.kuali.common.util.Counter;
023    import org.kuali.common.util.LongCounter;
024    import org.kuali.common.util.execute.Executable;
025    import org.kuali.common.util.inform.Inform;
026    import org.kuali.common.util.inform.PercentCompleteInformer;
027    import org.slf4j.Logger;
028    
029    @IdiotProofImmutable
030    public final class WagonDownloadExecutable implements Executable {
031    
032            private static final Logger logger = newLogger();
033    
034            private final String remoteFile;
035            private final File destination;
036            private final Wagon wagon;
037            private final Counter counter;
038            private final LongCounter bytesCounter;
039            @Min(0)
040            private final int total;
041            @Min(0)
042            private final long start;
043            private final NumberFormat numberFormatter;
044            private final NumberFormat rateFormatter;
045            private final PercentCompleteInformer informer;
046    
047            @Override
048            public void execute() {
049                    try {
050                            touch(destination);
051                            wagon.get(remoteFile, destination);
052                            counter.increment();
053                            if (useInformer()) {
054                                    inform();
055                            } else {
056                                    stats();
057                            }
058                    } catch (Exception e) {
059                            throw illegalState(e);
060                    }
061            }
062    
063            private void inform() {
064                    synchronized (informer) {
065                            informer.incrementProgress();
066                            if (informer.getProgress() % informer.getTotal() == 0) {
067                                    Inform inform = informer.getInform();
068                                    String msg = String.format(" - [%s of %s]%s%s", informer.getProgress(), total, inform.getCompleteToken(), inform.getStartToken());
069                                    inform.getPrintStream().print(msg);
070                            }
071                    }
072            }
073    
074            private boolean useInformer() {
075                    return "warn".equalsIgnoreCase(System.getProperty("org.slf4j.simpleLogger.log.org.kuali.maven.wagon"));
076            }
077    
078            private void stats() {
079                    bytesCounter.increment(destination.length());
080                    int count = counter.getValue();
081                    long elapsed = currentTimeMillis() - start;
082                    String rate = getRate(elapsed, bytesCounter.getValue(), rateFormatter);
083                    // long millisPerFile = elapsed / count;
084                    int filesRemaining = total - count;
085                    // long timeRemaining = millisPerFile * filesRemaining;
086                    // int percent = new Double((count / (total * 1D)) * 100).intValue();
087                    String amount = lpad(getSize(bytesCounter.getValue(), numberFormatter), 6);
088                    Object[] args = { lpad(getCount(count), 6), lpad(getCount(total), 6), lpad(getCount(filesRemaining), 6), ltime(elapsed), lpad(rate, 8), amount };
089                    logger.info(format("%s of %s - remaining %s [elapsed:%s  rate:%s  downloaded:%s]", args));
090            }
091    
092            private String ltime(long millis) {
093                    return leftPad(getTime(millis, numberFormatter), 5, " ");
094            }
095    
096            private String lpad(Object object, int size) {
097                    return leftPad(object.toString(), size, " ");
098            }
099    
100            private WagonDownloadExecutable(Builder builder) {
101                    this.remoteFile = builder.remoteFile;
102                    this.destination = builder.destination;
103                    this.wagon = builder.wagon;
104                    this.counter = builder.counter;
105                    this.total = builder.total;
106                    this.start = builder.start;
107                    this.bytesCounter = builder.bytesCounter;
108                    this.numberFormatter = builder.numberFormatter;
109                    this.rateFormatter = builder.rateFormatter;
110                    this.informer = builder.informer;
111            }
112    
113            public static Builder builder() {
114                    return new Builder();
115            }
116    
117            public static class Builder extends ValidatingBuilder<WagonDownloadExecutable> {
118    
119                    private String remoteFile;
120                    private File destination;
121                    private Wagon wagon;
122                    private Counter counter;
123                    private int total;
124                    private long start;
125                    private LongCounter bytesCounter;
126                    private NumberFormat numberFormatter = getDefaultNumberFormatter();
127                    private NumberFormat rateFormatter = getDefaultRateFormatter();
128                    private PercentCompleteInformer informer;
129    
130                    public Builder withInformer(PercentCompleteInformer informer) {
131                            this.informer = informer;
132                            return this;
133                    }
134    
135                    public Builder withRateFormatter(NumberFormat rateFormatter) {
136                            this.rateFormatter = rateFormatter;
137                            return this;
138                    }
139    
140                    public Builder withNumberFormatter(NumberFormat numberFormatter) {
141                            this.numberFormatter = numberFormatter;
142                            return this;
143                    }
144    
145                    public Builder withBytesCounter(LongCounter bytesCounter) {
146                            this.bytesCounter = bytesCounter;
147                            return this;
148                    }
149    
150                    public Builder withStart(long start) {
151                            this.start = start;
152                            return this;
153                    }
154    
155                    public Builder withTotal(int total) {
156                            this.total = total;
157                            return this;
158                    }
159    
160                    public Builder withCounter(Counter counter) {
161                            this.counter = counter;
162                            return this;
163                    }
164    
165                    public Builder withRemoteFile(String remoteFile) {
166                            this.remoteFile = remoteFile;
167                            return this;
168                    }
169    
170                    public Builder withDestination(File destination) {
171                            this.destination = destination;
172                            return this;
173                    }
174    
175                    public Builder withWagon(Wagon wagon) {
176                            this.wagon = wagon;
177                            return this;
178                    }
179    
180                    @Override
181                    public WagonDownloadExecutable build() {
182                            return validate(new WagonDownloadExecutable(this));
183                    }
184    
185                    private NumberFormat getDefaultNumberFormatter() {
186                            NumberFormat nf = NumberFormat.getInstance();
187                            nf.setMaximumFractionDigits(1);
188                            nf.setMinimumFractionDigits(1);
189                            return nf;
190                    }
191    
192                    private NumberFormat getDefaultRateFormatter() {
193                            NumberFormat nf = NumberFormat.getInstance();
194                            nf.setMaximumFractionDigits(0);
195                            return nf;
196                    }
197    
198            }
199    
200            public String getRemoteFile() {
201                    return remoteFile;
202            }
203    
204            public File getDestination() {
205                    return destination;
206            }
207    
208            public Wagon getWagon() {
209                    return wagon;
210            }
211    
212            public Counter getCounter() {
213                    return counter;
214            }
215    
216            public int getTotal() {
217                    return total;
218            }
219    
220            public long getStart() {
221                    return start;
222            }
223    
224            public PercentCompleteInformer getInformer() {
225                    return informer;
226            }
227    
228            public LongCounter getBytesCounter() {
229                    return bytesCounter;
230            }
231    
232    }