/* * Created on Mar 20, 2013 * Created by Paul Gardner * * Copyright 2013 Azureus Software, Inc. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 2 of the License only. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. */ package com.aelitis.azureus.core.tag.impl; import java.io.File; import java.util.ArrayList; import java.util.Set; import org.gudy.azureus2.core3.internat.MessageText; import org.gudy.azureus2.core3.util.Debug; import org.gudy.azureus2.core3.util.ListenerManager; import org.gudy.azureus2.core3.util.ListenerManagerDispatcher; import org.gudy.azureus2.core3.util.SimpleTimer; import com.aelitis.azureus.core.tag.Tag; import com.aelitis.azureus.core.tag.TagException; import com.aelitis.azureus.core.tag.TagFeatureFileLocation; import com.aelitis.azureus.core.tag.TagFeatureProperties; import com.aelitis.azureus.core.tag.TagFeatureRSSFeed; import com.aelitis.azureus.core.tag.TagFeatureRateLimit; import com.aelitis.azureus.core.tag.TagListener; import com.aelitis.azureus.core.tag.Taggable; import com.aelitis.azureus.core.tag.TagFeatureProperties.TagProperty; import com.aelitis.azureus.core.tag.TagFeatureProperties.TagPropertyListener; import com.aelitis.azureus.core.util.CopyOnWriteList; public abstract class TagBase implements Tag, SimpleTimer.TimerTickReceiver { protected static final String AT_RATELIMIT_UP = "rl.up"; protected static final String AT_RATELIMIT_DOWN = "rl.down"; protected static final String AT_VISIBLE = "vis"; protected static final String AT_PUBLIC = "pub"; protected static final String AT_CAN_BE_PUBLIC = "canpub"; protected static final String AT_ORIGINAL_NAME = "oname"; protected static final String AT_IMAGE_ID = "img.id"; protected static final String AT_COLOR_ID = "col.rgb"; protected static final String AT_RSS_ENABLE = "rss.enable"; protected static final String AT_RATELIMIT_UP_PRI = "rl.uppri"; protected static final String AT_XCODE_TARGET = "xcode.to"; protected static final String AT_FL_MOVE_COMP = "fl.comp"; protected static final String AT_FL_COPY_COMP = "fl.copy"; protected static final String AT_FL_INIT_LOC = "fl.init"; protected static final String AT_RATELIMIT_MIN_SR = "rl.minsr"; protected static final String AT_RATELIMIT_MAX_SR = "rl.maxsr"; protected static final String AT_PROPERTY_PREFX = "pp."; private static final String[] EMPTY_STRING_LIST = {}; private TagTypeBase tag_type; private int tag_id; private String tag_name; private static final int TL_ADD = 1; private static final int TL_REMOVE = 2; private static final int TL_SYNC = 3; private ListenerManager<TagListener> t_listeners = ListenerManager.createManager( "TagListeners", new ListenerManagerDispatcher<TagListener>() { public void dispatch( TagListener listener, int type, Object value ) { if ( type == TL_ADD ){ listener.taggableAdded(TagBase.this,(Taggable)value); }else if ( type == TL_REMOVE ){ listener.taggableRemoved(TagBase.this,(Taggable)value); }else if ( type == TL_SYNC ){ listener.taggableSync( TagBase.this ); } } }); private Boolean is_visible; private Boolean is_public; private TagFeatureRateLimit tag_rl; private TagFeatureRSSFeed tag_rss; private TagFeatureFileLocation tag_fl; protected TagBase( TagTypeBase _tag_type, int _tag_id, String _tag_name ) { tag_type = _tag_type; tag_id = _tag_id; tag_name = _tag_name; if ( getManager().isEnabled()){ is_visible = readBooleanAttribute( AT_VISIBLE, null ); is_public = readBooleanAttribute( AT_PUBLIC, null ); if ( this instanceof TagFeatureRateLimit ){ tag_rl = (TagFeatureRateLimit)this; } if ( this instanceof TagFeatureRSSFeed ){ tag_rss = (TagFeatureRSSFeed)this; if ( tag_rss.isTagRSSFeedEnabled()){ getManager().checkRSSFeeds( this, true ); } } if ( this instanceof TagFeatureFileLocation ){ tag_fl = (TagFeatureFileLocation)this; } } } protected void addTag() { if ( getManager().isEnabled()){ tag_type.addTag( this ); } } protected TagManagerImpl getManager() { return( tag_type.getTagManager()); } public TagTypeBase getTagType() { return( tag_type ); } public int getTagID() { return( tag_id ); } public long getTagUID() { return((((long)getTagType().getTagType())<<32) | tag_id ); } protected String getTagNameRaw() { return( tag_name ); } public String getTagName( boolean localize ) { if ( localize ){ if ( tag_name.startsWith( "tag." )){ return( MessageText.getString( tag_name )); }else{ return( tag_name ); } }else{ if ( tag_name.startsWith( "tag." )){ return( tag_name ); }else{ String original_name = readStringAttribute( AT_ORIGINAL_NAME, null ); if ( original_name != null && original_name.startsWith( "tag." )){ return( original_name ); } return( "!" + tag_name + "!" ); } } } public void setTagName( String name ) throws TagException { if ( getTagType().isTagTypeAuto()){ throw( new TagException( "Not supported" )); } if ( tag_name.startsWith( "tag." )){ String original_name = readStringAttribute( AT_ORIGINAL_NAME, null ); if ( original_name == null ){ writeStringAttribute( AT_ORIGINAL_NAME, tag_name ); } } tag_name = name; tag_type.fireChanged( this ); } // public public boolean isPublic() { boolean pub = is_public==null?getPublicDefault():is_public; if ( pub ){ if ( isTagAuto()){ pub = false; } } return( pub ); } public void setPublic( boolean v ) { if ( is_public == null || v != is_public ){ if ( v && !canBePublic()){ Debug.out( "Invalid attempt to set public" ); return; } is_public = v; writeBooleanAttribute( AT_PUBLIC, v ); tag_type.fireChanged( this ); } } protected boolean getPublicDefault() { if ( !getCanBePublicDefault()){ return( false ); } return( tag_type.getTagManager().getTagPublicDefault()); } public void setCanBePublic( boolean can_be_public ) { writeBooleanAttribute( AT_CAN_BE_PUBLIC, can_be_public ); if ( !can_be_public ){ if ( isPublic()){ setPublic( false ); } } } public boolean canBePublic() { return( readBooleanAttribute( AT_CAN_BE_PUBLIC, getCanBePublicDefault())); } protected boolean getCanBePublicDefault() { return( true ); } public boolean isTagAuto() { return( false ); } // visible public boolean isVisible() { return( is_visible==null?getVisibleDefault():is_visible ); } public void setVisible( boolean v ) { if ( is_visible == null || v != is_visible ){ is_visible = v; writeBooleanAttribute( AT_VISIBLE, v ); tag_type.fireChanged( this ); } } protected boolean getVisibleDefault() { return( true ); } public String getImageID() { return( readStringAttribute( AT_IMAGE_ID, null )); } public void setImageID( String id ) { writeStringAttribute( AT_IMAGE_ID, id ); } private int[] decodeRGB( String str ) { if ( str == null ){ return( null ); } String[] bits = str.split( "," ); if ( bits.length != 3 ){ return( null ); } int[] rgb = new int[3]; for ( int i=0;i<bits.length;i++){ try{ rgb[i] = Integer.parseInt(bits[i]); }catch( Throwable e ){ return( null ); } } return( rgb ); } private String encodeRGB( int[] rgb ) { if ( rgb == null || rgb.length != 3 ){ return( null ); } return( rgb[0]+","+rgb[1]+","+rgb[2] ); } public int[] getColor() { int[] result = decodeRGB( readStringAttribute( AT_COLOR_ID, null )); if ( result == null ){ result = tag_type.getColorDefault(); } return( result ); } public void setColor( int[] rgb ) { writeStringAttribute( AT_COLOR_ID, encodeRGB( rgb )); tag_type.fireChanged( this ); } public boolean isTagRSSFeedEnabled() { if ( tag_rss != null ){ return( readBooleanAttribute( AT_RSS_ENABLE, false )); } return( false ); } public void setTagRSSFeedEnabled( boolean enable ) { if ( tag_rss != null ){ if ( isTagRSSFeedEnabled() != enable ){ writeBooleanAttribute( AT_RSS_ENABLE, enable ); tag_type.fireChanged( this ); tag_type.getTagManager().checkRSSFeeds( this, enable ); } } } // initial save location public boolean supportsTagInitialSaveFolder() { return( false ); } public File getTagInitialSaveFolder() { if ( tag_fl != null ){ String str = readStringAttribute( AT_FL_INIT_LOC, null ); if ( str == null ){ return( null ); }else{ return( new File( str )); } } return( null ); } public void setTagInitialSaveFolder( File folder ) { if ( tag_fl != null ){ File existing = getTagInitialSaveFolder(); if ( existing == null && folder == null ){ return; }else if ( existing == null || folder == null || !existing.equals( folder )){ writeStringAttribute( AT_FL_INIT_LOC, folder==null?null:folder.getAbsolutePath()); tag_type.fireChanged( this ); } } } // move on complete public boolean supportsTagMoveOnComplete() { return( false ); } public File getTagMoveOnCompleteFolder() { if ( tag_fl != null ){ String str = readStringAttribute( AT_FL_MOVE_COMP, null ); if ( str == null ){ return( null ); }else{ return( new File( str )); } } return( null ); } public void setTagMoveOnCompleteFolder( File folder ) { if ( tag_fl != null ){ File existing = getTagMoveOnCompleteFolder(); if ( existing == null && folder == null ){ return; }else if ( existing == null || folder == null || !existing.equals( folder )){ writeStringAttribute( AT_FL_MOVE_COMP, folder==null?null:folder.getAbsolutePath()); tag_type.fireChanged( this ); } } } // copy on complete public boolean supportsTagCopyOnComplete() { return( false ); } public File getTagCopyOnCompleteFolder() { if ( tag_fl != null ){ String str = readStringAttribute( AT_FL_COPY_COMP, null ); if ( str == null ){ return( null ); }else{ return( new File( str )); } } return( null ); } public void setTagCopyOnCompleteFolder( File folder ) { if ( tag_fl != null ){ File existing = getTagCopyOnCompleteFolder(); if ( existing == null && folder == null ){ return; }else if ( existing == null || folder == null || !existing.equals( folder )){ writeStringAttribute( AT_FL_COPY_COMP, folder==null?null:folder.getAbsolutePath()); tag_type.fireChanged( this ); } } } // min ratio public int getTagMinShareRatio() { return( -1 ); } public void setTagMinShareRatio( int sr ) { Debug.out( "not supported" ); } // max ratio public int getTagMaxShareRatio() { return( -1 ); } public void setTagMaxShareRatio( int sr ) { Debug.out( "not supported" ); } public TagProperty[] getSupportedProperties() { return( new TagProperty[0] ); } public TagProperty getProperty( String name ) { TagProperty[] props = getSupportedProperties(); for ( TagProperty prop: props ){ if ( prop.getName( false ) == name ){ return( prop ); } } return( null ); } protected TagProperty createTagProperty( String name, int type ) { return( new TagPropertyImpl( name, type )); } public void addTaggable( Taggable t ) { t_listeners.dispatch( TL_ADD, t ); tag_type.taggableAdded( this, t ); tag_type.fireChanged( this ); } public void removeTaggable( Taggable t ) { t_listeners.dispatch( TL_REMOVE, t ); tag_type.taggableRemoved( this, t ); tag_type.fireChanged( this ); } protected void sync() { t_listeners.dispatch( TL_SYNC, null ); tag_type.taggableSync( this ); } public void removeTag() { boolean was_rss = isTagRSSFeedEnabled(); tag_type.removeTag( this ); if ( was_rss ){ tag_type.getTagManager().checkRSSFeeds( this, false ); } } public void addTagListener( TagListener listener, boolean fire_for_existing ) { t_listeners.addListener( listener ); if ( fire_for_existing ){ for ( Taggable t: getTagged()){ listener.taggableAdded( this, t ); } } } protected void destroy() { Set<Taggable> taggables = getTagged(); for( Taggable t: taggables ){ t_listeners.dispatch( TL_REMOVE, t ); tag_type.taggableRemoved( this, t ); } } public void removeTagListener( TagListener listener ) { t_listeners.removeListener( listener ); } protected Boolean readBooleanAttribute( String attr, Boolean def ) { return( tag_type.readBooleanAttribute( this, attr, def )); } protected boolean writeBooleanAttribute( String attr, boolean value ) { return( tag_type.writeBooleanAttribute( this, attr, value )); } protected long readLongAttribute( String attr, long def ) { return( tag_type.readLongAttribute( this, attr, def )); } protected void writeLongAttribute( String attr, long value ) { tag_type.writeLongAttribute( this, attr, value ); } protected String readStringAttribute( String attr, String def ) { return( tag_type.readStringAttribute( this, attr, def )); } protected void writeStringAttribute( String attr, String value ) { tag_type.writeStringAttribute( this, attr, value ); } protected String[] readStringListAttribute( String attr, String[] def ) { return( tag_type.readStringListAttribute( this, attr, def )); } protected boolean writeStringListAttribute( String attr, String[] value ) { return( tag_type.writeStringListAttribute( this, attr, value )); } private static final int HISTORY_MAX_SECS = 30*60; private volatile boolean history_retention_required; private long[] history; private int history_pos; private boolean history_wrapped; private boolean timer_registered; public void setRecentHistoryRetention( boolean required ) { if ( tag_rl == null || !tag_rl.supportsTagRates()){ return; } synchronized( this ){ if ( required ){ if ( !history_retention_required ){ history = new long[HISTORY_MAX_SECS]; history_pos = 0; history_retention_required = true; if ( !timer_registered ){ SimpleTimer.addTickReceiver( this ); timer_registered = true; } } }else{ history = null; history_retention_required = false; if ( timer_registered ){ SimpleTimer.removeTickReceiver( this ); timer_registered = false; } } } } public int[][] getRecentHistory() { synchronized( this ){ if ( history == null ){ return( new int[2][0] ); }else{ int entries = history_wrapped?HISTORY_MAX_SECS:history_pos; int start = history_wrapped?history_pos:0; int[][] result = new int[2][entries]; int pos = start; for ( int i=0;i<entries;i++){ if ( pos == HISTORY_MAX_SECS ){ pos = 0; } long entry = history[pos++]; int send_rate = (int)((entry>>32)&0xffffffffL); int recv_rate = (int)((entry) &0xffffffffL); result[0][i] = send_rate; result[1][i] = recv_rate; } return( result ); } } } public void tick( long mono_now, int count ) { if ( !history_retention_required ){ return; } long send_rate = tag_rl.getTagCurrentUploadRate(); long receive_rate = tag_rl.getTagCurrentDownloadRate(); long entry = (((send_rate)<<32) & 0xffffffff00000000L ) | (((receive_rate) & 0x00000000ffffffffL )); synchronized( this ){ if ( history != null ){ history[history_pos++] = entry; if ( history_pos == HISTORY_MAX_SECS ){ history_pos = 0; history_wrapped = true; } } } } private class TagPropertyImpl implements TagProperty { private String name; private int type; private CopyOnWriteList<TagPropertyListener> listeners = new CopyOnWriteList<TagPropertyListener>(); private TagPropertyImpl( String _name, int _type ) { name = _name; type = _type; } public Tag getTag() { return( TagBase.this ); } public int getType() { return( type ); } public String getName( boolean localize ) { if ( localize ){ return( MessageText.getString( "tag.property." + name )); }else{ return( name ); } } public void setStringList( String[] value ) { if ( writeStringListAttribute( AT_PROPERTY_PREFX + name, value )){ for ( TagPropertyListener l: listeners ){ try{ l.propertyChanged( this ); }catch( Throwable e ){ Debug.out( e ); } } tag_type.fireChanged( TagBase.this ); } } public String[] getStringList() { return( readStringListAttribute( AT_PROPERTY_PREFX + name, EMPTY_STRING_LIST )); } public void setBoolean( boolean value ) { if ( writeBooleanAttribute( AT_PROPERTY_PREFX + name, value )){ for ( TagPropertyListener l: listeners ){ try{ l.propertyChanged( this ); }catch( Throwable e ){ Debug.out( e ); } } tag_type.fireChanged( TagBase.this ); } } public Boolean getBoolean() { return( readBooleanAttribute( AT_PROPERTY_PREFX + name, null )); } public String getString() { String value = null; switch( getType()){ case TagFeatureProperties.PT_STRING_LIST:{ String[] vals = getStringList(); if ( vals != null && vals.length > 0 ){ value = ""; if ( getName( false ).equals( TagFeatureProperties.PR_TRACKER_TEMPLATES )){ String str_merge = MessageText.getString("label.merge" ); String str_replace = MessageText.getString("label.replace" ); String str_remove = MessageText.getString("Button.remove" ); for ( String val: vals ){ String[] bits = val.split( ":" ); String type = bits[0]; String str = bits[1]; if ( type.equals("m")){ str += ": " + str_merge; }else if ( type.equals( "r" )){ str += ": " + str_replace; }else{ str += ": " + str_remove; } value += (value.length()==0?"":"," ) + str; } }else{ for ( String val: vals ){ value += (value.length()==0?"":"," ) + val; } } } break; } case TagFeatureProperties.PT_BOOLEAN:{ Boolean val = getBoolean(); if ( val != null ){ value = String.valueOf( val ); } break; } default:{ value = "Unknown type"; } } if ( value == null ){ return( "" ); }else{ return( getName( true ) + "=" + value ); } } public void addListener( TagPropertyListener listener ) { listeners.add( listener ); } public void removeListener( TagPropertyListener listener ) { listeners.remove( listener ); } public void syncListeners() { for ( TagPropertyListener l: listeners ){ try{ l.propertySync( this ); }catch( Throwable e ){ Debug.out( e ); } } } } }