/*
* Created on 07-Nov-2004
* Created by Paul Gardner
* Copyright (C) 2004, 2005, 2006 Aelitis, All Rights Reserved.
*
* This program 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 2
* of the License, or (at your option) any later version.
* 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 General Public License for more details.
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*
* AELITIS, SAS au capital de 46,603.30 euros
* 8 Allee Lenotre, La Grille Royale, 78600 Le Mesnil le Roi, France.
*
*/
package org.gudy.azureus2.core3.torrent.impl;
/**
* @author parg
*
*/
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.*;
import org.gudy.azureus2.core3.torrent.*;
import org.gudy.azureus2.core3.util.AETemporaryFileHandler;
import org.gudy.azureus2.core3.util.BDecoder;
import org.gudy.azureus2.core3.util.Debug;
import org.gudy.azureus2.core3.util.FileUtil;
public class
TOTorrentCreatorImpl
implements TOTorrentCreator
{
private File torrent_base;
private URL announce_url;
private boolean add_other_hashes;
private long piece_length;
private long piece_min_size;
private long piece_max_size;
private long piece_num_lower;
private long piece_num_upper;
private boolean is_desc;
private Map<String,File> linkage_map = new HashMap<String, File>();
private File descriptor_dir;
private TOTorrentCreateImpl torrent;
private List<TOTorrentProgressListener> listeners = new ArrayList<TOTorrentProgressListener>();
public
TOTorrentCreatorImpl(
File _torrent_base )
{
torrent_base = _torrent_base;
}
public
TOTorrentCreatorImpl(
File _torrent_base,
URL _announce_url,
boolean _add_other_hashes,
long _piece_length )
throws TOTorrentException
{
torrent_base = _torrent_base;
announce_url = _announce_url;
add_other_hashes = _add_other_hashes;
piece_length = _piece_length;
}
public
TOTorrentCreatorImpl(
File _torrent_base,
URL _announce_url,
boolean _add_other_hashes,
long _piece_min_size,
long _piece_max_size,
long _piece_num_lower,
long _piece_num_upper )
throws TOTorrentException
{
torrent_base = _torrent_base;
announce_url = _announce_url;
add_other_hashes = _add_other_hashes;
piece_min_size = _piece_min_size;
piece_max_size = _piece_max_size;
piece_num_lower = _piece_num_lower;
piece_num_upper = _piece_num_upper;
}
public void
setFileIsLayoutDescriptor(
boolean b )
{
is_desc = b;
}
public TOTorrent
create()
throws TOTorrentException
{
try{
if ( announce_url == null ){
throw( new TOTorrentException( "Skeleton creator", TOTorrentException.RT_WRITE_FAILS ));
}
File base_to_use;
if ( is_desc ){
base_to_use = createLayoutMap();
}else{
base_to_use = torrent_base;
}
if ( piece_length > 0 ){
torrent =
new TOTorrentCreateImpl(
linkage_map,
base_to_use,
announce_url,
add_other_hashes,
piece_length );
}else{
torrent =
new TOTorrentCreateImpl(
linkage_map,
base_to_use,
announce_url,
add_other_hashes,
piece_min_size,
piece_max_size,
piece_num_lower,
piece_num_upper );
}
for ( TOTorrentProgressListener l: listeners ){
torrent.addListener( l );
}
torrent.create();
return( torrent );
}finally{
if ( is_desc ){
destroyLayoutMap();
}
}
}
private List<DescEntry>
readDescriptor()
throws TOTorrentException
{
try{
int top_files = 0;
int top_entries = 0;
String top_component = null;
Map map = BDecoder.decode( FileUtil.readFileAsByteArray( torrent_base ));
List<Map> file_map = (List<Map>)map.get( "file_map" );
if ( file_map == null ){
throw( new TOTorrentException( "Invalid descriptor file", TOTorrentException.RT_READ_FAILS ));
}
List<DescEntry> desc_entries = new ArrayList<DescEntry>();
BDecoder.decodeStrings( file_map );
for ( Map m: file_map ){
List<String> logical_path = (List<String>)m.get( "logical_path" );
String target = (String)m.get( "target" );
if ( logical_path == null || target == null ){
throw( new TOTorrentException( "Invalid descriptor file: entry=" + m, TOTorrentException.RT_READ_FAILS ));
}
if ( logical_path.size() == 0 ){
throw( new TOTorrentException( "Logical path must have at least one entry: " + m, TOTorrentException.RT_READ_FAILS ));
}
for ( int i=0;i<logical_path.size();i++ ){
logical_path.set( i, FileUtil.convertOSSpecificChars( logical_path.get(i), i < logical_path.size()-1));
}
File tf = new File( target );
if ( !tf.exists()){
throw( new TOTorrentException( "Invalid descriptor file: file '" + tf + "' not found" + m, TOTorrentException.RT_READ_FAILS ));
}else{
String str = logical_path.get(0);
if ( logical_path.size() == 1 ){
top_entries++;
}
if ( top_component != null && !top_component.equals( str )){
throw( new TOTorrentException( "Invalid descriptor file: multiple top level elements specified", TOTorrentException.RT_READ_FAILS ));
}
top_component = str;
}
desc_entries.add( new DescEntry( logical_path, tf ));
}
if ( top_entries > 1 ){
throw( new TOTorrentException( "Invalid descriptor file: exactly one top level entry required", TOTorrentException.RT_READ_FAILS ));
}
if ( desc_entries.isEmpty()){
throw( new TOTorrentException( "Invalid descriptor file: no mapping entries found", TOTorrentException.RT_READ_FAILS ));
}
return( desc_entries );
}catch( IOException e ){
throw( new TOTorrentException( "Invalid descriptor file: " + Debug.getNestedExceptionMessage( e ), TOTorrentException.RT_READ_FAILS ));
}
}
private void
mapDirectory(
int prefix_length,
File target,
File temp )
throws IOException
{
File[] files = target.listFiles();
for ( File f: files ){
String file_name = f.getName();
if ( file_name.equals( "." ) || file_name.equals( ".." )){
continue;
}
File t = new File( temp, file_name);
if ( f.isDirectory()){
if ( !t.isDirectory()){
t.mkdirs();
}
mapDirectory( prefix_length, f, t );
}else{
if ( !t.exists()){
t.createNewFile();
}else{
throw( new IOException( "Duplicate file: " + t ));
}
linkage_map.put( t.getAbsolutePath().substring( prefix_length ), f );
}
}
}
private File
createLayoutMap()
throws TOTorrentException
{
// create a directory/file hierarchy that mirrors that prescribed by the descriptor
// along with a linkage map to be applied during construction
if ( descriptor_dir != null ){
return( descriptor_dir );
}
try{
descriptor_dir = AETemporaryFileHandler.createTempDir();
File top_level_file = null;
List<DescEntry> desc_entries = readDescriptor();
for ( DescEntry entry: desc_entries ){
List<String> logical_path = entry.getLogicalPath();
File target = entry.getTarget();
File temp = descriptor_dir;
int prefix_length = descriptor_dir.getAbsolutePath().length() + 1;
for ( int i=0;i<logical_path.size();i++ ){
temp = new File( temp, logical_path.get( i ));
if ( top_level_file == null ){
top_level_file = temp;
}
}
if ( target.isDirectory()){
if ( !temp.isDirectory()){
if ( !temp.mkdirs()){
throw( new TOTorrentException( "Failed to create logical directory: " + temp, TOTorrentException.RT_WRITE_FAILS ));
}
}
mapDirectory( prefix_length, target, temp );
}else{
File p = temp.getParentFile();
if ( !p.isDirectory()){
if ( !p.mkdirs()){
throw( new TOTorrentException( "Failed to create logical directory: " + p, TOTorrentException.RT_WRITE_FAILS ));
}
}
if ( temp.exists()){
throw( new TOTorrentException( "Duplicate file: " + temp, TOTorrentException.RT_WRITE_FAILS ));
}else{
temp.createNewFile();
linkage_map.put( temp.getAbsolutePath().substring( prefix_length ), target );
}
}
}
return( top_level_file );
}catch( TOTorrentException e ){
throw( e );
}catch( Throwable e ){
throw( new TOTorrentException( Debug.getNestedExceptionMessage( e ), TOTorrentException.RT_WRITE_FAILS ));
}
}
private void
destroyLayoutMap()
{
if ( descriptor_dir != null && descriptor_dir.exists()){
if ( !FileUtil.recursiveDelete( descriptor_dir )){
Debug.out( "Failed to delete descriptor directory '" + descriptor_dir + "'" );
}
}
}
public long
getTorrentDataSizeFromFileOrDir()
throws TOTorrentException
{
if ( is_desc ){
List<DescEntry> desc_entries = readDescriptor();
long result = 0;
for ( DescEntry entry: desc_entries ){
result += getTorrentDataSizeFromFileOrDir( entry.getTarget());
}
return( result );
}else{
return( getTorrentDataSizeFromFileOrDir( torrent_base ));
}
}
private long
getTorrentDataSizeFromFileOrDir(
File file )
{
String name = file.getName();
if ( name.equals( "." ) || name.equals( ".." )){
return( 0 );
}
if ( !file.exists()){
return(0);
}
if ( file.isFile()){
return( file.length());
}else{
File[] dir_files = file.listFiles();
long length = 0;
for (int i=0;i<dir_files.length;i++){
length += getTorrentDataSizeFromFileOrDir( dir_files[i] );
}
return( length );
}
}
public void
cancel()
{
if ( torrent != null ){
torrent.cancel();
}
}
public void
addListener(
TOTorrentProgressListener listener )
{
if ( torrent == null ){
listeners.add( listener );
}else{
torrent.addListener( listener );
}
}
public void
removeListener(
TOTorrentProgressListener listener )
{
if ( torrent == null ){
listeners.remove( listener );
}else{
torrent.removeListener( listener );
}
}
private static class
DescEntry
{
private List<String> logical_path;
private File target;
private
DescEntry(
List<String> _l,
File _t )
{
logical_path = _l;
target = _t;
}
private List<String>
getLogicalPath()
{
return( logical_path );
}
private File
getTarget()
{
return( target );
}
}
}