1 /*
2  * Created on Jul 11, 2008
3  * Created by Paul Gardner
4  *
5  * Copyright (C) Azureus Software, Inc, All Rights Reserved.
6  *
7  * This program is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License
9  * as published by the Free Software Foundation; either version 2
10  * of the License, or (at your option) any later version.
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
18  */
19 
20 
21 package com.aelitis.azureus.core.subs.impl;
22 
23 import java.io.File;
24 import java.io.IOException;
25 import java.net.URL;
26 import java.security.KeyPair;
27 import java.util.*;
28 
29 import org.gudy.azureus2.core3.internat.MessageText;
30 import org.gudy.azureus2.core3.torrent.TOTorrent;
31 import org.gudy.azureus2.core3.torrent.TOTorrentCreator;
32 import org.gudy.azureus2.core3.torrent.TOTorrentFactory;
33 import org.gudy.azureus2.core3.util.AENetworkClassifier;
34 import org.gudy.azureus2.core3.util.AEThread2;
35 import org.gudy.azureus2.core3.util.BDecoder;
36 import org.gudy.azureus2.core3.util.BEncoder;
37 import org.gudy.azureus2.core3.util.Base32;
38 import org.gudy.azureus2.core3.util.ByteFormatter;
39 import org.gudy.azureus2.core3.util.Debug;
40 import org.gudy.azureus2.core3.util.FileUtil;
41 import org.gudy.azureus2.core3.util.HashWrapper;
42 import org.gudy.azureus2.core3.util.IndentWriter;
43 import org.gudy.azureus2.core3.util.LightHashMap;
44 import org.gudy.azureus2.core3.util.RandomUtils;
45 import org.gudy.azureus2.core3.util.SystemTime;
46 import org.gudy.azureus2.core3.util.TorrentUtils;
47 import org.gudy.azureus2.core3.util.UrlUtils;
48 import org.gudy.bouncycastle.util.encoders.Base64;
49 import org.json.simple.JSONObject;
50 
51 import com.aelitis.azureus.core.lws.LightWeightSeed;
52 import com.aelitis.azureus.core.lws.LightWeightSeedAdapter;
53 import com.aelitis.azureus.core.lws.LightWeightSeedManager;
54 import com.aelitis.azureus.core.metasearch.Engine;
55 import com.aelitis.azureus.core.metasearch.MetaSearchManagerFactory;
56 import com.aelitis.azureus.core.security.CryptoECCUtils;
57 import com.aelitis.azureus.core.subs.Subscription;
58 import com.aelitis.azureus.core.subs.SubscriptionException;
59 import com.aelitis.azureus.core.subs.SubscriptionHistory;
60 import com.aelitis.azureus.core.subs.SubscriptionListener;
61 import com.aelitis.azureus.core.subs.SubscriptionManager;
62 import com.aelitis.azureus.core.subs.SubscriptionPopularityListener;
63 import com.aelitis.azureus.core.subs.SubscriptionResult;
64 import com.aelitis.azureus.core.util.CopyOnWriteList;
65 import com.aelitis.azureus.core.vuzefile.VuzeFile;
66 import com.aelitis.azureus.core.vuzefile.VuzeFileHandler;
67 import com.aelitis.azureus.util.ImportExportUtils;
68 import com.aelitis.azureus.util.JSONUtils;
69 
70 public class
71 SubscriptionImpl
72 	implements Subscription
73 {
74 
75 	private static final int MAX_ASSOCIATIONS;
76 
77 	static{
78 		int max_assoc = 256;
79 
80 		try{
81 			max_assoc = Integer.parseInt( System.getProperty( "azureus.subs.max.associations", ""+max_assoc));
82 
83 		}catch( Throwable e ){
84 			Debug.out( e );
85 		}
86 
87 		MAX_ASSOCIATIONS = max_assoc;
88 	}
89 
90 	private static final int MIN_RECENT_ASSOC_TO_RETAIN		= 16;
91 
92 	//private static final byte[] GENERIC_PUBLIC_KEY 		= {(byte)0x04,(byte)0xd0,(byte)0x1a,(byte)0xd9,(byte)0xb9,(byte)0x99,(byte)0xd8,(byte)0x49,(byte)0x15,(byte)0x5f,(byte)0xe9,(byte)0x6b,(byte)0x3c,(byte)0xd8,(byte)0x18,(byte)0x81,(byte)0xf7,(byte)0x92,(byte)0x15,(byte)0x3f,(byte)0x24,(byte)0xaa,(byte)0x35,(byte)0x6f,(byte)0x52,(byte)0x01,(byte)0x79,(byte)0x2e,(byte)0x93,(byte)0xf6,(byte)0xf1,(byte)0x57,(byte)0x13,(byte)0x2a,(byte)0x3c,(byte)0x31,(byte)0x66,(byte)0xa5,(byte)0x34,(byte)0x9f,(byte)0x79,(byte)0x62,(byte)0x04,(byte)0x31,(byte)0x68,(byte)0x37,(byte)0x8f,(byte)0x77,(byte)0x5c};
93 	// private static final byte[] GENERIC_PRIVATE_KEY 	= {(byte)0x71,(byte)0xc3,(byte)0xe8,(byte)0x6c,(byte)0x56,(byte)0xbb,(byte)0x30,(byte)0x14,(byte)0x9e,(byte)0x19,(byte)0xa5,(byte)0x3d,(byte)0xcb,(byte)0x47,(byte)0xbb,(byte)0x6d,(byte)0x57,(byte)0x57,(byte)0xd3,(byte)0x59,(byte)0xce,(byte)0x8f,(byte)0x79,(byte)0xe5};
94 
95 	protected static byte[]
intToBytes( int version )96 	intToBytes(
97 		int		version )
98 	{
99 		return( new byte[]{ (byte)(version>>24), (byte)(version>>16),(byte)(version>>8),(byte)version } );
100 	}
101 
102 	protected static int
bytesToInt( byte[] bytes )103 	bytesToInt(
104 		byte[]		bytes )
105 	{
106 		return( (bytes[0]<<24)&0xff000000 | (bytes[1] << 16)&0x00ff0000 | (bytes[2] << 8)&0x0000ff00 | bytes[3]&0x000000ff );
107 	}
108 
109 	private SubscriptionManagerImpl		manager;
110 
111 	private byte[]			public_key;
112 	private byte[]			private_key;
113 
114 	private String			name;
115 	private String			name_ex;
116 
117 	private int				version;
118 	private int				az_version;
119 
120 	private boolean			is_public;		// whether or not we publish associations
121 	private boolean			is_anonymous;	// whether or not the subscription is anon
122 
123 	private Map				singleton_details;
124 
125 	private byte[]			hash;
126 	private byte[]			sig;
127 	private int				sig_data_size;
128 
129 	private int				add_type;
130 	private long			add_time;
131 
132 	private boolean			is_subscribed;
133 
134 	private int				highest_prompted_version;
135 
136 	private byte[]			short_id;
137 
138 	private String			id;
139 
140 	private List<association>			associations = new ArrayList<association>();
141 
142 	private int				fixed_random;
143 
144 	private long			popularity				= -1;
145 
146 	private long			last_auto_upgrade_check	= -1;
147 	private boolean			published;
148 
149 	private boolean			server_published;
150 	private boolean			server_publication_outstanding;
151 
152 	private boolean			singleton_sp_attempted;
153 	private String			local_name;
154 
155 	private LightWeightSeed	lws;
156 	private int				lws_skip_check;
157 
158 	private boolean			destroyed;
159 
160 	private Map				history_map;
161 	private Map				schedule_map;
162 
163 	private Map				user_data = new LightHashMap();
164 
165 	private final 			SubscriptionHistoryImpl	history;
166 
167 	private String			referer;
168 
169 	private CopyOnWriteList	listeners = new CopyOnWriteList();
170 
171 	private Map				verify_cache_details;
172 	private boolean			verify_cache_result;
173 
174 	private String			creator_ref;
175 	private String			category;
176 	private long			tag_id = -1;
177 	private String			parent;
178 
179 	protected static String
getSkeletonJSON( Engine engine, int check_interval_mins )180 	getSkeletonJSON(
181 		Engine		engine,
182 		int			check_interval_mins )
183 	{
184 		JSONObject	map = new JSONObject();
185 
186 		map.put( "engine_id", new Long( engine.getId()));
187 
188 		map.put( "search_term", "" );
189 
190 		map.put( "filters", new HashMap());
191 
192 		map.put( "options", new HashMap());
193 
194 		Map schedule = new HashMap();
195 
196 		schedule.put( "interval", new Long( check_interval_mins ));
197 
198 		List	days = new ArrayList();
199 
200 		for (int i=1;i<=7;i++){
201 
202 			days.add( String.valueOf(i));
203 		}
204 
205 		schedule.put( "days", days );
206 
207 		map.put( "schedule", schedule );
208 
209 		embedEngines( map, engine );
210 
211 		return( JSONUtils.encodeToJSON( map ));
212 	}
213 
214 	protected static String
getSkeletonJSON( Engine engine, String term, String networks, int check_interval_mins )215 	getSkeletonJSON(
216 		Engine		engine,
217 		String		term,
218 		String		networks,
219 		int			check_interval_mins )
220 	{
221 		JSONObject	map = new JSONObject();
222 
223 		map.put( "engine_id", new Long( engine.getId()));
224 
225 		map.put( "search_term", term );
226 
227 		if ( networks != null ){
228 
229 			map.put( "networks", networks );
230 		}
231 
232 		map.put( "filters", new HashMap());
233 
234 		map.put( "options", new HashMap());
235 
236 		Map schedule = new HashMap();
237 
238 		schedule.put( "interval", new Long( check_interval_mins ));
239 
240 		List	days = new ArrayList();
241 
242 		for (int i=1;i<=7;i++){
243 
244 			days.add( String.valueOf(i));
245 		}
246 
247 		schedule.put( "days", days );
248 
249 		map.put( "schedule", schedule );
250 
251 		embedEngines( map, engine );
252 
253 		return( JSONUtils.encodeToJSON( map ));
254 	}
255 
256 
257 		// new subs constructor
258 
259 	protected
SubscriptionImpl( SubscriptionManagerImpl _manager, String _name, boolean _public, boolean _anonymous, Map _singleton_details, String _json_content, int _add_type )260 	SubscriptionImpl(
261 		SubscriptionManagerImpl		_manager,
262 		String						_name,
263 		boolean						_public,
264 		boolean						_anonymous,
265 		Map							_singleton_details,
266 		String						_json_content,
267 		int							_add_type )
268 
269 		throws SubscriptionException
270 	{
271 		manager	= _manager;
272 
273 		history_map	= new HashMap();
274 
275 		history = new SubscriptionHistoryImpl( manager, this );
276 
277 		name				= _name;
278 		is_public			= _public;
279 		is_anonymous		= _anonymous;
280 		singleton_details	= _singleton_details;
281 
282 		version				= 1;
283 		az_version			= AZ_VERSION;
284 
285 		add_type			= _add_type;
286 		add_time			= SystemTime.getCurrentTime();
287 
288 		is_subscribed		= true;
289 
290 		try{
291 			KeyPair	kp = CryptoECCUtils.createKeys();
292 
293 			public_key 			= CryptoECCUtils.keyToRawdata( kp.getPublic());
294 			private_key 		= CryptoECCUtils.keyToRawdata( kp.getPrivate());
295 
296 
297 			fixed_random	= RandomUtils.nextInt();
298 
299 			init();
300 
301 			String json_content = embedEngines( _json_content );
302 
303 			SubscriptionBodyImpl body = new SubscriptionBodyImpl( manager, name, is_public, is_anonymous, json_content, public_key, version, az_version, singleton_details );
304 
305 			syncToBody( body );
306 
307 		}catch( Throwable e ){
308 
309 			throw( new SubscriptionException( "Failed to create subscription", e ));
310 		}
311 	}
312 
313 		// cache detail constructor
314 
315 	protected
SubscriptionImpl( SubscriptionManagerImpl _manager, Map map )316 	SubscriptionImpl(
317 		SubscriptionManagerImpl		_manager,
318 		Map							map )
319 
320 		throws IOException
321 	{
322 		manager	= _manager;
323 
324 		fromMap( map );
325 
326 		history = new SubscriptionHistoryImpl( manager, this );
327 
328 		init();
329 	}
330 
331 		// import constructor
332 
333 	protected
SubscriptionImpl( SubscriptionManagerImpl _manager, SubscriptionBodyImpl _body, int _add_type, boolean _is_subscribed )334 	SubscriptionImpl(
335 		SubscriptionManagerImpl		_manager,
336 		SubscriptionBodyImpl		_body,
337 		int							_add_type,
338 		boolean						_is_subscribed )
339 
340 		throws SubscriptionException
341 	{
342 		manager	= _manager;
343 
344 		history_map	= new HashMap();
345 
346 		history = new SubscriptionHistoryImpl( manager, this );
347 
348 		syncFromBody( _body );
349 
350 		add_type		= _add_type;
351 		add_time		= SystemTime.getCurrentTime();
352 
353 		is_subscribed	= _is_subscribed;
354 
355 		fixed_random	= RandomUtils.nextInt();
356 
357 		init();
358 
359 		syncToBody( _body );
360 	}
361 
362 	protected void
syncFromBody( SubscriptionBodyImpl body )363 	syncFromBody(
364 		SubscriptionBodyImpl	body )
365 
366 		throws SubscriptionException
367 	{
368 		public_key			= body.getPublicKey();
369 		version				= body.getVersion();
370 		az_version			= body.getAZVersion();
371 
372 		name				= body.getName();
373 		is_public			= body.isPublic();
374 		is_anonymous		= body.isAnonymous();
375 		singleton_details	= body.getSingletonDetails();
376 
377 		if ( az_version > AZ_VERSION ){
378 
379 			throw( new SubscriptionException( MessageText.getString( "subscription.version.bad", new String[]{ name })));
380 		}
381 	}
382 
383 	protected void
syncToBody( SubscriptionBodyImpl body )384 	syncToBody(
385 		SubscriptionBodyImpl		body )
386 
387 		throws SubscriptionException
388 	{
389 			// this picks up latest values of version, name + is_public from here
390 
391 		body.writeVuzeFile( this );
392 
393 		hash 			= body.getHash();
394 		sig				= body.getSig();
395 		sig_data_size	= body.getSigDataSize();
396 	}
397 
398 	protected Map
toMap()399 	toMap()
400 
401 		throws IOException
402 	{
403 		synchronized( this ){
404 
405 			Map	map = new HashMap();
406 
407 			map.put( "name", name.getBytes( "UTF-8" ));
408 
409 			map.put( "public_key", public_key );
410 
411 			map.put( "version", new Long( version ));
412 
413 			map.put( "az_version", new Long( az_version ));
414 
415 			map.put( "is_public", new Long( is_public?1:0 ));
416 
417 			map.put( "is_anonymous", new Long( is_anonymous?1:0 ));
418 
419 			if ( singleton_details != null ){
420 
421 				map.put( "sin_details", singleton_details );
422 				map.put( "spa", new Long( singleton_sp_attempted?1:0 ));
423 			}
424 
425 			if ( local_name != null ){
426 
427 				map.put( "local_name", local_name );
428 			}
429 				// body data
430 
431 			map.put( "hash", hash );
432 			map.put( "sig", sig );
433 			map.put( "sig_data_size", new Long( sig_data_size ));
434 
435 				// local data
436 
437 			if ( private_key != null ){
438 
439 				map.put( "private_key", private_key );
440 			}
441 
442 			map.put( "add_type", new Long( add_type ));
443 			map.put( "add_time", new Long( add_time ));
444 
445 			map.put( "subscribed", new Long( is_subscribed?1:0 ));
446 
447 			map.put( "pop", new Long( popularity ));
448 
449 			map.put( "rand", new Long( fixed_random ));
450 
451 			map.put( "hupv", new Long( highest_prompted_version ));
452 
453 			map.put( "sp", new Long( server_published?1:0 ));
454 			map.put( "spo", new Long( server_publication_outstanding?1:0 ));
455 
456 			if ( associations.size() > 0 ){
457 
458 				List	l_assoc = new ArrayList();
459 
460 				map.put( "assoc", l_assoc );
461 
462 				for (int i=0;i<associations.size();i++){
463 
464 					association assoc = (association)associations.get(i);
465 
466 					Map m = new HashMap();
467 
468 					l_assoc.add( m );
469 
470 					m.put( "h", assoc.getHash());
471 					m.put( "w", new Long( assoc.getWhen()));
472 				}
473 			}
474 
475 			map.put( "history", history_map );
476 
477 			if ( creator_ref != null ){
478 
479 				map.put( "cref", creator_ref.getBytes( "UTF-8" ));
480 			}
481 
482 			if ( category != null ){
483 
484 				map.put( "cat", category.getBytes( "UTF-8" ));
485 			}
486 
487 			if ( tag_id != -1 ){
488 
489 				map.put( "tag", tag_id );
490 			}
491 
492 			if ( parent != null ){
493 
494 				map.put( "par", parent.getBytes( "UTF-8" ));
495 			}
496 
497 			return( map );
498 		}
499 	}
500 
501 	protected void
fromMap( Map map )502 	fromMap(
503 		Map		map )
504 
505 		throws IOException
506 	{
507 		name				= new String((byte[])map.get( "name"), "UTF-8" );
508 		public_key			= (byte[])map.get( "public_key" );
509 		private_key			= (byte[])map.get( "private_key" );
510 		version				= ((Long)map.get( "version" )).intValue();
511 		az_version			= (int)ImportExportUtils.importLong( map, "az_version", AZ_VERSION );
512 		is_public			= ((Long)map.get( "is_public")).intValue() == 1;
513 		Long anon			= (Long)map.get( "is_anonymous" );
514 		is_anonymous		= anon!=null&&anon==1;
515 		singleton_details	= (Map)map.get( "sin_details" );
516 
517 		hash			= (byte[])map.get( "hash" );
518 		sig				= (byte[])map.get( "sig" );
519 		sig_data_size	= ((Long)map.get( "sig_data_size" )).intValue();
520 
521 		fixed_random	= ((Long)map.get( "rand" )).intValue();
522 
523 		add_type		= ((Long)map.get( "add_type" )).intValue();
524 		add_time		= ((Long)map.get( "add_time" )).longValue();
525 
526 		is_subscribed	= ((Long)map.get( "subscribed" )).intValue()==1;
527 
528 		popularity		= ((Long)map.get( "pop" )).longValue();
529 
530 		highest_prompted_version = ((Long)map.get( "hupv" )).intValue();
531 
532 		server_published = ((Long)map.get( "sp" )).intValue()==1;
533 		server_publication_outstanding = ((Long)map.get( "spo" )).intValue()==1;
534 
535 		Long	l_spa = (Long)map.get( "spa" );
536 
537 		if ( l_spa != null ){
538 			singleton_sp_attempted = l_spa.longValue()==1;
539 		}
540 
541 		byte[]	b_local_name = (byte[])map.get( "local_name" );
542 
543 		if ( b_local_name != null ){
544 
545 			local_name = new String( b_local_name, "UTF-8" );
546 		}
547 
548 		List	l_assoc = (List)map.get( "assoc" );
549 
550 		if ( l_assoc != null ){
551 
552 			for (int i=0;i<l_assoc.size();i++){
553 
554 				Map	m = (Map)l_assoc.get(i);
555 
556 				byte[]		hash 	= (byte[])m.get("h");
557 				long		when	= ((Long)m.get( "w" )).longValue();
558 
559 				associations.add( new association( hash, when ));
560 			}
561 		}
562 
563 		history_map = (Map)map.get( "history" );
564 
565 		if ( history_map == null ){
566 
567 			history_map = new HashMap();
568 		}
569 
570 		byte[] b_cref = (byte[])map.get( "cref" );
571 
572 		if ( b_cref != null ){
573 
574 			creator_ref = new String( b_cref, "UTF-8" );
575 		}
576 
577 		byte[] b_cat = (byte[])map.get( "cat" );
578 
579 		if ( b_cat != null ){
580 
581 			category = new String( b_cat, "UTF-8" );
582 		}
583 
584 		Long l_tag_id = (Long)map.get( "tag" );
585 
586 		if ( l_tag_id != null ){
587 
588 			tag_id = l_tag_id;
589 		}
590 
591 		byte[] b_parent = (byte[])map.get( "par" );
592 
593 		if ( b_parent != null ){
594 
595 			parent = new String( b_parent, "UTF-8" );
596 		}
597 	}
598 
599 	protected Map
getScheduleConfig()600 	getScheduleConfig()
601 	{
602 		if ( schedule_map == null ){
603 
604 			try{
605 				Map map = JSONUtils.decodeJSON( getJSON());
606 
607 				schedule_map = (Map)map.get( "schedule" );
608 
609 				if ( schedule_map == null ){
610 
611 					schedule_map = new HashMap();
612 				}
613 			}catch( Throwable e ){
614 
615 				log( "Failed to load schedule", e );
616 
617 				schedule_map = new HashMap();
618 			}
619 		}
620 
621 		return( schedule_map );
622 	}
623 
624 	protected Map
getHistoryConfig()625 	getHistoryConfig()
626 	{
627 		return( history_map );
628 	}
629 
630 	protected void
updateHistoryConfig( Map _history_map )631 	updateHistoryConfig(
632 		Map		_history_map )
633 	{
634 		history_map = _history_map;
635 
636 		fireChanged();
637 	}
638 
639 	protected void
upgrade( SubscriptionBodyImpl body )640 	upgrade(
641 		SubscriptionBodyImpl		body )
642 
643 		throws SubscriptionException
644 	{
645 			// pick up details from the body (excluding json that is maintained in body only)
646 
647 		syncFromBody( body );
648 
649 			// write to file
650 
651 		syncToBody(body);
652 
653 		fireChanged();
654 	}
655 
656 	protected void
init()657 	init()
658 	{
659 		short_id = SubscriptionBodyImpl.deriveShortID( public_key, singleton_details );
660 		id = null;
661 	}
662 
663 	public boolean
isSingleton()664 	isSingleton()
665 	{
666 		return( singleton_details != null );
667 	}
668 
669 	public boolean
isShareable()670 	isShareable()
671 	{
672 		try{
673 			return( getEngine().isShareable() && !isSingleton());
674 
675 		}catch( Throwable e ){
676 
677 			Debug.printStackTrace(e);
678 
679 			return( false );
680 		}
681 	}
682 
683 	public boolean
isSearchTemplate()684 	isSearchTemplate()
685 	{
686 		return( getName(false).startsWith( "Search Template:" ));
687 	}
688 
689 	protected Map
getSingletonDetails()690 	getSingletonDetails()
691 	{
692 		return( singleton_details );
693 	}
694 
695 	protected boolean
getSingletonPublishAttempted()696 	getSingletonPublishAttempted()
697 	{
698 		return( singleton_sp_attempted );
699 	}
700 
701 	protected void
setSingletonPublishAttempted()702 	setSingletonPublishAttempted()
703 	{
704 		if ( !singleton_sp_attempted ){
705 
706 			singleton_sp_attempted = true;
707 
708 			manager.configDirty( this );
709 		}
710 	}
711 
712 	public String
getName()713 	getName()
714 	{
715 		return( getName( true ));
716 	}
717 
718 	public String
getName( boolean use_local )719 	getName(
720 		boolean	use_local )
721 	{
722 		return( local_name==null?name:local_name );
723 	}
724 
725 	public String
getURI()726 	getURI()
727 	{
728 		String str = "sub:?name=" + UrlUtils.encode(getName()) + "&id=" + Base32.encode(getShortID()) + "&v=" + getVersion();
729 
730 		return( "azplug:?id=subscription&arg=" + UrlUtils.encode( str ));
731 	}
732 
733 	public void
requestAttention()734 	requestAttention()
735 	{
736 		manager.selectSubscription( this );
737 	}
738 
739 	public void
setLocalName( String str )740 	setLocalName(
741 		String		str )
742 	{
743 		local_name = str;
744 
745 		manager.configDirty( this );
746 
747 		fireChanged();
748 	}
749 
750 	public void
setName( String _name )751 	setName(
752 		String		_name )
753 
754 		throws SubscriptionException
755 	{
756 		if ( !name.equals( _name )){
757 
758 			boolean	ok = false;
759 
760 			String	old_name 	= name;
761 			int		old_version	= version;
762 
763 			try{
764 				name	= _name;
765 
766 				version++;
767 
768 				SubscriptionBodyImpl body = new SubscriptionBodyImpl( manager, this );
769 
770 				syncToBody( body );
771 
772 				versionUpdated( body, false );
773 
774 				ok	= true;
775 
776 			}finally{
777 
778 				if ( !ok ){
779 
780 					name 	= old_name;
781 					version	= old_version;
782 				}
783 			}
784 
785 			fireChanged();
786 		}
787 	}
788 
789 	public String
getNameEx()790 	getNameEx()
791 	{
792 		if ( name_ex == null ){
793 
794 			try{
795 				Map map = JSONUtils.decodeJSON( getJSON());
796 
797 				String	search_term	= (String)map.get( "search_term" );
798 				Map		filters		= (Map)map.get( "filters" );
799 
800 				Engine engine = manager.getEngine( this, map, true );
801 
802 				String	engine_name = engine.getNameEx();
803 
804 				if ( name.startsWith( engine_name )){
805 
806 					name_ex = name;
807 
808 				}else if ( engine_name.startsWith( name )){
809 
810 					name_ex = engine_name;
811 
812 				}else{
813 
814 					name_ex = name + ": " + engine.getNameEx();
815 				}
816 
817 				if ( search_term != null && search_term.length() > 0 ){
818 
819 					name_ex += ", query=" + search_term;
820 				}
821 
822 				if ( filters != null && filters.size() > 0 ){
823 
824 					name_ex += ", filters=" + new SubscriptionResultFilter(filters).getString();
825 				}
826 
827 			}catch( Throwable e ){
828 
829 				name_ex = name + ": " + Debug.getNestedExceptionMessage(e);
830 			}
831 		}
832 
833 		return( name_ex );
834 	}
835 
836 	public String
getQueryKey()837 	getQueryKey()
838 	{
839 		try{
840 			Map map = JSONUtils.decodeJSON( getJSON());
841 
842 			String	search_term	= (String)map.get( "search_term" );
843 			Map		filters		= (Map)map.get( "filters" );
844 
845 			Engine engine = manager.getEngine( this, map, true );
846 
847 			String	name = engine.getNameEx();
848 
849 			if ( search_term != null && search_term.length() > 0 ){
850 
851 				name += ", query=" + search_term;
852 			}
853 
854 			if ( filters != null && filters.size() > 0 ){
855 
856 				name += ", filters=" + new SubscriptionResultFilter(filters).getString();
857 			}
858 
859 			return( name );
860 
861 		}catch( Throwable e ){
862 
863 			return( null );
864 		}
865 	}
866 
867 	public long
getAddTime()868 	getAddTime()
869 	{
870 		return( add_time );
871 	}
872 
873 	public int
getAddType()874 	getAddType()
875 	{
876 		return( add_type );
877 	}
878 
879 	public boolean
isPublic()880 	isPublic()
881 	{
882 		return( is_public );
883 	}
884 
885 	public boolean
isAnonymous()886 	isAnonymous()
887 	{
888 		return( is_anonymous );
889 	}
890 
891 	public void
setPublic( boolean _is_public )892 	setPublic(
893 		boolean		_is_public )
894 
895 		throws SubscriptionException
896 	{
897 		if ( is_public != _is_public ){
898 
899 			boolean	ok = false;
900 
901 			boolean	old_public	= is_public;
902 			int		old_version	= version;
903 
904 			try{
905 				is_public	= _is_public;
906 
907 				version++;
908 
909 				SubscriptionBodyImpl body = new SubscriptionBodyImpl( manager, this );
910 
911 				syncToBody( body );
912 
913 				versionUpdated( body, false );
914 
915 				ok = true;
916 
917 			}finally{
918 
919 				if ( !ok ){
920 
921 					version		= old_version;
922 					is_public	= old_public;
923 				}
924 			}
925 
926 			fireChanged();
927 		}
928 	}
929 
930 	protected boolean
getServerPublicationOutstanding()931 	getServerPublicationOutstanding()
932 	{
933 		return( server_publication_outstanding );
934 	}
935 
936 	protected void
setServerPublicationOutstanding()937 	setServerPublicationOutstanding()
938 	{
939 		if ( !server_publication_outstanding ){
940 
941 			server_publication_outstanding = true;
942 
943 			fireChanged();
944 		}
945 	}
946 
947 	protected void
setServerPublished()948 	setServerPublished()
949 	{
950 		if ( server_publication_outstanding || !server_published ){
951 
952 			server_published 				= true;
953 			server_publication_outstanding	= false;
954 
955 			fireChanged();
956 		}
957 	}
958 
959 	protected boolean
getServerPublished()960 	getServerPublished()
961 	{
962 		return( server_published );
963 	}
964 
965 	public String
getJSON()966 	getJSON()
967 
968 		throws SubscriptionException
969 	{
970 		try{
971 			SubscriptionBodyImpl body = new SubscriptionBodyImpl( manager, this );
972 
973 			return( body.getJSON());
974 
975 		}catch( Throwable e ){
976 
977 			history.setFatalError( Debug.getNestedExceptionMessage(e));
978 
979 			if ( e instanceof SubscriptionException ){
980 
981 				throw((SubscriptionException)e );
982 			}
983 
984 			throw( new SubscriptionException( "Failed to read subscription", e ));
985 		}
986 	}
987 
988 	public boolean
setJSON( String _json )989 	setJSON(
990 		String		_json )
991 
992 		throws SubscriptionException
993 	{
994 		String json = embedEngines( _json );
995 
996 		SubscriptionBodyImpl body = new SubscriptionBodyImpl( manager, this );
997 
998 		String	old_json = body.getJSON();
999 
1000 		if ( !json.equals( old_json )){
1001 
1002 			boolean	ok = false;
1003 
1004 			int		old_version	= version;
1005 
1006 			try{
1007 				version++;
1008 
1009 				body.setJSON( json );
1010 
1011 				syncToBody( body );
1012 
1013 				versionUpdated( body, true );
1014 
1015 				referer = null;
1016 
1017 				ok	= true;
1018 
1019 			}finally{
1020 
1021 				if ( !ok ){
1022 
1023 					version	= old_version;
1024 				}
1025 			}
1026 
1027 			fireChanged();
1028 
1029 			return( true );
1030 		}
1031 
1032 		return( false );
1033 	}
1034 
1035 	protected String
embedEngines( String json_in )1036 	embedEngines(
1037 		String		json_in )
1038 	{
1039 			// see if we need to embed private search templates
1040 
1041 		Map map = JSONUtils.decodeJSON( json_in );
1042 
1043 		long 	engine_id 	= ((Long)map.get( "engine_id" )).longValue();
1044 
1045 		String	json_out	= json_in;
1046 
1047 		if ( engine_id >= Integer.MAX_VALUE || engine_id < 0 ){
1048 
1049 			Engine engine = MetaSearchManagerFactory.getSingleton().getMetaSearch().getEngine( engine_id );
1050 
1051 			if ( engine == null ){
1052 
1053 				log( "Private search template with id '" + engine_id + "' not found!!!!" );
1054 
1055 			}else{
1056 
1057 				try{
1058 					embedEngines( map, engine );
1059 
1060 					json_out = JSONUtils.encodeToJSON( map );
1061 
1062 
1063 					log( "Embedded private search template '" + engine.getName() + "'" );
1064 
1065 				}catch( Throwable e ){
1066 
1067 					log( "Failed to embed private search template", e );
1068 				}
1069 			}
1070 		}
1071 
1072 		return( json_out );
1073 	}
1074 
1075 	protected static void
embedEngines( Map map, Engine engine )1076 	embedEngines(
1077 		Map			map,
1078 		Engine		engine )
1079 	{
1080 		Map	engines = new HashMap();
1081 
1082 		map.put( "engines", engines );
1083 
1084 		Map	engine_map = new HashMap();
1085 
1086 		try{
1087 
1088 			String	engine_str = new String( Base64.encode( BEncoder.encode( engine.exportToBencodedMap())), "UTF-8" );
1089 
1090 			engine_map.put( "content", engine_str );
1091 
1092 			engines.put( String.valueOf( engine.getId()), engine_map );
1093 
1094 		}catch( Throwable e ){
1095 
1096 			Debug.out( e );
1097 		}
1098 	}
1099 
1100 	protected Engine
extractEngine( Map json_map, long id )1101 	extractEngine(
1102 		Map		json_map,
1103 		long	id )
1104 	{
1105 		Map engines = (Map)json_map.get( "engines" );
1106 
1107 		if ( engines != null ){
1108 
1109 			Map	engine_map = (Map)engines.get( String.valueOf( id ));
1110 
1111 			if ( engine_map != null ){
1112 
1113 				String	engine_str = (String)engine_map.get( "content" );
1114 
1115 				try{
1116 
1117 					Map map = BDecoder.decode( Base64.decode( engine_str.getBytes( "UTF-8" )));
1118 
1119 					return( MetaSearchManagerFactory.getSingleton().getMetaSearch().importFromBEncodedMap(map));
1120 
1121 				}catch( Throwable e ){
1122 
1123 					log( "failed to import engine", e );
1124 				}
1125 			}
1126 		}
1127 
1128 		return( null );
1129 	}
1130 
1131 	public Subscription
cloneWithNewEngine( Engine engine )1132 	cloneWithNewEngine(
1133 		Engine		engine )
1134 
1135 		throws SubscriptionException
1136 	{
1137 		try{
1138 			String	json = getJSON();
1139 
1140 			Map map = JSONUtils.decodeJSON( json );
1141 
1142 			long	id = ((Long)map.get( "engine_id" )).longValue();
1143 
1144 			if ( id == engine.getId()){
1145 
1146 				embedEngines(map, engine);
1147 
1148 				SubscriptionImpl subs = new SubscriptionImpl( manager, getName(), engine.isPublic(), isAnonymous(), null, JSONUtils.encodeToJSON(map), SubscriptionImpl.ADD_TYPE_CREATE );
1149 
1150 				subs = manager.addSubscription( subs );
1151 
1152 				setLocalName( getName( false ) + " (old)" );
1153 
1154 				return( subs );
1155 
1156 			}else{
1157 
1158 				throw( new SubscriptionException( "Engine mismatch" ));
1159 			}
1160 		}catch( Throwable e ){
1161 
1162 			throw( new SubscriptionException( "Failed to export engine", e ));
1163 		}
1164 	}
1165 
1166 	public Engine
getEngine()1167 	getEngine()
1168 
1169 		throws SubscriptionException
1170 	{
1171 		return( getEngine( true ));
1172 	}
1173 
1174 	protected Engine
getEngine( boolean local_only )1175 	getEngine(
1176 		boolean		local_only )
1177 
1178 		throws SubscriptionException
1179 	{
1180 		Map map = JSONUtils.decodeJSON( getJSON());
1181 
1182 		return( manager.getEngine( this, map, local_only ));
1183 	}
1184 
1185 	protected void
engineUpdated( Engine engine )1186 	engineUpdated(
1187 		Engine		engine )
1188 	{
1189 		try{
1190 			String	json = getJSON();
1191 
1192 			Map map = JSONUtils.decodeJSON( json );
1193 
1194 			long	id = ((Long)map.get( "engine_id" )).longValue();
1195 
1196 			if ( id == engine.getId()){
1197 
1198 				if ( setJSON( json )){
1199 
1200 					log( "Engine has been updated, saved" );
1201 				}
1202 			}
1203 		}catch( Throwable e ){
1204 
1205 			log( "Engine update failed", e );
1206 		}
1207 	}
1208 
1209 	public boolean
setDetails( String _name, boolean _is_public, String _json )1210 	setDetails(
1211 		String		_name,
1212 		boolean		_is_public,
1213 		String		_json )
1214 
1215 		throws SubscriptionException
1216 	{
1217 		_json = embedEngines( _json );
1218 
1219 		SubscriptionBodyImpl body = new SubscriptionBodyImpl( manager, this );
1220 
1221 		String	old_json = body.getJSON();
1222 
1223 		boolean	json_changed = !_json.equals( old_json );
1224 
1225 		if ( 	!_name.equals( name ) ||
1226 				_is_public != is_public ||
1227 				json_changed ){
1228 
1229 			boolean	ok = false;
1230 
1231 			String	old_name	= name;
1232 			boolean	old_public	= is_public;
1233 			int		old_version	= version;
1234 
1235 			try{
1236 				is_public	= _is_public;
1237 				name		= _name;
1238 
1239 				body.setJSON( _json );
1240 
1241 				version++;
1242 
1243 				syncToBody( body );
1244 
1245 				versionUpdated( body, json_changed );
1246 
1247 				ok = true;
1248 
1249 			}finally{
1250 
1251 				if ( !ok ){
1252 
1253 					version		= old_version;
1254 					is_public	= old_public;
1255 					name		= old_name;
1256 				}
1257 			}
1258 
1259 			fireChanged();
1260 
1261 			return( true );
1262 		}
1263 
1264 		return( false );
1265 	}
1266 
1267 	protected void
versionUpdated( SubscriptionBodyImpl body, boolean json_changed )1268 	versionUpdated(
1269 		SubscriptionBodyImpl		body,
1270 		boolean						json_changed )
1271 	{
1272 		if ( json_changed ){
1273 
1274 			try{
1275 				Map map = JSONUtils.decodeJSON( body.getJSON());
1276 
1277 				schedule_map = (Map)map.get( "schedule" );
1278 
1279 			}catch( Throwable e ){
1280 			}
1281 		}
1282 
1283 		name_ex = null;
1284 
1285 		if ( is_public ){
1286 
1287 			manager.updatePublicSubscription( this );
1288 
1289 			setPublished( false );
1290 
1291 			synchronized( this ){
1292 
1293 				for (int i=0;i<associations.size();i++){
1294 
1295 					((association)associations.get(i)).setPublished( false );
1296 				}
1297 			}
1298 		}
1299 	}
1300 
1301 	public byte[]
getPublicKey()1302 	getPublicKey()
1303 	{
1304 		return( public_key );
1305 	}
1306 
1307 	public byte[]
getShortID()1308 	getShortID()
1309 	{
1310 		return( short_id );
1311 	}
1312 
1313 	public String
getID()1314 	getID()
1315 	{
1316 		if (id == null) {
1317 			id = Base32.encode(getShortID());
1318 		}
1319 		return( id );
1320 	}
1321 
1322 	protected byte[]
getPrivateKey()1323 	getPrivateKey()
1324 	{
1325 		return( private_key );
1326 	}
1327 
1328 	protected int
getFixedRandom()1329 	getFixedRandom()
1330 	{
1331 		return( fixed_random );
1332 	}
1333 
1334 	public int
getVersion()1335 	getVersion()
1336 	{
1337 		return( version );
1338 	}
1339 
1340 	public int
getAZVersion()1341 	getAZVersion()
1342 	{
1343 		return( az_version );
1344 	}
1345 
1346 	protected void
setHighestUserPromptedVersion( int v )1347 	setHighestUserPromptedVersion(
1348 		int		v )
1349 	{
1350 		if ( v < version ){
1351 
1352 			v  = version;
1353 		}
1354 
1355 		if ( highest_prompted_version != v ){
1356 
1357 			highest_prompted_version = v;
1358 
1359 			fireChanged();
1360 		}
1361 	}
1362 
1363 	protected int
getHighestUserPromptedVersion()1364 	getHighestUserPromptedVersion()
1365 	{
1366 		return( highest_prompted_version );
1367 	}
1368 
1369 	public int
getHighestVersion()1370 	getHighestVersion()
1371 	{
1372 		return( Math.max( version, highest_prompted_version ));
1373 	}
1374 
1375 	public void
resetHighestVersion()1376 	resetHighestVersion()
1377 	{
1378 		if ( highest_prompted_version > 0 ){
1379 
1380 			highest_prompted_version = 0;
1381 
1382 			fireChanged();
1383 
1384 			manager.checkUpgrade(this);
1385 		}
1386 	}
1387 
1388 	public boolean
isMine()1389 	isMine()
1390 	{
1391 		if ( private_key == null ){
1392 
1393 			return( false );
1394 		}
1395 
1396 		if ( isSingleton() && add_type != ADD_TYPE_CREATE ){
1397 
1398 			return( false );
1399 		}
1400 
1401 		return( true );
1402 	}
1403 
1404 	public boolean
isUpdateable()1405 	isUpdateable()
1406 	{
1407 		return( private_key != null );
1408 	}
1409 
1410 	public boolean
isSubscribed()1411 	isSubscribed()
1412 	{
1413 		return( is_subscribed );
1414 	}
1415 
1416 	public void
setSubscribed( boolean s )1417 	setSubscribed(
1418 		boolean			s )
1419 	{
1420 		if ( is_subscribed != s ){
1421 
1422 			is_subscribed = s;
1423 
1424 			if ( is_subscribed ){
1425 
1426 				manager.setSelected( this );
1427 
1428 			}else{
1429 
1430 				reset();
1431 			}
1432 
1433 			fireChanged();
1434 		}
1435 	}
1436 
1437 	public boolean
isAutoDownloadSupported()1438 	isAutoDownloadSupported()
1439 	{
1440 		return( history.isAutoDownloadSupported());
1441 	}
1442 
1443 	public void
getPopularity( final SubscriptionPopularityListener listener )1444 	getPopularity(
1445 		final SubscriptionPopularityListener	listener )
1446 
1447 		throws SubscriptionException
1448 	{
1449 		new AEThread2( "subs:popwait", true )
1450 		{
1451 			public void
1452 			run()
1453 			{
1454 				try{
1455 					manager.getPopularity(
1456 						SubscriptionImpl.this,
1457 						new SubscriptionPopularityListener()
1458 						{
1459 							public void
1460 							gotPopularity(
1461 								long						pop )
1462 							{
1463 								if ( pop != popularity ){
1464 
1465 									popularity = pop;
1466 
1467 									fireChanged();
1468 								}
1469 
1470 								listener.gotPopularity( popularity );
1471 							}
1472 
1473 							public void
1474 							failed(
1475 								SubscriptionException		e )
1476 							{
1477 								if ( popularity == -1 ){
1478 
1479 									listener.failed( new SubscriptionException( "Failed to read popularity", e ));
1480 
1481 								}else{
1482 
1483 									listener.gotPopularity( popularity );
1484 								}
1485 							}
1486 						});
1487 
1488 				}catch( Throwable e ){
1489 
1490 					if ( popularity == -1 ){
1491 
1492 						listener.failed( new SubscriptionException( "Failed to read popularity", e ));
1493 
1494 					}else{
1495 
1496 						listener.gotPopularity( popularity );
1497 					}
1498 				}
1499 			}
1500 		}.start();
1501 	}
1502 
1503 	public long
getCachedPopularity()1504 	getCachedPopularity()
1505 	{
1506 		return( popularity );
1507 	}
1508 
1509 	protected void
setCachedPopularity( long pop )1510 	setCachedPopularity(
1511 		long		pop )
1512 	{
1513 		if ( pop != popularity ){
1514 
1515 			popularity		= pop;
1516 
1517 			fireChanged();
1518 		}
1519 	}
1520 
1521 	public String
getReferer()1522 	getReferer()
1523 	{
1524 		if ( referer == null ){
1525 
1526 			try{
1527 				Map map = JSONUtils.decodeJSON( getJSON());
1528 
1529 				Engine engine = manager.getEngine( this, map, false );
1530 
1531 				if ( engine != null ){
1532 
1533 					referer = engine.getReferer();
1534 				}
1535 			}catch( Throwable e ){
1536 
1537 				log( "Failed to get referer", e );
1538 			}
1539 
1540 			if ( referer == null ){
1541 
1542 				referer = "";
1543 			}
1544 		}
1545 
1546 		return( referer );
1547 	}
1548 
1549 	protected void
checkPublish()1550 	checkPublish()
1551 	{
1552 		synchronized( this ){
1553 
1554 			if ( destroyed ){
1555 
1556 				return;
1557 			}
1558 
1559 				// singleton's not available for upgrade
1560 
1561 			if ( isSingleton()){
1562 
1563 				return;
1564 			}
1565 
1566 				// nothing to do for unsubscribed ones
1567 
1568 			if ( !isSubscribed()){
1569 
1570 				return;
1571 			}
1572 
1573 			if ( popularity > 100 ){
1574 
1575 					// one off test on whether to track so we have around 100 active
1576 
1577 				if ( lws_skip_check == 2 ){
1578 
1579 					return;
1580 
1581 				}else if ( lws_skip_check == 0 ){
1582 
1583 					if ( RandomUtils.nextInt((int)(( popularity + 99 ) / 100 )) == 0 ){
1584 
1585 						lws_skip_check = 1;
1586 
1587 					}else{
1588 
1589 						lws_skip_check = 2;
1590 
1591 						return;
1592 					}
1593 				}
1594 			}
1595 
1596 			if ( hash != null ){
1597 
1598 				boolean	create = false;
1599 
1600 				if ( lws == null ){
1601 
1602 					create = true;
1603 
1604 				}else{
1605 
1606 					if ( !Arrays.equals( lws.getHash().getBytes(), hash )){
1607 
1608 						lws.remove();
1609 
1610 						create = true;
1611 					}
1612 				}
1613 
1614 				if ( create ){
1615 
1616 					try{
1617 						File original_data_location = manager.getVuzeFile( this );
1618 
1619 						if ( original_data_location.exists()){
1620 
1621 								// make a version based filename to avoid issues regarding multiple
1622 								// versions
1623 
1624 							final File	versioned_data_location = new File( original_data_location.getParent(), original_data_location.getName() + "." + getVersion());
1625 
1626 							if ( !versioned_data_location.exists()){
1627 
1628 								if ( !FileUtil.copyFile( original_data_location, versioned_data_location )){
1629 
1630 									throw( new Exception( "Failed to copy file to '" + versioned_data_location + "'" ));
1631 								}
1632 							}
1633 
1634 							lws = LightWeightSeedManager.getSingleton().add(
1635 									getName(),
1636 									new HashWrapper( hash ),
1637 									TorrentUtils.getDecentralisedEmptyURL(),
1638 									versioned_data_location,
1639 									isAnonymous()?AENetworkClassifier.AT_I2P:AENetworkClassifier.AT_PUBLIC,
1640 									new LightWeightSeedAdapter()
1641 									{
1642 										public TOTorrent
1643 										getTorrent(
1644 											byte[] 		hash,
1645 											URL 		announce_url,
1646 											File 		data_location)
1647 
1648 											throws Exception
1649 										{
1650 											log( "Generating light-weight torrent: hash=" + ByteFormatter.encodeString( hash ));
1651 
1652 											TOTorrentCreator creator =
1653 												TOTorrentFactory.createFromFileOrDirWithFixedPieceLength(
1654 														data_location,
1655 														announce_url,
1656 														256*1024 );
1657 
1658 											TOTorrent t = creator.create();
1659 
1660 											t.setHashOverride( hash );
1661 
1662 											return( t );
1663 										}
1664 									});
1665 						}
1666 
1667 					}catch( Throwable e ){
1668 
1669 						log( "Failed to create light-weight-seed", e );
1670 					}
1671 				}
1672 			}
1673 		}
1674 	}
1675 
1676 	protected synchronized boolean
canAutoUpgradeCheck()1677 	canAutoUpgradeCheck()
1678 	{
1679 		if ( isSingleton()){
1680 
1681 			return( false );
1682 		}
1683 
1684 		long	now = SystemTime.getMonotonousTime();
1685 
1686 		if ( last_auto_upgrade_check == -1 || now - last_auto_upgrade_check > 4*60*60*1000 ){
1687 
1688 			last_auto_upgrade_check = now;
1689 
1690 			return( true );
1691 		}
1692 
1693 		return( false );
1694 	}
1695 
1696 	public void
addAssociation( byte[] hash )1697 	addAssociation(
1698 		byte[]		hash )
1699 	{
1700 		if ( hash.length != 20 ){
1701 
1702 			Debug.out( "Invalid hash: " + ByteFormatter.encodeString( hash ));
1703 
1704 			return;
1705 		}
1706 
1707 		addAssociationSupport( hash, false );
1708 	}
1709 
1710 	protected boolean
addAssociationSupport( byte[] hash, boolean internal )1711 	addAssociationSupport(
1712 		byte[]		hash,
1713 		boolean		internal )
1714 	{
1715 		synchronized( this ){
1716 
1717 			for (int i=0;i<associations.size();i++){
1718 
1719 				association assoc = (association)associations.get(i);
1720 
1721 				if ( Arrays.equals( assoc.getHash(), hash )){
1722 
1723 					return( false );
1724 				}
1725 			}
1726 
1727 			associations.add( new association( hash, SystemTime.getCurrentTime()));
1728 
1729 			if ( MAX_ASSOCIATIONS > 0 && associations.size() > MAX_ASSOCIATIONS ){
1730 
1731 				associations.remove( RandomUtils.nextInt( MAX_ASSOCIATIONS - MIN_RECENT_ASSOC_TO_RETAIN ));
1732 			}
1733 		}
1734 
1735 		if ( !internal ){
1736 
1737 			fireChanged();
1738 
1739 			manager.associationAdded( this, hash);
1740 		}
1741 
1742 		return( true );
1743 	}
1744 
1745 	public boolean
hasAssociation( byte[] hash )1746 	hasAssociation(
1747 		byte[]		hash )
1748 	{
1749 		synchronized( this ){
1750 
1751 			for (int i=0;i<associations.size();i++){
1752 
1753 				association assoc = (association)associations.get(i);
1754 
1755 				if ( Arrays.equals( assoc.getHash(), hash )){
1756 
1757 					return( true );
1758 				}
1759 			}
1760 		}
1761 
1762 		return( false );
1763 	}
1764 
1765 	public void
addPotentialAssociation( String result_id, String key )1766 	addPotentialAssociation(
1767 		String		result_id,
1768 		String		key )
1769 	{
1770 		manager.addPotentialAssociation( this, result_id, key );
1771 	}
1772 
1773 	public int
getAssociationCount()1774 	getAssociationCount()
1775 	{
1776 		synchronized( this ){
1777 
1778 			return( associations.size());
1779 		}
1780 	}
1781 
1782 	protected association
getAssociationForPublish()1783 	getAssociationForPublish()
1784 	{
1785 		synchronized( this ){
1786 
1787 			int	num_assoc = associations.size();
1788 
1789 				// first set in order of most recent
1790 
1791 			for (int i=num_assoc-1;i>=Math.max( 0, num_assoc-MIN_RECENT_ASSOC_TO_RETAIN);i--){
1792 
1793 				association assoc = (association)associations.get(i);
1794 
1795 				if ( !assoc.getPublished()){
1796 
1797 					assoc.setPublished( true );
1798 
1799 					return( assoc );
1800 				}
1801 			}
1802 
1803 				// remaining randomised
1804 
1805 			int	rem = associations.size() - MIN_RECENT_ASSOC_TO_RETAIN;
1806 
1807 			if ( rem > 0 ){
1808 
1809 				List<association> l = new ArrayList<association>( associations.subList( 0, rem ));
1810 
1811 				Collections.shuffle( l );
1812 
1813 				for (int i=0;i<l.size();i++){
1814 
1815 					association assoc = l.get(i);
1816 
1817 					if ( !assoc.getPublished()){
1818 
1819 						assoc.setPublished( true );
1820 
1821 						return( assoc );
1822 					}
1823 				}
1824 			}
1825 		}
1826 
1827 		return( null );
1828 	}
1829 
1830 	protected int
getAssociationsRemainingForPublish()1831 	getAssociationsRemainingForPublish()
1832 	{
1833 		synchronized( this ){
1834 
1835 			int	result = 0;
1836 
1837 			for ( association a: associations ){
1838 
1839 				if ( !a.getPublished()){
1840 
1841 					result++;
1842 				}
1843 			}
1844 
1845 			return( result );
1846 		}
1847 	}
1848 
1849 	protected boolean
getPublished()1850 	getPublished()
1851 	{
1852 		return( published );
1853 	}
1854 
1855 	protected void
setPublished( boolean b )1856 	setPublished(
1857 		boolean		b )
1858 	{
1859 		published = b;
1860 	}
1861 
1862 	protected int
getVerifiedPublicationVersion( Map details )1863 	getVerifiedPublicationVersion(
1864 		Map		details )
1865 	{
1866 			// singleton versions always 1 and each instance has separate private key so
1867 			// verification will always fail so save to just return current version
1868 
1869 		if ( isSingleton()){
1870 
1871 			return( getVersion());
1872 		}
1873 
1874 		if ( !verifyPublicationDetails( details )){
1875 
1876 			return( -1 );
1877 		}
1878 
1879 		return( getPublicationVersion( details ));
1880 	}
1881 
1882 	protected static int
getPublicationVersion( Map details )1883 	getPublicationVersion(
1884 		Map		details )
1885 	{
1886 		return(((Long)details.get("v")).intValue());
1887 	}
1888 
1889 	protected byte[]
getPublicationHash()1890 	getPublicationHash()
1891 	{
1892 		return( hash );
1893 	}
1894 
1895 	protected static byte[]
getPublicationHash( Map details )1896 	getPublicationHash(
1897 		Map		details )
1898 	{
1899 		return((byte[])details.get( "h" ));
1900 	}
1901 
1902 	protected static int
getPublicationSize( Map details )1903 	getPublicationSize(
1904 		Map		details )
1905 	{
1906 		return(((Long)details.get("z")).intValue());
1907 	}
1908 
1909 	protected Map
getPublicationDetails()1910 	getPublicationDetails()
1911 	{
1912 		Map	result = new HashMap();
1913 
1914 		result.put( "v", new Long( version ));
1915 
1916 		if ( singleton_details == null ){
1917 
1918 			result.put( "h", hash );
1919 			result.put( "z", new Long( sig_data_size ));
1920 			result.put( "s", sig );
1921 
1922 		}else{
1923 
1924 			result.put( "x", singleton_details );
1925 		}
1926 
1927 		return( result );
1928 	}
1929 
1930 	protected boolean
verifyPublicationDetails( Map details )1931 	verifyPublicationDetails(
1932 		Map		details )
1933 	{
1934 		synchronized( this ){
1935 
1936 			if ( BEncoder.mapsAreIdentical( verify_cache_details, details )){
1937 
1938 				return( verify_cache_result );
1939 			}
1940 		}
1941 
1942 		byte[]	hash 	= (byte[])details.get( "h" );
1943 		int		version	= ((Long)details.get( "v" )).intValue();
1944 		int		size	= ((Long)details.get( "z" )).intValue();
1945 		byte[]	sig		= (byte[])details.get( "s" );
1946 
1947 		boolean	result = SubscriptionBodyImpl.verify( public_key, hash, version, size, sig );
1948 
1949 		synchronized( this ){
1950 
1951 			verify_cache_details 	= details;
1952 			verify_cache_result		= result;
1953 		}
1954 
1955 		return( result );
1956 	}
1957 
1958 	public void
setCreatorRef( String ref )1959 	setCreatorRef(
1960 		String	ref )
1961 	{
1962 		creator_ref = ref;
1963 
1964 		fireChanged();
1965 	}
1966 
1967 	public String
getCreatorRef()1968 	getCreatorRef()
1969 	{
1970 		return( creator_ref );
1971 	}
1972 
1973 	public void
setCategory( String _category )1974 	setCategory(
1975 		String	_category )
1976 	{
1977 		if ( _category == null && category == null ){
1978 
1979 			return;
1980 		}
1981 
1982 		if ( _category != null && category != null && _category.equals( category )){
1983 
1984 			return;
1985 		}
1986 
1987 		manager.setCategoryOnExisting( this, category, _category );
1988 
1989 		category = _category;
1990 
1991 		fireChanged();
1992 	}
1993 
1994 	public String
getCategory()1995 	getCategory()
1996 	{
1997 		return( category );
1998 	}
1999 
2000 	public void
setTagID( long _tag_id )2001 	setTagID(
2002 		long	_tag_id )
2003 	{
2004 
2005 		if ( _tag_id == tag_id ){
2006 
2007 			return;
2008 		}
2009 
2010 		// don't update existing download tagging at the moment
2011 		//manager.setTagOnExisting( this, tag, _tag );
2012 
2013 		tag_id = _tag_id;
2014 
2015 		fireChanged();
2016 	}
2017 
2018 	public long
getTagID()2019 	getTagID()
2020 	{
2021 		return( tag_id );
2022 	}
2023 
2024 	public String
getParent()2025 	getParent()
2026 	{
2027 		return( parent );
2028 	}
2029 
2030 	public void
setParent( String _parent )2031 	setParent(
2032 		String		_parent )
2033 	{
2034 		if ( _parent == null && parent == null ){
2035 
2036 			return;
2037 		}
2038 
2039 		if ( _parent != null && parent != null && _parent.equals( parent )){
2040 
2041 			return;
2042 		}
2043 
2044 		parent = _parent;
2045 
2046 		fireChanged();
2047 	}
2048 
2049 	protected void
fireChanged()2050 	fireChanged()
2051 	{
2052 		manager.configDirty( this );
2053 
2054 		Iterator it = listeners.iterator();
2055 
2056 		while( it.hasNext()){
2057 
2058 			try{
2059 				((SubscriptionListener)it.next()).subscriptionChanged( this );
2060 
2061 			}catch( Throwable e ){
2062 
2063 				Debug.printStackTrace(e);
2064 			}
2065 		}
2066 	}
2067 
2068 	protected void
fireDownloaded( boolean was_auto )2069 	fireDownloaded(
2070 		boolean	was_auto )
2071 	{
2072 
2073 		Iterator it = listeners.iterator();
2074 
2075 		while( it.hasNext()){
2076 
2077 			try{
2078 				((SubscriptionListener)it.next()).subscriptionDownloaded( this, was_auto );
2079 
2080 			}catch( Throwable e ){
2081 
2082 				Debug.printStackTrace(e);
2083 			}
2084 		}
2085 	}
2086 
2087 	public void
addListener( SubscriptionListener l )2088 	addListener(
2089 		SubscriptionListener	l )
2090 	{
2091 		listeners.add( l );
2092 	}
2093 
2094 	public void
removeListener( SubscriptionListener l )2095 	removeListener(
2096 		SubscriptionListener	l )
2097 	{
2098 		listeners.remove( l );
2099 	}
2100 
2101 	public SubscriptionHistory
getHistory()2102 	getHistory()
2103 	{
2104 		return( history );
2105 	}
2106 
2107 	public SubscriptionManager
getManager()2108 	getManager()
2109 	{
2110 		return( manager );
2111 	}
2112 
2113 	public VuzeFile
getVuzeFile()2114 	getVuzeFile()
2115 
2116 		throws SubscriptionException
2117 	{
2118 		try{
2119 			return( VuzeFileHandler.getSingleton().loadVuzeFile( manager.getVuzeFile( this ).getAbsolutePath()));
2120 
2121 		}catch( Throwable e ){
2122 
2123 			throw( new SubscriptionException( "Failed to get Vuze file", e ));
2124 		}
2125 	}
2126 
2127 	public VuzeFile
getSearchTemplateVuzeFile()2128 	getSearchTemplateVuzeFile()
2129 	{
2130 		if ( !isSearchTemplate()){
2131 
2132 			return( null );
2133 		}
2134 
2135 		Object[] details = manager.getSearchTemplateVuzeFile( this );
2136 
2137 		if ( details != null ){
2138 
2139 			return((VuzeFile)details[0]);
2140 		}
2141 
2142 		return( null );
2143 	}
2144 
2145 	public boolean
isSearchTemplateImportable()2146 	isSearchTemplateImportable()
2147 	{
2148 		return( manager.isSearchTemplateImportable( this ));
2149 	}
2150 
2151 	protected void
destroy()2152 	destroy()
2153 	{
2154 		LightWeightSeed l;
2155 
2156 		synchronized( this ){
2157 
2158 			destroyed	= true;
2159 
2160 			l = lws;
2161 		}
2162 
2163 		if ( l != null ){
2164 
2165 			l.remove();
2166 		}
2167 	}
2168 
2169 	public void
reset()2170 	reset()
2171 	{
2172 		getHistory().reset();
2173 
2174 		try{
2175 			getEngine().reset();
2176 
2177 		}catch( Throwable e ){
2178 
2179 			Debug.printStackTrace(e);
2180 		}
2181 	}
2182 
2183 	public void
remove()2184 	remove()
2185 	{
2186 		destroy();
2187 
2188 		manager.removeSubscription( this );
2189 	}
2190 
2191 	protected boolean
isRemoved()2192 	isRemoved()
2193 	{
2194 		synchronized( this ){
2195 
2196 			return( destroyed );
2197 		}
2198 	}
2199 
2200 	public SubscriptionResult[]
getResults( boolean include_deleted )2201   	getResults(
2202   		boolean		include_deleted )
2203 	{
2204 		return( getHistory().getResults( include_deleted ));
2205 	}
2206 
2207 	public void
setUserData( Object key, Object data )2208 	setUserData(
2209 		Object		key,
2210 		Object		data )
2211 	{
2212 		synchronized( user_data ){
2213 
2214 			if ( data == null ){
2215 
2216 				user_data.remove( key );
2217 
2218 			}else{
2219 
2220 				user_data.put( key, data );
2221 			}
2222 		}
2223 	}
2224 
2225 	public Object
getUserData( Object key )2226 	getUserData(
2227 		Object		key )
2228 	{
2229 		synchronized( user_data ){
2230 
2231 			return( user_data.get( key ));
2232 		}
2233 	}
2234 
2235 	protected void
log( String str )2236 	log(
2237 		String		str )
2238 	{
2239 		manager.log( getString() + ": " + str );
2240 	}
2241 
2242 	protected void
log( String str, Throwable e )2243 	log(
2244 		String		str,
2245 		Throwable	e )
2246 	{
2247 		manager.log( getString() + ": " + str, e );
2248 	}
2249 
2250 	public String
getString()2251 	getString()
2252 	{
2253 		return( "name=" + name +
2254 					",sid=" + ByteFormatter.encodeString( short_id ) +
2255 					",ver=" + version +
2256 					",pub=" + is_public +
2257 					",anon=" + is_anonymous +
2258 					",mine=" + isMine() +
2259 					",sub=" + is_subscribed +
2260 					(is_subscribed?(",hist={" + history.getString() + "}"):"") +
2261 					",pop=" + popularity +
2262 					(server_publication_outstanding?",spo=true":""));
2263 	}
2264 
2265 	protected void
generate( IndentWriter writer )2266 	generate(
2267 		IndentWriter		writer )
2268 	{
2269 		String	engine_str;
2270 
2271 		try{
2272 
2273 			engine_str = "" + getEngine().getId();
2274 
2275 		}catch( Throwable e ){
2276 
2277 			engine_str = Debug.getNestedExceptionMessage(e);
2278 		}
2279 
2280 		writer.println( getString() + ": engine=" + engine_str );
2281 
2282 		try{
2283 			writer.indent();
2284 
2285 			synchronized( this ){
2286 
2287 				for (int i=0;i<associations.size();i++){
2288 
2289 					((association)associations.get(i)).generate( writer );
2290 				}
2291 			}
2292 		}finally{
2293 
2294 			writer.exdent();
2295 		}
2296 	}
2297 
2298 	protected static class
2299 	association
2300 	{
2301 		private byte[]	hash;
2302 		private long	when;
2303 		private boolean	published;
2304 
2305 		protected
association( byte[] _hash, long _when )2306 		association(
2307 			byte[]		_hash,
2308 			long		_when )
2309 		{
2310 			hash		= _hash;
2311 			when		= _when;
2312 		}
2313 
2314 		protected byte[]
getHash()2315 		getHash()
2316 		{
2317 			return( hash );
2318 		}
2319 
2320 		protected long
getWhen()2321 		getWhen()
2322 		{
2323 			return( when );
2324 		}
2325 
2326 		protected boolean
getPublished()2327 		getPublished()
2328 		{
2329 			return( published );
2330 		}
2331 
2332 		protected void
setPublished( boolean b )2333 		setPublished(
2334 			boolean		b )
2335 		{
2336 			published = b;
2337 		}
2338 
2339 		protected String
getString()2340 		getString()
2341 		{
2342 			return( ByteFormatter.encodeString( hash ) + ", pub=" + published );
2343 		}
2344 
2345 		protected void
generate( IndentWriter writer )2346 		generate(
2347 			IndentWriter		writer )
2348 		{
2349 			writer.println( getString());
2350 		}
2351 	}
2352 }
2353