/* * File : TOTorrentCreateImpl.java * Created : 5 Oct. 2003 * By : Parg * * Azureus - a Java Bittorrent client * * 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. * * 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 ( see the LICENSE file ). * * 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 */ package org.gudy.azureus2.core3.torrent.impl; import java.io.*; import java.net.*; import java.util.*; import java.util.zip.GZIPOutputStream; import org.gudy.azureus2.core3.internat.MessageText; import org.gudy.azureus2.core3.torrent.*; import org.gudy.azureus2.core3.util.*; public class TOTorrentCreateImpl extends TOTorrentImpl implements TOTorrentFileHasherListener { private static final Comparator<File> file_comparator; static{ if ( System.getProperty( "az.create.torrent.alphanumeric.sort", "0" ).equals( "1" )){ file_comparator = new Comparator<File>() { public int compare( File f1, File f2 ) { String s1 = f1.getName(); String s2 = f2.getName(); int l1 = s1.length(); int l2 = s2.length(); int c1_pos = 0; int c2_pos = 0; while( c1_pos < l1 && c2_pos < l2 ){ char c1 = s1.charAt( c1_pos++ ); char c2 = s2.charAt( c2_pos++ ); if ( Character.isDigit(c1) && Character.isDigit(c2)){ int n1_pos = c1_pos-1; int n2_pos = c2_pos-1; while( c1_pos < l1 ){ if ( !Character.isDigit( s1.charAt( c1_pos ))){ break; } c1_pos++; } while(c2_pos<l2){ if ( !Character.isDigit( s2.charAt( c2_pos ))){ break; } c2_pos++; } int n1_length = c1_pos - n1_pos; int n2_length = c2_pos - n2_pos; if ( n1_length != n2_length ){ return( n1_length - n2_length ); } for (int i=0;i<n1_length;i++){ char nc1 = s1.charAt( n1_pos++ ); char nc2 = s2.charAt( n2_pos++ ); if ( nc1 != nc2 ){ return( nc1 - nc2 ); } } }else{ if ( true ){ c1 = Character.toLowerCase( c1 ); c2 = Character.toLowerCase( c2 ); } if ( c1 != c2 ){ return( c1 - c2 ); } } } return( l1 - l2); } }; }else{ file_comparator = null; } } private File torrent_base; private long piece_length; private TOTorrentFileHasher file_hasher; private long total_file_size = -1; private long total_file_count = 0; private long piece_count; private boolean add_other_hashes; private List<TOTorrentProgressListener> progress_listeners = new ArrayList<TOTorrentProgressListener>(); private int reported_progress; private Set<String> ignore_set = new HashSet<String>(); private Map<String,File> linkage_map; private Map<String,String> linked_tf_map = new HashMap<String, String>(); private boolean cancelled; protected TOTorrentCreateImpl( Map<String,File> _linkage_map, File _torrent_base, URL _announce_url, boolean _add_other_hashes, long _piece_length ) throws TOTorrentException { super( _torrent_base.getName(), _announce_url, _torrent_base.isFile()); linkage_map = _linkage_map; torrent_base = _torrent_base; piece_length = _piece_length; add_other_hashes = _add_other_hashes; } protected TOTorrentCreateImpl( Map<String,File> _linkage_map, 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 { super( _torrent_base.getName(), _announce_url, _torrent_base.isFile()); linkage_map = _linkage_map; torrent_base = _torrent_base; add_other_hashes = _add_other_hashes; long total_size = calculateTotalFileSize( _torrent_base ); piece_length = getComputedPieceSize( total_size, _piece_min_size, _piece_max_size, _piece_num_lower, _piece_num_upper ); } protected void create() throws TOTorrentException { constructFixed( torrent_base, piece_length ); if ( linkage_map.size() != linked_tf_map.size()){ throw( new TOTorrentException( "TOTorrentCreate: unresolved linkages: required=" + linkage_map + ", resolved=" + linked_tf_map, TOTorrentException.RT_DECODE_FAILS)); } if ( linked_tf_map.size() > 0 ){ Map m = getAdditionalMapProperty( TOTorrent.AZUREUS_PRIVATE_PROPERTIES ); if ( m == null ){ m = new HashMap(); setAdditionalMapProperty( TOTorrent.AZUREUS_PRIVATE_PROPERTIES, m ); } if ( linked_tf_map.size() < 100 ){ m.put( TorrentUtils.TORRENT_AZ_PROP_INITIAL_LINKAGE, linked_tf_map ); }else{ ByteArrayOutputStream baos = new ByteArrayOutputStream( 100*1024 ); try{ GZIPOutputStream gos = new GZIPOutputStream( baos ); gos.write( BEncoder.encode( linked_tf_map )); gos.close(); m.put( TorrentUtils.TORRENT_AZ_PROP_INITIAL_LINKAGE2, baos.toByteArray() ); }catch( Throwable e ){ throw( new TOTorrentException( "Failed to serialise linkage", TOTorrentException.RT_WRITE_FAILS )); } } } } protected void constructFixed( File _torrent_base, long _piece_length ) throws TOTorrentException { setIgnoreList(); setCreationDate( SystemTime.getCurrentTime() / 1000); setCreatedBy( Constants.AZUREUS_NAME + "/" + Constants.AZUREUS_VERSION ); setPieceLength( _piece_length ); report( "Torrent.create.progress.piecelength", _piece_length ); piece_count = calculateNumberOfPieces( _torrent_base,_piece_length ); if ( piece_count == 0 ){ throw( new TOTorrentException( "TOTorrentCreate: specified files have zero total length", TOTorrentException.RT_ZERO_LENGTH )); } report( "Torrent.create.progress.hashing"); for (int i=0;i<progress_listeners.size();i++){ ((TOTorrentProgressListener)progress_listeners.get(i)).reportProgress( 0 ); } boolean add_other_per_file_hashes = add_other_hashes&&!getSimpleTorrent(); file_hasher = new TOTorrentFileHasher( add_other_hashes, add_other_per_file_hashes, (int)_piece_length, progress_listeners.size()==0?null:this ); try{ if ( cancelled ){ throw( new TOTorrentException( "TOTorrentCreate: operation cancelled", TOTorrentException.RT_CANCELLED )); } if ( getSimpleTorrent()){ File link = linkage_map.get( _torrent_base.getName()); if ( link != null ){ linked_tf_map.put( "0", link.getAbsolutePath()); } long length = file_hasher.add( link==null?_torrent_base:link ); setFiles( new TOTorrentFileImpl[]{ new TOTorrentFileImpl( this, 0, 0, length, new byte[][]{ getName()})}); setPieces( file_hasher.getPieces()); }else{ List<TOTorrentFileImpl> encoded = new ArrayList<TOTorrentFileImpl>(); processDir( file_hasher, _torrent_base, encoded, _torrent_base.getName(), "" ); TOTorrentFileImpl[] files = new TOTorrentFileImpl[ encoded.size()]; encoded.toArray( files ); setFiles( files ); } setPieces( file_hasher.getPieces()); if ( add_other_hashes ){ byte[] sha1_digest = file_hasher.getSHA1Digest(); byte[] ed2k_digest = file_hasher.getED2KDigest(); addAdditionalInfoProperty( "sha1", sha1_digest ); addAdditionalInfoProperty( "ed2k", ed2k_digest ); //System.out.println( "overall:sha1 = " + ByteFormatter.nicePrint( sha1_digest, true)); //System.out.println( "overall:ed2k = " + ByteFormatter.nicePrint( ed2k_digest, true)); } }finally{ file_hasher = null; } } protected void processDir( TOTorrentFileHasher hasher, File dir, List<TOTorrentFileImpl> encoded, String base_name, String root ) throws TOTorrentException { File[] dir_file_list = dir.listFiles(); if ( dir_file_list == null ){ throw( new TOTorrentException( "TOTorrentCreate: directory '" + dir.getAbsolutePath() + "' returned error when listing files in it", TOTorrentException.RT_FILE_NOT_FOUND )); } // sort contents so that multiple encodes of a dir always // generate same torrent List<File> file_list = new ArrayList<File>(Arrays.asList(dir_file_list)); if ( file_comparator == null ){ Collections.sort(file_list); }else{ Collections.sort( file_list,file_comparator); } long offset = 0; for (int i=0;i<file_list.size();i++){ File file = (File)file_list.get(i); String file_name = file.getName(); if ( !(file_name.equals( "." ) || file_name.equals( ".." ))){ if ( file.isDirectory()){ if ( root.length() > 0 ){ file_name = root + File.separator + file_name ; } processDir( hasher, file, encoded, base_name, file_name ); }else{ if ( !ignoreFile( file_name )){ if ( root.length() > 0 ){ file_name = root + File.separator + file_name; } File link = linkage_map.get( base_name + File.separator + file_name ); if ( link != null ){ linked_tf_map.put( String.valueOf( encoded.size()), link.getAbsolutePath()); } long length = hasher.add( link==null?file:link ); TOTorrentFileImpl tf = new TOTorrentFileImpl( this, i, offset, length, file_name ); offset += length; if ( add_other_hashes ){ byte[] ed2k_digest = hasher.getPerFileED2KDigest(); byte[] sha1_digest = hasher.getPerFileSHA1Digest(); //System.out.println( "file:ed2k = " + ByteFormatter.nicePrint( ed2k_digest, true )); //System.out.println( "file:sha1 = " + ByteFormatter.nicePrint( sha1_digest, true )); tf.setAdditionalProperty( "sha1", sha1_digest ); tf.setAdditionalProperty( "ed2k", ed2k_digest ); } encoded.add( tf ); } } } } } public void pieceHashed( int piece_number ) { for (int i=0;i<progress_listeners.size();i++){ int this_progress = (int)((piece_number*100)/piece_count ); if ( this_progress != reported_progress ){ reported_progress = this_progress; ((TOTorrentProgressListener)progress_listeners.get(i)).reportProgress( reported_progress ); } } } protected long calculateNumberOfPieces( File _file, long _piece_length ) throws TOTorrentException { long res = getPieceCount(calculateTotalFileSize( _file ), _piece_length ); report( "Torrent.create.progress.piececount", ""+res ); return( res ); } protected long calculateTotalFileSize( File file ) throws TOTorrentException { if ( total_file_size == -1 ){ total_file_size = getTotalFileSize( file ); } return( total_file_size ); } protected long getTotalFileSize( File file ) throws TOTorrentException { report( "Torrent.create.progress.parsingfiles" ); long res = getTotalFileSizeSupport( file, "" ); report( "Torrent.create.progress.totalfilesize", res ); report( "Torrent.create.progress.totalfilecount", ""+total_file_count ); return( res ); } protected long getTotalFileSizeSupport( File file, String root ) throws TOTorrentException { String name = file.getName(); if ( name.equals( "." ) || name.equals( ".." )){ return( 0 ); } if ( !file.exists()){ throw( new TOTorrentException( "TOTorrentCreate: file '" + file.getName() + "' doesn't exist", TOTorrentException.RT_FILE_NOT_FOUND )); } if ( file.isFile()){ if ( !ignoreFile( name )){ total_file_count++; if ( root.length() > 0 ){ name = root + File.separator + name; } File link = linkage_map.get( name ); return( link==null?file.length():link.length()); }else{ return( 0 ); } }else{ File[] dir_files = file.listFiles(); if ( dir_files == null ){ throw( new TOTorrentException( "TOTorrentCreate: directory '" + file.getAbsolutePath() + "' returned error when listing files in it", TOTorrentException.RT_FILE_NOT_FOUND )); } long length = 0; if ( root.length() == 0 ){ root = name; }else{ root = root + File.separator + name; } for (int i=0;i<dir_files.length;i++){ length += getTotalFileSizeSupport( dir_files[i], root ); } return( length ); } } protected void report( String resource_key ) { report( resource_key, null ); } protected void report( String resource_key, long bytes ) { if ( progress_listeners.size() > 0 ){ report( resource_key, DisplayFormatters.formatByteCountToKiBEtc( bytes )); } } protected void report( String resource_key, String additional_text ) { if ( progress_listeners.size() > 0 ){ String prefix = MessageText.getString(resource_key); for (int i=0;i<progress_listeners.size();i++){ ((TOTorrentProgressListener)progress_listeners.get(i)).reportCurrentTask( prefix + (additional_text==null?"":additional_text )); } } } public static long getComputedPieceSize( long total_size, long _piece_min_size, long _piece_max_size, long _piece_num_lower, long _piece_num_upper ) { long piece_length = -1; long current_piece_size = _piece_min_size; while( current_piece_size <= _piece_max_size ){ long pieces = total_size / current_piece_size; if ( pieces <= _piece_num_upper ){ piece_length = current_piece_size; break; } current_piece_size = current_piece_size << 1; } // if we haven't set a piece length here then there are too many pieces even // at maximum piece size. Go for largest piece size if ( piece_length == -1 ){ // just go for the maximum piece size piece_length = _piece_max_size; } return( piece_length ); } public static long getPieceCount( long total_size, long piece_size ) { return( (total_size + (piece_size-1))/piece_size ); } protected void setIgnoreList() { try{ ignore_set = TorrentUtils.getIgnoreSet(); }catch( NoClassDefFoundError e ){ return; } } protected boolean ignoreFile( String file ) { if ( ignore_set.contains(file.toLowerCase())){ report( "Torrent.create.progress.ignoringfile", " '" + file + "'" ); return( true ); } return( false ); } protected void cancel() { if ( !cancelled ){ report( "Torrent.create.progress.cancelled"); cancelled = true; if ( file_hasher != null ){ file_hasher.cancel(); } } } protected void addListener( TOTorrentProgressListener listener ) { progress_listeners.add( listener ); } protected void removeListener( TOTorrentProgressListener listener ) { progress_listeners.remove( listener ); } }