package com.limegroup.gnutella.archive;
import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InterruptedIOException;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.util.Collection;
import java.util.Iterator;
import java.util.Map;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import org.apache.commons.net.ftp.FTP;
import org.apache.commons.net.ftp.FTPClient;
import org.apache.commons.net.ftp.FTPConnectionClosedException;
import org.apache.commons.net.ftp.FTPReply;
import org.apache.commons.net.io.CopyStreamException;
import org.apache.xerces.dom3.bootstrap.DOMImplementationRegistry;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.ls.DOMImplementationLS;
import org.w3c.dom.ls.LSSerializer;
import com.limegroup.gnutella.util.CommonUtils;
abstract class ArchiveContribution extends AbstractContribution {
/**
* @param media
* One of ArchiveConstants.MEDIA_*
*
* @throws IllegalArgumentException
* If media is not valid
*/
public ArchiveContribution( String username, String password,
String title, String description, int media )
throws DescriptionTooShortException {
this( username, password, title, description, media,
Archives.defaultCollectionForMedia( media ),
Archives.defaultTypesForMedia( media ));
}
public ArchiveContribution( String username, String password,
String title, String description, int media,
int collection, int type )
throws DescriptionTooShortException {
setUsername( username );
setPassword( password );
setTitle( title );
setDescription( description );
setMedia( media );
setCollection( collection );
setType( type );
}
abstract protected String getFtpServer();
abstract protected String getFtpPath();
abstract protected boolean isFtpDirPreMade();
abstract protected void checkin() throws IOException;
/**
*
* @throws UnknownHostException
* If the hostname cannot be resolved.
*
* @throws SocketException
* If the socket timeout could not be set.
*
* @throws FTPConnectionClosedException
* If the connection is closed by the server.
*
* @throws LoginFailedException
* If the login fails.
*
* @throws DirectoryChangeFailedException
* If changing to the directory provided by the internet
* archive fails.
*
* @throws CopyStreamException
* If an I/O error occurs while in the middle of
* transferring a file.
*
* @throws IOException
* If an I/O error occurs while sending a command or
* receiving a reply from the server
*
* @throws IllegalStateException
* If the contribution object is not ready to upload
* (no username, password, server, etc. set)
* or if java's xml parser is configured badly
*/
public void upload() throws UnknownHostException, SocketException,
FTPConnectionClosedException, LoginFailedException,
DirectoryChangeFailedException, CopyStreamException,
RefusedConnectionException, IOException {
final int NUM_XML_FILES = 2;
final String META_XML_SUFFIX = "_meta.xml";
final String FILES_XML_SUFFIX = "_files.xml";
final String username = getUsername();
final String password = getPassword();
if ( getFtpServer() == null ) {
throw new IllegalStateException( "ftp server not set" );
}
if ( getFtpPath() == null ) {
throw new IllegalStateException( "ftp path not set" );
}
if ( username == null ) {
throw new IllegalStateException( "username not set" );
}
if ( password == null ) {
throw new IllegalStateException( "password not set" );
}
// calculate total number of files and bytes
final String metaXmlString = serializeDocument( getMetaDocument() );
final String filesXmlString = serializeDocument( getFilesDocument() );
final byte[] metaXmlBytes = metaXmlString.getBytes();
final byte[] filesXmlBytes = filesXmlString.getBytes();
final int metaXmlLength = metaXmlBytes.length;
final int filesXmlLength = filesXmlBytes.length;
final Collection files = getFiles();
final int totalFiles = NUM_XML_FILES + files.size();
final String[] fileNames = new String[totalFiles];
final long[] fileSizes = new long[totalFiles];
final String metaXmlName = getIdentifier() + META_XML_SUFFIX;
fileNames[0] = metaXmlName;
fileSizes[0] = metaXmlLength;
final String filesXmlName = getIdentifier() + FILES_XML_SUFFIX;
fileNames[1] = filesXmlName;
fileSizes[1] = filesXmlLength;
int j = 2;
for (Iterator i = files.iterator(); i.hasNext();) {
final File f = (File) i.next();
fileNames[j] = f.getRemoteFileName();
fileSizes[j] = f.getFileSize();
j++;
}
// init the progress mapping
for (int i = 0; i < fileSizes.length; i++) {
_fileNames2Progress.put(fileNames[i],new UploadFileProgress(fileSizes[i]));
_totalUploadSize+=fileSizes[i];
}
FTPClient ftp = new FTPClient();
try {
// first connect
if ( isCancelled() ) { return; }
ftp.enterLocalPassiveMode();
if ( isCancelled() ) { return; }
ftp.connect( getFtpServer() );
final int reply = ftp.getReplyCode();
if ( !FTPReply.isPositiveCompletion(reply) ) {
throw new RefusedConnectionException( getFtpServer() + "refused FTP connection" );
}
// now login
if ( isCancelled() ) { return; }
if (!ftp.login( username, password )) {
throw new LoginFailedException();
}
try {
// try to change the directory
if (!ftp.changeWorkingDirectory(getFtpPath())) {
// if changing fails, make the directory
if ( !isFtpDirPreMade() &&
!ftp.makeDirectory( getFtpPath() )) {
throw new DirectoryChangeFailedException();
}
// now change directory, if it fails again bail
if ( isCancelled() ) { return; }
if (!ftp.changeWorkingDirectory( getFtpPath() )) {
throw new DirectoryChangeFailedException();
}
}
if ( isCancelled() ) { return; }
connected();
// upload xml files
uploadFile( metaXmlName,
new ByteArrayInputStream( metaXmlBytes ),
ftp);
uploadFile( filesXmlName,
new ByteArrayInputStream( filesXmlBytes ),
ftp);
// now switch to binary mode
if ( isCancelled() ) { return; }
ftp.setFileType( FTP.BINARY_FILE_TYPE );
// upload contributed files
for (final Iterator i = files.iterator(); i.hasNext();) {
final File f = (File) i.next();
uploadFile( f.getRemoteFileName(),
new FileInputStream( f.getIOFile() ),
ftp);
}
} catch( InterruptedIOException ioe ) {
// we've been requested to cancel
return;
} finally {
ftp.logout(); // we don't care if logging out fails
}
} finally {
try{
ftp.disconnect();
} catch( IOException e ) {} // don't care if disconnecting fails
}
// now tell the Internet Archive that we're done
if ( isCancelled() ) { return; }
checkinStarted();
if ( isCancelled() ) { return; }
checkin();
if ( isCancelled() ) { return; }
checkinCompleted();
}
/**
*
* @param fileName
* @param input
* The input stream (not necessarily buffered).
* This stream will be closed by this method
*/
private void uploadFile( String remoteFileName,
InputStream input, FTPClient ftp)
throws InterruptedIOException, IOException {
fileStarted( remoteFileName );
final InputStream fileStream = new BufferedInputStream(
new UploadMonitorInputStream( input, this));
try {
if ( isCancelled() ) {
throw new InterruptedIOException();
}
ftp.storeFile( remoteFileName, fileStream );
} finally {
fileStream.close();
}
if ( isCancelled() ) {
throw new InterruptedIOException();
}
fileCompleted();
}
/**
* @throws IllegalStateException
* If java's xml parser configuration is bad
*/
private Document getMetaDocument() {
/*
* Sample _meta.xml file:
*
* <metadata>
* <collection>opensource_movies</collection>
* <mediatype>movies</mediatype>
* <title>My Home Movie</title>
* <runtime>2:30</runtime>
* <director>Joe Producer</director>
* </metadata>
*
*/
final String METADATA_ELEMENT = "metadata";
final String COLLECTION_ELEMENT = "collection";
final String MEDIATYPE_ELEMENT = "mediatype";
final String TITLE_ELEMENT = "title";
final String DESCRIPTION_ELEMENT = "description";
final String LICENSE_URL_ELEMENT = "licenseurl";
final String UPLOAD_APPLICATION_ELEMENT = "upload_application";
final String APPID_ATTR = "appid";
final String APPID_ATTR_VALUE = "LimeWire";
final String VERSION_ATTR = "version";
final String UPLOADER_ELEMENT = "uploader";
final String IDENTIFIER_ELEMENT = "identifier";
final String TYPE_ELEMENT = "type";
try {
final DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
final DocumentBuilder db = dbf.newDocumentBuilder();
final Document document = db.newDocument();
final Element metadataElement = document.createElement(METADATA_ELEMENT);
document.appendChild( metadataElement );
final Element collectionElement = document.createElement( COLLECTION_ELEMENT );
metadataElement.appendChild( collectionElement );
collectionElement.appendChild( document.createTextNode( Archives.getCollectionString( getCollection())));
final Element mediatypeElement = document.createElement(MEDIATYPE_ELEMENT);
metadataElement.appendChild( mediatypeElement );
mediatypeElement.appendChild( document.createTextNode( Archives.getMediaString( getMedia() )));
final Element typeElement = document.createElement(TYPE_ELEMENT);
metadataElement.appendChild( typeElement );
typeElement.appendChild( document.createTextNode( Archives.getTypeString( getType() )));
final Element titleElement = document.createElement( TITLE_ELEMENT );
metadataElement.appendChild( titleElement );
titleElement.appendChild( document.createTextNode( getTitle()));
final Element descriptionElement = document.createElement( DESCRIPTION_ELEMENT );
metadataElement.appendChild( descriptionElement );
descriptionElement.appendChild( document.createTextNode( getDescription() ));
final Element identifierElement = document.createElement( IDENTIFIER_ELEMENT );
metadataElement.appendChild( identifierElement );
identifierElement.appendChild( document.createTextNode( getIdentifier() ));
final Element uploadApplicationElement = document.createElement( UPLOAD_APPLICATION_ELEMENT );
metadataElement.appendChild( uploadApplicationElement );
uploadApplicationElement.setAttribute( APPID_ATTR, APPID_ATTR_VALUE );
uploadApplicationElement.setAttribute( VERSION_ATTR, CommonUtils.getLimeWireVersion() );
final Element uploaderElement = document.createElement( UPLOADER_ELEMENT );
metadataElement.appendChild( uploaderElement );
uploaderElement.appendChild( document.createTextNode( getUsername() ));
//take licenseurl from the first File
final Iterator filesIterator = getFiles().iterator();
if ( filesIterator.hasNext() ) {
final File firstFile = (File) filesIterator.next();
final String licenseUrl = firstFile.getLicenseUrl();
if ( licenseUrl != null ) {
final Element licenseUrlElement = document.createElement( LICENSE_URL_ELEMENT );
metadataElement.appendChild( licenseUrlElement );
licenseUrlElement.appendChild( document.createTextNode( licenseUrl ));
}
}
// now build user-defined elements
final Map userFields = getFields();
for ( final Iterator i = userFields.keySet().iterator(); i.hasNext(); ) {
final Object field = i.next();
final Object value = userFields.get( field );
if ( field instanceof String ) {
final Element e = document.createElement( (String) field );
metadataElement.appendChild( e );
if ( value != null && value instanceof String) {
e.appendChild( document.createTextNode( (String) value ));
}
}
}
return document;
} catch (final ParserConfigurationException e) {
final IllegalStateException ise = new IllegalStateException();
ise.initCause( e );
throw ise;
}
}
/**
* @throws IllegalStateException
* If java's xml configuration is bad
* @return
*/
private Document getFilesDocument() {
/*
* Sample _files.xml file:
*
* <files>
* <file name="MyHomeMovie.mpeg" source="original">
* <runtime>2:30</runtime>
* <format>MPEG2</format>
* </file>
* </files>
*
*/
final String FILES_ELEMENT = "files";
try {
final DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
final DocumentBuilder db = dbf.newDocumentBuilder();
final Document document = db.newDocument();
final Element filesElement = document.createElement( FILES_ELEMENT );
document.appendChild( filesElement );
Collection files = getFiles();
for (final Iterator i = files.iterator(); i.hasNext();) {
final File file = (File) i.next();
final Element fileElement = file.getElement( document );
filesElement.appendChild( fileElement );
}
return document;
} catch (final ParserConfigurationException e) {
final IllegalStateException ise = new IllegalStateException();
ise.initCause( e );
throw ise;
}
}
/**
* @throws IllegalStateException
* If java's DOMImplementationLS class is screwed up or
* this code is buggy
* @param document
* @return
*/
private String serializeDocument( Document document ) {
try {
System.setProperty(DOMImplementationRegistry.PROPERTY,
"org.apache.xerces.dom.DOMImplementationSourceImpl");
final DOMImplementationRegistry registry = DOMImplementationRegistry.newInstance();
final DOMImplementationLS impl =
(DOMImplementationLS)registry.getDOMImplementation("LS");
final LSSerializer writer = impl.createLSSerializer();
final String str = writer.writeToString( document );
return str;
} catch (final ClassNotFoundException e) {
final IllegalStateException ise = new IllegalStateException();
ise.initCause( e );
throw ise;
} catch (final InstantiationException e) {
final IllegalStateException ise = new IllegalStateException();
ise.initCause( e );
throw ise;
} catch (final IllegalAccessException e) {
final IllegalStateException ise = new IllegalStateException();
ise.initCause( e );
throw ise;
}
}
}