summaryrefslogblamecommitdiffstats
path: root/src/main/java/org/openslx/filetransfer/Downloader.java
blob: cb933af1d722531744237b1998d53ff5a03b57eb (plain) (tree)
1
2
3
4
5
6
7
8
9

                                 
                    
                                     

                                
                       

                                
 

                               
                                        
 
 
                                                                               


                                                                                 
                                                                           
           


                                                         
           
                                                                                                                          
         
                                                                 


                                                                        

         

                                                                                 
                                                                                  
           
                                                                                  
                              
           
                                                                
         
                                     

         








                                                                                                      
                                                                                                 
         
                                             
                     





                                                                                                 















                                                                                                                                          
                                                   


                 









                                                                                                         








                                                                                                     

                                                                                                                                                            

                                                     



















                                                                                                                                              



                                                                                                                                             



                                                                                                           








                                                                                                              

                                                                                                                                  

                                                             
                                                       
                                 
                         

                                        



                                                                
                           
                                           
                 

                            
 
 
package org.openslx.filetransfer;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.net.Socket;

import javax.net.ssl.SSLContext;

import org.apache.log4j.Logger;

public class Downloader extends Transfer
{

	private static final Logger log = Logger.getLogger( Downloader.class );

	/***********************************************************************/
	/**
	 * Actively initiate a connection to a remote peer for downloading.
	 * 
	 * @param host Host name or address to connect to
	 * @param port Port to connect to
	 * @throws IOException
	 */
	public Downloader( String host, int port, int readTimeoutMs, SSLContext context, String token ) throws IOException
	{
		super( host, port, readTimeoutMs, context, log );
		outStream.writeByte( 'D' );
		if ( !sendToken( token ) || !sendEndOfMeta() )
			throw new IOException( "Sending token failed" );
	}

	/***********************************************************************/
	/**
	 * Constructor used by Listener to create an incoming download connection.
	 * 
	 * @param socket established connection to peer which requested an upload.
	 * @throws IOException
	 */
	protected Downloader( Socket socket ) throws IOException
	{
		super( socket, log );
	}

	/**
	 * Initiate the download. This method does not return until the file transfer finished.
	 * 
	 * @param destinationFile destination file name to download to
	 * @param rangeCallback this object's .get() method is called whenever the downloader needs to
	 *           know which part of the file to request next. This method should return null if no
	 *           more parts are needed, which in turn let's this method return true
	 * @return true on success, false otherwise
	 */
	public boolean download( final String destinationFile, final WantRangeCallback callback )
	{
		RandomAccessFile file = null;
		try {
			try {
				file = new RandomAccessFile( new File( destinationFile ), "rw" );
			} catch ( FileNotFoundException e2 ) {
				log.error( "Cannot open " + destinationFile + " for writing." );
				return false;
			}
			final RandomAccessFile f = file;
			DataReceivedCallback cb = new DataReceivedCallback() {
				public boolean dataReceived( final long fileOffset, final int dataLength, final byte[] data )
				{
					try {
						f.seek( fileOffset );
						f.write( data, 0, dataLength );
					} catch ( Exception e ) {
						log.error( "Could not write to file " + destinationFile + " at offset " + fileOffset, e );
						return false;
					}
					return true;
				}
			};
			return download( cb, callback );
		} finally {
			Transfer.safeClose( file );
		}
	}

	/**
	 * Initiate the download. This method does not return until the file transfer finished.
	 * 
	 * @param dataCallback this object's .dataReceived() method is called whenever a chunk of data is
	 *           received
	 * @param rangeCallback this object's .get() method is called whenever the downloader needs to
	 *           know which part of the file to request next. This method should return null if no
	 *           more parts are needed, which in turn let's this method return true
	 * @return true on success, false otherwise
	 */
	public boolean download( DataReceivedCallback dataCallback, WantRangeCallback rangeCallback )
	{
		if ( shouldGetToken() ) {
			log.error( "You didn't call getToken yet!" );
			return false;
		}
		FileRange requestedRange;
		try {
			while ( ( requestedRange = rangeCallback.get() ) != null ) {
				if ( requestedRange.startOffset < 0 || requestedRange.startOffset >= requestedRange.endOffset ) {
					log.error( "Callback supplied bad range (" + requestedRange.startOffset + " to " + requestedRange.endOffset + ")" );
					return false;
				}
				// Send range request
				if ( !sendRange( requestedRange.startOffset, requestedRange.endOffset ) || !sendEndOfMeta() ) {
					log.error( "Could not send next range request, download failed." );
					return false;
				}
				// See if remote peer acknowledges range request
				MetaData meta = readMetaData();
				if ( meta == null ) {
					log.error( "Did not receive meta data from uploading remote peer after requesting range, aborting." );
					return false;
				}
				FileRange remoteRange = meta.getRange();
				if ( remoteRange == null || !remoteRange.equals( requestedRange ) ) {
					log.error( "Confirmed range by remote peer does not match requested range, aborting download." );
					return false;
				}
				// Receive requested range
				int chunkLength = requestedRange.getLength();
				byte[] incoming = new byte[ 500000 ]; // 500kb
				int hasRead = 0;
				while ( hasRead < chunkLength ) {
					int ret;
					try {
						ret = dataFromServer.read( incoming, 0, Math.min( chunkLength - hasRead, incoming.length ) );
						if ( Thread.currentThread().isInterrupted() ) {
							log.debug( "Thread interrupted in download loop" );
							return false;
						}
					} catch ( IOException e ) {
						log.error( "Could not read payload from socket" );
						sendErrorCode( "payload read error" );
						return false;
					}
					if ( ret == -1 ) {
						log.info( "Remote peer unexpectedly closed the connection." );
						return false;
					}
					if ( !dataCallback.dataReceived( requestedRange.startOffset + hasRead, ret, incoming ) ) {
						this.close( "Aborting due to I/O error..." );
						return false;
					}
					hasRead += ret;
				}
			}
			sendDone();
			sendEndOfMeta();
			try {
				transferSocket.shutdownOutput();
			} catch ( IOException e ) {
			}
		} finally {
			this.close( null );
		}
		return true;
	}

}