1 /*
2  * Created on Sep 4, 2013
3  * Created by Paul Gardner
4  *
5  * Copyright 2013 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.tag.impl;
22 
23 import java.util.*;
24 import java.util.regex.Pattern;
25 
26 import org.gudy.azureus2.core3.disk.DiskManager;
27 import org.gudy.azureus2.core3.download.DownloadManager;
28 import org.gudy.azureus2.core3.download.DownloadManagerState;
29 import org.gudy.azureus2.core3.torrent.TOTorrent;
30 import org.gudy.azureus2.core3.tracker.client.TRTrackerScraperResponse;
31 import org.gudy.azureus2.core3.util.AENetworkClassifier;
32 import org.gudy.azureus2.core3.util.AERunnable;
33 import org.gudy.azureus2.core3.util.AsyncDispatcher;
34 import org.gudy.azureus2.core3.util.Debug;
35 import org.gudy.azureus2.core3.util.FrequencyLimitedDispatcher;
36 import org.gudy.azureus2.core3.util.SimpleTimer;
37 import org.gudy.azureus2.core3.util.SystemTime;
38 import org.gudy.azureus2.core3.util.TimerEvent;
39 import org.gudy.azureus2.core3.util.TimerEventPerformer;
40 import org.gudy.azureus2.core3.util.TimerEventPeriodic;
41 import org.gudy.azureus2.plugins.download.Download;
42 import org.gudy.azureus2.plugins.download.DownloadListener;
43 import org.gudy.azureus2.pluginsimpl.local.PluginCoreUtils;
44 
45 import com.aelitis.azureus.core.AzureusCore;
46 import com.aelitis.azureus.core.AzureusCoreFactory;
47 import com.aelitis.azureus.core.AzureusCoreRunningListener;
48 import com.aelitis.azureus.core.tag.Tag;
49 import com.aelitis.azureus.core.tag.TagFeatureProperties;
50 import com.aelitis.azureus.core.tag.TagFeatureProperties.TagProperty;
51 import com.aelitis.azureus.core.tag.TagFeatureProperties.TagPropertyListener;
52 import com.aelitis.azureus.core.tag.TagListener;
53 import com.aelitis.azureus.core.tag.TagType;
54 import com.aelitis.azureus.core.tag.TagTypeListener;
55 import com.aelitis.azureus.core.tag.Taggable;
56 import com.aelitis.azureus.core.tag.TaggableLifecycleAdapter;
57 
58 public class
59 TagPropertyConstraintHandler
60 	implements TagTypeListener, DownloadListener
61 {
62 	private final AzureusCore		azureus_core;
63 	private final TagManagerImpl	tag_manager;
64 
65 	private boolean		initialised;
66 	private boolean 	initial_assignment_complete;
67 
68 	private Map<Tag,TagConstraint>	constrained_tags 	= new HashMap<Tag,TagConstraint>();
69 
70 	private boolean	dm_listener_added;
71 
72 	private Map<Tag,Map<DownloadManager,Long>>			apply_history 		= new HashMap<Tag, Map<DownloadManager,Long>>();
73 
74 	private AsyncDispatcher	dispatcher = new AsyncDispatcher( "tag:constraints" );
75 
76 	private FrequencyLimitedDispatcher	freq_lim_dispatcher =
77 		new FrequencyLimitedDispatcher(
78 			new AERunnable()
79 			{
80 				public void
81 				runSupport()
82 				{
83 					checkFreqLimUpdates();
84 				}
85 			},
86 			5000 );
87 
88 	private IdentityHashMap<DownloadManager,List<TagConstraint>>	freq_lim_pending = new IdentityHashMap<DownloadManager,List<TagConstraint>>();
89 
90 
91 	private TimerEventPeriodic		timer;
92 
93 	private
TagPropertyConstraintHandler()94 	TagPropertyConstraintHandler()
95 	{
96 		azureus_core	= null;
97 		tag_manager		= null;
98 	}
99 
100 	protected
TagPropertyConstraintHandler( AzureusCore _core, TagManagerImpl _tm )101 	TagPropertyConstraintHandler(
102 		AzureusCore		_core,
103 		TagManagerImpl	_tm )
104 	{
105 		azureus_core	= _core;
106 		tag_manager		= _tm;
107 
108 		tag_manager.addTaggableLifecycleListener(
109 			Taggable.TT_DOWNLOAD,
110 			new TaggableLifecycleAdapter()
111 			{
112 				public void
113 				initialised(
114 					List<Taggable>	current_taggables )
115 				{
116 					try{
117 						TagType tt = tag_manager.getTagType( TagType.TT_DOWNLOAD_MANUAL );
118 
119 						tt.addTagTypeListener( TagPropertyConstraintHandler.this, true );
120 
121 					}finally{
122 
123 						AzureusCoreFactory.addCoreRunningListener(
124 							new AzureusCoreRunningListener()
125 							{
126 								public void
127 								azureusCoreRunning(
128 									AzureusCore core )
129 								{
130 									synchronized( constrained_tags ){
131 
132 										initialised = true;
133 
134 										apply( core.getGlobalManager().getDownloadManagers(), true );
135 									}
136 								}
137 							});
138 					}
139 				}
140 
141 				public void
142 				taggableCreated(
143 					Taggable		taggable )
144 				{
145 					apply((DownloadManager)taggable, null, false );
146 				}
147 			});
148 	}
149 
150 	public void
tagTypeChanged( TagType tag_type )151 	tagTypeChanged(
152 		TagType		tag_type )
153 	{
154 	}
155 
156 	public void
tagAdded( Tag tag )157 	tagAdded(
158 		Tag			tag )
159 	{
160 		TagFeatureProperties tfp = (TagFeatureProperties)tag;
161 
162 		TagProperty prop = tfp.getProperty( TagFeatureProperties.PR_CONSTRAINT );
163 
164 		if ( prop != null ){
165 
166 			prop.addListener(
167 				new TagPropertyListener()
168 				{
169 					public void
170 					propertyChanged(
171 						TagProperty		property )
172 					{
173 						handleProperty( property );
174 					}
175 
176 					public void
177 					propertySync(
178 						TagProperty		property )
179 					{
180 					}
181 				});
182 
183 			handleProperty( prop );
184 		}
185 
186 		tag.addTagListener(
187 			new TagListener()
188 			{
189 				public void
190 				taggableSync(
191 					Tag tag )
192 				{
193 				}
194 
195 				public void
196 				taggableRemoved(
197 					Tag 		tag,
198 					Taggable 	tagged )
199 				{
200 					apply((DownloadManager)tagged, tag, true );
201 				}
202 
203 				public void
204 				taggableAdded(
205 					Tag 		tag,
206 					Taggable 	tagged )
207 				{
208 					apply((DownloadManager)tagged, tag, true );
209 				}
210 			}, false );
211 	}
212 
213 	public void
tagChanged( Tag tag )214 	tagChanged(
215 		Tag			tag )
216 	{
217 	}
218 
219 	private void
checkTimer()220 	checkTimer()
221 	{
222 			// already synchronized on constrainted_tags by callers
223 
224 		if ( constrained_tags.size() > 0 ){
225 
226 			if ( timer == null ){
227 
228 				timer =
229 					SimpleTimer.addPeriodicEvent(
230 						"tag:constraint:timer",
231 						30*1000,
232 						new TimerEventPerformer() {
233 
234 							public void
235 							perform(
236 								TimerEvent event)
237 							{
238 								apply_history.clear();
239 
240 								apply();
241 							}
242 						});
243 
244 				AzureusCoreFactory.addCoreRunningListener(
245 					new AzureusCoreRunningListener()
246 					{
247 						public void
248 						azureusCoreRunning(
249 							AzureusCore core )
250 						{
251 							synchronized( constrained_tags ){
252 
253 								if ( timer != null ){
254 
255 									azureus_core.getPluginManager().getDefaultPluginInterface().getDownloadManager().getGlobalDownloadEventNotifier().addListener( TagPropertyConstraintHandler.this );
256 
257 									dm_listener_added = true;
258 								}
259 							}
260 						}
261 					});
262 			}
263 
264 		}else if ( timer != null ){
265 
266 			timer.cancel();
267 
268 			timer = null;
269 
270 			if ( dm_listener_added ){
271 
272 				azureus_core.getPluginManager().getDefaultPluginInterface().getDownloadManager().getGlobalDownloadEventNotifier().removeListener( this );
273 			}
274 
275 			apply_history.clear();
276 		}
277 	}
278 
279 	private void
checkFreqLimUpdates()280 	checkFreqLimUpdates()
281 	{
282 		dispatcher.dispatch(
283 			new AERunnable()
284 			{
285 				public void
286 				runSupport()
287 				{
288 					synchronized( freq_lim_pending ){
289 
290 						for ( Map.Entry<DownloadManager,List<TagConstraint>> entry: freq_lim_pending.entrySet()){
291 
292 							for ( TagConstraint con: entry.getValue()){
293 
294 								con.apply( entry.getKey());
295 							}
296 						}
297 
298 						freq_lim_pending.clear();
299 					}
300 				}
301 			});
302 	}
303 
304 	public void
stateChanged( Download download, int old_state, int new_state )305 	stateChanged(
306 		Download		download,
307 		int				old_state,
308 		int				new_state )
309 	{
310 		List<TagConstraint>	interesting = new ArrayList<TagConstraint>();
311 
312 		synchronized( constrained_tags ){
313 
314 			if ( !initialised ){
315 
316 				return;
317 			}
318 
319 			for ( TagConstraint tc: constrained_tags.values()){
320 
321 				if ( tc.dependOnDownloadState()){
322 
323 					interesting.add( tc );
324 				}
325 			}
326 		}
327 
328 		if ( interesting.size() > 0 ){
329 
330 			DownloadManager dm = PluginCoreUtils.unwrap( download );
331 
332 			synchronized( freq_lim_pending ){
333 
334 				freq_lim_pending.put( dm, interesting );
335 			}
336 
337 			freq_lim_dispatcher.dispatch();
338 		}
339 	}
340 
341 	public void
positionChanged( Download download, int oldPosition, int newPosition )342 	positionChanged(
343 		Download	download,
344 		int 		oldPosition,
345 		int 		newPosition )
346 	{
347 	}
348 
349 	public void
tagRemoved( Tag tag )350 	tagRemoved(
351 		Tag			tag )
352 	{
353 		synchronized( constrained_tags ){
354 
355 			if ( constrained_tags.containsKey( tag )){
356 
357 				constrained_tags.remove( tag );
358 
359 				checkTimer();
360 			}
361 		}
362 	}
363 
364 	private void
handleProperty( TagProperty property )365 	handleProperty(
366 		TagProperty		property )
367 	{
368 		Tag	tag = property.getTag();
369 
370 		synchronized( constrained_tags ){
371 
372 			String[] value = property.getStringList();
373 
374 			String 	constraint;
375 			String	options;
376 
377 			if ( value == null ){
378 
379 				constraint 	= "";
380 				options		= "";
381 
382 			}else{
383 
384 				constraint 	= value.length>0&&value[0]!=null?value[0].trim():"";
385 				options		= value.length>1&&value[1]!=null?value[1].trim():"";
386 			}
387 
388 			if ( constraint.length() == 0 ){
389 
390 				if ( constrained_tags.containsKey( tag )){
391 
392 					constrained_tags.remove( tag );
393 				}
394 			}else{
395 
396 				TagConstraint con = constrained_tags.get( tag );
397 
398 				if ( con != null && con.getConstraint().equals( constraint ) && con.getOptions().equals( options )){
399 
400 					return;
401 				}
402 
403 				Set<Taggable> existing = tag.getTagged();
404 
405 				for ( Taggable e: existing ){
406 
407 					tag.removeTaggable( e );
408 				}
409 
410 				con = new TagConstraint( this, tag, constraint, options );
411 
412 				constrained_tags.put( tag, con );
413 
414 				if ( initialised ){
415 
416 					apply( con );
417 				}
418 			}
419 
420 			checkTimer();
421 		}
422 	}
423 
424 	private void
apply( final DownloadManager dm, Tag related_tag, boolean auto )425 	apply(
426 		final DownloadManager				dm,
427 		Tag									related_tag,
428 		boolean								auto )
429 	{
430 		if ( dm.isDestroyed()){
431 
432 			return;
433 		}
434 
435 		synchronized( constrained_tags ){
436 
437 			if ( constrained_tags.size() == 0 || !initialised ){
438 
439 				return;
440 			}
441 
442 			if ( auto && !initial_assignment_complete ){
443 
444 				return;
445 			}
446 		}
447 
448 		dispatcher.dispatch(
449 			new AERunnable()
450 			{
451 				public void
452 				runSupport()
453 				{
454 					List<TagConstraint>	cons;
455 
456 					synchronized( constrained_tags ){
457 
458 						cons = new ArrayList<TagConstraint>( constrained_tags.values());
459 					}
460 
461 					for ( TagConstraint con: cons ){
462 
463 						con.apply( dm );
464 					}
465 				}
466 			});
467 	}
468 
469 	private void
apply( final List<DownloadManager> dms, final boolean initial_assignment )470 	apply(
471 		final List<DownloadManager>		dms,
472 		final boolean					initial_assignment )
473 	{
474 		synchronized( constrained_tags ){
475 
476 			if ( constrained_tags.size() == 0 || !initialised ){
477 
478 				return;
479 			}
480 		}
481 
482 		dispatcher.dispatch(
483 			new AERunnable()
484 			{
485 				public void
486 				runSupport()
487 				{
488 					List<TagConstraint>	cons;
489 
490 					synchronized( constrained_tags ){
491 
492 						cons = new ArrayList<TagConstraint>( constrained_tags.values());
493 					}
494 
495 						// set up initial constraint tagged state without following implications
496 
497 					for ( TagConstraint con: cons ){
498 
499 						con.apply( dms );
500 					}
501 
502 					if ( initial_assignment ){
503 
504 						synchronized( constrained_tags ){
505 
506 							initial_assignment_complete = true;
507 						}
508 
509 							// go over them one more time to pick up consequential constraints
510 
511 						for ( TagConstraint con: cons ){
512 
513 							con.apply( dms );
514 						}
515 					}
516 				}
517 			});
518 	}
519 
520 	private void
apply( final TagConstraint constraint )521 	apply(
522 		final TagConstraint		constraint )
523 	{
524 		synchronized( constrained_tags ){
525 
526 			if ( !initialised ){
527 
528 				return;
529 			}
530 		}
531 
532 		dispatcher.dispatch(
533 			new AERunnable()
534 			{
535 				public void
536 				runSupport()
537 				{
538 					List<DownloadManager> dms = azureus_core.getGlobalManager().getDownloadManagers();
539 
540 					constraint.apply( dms );
541 				}
542 			});
543 	}
544 
545 	private void
apply()546 	apply()
547 	{
548 		synchronized( constrained_tags ){
549 
550 			if ( constrained_tags.size() == 0 || !initialised ){
551 
552 				return;
553 			}
554 		}
555 
556 		dispatcher.dispatch(
557 			new AERunnable()
558 			{
559 				public void
560 				runSupport()
561 				{
562 					List<DownloadManager> dms = azureus_core.getGlobalManager().getDownloadManagers();
563 
564 					List<TagConstraint>	cons;
565 
566 					synchronized( constrained_tags ){
567 
568 						cons = new ArrayList<TagConstraint>( constrained_tags.values());
569 					}
570 
571 					for ( TagConstraint con: cons ){
572 
573 						con.apply( dms );
574 					}
575 				}
576 			});
577 	}
578 
579 	private TagConstraint.ConstraintExpr
compileConstraint( String expr )580 	compileConstraint(
581 		String		expr )
582 	{
583 		return( new TagConstraint( this, null, expr, null ).expr );
584 	}
585 
586 	private static class
587 	TagConstraint
588 	{
589 		private final TagPropertyConstraintHandler	handler;
590 		private final Tag							tag;
591 		private final String						constraint;
592 
593 		private final boolean		auto_add;
594 		private final boolean		auto_remove;
595 
596 		private final ConstraintExpr	expr;
597 
598 		private boolean	depends_on_download_state;
599 
600 		private
TagConstraint( TagPropertyConstraintHandler _handler, Tag _tag, String _constraint, String options )601 		TagConstraint(
602 			TagPropertyConstraintHandler	_handler,
603 			Tag								_tag,
604 			String							_constraint,
605 			String							options )
606 		{
607 			handler		= _handler;
608 			tag			= _tag;
609 			constraint	= _constraint;
610 
611 			if ( options == null ){
612 
613 				auto_add	= true;
614 				auto_remove	= true;
615 
616 			}else{
617 					// 0 = add+remove; 1 = add only; 2 = remove only
618 
619 				auto_add 	= !options.contains( "am=2;" );
620 				auto_remove = !options.contains( "am=1;" );
621 			}
622 
623 			ConstraintExpr compiled_expr = null;
624 
625 			try{
626 				compiled_expr = compileStart( constraint, new HashMap<String,ConstraintExpr>());
627 
628 			}catch( Throwable e ){
629 
630 				Debug.out( "Invalid constraint: " + constraint + " - " + Debug.getNestedExceptionMessage( e ));
631 
632 			}finally{
633 
634 				expr = compiled_expr;
635 			}
636 		}
637 
638 		private boolean
dependOnDownloadState()639 		dependOnDownloadState()
640 		{
641 			return( depends_on_download_state );
642 		}
643 
644 		private ConstraintExpr
compileStart( String str, Map<String,ConstraintExpr> context )645 		compileStart(
646 			String						str,
647 			Map<String,ConstraintExpr>	context )
648 		{
649 			str = str.trim();
650 
651 			if ( str.equalsIgnoreCase( "true" )){
652 
653 				return( new ConstraintExprTrue());
654 			}
655 
656 			char[] chars = str.toCharArray();
657 
658 			boolean	in_quote 	= false;
659 
660 			int	level 			= 0;
661 			int	bracket_start 	= 0;
662 
663 			StringBuffer result = new StringBuffer( str.length());
664 
665 			for ( int i=0;i<chars.length;i++){
666 
667 				char c = chars[i];
668 
669 				if ( c == '"' ){
670 
671 					if ( i == 0 || chars[i-1] != '\\' ){
672 
673 						in_quote = !in_quote;
674 					}
675 				}
676 
677 				if ( !in_quote ){
678 
679 					if ( c == '(' ){
680 
681 						level++;
682 
683 						if ( level == 1 ){
684 
685 							bracket_start = i+1;
686 						}
687 					}else if ( c == ')' ){
688 
689 						level--;
690 
691 						if ( level == 0 ){
692 
693 							String bracket_text = new String( chars, bracket_start, i-bracket_start ).trim();
694 
695 							if ( result.length() > 0 && Character.isLetterOrDigit( result.charAt( result.length()-1 ))){
696 
697 									// function call
698 
699 								String key = "{" + context.size() + "}";
700 
701 								context.put( key, new ConstraintExprParams( bracket_text ));
702 
703 								result.append( "(" ).append( key ).append( ")" );
704 
705 							}else{
706 
707 								ConstraintExpr sub_expr = compileStart( bracket_text, context );
708 
709 								String key = "{" + context.size() + "}";
710 
711 								context.put(key, sub_expr );
712 
713 								result.append( key );
714 							}
715 						}
716 					}else if ( level == 0 ){
717 
718 						if ( !Character.isWhitespace( c )){
719 
720 							result.append( c );
721 						}
722 					}
723 				}else if ( level == 0 ){
724 
725 					result.append( c );
726 
727 				}
728 			}
729 
730 			if ( level != 0 ){
731 
732 				throw( new RuntimeException( "Unmatched '(' in \"" + str + "\"" ));
733 			}
734 
735 			if ( in_quote ){
736 
737 				throw( new RuntimeException( "Unmatched '\"' in \"" + str + "\"" ));
738 			}
739 
740 			return( compileBasic( result.toString(), context ));
741 		}
742 
743 		private ConstraintExpr
compileBasic( String str, Map<String,ConstraintExpr> context )744 		compileBasic(
745 			String						str,
746 			Map<String,ConstraintExpr>	context )
747 		{
748 			if ( str.startsWith( "{" )){
749 
750 				return( context.get( str ));
751 
752 			}else if ( str.contains( "||" )){
753 
754 				String[] bits = str.split( "\\|\\|" );
755 
756 				return( new ConstraintExprOr( compile( bits, context )));
757 
758 			}else if ( str.contains( "&&" )){
759 
760 				String[] bits = str.split( "&&" );
761 
762 				return( new ConstraintExprAnd( compile( bits, context )));
763 
764 			}else if ( str.contains( "^" )){
765 
766 				String[] bits = str.split( "\\^" );
767 
768 				return( new ConstraintExprXor( compile( bits, context )));
769 
770 			}else if ( str.startsWith( "!" )){
771 
772 				return( new ConstraintExprNot( compileBasic( str.substring(1).trim(), context )));
773 
774 			}else{
775 
776 				int	pos = str.indexOf( '(' );
777 
778 				if ( pos > 0 && str.endsWith( ")" )){
779 
780 					String func = str.substring( 0, pos );
781 
782 					String key = str.substring( pos+1, str.length() - 1 ).trim();
783 
784 					ConstraintExprParams params = (ConstraintExprParams)context.get( key );
785 
786 					return( new ConstraintExprFunction( func, params ));
787 
788 				}else{
789 
790 					throw( new RuntimeException( "Unsupported construct: " + str ));
791 				}
792 			}
793 		}
794 
795 		private ConstraintExpr[]
compile( String[] bits, Map<String,ConstraintExpr> context )796 		compile(
797 			String[]					bits,
798 			Map<String,ConstraintExpr>	context )
799 		{
800 			ConstraintExpr[] res = new ConstraintExpr[ bits.length ];
801 
802 			for ( int i=0; i<bits.length;i++){
803 
804 				res[i] = compileBasic( bits[i].trim(), context );
805 			}
806 
807 			return( res );
808 		}
809 
810 		private Tag
getTag()811 		getTag()
812 		{
813 			return( tag );
814 		}
815 
816 		private String
getConstraint()817 		getConstraint()
818 		{
819 			return( constraint );
820 		}
821 
822 		private String
getOptions()823 		getOptions()
824 		{
825 			if ( auto_add ){
826 				return( "am=1;" );
827 			}else if ( auto_remove ){
828 				return( "am=2;" );
829 			}else{
830 				return( "am=0;" );
831 			}
832 		}
833 
834 		private void
apply( DownloadManager dm )835 		apply(
836 			DownloadManager			dm )
837 		{
838 			if ( dm.isDestroyed() || !dm.isPersistent()){
839 
840 				return;
841 			}
842 
843 			if ( expr == null ){
844 
845 				return;
846 			}
847 
848 			Set<Taggable>	existing = tag.getTagged();
849 
850 			if ( testConstraint( dm )){
851 
852 				if ( auto_add ){
853 
854 					if ( !existing.contains( dm )){
855 
856 						if( canAddTaggable( dm )){
857 
858 							tag.addTaggable( dm );
859 						}
860 					}
861 				}
862 			}else{
863 
864 				if ( auto_remove ){
865 
866 					if ( existing.contains( dm )){
867 
868 						tag.removeTaggable( dm );
869 					}
870 				}
871 			}
872 		}
873 
874 		private void
apply( List<DownloadManager> dms )875 		apply(
876 			List<DownloadManager>	dms )
877 		{
878 			if ( expr == null ){
879 
880 				return;
881 			}
882 
883 			Set<Taggable>	existing = tag.getTagged();
884 
885 			for ( DownloadManager dm: dms ){
886 
887 				if ( dm.isDestroyed() || !dm.isPersistent()){
888 
889 					continue;
890 				}
891 
892 				if ( testConstraint( dm )){
893 
894 					if ( auto_add ){
895 
896 						if ( !existing.contains( dm )){
897 
898 							if ( canAddTaggable( dm )){
899 
900 								tag.addTaggable( dm );
901 							}
902 						}
903 					}
904 				}else{
905 
906 					if ( auto_remove ){
907 
908 						if ( existing.contains( dm )){
909 
910 							tag.removeTaggable( dm );
911 						}
912 					}
913 				}
914 			}
915 		}
916 
917 
918 
919 		private boolean
canAddTaggable( DownloadManager dm )920 		canAddTaggable(
921 			DownloadManager		dm )
922 		{
923 			long	now = SystemTime.getMonotonousTime();
924 
925 			Map<DownloadManager,Long> recent_dms = handler.apply_history.get( tag );
926 
927 			if ( recent_dms != null ){
928 
929 				Long time = recent_dms.get( dm );
930 
931 				if ( time != null && now - time < 1000 ){
932 
933 					System.out.println( "Not applying constraint as too recently actioned: " + dm.getDisplayName() + "/" + tag.getTagName( true ));
934 
935 					return( false );
936 				}
937 			}
938 
939 			if ( recent_dms == null ){
940 
941 				recent_dms = new HashMap<DownloadManager,Long>();
942 
943 				handler.apply_history.put( tag, recent_dms );
944 			}
945 
946 			recent_dms.put( dm, now );
947 
948 			return( true );
949 		}
950 
951 		private boolean
testConstraint( DownloadManager dm )952 		testConstraint(
953 			DownloadManager	dm )
954 		{
955 			List<Tag> dm_tags = handler.tag_manager.getTagsForTaggable( dm );
956 
957 			return( expr.eval( dm, dm_tags ));
958 		}
959 
960 		private interface
961 		ConstraintExpr
962 		{
963 			public boolean
eval( DownloadManager dm, List<Tag> tags )964 			eval(
965 				DownloadManager		dm,
966 				List<Tag>			tags );
967 
968 			public String
getString()969 			getString();
970 		}
971 
972 		private class
973 		ConstraintExprTrue
974 			implements ConstraintExpr
975 		{
976 			public boolean
eval( DownloadManager dm, List<Tag> tags )977 			eval(
978 				DownloadManager		dm,
979 				List<Tag>			tags )
980 			{
981 				return( true );
982 			}
983 
984 			public String
getString()985 			getString()
986 			{
987 				return( "true" );
988 			}
989 		}
990 
991 		private class
992 		ConstraintExprParams
993 			implements  ConstraintExpr
994 		{
995 			private String	value;
996 
997 			private
ConstraintExprParams( String _value )998 			ConstraintExprParams(
999 				String	_value )
1000 			{
1001 				value = _value.trim();
1002 			}
1003 
1004 			public boolean
eval( DownloadManager dm, List<Tag> tags )1005 			eval(
1006 				DownloadManager		dm,
1007 				List<Tag>			tags )
1008 			{
1009 				return( false );
1010 			}
1011 
1012 			public Object[]
getValues()1013 			getValues()
1014 			{
1015 				if ( value.length() == 0 ){
1016 
1017 					return( new String[0]);
1018 
1019 				}else if ( !value.contains( "," )){
1020 
1021 					return( new Object[]{ value });
1022 
1023 				}else{
1024 
1025 					char[]	chars = value.toCharArray();
1026 
1027 					boolean in_quote = false;
1028 
1029 					List<String>	params = new ArrayList<String>(16);
1030 
1031 					StringBuffer current_param = new StringBuffer( value.length());
1032 
1033 					for (int i=0;i<chars.length;i++){
1034 
1035 						char c = chars[i];
1036 
1037 						if ( c == '"' ){
1038 
1039 							if ( i == 0 || chars[i-1] != '\\' ){
1040 
1041 								in_quote = !in_quote;
1042 							}
1043 						}
1044 
1045 						if ( c == ',' && !in_quote ){
1046 
1047 							params.add( current_param.toString());
1048 
1049 							current_param.setLength( 0 );
1050 
1051 						}else{
1052 
1053 							if ( in_quote || !Character.isWhitespace( c )){
1054 
1055 								current_param.append( c );
1056 							}
1057 						}
1058 					}
1059 
1060 					params.add( current_param.toString());
1061 
1062 					return( params.toArray( new Object[ params.size()]));
1063 				}
1064 			}
1065 
1066 			public String
getString()1067 			getString()
1068 			{
1069 				return( value );
1070 			}
1071 		}
1072 
1073 		private class
1074 		ConstraintExprNot
1075 			implements  ConstraintExpr
1076 		{
1077 			private	ConstraintExpr expr;
1078 
1079 			private
ConstraintExprNot( ConstraintExpr e )1080 			ConstraintExprNot(
1081 				ConstraintExpr	e )
1082 			{
1083 				expr = e;
1084 			}
1085 
1086 			public boolean
eval( DownloadManager dm, List<Tag> tags )1087 			eval(
1088 				DownloadManager		dm,
1089 				List<Tag>			tags )
1090 			{
1091 				return( !expr.eval( dm, tags ));
1092 			}
1093 
1094 			public String
getString()1095 			getString()
1096 			{
1097 				return( "!(" + expr.getString() + ")");
1098 			}
1099 		}
1100 
1101 		private class
1102 		ConstraintExprOr
1103 			implements  ConstraintExpr
1104 		{
1105 			private ConstraintExpr[]	exprs;
1106 
1107 			private
ConstraintExprOr( ConstraintExpr[] _exprs )1108 			ConstraintExprOr(
1109 				ConstraintExpr[]	_exprs )
1110 			{
1111 				exprs = _exprs;
1112 			}
1113 
1114 			public boolean
eval( DownloadManager dm, List<Tag> tags )1115 			eval(
1116 				DownloadManager		dm,
1117 				List<Tag>			tags )
1118 			{
1119 				for ( ConstraintExpr expr: exprs ){
1120 
1121 					if ( expr.eval( dm, tags )){
1122 
1123 						return( true );
1124 					}
1125 				}
1126 
1127 				return( false );
1128 			}
1129 
1130 			public String
getString()1131 			getString()
1132 			{
1133 				String res = "";
1134 
1135 				for ( int i=0;i<exprs.length;i++){
1136 
1137 					res += (i==0?"":"||") + exprs[i].getString();
1138 				}
1139 
1140 				return( "(" + res + ")" );
1141 			}
1142 		}
1143 
1144 		private class
1145 		ConstraintExprAnd
1146 			implements  ConstraintExpr
1147 		{
1148 			private ConstraintExpr[]	exprs;
1149 
1150 			private
ConstraintExprAnd( ConstraintExpr[] _exprs )1151 			ConstraintExprAnd(
1152 				ConstraintExpr[]	_exprs )
1153 			{
1154 				exprs = _exprs;
1155 			}
1156 
1157 			public boolean
eval( DownloadManager dm, List<Tag> tags )1158 			eval(
1159 				DownloadManager		dm,
1160 				List<Tag>			tags )
1161 			{
1162 				for ( ConstraintExpr expr: exprs ){
1163 
1164 					if ( !expr.eval( dm, tags )){
1165 
1166 						return( false );
1167 					}
1168 				}
1169 
1170 				return( true );
1171 			}
1172 
1173 			public String
getString()1174 			getString()
1175 			{
1176 				String res = "";
1177 
1178 				for ( int i=0;i<exprs.length;i++){
1179 
1180 					res += (i==0?"":"&&") + exprs[i].getString();
1181 				}
1182 
1183 				return( "(" + res + ")" );
1184 			}
1185 		}
1186 
1187 		private class
1188 		ConstraintExprXor
1189 			implements  ConstraintExpr
1190 		{
1191 			private ConstraintExpr[]	exprs;
1192 
1193 			private
ConstraintExprXor( ConstraintExpr[] _exprs )1194 			ConstraintExprXor(
1195 				ConstraintExpr[]	_exprs )
1196 			{
1197 				exprs = _exprs;
1198 
1199 				if ( exprs.length < 2 ){
1200 
1201 					throw( new RuntimeException( "Two or more arguments required for ^" ));
1202 				}
1203 			}
1204 
1205 			public boolean
eval( DownloadManager dm, List<Tag> tags )1206 			eval(
1207 				DownloadManager		dm,
1208 				List<Tag>			tags )
1209 			{
1210 				boolean res = exprs[0].eval( dm, tags );
1211 
1212 				for ( int i=1;i<exprs.length;i++){
1213 
1214 					res = res ^ exprs[i].eval( dm, tags );
1215 				}
1216 
1217 				return( res );
1218 			}
1219 
1220 			public String
getString()1221 			getString()
1222 			{
1223 				String res = "";
1224 
1225 				for ( int i=0;i<exprs.length;i++){
1226 
1227 					res += (i==0?"":"^") + exprs[i].getString();
1228 				}
1229 
1230 				return( "(" + res + ")" );
1231 			}
1232 		}
1233 
1234 		private static final int FT_HAS_TAG		= 1;
1235 		private static final int FT_IS_PRIVATE	= 2;
1236 
1237 		private static final int FT_GE			= 3;
1238 		private static final int FT_GT			= 4;
1239 		private static final int FT_LE			= 5;
1240 		private static final int FT_LT			= 6;
1241 		private static final int FT_EQ			= 7;
1242 		private static final int FT_NEQ			= 8;
1243 
1244 		private static final int FT_CONTAINS	= 9;
1245 		private static final int FT_MATCHES		= 10;
1246 
1247 		private static final int FT_HAS_NET			= 11;
1248 		private static final int FT_IS_COMPLETE		= 12;
1249 		private static final int FT_CAN_ARCHIVE		= 13;
1250 		private static final int FT_IS_FORCE_START	= 14;
1251 		private static final int FT_JAVASCRIPT		= 15;
1252 		private static final int FT_IS_CHECKING		= 16;
1253 		private static final int FT_IS_STOPPED		= 17;
1254 		private static final int FT_IS_PAUSED		= 18;
1255 
1256 
1257 		private static Map<String,Integer>	keyword_map = new HashMap<String, Integer>();
1258 
1259 		private static final int	KW_SHARE_RATIO		= 0;
1260 		private static final int	KW_AGE 				= 1;
1261 		private static final int	KW_PERCENT 			= 2;
1262 		private static final int	KW_DOWNLOADING_FOR 	= 3;
1263 		private static final int	KW_SEEDING_FOR 		= 4;
1264 		private static final int	KW_SWARM_MERGE 		= 5;
1265 		private static final int	KW_LAST_ACTIVE 		= 6;
1266 		private static final int	KW_SEED_COUNT 		= 7;
1267 		private static final int	KW_PEER_COUNT 		= 8;
1268 		private static final int	KW_SEED_PEER_RATIO 	= 9;
1269 		private static final int	KW_RESUME_IN 		= 10;
1270 
1271 		static{
1272 			keyword_map.put( "shareratio", KW_SHARE_RATIO );
1273 			keyword_map.put( "share_ratio", KW_SHARE_RATIO );
1274 			keyword_map.put( "age", KW_AGE );
1275 			keyword_map.put( "percent", KW_PERCENT );
1276 			keyword_map.put( "downloadingfor", KW_DOWNLOADING_FOR );
1277 			keyword_map.put( "downloading_for", KW_DOWNLOADING_FOR );
1278 			keyword_map.put( "seedingfor", KW_SEEDING_FOR );
1279 			keyword_map.put( "seeding_for", KW_SEEDING_FOR );
1280 			keyword_map.put( "swarmmergebytes", KW_SWARM_MERGE );
1281 			keyword_map.put( "swarm_merge_bytes", KW_SWARM_MERGE );
1282 			keyword_map.put( "lastactive", KW_LAST_ACTIVE );
1283 			keyword_map.put( "last_active", KW_LAST_ACTIVE );
1284 			keyword_map.put( "seedcount", KW_SEED_COUNT );
1285 			keyword_map.put( "seed_count", KW_SEED_COUNT );
1286 			keyword_map.put( "peercount", KW_PEER_COUNT );
1287 			keyword_map.put( "peer_count", KW_PEER_COUNT );
1288 			keyword_map.put( "seedpeerratio", KW_SEED_PEER_RATIO );
1289 			keyword_map.put( "seed_peer_ratio", KW_SEED_PEER_RATIO );
1290 			keyword_map.put( "resumein", KW_RESUME_IN );
1291 			keyword_map.put( "resume_in", KW_RESUME_IN );
1292 		}
1293 
1294 		private class
1295 		ConstraintExprFunction
1296 			implements  ConstraintExpr
1297 		{
1298 
1299 			private	final String 				func_name;
1300 			private final ConstraintExprParams	params_expr;
1301 			private final Object[]				params;
1302 
1303 			private final int	fn_type;
1304 
1305 			private
ConstraintExprFunction( String _func_name, ConstraintExprParams _params )1306 			ConstraintExprFunction(
1307 				String 					_func_name,
1308 				ConstraintExprParams	_params )
1309 			{
1310 				func_name	= _func_name;
1311 				params_expr	= _params;
1312 
1313 				params		= _params.getValues();
1314 
1315 				boolean	params_ok = false;
1316 
1317 				if ( func_name.equals( "hasTag" )){
1318 
1319 					fn_type = FT_HAS_TAG;
1320 
1321 					params_ok = params.length == 1 && getStringLiteral( params, 0 );
1322 
1323 				}else if ( func_name.equals( "hasNet" )){
1324 
1325 					fn_type = FT_HAS_NET;
1326 
1327 					params_ok = params.length == 1 && getStringLiteral( params, 0 );
1328 
1329 					if ( params_ok ){
1330 
1331 						params[0] = AENetworkClassifier.internalise((String)params[0]);
1332 
1333 						params_ok = params[0] != null;
1334 					}
1335 				}else if ( func_name.equals( "isPrivate" )){
1336 
1337 					fn_type = FT_IS_PRIVATE;
1338 
1339 					params_ok = params.length == 0;
1340 
1341 				}else if ( func_name.equals( "isForceStart" )){
1342 
1343 					fn_type = FT_IS_FORCE_START;
1344 
1345 					depends_on_download_state = true;
1346 
1347 					params_ok = params.length == 0;
1348 
1349 				}else if ( func_name.equals( "isChecking" )){
1350 
1351 					fn_type = FT_IS_CHECKING;
1352 
1353 					depends_on_download_state = true;
1354 
1355 					params_ok = params.length == 0;
1356 
1357 				}else if ( func_name.equals( "isComplete" )){
1358 
1359 					fn_type = FT_IS_COMPLETE;
1360 
1361 					depends_on_download_state = true;
1362 
1363 					params_ok = params.length == 0;
1364 
1365 				}else if ( func_name.equals( "isStopped" )){
1366 
1367 					fn_type = FT_IS_STOPPED;
1368 
1369 					depends_on_download_state = true;
1370 
1371 					params_ok = params.length == 0;
1372 
1373 				}else if ( func_name.equals( "isPaused" )){
1374 
1375 					fn_type = FT_IS_PAUSED;
1376 
1377 					depends_on_download_state = true;
1378 
1379 					params_ok = params.length == 0;
1380 
1381 				}else if ( func_name.equals( "canArchive" )){
1382 
1383 					fn_type = FT_CAN_ARCHIVE;
1384 
1385 					params_ok = params.length == 0;
1386 
1387 				}else if ( func_name.equals( "isGE" )){
1388 
1389 					fn_type = FT_GE;
1390 
1391 					params_ok = params.length == 2;
1392 
1393 				}else if ( func_name.equals( "isGT" )){
1394 
1395 					fn_type = FT_GT;
1396 
1397 					params_ok = params.length == 2;
1398 
1399 				}else if ( func_name.equals( "isLE" )){
1400 
1401 					fn_type = FT_LE;
1402 
1403 					params_ok = params.length == 2;
1404 
1405 				}else if ( func_name.equals( "isLT" )){
1406 
1407 					fn_type = FT_LT;
1408 
1409 					params_ok = params.length == 2;
1410 
1411 				}else if ( func_name.equals( "isEQ" )){
1412 
1413 					fn_type = FT_EQ;
1414 
1415 					params_ok = params.length == 2;
1416 
1417 				}else if ( func_name.equals( "isNEQ" )){
1418 
1419 					fn_type = FT_NEQ;
1420 
1421 					params_ok = params.length == 2;
1422 
1423 				}else if ( func_name.equals( "contains" )){
1424 
1425 					fn_type = FT_CONTAINS;
1426 
1427 					params_ok = params.length == 2;
1428 
1429 				}else if ( func_name.equals( "matches" )){
1430 
1431 					fn_type = FT_MATCHES;
1432 
1433 					params_ok = params.length == 2 && getStringLiteral( params, 1 );
1434 
1435 				}else if ( func_name.equals( "javascript" )){
1436 
1437 					fn_type = FT_JAVASCRIPT;
1438 
1439 					params_ok = params.length == 1 && getStringLiteral( params, 0 );
1440 
1441 					depends_on_download_state = true;	// dunno so let's assume so
1442 
1443 				}else{
1444 
1445 					throw( new RuntimeException( "Unsupported function '" + func_name + "'" ));
1446 				}
1447 
1448 				if ( !params_ok ){
1449 
1450 					throw( new RuntimeException( "Invalid parameters for function '" + func_name + "': " + params_expr.getString()));
1451 
1452 				}
1453 			}
1454 
1455 			public boolean
eval( DownloadManager dm, List<Tag> tags )1456 			eval(
1457 				DownloadManager		dm,
1458 				List<Tag>			tags )
1459 			{
1460 				switch( fn_type ){
1461 					case FT_HAS_TAG:{
1462 
1463 						String tag_name = (String)params[0];
1464 
1465 						for ( Tag t: tags ){
1466 
1467 							if ( t.getTagName( true ).equals( tag_name )){
1468 
1469 								return( true );
1470 							}
1471 						}
1472 
1473 						return( false );
1474 					}
1475 					case FT_HAS_NET:{
1476 
1477 						String net_name = (String)params[0];
1478 
1479 						if ( net_name != null ){
1480 
1481 							String[] nets = dm.getDownloadState().getNetworks();
1482 
1483 							if ( nets != null ){
1484 
1485 								for ( String net: nets ){
1486 
1487 									if ( net == net_name ){
1488 
1489 										return( true );
1490 									}
1491 								}
1492 							}
1493 						}
1494 
1495 						return( false );
1496 					}
1497 					case FT_IS_PRIVATE:{
1498 
1499 						TOTorrent t = dm.getTorrent();
1500 
1501 						return( t != null && t.getPrivate());
1502 					}
1503 					case FT_IS_FORCE_START:{
1504 
1505 						return( dm.isForceStart());
1506 					}
1507 					case FT_IS_CHECKING:{
1508 
1509 						int state = dm.getState();
1510 
1511 						if ( state == DownloadManager.STATE_CHECKING ){
1512 
1513 							return( true );
1514 
1515 						}else if ( state == DownloadManager.STATE_SEEDING ){
1516 
1517 							DiskManager disk_manager = dm.getDiskManager();
1518 
1519 							if ( disk_manager != null ){
1520 
1521 								return( disk_manager.getCompleteRecheckStatus() != -1 );
1522 							}
1523 						}
1524 
1525 						return( false );
1526 					}
1527 					case FT_IS_COMPLETE:{
1528 
1529 						return( dm.isDownloadComplete( false ));
1530 					}
1531 					case FT_IS_STOPPED:{
1532 
1533 						int state = dm.getState();
1534 
1535 						return( state == DownloadManager.STATE_STOPPED && !dm.isPaused());
1536 					}
1537 					case FT_IS_PAUSED:{
1538 
1539 						return( dm.isPaused());
1540 					}
1541 					case FT_CAN_ARCHIVE:{
1542 
1543 						Download dl = PluginCoreUtils.wrap( dm );
1544 
1545 						return( dl != null && dl.canStubbify());
1546 					}
1547 					case FT_GE:
1548 					case FT_GT:
1549 					case FT_LE:
1550 					case FT_LT:
1551 					case FT_EQ:
1552 					case FT_NEQ:{
1553 
1554 						Number n1 = getNumeric( dm, params, 0 );
1555 						Number n2 = getNumeric( dm, params, 1 );
1556 
1557 						switch( fn_type ){
1558 
1559 							case FT_GE:
1560 								return( n1.doubleValue() >= n2.doubleValue());
1561 							case FT_GT:
1562 								return( n1.doubleValue() > n2.doubleValue());
1563 							case FT_LE:
1564 								return( n1.doubleValue() <= n2.doubleValue());
1565 							case FT_LT:
1566 								return( n1.doubleValue() < n2.doubleValue());
1567 							case FT_EQ:
1568 								return( n1.doubleValue() == n2.doubleValue());
1569 							case FT_NEQ:
1570 								return( n1.doubleValue() != n2.doubleValue());
1571 						}
1572 
1573 						return( false );
1574 					}
1575 					case FT_CONTAINS:{
1576 
1577 						String	s1 = getString( dm, params, 0 );
1578 						String	s2 = getString( dm, params, 1 );
1579 
1580 						return( s1.contains( s2 ));
1581 					}
1582 					case FT_MATCHES:{
1583 
1584 						String	s1 = getString( dm, params, 0 );
1585 
1586 						if ( params[1] == null ){
1587 
1588 							return( false );
1589 
1590 						}else if ( params[1] instanceof Pattern ){
1591 
1592 							return(((Pattern)params[1]).matcher( s1 ).find());
1593 
1594 						}else{
1595 
1596 							try{
1597 								Pattern p = Pattern.compile((String)params[1], Pattern.CASE_INSENSITIVE );
1598 
1599 								params[1] = p;
1600 
1601 								return( p.matcher( s1 ).find());
1602 
1603 							}catch( Throwable e ){
1604 
1605 								Debug.out( "Invalid constraint pattern: " + params[1] );
1606 
1607 								params[1] = null;
1608 							}
1609 						}
1610 
1611 						return( false );
1612 					}
1613 					case FT_JAVASCRIPT:{
1614 
1615 						Object result =
1616 							handler.tag_manager.evalScript(
1617 								tag,
1618 								"javascript( " + (String)params[0] + ")",
1619 								dm,
1620 								"inTag" );
1621 
1622 						if ( result instanceof Boolean ){
1623 
1624 							return((Boolean)result);
1625 						}
1626 
1627 						return( false );
1628 					}
1629 				}
1630 
1631 				return( false );
1632 			}
1633 
1634 			private boolean
getStringLiteral( Object[] args, int index )1635 			getStringLiteral(
1636 				Object[]	args,
1637 				int			index )
1638 			{
1639 				Object _arg = args[index];
1640 
1641 				if ( _arg instanceof String ){
1642 
1643 					String arg = (String)_arg;
1644 
1645 					if ( arg.startsWith( "\"" ) && arg.endsWith( "\"" )){
1646 
1647 						args[index] = arg.substring( 1, arg.length() - 1 );
1648 
1649 						return( true );
1650 					}
1651 				}
1652 
1653 				return( false );
1654 			}
1655 
1656 			private String
getString( DownloadManager dm, Object[] args, int index )1657 			getString(
1658 				DownloadManager		dm,
1659 				Object[]			args,
1660 				int					index )
1661 			{
1662 				String str = (String)args[index];
1663 
1664 				if ( str.startsWith( "\"" ) && str.endsWith( "\"" )){
1665 
1666 					return( str.substring( 1, str.length() - 1 ));
1667 
1668 				}else if ( str.equals( "name" )){
1669 
1670 					return( dm.getDisplayName());
1671 
1672 				}else{
1673 
1674 					Debug.out( "Invalid constraint string: " + str );
1675 
1676 					String result = "\"\"";
1677 
1678 					args[index] = result;
1679 
1680 					return( result );
1681 				}
1682 			}
1683 
1684 			private Number
getNumeric( DownloadManager dm, Object[] args, int index )1685 			getNumeric(
1686 				DownloadManager		dm,
1687 				Object[]			args,
1688 				int					index )
1689 			{
1690 				Object arg = args[index];
1691 
1692 				if ( arg instanceof Number ){
1693 
1694 					return((Number)arg);
1695 				}
1696 
1697 				String str = (String)arg;
1698 
1699 				Number result = 0;
1700 
1701 				try{
1702 					if ( Character.isDigit( str.charAt(0))){
1703 
1704 						if ( str.contains( "." )){
1705 
1706 							result = Float.parseFloat( str );
1707 
1708 						}else{
1709 
1710 							result = Long.parseLong( str );
1711 						}
1712 
1713 						return( result );
1714 					}else{
1715 
1716 						Integer kw = keyword_map.get( str.toLowerCase( Locale.US ));
1717 
1718 						if ( kw == null ){
1719 
1720 							Debug.out( "Invalid constraint keyword: " + str );
1721 
1722 							return( result );
1723 						}
1724 
1725 						switch( kw ){
1726 							case KW_SHARE_RATIO:{
1727 								result = null;	// don't cache this!
1728 
1729 								int sr = dm.getStats().getShareRatio();
1730 
1731 								if ( sr == -1 ){
1732 
1733 									return( Integer.MAX_VALUE );
1734 
1735 								}else{
1736 
1737 									return( new Float( sr/1000.0f ));
1738 								}
1739 							}
1740 							case KW_PERCENT:{
1741 
1742 								result = null;	// don't cache this!
1743 
1744 									// 0->1000
1745 
1746 								int percent = dm.getStats().getPercentDoneExcludingDND();
1747 
1748 								return( new Float( percent/10.0f ));
1749 							}
1750 							case KW_AGE:{
1751 
1752 								result = null;	// don't cache this!
1753 
1754 								long added = dm.getDownloadState().getLongParameter( DownloadManagerState.PARAM_DOWNLOAD_ADDED_TIME );
1755 
1756 								if ( added <= 0 ){
1757 
1758 									return( 0 );
1759 								}
1760 
1761 								return(( SystemTime.getCurrentTime() - added )/1000 );		// secs
1762 							}
1763 							case KW_DOWNLOADING_FOR:{
1764 
1765 								result = null;	// don't cache this!
1766 
1767 								return( dm.getStats().getSecondsDownloading());
1768 							}
1769 							case KW_SEEDING_FOR:{
1770 
1771 								result = null;	// don't cache this!
1772 
1773 								return( dm.getStats().getSecondsOnlySeeding());
1774 							}
1775 							case KW_LAST_ACTIVE:{
1776 
1777 								result = null;	// don't cache this!
1778 
1779 								DownloadManagerState dms = dm.getDownloadState();
1780 
1781 								long	timestamp = dms.getLongAttribute( DownloadManagerState.AT_LAST_ADDED_TO_ACTIVE_TAG );
1782 
1783 								if ( timestamp <= 0 ){
1784 
1785 									return( Long.MAX_VALUE );
1786 								}
1787 
1788 								return(( SystemTime.getCurrentTime() - timestamp )/1000 );
1789 							}
1790 							case KW_RESUME_IN:{
1791 
1792 								result = null;	// don't cache this!
1793 
1794 								long resume_millis = dm.getAutoResumeTime();
1795 
1796 								long	now = SystemTime.getCurrentTime();
1797 
1798 								if ( resume_millis <= 0 || resume_millis <= now ){
1799 
1800 									return( 0 );
1801 								}
1802 
1803 								return(( resume_millis - now )/1000 );
1804 							}
1805 
1806 							case KW_SWARM_MERGE:{
1807 
1808 								result = null;	// don't cache this!
1809 
1810 								return( dm.getDownloadState().getLongAttribute( DownloadManagerState.AT_MERGED_DATA ));
1811 							}
1812 							case KW_SEED_COUNT:{
1813 
1814 								result = null;	// don't cache this!
1815 
1816 								TRTrackerScraperResponse response = dm.getTrackerScrapeResponse();
1817 
1818 								int	seeds = dm.getNbSeeds();
1819 
1820 								if ( response != null && response.isValid()){
1821 
1822 									seeds = Math.max( seeds, response.getSeeds());
1823 								}
1824 
1825 								return( Math.max( 0, seeds ));
1826 							}
1827 							case KW_PEER_COUNT:{
1828 
1829 								result = null;	// don't cache this!
1830 
1831 								TRTrackerScraperResponse response = dm.getTrackerScrapeResponse();
1832 
1833 								int	peers = dm.getNbSeeds();
1834 
1835 								if ( response != null && response.isValid()){
1836 
1837 									peers = Math.max( peers, response.getPeers());
1838 								}
1839 
1840 								return( Math.max( 0, peers ));
1841 							}
1842 							case KW_SEED_PEER_RATIO:{
1843 
1844 								result = null;	// don't cache this!
1845 
1846 								TRTrackerScraperResponse response = dm.getTrackerScrapeResponse();
1847 
1848 								int	seeds = dm.getNbSeeds();
1849 								int	peers = dm.getNbSeeds();
1850 
1851 								if ( response != null && response.isValid()){
1852 
1853 									seeds = Math.max( seeds, response.getSeeds());
1854 									peers = Math.max( peers, response.getPeers());
1855 								}
1856 
1857 								float ratio;
1858 
1859 								if ( peers < 0 || seeds < 0 ){
1860 
1861 									ratio = 0;
1862 
1863 								}else{
1864 
1865 									if ( peers == 0 ){
1866 
1867 										if ( seeds == 0 ){
1868 
1869 											ratio = 0;
1870 
1871 										}else{
1872 
1873 											ratio = Float.MAX_VALUE;
1874 										}
1875 									}else{
1876 
1877 										ratio = (float)seeds/peers;
1878 									}
1879 								}
1880 
1881 								return( ratio );
1882 							}
1883 							default:{
1884 
1885 								Debug.out( "Invalid constraint keyword: " + str );
1886 
1887 								return( result );
1888 							}
1889 						}
1890 					}
1891 				}catch( Throwable e){
1892 
1893 					Debug.out( "Invalid constraint numeric: " + str );
1894 
1895 					return( result );
1896 
1897 				}finally{
1898 
1899 					if ( result != null ){
1900 
1901 							// cache literal results
1902 
1903 						args[index] = result;
1904 					}
1905 				}
1906 			}
1907 
1908 			public String
getString()1909 			getString()
1910 			{
1911 				return( func_name + "(" + params_expr.getString() + ")" );
1912 			}
1913 		}
1914 	}
1915 
1916 	public static void
main( String[] args )1917 	main(
1918 		String[]	args )
1919 	{
1920 		TagPropertyConstraintHandler handler = new TagPropertyConstraintHandler();
1921 
1922 		//System.out.println( handler.compileConstraint( "!(hasTag(\"bil\") && (hasTag( \"fred\" ))) || hasTag(\"toot\")" ).getString());
1923 		System.out.println( handler.compileConstraint( "isGE( shareratio, 1.5)" ).getString());
1924 	}
1925 }
1926