/* This file is part of leafdigital leafChat. leafChat is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. leafChat is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with leafChat. If not, see <http://www.gnu.org/licenses/>. Copyright 2011 Samuel Marshall. */ package com.leafdigital.dcc; import util.*; import util.xml.XML; import com.leafdigital.ui.api.*; import leafchat.core.api.*; /** * Receives information about DCC file transfer progress and updates a * progress bar window. */ public class TransferProgress implements Runnable { private TransfersWindow tw; private Page page; private Progress p; private Button b; private Label transferRate,timeRemaining; private long soFar=0,size,startTime; long[] previousBytes=new long[5]; long[] previousTime=new long[previousBytes.length]; final static int SIZE_UNKNOWN=-1; private final static int UPDATEDELAY=1000; private int eventID; private Downloader d; private Uploader u; synchronized void setTransferred(long bytes) { if(soFar==0) startTime=System.currentTimeMillis(); soFar=bytes; } void setDownloader(Downloader d) { this.d=d; b.setEnabled(true); } void setUploader(Uploader u) { this.u=u; b.setEnabled(true); } synchronized void setFinished() { stopTimer(); if(soFar<size) { error("Transfer stopped before complete"); return; } // Clear indeterminate state if set, and make sure bar is full p.setRange(1); p.setProgress(1); // Display final size long now=System.currentTimeMillis(); transferRate.setText(StringUtils.displayBytes(soFar)); timeRemaining.setText("Finished in "+StringUtils.displayMilliseconds( now-startTime)); // TODO Maybe add support for changing to Open button here. For now, // just make the Cancel button go away b.setVisible(false); tw.markFinished(this); } /** Updates the display */ @Override public synchronized void run() { eventID=0; long now=System.currentTimeMillis(); // Update progress bar if(!p.isIndeterminate()) p.setProgress((int)(soFar/1024)); String speed=""; if(previousBytes[0]>0) // Don't display until we have an estimate { long bytesPerSecond=((soFar-previousBytes[0])*1000L)/(now-previousTime[0]); speed=" at "+StringUtils.displayBytes(bytesPerSecond)+"/s"; if(size==SIZE_UNKNOWN) timeRemaining.setText("Total size unknown"); else { if(bytesPerSecond==0) timeRemaining.setText("Stalled, waiting..."); else { long remainingSeconds=((size-soFar) / bytesPerSecond)+1; // Pessimistic :) Actually to avoid rounding down to 0 timeRemaining.setText( StringUtils.displayMilliseconds(remainingSeconds*1000L)+" remaining"); } } } System.arraycopy(previousBytes,1,previousBytes,0,previousBytes.length-1); System.arraycopy(previousTime,1,previousTime,0,previousTime.length-1); previousBytes[previousBytes.length-1]=soFar; previousTime[previousTime.length-1]=now; transferRate.setText(StringUtils.displayBytes(soFar)+speed); startTimer(); } /** * Action: User clicks cancel button. */ public void actionButton() { if(d!=null) d.cancel(); if(u!=null) u.cancel(); cancelled=true; error("Cancelled"); } private boolean cancelled=false; private String error=null; private String file; boolean isCancelled() { return cancelled; } boolean isError() { return error!=null; } String getError() { return error; } String getFilename() { return file; } /** * Sets error text and marks as finished. * @param error Error string (pure string, no XML) */ void error(String error) { b.setVisible(false); timeRemaining.setText("<error>"+XML.esc(error)+"</error>"); p.setProgress(0); stopTimer(); this.error=error; tw.markFinished(this); } /** * Sets error text, marks as finished, logs in system log. * @param error Error string (pure string, no XML) * @param t Error that caused failure */ void error(String error, Throwable t) { } /** * Sets status text. (Only applicable when transfer hasn't started yet.) * @param status New status string (pure string, no XML) */ void status(String status) { timeRemaining.setText(XML.esc(status)); } void stopTimer() { if(eventID!=0) TimeUtils.cancelTimedEvent(eventID); } Page getPage() { return page; } TransferProgress(TransfersWindow tw,PluginContext pc,boolean upload,String nick,String file,long size) { startTime=System.currentTimeMillis(); this.tw=tw; this.size=size; this.file=file; UI u=pc.getSingle(UI.class); page=u.newPage(this); VerticalPanel vp=u.newVerticalPanel(); page.setContents(vp); BorderPanel bpOuter=u.newBorderPanel(); vp.add(bpOuter); BorderPanel bp=u.newBorderPanel(); bp.setSpacing(8); bpOuter.set(BorderPanel.WEST,bp); b=u.newButton(); b.setLabel("Cancel"); b.setEnabled(false); bp.set(BorderPanel.EAST,b); b.setOnAction("actionButton"); b.setBaseGroup("a"); HorizontalPanel hp=u.newHorizontalPanel(); bp.set(BorderPanel.CENTRAL,hp); Spacer s=u.newSpacer(); s.setWidth(2); hp.add(s); Pic direction=u.newPic(); direction.setProperty(upload ? "dcc/uploadIcon" : "dcc/downloadIcon"); hp.add(direction); s=u.newSpacer(); s.setWidth(4); hp.add(s); Label l=u.newLabel(); l.setOwner(page); l.setText("<nick>"+XML.esc(nick)+"</nick>"); l.setBaseGroup("a"); hp.add(l); s=u.newSpacer(); s.setWidth(8); hp.add(s); l=u.newLabel(); l.setOwner(page); l.setText(XML.esc(file)); l.setBaseGroup("a"); hp.add(l); s=u.newSpacer(); s.setHeight(3); vp.add(s); p=u.newProgress(); if(size==SIZE_UNKNOWN) p.setIndeterminate(); else p.setRange((int)(size/1024)); vp.add(p); s=u.newSpacer(); s.setHeight(1); vp.add(s); bp=u.newBorderPanel(); vp.add(bp); hp=u.newHorizontalPanel(); bp.set(BorderPanel.WEST,hp); s=u.newSpacer(); s.setWidth(2); hp.add(s); transferRate=u.newLabel(); transferRate.setSmall(true); transferRate.setText(""); hp.add(transferRate); hp=u.newHorizontalPanel(); bp.set(BorderPanel.EAST,hp); timeRemaining=u.newLabel(); timeRemaining.setSmall(true); timeRemaining.setText(""); hp.add(timeRemaining); s=u.newSpacer(); s.setWidth(2); hp.add(s); tw.add(this); startTimer(); } /** Starts the timer for next update */ private void startTimer() { eventID=TimeUtils.addTimedEvent(this,UPDATEDELAY,false); } }