package communication;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.PrintWriter;
import java.io.StringWriter;
import org.apache.commons.io.FileUtils;
public class DownloadResumer implements Runnable {
/** Main download which this part belongs to */
private Download rootDownload;
/** Status members */
private long size;
private long downloadedAmount;
private boolean resuming;
/** Download path of this part */
private String path;
/** Indicates if this download part faulty */
private boolean isFaulty = false;
private Thread[] partThreads;
private DownloadPart[] parts;
private int numberOfParts;
private boolean isCompleted = false;
private String errorMsg;
/**
* @return the size
*/
public long getSize() {
return size;
}
/**
* @return the path
*/
public String getPath() {
return path;
}
/**
* @return the rootDownload
*/
public Download getRootDownload() {
return rootDownload;
}
/**
* @return isFaulty
*/
public boolean isFaulty() {
return isFaulty;
}
public boolean isCompleted() {
return isCompleted;
}
/**
* @return the downloadedAmount
*/
public long getDownloadedAmount() {
return downloadedAmount;
}
public void addDownloadedAmount( long partDownloadedAmount ) {
this.downloadedAmount += partDownloadedAmount;
this.rootDownload.setDownloadedAmount( this.downloadedAmount );
}
public boolean isResuming() {
return this.resuming;
}
public String getErrorMsg() {
return this.errorMsg;
}
/**
* Constructor
*
* @param size
* Part size
*/
public DownloadResumer( Download download, long size, int defaultParts ) throws Exception {
this.rootDownload = download;
this.size = size;
this.path = download.getPath() + File.separator + download.getFileName();
this.downloadedAmount = 0L;
this.isCompleted = false;
// Checking existing parts
int i = 0;
while ( true ) {
File fDown = new File( this.path + ".pio" + i );
if ( !fDown.exists() )
break;
else
this.resuming = true;
i++;
}
// Parts found, we use the initial number of parts for resuming the
// download
if ( i > 0 )
this.numberOfParts = i;
else
this.numberOfParts = defaultParts;
}
@Override
public void run() {
try {
int i;
this.partThreads = new Thread[ this.numberOfParts ];
this.parts = new DownloadPart[ this.numberOfParts ];
long regularSize = Math.round( (double) this.size / this.numberOfParts );
long sizeOfLastPart = ( this.size - regularSize * ( this.numberOfParts - 1 ) );
for ( i = 0; i < this.partThreads.length; i++ ) {
if ( i != this.partThreads.length - 1 ) {
this.parts[ i ] = new DownloadPart( this, new File( path + ".pio" + i ),
( i * regularSize ), regularSize );
}
else {
this.parts[ i ] = new DownloadPart( this, new File( path + ".pio" + i ),
( i * regularSize ), sizeOfLastPart );
}
this.partThreads[ i ] = new Thread( this.parts[ i ] );
this.partThreads[ i ].start();
}
while ( loopOverAllParts() && !this.isFaulty ) {
Thread.sleep( 1000 );
}
// Merge parts
if ( !isFaulty && isCompleted ) {
FileOutputStream fout = new FileOutputStream( this.path );
File partFile;
FileInputStream finp;
int length;
byte[] buff = new byte[ 8000 ];
for ( i = 0; i < this.parts.length; i++ ) {
partFile = new File( this.path + ".pio" + i );
if ( !this.isFaulty && partFile.length() > 0 ) {
finp = new FileInputStream( partFile );
while ( ( length = finp.read( buff ) ) > 0 ) {
fout.write( buff, 0, length );
}
finp.close();
}
partFile.delete();
}
fout.close();
// If crc32check ?
File f = new File( this.path );
StringBuilder hex = new StringBuilder();
hex.append( Long.toHexString( FileUtils.checksumCRC32( f ) ) );
if ( ( hex.length() % 2 ) != 0 )
hex.insert( 0, '0' );
if ( !hex.toString().equals( rootDownload.getItem().getCrc32() ) )
throw new Exception( "Checksum check failed for file: " + this.path + "(CRC32: "
+ hex.toString() + " - putioCRC32: " + rootDownload.getItem().getCrc32() + ")" );
}
}
catch ( Exception e ) {
this.isFaulty = true;
this.errorMsg = e.getLocalizedMessage();
StringWriter sw = new StringWriter();
e.printStackTrace( new PrintWriter( sw ) );
System.out.println( sw.toString() );
}
}
private boolean loopOverAllParts() {
boolean retVal = false;
boolean complete = true;
for ( int i = 0; i < this.parts.length; i++ ) {
retVal |= this.partThreads[ i ].isAlive();
this.isFaulty |= this.parts[ i ].isFaulty();
complete &= this.parts[ i ].isCompleted();
}
this.isCompleted = complete;
return retVal;
}
}