1 /*
2  * File    : TOTorrentImpl.java
3  * Created : 5 Oct. 2003
4  * By      : Parg
5  *
6  * Azureus - a Java Bittorrent client
7  *
8  * This program is free software; you can redistribute it and/or modify
9  * it under the terms of the GNU General Public License as published by
10  * the Free Software Foundation; either version 2 of the License, or
11  * (at your option) any later version.
12  *
13  * This program is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  * GNU General Public License for more details ( see the LICENSE file ).
17  *
18  * You should have received a copy of the GNU General Public License
19  * along with this program; if not, write to the Free Software
20  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
21  */
22 
23 package org.gudy.azureus2.core3.torrent.impl;
24 
25 
26 import java.io.*;
27 import java.net.*;
28 import java.util.*;
29 
30 import org.gudy.azureus2.core3.logging.LogRelation;
31 import org.gudy.azureus2.core3.torrent.*;
32 import org.gudy.azureus2.core3.util.*;
33 
34 import com.aelitis.azureus.core.AzureusCoreFactory;
35 
36 public class
37 TOTorrentImpl
38 	extends LogRelation
39 	implements TOTorrent
40 {
41 	protected static final String TK_ANNOUNCE			= "announce";
42 	protected static final String TK_ANNOUNCE_LIST		= "announce-list";
43 	protected static final String TK_COMMENT			= "comment";
44 	protected static final String TK_CREATION_DATE		= "creation date";
45 	protected static final String TK_CREATED_BY			= "created by";
46 
47 	protected static final String TK_INFO				= "info";
48 	protected static final String TK_NAME				= "name";
49 	protected static final String TK_LENGTH				= "length";
50 	protected static final String TK_PATH				= "path";
51 	protected static final String TK_FILES				= "files";
52 	protected static final String TK_PIECE_LENGTH		= "piece length";
53 	protected static final String TK_PIECES				= "pieces";
54 
55 	protected static final String TK_PRIVATE			= "private";
56 
57 	protected static final String TK_NAME_UTF8			= "name.utf-8";
58 	protected static final String TK_PATH_UTF8			= "path.utf-8";
59 	protected static final String TK_COMMENT_UTF8		= "comment.utf-8";
60 
61 	protected static final String TK_WEBSEED_BT			= "httpseeds";
62 	protected static final String TK_WEBSEED_GR			= "url-list";
63 
64 	protected static final String TK_HASH_OVERRIDE		= "hash-override";
65 
66 	protected static final List	TK_ADDITIONAL_OK_ATTRS =
67 		Arrays.asList(new String[]{ TK_COMMENT_UTF8, AZUREUS_PROPERTIES, TK_WEBSEED_BT, TK_WEBSEED_GR });
68 
69 	private byte[]							torrent_name;
70 	private byte[]							torrent_name_utf8;
71 
72 	private byte[]							comment;
73 	private URL								announce_url;
74 	private TOTorrentAnnounceURLGroupImpl	announce_group = new TOTorrentAnnounceURLGroupImpl(this);
75 
76 	private long		piece_length;
77 	private byte[][]	pieces;
78 	private int			number_of_pieces;
79 
80 	private byte[]		torrent_hash_override;
81 
82 	private byte[]		torrent_hash;
83 	private HashWrapper	torrent_hash_wrapper;
84 
85 	private boolean				simple_torrent;
86 	private TOTorrentFileImpl[]	files;
87 
88 	private long				creation_date;
89 	private byte[]				created_by;
90 
91 	private Map					additional_properties 		= new LightHashMap(4);
92 	private Map					additional_info_properties	= new LightHashMap(4);
93 
94 	private boolean				created;
95 	private boolean				serialising;
96 
97 	private List<TOTorrentListener>	listeners;
98 
99 	protected AEMonitor this_mon 	= new AEMonitor( "TOTorrent" );
100 
101 	/**
102 	 * Constructor for deserialisation
103 	 */
104 
105 	protected
TOTorrentImpl()106 	TOTorrentImpl()
107 	{
108 	}
109 
110 	/**
111 	 * Constructor for creation
112 	 */
113 
114 	protected
TOTorrentImpl( String _torrent_name, URL _announce_url, boolean _simple_torrent )115 	TOTorrentImpl(
116 		String		_torrent_name,
117 		URL			_announce_url,
118 		boolean		_simple_torrent )
119 
120 		throws TOTorrentException
121 	{
122 		created	= true;
123 
124 		try{
125 
126 			torrent_name		= _torrent_name.getBytes( Constants.DEFAULT_ENCODING );
127 
128 			torrent_name_utf8	= torrent_name;
129 
130 			setAnnounceURL( _announce_url );
131 
132 			simple_torrent		= _simple_torrent;
133 
134 		}catch( UnsupportedEncodingException e ){
135 
136 			throw( new TOTorrentException( 	"Unsupported encoding for '" + _torrent_name + "'",
137 											TOTorrentException.RT_UNSUPPORTED_ENCODING));
138 		}
139 	}
140 
141 	public void
serialiseToBEncodedFile( final File output_file )142 	serialiseToBEncodedFile(
143 		final File		output_file )
144 
145 		throws TOTorrentException
146 	{
147 			// we have to defer marking as created until some kind of persisting occurs as we don't know that the info-hash is "permanent" until#
148 			// this point (external code can set info-hash internal properties between create + complete )
149 
150 		if ( created ){
151 
152 			TorrentUtils.addCreatedTorrent( this );
153 		}
154 
155 		byte[]	res = serialiseToByteArray();
156 
157         BufferedOutputStream bos = null;
158 
159 		try{
160 			File parent = output_file.getParentFile();
161 			if (parent == null) {
162 				throw new TOTorrentException( "Path '" + output_file + "' is invalid", TOTorrentException.RT_WRITE_FAILS);
163 			}
164 
165 			// We would expect this to be normally true most of the time.
166 			if (!parent.isDirectory()) {
167 
168 				// Try to create a directory.
169 				boolean dir_created = FileUtil.mkdirs(parent);
170 
171 				// Something strange going on...
172 				if (!dir_created) {
173 
174 					// Does it exist already?
175 					if (parent.exists()) {
176 
177 						// And it really isn't a directory?
178 						if (!parent.isDirectory()) {
179 
180 							// How strange.
181 							throw new TOTorrentException( "Path '" + output_file + "' is invalid", TOTorrentException.RT_WRITE_FAILS);
182 
183 						}
184 
185 						// It is a directory which does exist. But we tested for that earlier. Perhaps it has been created in the
186 						// meantime.
187 						else {
188 							/* do nothing */
189 						}
190 					}
191 
192 					// It doesn't exist, and we couldn't create it.
193 					else {
194 						throw new TOTorrentException( "Failed to create directory '" + parent + "'", TOTorrentException.RT_WRITE_FAILS );
195 					}
196 				} // end if (!dir_created)
197 
198 			} // end if (!parent.isDirectory)
199 
200 
201 			File temp = new File( parent, output_file.getName() + ".saving");
202 
203 			if ( temp.exists()){
204 
205 				if ( !temp.delete()){
206 
207 					throw( new TOTorrentException( "Insufficient permissions to delete '" + temp + "'", TOTorrentException.RT_WRITE_FAILS ));
208 				}
209 			}else{
210 
211 				boolean	ok = false;
212 
213 				try{
214 					ok = temp.createNewFile();
215 
216 				}catch( Throwable e ){
217 				}
218 
219 				if ( !ok ){
220 
221 					throw( new TOTorrentException( "Insufficient permissions to write '" + temp + "'", TOTorrentException.RT_WRITE_FAILS ));
222 
223 				}
224 			}
225 
226             FileOutputStream fos = new FileOutputStream( temp, false );
227 
228             bos = new BufferedOutputStream( fos, 8192 );
229 
230             bos.write( res );
231 
232             bos.flush();
233 
234 		  		// thinking about removing this - just do so for CVS for the moment
235 
236 			if ( !Constants.isCVSVersion()){
237 
238 				fos.getFD().sync();
239 			}
240 
241             bos.close();
242 
243             bos = null;
244 
245               //only use newly saved file if it got this far, i.e. it was written successfully
246 
247             if ( temp.length() > 1L ) {
248             	output_file.delete(); // Will fail silently if it doesn't exist.
249                 temp.renameTo( output_file );
250             }
251 
252 		}catch( TOTorrentException e ){
253 
254 			throw( e );
255 
256 		}catch( Throwable e){
257 
258 			throw( new TOTorrentException( 	"Failed to serialise torrent: " + Debug.getNestedExceptionMessage(e),
259 											TOTorrentException.RT_WRITE_FAILS ));
260 
261 		}finally{
262 
263 			if ( bos != null ){
264 
265 				try{
266 					bos.close();
267 
268 				}catch( IOException e ){
269 
270 					Debug.printStackTrace( e );
271 				}
272 			}
273 		}
274 	}
275 
276 	protected byte[]
serialiseToByteArray()277 	serialiseToByteArray()
278 
279 		throws TOTorrentException
280 	{
281 		if ( created ){
282 
283 			TorrentUtils.addCreatedTorrent( this );
284 		}
285 
286 		Map	root = serialiseToMap();
287 
288 		try{
289 			return( BEncoder.encode( root ));
290 
291 		}catch( IOException e ){
292 
293 			throw( 	new TOTorrentException(
294 							"Failed to serialise torrent: " + Debug.getNestedExceptionMessage(e),
295 							TOTorrentException.RT_WRITE_FAILS ));
296 
297 		}
298 	}
299 
300 	public Map
serialiseToMap()301 	serialiseToMap()
302 
303 		throws TOTorrentException
304 	{
305 			// protect against recursion when getting the hash
306 
307 		if ( created && !serialising ){
308 
309 			try{
310 				serialising	= true;	// not thread safe but we can live without the hassle of using TLS or whatever
311 
312 				TorrentUtils.addCreatedTorrent( this );
313 
314 			}finally{
315 
316 				serialising = false;
317 			}
318 		}
319 
320 		Map	root = new HashMap();
321 
322 			// seen a NPE here, not sure of cause so handling null announce_url in case
323 
324 		writeStringToMetaData( root, TK_ANNOUNCE, (announce_url==null?TorrentUtils.getDecentralisedEmptyURL():announce_url).toString());
325 
326 		TOTorrentAnnounceURLSet[] sets = announce_group.getAnnounceURLSets();
327 
328 		if (sets.length > 0 ){
329 
330 			List	announce_list = new ArrayList();
331 
332 			for (int i=0;i<sets.length;i++){
333 
334 				TOTorrentAnnounceURLSet	set = sets[i];
335 
336 				URL[]	urls = set.getAnnounceURLs();
337 
338 				if ( urls.length == 0 ){
339 
340 					continue;
341 				}
342 
343 				List sub_list = new ArrayList();
344 
345 				announce_list.add( sub_list );
346 
347 				for (int j=0;j<urls.length;j++){
348 
349 					sub_list.add( writeStringToMetaData( urls[j].toString()));
350 				}
351 			}
352 
353 			if ( announce_list.size() > 0 ){
354 
355 				root.put( TK_ANNOUNCE_LIST, announce_list );
356 			}
357 		}
358 
359 		if ( comment != null ){
360 
361 			root.put( TK_COMMENT, comment );
362 		}
363 
364 		if ( creation_date != 0 ){
365 
366 			root.put( TK_CREATION_DATE, new Long( creation_date ));
367 		}
368 
369 		if ( created_by != null ){
370 
371 			root.put( TK_CREATED_BY, created_by );
372 		}
373 
374 		Map info = new HashMap();
375 
376 		root.put( TK_INFO, info );
377 
378 		info.put( TK_PIECE_LENGTH, new Long( piece_length ));
379 
380 		if ( pieces == null ){
381 
382 			throw( new TOTorrentException( "Pieces is null", TOTorrentException.RT_WRITE_FAILS ));
383 		}
384 
385 		byte[]	flat_pieces = new byte[pieces.length*20];
386 
387 		for (int i=0;i<pieces.length;i++){
388 
389 			System.arraycopy( pieces[i], 0, flat_pieces, i*20, 20 );
390 		}
391 
392 		info.put( TK_PIECES, flat_pieces );
393 
394 		info.put( TK_NAME, torrent_name );
395 
396 		if ( torrent_name_utf8 != null ){
397 
398 			info.put( TK_NAME_UTF8, torrent_name_utf8 );
399 		}
400 
401 		if ( torrent_hash_override != null ){
402 
403 			info.put( TK_HASH_OVERRIDE, torrent_hash_override );
404 		}
405 
406 		if ( simple_torrent ){
407 
408 			TOTorrentFile	file = files[0];
409 
410 			info.put( TK_LENGTH, new Long( file.getLength()));
411 
412 		}else{
413 
414 			List	meta_files = new ArrayList();
415 
416 			info.put( TK_FILES, meta_files );
417 
418 			for (int i=0;i<files.length;i++){
419 
420 				TOTorrentFileImpl	file	= files[i];
421 
422 				Map	file_map = file.serializeToMap();
423 
424 				meta_files.add( file_map );
425 
426 			}
427 		}
428 
429 		Iterator info_it = additional_info_properties.keySet().iterator();
430 
431 		while( info_it.hasNext()){
432 
433 			String	key = (String)info_it.next();
434 
435 			info.put( key, additional_info_properties.get( key ));
436 		}
437 
438 		Iterator it = additional_properties.keySet().iterator();
439 
440 		while( it.hasNext()){
441 
442 			String	key = (String)it.next();
443 
444 			Object	value = additional_properties.get( key );
445 
446 			if ( value != null ){
447 
448 				root.put( key, value );
449 			}
450 		}
451 
452 		return( root );
453 	}
454 
455 	public void
serialiseToXMLFile( File file )456 	serialiseToXMLFile(
457 	  File		file )
458 
459 	  throws TOTorrentException
460 	{
461 		if ( created ){
462 
463 			TorrentUtils.addCreatedTorrent( this );
464 		}
465 
466 		TOTorrentXMLSerialiser	serialiser = new TOTorrentXMLSerialiser( this );
467 
468 		serialiser.serialiseToFile( file );
469 	}
470 
471 	public byte[]
getName()472 	getName()
473 	{
474 		return( torrent_name );
475 	}
476 
477 	protected void
setName( byte[] _name )478 	setName(
479 		byte[]	_name )
480 	{
481 		torrent_name	= _name;
482 	}
483 
484 	public String
getUTF8Name()485 	getUTF8Name()
486 	{
487 		try {
488 			return torrent_name_utf8 == null ? null : new String(torrent_name_utf8,
489 					"utf8");
490 		} catch (UnsupportedEncodingException e) {
491 			return null;
492 		}
493 	}
494 
495 	protected void
setNameUTF8( byte[] _name )496 	setNameUTF8(
497 		byte[]	_name )
498 	{
499 		torrent_name_utf8	= _name;
500 	}
501 
502 	public boolean
isSimpleTorrent()503 	isSimpleTorrent()
504 	{
505 		return( simple_torrent );
506 	}
507 
508 	public byte[]
getComment()509 	getComment()
510 	{
511 		return( comment );
512 	}
513 
514 	protected void
setComment( byte[] _comment )515 	setComment(
516 		byte[]		_comment )
517 
518 	{
519 		comment = _comment;
520 	}
521 
522 	public void
setComment( String _comment )523 	setComment(
524 		String	_comment )
525 	{
526 		try{
527 
528 			byte[]	utf8_comment = _comment.getBytes( Constants.DEFAULT_ENCODING );
529 
530 			setComment( utf8_comment );
531 
532 			setAdditionalByteArrayProperty( TK_COMMENT_UTF8, utf8_comment );
533 
534 		}catch( UnsupportedEncodingException e ){
535 
536 			Debug.printStackTrace( e );
537 
538 			comment = null;
539 		}
540 	}
541 
542 	public URL
getAnnounceURL()543 	getAnnounceURL()
544 	{
545 		return( announce_url );
546 	}
547 
548 	public boolean
setAnnounceURL( URL url )549 	setAnnounceURL(
550 		URL		url )
551 	{
552 		URL newURL = anonymityTransform( url );
553 		String s0 = (newURL == null) ? "" : newURL.toString();
554 		String s1 = (announce_url == null) ? "" : announce_url.toString();
555 		if (s0.equals(s1))
556 			return false;
557 
558 		if ( newURL == null ){
559 
560 				// anything's better than null...
561 
562 			newURL = TorrentUtils.getDecentralisedEmptyURL();
563 		}
564 
565 		announce_url	= StringInterner.internURL(newURL);
566 
567 		fireChanged( TOTorrentListener.CT_ANNOUNCE_URLS );
568 
569 		return true;
570 	}
571 
572 	public boolean
isDecentralised()573 	isDecentralised()
574 	{
575 		return( TorrentUtils.isDecentralised( getAnnounceURL()));
576 	}
577 
578 	public long
getCreationDate()579 	getCreationDate()
580 	{
581 		return( creation_date );
582 	}
583 
584 	public void
setCreationDate( long _creation_date )585 	setCreationDate(
586 		long		_creation_date )
587 	{
588 			// supposed to be in seconds, not millis. Some torrents have millis so try and
589 			// fix it
590 
591 		long	now_secs = SystemTime.getCurrentTime()/1000;
592 
593 		if ( _creation_date > now_secs + 100*365*24*60*60L){
594 
595 			_creation_date = _creation_date/1000;
596 		}
597 
598 		creation_date 	= _creation_date;
599 	}
600 
601 	public void
setCreatedBy( byte[] _created_by )602 	setCreatedBy(
603 		byte[]		_created_by )
604 	{
605 		created_by	= _created_by;
606 	}
607 
608 	protected void
setCreatedBy( String _created_by )609 	setCreatedBy(
610 		String		_created_by )
611 	{
612 		try{
613 
614 			setCreatedBy( _created_by.getBytes( Constants.DEFAULT_ENCODING ));
615 
616 		}catch( UnsupportedEncodingException e ){
617 
618 			Debug.printStackTrace( e );
619 
620 			created_by = null;
621 		}
622 	}
623 
624 	public byte[]
getCreatedBy()625 	getCreatedBy()
626 	{
627 		return( created_by );
628 	}
629 
630 	public boolean
isCreated()631 	isCreated()
632 	{
633 		return( created );
634 	}
635 
636 	public byte[]
getHash()637 	getHash()
638 
639 		throws TOTorrentException
640 	{
641 		if ( torrent_hash == null ){
642 
643 			Map	root = serialiseToMap();
644 
645 			Map info = (Map)root.get( TK_INFO );
646 
647 			setHashFromInfo( info );
648 		}
649 
650 		return( torrent_hash );
651 	}
652 
653 	public HashWrapper
getHashWrapper()654 	getHashWrapper()
655 
656 		throws TOTorrentException
657 	{
658 		if ( torrent_hash_wrapper == null ){
659 
660 			getHash();
661 		}
662 
663 		return( torrent_hash_wrapper );
664 	}
665 
666 	public boolean
hasSameHashAs( TOTorrent other )667 	hasSameHashAs(
668 		TOTorrent		other )
669 	{
670 		try{
671 			byte[]	other_hash = other.getHash();
672 
673 			return( Arrays.equals( getHash(), other_hash ));
674 
675 		}catch( TOTorrentException e ){
676 
677 			Debug.printStackTrace( e );
678 
679 			return( false );
680 		}
681 	}
682 
683 	protected void
setHashFromInfo( Map info )684 	setHashFromInfo(
685 		Map		info )
686 
687 		throws TOTorrentException
688 	{
689 		try{
690 			if ( torrent_hash_override == null ){
691 
692 				SHA1Hasher s = new SHA1Hasher();
693 
694 				torrent_hash = s.calculateHash(BEncoder.encode(info));
695 
696 			}else{
697 
698 				torrent_hash = torrent_hash_override;
699 			}
700 
701 			torrent_hash_wrapper = new HashWrapper( torrent_hash );
702 
703 		}catch( Throwable e ){
704 
705 			throw( new TOTorrentException( 	"Failed to calculate hash: " + Debug.getNestedExceptionMessage(e),
706 											TOTorrentException.RT_HASH_FAILS ));
707 		}
708 	}
709 
710 	public void
setHashOverride( byte[] hash )711 	setHashOverride(
712 		byte[] 	hash )
713 
714 		throws TOTorrentException
715 	{
716 		if ( torrent_hash_override != null ){
717 
718 			if ( Arrays.equals( hash, torrent_hash_override )){
719 
720 				return;
721 
722 			}else{
723 
724 				throw( new TOTorrentException( 	"Hash override can only be set once",
725 								TOTorrentException.RT_HASH_FAILS ));
726 			}
727 		}
728 
729 		/* support this for fixing borked torrents
730 		if ( !TorrentUtils.isDecentralised( announce_url )){
731 
732 			throw( new TOTorrentException(
733 						"Hash override can only be set on decentralised torrents",
734 						TOTorrentException.RT_HASH_FAILS ));
735 		}
736 		*/
737 
738 		torrent_hash_override = hash;
739 
740 		torrent_hash	= null;
741 
742 		getHash();
743 	}
744 
745 	protected byte[]
getHashOverride()746 	getHashOverride()
747 	{
748 		return( torrent_hash_override );
749 	}
750 
751 	public void
setPrivate( boolean _private_torrent )752 	setPrivate(
753 		boolean	_private_torrent )
754 
755 		throws TOTorrentException
756 	{
757 		additional_info_properties.put( TK_PRIVATE, new Long(_private_torrent?1:0));
758 
759 			// update torrent hash
760 
761 		torrent_hash	= null;
762 
763 		getHash();
764 	}
765 
766 	public boolean
getPrivate()767 	getPrivate()
768 	{
769 		Object o = additional_info_properties.get( TK_PRIVATE );
770 
771 		if ( o instanceof Long ){
772 
773 			return(((Long)o).intValue() != 0 );
774 		}
775 
776 		return( false );
777 	}
778 
779 	public TOTorrentAnnounceURLGroup
getAnnounceURLGroup()780 	getAnnounceURLGroup()
781 	{
782 		return( announce_group );
783 	}
784 
785 	protected void
addTorrentAnnounceURLSet( URL[] urls )786 	addTorrentAnnounceURLSet(
787 		URL[]		urls )
788 	{
789 		announce_group.addSet( new TOTorrentAnnounceURLSetImpl( this, urls ));
790 	}
791 
792 	public long
getSize()793 	getSize()
794 	{
795 		long	res = 0;
796 
797 		for (int i=0;i<files.length;i++){
798 
799 			res += files[i].getLength();
800 		}
801 
802 		return( res );
803 	}
804 
805 	public long
getPieceLength()806 	getPieceLength()
807 	{
808 		return( piece_length );
809 	}
810 
811 	protected void
setPieceLength( long _length )812 	setPieceLength(
813 		long	_length )
814 	{
815 		piece_length	= _length;
816 	}
817 
818 	public int
getNumberOfPieces()819 	getNumberOfPieces()
820 	{
821 			// to support buggy torrents with extraneous pieces (they seem to exist) we calculate
822 			// the required number of pieces rather than the using the actual. Note that we
823 			// can't adjust the pieces array itself as this results in incorrect torrent hashes
824 			// being derived later after a save + restore
825 
826 		if ( number_of_pieces == 0 ){
827 
828 			number_of_pieces = (int)((getSize() + (piece_length-1)) / piece_length );
829 		}
830 
831 		return( number_of_pieces );
832 	}
833 
834 	public byte[][]
getPieces()835 	getPieces()
836 	{
837 		return( pieces );
838 	}
839 
840 	public void
setPieces( byte[][] _pieces )841 	setPieces(
842 		byte[][]	_pieces )
843 	{
844 		pieces = _pieces;
845 	}
846 
847 	public int
getFileCount()848 	getFileCount()
849 	{
850 		return( files.length );
851 	}
852 
853 	public TOTorrentFile[]
getFiles()854 	getFiles()
855 	{
856 		return( files );
857 	}
858 
859 	protected void
setFiles( TOTorrentFileImpl[] _files )860 	setFiles(
861 		TOTorrentFileImpl[]		_files )
862 	{
863 		files	= _files;
864 	}
865 
866 	protected boolean
getSimpleTorrent()867 	getSimpleTorrent()
868 	{
869 		return( simple_torrent );
870 	}
871 
872 	protected void
setSimpleTorrent( boolean _simple_torrent )873 	setSimpleTorrent(
874 		boolean	_simple_torrent )
875 	{
876 		simple_torrent	= _simple_torrent;
877 	}
878 
879 	protected Map
getAdditionalProperties()880 	getAdditionalProperties()
881 	{
882 		return( additional_properties );
883 	}
884 
885 	public void
setAdditionalStringProperty( String name, String value )886 	setAdditionalStringProperty(
887 		String		name,
888 		String		value )
889 	{
890 		try{
891 
892 			setAdditionalByteArrayProperty( name, writeStringToMetaData( value ));
893 
894 		}catch( TOTorrentException e ){
895 
896 				// hide encoding exceptions as default encoding must be available
897 
898 			Debug.printStackTrace( e );
899 		}
900 	}
901 
902 	public String
getAdditionalStringProperty( String name )903 	getAdditionalStringProperty(
904 		String		name )
905 	{
906 		try{
907 
908 			return( readStringFromMetaData( getAdditionalByteArrayProperty(name)));
909 
910 		}catch( TOTorrentException e ){
911 
912 				// hide encoding exceptions as default encoding must be available
913 
914 			Debug.printStackTrace( e );
915 
916 			return( null );
917 		}
918 	}
919 
920 	public void
setAdditionalByteArrayProperty( String name, byte[] value )921 	setAdditionalByteArrayProperty(
922 		String		name,
923 		byte[]		value )
924 	{
925 		additional_properties.put( name, value );
926 	}
927 
928 	public void
setAdditionalProperty( String name, Object value )929 	setAdditionalProperty(
930 		String		name,
931 		Object		value )
932 	{
933 		if ( value instanceof String ){
934 
935 			setAdditionalStringProperty(name,(String)value);
936 
937 		}else{
938 
939 			additional_properties.put( name, value );
940 		}
941 	}
942 
943 	public byte[]
getAdditionalByteArrayProperty( String name )944 	getAdditionalByteArrayProperty(
945 		String		name )
946 	{
947 		Object	obj = additional_properties.get( name );
948 
949 		if ( obj instanceof byte[] ){
950 
951 			return((byte[])obj);
952 		}
953 
954 		return( null );
955 	}
956 
957 	public void
setAdditionalLongProperty( String name, Long value )958 	setAdditionalLongProperty(
959 		String		name,
960 		Long		value )
961 	{
962 		additional_properties.put( name, value );
963 	}
964 
965 	public Long
getAdditionalLongProperty( String name )966 	getAdditionalLongProperty(
967 		String		name )
968 	{
969 		Object	obj = additional_properties.get( name );
970 
971 		if ( obj instanceof Long ){
972 
973 			return((Long)obj);
974 		}
975 
976 		return( null );
977 	}
978 
979 	public void
setAdditionalListProperty( String name, List value )980 	setAdditionalListProperty(
981 		String		name,
982 		List		value )
983 	{
984 		additional_properties.put( name, value );
985 	}
986 
987 	public List
getAdditionalListProperty( String name )988 	getAdditionalListProperty(
989 		String		name )
990 	{
991 		Object	obj = additional_properties.get( name );
992 
993 		if ( obj instanceof List ){
994 
995 			return((List)obj);
996 		}
997 
998 		return( null );
999 	}
1000 
1001 	public void
setAdditionalMapProperty( String name, Map value )1002 	setAdditionalMapProperty(
1003 		String		name,
1004 		Map 		value )
1005 	{
1006 		additional_properties.put( name, value );
1007 	}
1008 
1009 	public Map
getAdditionalMapProperty( String name )1010 	getAdditionalMapProperty(
1011 		String		name )
1012 	{
1013 		Object	obj = additional_properties.get( name );
1014 
1015 		if ( obj instanceof Map ){
1016 
1017 			return((Map)obj);
1018 		}
1019 
1020 		return( null );
1021 	}
1022 
1023 	public Object
getAdditionalProperty( String name )1024 	getAdditionalProperty(
1025 		String		name )
1026 	{
1027 		return(additional_properties.get( name ));
1028 	}
1029 
1030 	public void
removeAdditionalProperty( String name )1031 	removeAdditionalProperty(
1032 		String name )
1033 	{
1034 		additional_properties.remove( name );
1035 	}
1036 
1037 	public void
removeAdditionalProperties()1038 	removeAdditionalProperties()
1039 	{
1040 		Map	new_props = new HashMap();
1041 
1042 		Iterator it = additional_properties.keySet().iterator();
1043 
1044 		while( it.hasNext()){
1045 
1046 			String	key = (String)it.next();
1047 
1048 			if ( TK_ADDITIONAL_OK_ATTRS.contains(key)){
1049 
1050 				new_props.put( key, additional_properties.get( key ));
1051 			}
1052 		}
1053 
1054 		additional_properties = new_props;
1055 	}
1056 
1057 	protected void
addAdditionalProperty( String name, Object value )1058 	addAdditionalProperty(
1059 		String			name,
1060 		Object			value )
1061 	{
1062 		additional_properties.put( name, value );
1063 	}
1064 
1065 	protected void
addAdditionalInfoProperty( String name, Object value )1066 	addAdditionalInfoProperty(
1067 		String			name,
1068 		Object			value )
1069 	{
1070 		additional_info_properties.put( name, value );
1071 	}
1072 
1073 	protected Map
getAdditionalInfoProperties()1074 	getAdditionalInfoProperties()
1075 	{
1076 		return( additional_info_properties );
1077 	}
1078 
1079 	protected String
readStringFromMetaData( Map meta_data, String name )1080 	readStringFromMetaData(
1081 		Map		meta_data,
1082 		String	name )
1083 
1084 		throws TOTorrentException
1085 	{
1086 		Object	obj = meta_data.get(name);
1087 
1088 		if ( obj instanceof byte[]){
1089 
1090 			return(readStringFromMetaData((byte[])obj));
1091 		}
1092 
1093 		return( null );
1094 	}
1095 
1096 	protected String
readStringFromMetaData( byte[] value )1097 	readStringFromMetaData(
1098 		byte[]		value )
1099 
1100 		throws TOTorrentException
1101 	{
1102 		try{
1103 			if ( value == null ){
1104 
1105 				return( null );
1106 			}
1107 
1108 			return(	new String(value, Constants.DEFAULT_ENCODING ));
1109 
1110 		}catch( UnsupportedEncodingException e ){
1111 
1112 			throw( new TOTorrentException( 	"Unsupported encoding for '" + value + "'",
1113 											TOTorrentException.RT_UNSUPPORTED_ENCODING));
1114 		}
1115 	}
1116 
1117 	protected void
writeStringToMetaData( Map meta_data, String name, String value )1118 	writeStringToMetaData(
1119 		Map		meta_data,
1120 		String	name,
1121 		String	value )
1122 
1123 		throws TOTorrentException
1124 	{
1125 		meta_data.put( name, writeStringToMetaData( value ));
1126 	}
1127 
1128 	protected byte[]
writeStringToMetaData( String value )1129 	writeStringToMetaData(
1130 		String		value )
1131 
1132 		throws TOTorrentException
1133 	{
1134 		try{
1135 
1136 			return(	value.getBytes( Constants.DEFAULT_ENCODING ));
1137 
1138 		}catch( UnsupportedEncodingException e ){
1139 
1140 			throw( new TOTorrentException( 	"Unsupported encoding for '" + value + "'",
1141 											TOTorrentException.RT_UNSUPPORTED_ENCODING));
1142 		}
1143 	}
1144 
1145 	protected URL
anonymityTransform( URL url )1146 	anonymityTransform(
1147 		URL		url )
1148 	{
1149 		/*
1150 		 * 	hmm, doing this is harder than it looks as we have issues hosting
1151 		 *  (both starting tracker instances and also short-cut loopback for seeding
1152 		 *  leave as is for the moment
1153 		if ( HostNameToIPResolver.isNonDNSName( url.getHost())){
1154 
1155 			// remove the port as it is uninteresting and could leak information about the
1156 			// tracker
1157 
1158 			String	url_string = url.toString();
1159 
1160 			String	port_string = ":" + (url.getPort()==-1?url.getDefaultPort():url.getPort());
1161 
1162 			int	port_pos = url_string.indexOf( ":" + url.getPort());
1163 
1164 			if ( port_pos != -1 ){
1165 
1166 				try{
1167 
1168 					return( new URL( url_string.substring(0,port_pos) + url_string.substring(port_pos+port_string.length())));
1169 
1170 				}catch( MalformedURLException e){
1171 
1172 					Debug.printStackTrace(e);
1173 				}
1174 			}
1175 		}
1176 		*/
1177 
1178 		return( url );
1179 	}
1180 
1181 	public void
print()1182 	print()
1183 	{
1184 		try{
1185 			byte[]	hash = getHash();
1186 
1187 			System.out.println( "name = " + torrent_name );
1188 			System.out.println( "announce url = " + announce_url );
1189 			System.out.println( "announce group = " + announce_group.getAnnounceURLSets().length );
1190 			System.out.println( "creation date = " + creation_date );
1191 			System.out.println( "creation by = " + created_by );
1192 			System.out.println( "comment = " + comment );
1193 			System.out.println( "hash = " + ByteFormatter.nicePrint( hash ));
1194 			System.out.println( "piece length = " + getPieceLength() );
1195 			System.out.println( "pieces = " + getNumberOfPieces() );
1196 
1197 			Iterator info_it = additional_info_properties.keySet().iterator();
1198 
1199 			while( info_it.hasNext()){
1200 
1201 				String	key = (String)info_it.next();
1202 				Object	value = additional_info_properties.get( key );
1203 
1204 				try{
1205 
1206 					System.out.println( "info prop '" + key + "' = '" +
1207 										( value instanceof byte[]?new String((byte[])value, Constants.DEFAULT_ENCODING):value.toString()) + "'" );
1208 				}catch( UnsupportedEncodingException e){
1209 
1210 					System.out.println( "info prop '" + key + "' = unsupported encoding!!!!");
1211 				}
1212 			}
1213 
1214 			Iterator it = additional_properties.keySet().iterator();
1215 
1216 			while( it.hasNext()){
1217 
1218 				String	key = (String)it.next();
1219 				Object	value = additional_properties.get( key );
1220 
1221 				try{
1222 
1223 					System.out.println( "prop '" + key + "' = '" +
1224 										( value instanceof byte[]?new String((byte[])value, Constants.DEFAULT_ENCODING):value.toString()) + "'" );
1225 				}catch( UnsupportedEncodingException e){
1226 
1227 					System.out.println( "prop '" + key + "' = unsupported encoding!!!!");
1228 				}
1229 			}
1230 
1231 			if ( pieces == null ){
1232 
1233 				System.out.println( "\tpieces = null" );
1234 
1235 			}else{
1236 				for (int i=0;i<pieces.length;i++){
1237 
1238 					System.out.println( "\t" + ByteFormatter.nicePrint(pieces[i]));
1239 				}
1240 			}
1241 
1242 			for (int i=0;i<files.length;i++){
1243 
1244 				byte[][]path_comps = files[i].getPathComponents();
1245 
1246 				String	path_str = "";
1247 
1248 				for (int j=0;j<path_comps.length;j++){
1249 
1250 					try{
1251 
1252 						path_str += (j==0?"":File.separator) + new String( path_comps[j], Constants.DEFAULT_ENCODING );
1253 
1254 					}catch( UnsupportedEncodingException e ){
1255 
1256 						System.out.println( "file - unsupported encoding!!!!");
1257 					}
1258 				}
1259 
1260 				System.out.println( "\t" + path_str + " (" + files[i].getLength() + ")" );
1261 			}
1262 		}catch( TOTorrentException e ){
1263 
1264 			Debug.printStackTrace( e );
1265 		}
1266 	}
1267 
1268 	protected void
fireChanged( int type )1269 	fireChanged(
1270 		int	type )
1271 	{
1272 		List<TOTorrentListener> to_fire = null;
1273 
1274 		try{
1275 			this_mon.enter();
1276 
1277 			if ( listeners != null ){
1278 
1279 				to_fire = new ArrayList<TOTorrentListener>( listeners );
1280 			}
1281 		}finally{
1282 
1283 			this_mon.exit();
1284 		}
1285 
1286 		if ( to_fire != null ){
1287 
1288 			for ( TOTorrentListener l: to_fire ){
1289 
1290 				try{
1291 					l.torrentChanged( this, type );
1292 
1293 				}catch( Throwable e ){
1294 
1295 					Debug.out(e);
1296 				}
1297 			}
1298 		}
1299 	}
1300 
1301  	public void
addListener( TOTorrentListener l )1302 	addListener(
1303 		TOTorrentListener		l )
1304 	{
1305  		try{
1306 			this_mon.enter();
1307 
1308 			if ( listeners == null ){
1309 
1310 				listeners = new ArrayList<TOTorrentListener>();
1311 			}
1312 
1313 			listeners.add( l );
1314 
1315 		}finally{
1316 
1317 			this_mon.exit();
1318 		}
1319 	}
1320 
1321 	public void
removeListener( TOTorrentListener l )1322 	removeListener(
1323 		TOTorrentListener		l )
1324 	{
1325  		try{
1326 			this_mon.enter();
1327 
1328 			if ( listeners != null ){
1329 
1330 				listeners.remove( l );
1331 
1332 				if ( listeners.size() == 0 ){
1333 
1334 					listeners = null;
1335 				}
1336 			}
1337 		}finally{
1338 
1339 			this_mon.exit();
1340 		}
1341 	}
1342 
1343 	public AEMonitor
getMonitor()1344 	getMonitor()
1345 	{
1346 		return( this_mon );
1347 	}
1348 
1349 	/* (non-Javadoc)
1350 	 * @see org.gudy.azureus2.core3.logging.LogRelation#getLogRelationText()
1351 	 */
getRelationText()1352 	public String getRelationText() {
1353 		return "Torrent: '" + new String(torrent_name) + "'";
1354 	}
1355 
1356 	/* (non-Javadoc)
1357 	 * @see org.gudy.azureus2.core3.logging.LogRelation#queryForClass(java.lang.Class)
1358 	 */
getQueryableInterfaces()1359 	public Object[] getQueryableInterfaces() {
1360 		// yuck
1361 		try {
1362 			return new Object[] { AzureusCoreFactory.getSingleton()
1363 					.getGlobalManager().getDownloadManager(this) };
1364 		} catch (Exception e) {
1365 		}
1366 
1367 		return null;
1368 	}
1369 }