1 /*
2  * File    : TorrentUtils.java
3  * Created : 13-Oct-2003
4  * By      : stuff
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.util;
24 
25 /**
26  * @author parg
27  *
28  */
29 
30 import java.io.*;
31 import java.net.*;
32 import java.util.*;
33 import java.util.concurrent.atomic.AtomicLong;
34 import java.util.regex.Matcher;
35 import java.util.regex.Pattern;
36 import java.util.zip.GZIPInputStream;
37 
38 import com.aelitis.azureus.core.*;
39 import com.aelitis.azureus.core.proxy.AEProxyFactory;
40 import com.aelitis.azureus.core.proxy.AEProxyFactory.PluginProxy;
41 import com.aelitis.azureus.core.util.CopyOnWriteList;
42 import com.aelitis.azureus.core.util.DNSUtils;
43 
44 import org.gudy.azureus2.core3.config.COConfigurationManager;
45 import org.gudy.azureus2.core3.config.ParameterListener;
46 import org.gudy.azureus2.core3.internat.*;
47 import org.gudy.azureus2.core3.logging.LogRelation;
48 import org.gudy.azureus2.core3.torrent.*;
49 import org.gudy.azureus2.core3.disk.*;
50 import org.gudy.azureus2.core3.download.*;
51 import org.gudy.azureus2.plugins.utils.resourcedownloader.ResourceDownloader;
52 import org.gudy.azureus2.pluginsimpl.local.utils.resourcedownloader.ResourceDownloaderFactoryImpl;
53 
54 
55 public class
56 TorrentUtils
57 {
58 	public static final long MAX_TORRENT_FILE_SIZE = 64*1024*1024L;
59 
60 	private static final String NO_VALID_URL_URL = "http://no.valid.urls.defined/announce";
61 
62 	static{
AEDiagnostics.addEvidenceGenerator( new AEDiagnosticsEvidenceGenerator() { public void generate( IndentWriter writer) { writer.println( R ); try{ writer.indent(); Set<String> names = COConfigurationManager.getDefinedParameters(); String prefix = R; for ( String name: names ){ if ( name.startsWith( prefix )){ try{ String tracker = new String( Base32.decode( name.substring( prefix.length())), R ); String str = R; List<byte[]> txts = (List<byte[]>)COConfigurationManager.getListParameter( name, null ); if ( txts != null ){ for ( byte[] txt: txts ){ str += (str.length()==0?R:R) + new String( txt, R ); } } writer.println( tracker + R + str + R ); }catch( Throwable e ){ Debug.out( e ); } } } }finally{ writer.exdent(); } } })63 		AEDiagnostics.addEvidenceGenerator(
64 			new AEDiagnosticsEvidenceGenerator()
65 			{
66 				public void
67 				generate(
68 					IndentWriter writer)
69 				{
70 					writer.println( "DNS TXT Records" );
71 
72 					try{
73 						writer.indent();
74 
75 						Set<String> names = COConfigurationManager.getDefinedParameters();
76 
77 						String prefix = "dns.txts.cache.";
78 
79 						for ( String name: names ){
80 
81 							if ( name.startsWith( prefix )){
82 
83 								try{
84 									String tracker = new String( Base32.decode( name.substring( prefix.length())), "UTF-8" );
85 
86 									String str = "";
87 
88 									List<byte[]> txts = (List<byte[]>)COConfigurationManager.getListParameter( name, null );
89 
90 									if ( txts != null ){
91 
92 										for ( byte[] txt: txts ){
93 
94 											str += (str.length()==0?"":", ") + new String( txt, "UTF-8" );
95 										}
96 									}
97 
98 									writer.println( tracker + " -> [" + str + "]" );
99 
100 								}catch( Throwable e ){
101 
102 									Debug.out( e );
103 								}
104 							}
105 						}
106 					}finally{
107 
108 						writer.exdent();
109 					}
110 				}
111 			});
112 	}
113 
114 	public static final int TORRENT_FLAG_LOW_NOISE			= 0x00000001;
115 	public static final int TORRENT_FLAG_METADATA_TORRENT	= 0x00000002;
116 
117 	private static final String		TORRENT_AZ_PROP_DHT_BACKUP_ENABLE		= "dht_backup_enable";
118 	private static final String		TORRENT_AZ_PROP_DHT_BACKUP_REQUESTED	= "dht_backup_requested";
119 	private static final String		TORRENT_AZ_PROP_TORRENT_FLAGS			= "torrent_flags";
120 	private static final String		TORRENT_AZ_PROP_PLUGINS					= "plugins";
121 
122 	public static final String		TORRENT_AZ_PROP_OBTAINED_FROM			= "obtained_from";
123 	private static final String		TORRENT_AZ_PROP_NETWORK_CACHE			= "network_cache";
124 	private static final String		TORRENT_AZ_PROP_TAG_CACHE				= "tag_cache";
125 	private static final String		TORRENT_AZ_PROP_PEER_CACHE				= "peer_cache";
126 	private static final String		TORRENT_AZ_PROP_PEER_CACHE_VALID		= "peer_cache_valid";
127 	public static final String		TORRENT_AZ_PROP_INITIAL_LINKAGE			= "initial_linkage";
128 	public static final String		TORRENT_AZ_PROP_INITIAL_LINKAGE2		= "initial_linkage2";
129 
130 	private static final String		MEM_ONLY_TORRENT_PATH		= "?/\\!:mem_only:!\\/?";
131 
132 	private static final long		PC_MARKER = RandomUtils.nextLong();
133 
134 	private static final List<byte[]>		created_torrents;
135 	private static final Set<HashWrapper>	created_torrents_set;
136 
137 	private static ThreadLocal<Map<String,Object>>		tls	=
138 		new ThreadLocal<Map<String,Object>>()
139 		{
140 			public Map<String,Object>
141 			initialValue()
142 			{
143 				return( new HashMap<String,Object>());
144 			}
145 		};
146 
147 	private static volatile Set<String>		ignore_files_set;
148 	private static volatile Set<String>		skip_extensions_set;
149 
150 	private static boolean bSaveTorrentBackup;
151 
152 	private static CopyOnWriteList<torrentAttributeListener>			torrent_attribute_listeners 	= new CopyOnWriteList<torrentAttributeListener>();
153 	private static CopyOnWriteList<TorrentAnnounceURLChangeListener>	torrent_url_changed_listeners 	= new CopyOnWriteList<TorrentAnnounceURLChangeListener>();
154 
155 	private static AsyncDispatcher	dispatcher = new AsyncDispatcher();
156 
157 	private static boolean					DNS_HANDLING_ENABLE	= true;
158 	private static final boolean			TRACE_DNS 			= false;
159 	private static int						DNS_HISTORY_TIMEOUT	= 4*60*60*1000;
160 
161 	private static Map<String,DNSTXTEntry>	dns_mapping = new HashMap<String, DNSTXTEntry>();
162 	private static volatile int				dns_mapping_seq_count;
163 	private static ThreadPool				dns_threads	= new ThreadPool( "DNS:lookups", 16, true );
164 
165 	static{
166 		SimpleTimer.addPeriodicEvent(
167 			"TU:dnstimer",
168 			DNS_HISTORY_TIMEOUT/2,
169 			new TimerEventPerformer()
170 			{
171 				public void
172 				perform(
173 					TimerEvent event )
174 				{
175 					if ( DNS_HANDLING_ENABLE ){
176 
177 						checkDNSTimeouts();
178 					}
179 				}
180 			});
181 	}
182 
183 	static DNSUtils.DNSUtilsIntf dns_utils = DNSUtils.getSingleton();
184 
185 	static {
COConfigurationManager.addAndFireParameterListeners( new String[]{ R, R, R }, new ParameterListener() { public void parameterChanged( String _name) { bSaveTorrentBackup = COConfigurationManager.getBooleanParameter( R ); boolean enable_proxy = COConfigurationManager.getBooleanParameter( R ); DNS_HANDLING_ENABLE = dns_utils != null && COConfigurationManager.getBooleanParameter( R ) && !enable_proxy; } })186 		COConfigurationManager.addAndFireParameterListeners(
187 			new String[]{
188 				"Save Torrent Backup",
189 				"Tracker DNS Records Enable",
190 				"Enable.Proxy" },
191 			new ParameterListener() {
192 				public void
193 				parameterChanged(
194 					String _name)
195 				{
196 					bSaveTorrentBackup = COConfigurationManager.getBooleanParameter( "Save Torrent Backup" );
197 
198 					boolean enable_proxy 	= COConfigurationManager.getBooleanParameter( "Enable.Proxy" );
199 
200 					DNS_HANDLING_ENABLE = dns_utils != null && COConfigurationManager.getBooleanParameter( "Tracker DNS Records Enable" ) && !enable_proxy;
201 				}
202 			});
203 
204 		created_torrents = COConfigurationManager.getListParameter( "my.created.torrents", new ArrayList());
205 
206 		created_torrents_set	= new HashSet();
207 
208 		Iterator	it = created_torrents.iterator();
209 
210 		while( it.hasNext()){
211 
created_torrents_set.add( new HashWrapper((byte[])it.next()))212 			created_torrents_set.add( new HashWrapper((byte[])it.next()));
213 		}
214 	}
215 
216 	static AtomicLong	torrent_delete_level = new AtomicLong();
217 	static long			torrent_delete_time;
218 
219 
220 	public static TOTorrent
readFromFile( File file, boolean create_delegate )221 	readFromFile(
222 		File		file,
223 		boolean		create_delegate )
224 
225 		throws TOTorrentException
226 	{
227 		return( readFromFile( file, create_delegate, false ));
228 	}
229 
230 		/**
231 		 * If you set "create_delegate" to true then you must understand that this results
232 		 * is piece hashes being discarded and then re-read from the torrent file if needed
233 		 * Therefore, if you delete the original torrent file you're going to get errors
234 		 * if you access the pieces after this (and they've been discarded)
235 		 * @param file
236 		 * @param create_delegate
237 		 * @param force_initial_discard - use to get rid of pieces immediately
238 		 * @return
239 		 * @throws TOTorrentException
240 		 */
241 
242 	public static ExtendedTorrent
readDelegateFromFile( File file, boolean force_initial_discard )243 	readDelegateFromFile(
244 		File		file,
245 		boolean		force_initial_discard )
246 
247 		throws TOTorrentException
248 	{
249 		return((ExtendedTorrent)readFromFile( file, true, force_initial_discard ));
250 	}
251 
252 	public static TOTorrent
readFromFile( File file, boolean create_delegate, boolean force_initial_discard )253 	readFromFile(
254 		File		file,
255 		boolean		create_delegate,
256 		boolean		force_initial_discard )
257 
258 		throws TOTorrentException
259 	{
260 		TOTorrent torrent;
261 
262 		try{
263 			torrent = TOTorrentFactory.deserialiseFromBEncodedFile(file);
264 
265 				// make an immediate backup if requested and one doesn't exist
266 
267 	    	if (bSaveTorrentBackup) {
268 
269 	    		File torrent_file_bak = new File(file.getParent(), file.getName() + ".bak");
270 
271 	    		if ( !torrent_file_bak.exists()){
272 
273 	    			try{
274 	    				torrent.serialiseToBEncodedFile(torrent_file_bak);
275 
276 	    			}catch( Throwable e ){
277 
278 	    				Debug.printStackTrace(e);
279 	    			}
280 	    		}
281 	    	}
282 
283 		}catch (TOTorrentException e){
284 
285 			// Debug.outNoStack( e.getMessage() );
286 
287 			File torrentBackup = new File(file.getParent(), file.getName() + ".bak");
288 
289 			if( torrentBackup.exists()){
290 
291 				torrent = TOTorrentFactory.deserialiseFromBEncodedFile(torrentBackup);
292 
293 					// use the original torrent's file name so that when this gets saved
294 					// it writes back to the original and backups are made as required
295 					// - set below
296 			}else{
297 
298 				throw e;
299 			}
300 		}
301 
302 		torrent.setAdditionalStringProperty("torrent filename", file.toString());
303 
304 		if ( create_delegate ){
305 
306 			torrentDelegate	res = new torrentDelegate( torrent, file );
307 
308 			if ( force_initial_discard ){
309 
310 				res.discardPieces( SystemTime.getCurrentTime(), true );
311 			}
312 
313 			return( res );
314 
315 		}else{
316 
317 			return( torrent );
318 		}
319 	}
320 
321 	public static TOTorrent
readFromBEncodedInputStream( InputStream is )322 	readFromBEncodedInputStream(
323 		InputStream		is )
324 
325 		throws TOTorrentException
326 	{
327 		TOTorrent	torrent = TOTorrentFactory.deserialiseFromBEncodedInputStream( is );
328 
329 			// as we've just imported this torrent we want to clear out any possible attributes that we
330 			// don't want such as "torrent filename"
331 
332 		torrent.removeAdditionalProperties();
333 
334 		return( torrent );
335 	}
336 
337 	public static TOTorrent
cloneTorrent( TOTorrent torrent )338 	cloneTorrent(
339 		TOTorrent	torrent )
340 
341 		throws TOTorrentException
342 	{
343 		return( TOTorrentFactory.deserialiseFromMap( torrent.serialiseToMap()));
344 	}
345 
346 	public static void
setMemoryOnly( TOTorrent torrent, boolean mem_only )347 	setMemoryOnly(
348 		TOTorrent			torrent,
349 		boolean				mem_only )
350 	{
351 		if ( mem_only ){
352 
353 			torrent.setAdditionalStringProperty("torrent filename", MEM_ONLY_TORRENT_PATH );
354 
355 		}else{
356 
357 			String s = torrent.getAdditionalStringProperty("torrent filename");
358 
359 			if ( s != null && s.equals( MEM_ONLY_TORRENT_PATH )){
360 
361 				torrent.removeAdditionalProperty( "torrent filename" );
362 			}
363 		}
364 	}
365 
366 	public static void
writeToFile( final TOTorrent torrent )367 	writeToFile(
368 		final TOTorrent		torrent )
369 
370 		throws TOTorrentException
371 	{
372 		writeToFile( torrent, false );
373 	}
374 
375 	public static void
writeToFile( TOTorrent torrent, boolean force_backup )376 	writeToFile(
377 		TOTorrent		torrent,
378 		boolean			force_backup )
379 
380 		throws TOTorrentException
381 	{
382 	   try{
383 	   		torrent.getMonitor().enter();
384 
385 	    	String str = torrent.getAdditionalStringProperty("torrent filename");
386 
387 	    	if ( str == null ){
388 
389 	    		throw (new TOTorrentException("TorrentUtils::writeToFile: no 'torrent filename' attribute defined", TOTorrentException.RT_FILE_NOT_FOUND));
390 	    	}
391 
392 	    	if ( str.equals( MEM_ONLY_TORRENT_PATH )){
393 
394 	    		return;
395 	    	}
396 
397 	    		// save first to temporary file as serialisation may require state to be re-read from
398 	    		// the existing file first and if we rename to .bak first then this aint good
399 
400     		File torrent_file_tmp = new File(str + "._az");
401 
402 	    	torrent.serialiseToBEncodedFile( torrent_file_tmp );
403 
404 	    		// now backup if required
405 
406 	    	File torrent_file = new File(str);
407 
408 	    	if ( 	( force_backup ||COConfigurationManager.getBooleanParameter("Save Torrent Backup")) &&
409 	    			torrent_file.exists()) {
410 
411 	    		File torrent_file_bak = new File(str + ".bak");
412 
413 	    		try{
414 
415 	    				// Will return false if it cannot be deleted (including if the file doesn't exist).
416 
417 	    			torrent_file_bak.delete();
418 
419 	    			torrent_file.renameTo(torrent_file_bak);
420 
421 	    		}catch( SecurityException e){
422 
423 	    			Debug.printStackTrace( e );
424 	    		}
425 	    	}
426 
427 	    		// now rename the temp file to required one
428 
429 	    	if ( torrent_file.exists()){
430 
431 	    		torrent_file.delete();
432 	    	}
433 
434 	    	torrent_file_tmp.renameTo( torrent_file );
435 
436 	   	}finally{
437 
438 	   		torrent.getMonitor().exit();
439 	   	}
440 	}
441 
442 	public static void
writeToFile( TOTorrent torrent, File file )443 	writeToFile(
444 		TOTorrent		torrent,
445 		File			file )
446 
447 		throws TOTorrentException
448 	{
449 		writeToFile( torrent, file, false );
450 	}
451 
452 	public static void
writeToFile( TOTorrent torrent, File file, boolean force_backup )453 	writeToFile(
454 		TOTorrent		torrent,
455 		File			file,
456 		boolean			force_backup )
457 
458 		throws TOTorrentException
459 	{
460 		torrent.setAdditionalStringProperty("torrent filename", file.toString());
461 
462 		writeToFile( torrent, force_backup );
463 	}
464 
465 	public static String
getTorrentFileName( TOTorrent torrent )466 	getTorrentFileName(
467 		TOTorrent		torrent )
468 
469 		throws TOTorrentException
470 	{
471     	String str = torrent.getAdditionalStringProperty("torrent filename");
472 
473     	if ( str == null ){
474 
475     		throw( new TOTorrentException("TorrentUtils::getTorrentFileName: no 'torrent filename' attribute defined", TOTorrentException.RT_FILE_NOT_FOUND));
476     	}
477 
478     	if ( str.equals( MEM_ONLY_TORRENT_PATH )){
479 
480     		return( null );
481     	}
482 
483 		return( str );
484 	}
485 
486 	public static void
copyToFile( TOTorrent torrent, File file )487 	copyToFile(
488 		TOTorrent		torrent,
489 		File			file )
490 
491 		throws TOTorrentException
492 	{
493 	   	torrent.serialiseToBEncodedFile(file);
494 	}
495 
496 	public static void
delete( TOTorrent torrent )497 	delete(
498 		TOTorrent 		torrent )
499 
500 		throws TOTorrentException
501 	{
502 	   try{
503 	   		torrent.getMonitor().enter();
504 
505 	    	String str = torrent.getAdditionalStringProperty("torrent filename");
506 
507 	    	if ( str == null ){
508 
509 	    		throw( new TOTorrentException("TorrentUtils::delete: no 'torrent filename' attribute defined", TOTorrentException.RT_FILE_NOT_FOUND));
510 	    	}
511 
512 	    	if ( str.equals( MEM_ONLY_TORRENT_PATH )){
513 
514 	    		return;
515 	    	}
516 
517 	    	File file = new File(str);
518 
519 	    	if ( !file.delete()){
520 
521 	    		if ( file.exists()){
522 
523 	    			throw( new TOTorrentException("TorrentUtils::delete: failed to delete '" + str + "'", TOTorrentException.RT_WRITE_FAILS));
524 	    		}
525 	    	}
526 
527 	    	new File( str + ".bak" ).delete();
528 
529 	    }finally{
530 
531 	    	torrent.getMonitor().exit();
532 	    }
533 	}
534 
535 	public static void
delete( File torrent_file, boolean force_no_recycle )536 	delete(
537 		File 		torrent_file,
538 		boolean		force_no_recycle )
539 	{
540 		if ( !FileUtil.deleteWithRecycle( torrent_file, force_no_recycle )){
541 
542 			if ( torrent_file.exists()){
543 
544 				Debug.out( "TorrentUtils::delete: failed to delete '" + torrent_file + "'" );
545 			}
546     	}
547 
548     	new File( torrent_file.toString() + ".bak" ).delete();
549 	}
550 
551 	public static boolean
move( File from_torrent, File to_torrent )552 	move(
553 		File		from_torrent,
554 		File		to_torrent )
555 	{
556 		if ( !FileUtil.renameFile(from_torrent, to_torrent )){
557 
558 			return( false );
559 		}
560 
561 		if ( new File( from_torrent.toString() + ".bak").exists()){
562 
563 			FileUtil.renameFile(
564 				new File( from_torrent.toString() + ".bak"),
565 				new File( to_torrent.toString() + ".bak"));
566 		}
567 
568 		return( true );
569 	}
570 
571 	public static String
exceptionToText( TOTorrentException e )572 	exceptionToText(
573 		TOTorrentException	e )
574 	{
575 		String	errorDetail;
576 
577 		int	reason = e.getReason();
578 
579 		if ( reason == TOTorrentException.RT_FILE_NOT_FOUND ){
580 
581 			errorDetail = MessageText.getString("DownloadManager.error.filenotfound" );
582 
583 		}else if ( reason == TOTorrentException.RT_ZERO_LENGTH ){
584 
585 			errorDetail = MessageText.getString("DownloadManager.error.fileempty");
586 
587 		}else if ( reason == TOTorrentException.RT_TOO_BIG ){
588 
589 			errorDetail = MessageText.getString("DownloadManager.error.filetoobig");
590 
591 		}else if ( reason == TOTorrentException.RT_DECODE_FAILS ){
592 
593 			errorDetail = MessageText.getString("DownloadManager.error.filewithouttorrentinfo" );
594 
595 		}else if ( reason == TOTorrentException.RT_UNSUPPORTED_ENCODING ){
596 
597 			errorDetail = MessageText.getString("DownloadManager.error.unsupportedencoding");
598 
599 		}else if ( reason == TOTorrentException.RT_READ_FAILS ){
600 
601 			errorDetail = MessageText.getString("DownloadManager.error.ioerror");
602 
603 		}else if ( reason == TOTorrentException.RT_HASH_FAILS ){
604 
605 			errorDetail = MessageText.getString("DownloadManager.error.sha1");
606 
607 		}else if ( reason == TOTorrentException.RT_CANCELLED ){
608 
609 			errorDetail = MessageText.getString("DownloadManager.error.operationcancancelled");
610 
611 		}else{
612 
613 			errorDetail = Debug.getNestedExceptionMessage(e);
614 		}
615 
616 		String	msg = Debug.getNestedExceptionMessage(e);
617 
618 		if ( errorDetail.indexOf( msg ) == -1){
619 
620 			errorDetail += " (" + msg + ")";
621 		}
622 
623 		return( errorDetail );
624 	}
625 
626 	public static Set<String>
getUniqueTrackerHosts( TOTorrent torrent )627 	getUniqueTrackerHosts(
628 		TOTorrent	torrent )
629 	{
630 		Set<String>	hosts = new HashSet<String>();
631 
632 		if ( torrent != null ){
633 
634 			URL	announce_url = torrent.getAnnounceURL();
635 
636 			if ( announce_url != null ){
637 
638 				String host = announce_url.getHost();
639 
640 				if ( host != null ){
641 
642 					hosts.add( host.toLowerCase( Locale.US ));
643 				}
644 			}
645 
646 			TOTorrentAnnounceURLGroup group = torrent.getAnnounceURLGroup();
647 
648 			TOTorrentAnnounceURLSet[]	sets = group.getAnnounceURLSets();
649 
650 			for ( TOTorrentAnnounceURLSet set: sets ){
651 
652 				URL[]	urls = set.getAnnounceURLs();
653 
654 				for ( URL u: urls ){
655 
656 					String host = u.getHost();
657 
658 					if ( host != null ){
659 
660 						hosts.add( host.toLowerCase( Locale.US ));
661 					}
662 				}
663 			}
664 		}
665 
666 		return( hosts );
667 	}
668 
669 	public static String
announceGroupsToText( TOTorrent torrent )670 	announceGroupsToText(
671 		TOTorrent	torrent )
672 	{
673 		URL	announce_url = torrent.getAnnounceURL();
674 
675 		String announce_url_str = announce_url==null?"":announce_url.toString().trim();
676 
677 		TOTorrentAnnounceURLGroup group = torrent.getAnnounceURLGroup();
678 
679 		TOTorrentAnnounceURLSet[]	sets = group.getAnnounceURLSets();
680 
681 		if ( sets.length == 0 ){
682 
683 			return( announce_url_str );
684 
685 		}else{
686 
687 			StringBuffer	sb = new StringBuffer(1024);
688 
689 			boolean	announce_found = false;
690 
691 			for (int i=0;i<sets.length;i++){
692 
693 				TOTorrentAnnounceURLSet	set = sets[i];
694 
695 				URL[]	urls = set.getAnnounceURLs();
696 
697 				if ( urls.length > 0 ){
698 
699 					for (int j=0;j<urls.length;j++){
700 
701 						String	str = urls[j].toString().trim();
702 
703 						if ( str.equals( announce_url_str )){
704 
705 							announce_found = true;
706 						}
707 
708 						sb.append( str );
709 						sb.append( "\r\n" );
710 					}
711 
712 					sb.append( "\r\n" );
713 				}
714 			}
715 
716 			String result = sb.toString().trim();
717 
718 			if ( !announce_found ){
719 
720 				if ( announce_url_str.length() > 0 ){
721 
722 					if ( result.length() == 0 ){
723 
724 						result = announce_url_str;
725 
726 					}else{
727 
728 						result = "\r\n\r\n" + announce_url_str;
729 					}
730 				}
731 			}
732 
733 			return( result );
734 		}
735 	}
736 
737 	public static String
announceGroupsToText( List<List<String>> group )738 	announceGroupsToText(
739 		List<List<String>>	group )
740 	{
741 		StringBuffer	sb = new StringBuffer(1024);
742 
743 		for ( List<String> urls: group ){
744 
745 			if ( sb.length() > 0 ){
746 
747 				sb.append( "\r\n" );
748 			}
749 
750 			for ( String str: urls ){
751 
752 				sb.append( str );
753 				sb.append( "\r\n" );
754 			}
755 		}
756 
757 		return( sb.toString().trim());
758 	}
759 
760 	public static List<List<String>>
announceTextToGroups( String text )761 	announceTextToGroups(
762 		String	text )
763 	{
764 		List<List<String>>	groups = new ArrayList<List<String>>();
765 
766 		String[]	lines = text.split( "\n" );
767 
768 		List<String>	current_group = new ArrayList<String>();
769 
770 		Set<String>	hits = new HashSet<String>();
771 
772 		for( String line: lines ){
773 
774 			line = line.trim();
775 
776 			if ( line.length() == 0 ){
777 
778 				if ( current_group.size() > 0 ){
779 
780 					groups.add( current_group );
781 
782 					current_group = new ArrayList<String>();
783 				}
784 			}else{
785 				String lc_line = line.toLowerCase();
786 
787 				if ( hits.contains( lc_line )){
788 
789 					continue;
790 				}
791 
792 				hits.add( lc_line );
793 
794 				current_group.add( line );
795 			}
796 		}
797 
798 		if ( current_group.size() > 0 ){
799 
800 			groups.add( current_group );
801 		}
802 
803 		return( groups );
804 	}
805 
806 	public static List<List<String>>
announceGroupsToList( TOTorrent torrent )807 	announceGroupsToList(
808 		TOTorrent	torrent )
809 	{
810 		List<List<String>>	groups = new ArrayList<List<String>>();
811 
812 		TOTorrentAnnounceURLGroup group = torrent.getAnnounceURLGroup();
813 
814 		TOTorrentAnnounceURLSet[]	sets = group.getAnnounceURLSets();
815 
816 		if ( sets.length == 0 ){
817 
818 			List<String>	s = new ArrayList<String>();
819 
820 			s.add( UrlUtils.getCanonicalString( torrent.getAnnounceURL()));
821 
822 			groups.add(s);
823 
824 		}else{
825 
826 			Set<String>	all_urls = new HashSet<String>();
827 
828 			for (int i=0;i<sets.length;i++){
829 
830 				List<String>	s = new ArrayList<String>();
831 
832 				TOTorrentAnnounceURLSet	set = sets[i];
833 
834 				URL[]	urls = set.getAnnounceURLs();
835 
836 				for (int j=0;j<urls.length;j++){
837 
838 					String u = UrlUtils.getCanonicalString( urls[j] );
839 
840 					s.add( u );
841 
842 					all_urls.add( u );
843 				}
844 
845 				if ( s.size() > 0 ){
846 
847 					groups.add(s);
848 				}
849 			}
850 
851 			String a = UrlUtils.getCanonicalString( torrent.getAnnounceURL());
852 
853 			if ( !all_urls.contains( a )){
854 
855 				List<String>	s = new ArrayList<String>();
856 
857 				s.add( a );
858 
859 				groups.add( 0, s );
860 			}
861 		}
862 
863 		return( groups );
864 	}
865 
866 		/**
867 		 * This method DOES NOT MODIFY THE TORRENT
868 		 * @param groups
869 		 * @param torrent
870 		 * @return
871 		 */
872 
873 	public static TOTorrentAnnounceURLSet[]
listToAnnounceSets( List<List<String>> groups, TOTorrent torrent )874 	listToAnnounceSets(
875 		List<List<String>>		groups,
876 		TOTorrent				torrent )
877 	{
878 		List<TOTorrentAnnounceURLSet> sets = new ArrayList<TOTorrentAnnounceURLSet>();
879 
880 		for ( List<String> group: groups ){
881 
882 			List<URL> urls = new ArrayList<URL>( group.size());
883 
884 			for ( String s: group ){
885 
886 				try{
887 					urls.add( new URL( s));
888 
889 				}catch( Throwable e ){
890 				}
891 			}
892 
893 			if ( urls.size() > 0 ){
894 
895 				sets.add( torrent.getAnnounceURLGroup().createAnnounceURLSet(urls.toArray( new URL[urls.size()])));
896 			}
897 		}
898 
899 		return( sets.toArray( new TOTorrentAnnounceURLSet[sets.size()]));
900 	}
901 
902 	public static void
listToAnnounceGroups( List<List<String>> groups, TOTorrent torrent )903 	listToAnnounceGroups(
904 		List<List<String>>		groups,
905 		TOTorrent				torrent )
906 	{
907 			// if the new groups no longer contain the main announce url then we replace this with the first in the groups. The primary reason for this
908 			// is that the DNS TXT record munging code always considers the announce-url as input to the generation of (potentially) modified URLs and if we
909 			// leave the announce-url there it will magically re-appear
910 			//
911 
912 		try{
913 			TOTorrentAnnounceURLGroup tg = torrent.getAnnounceURLGroup();
914 
915 			if ( groups.size() == 1 ){
916 
917 				List	set = (List)groups.get(0);
918 
919 				if ( set.size() == 1 ){
920 
921 					torrent.setAnnounceURL( new URL((String)set.get(0)));
922 
923 					tg.setAnnounceURLSets( new TOTorrentAnnounceURLSet[0]);
924 
925 					return;
926 				}
927 			}
928 
929 			String announce_url 	= torrent.getAnnounceURL().toExternalForm();
930 
931 			URL	first_url		= null;
932 
933 			Vector	g = new Vector();
934 
935 			for (int i=0;i<groups.size();i++){
936 
937 				List<String>	set = (List<String>)groups.get(i);
938 
939 				URL[]	urls = new URL[set.size()];
940 
941 				for (int j=0;j<set.size();j++){
942 
943 					String url_str = set.get(j);
944 
945 					if ( announce_url != null && url_str.equals( announce_url )){
946 
947 						announce_url = null;
948 					}
949 
950 					urls[j] = new URL((String)set.get(j));
951 
952 					if ( first_url == null ){
953 
954 						first_url = urls[j];
955 					}
956 				}
957 
958 				if ( urls.length > 0 ){
959 
960 					g.add( tg.createAnnounceURLSet( urls ));
961 				}
962 			}
963 
964 			TOTorrentAnnounceURLSet[]	sets = new TOTorrentAnnounceURLSet[g.size()];
965 
966 			if ( sets.length == 0 ){
967 
968 					// hmm, no valid urls at all
969 
970 				torrent.setAnnounceURL( new URL( NO_VALID_URL_URL ));
971 
972 			}else{
973 
974 				if ( announce_url != null && first_url != null ){
975 
976 					torrent.setAnnounceURL( first_url );
977 				}
978 			}
979 
980 			g.copyInto( sets );
981 
982 			tg.setAnnounceURLSets( sets );
983 
984 		}catch( MalformedURLException e ){
985 
986 			Debug.printStackTrace( e );
987 		}
988 	}
989 
990 	public static void
announceGroupsInsertFirst( TOTorrent torrent, String first_url )991 	announceGroupsInsertFirst(
992 		TOTorrent	torrent,
993 		String		first_url )
994 	{
995 		try{
996 
997 			announceGroupsInsertFirst( torrent, new URL( first_url ));
998 
999 		}catch( MalformedURLException e ){
1000 
1001 			Debug.printStackTrace( e );
1002 		}
1003 	}
1004 
1005 	public static void
announceGroupsInsertFirst( TOTorrent torrent, URL first_url )1006 	announceGroupsInsertFirst(
1007 		TOTorrent	torrent,
1008 		URL			first_url )
1009 	{
1010 		announceGroupsInsertFirst( torrent, new URL[]{ first_url });
1011 	}
1012 
1013 	public static void
announceGroupsInsertFirst( TOTorrent torrent, URL[] first_urls )1014 	announceGroupsInsertFirst(
1015 		TOTorrent	torrent,
1016 		URL[]		first_urls )
1017 	{
1018 		TOTorrentAnnounceURLGroup group = torrent.getAnnounceURLGroup();
1019 
1020 		TOTorrentAnnounceURLSet[] sets = group.getAnnounceURLSets();
1021 
1022 		TOTorrentAnnounceURLSet set1 = group.createAnnounceURLSet( first_urls );
1023 
1024 
1025 		if ( sets.length > 0 ){
1026 
1027 			TOTorrentAnnounceURLSet[]	new_sets = new TOTorrentAnnounceURLSet[sets.length+1];
1028 
1029 			new_sets[0] = set1;
1030 
1031 			System.arraycopy( sets, 0, new_sets, 1, sets.length );
1032 
1033 			group.setAnnounceURLSets( new_sets );
1034 
1035 		}else{
1036 
1037 			TOTorrentAnnounceURLSet set2 = group.createAnnounceURLSet(new URL[]{torrent.getAnnounceURL()});
1038 
1039 			group.setAnnounceURLSets(
1040 				new  TOTorrentAnnounceURLSet[]{ set1, set2 });
1041 		}
1042 	}
1043 
1044 	public static void
announceGroupsInsertLast( TOTorrent torrent, URL[] first_urls )1045 	announceGroupsInsertLast(
1046 		TOTorrent	torrent,
1047 		URL[]		first_urls )
1048 	{
1049 		TOTorrentAnnounceURLGroup group = torrent.getAnnounceURLGroup();
1050 
1051 		TOTorrentAnnounceURLSet[] sets = group.getAnnounceURLSets();
1052 
1053 		TOTorrentAnnounceURLSet set1 = group.createAnnounceURLSet( first_urls );
1054 
1055 
1056 		if ( sets.length > 0 ){
1057 
1058 			TOTorrentAnnounceURLSet[]	new_sets = new TOTorrentAnnounceURLSet[sets.length+1];
1059 
1060 			new_sets[sets.length] = set1;
1061 
1062 			System.arraycopy( sets, 0, new_sets, 0, sets.length );
1063 
1064 			group.setAnnounceURLSets( new_sets );
1065 
1066 		}else{
1067 
1068 			TOTorrentAnnounceURLSet set2 = group.createAnnounceURLSet(new URL[]{torrent.getAnnounceURL()});
1069 
1070 			group.setAnnounceURLSets(
1071 				new  TOTorrentAnnounceURLSet[]{ set2, set1 });
1072 		}
1073 	}
1074 
1075 	public static void
announceGroupsSetFirst( TOTorrent torrent, String first_url )1076 	announceGroupsSetFirst(
1077 		TOTorrent	torrent,
1078 		String		first_url )
1079 	{
1080 		List	groups = announceGroupsToList( torrent );
1081 
1082 		boolean	found = false;
1083 
1084 		outer:
1085 		for (int i=0;i<groups.size();i++){
1086 
1087 			List	set = (List)groups.get(i);
1088 
1089 			for (int j=0;j<set.size();j++){
1090 
1091 				if ( first_url.equals(set.get(j))){
1092 
1093 					set.remove(j);
1094 
1095 					set.add(0, first_url);
1096 
1097 					groups.remove(set);
1098 
1099 					groups.add(0,set);
1100 
1101 					found = true;
1102 
1103 					break outer;
1104 				}
1105 			}
1106 		}
1107 
1108 		if ( !found ){
1109 
1110 			System.out.println( "TorrentUtils::announceGroupsSetFirst - failed to find '" + first_url + "'" );
1111 		}
1112 
1113 		listToAnnounceGroups( groups, torrent );
1114 	}
1115 
1116 	public static boolean
announceGroupsContainsURL( TOTorrent torrent, String url )1117 	announceGroupsContainsURL(
1118 		TOTorrent	torrent,
1119 		String		url )
1120 	{
1121 		List	groups = announceGroupsToList( torrent );
1122 
1123 		for (int i=0;i<groups.size();i++){
1124 
1125 			List	set = (List)groups.get(i);
1126 
1127 			for (int j=0;j<set.size();j++){
1128 
1129 				if ( url.equals(set.get(j))){
1130 
1131 					return( true );
1132 				}
1133 			}
1134 		}
1135 
1136 		return( false );
1137 	}
1138 
1139 	public static boolean
canMergeAnnounceURLs( TOTorrent new_torrent, TOTorrent dest_torrent )1140 	canMergeAnnounceURLs(
1141 		TOTorrent	new_torrent,
1142 		TOTorrent	dest_torrent )
1143 	{
1144 		try{
1145 			List<List<String>>	new_groups 	= announceGroupsToList( new_torrent );
1146 			List<List<String>> 	dest_groups = announceGroupsToList( dest_torrent );
1147 
1148 			Set<String>	all_dest 	= new HashSet<String>();
1149 
1150 			for ( List<String> l: dest_groups ){
1151 
1152 				all_dest.addAll( l );
1153 			}
1154 
1155 			for ( List<String> l: new_groups ){
1156 
1157 				for ( String u: l ){
1158 
1159 					List<URL> mods = applyAllDNSMods( new URL( u ));
1160 
1161 					if ( mods != null ){
1162 
1163 						for ( URL m: mods ){
1164 
1165 							if ( !all_dest.contains( UrlUtils.getCanonicalString( m ))){
1166 
1167 								return( true );
1168 							}
1169 						}
1170 					}
1171 				}
1172 			}
1173 		}catch( Throwable e ){
1174 
1175 			Debug.out( e );
1176 		}
1177 
1178 		return( false );
1179 	}
1180 
1181 	public static boolean
mergeAnnounceURLs( TOTorrent new_torrent, TOTorrent dest_torrent )1182 	mergeAnnounceURLs(
1183 		TOTorrent 	new_torrent,
1184 		TOTorrent	dest_torrent )
1185 	{
1186 		if ( new_torrent == null || dest_torrent == null ){
1187 
1188 			return( false);
1189 		}
1190 
1191 		List	new_groups 	= announceGroupsToList( new_torrent );
1192 		List 	dest_groups = announceGroupsToList( dest_torrent );
1193 
1194 		List	groups_to_add = new ArrayList();
1195 
1196 		for (int i=0;i<new_groups.size();i++){
1197 
1198 			List new_set = (List)new_groups.get(i);
1199 
1200 			boolean	match = false;
1201 
1202 			for (int j=0;j<dest_groups.size();j++){
1203 
1204 				List dest_set = (List)dest_groups.get(j);
1205 
1206 				boolean same = new_set.size() == dest_set.size();
1207 
1208 				if ( same ){
1209 
1210 					for (int k=0;k<new_set.size();k++){
1211 
1212 						String new_url = (String)new_set.get(k);
1213 
1214 						if ( !dest_set.contains(new_url)){
1215 
1216 							same = false;
1217 
1218 							break;
1219 						}
1220 					}
1221 				}
1222 
1223 				if ( same ){
1224 
1225 					match = true;
1226 
1227 					break;
1228 				}
1229 			}
1230 
1231 			if ( !match ){
1232 
1233 				groups_to_add.add( new_set );
1234 			}
1235 		}
1236 
1237 		if ( groups_to_add.size() == 0 ){
1238 
1239 			return( false );
1240 		}
1241 
1242 		for (int i=0;i<groups_to_add.size();i++){
1243 
1244 			dest_groups.add(i,groups_to_add.get(i));
1245 		}
1246 
1247 		listToAnnounceGroups( dest_groups, dest_torrent );
1248 
1249 		return( true );
1250 	}
1251 
1252 	public static List<List<String>>
mergeAnnounceURLs( List<List<String>> base_urls, List<List<String>> merge_urls )1253 	mergeAnnounceURLs(
1254 		List<List<String>> 	base_urls,
1255 		List<List<String>>	merge_urls )
1256 	{
1257 		base_urls = getClone( base_urls );
1258 		if ( merge_urls == null ){
1259 			return( base_urls );
1260 		}
1261 		Set<String> mergesSet = new HashSet<String>();
1262 		mergesSet.add( NO_VALID_URL_URL );	// this results in removal of this dummy url if present
1263 		for ( List<String> l: merge_urls ){
1264 			mergesSet.addAll(l);
1265 		}
1266 		Iterator<List<String>> it1 = base_urls.iterator();
1267 		while( it1.hasNext()){
1268 			List<String> l = it1.next();
1269 			Iterator<String> it2 = l.iterator();
1270 			while( it2.hasNext()){
1271 				if ( mergesSet.contains( it2.next())){
1272 					it2.remove();
1273 				}
1274 			}
1275 			if ( l.isEmpty()){
1276 				it1.remove();
1277 			}
1278 		}
1279 
1280 		for (List<String> l: merge_urls ){
1281 			if ( !l.isEmpty()){
1282 				base_urls.add( l );
1283 			}
1284 		}
1285 
1286 		return( base_urls );
1287 	}
1288 
1289 	public static List<List<String>>
removeAnnounceURLs( List<List<String>> base_urls, List<List<String>> remove_urls, boolean use_prefix_match )1290 	removeAnnounceURLs(
1291 		List<List<String>> 	base_urls,
1292 		List<List<String>>	remove_urls,
1293 		boolean				use_prefix_match )
1294 	{
1295 		base_urls = getClone( base_urls );
1296 		if ( remove_urls == null ){
1297 			return( base_urls );
1298 		}
1299 		Set<String> removeSet = new HashSet<String>();
1300 		removeSet.add( NO_VALID_URL_URL );	// this results in removal of this dummy url if present
1301 		for ( List<String> l: remove_urls ){
1302 			for ( String s: l ){
1303 				removeSet.add( s.toLowerCase( Locale.US ));
1304 			}
1305 		}
1306 		Iterator<List<String>> it1 = base_urls.iterator();
1307 		while( it1.hasNext()){
1308 			List<String> l = it1.next();
1309 			Iterator<String> it2 = l.iterator();
1310 			while( it2.hasNext()){
1311 				String url = it2.next();
1312 				if ( url.equals( NO_VALID_URL_URL )){
1313 					it2.remove();
1314 				}else{
1315 					url = url.toLowerCase( Locale.US );
1316 
1317 					if ( use_prefix_match ){
1318 
1319 						for ( String s: removeSet ){
1320 
1321 							if ( url.startsWith( s )){
1322 
1323 								it2.remove();
1324 								break;
1325 							}
1326 						}
1327 					}else{
1328 						if ( removeSet.contains( url )){
1329 
1330 							it2.remove();
1331 						}
1332 					}
1333 				}
1334 			}
1335 			if ( l.isEmpty()){
1336 				it1.remove();
1337 			}
1338 		}
1339 
1340 		return( base_urls );
1341 	}
1342 
1343 	public static List<List<String>>
removeAnnounceURLs2( List<List<String>> base_urls, List<String> remove_urls, boolean use_prefix_match )1344 	removeAnnounceURLs2(
1345 		List<List<String>> 	base_urls,
1346 		List<String>		remove_urls,
1347 		boolean				use_prefix_match )
1348 	{
1349 		if ( remove_urls == null ){
1350 				// general semantics are to return a clone so caller can modify
1351 			return( getClone( base_urls ) );
1352 		}
1353 
1354 		List<List<String>> temp = new ArrayList<List<String>>(1);
1355 
1356 		temp.add( remove_urls );
1357 
1358 		return( removeAnnounceURLs( base_urls, temp, use_prefix_match ));
1359 	}
1360 
1361 	public static List<List<String>>
getClone( List<List<String>> lls )1362 	getClone(
1363 		List<List<String>> lls )
1364 	{
1365 		if ( lls == null ){
1366 			return( lls );
1367 		}
1368 		List<List<String>>	result = new ArrayList<List<String>>( lls.size());
1369 		for ( List<String> l: lls ){
1370 			result.add(new ArrayList<String>( l ));
1371 		}
1372 		return( result );
1373 	}
1374 
1375 	public static boolean
replaceAnnounceURL( TOTorrent torrent, URL old_url, URL new_url )1376 	replaceAnnounceURL(
1377 		TOTorrent		torrent,
1378 		URL				old_url,
1379 		URL				new_url )
1380 	{
1381 		boolean	found = false;
1382 
1383 		String	old_str = old_url.toString();
1384 		String	new_str = new_url.toString();
1385 
1386 		List	l = announceGroupsToList( torrent );
1387 
1388 		for (int i=0;i<l.size();i++){
1389 
1390 			List	set = (List)l.get(i);
1391 
1392 			for (int j=0;j<set.size();j++){
1393 
1394 				if (((String)set.get(j)).equals(old_str)){
1395 
1396 					found	= true;
1397 
1398 					set.set( j, new_str );
1399 				}
1400 			}
1401 		}
1402 
1403 		if ( found ){
1404 
1405 			listToAnnounceGroups( l, torrent );
1406 		}
1407 
1408 		if ( torrent.getAnnounceURL().toString().equals( old_str )){
1409 
1410 			torrent.setAnnounceURL( new_url );
1411 
1412 			found	= true;
1413 		}
1414 
1415 		if ( found ){
1416 
1417 			try{
1418 				writeToFile( torrent );
1419 
1420 			}catch( Throwable e ){
1421 
1422 				Debug.printStackTrace(e);
1423 
1424 				return( false );
1425 			}
1426 		}
1427 
1428 		return( found );
1429 	}
1430 
1431 	public static void
setResumeDataCompletelyValid( DownloadManagerState download_manager_state )1432 	setResumeDataCompletelyValid(
1433 		DownloadManagerState	download_manager_state )
1434 	{
1435 		DiskManagerFactory.setResumeDataCompletelyValid( download_manager_state );
1436 	}
1437 
1438 	public static String
getLocalisedName( TOTorrent torrent )1439 	getLocalisedName(
1440 		TOTorrent		torrent )
1441 	{
1442 		if (torrent == null) {
1443 			return "";
1444 		}
1445 		try{
1446 			String utf8Name = torrent.getUTF8Name();
1447 			if (utf8Name != null) {
1448 				return utf8Name;
1449 			}
1450 
1451 			LocaleUtilDecoder decoder = LocaleTorrentUtil.getTorrentEncodingIfAvailable( torrent );
1452 
1453 			if ( decoder == null ){
1454 
1455 				return( new String(torrent.getName(),Constants.DEFAULT_ENCODING));
1456 			}
1457 
1458 			return( decoder.decodeString(torrent.getName()));
1459 
1460 		}catch( Throwable e ){
1461 
1462 			Debug.printStackTrace( e );
1463 
1464 			return( new String( torrent.getName()));
1465 		}
1466 	}
1467 
1468 	public static void
setTLSTorrentHash( HashWrapper hash )1469 	setTLSTorrentHash(
1470 		HashWrapper		hash )
1471 	{
1472 		tls.get().put( "hash", hash );
1473 	}
1474 
1475 	public static HashWrapper
getTLSTorrentHash()1476 	getTLSTorrentHash()
1477 	{
1478 		return((HashWrapper)tls.get().get( "hash" ));
1479 	}
1480 
1481 	public static TOTorrent
getTLSTorrent()1482 	getTLSTorrent()
1483 	{
1484 		HashWrapper	hash = (HashWrapper)(tls.get()).get("hash");
1485 
1486 		if ( hash != null ){
1487 
1488 			try{
1489 				AzureusCore	core = AzureusCoreFactory.getSingleton();
1490 
1491 				DownloadManager dm = core.getGlobalManager().getDownloadManager( hash );
1492 
1493 				if ( dm != null ){
1494 
1495 					return( dm.getTorrent());
1496 				}
1497 			}catch( Throwable e ){
1498 
1499 				Debug.printStackTrace(e);
1500 			}
1501 		}
1502 
1503 		return( null );
1504 	}
1505 
1506 	public static void
setTLSDescription( String desc )1507 	setTLSDescription(
1508 		String		desc )
1509 	{
1510 		tls.get().put( "desc", desc );
1511 	}
1512 
1513 	public static String
getTLSDescription()1514 	getTLSDescription()
1515 	{
1516 		return((String)tls.get().get( "desc" ));
1517 	}
1518 
1519 		/**
1520 		 * get tls for cloning onto another thread
1521 		 * @return
1522 		 */
1523 
1524 	public static Object
getTLS()1525 	getTLS()
1526 	{
1527 		return( new HashMap<String,Object>(tls.get()));
1528 	}
1529 
1530 	public static void
setTLS( Object obj )1531 	setTLS(
1532 		Object	obj )
1533 	{
1534 		Map<String,Object>	m = (Map<String,Object>)obj;
1535 
1536 		Map<String,Object> tls_map = tls.get();
1537 
1538 		tls_map.clear();
1539 
1540 		tls_map.putAll(m);
1541 	}
1542 
1543 	public static URL
getDecentralisedEmptyURL()1544 	getDecentralisedEmptyURL()
1545 	{
1546 		try{
1547 			return( new URL( "dht://" ));
1548 
1549 		}catch( Throwable e ){
1550 
1551 			Debug.printStackTrace(e);
1552 
1553 			return( null );
1554 		}
1555 	}
1556 
1557 	public static URL
getDecentralisedURL( byte[] hash )1558 	getDecentralisedURL(
1559 		byte[]		hash )
1560 	{
1561 		try{
1562 			return( new URL( "dht://" + ByteFormatter.encodeString( hash ) + ".dht/announce" ));
1563 
1564 		}catch( Throwable e ){
1565 
1566 			Debug.out( e );
1567 
1568 			return( getDecentralisedEmptyURL());
1569 		}
1570 	}
1571 
1572 	public static URL
getDecentralisedURL( TOTorrent torrent )1573 	getDecentralisedURL(
1574 		TOTorrent	torrent )
1575 	{
1576 		try{
1577 			return( new URL( "dht://" + ByteFormatter.encodeString( torrent.getHash()) + ".dht/announce" ));
1578 
1579 		}catch( Throwable e ){
1580 
1581 			Debug.out( e );
1582 
1583 			return( getDecentralisedEmptyURL());
1584 		}
1585 	}
1586 
1587 	public static void
setDecentralised( TOTorrent torrent )1588 	setDecentralised(
1589 		TOTorrent	torrent )
1590 	{
1591    		torrent.setAnnounceURL( getDecentralisedURL( torrent ));
1592 	}
1593 
1594 	public static boolean
isDecentralised( TOTorrent torrent )1595 	isDecentralised(
1596 		TOTorrent		torrent )
1597 	{
1598 		if ( torrent == null ){
1599 
1600 			return( false );
1601 		}
1602 
1603 		return( torrent.isDecentralised());
1604 	}
1605 
1606 
1607 	public static boolean
isDecentralised( URL url )1608 	isDecentralised(
1609 		URL		url )
1610 	{
1611 		if ( url == null ){
1612 
1613 			return( false );
1614 		}
1615 
1616 		return( url.getProtocol().equalsIgnoreCase( "dht" ));
1617 	}
1618 
1619 	public static boolean
isDecentralised( String host )1620 	isDecentralised(
1621 		String		host )
1622 	{
1623 		if ( host == null ){
1624 
1625 			return( false );
1626 		}
1627 
1628 		return( host.endsWith( ".dht" ));
1629 	}
1630 
1631 	private static Map
getAzureusProperties( TOTorrent torrent )1632 	getAzureusProperties(
1633 		TOTorrent	torrent )
1634 	{
1635 		Map	m = torrent.getAdditionalMapProperty( TOTorrent.AZUREUS_PROPERTIES );
1636 
1637 		if ( m == null ){
1638 
1639 			m = new HashMap();
1640 
1641 			torrent.setAdditionalMapProperty( TOTorrent.AZUREUS_PROPERTIES, m );
1642 		}
1643 
1644 		return( m );
1645 	}
1646 
1647 	private static Map
getAzureusPrivateProperties( TOTorrent torrent )1648 	getAzureusPrivateProperties(
1649 		TOTorrent	torrent )
1650 	{
1651 		Map	m = torrent.getAdditionalMapProperty( TOTorrent.AZUREUS_PRIVATE_PROPERTIES );
1652 
1653 		if ( m == null ){
1654 
1655 			m = new HashMap();
1656 
1657 			torrent.setAdditionalMapProperty( TOTorrent.AZUREUS_PRIVATE_PROPERTIES, m );
1658 		}
1659 
1660 		return( m );
1661 	}
1662 
1663 	private static String
getContentMapString( TOTorrent torrent, String key )1664 	getContentMapString(
1665 		TOTorrent	torrent,
1666 		String 		key )
1667 	{
1668 		Map m = getAzureusProperties( torrent );
1669 
1670 		Object content = m.get( "Content" );
1671 
1672 		if ( !(content instanceof Map )){
1673 
1674 			return null;
1675 		}
1676 
1677 		Map mapContent = (Map)content;
1678 
1679 		Object obj = mapContent.get(key);
1680 
1681 		if ( obj instanceof String ){
1682 
1683 			return (String) obj;
1684 
1685 		}else if ( obj instanceof byte[] ){
1686 
1687 			try{
1688 				return new String((byte[]) obj, Constants.DEFAULT_ENCODING);
1689 
1690 			}catch ( UnsupportedEncodingException e ){
1691 
1692 				e.printStackTrace();
1693 			}
1694 		}
1695 
1696 		return null;
1697 	}
1698 
1699 	public static boolean
isFeaturedContent( TOTorrent torrent )1700 	isFeaturedContent(
1701 		TOTorrent		torrent )
1702 	{
1703 		String content_type = getContentMapString( torrent, "Content Type" );
1704 
1705 		return( content_type != null && content_type.equalsIgnoreCase( "featured" ));
1706 	}
1707 
1708 	public static void
setObtainedFrom( File file, String str )1709 	setObtainedFrom(
1710 		File			file,
1711 		String			str )
1712 	{
1713 		try{
1714 			TOTorrent	torrent = readFromFile( file, false, false );
1715 
1716 			setObtainedFrom( torrent, str );
1717 
1718 			writeToFile( torrent );
1719 
1720 		} catch (TOTorrentException e) {
1721 			// ignore, file probably not torrent
1722 
1723 		}catch( Throwable e ){
1724 
1725 			Debug.out( e );
1726 		}
1727 	}
1728 
1729 	public static void
setObtainedFrom( TOTorrent torrent, String str )1730 	setObtainedFrom(
1731 		TOTorrent		torrent,
1732 		String			str )
1733 	{
1734 		Map	m = getAzureusPrivateProperties( torrent );
1735 
1736 		try{
1737 			str = str.trim();
1738 
1739 			if ( str == null || str.length() == 0 ){
1740 
1741 				m.remove( TORRENT_AZ_PROP_OBTAINED_FROM );
1742 
1743 			}else{
1744 
1745 				m.put( TORRENT_AZ_PROP_OBTAINED_FROM, str.getBytes( "UTF-8" ));
1746 			}
1747 
1748 			fireAttributeListener( torrent, TORRENT_AZ_PROP_OBTAINED_FROM, str );
1749 
1750 		}catch( Throwable e ){
1751 
1752 			Debug.printStackTrace(e);
1753 		}
1754 	}
1755 
1756 	public static String
getObtainedFrom( TOTorrent torrent )1757 	getObtainedFrom(
1758 		TOTorrent		torrent )
1759 	{
1760 		Map	m = getAzureusPrivateProperties( torrent );
1761 
1762 		byte[]	from = (byte[])m.get( TORRENT_AZ_PROP_OBTAINED_FROM );
1763 
1764 		if ( from != null ){
1765 
1766 			try{
1767 				return( new String( from, "UTF-8" ));
1768 
1769 			}catch( Throwable e ){
1770 
1771 				Debug.printStackTrace(e);
1772 			}
1773 		}
1774 
1775 		return( null );
1776 	}
1777 
1778 	public static void
setNetworkCache( TOTorrent torrent, List<String> networks )1779 	setNetworkCache(
1780 		TOTorrent		torrent,
1781 		List<String>	networks )
1782 	{
1783 		Map	m = getAzureusPrivateProperties( torrent );
1784 
1785 		try{
1786 			m.put( TORRENT_AZ_PROP_NETWORK_CACHE, networks );
1787 
1788 		}catch( Throwable e ){
1789 
1790 			Debug.printStackTrace(e);
1791 		}
1792 	}
1793 
1794 	public static List<String>
getNetworkCache( TOTorrent torrent )1795 	getNetworkCache(
1796 		TOTorrent		torrent )
1797 	{
1798 		List<String>	result = new ArrayList<String>();
1799 
1800 		Map	m = getAzureusPrivateProperties( torrent );
1801 
1802 		try{
1803 			List l = (List)m.get( TORRENT_AZ_PROP_NETWORK_CACHE );
1804 
1805 			if ( l != null ){
1806 
1807 				for (Object o: l ){
1808 
1809 					if ( o instanceof String ){
1810 
1811 						result.add((String)o);
1812 
1813 					}else if ( o instanceof byte[] ){
1814 
1815 						String s = new String((byte[])o, "UTF-8" );
1816 
1817 						for ( String x: AENetworkClassifier.AT_NETWORKS ){
1818 
1819 							if ( s.equals( x )){
1820 
1821 								result.add( x );
1822 
1823 								break;
1824 							}
1825 						}
1826 					}
1827 				}
1828 			}
1829 		}catch( Throwable e ){
1830 
1831 			Debug.printStackTrace(e);
1832 		}
1833 
1834 		return( result );
1835 	}
1836 
1837 	public static void
setTagCache( TOTorrent torrent, List<String> networks )1838 	setTagCache(
1839 		TOTorrent		torrent,
1840 		List<String>	networks )
1841 	{
1842 		Map	m = getAzureusPrivateProperties( torrent );
1843 
1844 		try{
1845 			m.put( TORRENT_AZ_PROP_TAG_CACHE, networks );
1846 
1847 		}catch( Throwable e ){
1848 
1849 			Debug.printStackTrace(e);
1850 		}
1851 	}
1852 
1853 	public static List<String>
getTagCache( TOTorrent torrent )1854 	getTagCache(
1855 		TOTorrent		torrent )
1856 	{
1857 		List<String>	result = new ArrayList<String>();
1858 
1859 		Map	m = getAzureusPrivateProperties( torrent );
1860 
1861 		try{
1862 			List l = (List)m.get( TORRENT_AZ_PROP_TAG_CACHE );
1863 
1864 			if ( l != null ){
1865 
1866 				for (Object o: l ){
1867 
1868 					if ( o instanceof String ){
1869 
1870 						result.add((String)o);
1871 
1872 					}else if ( o instanceof byte[] ){
1873 
1874 						String s = new String((byte[])o, "UTF-8" );
1875 
1876 						result.add( s );
1877 					}
1878 				}
1879 			}
1880 		}catch( Throwable e ){
1881 
1882 			Debug.printStackTrace(e);
1883 		}
1884 
1885 		return( result );
1886 	}
1887 
1888 	public static void
setPeerCache( TOTorrent torrent, Map pc )1889 	setPeerCache(
1890 		TOTorrent		torrent,
1891 		Map				pc )
1892 	{
1893 		Map	m = getAzureusPrivateProperties( torrent );
1894 
1895 		try{
1896 			m.put( TORRENT_AZ_PROP_PEER_CACHE, pc );
1897 
1898 			setPeerCacheValid( torrent );
1899 
1900 		}catch( Throwable e ){
1901 
1902 			Debug.printStackTrace(e);
1903 		}
1904 	}
1905 
1906 	public static void
setPeerCacheValid( TOTorrent torrent )1907 	setPeerCacheValid(
1908 		TOTorrent		torrent )
1909 	{
1910 		Map	m = getAzureusPrivateProperties( torrent );
1911 
1912 		try{
1913 			m.put( TORRENT_AZ_PROP_PEER_CACHE_VALID, new Long( PC_MARKER ));
1914 
1915 		}catch( Throwable e ){
1916 
1917 			Debug.printStackTrace(e);
1918 		}
1919 	}
1920 
1921 	public static Map
getPeerCache( TOTorrent torrent )1922 	getPeerCache(
1923 		TOTorrent		torrent )
1924 	{
1925 		try{
1926 			Map	m = getAzureusPrivateProperties( torrent );
1927 
1928 			Long value = (Long)m.get( TORRENT_AZ_PROP_PEER_CACHE_VALID );
1929 
1930 			if ( value != null && value == PC_MARKER ){
1931 
1932 				Map	pc = (Map)m.get( TORRENT_AZ_PROP_PEER_CACHE );
1933 
1934 				return( pc );
1935 			}
1936 
1937 		}catch( Throwable e ){
1938 
1939 			Debug.out( e );
1940 		}
1941 
1942 		return( null );
1943 	}
1944 
1945 	public static void
setFlag( TOTorrent torrent, int flag, boolean value )1946 	setFlag(
1947 		TOTorrent		torrent,
1948 		int				flag,
1949 		boolean			value )
1950 	{
1951 		Map	m = getAzureusProperties( torrent );
1952 
1953 		Long	flags = (Long)m.get( TORRENT_AZ_PROP_TORRENT_FLAGS );
1954 
1955 		if ( flags == null ){
1956 
1957 			flags = new Long(0);
1958 		}
1959 
1960 		m.put( TORRENT_AZ_PROP_TORRENT_FLAGS, new Long(flags.intValue() | flag ));
1961 	}
1962 
1963 	public static boolean
getFlag( TOTorrent torrent, int flag )1964 	getFlag(
1965 		TOTorrent		torrent,
1966 		int				flag )
1967 	{
1968 		Map	m = getAzureusProperties( torrent );
1969 
1970 		Long	flags = (Long)m.get( TORRENT_AZ_PROP_TORRENT_FLAGS );
1971 
1972 		if ( flags == null ){
1973 
1974 			return( false );
1975 		}
1976 
1977 		return(( flags.intValue() & flag ) != 0 );
1978 	}
1979 
1980 	public static Map<Integer,File>
getInitialLinkage( TOTorrent torrent )1981 	getInitialLinkage(
1982 		TOTorrent		torrent )
1983 	{
1984 		Map<Integer,File>	result = new HashMap<Integer, File>();
1985 
1986 		try{
1987 			Map	pp = torrent.getAdditionalMapProperty( TOTorrent.AZUREUS_PRIVATE_PROPERTIES );
1988 
1989 			if ( pp != null ){
1990 
1991 				Map<String,Object> _links;
1992 
1993 				byte[]	g_data = (byte[])pp.get( TorrentUtils.TORRENT_AZ_PROP_INITIAL_LINKAGE2 );
1994 
1995 				if ( g_data == null ){
1996 
1997 					_links = (Map<String,Object>)pp.get( TorrentUtils.TORRENT_AZ_PROP_INITIAL_LINKAGE );
1998 
1999 				}else{
2000 
2001 					_links = BDecoder.decode(new BufferedInputStream( new GZIPInputStream( new ByteArrayInputStream( g_data ))));
2002 
2003 				}
2004 				if ( _links != null ){//&& TorrentUtils.isCreatedTorrent( torrent )){
2005 
2006 					Map<String,String> links = (Map<String,String>)BDecoder.decodeStrings( _links );
2007 
2008 					for ( Map.Entry<String,String> entry: links.entrySet()){
2009 
2010 						int		file_index 	= Integer.parseInt( entry.getKey());
2011 						String	file		= entry.getValue();
2012 
2013 						result.put( file_index, new File( file ));
2014 					}
2015 				}
2016 			}
2017 		}catch( Throwable e ){
2018 
2019 			Debug.out( "Failed to read linkage map", e );
2020 		}
2021 
2022 		return( result );
2023 	}
2024 
2025 	public static void
setPluginStringProperty( TOTorrent torrent, String name, String value )2026 	setPluginStringProperty(
2027 		TOTorrent		torrent,
2028 		String			name,
2029 		String			value )
2030 	{
2031 		Map	m = getAzureusProperties( torrent );
2032 
2033 		Object obj = m.get( TORRENT_AZ_PROP_PLUGINS );
2034 
2035 		Map	p;
2036 
2037 		if ( obj instanceof Map ){
2038 
2039 			p = (Map)obj;
2040 
2041 		}else{
2042 
2043 			p = new HashMap();
2044 
2045 			m.put( TORRENT_AZ_PROP_PLUGINS, p );
2046 		}
2047 
2048 		if ( value == null ){
2049 
2050 			p.remove( name );
2051 
2052 		}else{
2053 
2054 			p.put( name, value.getBytes());
2055 		}
2056 	}
2057 
2058 	public static String
getPluginStringProperty( TOTorrent torrent, String name )2059 	getPluginStringProperty(
2060 		TOTorrent		torrent,
2061 		String			name )
2062 	{
2063 		Map	m = getAzureusProperties( torrent );
2064 
2065 		Object	obj = m.get( TORRENT_AZ_PROP_PLUGINS );
2066 
2067 		if ( obj instanceof Map ){
2068 
2069 			Map p = (Map)obj;
2070 
2071 			obj = p.get( name );
2072 
2073 			if ( obj instanceof byte[]){
2074 
2075 				return( new String((byte[])obj));
2076 			}
2077 		}
2078 
2079 		return( null );
2080 	}
2081 
2082 	public static void
setPluginMapProperty( TOTorrent torrent, String name, Map value )2083 	setPluginMapProperty(
2084 		TOTorrent		torrent,
2085 		String			name,
2086 		Map				value )
2087 	{
2088 		Map	m = getAzureusProperties( torrent );
2089 
2090 		Object obj = m.get( TORRENT_AZ_PROP_PLUGINS );
2091 
2092 		Map	p;
2093 
2094 		if ( obj instanceof Map ){
2095 
2096 			p = (Map)obj;
2097 
2098 		}else{
2099 
2100 			p = new HashMap();
2101 
2102 			m.put( TORRENT_AZ_PROP_PLUGINS, p );
2103 		}
2104 
2105 		if ( value == null ){
2106 
2107 			p.remove( name );
2108 
2109 		}else{
2110 
2111 			p.put( name, value );
2112 		}
2113 	}
2114 
2115 	public static Map
getPluginMapProperty( TOTorrent torrent, String name )2116 	getPluginMapProperty(
2117 		TOTorrent		torrent,
2118 		String			name )
2119 	{
2120 		Map	m = getAzureusProperties( torrent );
2121 
2122 		Object	obj = m.get( TORRENT_AZ_PROP_PLUGINS );
2123 
2124 		if ( obj instanceof Map ){
2125 
2126 			Map p = (Map)obj;
2127 
2128 			obj = p.get( name );
2129 
2130 			if ( obj instanceof Map ){
2131 
2132 				return((Map)obj);
2133 			}
2134 		}
2135 
2136 		return( null );
2137 	}
2138 
2139 	public static void
setDHTBackupEnabled( TOTorrent torrent, boolean enabled )2140 	setDHTBackupEnabled(
2141 		TOTorrent		torrent,
2142 		boolean			enabled )
2143 	{
2144 		Map	m = getAzureusProperties( torrent );
2145 
2146 		m.put( TORRENT_AZ_PROP_DHT_BACKUP_ENABLE, new Long(enabled?1:0));
2147 	}
2148 
2149 	public static boolean
getDHTBackupEnabled( TOTorrent torrent )2150 	getDHTBackupEnabled(
2151 		TOTorrent	torrent )
2152 	{
2153 			// missing -> true
2154 
2155 		Map	m = getAzureusProperties( torrent );
2156 
2157 		Object	obj = m.get( TORRENT_AZ_PROP_DHT_BACKUP_ENABLE );
2158 
2159 		if ( obj instanceof Long ){
2160 
2161 			return( ((Long)obj).longValue() == 1 );
2162 		}
2163 
2164 		return( true );
2165 	}
2166 
2167 	public static boolean
isDHTBackupRequested( TOTorrent torrent )2168 	isDHTBackupRequested(
2169 		TOTorrent	torrent )
2170 	{
2171 			// missing -> false
2172 
2173 		Map	m = getAzureusProperties( torrent );
2174 
2175 		Object obj = m.get( TORRENT_AZ_PROP_DHT_BACKUP_REQUESTED );
2176 
2177 		if ( obj instanceof Long ){
2178 
2179 			return( ((Long)obj).longValue() == 1 );
2180 		}
2181 
2182 		return( false );
2183 	}
2184 
2185 	public static void
setDHTBackupRequested( TOTorrent torrent, boolean requested )2186 	setDHTBackupRequested(
2187 		TOTorrent		torrent,
2188 		boolean			requested )
2189 	{
2190 		Map	m = getAzureusProperties( torrent );
2191 
2192 		m.put( TORRENT_AZ_PROP_DHT_BACKUP_REQUESTED, new Long(requested?1:0));
2193 	}
2194 
2195 
2196 	public static boolean
isReallyPrivate( TOTorrent torrent)2197 	isReallyPrivate(
2198 		TOTorrent torrent)
2199 	{
2200 		if ( torrent == null ){
2201 
2202 			return( false );
2203 		}
2204 
2205 		URL url = torrent.getAnnounceURL();
2206 
2207 		if ( url == null ){
2208 
2209 			TOTorrentAnnounceURLSet[] sets = torrent.getAnnounceURLGroup().getAnnounceURLSets();
2210 
2211 			if ( sets != null && sets.length > 0 ){
2212 
2213 				URL[] urls = sets[0].getAnnounceURLs();
2214 
2215 				if ( urls.length > 0 ){
2216 
2217 					url = urls[0];
2218 				}
2219 			}
2220 		}
2221 
2222 		if ( url != null ){
2223 
2224 			if ( UrlUtils.containsPasskey( url )){
2225 
2226 				return torrent.getPrivate();
2227 			}
2228 		}
2229 
2230 		return false;
2231 	}
2232 
2233 	public static boolean
getPrivate( TOTorrent torrent )2234 	getPrivate(
2235 		TOTorrent		torrent )
2236 	{
2237 		if ( torrent == null ){
2238 
2239 			return( false );
2240 		}
2241 
2242 		return( torrent.getPrivate());
2243 	}
2244 
2245 	public static void
setPrivate( TOTorrent torrent, boolean _private )2246 	setPrivate(
2247 		TOTorrent		torrent,
2248 		boolean			_private )
2249 	{
2250 		if ( torrent == null ){
2251 
2252 			return;
2253 		}
2254 
2255 		try{
2256 			torrent.setPrivate( _private );
2257 
2258 		}catch( Throwable e ){
2259 
2260 			Debug.printStackTrace(e);
2261 		}
2262 	}
2263 
2264 		// skip extensions
2265 
2266 
2267 	public static Set<String>
getSkipExtensionsSet()2268 	getSkipExtensionsSet()
2269 	{
2270 		return(getSkipExtensionsSetSupport(false));
2271 	}
2272 
2273 	private static synchronized Set<String>
getSkipExtensionsSetSupport( boolean force )2274 	getSkipExtensionsSetSupport(
2275 		boolean	force )
2276 	{
2277 		if ( skip_extensions_set == null || force ){
2278 
2279 			Set<String>		new_skip_set	= new HashSet<String>();
2280 
2281 			String	skip_list = COConfigurationManager.getStringParameter( "File.Torrent.AutoSkipExtensions" );
2282 
2283 			skip_list = skip_list.replace( ',', ';' );
2284 
2285 			if ( skip_extensions_set == null ){
2286 
2287 					// first time - add the listener
2288 
2289 				COConfigurationManager.addParameterListener(
2290 					"File.Torrent.AutoSkipExtensions",
2291 					new ParameterListener()
2292 					{
2293 						public void
2294 						parameterChanged(
2295 							String parameterName)
2296 						{
2297 							getSkipExtensionsSetSupport( true );
2298 						}
2299 					});
2300 			}
2301 
2302 			int	pos = 0;
2303 
2304 			while( true ){
2305 
2306 				int	p1 = skip_list.indexOf( ";", pos );
2307 
2308 				String	bit;
2309 
2310 				if ( p1 == -1 ){
2311 
2312 					bit = skip_list.substring(pos);
2313 
2314 				}else{
2315 
2316 					bit	= skip_list.substring( pos, p1 );
2317 
2318 					pos	= p1+1;
2319 				}
2320 
2321 				String ext = bit.trim().toLowerCase(); 	// use default locale as we're dealing with local file names
2322 
2323 				if ( ext.startsWith(".")){
2324 
2325 					ext = ext.substring(1);
2326 				}
2327 
2328 				if ( ext.length() > 0 ){
2329 
2330 					new_skip_set.add( ext );
2331 				}
2332 
2333 				if ( p1 == -1 ){
2334 
2335 					break;
2336 				}
2337 			}
2338 
2339 			skip_extensions_set = new_skip_set;
2340 		}
2341 
2342 		return( skip_extensions_set );
2343 	}
2344 
2345 
2346 		// ignore files
2347 
2348 	public static Set<String>
getIgnoreSet()2349 	getIgnoreSet()
2350 	{
2351 		return(getIgnoreSetSupport(false));
2352 	}
2353 
2354 	private static synchronized Set<String>
getIgnoreSetSupport( boolean force )2355 	getIgnoreSetSupport(
2356 		boolean	force )
2357 	{
2358 		if ( ignore_files_set == null || force ){
2359 
2360 			Set<String>		new_ignore_set	= new HashSet<String>();
2361 
2362 			String	ignore_list = COConfigurationManager.getStringParameter( "File.Torrent.IgnoreFiles", TOTorrent.DEFAULT_IGNORE_FILES );
2363 
2364 			if ( ignore_files_set == null ){
2365 
2366 					// first time - add the listener
2367 
2368 				COConfigurationManager.addParameterListener(
2369 					"File.Torrent.IgnoreFiles",
2370 					new ParameterListener()
2371 					{
2372 						public void
2373 						parameterChanged(
2374 							String parameterName)
2375 						{
2376 							getIgnoreSetSupport( true );
2377 						}
2378 					});
2379 			}
2380 
2381 			int	pos = 0;
2382 
2383 			while(true){
2384 
2385 				int	p1 = ignore_list.indexOf( ";", pos );
2386 
2387 				String	bit;
2388 
2389 				if ( p1 == -1 ){
2390 
2391 					bit = ignore_list.substring(pos);
2392 
2393 				}else{
2394 
2395 					bit	= ignore_list.substring( pos, p1 );
2396 
2397 					pos	= p1+1;
2398 				}
2399 
2400 				new_ignore_set.add(bit.trim().toLowerCase());	// use default locale as we're dealing with local file names
2401 
2402 				if ( p1 == -1 ){
2403 
2404 					break;
2405 				}
2406 			}
2407 
2408 			ignore_files_set = new_ignore_set;
2409 		}
2410 
2411 		return( ignore_files_set );
2412 	}
2413 
2414 
2415 
2416 		// this class exists to minimise memory requirements by discarding the piece hash values
2417 		// when "idle"
2418 
2419 	private static final int	PIECE_HASH_TIMEOUT	= 3*60*1000;
2420 
2421 	private static Map	torrent_delegates = new WeakHashMap();
2422 
2423 	static{
2424 		SimpleTimer.addPeriodicEvent(
2425 			"TorrentUtils:pieceDiscard",
2426 			PIECE_HASH_TIMEOUT/2,
2427 			new TimerEventPerformer()
2428 			{
2429 				public void
2430 				perform(
2431 					TimerEvent	event )
2432 				{
2433 					long	now = SystemTime.getCurrentTime();
2434 
2435 					synchronized( torrent_delegates ){
2436 
2437 						Iterator it = torrent_delegates.keySet().iterator();
2438 
2439 						while( it.hasNext()){
2440 
2441 							((torrentDelegate)it.next()).discardPieces(now,false);
2442 						}
2443 					}
2444 				}
2445 			});
2446 	}
2447 
2448 	private static HashSet	torrentFluffKeyset = new HashSet(2);
2449 	private static Map		fluffThombstone = new HashMap(1);
2450 
2451 	/**
2452 	 * Register keys that are used for heavyweight maps that should be discarded when the torrent is not in use
2453 	 * Make sure these keys are only ever used for Map objects!
2454 	 */
2455 	public static void
registerMapFluff( String[] fluff )2456 	registerMapFluff(
2457 		String[]		fluff )
2458 	{
2459 		synchronized (TorrentUtils.class)
2460 		{
2461 			for (int i = 0; i < fluff.length; i++)
2462 				torrentFluffKeyset.add(fluff[i]);
2463 		}
2464 	}
2465 
2466 	public interface
2467 	ExtendedTorrent
2468 		extends TOTorrent
2469 	{
2470 		public byte[][]
peekPieces()2471 		peekPieces()
2472 
2473 			throws TOTorrentException;
2474 
2475 		public void
setDiscardFluff( boolean discard )2476 		setDiscardFluff(
2477 			boolean	discard );
2478 	}
2479 
2480 	public static class
2481 	torrentDelegate
2482 		extends LogRelation
2483 		implements ExtendedTorrent
2484 	{
2485 		private TOTorrent		delegate;
2486 		private File			file;
2487 
2488 		private boolean			fluff_dirty;
2489 
2490 		private long			last_pieces_read_time	= SystemTime.getCurrentTime();
2491 
2492 		private URL							url_mod_last_pre;
2493 		private URL							url_mod_last_post;
2494 		private int							url_mod_last_seq;
2495 
2496 		private List<URL>					urlg_mod_last_pre;
2497 		private TOTorrentAnnounceURLGroup	urlg_mod_last_post;
2498 		private int							urlg_mod_last_seq;
2499 
2500 		protected
torrentDelegate( TOTorrent _delegate, File _file )2501 		torrentDelegate(
2502 			TOTorrent		_delegate,
2503 			File			_file )
2504 		{
2505 			delegate		= _delegate;
2506 			file			= _file;
2507 
2508 			synchronized( torrent_delegates ){
2509 
2510 				torrent_delegates.put( this, null );
2511 			}
2512 		}
2513 
2514 		public void
setDiscardFluff( boolean discard )2515 		setDiscardFluff(
2516 			boolean	discard )
2517 		{
2518 			if ( discard && !torrentFluffKeyset.isEmpty() ){
2519 
2520 				//System.out.println( "Discarded fluff for " + new String(getName()));
2521 
2522 				try{
2523 			   		getMonitor().enter();
2524 
2525 			   		try{
2526 				   			// if file is out of sync with fluff then force a write
2527 
2528 				   		if ( fluff_dirty ){
2529 
2530 					   		boolean[]	restored = restoreState( true, true );
2531 
2532 					   		delegate.serialiseToBEncodedFile( file );
2533 
2534 					   		fluff_dirty = false;
2535 
2536 					   		if ( restored[0] ){
2537 
2538 					   			discardPieces( SystemTime.getCurrentTime(), true );
2539 					   		}
2540 				   		}
2541 
2542 				   		for(Iterator it = torrentFluffKeyset.iterator();it.hasNext();){
2543 
2544 				   			delegate.setAdditionalMapProperty( (String)it.next(), fluffThombstone );
2545 				   		}
2546 			   		}catch( Throwable e ){
2547 
2548 			   			Debug.printStackTrace( e );
2549 			   		}
2550 				}finally{
2551 
2552 					getMonitor().exit();
2553 				}
2554 			}
2555 		}
2556 
2557 		public byte[]
getName()2558 		getName()
2559 		{
2560 			return( delegate.getName());
2561 		}
2562 
2563 		public boolean
isSimpleTorrent()2564 		isSimpleTorrent()
2565 		{
2566 			return( delegate.isSimpleTorrent());
2567 		}
2568 
2569 		public byte[]
getComment()2570 		getComment()
2571 		{
2572 			return( delegate.getComment());
2573 		}
2574 
2575 		public void
setComment( String comment )2576 		setComment(
2577 			String		comment )
2578 		{
2579 			delegate.setComment( comment );
2580 		}
2581 
2582 		public long
getCreationDate()2583 		getCreationDate()
2584 		{
2585 			return( delegate.getCreationDate());
2586 		}
2587 
2588 		public void
setCreationDate( long date )2589 		setCreationDate(
2590 			long		date )
2591 		{
2592 			delegate.setCreationDate( date );
2593 		}
2594 
2595 		public byte[]
getCreatedBy()2596 		getCreatedBy()
2597 		{
2598 			return( delegate.getCreatedBy());
2599 		}
2600 
2601 	 	public void
setCreatedBy( byte[] cb )2602 		setCreatedBy(
2603 			byte[]		cb )
2604 	   	{
2605 	  		delegate.setCreatedBy( cb );
2606 	   	}
2607 
2608 		public boolean
isCreated()2609 		isCreated()
2610 		{
2611 			return( delegate.isCreated());
2612 		}
2613 
2614 		public boolean
isDecentralised()2615 		isDecentralised()
2616 		{
2617     		URL	url = getAnnounceURLSupport();
2618 
2619     		return( TorrentUtils.isDecentralised( url ));
2620 		}
2621 
2622 	   	public URL
getAnnounceURL()2623     	getAnnounceURL()
2624     	{
2625     		URL	url = getAnnounceURLSupport();
2626 
2627     		int	seq = dns_mapping_seq_count;
2628 
2629     		if ( 	url == url_mod_last_pre &&
2630     				url_mod_last_post != null &&
2631     				seq == url_mod_last_seq ){
2632 
2633     			// System.out.println( "using old url: " + url + " -> " + url_mod_last_post );
2634 
2635     			return( url_mod_last_post );
2636     		}
2637 
2638       		url_mod_last_post 		= applyDNSMods( url );
2639       		url_mod_last_pre		= url;
2640     		url_mod_last_seq		= seq;
2641 
2642     		return( url_mod_last_post );
2643     	}
2644 
2645        	public TOTorrentAnnounceURLGroup
getAnnounceURLGroup()2646     	getAnnounceURLGroup()
2647     	{
2648        		TOTorrentAnnounceURLGroup group = getAnnounceURLGroupSupport();
2649 
2650        		int	seq = dns_mapping_seq_count;
2651 
2652        		if ( seq == urlg_mod_last_seq && urlg_mod_last_pre != null && urlg_mod_last_post != null ){
2653 
2654        			boolean	match = !(urlg_mod_last_post instanceof URLGroup && ((URLGroup)urlg_mod_last_post).hasBeenModified());
2655 
2656        			if ( match ){
2657 
2658 	      			TOTorrentAnnounceURLSet[]	sets = group.getAnnounceURLSets();
2659 
2660 	      			Iterator<URL>	it = urlg_mod_last_pre.iterator();
2661 
2662  outer:
2663 	      			for (int i=0;i<sets.length;i++){
2664 
2665 	      				URL[]	urls = sets[i].getAnnounceURLs();
2666 
2667 	   					for ( int j=0;j<urls.length;j++){
2668 
2669 	   						if ( !it.hasNext()){
2670 
2671 	   							match = false;
2672 
2673 	   							break outer;
2674 	   						}
2675 
2676 	   						if ( it.next() != urls[j] ){
2677 
2678 	   							match = false;
2679 
2680 	   							break;
2681 	   						}
2682 	      				}
2683 	      			}
2684 
2685 	      			if (it.hasNext()){
2686 
2687 	      				match = false;
2688 	      			}
2689        			}
2690 
2691       			if ( match ){
2692 
2693       	    		// System.out.println( "using old urlg: " + group + " -> " + urlg_mod_last_post );
2694 
2695       	    		return( urlg_mod_last_post );
2696       			}
2697        		}
2698 
2699        		List<URL>		url_list = new ArrayList<URL>();
2700 
2701  			TOTorrentAnnounceURLSet[]	sets = group.getAnnounceURLSets();
2702 
2703   			for (int i=0;i<sets.length;i++){
2704 
2705   				URL[]	urls = sets[i].getAnnounceURLs();
2706 
2707   				for ( URL u: urls ){
2708 
2709   					url_list.add( u );
2710   				}
2711   			}
2712 
2713        		urlg_mod_last_post	= applyDNSMods( getAnnounceURL(), group );
2714        		urlg_mod_last_pre	= url_list;
2715        		urlg_mod_last_seq	= seq;
2716 
2717        		return( urlg_mod_last_post );
2718     	}
2719 
2720 		public URL
getAnnounceURLSupport()2721 		getAnnounceURLSupport()
2722 		{
2723 			return( delegate.getAnnounceURL());
2724 		}
2725 
2726 		public boolean
setAnnounceURL( URL url )2727 		setAnnounceURL(
2728 			URL		url )
2729 		{
2730 			return delegate.setAnnounceURL( url );
2731 		}
2732 
2733 
2734 		public TOTorrentAnnounceURLGroup
getAnnounceURLGroupSupport()2735 		getAnnounceURLGroupSupport()
2736 		{
2737 			return( delegate.getAnnounceURLGroup());
2738 		}
2739 
2740 		protected void
discardPieces( long now, boolean force )2741 		discardPieces(
2742 			long		now,
2743 			boolean		force )
2744 		{
2745 				// handle clock changes backwards
2746 
2747 			if ( now < last_pieces_read_time && !force ){
2748 
2749 				last_pieces_read_time	= now;
2750 
2751 			}else{
2752 
2753 				try{
2754 					if(		( now - last_pieces_read_time > PIECE_HASH_TIMEOUT || force ) &&
2755 							delegate.getPieces() != null ){
2756 
2757 						try{
2758 							getMonitor().enter();
2759 
2760 							// System.out.println( "clearing pieces for '" + new String(getName()) + "'");
2761 
2762 							delegate.setPieces( null );
2763 						}finally{
2764 
2765 							getMonitor().exit();
2766 						}
2767 					}
2768 				}catch( Throwable e ){
2769 
2770 					Debug.printStackTrace(e);
2771 				}
2772 			}
2773 		}
2774 
2775 		public byte[][]
getPieces()2776 		getPieces()
2777 
2778 			throws TOTorrentException
2779 		{
2780 			byte[][]	res = delegate.getPieces();
2781 
2782 			last_pieces_read_time	= SystemTime.getCurrentTime();
2783 
2784 			if ( res == null ){
2785 
2786 				// System.out.println( "recovering pieces for '" + new String(getName()) + "'");
2787 
2788 				try{
2789 			   		getMonitor().enter();
2790 
2791 			   		restoreState( true, false );
2792 
2793 			   		res = delegate.getPieces();
2794 
2795 				}finally{
2796 
2797 					getMonitor().exit();
2798 				}
2799 			}
2800 
2801 			return( res );
2802 		}
2803 
2804 		/**
2805 		 * monitor must be held before calling me
2806 		 * @param do_pieces
2807 		 * @param do_fluff
2808 		 * @throws TOTorrentException
2809 		 */
2810 
2811 		protected boolean[]
restoreState( boolean do_pieces, boolean do_fluff )2812 		restoreState(
2813 			boolean		do_pieces,
2814 			boolean		do_fluff )
2815 
2816 			throws TOTorrentException
2817 		{
2818 	   		boolean	had_pieces = delegate.getPieces() != null;
2819 
2820 	   		boolean	had_fluff = true;
2821 
2822 	   		for(Iterator it = torrentFluffKeyset.iterator();it.hasNext();){
2823 
2824 	   			had_fluff &= delegate.getAdditionalMapProperty( (String)it.next() ) != fluffThombstone;
2825 	   		}
2826 
2827 	   		if ( had_pieces ){
2828 
2829 	   			do_pieces = false;
2830 	   		}
2831 
2832 	   		if ( had_fluff ){
2833 
2834 	   			do_fluff = false;
2835 	   		}
2836 
2837 	   		if ( do_pieces || do_fluff ){
2838 
2839 		   		TOTorrent	temp = readFromFile( file, false );
2840 
2841 		   		if ( do_pieces ){
2842 
2843 		   			byte[][] res	= temp.getPieces();
2844 
2845 		   			delegate.setPieces( res );
2846 		   		}
2847 
2848 		   		if ( do_fluff ){
2849 
2850 		   			for (Iterator it = torrentFluffKeyset.iterator(); it.hasNext();){
2851 
2852 						String fluffKey = (String) it.next();
2853 
2854 							// only update the discarded entries as non-discarded may be out of sync
2855 							// with the file contents
2856 
2857 						if ( delegate.getAdditionalMapProperty( fluffKey ) == fluffThombstone ){
2858 
2859 							delegate.setAdditionalMapProperty(fluffKey, temp.getAdditionalMapProperty(fluffKey));
2860 						}
2861 					}
2862 		   		}
2863 	   		}
2864 
2865 	   		return( new boolean[]{ do_pieces, do_fluff });
2866 		}
2867 
2868 			/**
2869 			 * peeks the pieces, will return null if they are discarded
2870 			 * @return
2871 			 */
2872 
2873 		public byte[][]
peekPieces()2874 		peekPieces()
2875 
2876 			throws TOTorrentException
2877 		{
2878 			return( delegate.getPieces());
2879 		}
2880 
2881 		public void
setPieces( byte[][] pieces )2882 		setPieces(
2883 			byte[][]	pieces )
2884 
2885 			throws TOTorrentException
2886 		{
2887 			throw( new TOTorrentException( "Unsupported Operation", TOTorrentException.RT_WRITE_FAILS ));
2888 		}
2889 
2890 		public long
getPieceLength()2891 		getPieceLength()
2892 		{
2893 			return( delegate.getPieceLength());
2894 		}
2895 
2896 		public int
getNumberOfPieces()2897 		getNumberOfPieces()
2898 		{
2899 			return( delegate.getNumberOfPieces());
2900 		}
2901 
2902 		public long
getSize()2903 		getSize()
2904 		{
2905 			return( delegate.getSize());
2906 		}
2907 
2908 		public int
getFileCount()2909 		getFileCount()
2910 		{
2911 			return( delegate.getFileCount());
2912 		}
2913 
2914 		public TOTorrentFile[]
getFiles()2915 		getFiles()
2916 		{
2917 			return( delegate.getFiles());
2918 		}
2919 
2920 		public byte[]
getHash()2921 		getHash()
2922 
2923 			throws TOTorrentException
2924 		{
2925 			return( delegate.getHash());
2926 		}
2927 
2928 		public HashWrapper
getHashWrapper()2929 		getHashWrapper()
2930 
2931 			throws TOTorrentException
2932 		{
2933 			return( delegate.getHashWrapper());
2934 		}
2935 
2936 	   	public void
setHashOverride( byte[] hash )2937     	setHashOverride(
2938     		byte[] hash )
2939 
2940     		throws TOTorrentException
2941     	{
2942     		throw( new TOTorrentException( "Not supported", TOTorrentException.RT_HASH_FAILS ));
2943     	}
2944 
2945 		public boolean
getPrivate()2946 		getPrivate()
2947 		{
2948 			return( delegate.getPrivate());
2949 		}
2950 
2951 		public void
setPrivate( boolean _private )2952 		setPrivate(
2953 			boolean	_private )
2954 
2955 			throws TOTorrentException
2956 		{
2957 				// don't support this as it changes teh torrent hash
2958 
2959 			throw( new TOTorrentException( "Can't amend private attribute", TOTorrentException.RT_WRITE_FAILS ));
2960 		}
2961 
2962 		public boolean
hasSameHashAs( TOTorrent other )2963 		hasSameHashAs(
2964 			TOTorrent		other )
2965 		{
2966 			return( delegate.hasSameHashAs( other ));
2967 		}
2968 
2969 		public void
setAdditionalStringProperty( String name, String value )2970 		setAdditionalStringProperty(
2971 			String		name,
2972 			String		value )
2973 		{
2974 			delegate.setAdditionalStringProperty( name, value );
2975 		}
2976 
2977 		public String
getAdditionalStringProperty( String name )2978 		getAdditionalStringProperty(
2979 			String		name )
2980 		{
2981 			return( delegate.getAdditionalStringProperty( name ));
2982 		}
2983 
2984 		public void
setAdditionalByteArrayProperty( String name, byte[] value )2985 		setAdditionalByteArrayProperty(
2986 			String		name,
2987 			byte[]		value )
2988 		{
2989 			delegate.setAdditionalByteArrayProperty( name, value );
2990 		}
2991 
2992 		public byte[]
getAdditionalByteArrayProperty( String name )2993 		getAdditionalByteArrayProperty(
2994 			String		name )
2995 		{
2996 			return( delegate.getAdditionalByteArrayProperty( name ));
2997 		}
2998 
2999 		public void
setAdditionalLongProperty( String name, Long value )3000 		setAdditionalLongProperty(
3001 			String		name,
3002 			Long		value )
3003 		{
3004 			delegate.setAdditionalLongProperty( name, value );
3005 		}
3006 
3007 		public Long
getAdditionalLongProperty( String name )3008 		getAdditionalLongProperty(
3009 			String		name )
3010 		{
3011 			return( delegate.getAdditionalLongProperty( name ));
3012 		}
3013 
3014 
3015 		public void
setAdditionalListProperty( String name, List value )3016 		setAdditionalListProperty(
3017 			String		name,
3018 			List		value )
3019 		{
3020 			delegate.setAdditionalListProperty( name, value );
3021 		}
3022 
3023 		public List
getAdditionalListProperty( String name )3024 		getAdditionalListProperty(
3025 			String		name )
3026 		{
3027 			return( delegate.getAdditionalListProperty( name ));
3028 		}
3029 
3030 		public void
setAdditionalMapProperty( String name, Map value )3031 		setAdditionalMapProperty(
3032 			String		name,
3033 			Map			value )
3034 		{
3035 			if ( torrentFluffKeyset.contains(name)){
3036 
3037 				//System.out.println( "Set fluff for " + new String(getName()) + " to " + value );
3038 
3039 				try{
3040 					getMonitor().enter();
3041 
3042 					delegate.setAdditionalMapProperty( name, value );
3043 
3044 					fluff_dirty = true;
3045 
3046 				}finally{
3047 
3048 					getMonitor().exit();
3049 				}
3050 			}else{
3051 
3052 				delegate.setAdditionalMapProperty( name, value );
3053 			}
3054 		}
3055 
3056 		public Map
getAdditionalMapProperty( String name )3057 		getAdditionalMapProperty(
3058 			String		name )
3059 		{
3060 			if (torrentFluffKeyset.contains(name)){
3061 
3062 				try{
3063 					getMonitor().enter();
3064 
3065 					Map	result = delegate.getAdditionalMapProperty( name );
3066 
3067 					if ( result == fluffThombstone ){
3068 
3069 						try{
3070 							restoreState( false, true );
3071 
3072 							Map res = delegate.getAdditionalMapProperty( name );
3073 
3074 							//System.out.println( "Restored fluff for " + new String(getName()) + " to " + res );
3075 
3076 							return( res );
3077 
3078 						}catch( Throwable e ){
3079 
3080 							Debug.out( "Property '" + name + " lost due to torrent read error", e );
3081 						}
3082 					}
3083 				}finally{
3084 
3085 					getMonitor().exit();
3086 				}
3087 			}
3088 
3089 			return( delegate.getAdditionalMapProperty( name ));
3090 		}
3091 
getAdditionalProperty(String name)3092 		public Object getAdditionalProperty(String name) {
3093 			if (torrentFluffKeyset.contains(name))
3094 			{
3095 				try
3096 				{
3097 					getMonitor().enter();
3098 
3099 					Object result = delegate.getAdditionalProperty(name);
3100 					if (result == fluffThombstone)
3101 					{
3102 						try
3103 						{
3104 							restoreState(false, true);
3105 							Object res = delegate.getAdditionalProperty(name);
3106 							//System.out.println( "Restored fluff for " + new String(getName()) + " to " + res );
3107 
3108 							return (res);
3109 
3110 						} catch (Throwable e)
3111 						{
3112 							Debug.out("Property '" + name + " lost due to torrent read error", e);
3113 						}
3114 					}
3115 				} finally
3116 				{
3117 					getMonitor().exit();
3118 				}
3119 			}
3120 
3121 			return delegate.getAdditionalProperty(name);
3122 		}
3123 
3124 		public void
setAdditionalProperty( String name, Object value )3125 		setAdditionalProperty(
3126 			String		name,
3127 			Object		value )
3128 		{
3129 			if ( torrentFluffKeyset.contains(name)){
3130 
3131 				//System.out.println( "Set fluff for " + new String(getName()) + " to " + value );
3132 
3133 				try{
3134 					getMonitor().enter();
3135 
3136 					delegate.setAdditionalProperty( name, value );
3137 
3138 					fluff_dirty = true;
3139 
3140 				}finally{
3141 
3142 					getMonitor().exit();
3143 				}
3144 			}else{
3145 
3146 				delegate.setAdditionalProperty( name, value );
3147 			}
3148 		}
3149 
3150 		public void
removeAdditionalProperty( String name )3151 		removeAdditionalProperty(
3152 			String name )
3153 		{
3154 			if(delegate.getAdditionalProperty(name) == null)
3155 				return;
3156 
3157 			if ( torrentFluffKeyset.contains(name)){
3158 
3159 				//System.out.println( "Set fluff for " + new String(getName()) + " to " + value );
3160 
3161 				try{
3162 					getMonitor().enter();
3163 
3164 					delegate.removeAdditionalProperty( name );
3165 
3166 					fluff_dirty = true;
3167 
3168 				}finally{
3169 
3170 					getMonitor().exit();
3171 				}
3172 			}else{
3173 
3174 				delegate.removeAdditionalProperty( name );
3175 			}
3176 		}
3177 
3178 		public void
removeAdditionalProperties()3179 		removeAdditionalProperties()
3180 		{
3181 			try{
3182 				getMonitor().enter();
3183 
3184 				delegate.removeAdditionalProperties();
3185 
3186 				fluff_dirty = true;
3187 
3188 			}finally{
3189 
3190 				getMonitor().exit();
3191 			}
3192 		}
3193 
3194 		public void
serialiseToBEncodedFile( File target_file )3195 		serialiseToBEncodedFile(
3196 			File		target_file )
3197 
3198 			throws TOTorrentException
3199 		{
3200 				// make sure pieces are current
3201 
3202 			try{
3203 		   		getMonitor().enter();
3204 
3205 		   		boolean[]	restored = restoreState( true, true );
3206 
3207 		   		delegate.serialiseToBEncodedFile( target_file );
3208 
3209 		   		if ( target_file.equals( file )){
3210 
3211 		   			fluff_dirty = false;
3212 		   		}
3213 
3214 		   		if ( restored[0] ){
3215 
3216 		   			discardPieces( SystemTime.getCurrentTime(), true );
3217 		   		}
3218 
3219 		   		if ( restored[1] ){
3220 
3221 		   			for (Iterator it = torrentFluffKeyset.iterator(); it.hasNext();){
3222 
3223 		   				delegate.setAdditionalMapProperty( (String)it.next(), fluffThombstone );
3224 		   			}
3225 		   		}
3226 			}finally{
3227 
3228 				getMonitor().exit();
3229 			}
3230 		}
3231 
3232 
3233 		public Map
serialiseToMap()3234 		serialiseToMap()
3235 
3236 			throws TOTorrentException
3237 		{
3238 				// make sure pieces are current
3239 
3240 			try{
3241 		   		getMonitor().enter();
3242 
3243 		   		boolean[]	restored = restoreState( true, true );
3244 
3245 		   		Map	result = delegate.serialiseToMap();
3246 
3247 		   		if ( restored[0] ){
3248 
3249 		   			discardPieces( SystemTime.getCurrentTime(), true );
3250 		   		}
3251 
3252 		   		if ( restored[1]){
3253 
3254 					for (Iterator it = torrentFluffKeyset.iterator(); it.hasNext();){
3255 
3256 						delegate.setAdditionalMapProperty((String) it.next(), fluffThombstone);
3257 					}
3258 				}
3259 
3260 		   		return( result );
3261 
3262 			}finally{
3263 
3264 				getMonitor().exit();
3265 			}
3266 		}
3267 
3268 
3269 		public void
serialiseToXMLFile( File target_file )3270 		serialiseToXMLFile(
3271 			File		target_file )
3272 
3273 		   throws TOTorrentException
3274 		{
3275 				// make sure pieces are current
3276 
3277 			try{
3278 		   		getMonitor().enter();
3279 
3280 		   		boolean[]	restored = restoreState( true, true );
3281 
3282 		   		delegate.serialiseToXMLFile( target_file );
3283 
3284 		   		if ( restored[0] ){
3285 
3286 		   			discardPieces( SystemTime.getCurrentTime(), true );
3287 		   		}
3288 
3289 		   		if ( restored[1]){
3290 
3291 					for (Iterator it = torrentFluffKeyset.iterator(); it.hasNext();){
3292 
3293 						delegate.setAdditionalMapProperty((String) it.next(), fluffThombstone);
3294 					}
3295 				}
3296 			}finally{
3297 
3298 				getMonitor().exit();
3299 			}
3300 		}
3301 
3302 	 	public void
addListener( TOTorrentListener l )3303 		addListener(
3304 			TOTorrentListener		l )
3305 		{
3306 	 		delegate.addListener( l );
3307 		}
3308 
3309 		public void
removeListener( TOTorrentListener l )3310 		removeListener(
3311 			TOTorrentListener		l )
3312 		{
3313 	 		delegate.removeListener( l );
3314 		}
3315 
3316 		public AEMonitor
getMonitor()3317 		getMonitor()
3318 		{
3319 	   		return( delegate.getMonitor());
3320 		}
3321 
3322 
3323 		public void
print()3324 		print()
3325 		{
3326 			delegate.print();
3327 		}
3328 
getRelationText()3329 		public String getRelationText() {
3330 			if (delegate instanceof LogRelation)
3331 				return ((LogRelation)delegate).getRelationText();
3332 			return delegate.toString();
3333 		}
3334 
getQueryableInterfaces()3335 		public Object[] getQueryableInterfaces() {
3336 			if (delegate instanceof LogRelation)
3337 				return ((LogRelation)delegate).getQueryableInterfaces();
3338 			return super.getQueryableInterfaces();
3339 		}
3340 
getUTF8Name()3341 		public String getUTF8Name() {
3342 			return delegate.getUTF8Name();
3343 		}
3344 	}
3345 
3346 	/**
3347 	 * Copy a file to the Torrent Save Directory, taking into account all the
3348 	 * user config options related to that.
3349 	 * <p>
3350 	 * Also makes the directory if it doesn't exist.
3351 	 *
3352 	 * @param f File to copy
3353 	 * @param persistent Whether the torrent is persistent
3354 	 * @return File after it's been copied (may be the same as f)
3355 	 * @throws IOException
3356 	 */
copyTorrentFileToSaveDir(File f, boolean persistent)3357 	public static File copyTorrentFileToSaveDir(File f, boolean persistent)
3358 			throws IOException {
3359 		File torrentDir;
3360 		boolean saveTorrents = persistent
3361 				&& COConfigurationManager.getBooleanParameter("Save Torrent Files");
3362 		if (saveTorrents)
3363 			torrentDir = new File(COConfigurationManager
3364 					.getDirectoryParameter("General_sDefaultTorrent_Directory"));
3365 		else
3366 			torrentDir = new File(f.getParent());
3367 
3368 		//if the torrent is already in the completed files dir, use this
3369 		//torrent instead of creating a new one in the default dir
3370 		boolean moveWhenDone = COConfigurationManager.getBooleanParameter("Move Completed When Done");
3371 		String completedDir = COConfigurationManager.getStringParameter(
3372 				"Completed Files Directory", "");
3373 		if (moveWhenDone && completedDir.length() > 0) {
3374 			File cFile = new File(completedDir, f.getName());
3375 			if (cFile.exists()) {
3376 				//set the torrentDir to the completedDir
3377 				torrentDir = new File(completedDir);
3378 			}
3379 		}
3380 
3381 		FileUtil.mkdirs(torrentDir);
3382 
3383 		File fDest = new File(torrentDir, f.getName().replaceAll("%20", "."));
3384 		if (fDest.equals(f)) {
3385 			return f;
3386 		}
3387 
3388 		while (fDest.exists()) {
3389 			fDest = new File(torrentDir, "_" + fDest.getName());
3390 		}
3391 
3392 		fDest.createNewFile();
3393 
3394 		if (!FileUtil.copyFile(f, fDest)) {
3395 			throw new IOException("File copy failed");
3396 		}
3397 
3398 		if ( shouldDeleteTorrentFileAfterAdd( f, persistent )){
3399 
3400 			f.delete();
3401 		}
3402 
3403 		return fDest;
3404 	}
3405 
3406 	public static boolean
shouldDeleteTorrentFileAfterAdd( File f, boolean persistent )3407 	shouldDeleteTorrentFileAfterAdd(
3408 		File		f,
3409 		boolean		persistent )
3410 	{
3411 		if ( !persistent ){
3412 
3413 			return( false );
3414 		}
3415 
3416 		boolean delTorrents = COConfigurationManager.getBooleanParameter("Delete Original Torrent Files");
3417 
3418 		if ( !delTorrents ){
3419 
3420 			return( false );
3421 		}
3422 
3423 		boolean saveTorrents = COConfigurationManager.getBooleanParameter("Save Torrent Files");
3424 
3425 		if ( saveTorrents ){
3426 
3427 			try{
3428 				File torrentDir = new File(COConfigurationManager.getDirectoryParameter("General_sDefaultTorrent_Directory"));
3429 
3430 				if ( torrentDir.isDirectory() && torrentDir.equals( f.getParentFile())){
3431 
3432 					return( false );
3433 				}
3434 			}catch( Throwable e ){
3435 
3436 				return( false );
3437 			}
3438 		}
3439 
3440 		File active_dir = FileUtil.getUserFile( "active" );
3441 
3442 		return( !active_dir.equals( f.getParentFile()));
3443 	}
3444 
3445 	/**
3446 	 * Get the DownloadManager related to a torrent's hashBytes
3447 	 *
3448 	 * @param hashBytes
3449 	 * @return
3450 	 */
getDownloadManager( HashWrapper hash )3451   public static DownloadManager getDownloadManager( HashWrapper	hash ) {
3452 		try {
3453 			return AzureusCoreFactory.getSingleton().getGlobalManager()
3454 					.getDownloadManager(hash);
3455 		} catch (Exception e) {
3456 			return null;
3457 		}
3458 	}
3459 
3460 	/**
3461 	 * Deletes the given dir and all dirs underneath if empty.
3462 	 * Don't delete default save path or completed files directory, however,
3463 	 * allow deletion of their empty subdirectories
3464 	 * Files defined to be ignored for the sake of torrent creation are automatically deleted
3465 	 * For example, by default this includes thumbs.db
3466 	 */
recursiveEmptyDirDelete(File f)3467 	public static void recursiveEmptyDirDelete(File f) {
3468 		TorrentUtils.recursiveEmptyDirDelete(f, true);
3469 	}
3470 
3471 	/**
3472 	 * Same as #recursiveEmptyDirDelete(File), except allows disabling of logging
3473 	 * of any warnings
3474 	 *
3475 	 * @param f Dir to delete
3476 	 * @param log_warnings Whether to log warning
3477 	 */
recursiveEmptyDirDelete(File f, boolean log_warnings)3478 	public static void recursiveEmptyDirDelete(File f, boolean log_warnings) {
3479 		Set ignore_map = getIgnoreSet();
3480 
3481 		FileUtil.recursiveEmptyDirDelete(f, ignore_map, log_warnings);
3482 	}
3483 
3484 	/**
3485 	 * A nice string of a Torrent's hash
3486 	 *
3487 	 * @param torrent Torrent to fromat hash of
3488 	 * @return Hash string in a nice format
3489 	 */
nicePrintTorrentHash(TOTorrent torrent)3490 	public static String nicePrintTorrentHash(TOTorrent torrent) {
3491 		return nicePrintTorrentHash(torrent, false);
3492 	}
3493 
3494 	/**
3495 	 * A nice string of a Torrent's hash
3496 	 *
3497 	 * @param torrent Torrent to fromat hash of
3498 	 * @param tight No spaces between groups of numbers
3499 	 *
3500 	 * @return Hash string in a nice format
3501 	 */
nicePrintTorrentHash(TOTorrent torrent, boolean tight)3502 	public static String nicePrintTorrentHash(TOTorrent torrent, boolean tight) {
3503 		byte[] hash;
3504 
3505 		if (torrent == null) {
3506 
3507 			hash = new byte[20];
3508 		} else {
3509 			try {
3510 				hash = torrent.getHash();
3511 
3512 			} catch (TOTorrentException e) {
3513 
3514 				Debug.printStackTrace(e);
3515 
3516 				hash = new byte[20];
3517 			}
3518 		}
3519 
3520 		return (ByteFormatter.nicePrint(hash, tight));
3521 	}
3522 
3523 	/**
3524 	 * Runs a file through a series of test to verify if it is a torrent.
3525 	 *
3526 	 * @param filename File to test
3527 	 * @return true - file is a valid torrent file
3528 	 *
3529 	 * @throws FileNotFoundException
3530 	 * @throws IOException
3531 	 */
isTorrentFile(String filename)3532 	public static boolean isTorrentFile(String filename) throws FileNotFoundException, IOException {
3533 	  File check = new File(filename);
3534 	  if (!check.exists())
3535 	    throw new FileNotFoundException("File "+filename+" not found.");
3536 	  if (!check.canRead())
3537 	    throw new IOException("File "+filename+" cannot be read.");
3538 	  if (check.isDirectory())
3539 	    throw new FileIsADirectoryException("File "+filename+" is a directory.");
3540 	  try {
3541 	    TOTorrentFactory.deserialiseFromBEncodedFile(check);
3542 	    return true;
3543 	  } catch (Throwable e) {
3544 	    return false;
3545 	  }
3546 	}
3547 
3548 	public static void
addCreatedTorrent( TOTorrent torrent )3549 	addCreatedTorrent(
3550 		TOTorrent		torrent )
3551 	{
3552 		synchronized( created_torrents ){
3553 
3554 			try{
3555 				byte[]	hash = torrent.getHash();
3556 
3557 				HashWrapper	hw = new HashWrapper( hash );
3558 
3559 				boolean dirty = false;
3560 
3561 				long check = COConfigurationManager.getLongParameter("my.created.torrents.check", 0  );
3562 
3563 				COConfigurationManager.setParameter("my.created.torrents.check", check+1  );
3564 
3565 				if ( check % 200 == 0 ){
3566 
3567 					try{
3568 						List<DownloadManager> dms = AzureusCoreFactory.getSingleton().getGlobalManager().getDownloadManagers();
3569 
3570 						Set<HashWrapper> actual_hashes = new HashSet<HashWrapper>();
3571 
3572 						for ( DownloadManager dm: dms ){
3573 
3574 							TOTorrent t = dm.getTorrent();
3575 
3576 							if ( t != null ){
3577 
3578 								try{
3579 									actual_hashes.add( t.getHashWrapper());
3580 
3581 								}catch( Throwable e ){
3582 
3583 								}
3584 							}
3585 						}
3586 
3587 						Iterator<byte[]> it = created_torrents.iterator();
3588 
3589 						int deleted = 0;
3590 
3591 						while( it.hasNext()){
3592 
3593 							HashWrapper	existing_hw = new HashWrapper( it.next());
3594 
3595 							if ( !actual_hashes.contains( existing_hw ) && !existing_hw.equals( hw )){
3596 
3597 								it.remove();
3598 
3599 								created_torrents_set.remove( existing_hw );
3600 
3601 								deleted++;
3602 
3603 								dirty = true;
3604 							}
3605 						}
3606 					}catch( Throwable e ){
3607 					}
3608 				}
3609 
3610 				//System.out.println( "addCreated:" + new String(torrent.getName()) + "/" + ByteFormatter.encodeString( hash ));
3611 
3612 				if ( created_torrents.size() == 0 ){
3613 
3614 					COConfigurationManager.setParameter( "my.created.torrents", created_torrents );
3615 				}
3616 
3617 				if ( !created_torrents_set.contains( hw )){
3618 
3619 					created_torrents.add( hash );
3620 
3621 					created_torrents_set.add( hw );
3622 
3623 					dirty = true;
3624 				}
3625 
3626 				if ( dirty ){
3627 
3628 					COConfigurationManager.setDirty();
3629 				}
3630 			}catch( TOTorrentException e ){
3631 
3632 			}
3633 		}
3634 	}
3635 
3636 	public static void
removeCreatedTorrent( TOTorrent torrent )3637 	removeCreatedTorrent(
3638 		TOTorrent		torrent )
3639 	{
3640 		synchronized( created_torrents ){
3641 
3642 			try{
3643 				HashWrapper	hw = torrent.getHashWrapper();
3644 
3645 				byte[]		hash	= hw.getBytes();
3646 
3647 				//System.out.println( "removeCreated:" + new String(torrent.getName()) + "/" + ByteFormatter.encodeString( hash ));
3648 
3649 				Iterator<byte[]>	it = created_torrents.iterator();
3650 
3651 				while( it.hasNext()){
3652 
3653 					byte[]	h = it.next();
3654 
3655 					if ( Arrays.equals( hash, h )){
3656 
3657 						it.remove();
3658 					}
3659 				}
3660 
3661 				COConfigurationManager.setDirty();
3662 
3663 				created_torrents_set.remove( hw );
3664 
3665 			}catch( TOTorrentException e ){
3666 
3667 			}
3668 		}
3669 	}
3670 
3671 	public static boolean
isCreatedTorrent( TOTorrent torrent )3672 	isCreatedTorrent(
3673 		TOTorrent		torrent )
3674 	{
3675 		synchronized( created_torrents ){
3676 
3677 			try{
3678 				HashWrapper	hw = torrent.getHashWrapper();
3679 
3680 				boolean	res = created_torrents_set.contains( hw );
3681 
3682 					// if we don't have a persistent record of creation, check the non-persisted version
3683 
3684 				if ( !res ){
3685 
3686 					res = torrent.isCreated();
3687 				}
3688 
3689 				// System.out.println( "isCreated:" + new String(torrent.getName()) + "/" + ByteFormatter.encodeString( hw.getBytes()) + " -> " + res );
3690 
3691 				return( res );
3692 
3693 			}catch( TOTorrentException e ){
3694 
3695 				Debug.printStackTrace(e);
3696 
3697 				return( false );
3698 
3699 			}
3700 		}
3701 	}
3702 
3703 	public static TOTorrent
download( URL url )3704 	download(
3705 		URL		url )
3706 
3707 		throws IOException
3708 	{
3709 		return( download( url, 0 ));
3710 	}
3711 
3712 	public static TOTorrent
download( URL url, long timeout )3713 	download(
3714 		URL		url,
3715 		long	timeout )
3716 
3717 		throws IOException
3718 	{
3719 		try{
3720 			PluginProxy	plugin_proxy = null;
3721 
3722 			try{
3723 				if ( AENetworkClassifier.categoriseAddress( url.getHost()) != AENetworkClassifier.AT_PUBLIC ){
3724 
3725 					plugin_proxy = AEProxyFactory.getPluginProxy( "torrent download", url );
3726 				}
3727 
3728 				ResourceDownloader rd;
3729 
3730 				if ( plugin_proxy == null ){
3731 
3732 					rd = new ResourceDownloaderFactoryImpl().create( url );
3733 
3734 				}else{
3735 
3736 					rd = new ResourceDownloaderFactoryImpl().create( plugin_proxy.getURL(), plugin_proxy.getProxy());
3737 
3738 					rd.setProperty( "URL_HOST", url.getHost());
3739 				}
3740 
3741 				if ( timeout > 0 ){
3742 
3743 					rd.setProperty( "URL_Connect_Timeout", timeout );
3744 					rd.setProperty( "URL_Read_Timeout", timeout );
3745 				}
3746 
3747 				byte[] bytes = FileUtil.readInputStreamAsByteArray( rd.download(), BDecoder.MAX_BYTE_ARRAY_SIZE );
3748 
3749 				return( TOTorrentFactory.deserialiseFromBEncodedByteArray( bytes ));
3750 
3751 			}finally{
3752 
3753 				if (plugin_proxy != null ){
3754 
3755 					plugin_proxy.setOK( true );
3756 				}
3757 			}
3758 		}catch( IOException e ){
3759 
3760 			throw((IOException)e);
3761 
3762 		}catch( Throwable e ){
3763 
3764 			throw( new IOException( Debug.getNestedExceptionMessage( e )));
3765 		}
3766 	}
3767 
3768 	private static void
fireAttributeListener( TOTorrent torrent, String attribute, Object value )3769 	fireAttributeListener(
3770 		TOTorrent		torrent,
3771 		String			attribute,
3772 		Object			value )
3773 	{
3774 		Iterator it = torrent_attribute_listeners.iterator();
3775 
3776 		while( it.hasNext()){
3777 
3778 			try{
3779 				((torrentAttributeListener)it.next()).attributeSet(torrent, attribute, value);
3780 
3781 			}catch( Throwable e ){
3782 
3783 				Debug.printStackTrace(e);
3784 			}
3785 		}
3786 	}
3787 
3788 	public static void
addTorrentAttributeListener( torrentAttributeListener listener )3789 	addTorrentAttributeListener(
3790 		torrentAttributeListener	listener )
3791 	{
3792 		torrent_attribute_listeners.add( listener );
3793 	}
3794 
3795 	public static void
removeTorrentAttributeListener( torrentAttributeListener listener )3796 	removeTorrentAttributeListener(
3797 		torrentAttributeListener	listener )
3798 	{
3799 		torrent_attribute_listeners.remove( listener );
3800 	}
3801 
3802 	public static void
addTorrentURLChangeListener( TorrentAnnounceURLChangeListener listener )3803 	addTorrentURLChangeListener(
3804 		TorrentAnnounceURLChangeListener	listener )
3805 	{
3806 		torrent_url_changed_listeners.add( listener );
3807 	}
3808 
3809 	public static void
removeTorrentURLChangeListener( TorrentAnnounceURLChangeListener listener )3810 	removeTorrentURLChangeListener(
3811 		TorrentAnnounceURLChangeListener	listener )
3812 	{
3813 		torrent_url_changed_listeners.remove( listener );
3814 	}
3815 
3816 
3817 
3818 	private static final Pattern txt_pattern = Pattern.compile( "(UDP|TCP):([0-9]+)");
3819 
3820 	private static DNSTXTEntry
getDNSTXTEntry( URL url )3821 	getDNSTXTEntry(
3822 		URL		url )
3823 	{
3824 		if ( isDecentralised( url )){
3825 
3826 			return( null );
3827 		}
3828 
3829 		String host = url.getHost();
3830 
3831 		String	tracker_network	= AENetworkClassifier.categoriseAddress( host );
3832 
3833 		if ( tracker_network != AENetworkClassifier.AT_PUBLIC ){
3834 
3835 			return( null );
3836 		}
3837 
3838 		return( getDNSTXTEntry( host, false, null ));
3839 	}
3840 
3841 	private static void
checkDNSTimeouts()3842 	checkDNSTimeouts()
3843 	{
3844 		final List<String>	hosts = new ArrayList<String>();
3845 
3846 		long now = SystemTime.getMonotonousTime();
3847 
3848 		synchronized( dns_mapping ){
3849 
3850 			for ( Map.Entry<String,DNSTXTEntry> entry: dns_mapping.entrySet()){
3851 
3852 				DNSTXTEntry txt_entry = entry.getValue();
3853 
3854 				if ( now - txt_entry.getCreateTime() > DNS_HISTORY_TIMEOUT ){
3855 
3856 					hosts.add( entry.getKey());
3857 				}
3858 			}
3859 		}
3860 
3861 		if ( hosts.size() > 0 ){
3862 
3863 			new AEThread2( "DNS:updates" )
3864 			{
3865 				public void
3866 				run()
3867 				{
3868 					for ( String host: hosts ){
3869 
3870 						getDNSTXTEntry( host, true, null );
3871 					}
3872 				}
3873 			}.start();
3874 		}
3875 	}
3876 
3877 	private static DNSTXTEntry
getDNSTXTEntry( final String host, boolean force_update, final List<String> already_got_records )3878 	getDNSTXTEntry(
3879 		final String			host,
3880 		boolean					force_update,
3881 		final List<String>		already_got_records )
3882 	{
3883 		if ( TRACE_DNS ){
3884 			System.out.println( "Getting DNS records for " + host + ", force=" + force_update + ", got=" + already_got_records );
3885 		}
3886 
3887 		DNSTXTEntry		txt_entry;
3888 		DNSTXTEntry		old_txt_entry;
3889 
3890 		boolean			is_new = false;
3891 
3892 		synchronized( dns_mapping ){
3893 
3894 			old_txt_entry = txt_entry = dns_mapping.get( host );
3895 
3896 			if ( txt_entry != null && SystemTime.getMonotonousTime() - txt_entry.getCreateTime() > DNS_HISTORY_TIMEOUT ){
3897 
3898 				force_update = true;
3899 			}
3900 
3901 			if ( force_update || txt_entry == null ){
3902 
3903 				txt_entry = new DNSTXTEntry();
3904 
3905 				dns_mapping.put( host, txt_entry );
3906 
3907 				is_new = true;
3908 			}
3909 		}
3910 
3911 		if ( is_new ){
3912 
3913 			String _config_key = "";
3914 
3915 			try{
3916 				_config_key = "dns.txts.cache." + Base32.encode( host.getBytes( "UTF-8" ));
3917 
3918 			}catch( Throwable e ){
3919 
3920 				Debug.out( e );
3921 			}
3922 
3923 			final String config_key	= _config_key;
3924 
3925 			if ( TRACE_DNS ){
3926 				System.out.println( "Updating DNS records for " + host );
3927 			}
3928 
3929 			try{
3930 				List<String> txts;
3931 
3932 				if ( already_got_records != null ){
3933 
3934 					txts = already_got_records;
3935 
3936 				}else{
3937 
3938 					final AESemaphore lookup_sem = new AESemaphore( "DU:ls" );
3939 
3940 					final Object[]	result = { null, null };
3941 
3942 					final DNSTXTEntry	f_txt_entry = txt_entry;
3943 
3944 					dns_threads.run(
3945 						new AERunnable()
3946 						{
3947 							public void
3948 							runSupport()
3949 							{
3950 								try{
3951 									List<String> txts = dns_utils.getTXTRecords( host );
3952 
3953 									if ( TRACE_DNS ){
3954 										System.out.println( "Actual lookup: " + host + " -> " + txts );
3955 									}
3956 
3957 									synchronized( result ){
3958 
3959 										if ( result[0] == null ){
3960 
3961 											result[1] = txts;
3962 
3963 											return;
3964 										}
3965 									}
3966 
3967 										// they gave up waiting
3968 
3969 									try{
3970 										List txts_cache = new ArrayList();
3971 
3972 										for ( String str: txts ){
3973 
3974 											txts_cache.add( str.getBytes( "UTF-8" ));
3975 										}
3976 
3977 										List old_txts_cache = COConfigurationManager.getListParameter( config_key, null );
3978 
3979 										boolean	same = false;
3980 
3981 										if ( old_txts_cache != null ){
3982 
3983 											same = old_txts_cache.size() == txts_cache.size();
3984 
3985 											if ( same ){
3986 
3987 												for ( int i=0;i<old_txts_cache.size();i++){
3988 
3989 													if ( !Arrays.equals((byte[])old_txts_cache.get(i),(byte[])txts_cache.get(i))){
3990 
3991 														same = false;
3992 
3993 														break;
3994 													}
3995 												}
3996 											}
3997 										}
3998 
3999 										if ( !same ){
4000 
4001 											COConfigurationManager.setParameter( config_key, txts_cache );
4002 
4003 											f_txt_entry.getSemaphore().reserve();
4004 
4005 											if ( already_got_records == null ){
4006 
4007 												getDNSTXTEntry( host, true, txts );
4008 											}
4009 										}
4010 									}catch( Throwable e ){
4011 
4012 										Debug.out( e );
4013 									}
4014 
4015 								}finally{
4016 
4017 									lookup_sem.release();
4018 								}
4019 							}
4020 						});
4021 
4022 					List txts_cache = COConfigurationManager.getListParameter( config_key, null );
4023 
4024 						// if we have a cache and this isn't a force update and start of day then well just go with the cache
4025 
4026 					if ( old_txt_entry != null || txts_cache == null || force_update ){
4027 
4028 						lookup_sem.reserve( 2500 );
4029 					}
4030 
4031 					synchronized( result ){
4032 
4033 						result[0] = "";
4034 
4035 						txts = (List<String>)result[1];
4036 					}
4037 
4038 					try{
4039 						if ( txts == null ){
4040 
4041 							txts = new ArrayList<String>();
4042 
4043 							if ( txts_cache == null ){
4044 
4045 								if ( TRACE_DNS ){
4046 									System.out.println( "    No cache" );
4047 								}
4048 							}else{
4049 
4050 								for ( Object o: txts_cache ){
4051 
4052 									txts.add( new String((byte[])o, "UTF-8" ));
4053 								}
4054 
4055 								if ( TRACE_DNS ){
4056 									System.out.println( "    Using cache: " + txts );
4057 								}
4058 							}
4059 						}else{
4060 
4061 							txts_cache = new ArrayList();
4062 
4063 							for ( String str: txts ){
4064 
4065 								txts_cache.add( str.getBytes( "UTF-8" ));
4066 							}
4067 
4068 							COConfigurationManager.setParameter( config_key, txts_cache );
4069 						}
4070 					}catch( Throwable e ){
4071 
4072 						Debug.out( e );
4073 					}
4074 				}
4075 
4076 				boolean	found_bt = false;
4077 
4078 				for ( String txt: txts ){
4079 
4080 					if ( txt.startsWith( "BITTORRENT" )){
4081 
4082 						found_bt = true;
4083 
4084 						Matcher matcher = txt_pattern.matcher( txt.substring( 10 ));
4085 
4086 						while( matcher.find()){
4087 
4088 							boolean is_tcp	= matcher.group(1).startsWith( "T" );
4089 							Integer	port	= Integer.parseInt( matcher.group(2));
4090 
4091 							txt_entry.addPort( is_tcp, port );
4092 						}
4093 					}
4094 				}
4095 
4096 				txt_entry.setHasRecords( found_bt );
4097 
4098 				if ( old_txt_entry == null ){
4099 
4100 					dns_mapping_seq_count++;
4101 
4102 					if ( TRACE_DNS ){
4103 						System.out.println( "    New DNS override for " + host + ": " + txt_entry.getString());
4104 					}
4105 				}else{
4106 
4107 					if ( !old_txt_entry.sameAs( txt_entry )){
4108 
4109 						if ( TRACE_DNS ){
4110 							System.out.println( "    Updated DNS override for " + host + ": " + txt_entry.getString());
4111 						}
4112 
4113 						dns_mapping_seq_count++;
4114 
4115 						dispatcher.dispatch(
4116 							new AERunnable()
4117 							{
4118 								public void
4119 								runSupport()
4120 								{
4121 									for ( TorrentAnnounceURLChangeListener l: torrent_url_changed_listeners ){
4122 
4123 										try{
4124 											l.changed();
4125 
4126 										}catch( Throwable e ){
4127 
4128 											Debug.out(e );
4129 										}
4130 									}
4131 								}
4132 							});
4133 					}
4134 				}
4135 			}finally{
4136 
4137 				txt_entry.getSemaphore().releaseForever();
4138 			}
4139 		}
4140 
4141 		txt_entry.getSemaphore().reserve();
4142 
4143 		return( txt_entry );
4144 	}
4145 
4146 	private static URL
applyDNSMods( URL url )4147 	applyDNSMods(
4148 		URL		url )
4149 	{
4150 		if ( DNS_HANDLING_ENABLE ){
4151 
4152 			DNSTXTEntry txt_entry = getDNSTXTEntry( url );
4153 
4154 			if ( txt_entry != null && txt_entry.hasRecords()){
4155 
4156 				boolean url_is_tcp 	= url.getProtocol().toLowerCase().startsWith( "http" );
4157 				int		url_port	= url.getPort();
4158 
4159 				if ( url_port == -1 ){
4160 
4161 					url_port = url.getDefaultPort();
4162 				}
4163 
4164 				List<DNSTXTPortInfo>	ports = txt_entry.getPorts();
4165 
4166 				if ( ports.size() == 0 ){
4167 
4168 					return( UrlUtils.setHost( url, url.getHost() + ".disabled_by_tracker" ));
4169 
4170 				}else{
4171 
4172 					DNSTXTPortInfo	first_port 	= ports.get(0);
4173 
4174 					if ( url_port != first_port.getPort()){
4175 
4176 						url = UrlUtils.setPort( url, first_port.getPort());
4177 					}
4178 
4179 					if ( url_is_tcp == first_port.isTCP()){
4180 
4181 						return( url );
4182 
4183 					}else{
4184 
4185 						return( UrlUtils.setProtocol( url, first_port.isTCP()?"http":"udp" ));
4186 					}
4187 				}
4188 			}else{
4189 
4190 				return( url );
4191 			}
4192 		}else{
4193 
4194 			return( url );
4195 		}
4196 	}
4197 
4198 	private static List<URL>
applyAllDNSMods( URL url )4199 	applyAllDNSMods(
4200 		URL		url )
4201 	{
4202 		if ( DNS_HANDLING_ENABLE ){
4203 
4204 			DNSTXTEntry txt_entry = getDNSTXTEntry( url );
4205 
4206 			if ( txt_entry != null && txt_entry.hasRecords()){
4207 
4208 				boolean url_is_tcp 	= url.getProtocol().toLowerCase().startsWith( "http" );
4209 				int		url_port	= url.getPort();
4210 
4211 				if ( url_port == -1 ){
4212 
4213 					url_port = url.getDefaultPort();
4214 				}
4215 
4216 				List<DNSTXTPortInfo>	ports = txt_entry.getPorts();
4217 
4218 				if ( ports.size() == 0 ){
4219 
4220 					return( null );
4221 
4222 				}else{
4223 
4224 					List<URL>	result = new ArrayList<URL>();
4225 
4226 					for ( DNSTXTPortInfo port: ports ){
4227 
4228 						URL	mod_url = url;
4229 
4230 						if ( url_port != port.getPort()){
4231 
4232 							mod_url = UrlUtils.setPort( mod_url, port.getPort());
4233 						}
4234 
4235 						if ( url_is_tcp != port.isTCP()){
4236 
4237 							mod_url = UrlUtils.setProtocol( mod_url, port.isTCP()?"http":"udp" );
4238 						}
4239 
4240 						result.add( mod_url );
4241 					}
4242 
4243 					return( result );
4244 				}
4245 			}else{
4246 
4247 				return( null );
4248 			}
4249 		}else{
4250 
4251 			return( null );
4252 		}
4253 	}
4254 
4255 	private static TOTorrentAnnounceURLGroup
applyDNSMods( URL announce_url, TOTorrentAnnounceURLGroup group )4256 	applyDNSMods(
4257 		URL								announce_url,
4258 		TOTorrentAnnounceURLGroup		group )
4259 	{
4260 		if ( DNS_HANDLING_ENABLE ){
4261 
4262 			Map<String,Object[]>	dns_maps = new HashMap<String, Object[]>();
4263 
4264 			DNSTXTEntry announce_txt_entry = getDNSTXTEntry( announce_url );
4265 
4266 			if ( announce_txt_entry != null && announce_txt_entry.hasRecords()){
4267 
4268 				dns_maps.put( announce_url.getHost(), new Object[]{ announce_url, announce_txt_entry });
4269 			}
4270 
4271 			TOTorrentAnnounceURLSet[] sets = group.getAnnounceURLSets();
4272 
4273 			List<TOTorrentAnnounceURLSet>	mod_sets = new ArrayList<TOTorrentAnnounceURLSet>();
4274 
4275 			for ( TOTorrentAnnounceURLSet set: sets ){
4276 
4277 				URL[] urls = set.getAnnounceURLs();
4278 
4279 				List<URL>	mod_urls = new ArrayList<URL>();
4280 
4281 				for ( URL url: urls ){
4282 
4283 					DNSTXTEntry txt_entry = getDNSTXTEntry( url );
4284 
4285 					if ( txt_entry == null || !txt_entry.hasRecords()){
4286 
4287 						mod_urls.add( url );
4288 
4289 					}else{
4290 
4291 							// remove any affected entries here, we'll add them in if needed later
4292 
4293 						dns_maps.put( url.getHost(), new Object[]{ url, txt_entry });
4294 					}
4295 				}
4296 
4297 				if ( mod_urls.size() != urls.length ){
4298 
4299 					if ( mod_urls.size() > 0 ){
4300 
4301 						mod_sets.add( group.createAnnounceURLSet( mod_urls.toArray( new URL[ mod_urls.size()])));
4302 					}
4303 				}else{
4304 
4305 					mod_sets.add( set );
4306 				}
4307 			}
4308 
4309 			if ( dns_maps.size() > 0 ){
4310 
4311 				for( Map.Entry<String,Object[]> entry: dns_maps.entrySet()){
4312 
4313 					Object[] stuff		= entry.getValue();
4314 
4315 					URL			url = (URL)stuff[0];
4316 					DNSTXTEntry	dns	= (DNSTXTEntry)stuff[1];
4317 
4318 					List<DNSTXTPortInfo> ports = dns.getPorts();
4319 
4320 					if ( ports.size() > 0 ){
4321 
4322 						List<URL>	urls = new ArrayList<URL>();
4323 
4324 						for ( DNSTXTPortInfo port: ports ){
4325 
4326 							int		url_port 	= url.getPort();
4327 							boolean url_is_tcp 	= url.getProtocol().toLowerCase().startsWith( "http" );
4328 
4329 							if ( url_port != port.getPort()){
4330 
4331 								url = UrlUtils.setPort( url, port.getPort());
4332 							}
4333 
4334 							if ( url_is_tcp != port.isTCP()){
4335 
4336 								url = UrlUtils.setProtocol( url, port.isTCP()?"http":"udp" );
4337 							}
4338 
4339 							urls.add( url );
4340 						}
4341 
4342 						if ( urls.size() > 0 ){
4343 
4344 							mod_sets.add( group.createAnnounceURLSet( urls.toArray( new URL[ urls.size()])));
4345 						}
4346 					}
4347 				}
4348 
4349 				return( new URLGroup( group, mod_sets ));
4350 
4351 			}else{
4352 
4353 				return( group );
4354 			}
4355 		}else{
4356 
4357 			return( group );
4358 		}
4359 	}
4360 
4361 	public interface
4362 	torrentAttributeListener
4363 	{
4364 		public void
attributeSet( TOTorrent torrent, String attribute, Object value )4365 		attributeSet(
4366 			TOTorrent	torrent,
4367 			String		attribute,
4368 			Object		value );
4369 	}
4370 
4371 	public interface
4372 	TorrentAnnounceURLChangeListener
4373 	{
4374 		public void
changed()4375 		changed();
4376 	}
4377 
4378 	private static class
4379 	URLGroup
4380 		implements TOTorrentAnnounceURLGroup
4381 	{
4382 		private TOTorrentAnnounceURLGroup		delegate;
4383 		private TOTorrentAnnounceURLSet[]		sets;
4384 
4385 		private boolean modified;
4386 
4387 		private
URLGroup( TOTorrentAnnounceURLGroup _delegate, List<TOTorrentAnnounceURLSet> mod_sets )4388 		URLGroup(
4389 			TOTorrentAnnounceURLGroup		_delegate,
4390 			List<TOTorrentAnnounceURLSet>	mod_sets )
4391 		{
4392 			delegate	= _delegate;
4393 
4394 			sets = mod_sets.toArray( new TOTorrentAnnounceURLSet[mod_sets.size()]);
4395 		}
4396 
4397 		public TOTorrentAnnounceURLSet[]
getAnnounceURLSets()4398        	getAnnounceURLSets()
4399 		{
4400 			return( sets );
4401 		}
4402 
4403        	public void
setAnnounceURLSets( TOTorrentAnnounceURLSet[] _sets )4404        	setAnnounceURLSets(
4405        		TOTorrentAnnounceURLSet[]	_sets )
4406        	{
4407        		modified = true;
4408 
4409        		sets = _sets;
4410 
4411        		delegate.setAnnounceURLSets(_sets );
4412        	}
4413 
4414        	public TOTorrentAnnounceURLSet
createAnnounceURLSet( URL[] urls )4415        	createAnnounceURLSet(
4416        		URL[]	urls )
4417        	{
4418        		return( delegate.createAnnounceURLSet( urls ));
4419        	}
4420 
4421        	protected boolean
hasBeenModified()4422        	hasBeenModified()
4423        	{
4424        		return( modified );
4425        	}
4426 	}
4427 
4428 	private static class
4429 	DNSTXTEntry
4430 	{
4431 		private long					create_time = SystemTime.getMonotonousTime();
4432 
4433 		private AESemaphore				sem = new AESemaphore( "DNSTXTEntry" );
4434 
4435 		private boolean					has_records;
4436 		private List<DNSTXTPortInfo>	ports = new ArrayList<DNSTXTPortInfo>();
4437 
4438 		private long
getCreateTime()4439 		getCreateTime()
4440 		{
4441 			return( create_time );
4442 		}
4443 
4444 		private AESemaphore
getSemaphore()4445 		getSemaphore()
4446 		{
4447 			return( sem );
4448 		}
4449 
4450 		private void
setHasRecords( boolean has )4451 		setHasRecords(
4452 			boolean	has )
4453 		{
4454 			has_records = has;
4455 		}
4456 
4457 		private boolean
hasRecords()4458 		hasRecords()
4459 		{
4460 			return( has_records );
4461 		}
4462 
4463 		private void
addPort( boolean is_tcp, int port )4464 		addPort(
4465 			boolean	is_tcp,
4466 			int		port )
4467 		{
4468 			ports.add( new DNSTXTPortInfo( is_tcp, port ));
4469 		}
4470 
4471 		private List<DNSTXTPortInfo>
getPorts()4472 		getPorts()
4473 		{
4474 			return( ports );
4475 		}
4476 
4477 		private boolean
sameAs( DNSTXTEntry other )4478 		sameAs(
4479 			DNSTXTEntry		other )
4480 		{
4481 			if ( has_records != other.has_records ){
4482 
4483 				return( false );
4484 			}
4485 
4486 			if ( ports.size() != other.ports.size()){
4487 
4488 				return( false );
4489 			}
4490 
4491 			for ( int i=0;i<ports.size();i++ ){
4492 
4493 				if ( !ports.get(i).sameAs( other.ports.get(i))){
4494 
4495 					return( false );
4496 				}
4497 			}
4498 
4499 			return( true );
4500 		}
4501 
4502 		private String
getString()4503 		getString()
4504 		{
4505 			if ( has_records ){
4506 
4507 				if ( ports.size() == 0 ){
4508 
4509 					return( "Deny all" );
4510 
4511 				}else{
4512 
4513 					String	res = "";
4514 
4515 					for ( DNSTXTPortInfo port: ports ){
4516 
4517 						res += (res.length()==0?"":", ") + port.getString();
4518 					}
4519 
4520 					return( "Permit " + res );
4521 				}
4522 			}else{
4523 
4524 				return( "No records" );
4525 			}
4526 		}
4527 	}
4528 
4529 	private static class
4530 	DNSTXTPortInfo
4531 	{
4532 		private boolean	is_tcp;
4533 		private int		port;
4534 
4535 		private
DNSTXTPortInfo( boolean _is_tcp, int _port )4536 		DNSTXTPortInfo(
4537 			boolean	_is_tcp,
4538 			int		_port )
4539 		{
4540 			is_tcp 	= _is_tcp;
4541 			port	= _port;
4542 		}
4543 
4544 		private boolean
sameAs( DNSTXTPortInfo other )4545 		sameAs(
4546 			DNSTXTPortInfo		other )
4547 		{
4548 			return( is_tcp == other.is_tcp && port == other.port );
4549 		}
4550 
4551 		private boolean
isTCP()4552 		isTCP()
4553 		{
4554 			return( is_tcp );
4555 		}
4556 
4557 		private int
getPort()4558 		getPort()
4559 		{
4560 			return( port );
4561 		}
4562 
4563 		private String
getString()4564 		getString()
4565 		{
4566 			return( (is_tcp?"TCP" :"UDP ") + port );
4567 		}
4568 	}
4569 
4570 	public static void
startTorrentDelete()4571 	startTorrentDelete()
4572 	{
4573 		long val = torrent_delete_level.incrementAndGet();
4574 
4575 		//System.out.println( "delete level++ -> " + val );
4576 	}
4577 
4578 	public static void
endTorrentDelete()4579 	endTorrentDelete()
4580 	{
4581 		long val = torrent_delete_level.decrementAndGet();
4582 
4583 		//System.out.println( "delete level-- -> " + val );
4584 	}
4585 
4586 	public static void
runTorrentDelete( Runnable target )4587 	runTorrentDelete(
4588 		Runnable 	target )
4589 	{
4590 		try{
4591 			startTorrentDelete();
4592 
4593 			target.run();
4594 
4595 		}finally{
4596 
4597 			endTorrentDelete();
4598 		}
4599 	}
4600 
4601 	public static boolean
isTorrentDeleting()4602 	isTorrentDeleting()
4603 	{
4604 		return( torrent_delete_level.get() > 0 );
4605 	}
4606 
4607 	public static void
setTorrentDeleted()4608 	setTorrentDeleted()
4609 	{
4610 		synchronized( TorrentUtils.class ){
4611 
4612 			torrent_delete_time = SystemTime.getMonotonousTime();
4613 		}
4614 	}
4615 
4616 	public static long
getMillisecondsSinceLastTorrentDelete()4617 	getMillisecondsSinceLastTorrentDelete()
4618 	{
4619 		synchronized( TorrentUtils.class ){
4620 
4621 			if ( torrent_delete_time == 0 ){
4622 
4623 				return( Long.MAX_VALUE );
4624 			}
4625 			return( SystemTime.getMonotonousTime() - torrent_delete_time );
4626 		}
4627 	}
4628 
4629 	public static void
main( String[] args )4630 	main(
4631 		String[]	args )
4632 	{
4633 		try{
4634 			//URL url = new URL( "http://tracker.openbittorrent.com/");
4635 			URL url = new URL( "http://inferno.demonoid.com:3413/announce");
4636 
4637 			System.out.println( applyDNSMods( url ));
4638 
4639 			Thread.sleep( 1000*1000 );
4640 
4641 		}catch( Throwable e ){
4642 
4643 			e.printStackTrace();
4644 		}
4645 	}
4646 }
4647