/*
* EuroCarbDB, a framework for carbohydrate bioinformatics
*
* Copyright (c) 2006-2009, Eurocarb project, or third-party contributors as
* indicated by the @author tags or express copyright attribution
* statements applied by the authors.
*
* This copyrighted material is made available to anyone wishing to use, modify,
* copy, or redistribute it subject to the terms and conditions of the GNU
* Lesser General Public License, as published by the Free Software Foundation.
* A copy of this license accompanies this distribution in the file LICENSE.txt.
*
* This program 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 Lesser General Public License
* for more details.
*
* Last commit: $Rev: 1932 $ by $Author: glycoslave $ on $Date:: 2010-08-05 #$
*/
package org.eurocarbdb.action.admin;
// stdlib imports
import java.util.Calendar;
import java.util.Properties;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.FileOutputStream;
import java.io.FileNotFoundException;
// 3rd party imports
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.commons.net.ftp.FTPFile;
import org.apache.commons.net.ftp.FTPConnectionClosedException;
// eurocarb imports
import org.eurocarbdb.util.FTP_Client;
import org.eurocarbdb.util.ProgressWatchable;
import org.eurocarbdb.action.EurocarbAction;
/* class AbstractDownloadAction *//****************************************
*
* Abstract base class for actions that download files. This class
* currently only supports FTP downloads (although HTTP downloads
* would be easy to add).
*
* @author mjh <glycoslave@gmail.com>
* @version $Rev: 1932 $
*/
public abstract class AbstractDownloadAction
extends EurocarbAction implements ProgressWatchable
{
//~~~~~~~~~~~~~~~~~~~~~~ STATIC FIELDS ~~~~~~~~~~~~~~~~~~~~~~~~//
/** Logging handle. */
private static final Log log = LogFactory.getLog( AbstractDownloadAction.class );
/** Download stream buffer size in bytes. */
private static final int BUFFER_SIZE = 4096;
//~~~~~~~~~~~~~~~~~~~~~~~~~~ FIELDS ~~~~~~~~~~~~~~~~~~~~~~~~~~~//
/** When downloading commenced, in milliseconds, as given by the
* System.currentTimeMillis() call. */
protected long startTime = 0;
/** File size of the current (or most recent) downloaded file in bytes. */
protected long fileSize = 0;
/** Last modified time of the current (or most recent) downloaded file
* as reported by the server. */
private Calendar fileTimestamp;
/** Number of bytes downloaded for the current (or most recently) downloaded file. */
protected long bytesDownloaded = 0;
/** The input stream from the server for the file currently being downloaded.
* This stream will only be active for the duration of the call to download(). */
protected InputStream inStream;
/** The output stream to which bytes downloaded from the server input stream
* will be directed. This stream will only be active for the duration of
* the call to download(). */
protected OutputStream outStream;
/** The FTP client object. This is null when not connected. */
protected FTP_Client ftpClient;
//~~~~~~~~~~~~~~~~~~~~~~~~~ METHODS ~~~~~~~~~~~~~~~~~~~~~~~~~~~//
/**
* Returns the output stream to which a downloaded file is
* being directed. The return value will be null if a download is
* not currently in progress.
*/
public OutputStream getOutputStream() { return outStream; }
/* openConnection *//******************************************
*
* Opens a connection to the given server host with the given
* user credentials. Users of this method should wrap network
* actions in a <code>try-catch block</code> and call the
* <code>closeConnection</code> method in the <code>finally</code>
* section.
*/
protected void openConnection( String server, String username, String password )
throws IOException
{
if ( ftpClient != null )
throw new RuntimeException(
"FTP client still appears to be connected " +
"-- perhaps you forgot to close the previous connection?");
ftpClient = new FTP_Client();
ftpClient.connectAndLogin( server, username, password );
}
/* closeConnection *//*****************************************
*
* Closes an open connection; returns immediately without
* exception if connection is already closed or non-existent.
*/
protected void closeConnection()
{
if ( ftpClient == null )
{
log.debug("connection has already been closed, returning...");
return;
}
else try
{
log.info("FTP client disconnecting...");
ftpClient.disconnect();
}
catch ( IOException dont_care )
{
log.warn("Exception caught while disconnecting:", dont_care );
dont_care.printStackTrace();
}
finally { ftpClient = null; }
}
/* download *//************************************************
*
* Downloads the named remote file to the given output stream.
* This method blocks until download is complete, however
* information about download progress may be obtained
* asynchronously (in another thread) via the following methods:
* <ul>
* <li>getMillisecsElapsed</li>
* <li>getPercentComplete</li>
* <li>getDownloadSpeed</li>
* </ul>
*
* @see FTP_Client#statFile
*/
protected void download( String filename, OutputStream out )
throws IOException, FTPConnectionClosedException
{
if ( ftpClient == null )
throw new RuntimeException("client not yet connected!!!");
startTime = System.currentTimeMillis();
outStream = out;
try
{
ftpClient.setPassiveMode( true );
ftpClient.setBinaryMode( true );
// work out file mtime and size in bytes
FTPFile filestat = ftpClient.statFile( filename );
// mjh:TODO check not null here
this.fileSize = filestat.getSize();
this.fileTimestamp = filestat.getTimestamp();
if ( log.isDebugEnabled() )
{
log.debug( "File size is "
+ fileSize
+ ", last modified "
+ fileTimestamp );
}
// open input stream
this.inStream = ftpClient.retrieveFileStream( filename );
byte[] buffer = new byte[BUFFER_SIZE];
while ( true )
{
// read as many bytes as are available, up to the length of the buffer.
int available = inStream.available();
int bytes = (available <= buffer.length) ? available : buffer.length;
// the call blocks until data is read
int bytes_read = inStream.read( buffer, 0, bytes );
if ( bytes_read == -1 || bytesDownloaded == fileSize ) break;
if ( bytes_read == 0 ) continue;
//if ( log.isTraceEnabled() )
// log.trace("read " + bytes_read + " bytes");
bytesDownloaded += bytes_read;
outStream.write( buffer, 0, bytes_read );
}
log.debug("finished transfer, ending FTP transaction");
try { inStream.close(); }
catch ( IOException ioe )
{
log.warn("Caught exception closing input streams:", ioe );
}
if ( ! ftpClient.completePendingCommand() )
throw new IOException( "completePendingCommand returned false, "
+ "probably indicating a failed download "
+ "(unspecified reason)" );
log.info("download completed successfully");
}
catch ( FTPConnectionClosedException ioe )
{
log.warn("FTP server closed connection: ", ioe );
ioe.printStackTrace();
closeConnection();
throw ioe;
}
catch ( IOException ioe )
{
log.warn("Caught IO exception during FTP: ", ioe );
ioe.printStackTrace();
closeConnection();
throw ioe;
}
}
/* getFileSize *//*********************************************
*
* Returns the size of the currently downloaded file in bytes.
* Note that this value will be 0 unless a download is in progress.
*/
public long getFileSize() { return fileSize; }
/* getMillisecsElapsed *//*************************************
*
* Returns the number of milliseconds that have elapsed since a
* download commenced. Note that this value will be 0 unless a
* download is in progress.
*/
public int getMillisecsElapsed()
{
return (int) (System.currentTimeMillis() - startTime);
}
/* getPercentComplete *//**************************************
*
* Returns how much of a file has been downloaded at this point.
* Note that this value will be 0 unless a download is in progress.
*/
public int getPercentComplete()
{
if ( outStream == null ) return 0;
if ( fileSize == 0 ) return 0;
return (int) ((bytesDownloaded * 100) / fileSize);
}
/* getDownloadSpeed *//****************************************
*
* Returns the current rate of download in kilobytes/second.
* Note that this value will be 0 unless a download is in progress.
*/
public double getDownloadSpeed()
{
double elapsed_secs = (System.currentTimeMillis() - startTime) / 1000;
if ( elapsed_secs == 0 ) return 0;
// kilobytes per second
return (bytesDownloaded / 1000) / elapsed_secs;
}
/* getEstimateOfTimeRemaining *//******************************
*
* Returns an estimate of the number of seconds a download has
* left to complete based upon the current download speed and
* file size. Note that this value will only be meaningful if
* a download is in progress.
*/
public double getEstimateOfTimeRemaining()
{
assert fileSize != 0; // in bytes
double download_speed = getDownloadSpeed(); // in Kb/sec
if ( download_speed <= 0 ) return 0;
download_speed *= 1000; // now bytes/sec
return (fileSize - bytesDownloaded) / download_speed;
}
/*
public void doLongRunningTask()
{
try
{
for ( int i = 1; i <= 10; i++ )
{
Thread.sleep( 1000 );
secondsElapsed = i;
log.info( i + " second(s)" );
}
}
catch ( InterruptedException interrupt ) {}
}
*/
public abstract String execute();
} // end class