/*
* Created on Jan 28, 2009
* Created by Paul Gardner
*
* Copyright 2009 Vuze, 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.devices.impl;
import java.io.File;
import java.io.IOException;
import java.lang.ref.WeakReference;
import java.lang.reflect.Constructor;
import java.net.URL;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.gudy.azureus2.core3.internat.MessageText;
import org.gudy.azureus2.core3.util.*;
import org.gudy.azureus2.plugins.disk.DiskManagerFileInfo;
import com.aelitis.azureus.core.devices.Device;
import com.aelitis.azureus.core.devices.DeviceListener;
import com.aelitis.azureus.core.devices.DeviceMediaRenderer;
import com.aelitis.azureus.core.devices.TranscodeException;
import com.aelitis.azureus.core.devices.TranscodeFile;
import com.aelitis.azureus.core.devices.TranscodeProfile;
import com.aelitis.azureus.core.devices.TranscodeProvider;
import com.aelitis.azureus.core.devices.TranscodeTarget;
import com.aelitis.azureus.core.devices.TranscodeTargetListener;
import com.aelitis.azureus.core.util.CopyOnWriteList;
import com.aelitis.azureus.core.vuzefile.VuzeFile;
import com.aelitis.azureus.util.ImportExportUtils;
import com.aelitis.azureus.util.StringCompareUtils;
public abstract class
DeviceImpl
implements Device
{
private static final String MY_PACKAGE = "com.aelitis.azureus.core.devices.impl";
private static final TranscodeProfile blank_profile =
new TranscodeProfile()
{
public String
getUID()
{
return( null );
}
public String
getName()
{
return( "blank" );
}
public String
getDescription()
{
return( "blank" );
}
public boolean
isStreamable()
{
return( false );
}
public String
getIconURL()
{
return( null );
}
public int
getIconIndex()
{
return( 0 );
}
public String
getFileExtension()
{
return( null );
}
public String
getDeviceClassification()
{
return( "blank" );
}
public TranscodeProvider
getProvider()
{
return( null );
}
};
protected static DeviceImpl
importFromBEncodedMapStatic(
DeviceManagerImpl manager,
Map map )
throws IOException
{
String impl = ImportExportUtils.importString( map, "_impl" );
if ( impl.startsWith( "." )){
impl = MY_PACKAGE + impl;
}
try{
Class<DeviceImpl> cla = (Class<DeviceImpl>) Class.forName( impl );
Constructor<DeviceImpl> cons = cla.getDeclaredConstructor( DeviceManagerImpl.class, Map.class );
cons.setAccessible( true );
return( cons.newInstance( manager, map ));
}catch( Throwable e ){
Debug.out( "Can't construct device for " + impl, e );
throw( new IOException( "Construction failed: " + Debug.getNestedExceptionMessage(e)));
}
}
private static List<Pattern> device_renames = new ArrayList<Pattern>();
static{
try{
device_renames.add( Pattern.compile( "TV\\s*+\\(([^\\)]*)\\)", Pattern.CASE_INSENSITIVE ));
}catch( Throwable e ){
Debug.out( e );
}
}
private static String
modifyDeviceDisplayName(
String name )
{
for ( Pattern p: device_renames ){
Matcher m = p.matcher( name );
if ( m.find()){
String new_name = m.group(1);
return( new_name );
}
}
if ( name.startsWith( "WDTVLIVE")){
name = "WD TV Live";
}
return( name );
}
private static final String PP_REND_WORK_DIR = "tt_work_dir";
private static final String PP_REND_DEF_TRANS_PROF = "tt_def_trans_prof";
private static final String PP_REND_TRANS_REQ = "tt_req";
private static final String PP_REND_TRANS_CACHE = "tt_always_cache";
private static final String PP_REND_RSS_PUB = "tt_rss_pub";
protected static final String PP_REND_SHOW_CAT = "tt_show_cat";
protected static final String PP_REND_CLASSIFICATION = "tt_rend_class";
protected static final String PP_IP_ADDRESS = "rend_ip";
protected static final String PP_DONT_AUTO_HIDE = "rend_no_ah";
protected static final String TP_IP_ADDRESS = "DeviceUPnPImpl:ip"; // transient
protected static final String PP_FILTER_FILES = "rend_filter";
protected static final String PP_RESTRICT_ACCESS = "restrict_access";
protected static final String PP_COPY_OUTSTANDING = "copy_outstanding";
protected static final String PP_AUTO_START = "auto_start";
protected static final String PP_COPY_TO_FOLDER = "copy_to_folder";
protected static final String PP_AUTO_COPY = "auto_copy";
protected static final String PP_EXPORTABLE = "exportable";
protected static final String PP_LIVENESS_DETECTABLE = "live_det";
protected static final String PP_TIVO_MACHINE = "tivo_machine";
protected static final String PP_OD_ENABLED = "od_enabled";
protected static final String PP_OD_SHOWN_FTUX = "od_shown_ftux";
protected static final String PP_OD_MANUFACTURER = "od_manufacturer";
protected static final String PP_OD_STATE_CACHE = "od_state_cache";
protected static final String PP_OD_XFER_CACHE = "od_xfer_cache";
protected static final String PP_OD_UPNP_DISC_CACHE = "od_upnp_cache";
protected static final boolean PR_AUTO_START_DEFAULT = true;
protected static final boolean PP_AUTO_COPY_DEFAULT = false;
private static final String GENERIC = "generic";
private static final Object KEY_FILE_ALLOC_ERROR = new Object();
private DeviceManagerImpl manager;
private int type;
private String uid;
private String secondary_uid;
private String classification;
private String name;
private boolean manual;
private boolean hidden;
private boolean auto_hidden;
private boolean isGenericUSB;
private long last_seen;
private boolean can_remove = true;
private boolean tagged;
private int busy_count;
private boolean online;
private boolean transcoding;
private Map<String,Object> persistent_properties = new LightHashMap<String, Object>(1);
private Map<Object,Object> transient_properties = new LightHashMap<Object, Object>(1);
private long device_files_last_mod;
private boolean device_files_dirty;
private Map<String,Map<String,?>> device_files;
private WeakReference<Map<String,Map<String,?>>> device_files_ref;
private CopyOnWriteList<TranscodeTargetListener> listeners = new CopyOnWriteList<TranscodeTargetListener>();
private Map<Object,String> errors = new HashMap<Object, String>();
private Map<Object,String> infos = new HashMap<Object, String>();
private CopyOnWriteList<DeviceListener> device_listeners;
private String image_id;
private boolean isNameAutomatic;
protected
DeviceImpl(
DeviceManagerImpl _manager,
int _type,
String _uid,
String _classification,
boolean _manual )
{
this( _manager, _type, _uid, _classification, _manual, _classification );
}
protected
DeviceImpl(
DeviceManagerImpl _manager,
int _type,
String _uid,
String _classification,
boolean _manual,
String _name )
{
manager = _manager;
type = _type;
uid = _uid;
classification = _classification;
name = modifyDeviceDisplayName( _name );
manual = _manual;
isNameAutomatic = true;
}
protected
DeviceImpl(
DeviceManagerImpl _manager,
Map map )
throws IOException
{
manager = _manager;
type = (int)ImportExportUtils.importLong( map, "_type" );
uid = ImportExportUtils.importString( map, "_uid" );
classification = ImportExportUtils.importString( map, "_name" );
name = ImportExportUtils.importString( map, "_lname" );
isNameAutomatic = ImportExportUtils.importBoolean( map, "_autoname", true );
image_id = ImportExportUtils.importString( map, "_image_id" );
if ( name == null ){
name = classification;
}
if ( ImportExportUtils.importLong( map, "_rn", 0 ) == 0 ){
name = modifyDeviceDisplayName( name );
}
secondary_uid = ImportExportUtils.importString( map, "_suid" );
last_seen = ImportExportUtils.importLong( map, "_ls" );
hidden = ImportExportUtils.importBoolean( map, "_hide" );
auto_hidden = ImportExportUtils.importBoolean( map, "_ahide" );
can_remove = ImportExportUtils.importBoolean( map, "_rm", true );
isGenericUSB = ImportExportUtils.importBoolean( map, "_genericUSB" );
manual = ImportExportUtils.importBoolean( map, "_man" );
tagged = ImportExportUtils.importBoolean( map, "_tag", false );
if ( map.containsKey( "_pprops" )){
persistent_properties = (Map<String,Object>)map.get( "_pprops" );
}
}
protected void
exportToBEncodedMap(
Map map,
boolean for_export )
throws IOException
{
String cla = this.getClass().getName();
if ( cla.startsWith( MY_PACKAGE )){
cla = cla.substring( MY_PACKAGE.length());
}
ImportExportUtils.exportString( map, "_impl", cla );
ImportExportUtils.exportLong( map, "_type", type );
ImportExportUtils.exportString( map, "_uid", uid );
ImportExportUtils.exportString( map, "_name", classification );
ImportExportUtils.exportBoolean( map, "_autoname", isNameAutomatic );
ImportExportUtils.exportLong( map, "_rn", 1 );
ImportExportUtils.exportString( map, "_lname", name );
ImportExportUtils.exportString( map, "_image_id", image_id );
if ( secondary_uid != null ){
ImportExportUtils.exportString( map, "_suid", secondary_uid );
}
if ( !for_export ){
ImportExportUtils.exportLong( map, "_ls", last_seen );
ImportExportUtils.exportBoolean( map, "_hide", hidden );
ImportExportUtils.exportBoolean( map, "_ahide", auto_hidden );
}
ImportExportUtils.exportBoolean( map, "_rm", can_remove );
ImportExportUtils.exportBoolean( map, "_genericUSB", isGenericUSB );
ImportExportUtils.exportBoolean( map, "_man", manual );
if ( tagged ){
ImportExportUtils.exportBoolean( map, "_tag", tagged );
}
Map<String,Object> pp_copy;
synchronized( persistent_properties ){
pp_copy = new HashMap<String, Object>( persistent_properties );
}
if ( for_export ){
pp_copy.remove( PP_IP_ADDRESS );
pp_copy.remove( PP_COPY_OUTSTANDING );
pp_copy.remove( PP_COPY_TO_FOLDER );
pp_copy.remove( PP_REND_WORK_DIR );
map.put( "_pprops", pp_copy );
}else{
map.put( "_pprops", pp_copy );
}
}
protected boolean
updateFrom(
DeviceImpl other,
boolean is_alive )
{
if ( type != other.type ){
Debug.out( "Inconsistent update operation (type)" );
return( false );
}
String o_uid = other.uid;
if ( !uid.equals( o_uid )){
String o_suid = other.secondary_uid;
boolean borked = false;
if ( secondary_uid == null && o_suid == null ){
borked = true;
}else if ( ( secondary_uid == null && uid.equals( o_suid )) ||
( o_suid == null && o_uid.equals( secondary_uid )) ||
( o_suid != null && o_suid.equals( secondary_uid ))){
}else{
borked = true;
}
if ( borked ){
Debug.out( "Inconsistent update operation (uids)" );
return( false );
}
}
if ( !classification.equals( other.classification )){
classification = other.classification;
setDirty();
}
/* don't overwite the name as user may have altered it!
if ( !name.equals( other.name )){
name = other.name;
setDirty();
}
*/
if ( manual != other.manual ){
manual = other.manual;
setDirty();
}
if ( is_alive ){
alive();
}
return( true );
}
public void
setExportable(
boolean b )
{
setPersistentBooleanProperty( PP_EXPORTABLE, b );
}
public boolean
isExportable()
{
return( getPersistentBooleanProperty( PP_EXPORTABLE, false ));
}
public VuzeFile
getVuzeFile()
throws IOException
{
return( manager.exportVuzeFile( this ));
}
protected void
initialise()
{
updateStatus( 0 );
}
protected void
destroy()
{
}
public int
getType()
{
return( type );
}
public String
getID()
{
return( uid );
}
protected void
setSecondaryID(
String str )
{
secondary_uid = str;
}
protected String
getSecondaryID()
{
return( secondary_uid );
}
public String getImageID() {
return image_id;
}
public void setImageID(String id) {
if (!StringCompareUtils.equals(id, image_id)) {
image_id = id;
setDirty();
}
}
public Device
getDevice()
{
return( this );
}
public String
getName()
{
return( name );
}
public void
setName(
String _name,
boolean isAutomaticName )
{
if ( !name.equals( _name ) || isNameAutomatic != isAutomaticName ){
name = _name;
isNameAutomatic = isAutomaticName;
setDirty();
}
}
public boolean
isNameAutomatic()
{
return isNameAutomatic;
}
public String
getClassification()
{
String explicit_classification = getPersistentStringProperty( PP_REND_CLASSIFICATION, null );
if ( explicit_classification != null ){
return( explicit_classification );
}
return( classification );
}
public String
getShortDescription()
{
if ( getRendererSpecies() == DeviceMediaRenderer.RS_ITUNES ){
return( "iPad, iPhone, iPod, Apple TV" );
}
return( null );
}
public int
getRendererSpecies()
{
// note, overridden in itunes
if ( classification.equalsIgnoreCase( "PS3" )){
return( DeviceMediaRenderer.RS_PS3 );
}else if ( classification.equalsIgnoreCase( "XBox 360" )){
return( DeviceMediaRenderer.RS_XBOX );
}else if ( classification.equalsIgnoreCase( "Wii" )){
return( DeviceMediaRenderer.RS_WII );
}else if ( classification.equalsIgnoreCase( "Browser" )){
return( DeviceMediaRenderer.RS_BROWSER );
}else{
return( DeviceMediaRenderer.RS_OTHER );
}
}
protected String
getDeviceClassification()
{
// note, overridden in itunes
// bit of a mess here. First release used name as classification and mapped to
// species + device-classification here.
// second release moved to separate name and classification and used the correct
// device-classification as the classification
// so we deal with both for the moment...
// 'generic' means one we don't explicitly support, which are rendereres discovered
// by UPnP
switch( getRendererSpecies()){
case DeviceMediaRenderer.RS_PS3:{
return( "sony.PS3" );
}
case DeviceMediaRenderer.RS_XBOX:{
return( "microsoft.XBox" );
}
case DeviceMediaRenderer.RS_WII:{
return( "nintendo.Wii" );
}
case DeviceMediaRenderer.RS_BROWSER:{
return( "browser.generic" );
}
case DeviceMediaRenderer.RS_OTHER:{
if ( isManual()){
return( classification );
}
if ( classification.equals( "sony.PSP" ) ||
classification.startsWith( "tivo." )){
return( classification );
}
String str = getPersistentStringProperty( PP_REND_CLASSIFICATION, null );
if ( str != null ){
return( str );
}
return( GENERIC );
}
default:{
Debug.out( "Unknown classification" );
return( GENERIC );
}
}
}
public boolean
isNonSimple()
{
// apparently wmp isn't ready for the right chasm
return( getClassification().startsWith( "ms_wmp." ) || isGenericUSB());
}
public boolean
isManual()
{
return( manual );
}
public boolean
isHidden()
{
return( hidden );
}
public void
setHidden(
boolean h )
{
if ( h != hidden ){
hidden = h;
setDirty();
}
if ( auto_hidden ){
auto_hidden = false;
setDirty();
}
}
public boolean
isAutoHidden()
{
return( auto_hidden );
}
public void
setAutoHidden(
boolean h )
{
if ( h != auto_hidden ){
auto_hidden = h;
setDirty();
}
}
public boolean
isTagged()
{
return( tagged );
}
public void
setTagged(
boolean t )
{
if ( t != tagged ){
tagged = t;
setDirty();
}
}
public boolean
isGenericUSB()
{
return( isGenericUSB );
}
public void
setGenericUSB(
boolean is )
{
if ( is != isGenericUSB ){
isGenericUSB = is;
setDirty();
}
}
public long
getLastSeen() {
return last_seen;
}
public void
alive()
{
last_seen = SystemTime.getCurrentTime();
if ( !online ){
online = true;
setDirty( false );
}
}
public boolean
isLivenessDetectable()
{
return( !manual );
}
public boolean
isAlive()
{
return( online );
}
protected void
dead()
{
if ( online ){
online = false;
setDirty( false );
}
}
public URL
getWikiURL()
{
return( null );
}
protected void
setDirty()
{
setDirty( true );
}
protected void
setDirty(
boolean save_changes )
{
manager.configDirty( this, save_changes );
}
protected void
updateStatus(
int tick_count )
{
}
public void
requestAttention()
{
manager.requestAttention( this );
}
public int
getFileCount()
{
try{
synchronized( this ){
if ( device_files == null ){
loadDeviceFile();
}
return device_files.size();
}
}catch( Throwable e ){
Debug.out( "Failed to load device file", e );
}
return 0;
}
public TranscodeFileImpl[]
getFiles()
{
try{
synchronized( this ){
if ( device_files == null ){
loadDeviceFile();
}
List<TranscodeFile> result = new ArrayList<TranscodeFile>();
Iterator<Map.Entry<String,Map<String,?>>> it = device_files.entrySet().iterator();
while( it.hasNext()){
Map.Entry<String,Map<String,?>> entry = it.next();
try{
TranscodeFileImpl tf = new TranscodeFileImpl( this, entry.getKey(), device_files );
result.add( tf );
}catch( Throwable e ){
it.remove();
log( "Failed to deserialise transcode file", e );
}
}
return( result.toArray( new TranscodeFileImpl[ result.size() ]));
}
}catch( Throwable e ){
Debug.out( e );
return( new TranscodeFileImpl[0] );
}
}
public TranscodeFileImpl
allocateFile(
TranscodeProfile profile,
boolean no_xcode,
DiskManagerFileInfo file,
boolean for_job )
throws TranscodeException
{
TranscodeFileImpl result = null;
setError( KEY_FILE_ALLOC_ERROR, null );
try{
synchronized( this ){
if ( device_files == null ){
loadDeviceFile();
}
String key = ByteFormatter.encodeString( file.getDownloadHash() ) + ":" + file.getIndex() + ":" + profile.getUID();
if ( device_files.containsKey( key )){
try{
result = new TranscodeFileImpl( this, key, device_files );
}catch( Throwable e ){
device_files.remove( key );
log( "Failed to deserialise transcode file", e );
}
}
if ( result == null ){
String ext = profile.getFileExtension();
String target_file = file.getFile( true ).getName();
if ( ext != null && !no_xcode ){
int pos = target_file.lastIndexOf( '.' );
if ( pos != -1 ){
target_file = target_file.substring( 0, pos );
}
target_file += ext;
}
target_file = allocateUniqueFileName( target_file );
File output_file = getWorkingDirectory( true );
if ( !output_file.canWrite()){
throw( new TranscodeException( "Can't write to transcode folder '" + output_file.getAbsolutePath() + "'" ));
}
output_file = new File( output_file.getAbsoluteFile(), target_file );
result = new TranscodeFileImpl( this, key, profile.getName(), device_files, output_file, for_job );
result.setSourceFile( file );
device_files_last_mod = SystemTime.getMonotonousTime();
device_files_dirty = true;
}else{
result.setSourceFile( file );
result.setProfileName( profile.getName());
}
}
}catch( Throwable e ){
setError( KEY_FILE_ALLOC_ERROR, Debug.getNestedExceptionMessage( e ));
throw( new TranscodeException( "File allocation failed", e ));
}
for ( TranscodeTargetListener l: listeners ){
try{
l.fileAdded( result );
}catch( Throwable e ){
Debug.out( e );
}
}
return( result );
}
protected String
allocateUniqueFileName(
String str )
{
Set<String> name_set = new HashSet<String>();
for (Map<String,?> entry: device_files.values()){
try{
name_set.add( new File( ImportExportUtils.importString( entry, TranscodeFileImpl.KEY_FILE )).getName());
}catch( Throwable e ){
Debug.out( e );
}
}
for (int i=0;i<1024;i++){
String test_name = i==0?str:( i + "_" + str);
if ( !name_set.contains( test_name )){
str = test_name;
break;
}
}
return( str );
}
protected void
revertFileName(
TranscodeFileImpl tf )
throws TranscodeException
{
File cache_file = tf.getCacheFile();
if ( cache_file.exists()){
Debug.out( "Cache file already allocated, can't rename" );
return;
}
File source_file = tf.getSourceFile().getFile( true );
String original_name = source_file.getName();
int pos = original_name.indexOf('.');
if ( pos == -1 ){
return;
}
String cf_name = cache_file.getName();
if ( cf_name.endsWith( original_name.substring(pos))){
return;
}
try{
synchronized( this ){
if ( device_files == null ){
loadDeviceFile();
}
String reverted_name = allocateUniqueFileName( original_name );
tf.setCacheFile( new File( cache_file.getParentFile(), reverted_name ));
}
}catch( Throwable e ){
throw( new TranscodeException( "File name revertion failed", e ));
}
}
public TranscodeFileImpl
lookupFile(
TranscodeProfile profile,
DiskManagerFileInfo file )
{
try{
synchronized( this ){
if ( device_files == null ){
loadDeviceFile();
}
String key = ByteFormatter.encodeString( file.getDownloadHash() ) + ":" + file.getIndex() + ":" + profile.getUID();
if ( device_files.containsKey( key )){
try{
return( new TranscodeFileImpl( this, key, device_files ));
}catch( Throwable e ){
device_files.remove( key );
log( "Failed to deserialise transcode file", e );
}
}
}
}catch( Throwable e ){
}
return( null );
}
protected TranscodeFileImpl
getTranscodeFile(
String key )
{
try{
synchronized( this ){
if ( device_files == null ){
loadDeviceFile();
}
if ( device_files.containsKey( key )){
try{
return( new TranscodeFileImpl( this, key, device_files ));
} catch( Throwable e ){
device_files.remove( key );
log( "Failed to deserialise transcode file", e );
}
}
}
}catch( Throwable e ){
}
return( null );
}
public File
getWorkingDirectory()
{
return( getWorkingDirectory( false ));
}
public File
getWorkingDirectory(
boolean persist )
{
String result = getPersistentStringProperty( PP_REND_WORK_DIR );
if ( result.length() == 0 ){
File f = manager.getDefaultWorkingDirectory( persist );
if ( persist ){
f.mkdirs();
}
String name = FileUtil.convertOSSpecificChars( getName(), true );
for (int i=0;i<1024;i++){
String test_name = name + (i==0?"":("_"+i));
File test_file = new File( f, test_name );
if ( !test_file.exists()){
f = test_file;
break;
}
}
result = f.getAbsolutePath();
if ( persist ){
setPersistentStringProperty( PP_REND_WORK_DIR, result );
}
}
File f_result = new File( result );
if ( !f_result.exists()){
if ( persist ){
f_result.mkdirs();
}
}
return( f_result );
}
public void
setWorkingDirectory(
File directory )
{
setPersistentStringProperty( PP_REND_WORK_DIR, directory.getAbsolutePath());
}
protected void
resetWorkingDirectory()
{
setPersistentStringProperty( PP_REND_WORK_DIR, "" );
}
public TranscodeProfile[]
getTranscodeProfiles()
{
return getTranscodeProfiles(true);
}
public TranscodeProfile[]
getDirectTranscodeProfiles()
{
return getTranscodeProfiles(false);
}
public TranscodeProfile[]
getTranscodeProfiles(boolean walkup)
{
String classification = getDeviceClassification();
TranscodeProfile[] result = getTranscodeProfiles( classification );
if ( !walkup || result.length > 0 ){
return( result );
}
try{
String[] bits = Constants.PAT_SPLIT_DOT.split(classification);
// I would like to drill all the way up to just 'generic' but unfortunately this
// would break the current samsung/ms_wmp support that requires the detected profile
// set to be empty (we have two existing profiles at the 'generic' level)
for ( int i=bits.length-1;i>=1;i--){
String c = "";
for (int j=0;j<i;j++){
c = c + (c.length()==0?"":".") + bits[j];
}
c = c + (c.length()==0?"":".") + "generic";
result = getTranscodeProfiles( c );
if ( result.length > 0 ){
return( result );
}
}
}catch( Throwable e ){
Debug.out( e );
}
return( new TranscodeProfile[0] );
}
private TranscodeProfile[]
getTranscodeProfiles(
String classification )
{
List<TranscodeProfile> profiles = new ArrayList<TranscodeProfile>();
DeviceManagerImpl dm = getManager();
TranscodeProvider[] providers = dm.getProviders();
for ( TranscodeProvider provider: providers ){
TranscodeProfile[] ps = provider.getProfiles( classification );
if ( providers.length == 1 ){
return( ps );
}
profiles.addAll( Arrays.asList( ps ));
}
return( profiles.toArray( new TranscodeProfile[profiles.size()] ));
}
public TranscodeProfile
getDefaultTranscodeProfile()
{
String uid = getPersistentStringProperty( PP_REND_DEF_TRANS_PROF );
DeviceManagerImpl dm = getManager();
TranscodeManagerImpl tm = dm.getTranscodeManager();
TranscodeProfile profile = tm.getProfileFromUID( uid );
if ( profile != null ){
return( profile );
}
return( null );
}
public void
setDefaultTranscodeProfile(
TranscodeProfile profile )
{
if ( profile == null ){
removePersistentProperty( PP_REND_DEF_TRANS_PROF );
}else{
setPersistentStringProperty( PP_REND_DEF_TRANS_PROF, profile.getUID());
}
}
public TranscodeProfile
getBlankProfile()
{
return( blank_profile );
}
protected void
setTranscoding(
boolean _transcoding )
{
transcoding = _transcoding;
manager.deviceChanged( this, false );
}
public boolean
isTranscoding()
{
return( transcoding );
}
public int
getTranscodeRequirement()
{
return( getPersistentIntProperty( PP_REND_TRANS_REQ, TranscodeTarget.TRANSCODE_WHEN_REQUIRED ));
}
public void
setTranscodeRequirement(
int req )
{
setPersistentIntProperty( PP_REND_TRANS_REQ, req );
}
public boolean
isAudioCompatible(
TranscodeFile file )
{
return( false );
}
public boolean
getAlwaysCacheFiles()
{
return( getPersistentBooleanProperty( PP_REND_TRANS_CACHE, false ));
}
public void
setAlwaysCacheFiles(
boolean always_cache )
{
setPersistentBooleanProperty( PP_REND_TRANS_CACHE, always_cache );
}
public boolean
isRSSPublishEnabled()
{
return( getPersistentBooleanProperty( PP_REND_RSS_PUB, true ));
}
public void
setRSSPublishEnabled(
boolean enabled )
{
setPersistentBooleanProperty( PP_REND_RSS_PUB, enabled );
}
public String[][]
getDisplayProperties()
{
List<String[]> dp = new ArrayList<String[]>();
getDisplayProperties( dp );
String[][] res = new String[2][dp.size()];
int pos = 0;
for ( String[] entry: dp ){
res[0][pos] = entry[0];
res[1][pos] = entry[1];
pos++;
}
return( res );
}
protected void
getDisplayProperties(
List<String[]> dp )
{
if ( !name.equals( classification )){
addDP( dp, "TableColumn.header.name", name );
}
addDP( dp, "TableColumn.header.class", getClassification().toLowerCase());
addDP( dp, "!UID!", getID());
if ( !manual ){
addDP( dp, "azbuddy.ui.table.online", online );
addDP( dp, "device.lastseen", last_seen==0?"":new SimpleDateFormat().format(new Date( last_seen )));
}
}
protected void
getTTDisplayProperties(
List<String[]> dp )
{
addDP( dp, "devices.xcode.working_dir", getWorkingDirectory( false ).getAbsolutePath());
addDP( dp, "devices.xcode.prof_def", getDefaultTranscodeProfile());
addDP( dp, "devices.xcode.profs", getTranscodeProfiles());
int tran_req = getTranscodeRequirement();
String tran_req_str;
if ( tran_req == TranscodeTarget.TRANSCODE_ALWAYS ){
tran_req_str = "device.xcode.always";
}else if ( tran_req == TranscodeTarget.TRANSCODE_NEVER ){
tran_req_str = "device.xcode.never";
}else{
tran_req_str = "device.xcode.whenreq";
}
addDP( dp, "device.xcode", MessageText.getString( tran_req_str ));
if ( errors.size() > 0 ){
String key = "ManagerItem.error";
for ( String error: errors.values()){
addDP( dp, key, error );
key = "";
}
}
}
protected void
addDP(
List<String[]> dp,
String name,
String value )
{
dp.add( new String[]{ name, value });
}
protected void
addDP(
List<String[]> dp,
String name,
File value )
{
dp.add( new String[]{ name, value==null?"":value.getAbsolutePath() });
}
protected void
addDP(
List<String[]> dp,
String name,
String[] values )
{
String value = "";
for ( String v: values ){
value += (value.length()==0?"":",") + v;
}
dp.add( new String[]{ name, value });
}
protected void
addDP(
List<String[]> dp,
String name,
boolean value )
{
dp.add( new String[]{ name, MessageText.getString( value?"GeneralView.yes":"GeneralView.no" ) });
}
protected void
addDP(
List<String[]> dp,
String name,
TranscodeProfile value )
{
addDP( dp, name, value==null?"":value.getName());
}
protected void
addDP(
List<String[]> dp,
String name,
TranscodeProfile[] values )
{
String[] names = new String[values.length];
for (int i=0;i<values.length;i++){
names[i] = values[i].getName();
}
addDP( dp, name, names);
}
public void
setCanRemove(
boolean can )
{
if ( can_remove != can ){
can_remove = can;
setDirty();
}
}
public boolean
canRemove()
{
return( can_remove );
}
public boolean
isBusy()
{
if ( isTranscoding()){
return( true );
}
synchronized( this ){
return( busy_count > 0 );
}
}
protected void
setBusy(
boolean busy )
{
boolean changed = false;
synchronized( this ){
if ( busy ){
changed = busy_count++ == 0;
}else{
changed = busy_count-- == 1;
}
}
if ( changed ){
manager.deviceChanged( this, false );
}
}
public void
remove()
{
manager.removeDevice( this );
}
public String
getPersistentStringProperty(
String prop )
{
return( getPersistentStringProperty( prop, "" ));
}
public String
getPersistentStringProperty(
String prop,
String def )
{
synchronized( persistent_properties ){
try{
byte[] value = (byte[])persistent_properties.get( prop );
if ( value == null ){
return( def );
}
return( new String( value, "UTF-8" ));
}catch( Throwable e ){
Debug.printStackTrace(e);
return( def );
}
}
}
public void
setPersistentStringProperty(
String prop,
String value )
{
boolean dirty = false;
synchronized( persistent_properties ){
String existing = getPersistentStringProperty( prop );
if ( !existing.equals( value )){
try{
if ( value == null ){
persistent_properties.remove( prop );
}else{
persistent_properties.put( prop, value.getBytes( "UTF-8" ));
}
dirty = true;
}catch( Throwable e ){
Debug.printStackTrace(e);
}
}
}
if ( dirty ){
setDirty();
}
}
public <T> Map<String,T>
getPersistentMapProperty(
String prop,
Map<String,T> def )
{
synchronized( persistent_properties ){
try{
Map<String,T> value = (Map<String,T>)persistent_properties.get( prop );
if ( value == null ){
return( def );
}
return( value );
}catch( Throwable e ){
Debug.printStackTrace(e);
return( def );
}
}
}
public <T>void
setPersistentMapProperty(
String prop,
Map<String,T> value )
{
boolean dirty = false;
synchronized( persistent_properties ){
Map<String,T> existing = getPersistentMapProperty( prop, null );
if ( !BEncoder.mapsAreIdentical( value, existing )){
try{
if ( value == null ){
persistent_properties.remove( prop );
}else{
persistent_properties.put( prop, value );
}
dirty = true;
}catch( Throwable e ){
Debug.printStackTrace(e);
}
}
}
if ( dirty ){
setDirty();
}
}
public void
removePersistentProperty(
String prop )
{
boolean dirty = false;
synchronized( persistent_properties ){
String existing = getPersistentStringProperty( prop );
if ( existing != null ){
try{
persistent_properties.remove( prop );
dirty = true;
}catch( Throwable e ){
Debug.printStackTrace(e);
}
}
}
if ( dirty ){
setDirty();
}
}
public String
getError()
{
synchronized( errors ){
if ( errors.size() == 0 ){
return( null );
}
String res = "";
for ( String s: errors.values()){
res += (res.length()==0?"":"; ") + s;
}
return( res );
}
}
protected void
setError(
Object key,
String error )
{
boolean changed = false;
if ( error == null || error.length() == 0 ){
synchronized( errors ){
changed = errors.remove( key ) != null;
}
}else{
String existing;
synchronized( errors ){
existing = errors.put( key, error );
}
changed = existing == null || !existing.equals( error );
}
if ( changed ){
manager.deviceChanged( this, false );
}
}
public String
getInfo()
{
synchronized( infos ){
if ( infos.size() == 0 ){
return( null );
}
String res = "";
for ( String s: infos.values()){
res += (res.length()==0?"":"; ") + s;
}
return( res );
}
}
protected void
setInfo(
Object key,
String info )
{
boolean changed = false;
if ( info == null || info.length() == 0 ){
synchronized( infos ){
changed = infos.remove( key ) != null;
}
}else{
String existing;
synchronized( infos ){
existing = infos.put( key, info );
}
changed = existing == null || !existing.equals( info );
}
if ( changed ){
manager.deviceChanged( this, false );
}
}
public String
getStatus()
{
if ( isLivenessDetectable()){
if ( isAlive()){
return( MessageText.getString( "device.status.online" ));
}else{
return( MessageText.getString( "device.od.error.notfound" ));
}
}
return( null );
}
public boolean
getPersistentBooleanProperty(
String prop,
boolean def )
{
return( getPersistentStringProperty( prop, def?"true":"false" ).equals( "true" ));
}
public void
setPersistentBooleanProperty(
String prop,
boolean value )
{
setPersistentStringProperty(prop, value?"true":"false" );
}
public int
getPersistentIntProperty(
String prop,
int def )
{
return( Integer.parseInt( getPersistentStringProperty( prop, String.valueOf(def) )));
}
public void
setPersistentIntProperty(
String prop,
int value )
{
setPersistentStringProperty(prop, String.valueOf( value ));
}
public String[]
getPersistentStringListProperty(
String prop )
{
synchronized( persistent_properties ){
try{
List<byte[]> values = (List<byte[]>)persistent_properties.get( prop );
if ( values == null ){
return( new String[0] );
}
String[] res = new String[values.size()];
int pos = 0;
for (byte[] value: values ){
res[pos++] = new String( value, "UTF-8" );
}
return( res );
}catch( Throwable e ){
Debug.printStackTrace(e);
return( new String[0] );
}
}
}
public void
setPersistentStringListProperty(
String prop,
String[] values )
{
boolean dirty = false;
synchronized( persistent_properties ){
try{
List<byte[]> values_list = new ArrayList<byte[]>();
for (String value: values ){
values_list.add( value.getBytes( "UTF-8" ));
}
persistent_properties.put( prop, values_list );
dirty = true;
}catch( Throwable e ){
Debug.printStackTrace(e);
}
}
if ( dirty ){
setDirty();
}
}
public void
setTransientProperty(
Object key,
Object value )
{
synchronized( transient_properties ){
if ( value == null ){
transient_properties.remove( key );
}else{
transient_properties.put( key, value );
}
}
}
public Object
getTransientProperty(
Object key )
{
synchronized( transient_properties ){
return( transient_properties.get( key ));
}
}
public void
setTransientProperty(
Object key1,
Object key2,
Object value )
{
synchronized( transient_properties ){
Map<Object,Object> l1 = (Map<Object,Object>)transient_properties.get( key1 );
if ( l1 == null ){
if ( value == null ){
return;
}
l1 = new HashMap<Object, Object>();
transient_properties.put( key1, l1 );
}
if ( value == null ){
l1.remove( key2 );
if ( l1.size() == 0 ){
transient_properties.remove( key1 );
}
}else{
l1.put( key2, value );
}
}
}
public Object
getTransientProperty(
Object key1,
Object key2 )
{
synchronized( transient_properties ){
Map<Object,Object> l1 = (Map<Object,Object>)transient_properties.get( key1 );
if ( l1 == null ){
return( null );
}
return( l1.get( key2 ));
}
}
protected void
close()
{
synchronized( this ){
if ( device_files_dirty ){
saveDeviceFile();
}
}
}
protected void
loadDeviceFile()
throws IOException
{
device_files_last_mod = SystemTime.getMonotonousTime();
if ( device_files_ref != null ){
device_files = device_files_ref.get();
}
if ( device_files == null ){
Map map = FileUtil.readResilientFile( getDeviceFile());
device_files = (Map<String,Map<String,?>>)map.get( "files" );
if ( device_files == null ){
device_files = new HashMap<String, Map<String,?>>();
}
device_files_ref = new WeakReference<Map<String,Map<String,?>>>( device_files );
log( "Loaded device file for " + getName() + ": files=" + device_files.size());
}
final int GC_TIME = 15000;
new DelayedEvent(
"Device:gc",
GC_TIME,
new AERunnable()
{
public void
runSupport()
{
synchronized( DeviceImpl.this ){
if ( SystemTime.getMonotonousTime() - device_files_last_mod >= GC_TIME ){
if ( device_files_dirty ){
saveDeviceFile();
}
device_files = null;
}else{
new DelayedEvent( "Device:gc2", GC_TIME, this );
}
}
}
});
}
protected URL
getStreamURL(
TranscodeFileImpl file,
String host )
{
return( manager.getStreamURL( file, host ));
}
protected String
getMimeType(
TranscodeFileImpl file )
{
return( manager.getMimeType( file ));
}
protected void
deleteFile(
TranscodeFileImpl file,
boolean delete_contents,
boolean remove )
throws TranscodeException
{
if ( file.isDeleted()){
return;
}
if ( delete_contents ){
File f = file.getCacheFile();
int time = 0;
while( f.exists() && !f.delete()){
if ( time > 3000 ){
log( "Failed to remove file '" + f.getAbsolutePath() + "'" );
break;
}else{
try{
Thread.sleep(500);
}catch( Throwable e ){
}
time += 500;
}
}
}
if ( remove ){
try{
// fire the listeners FIRST as this gives listeners a chance to extract data
// from the file before it is deleted (otherwise operations fail with 'file has been
// deleted'
for ( TranscodeTargetListener l: listeners ){
try{
l.fileRemoved( file );
}catch( Throwable e ){
Debug.out( e );
}
}
synchronized( this ){
if ( device_files == null ){
loadDeviceFile();
}else{
device_files_last_mod = SystemTime.getMonotonousTime();
}
device_files.remove( file.getKey());
device_files_dirty = true;
}
}catch( Throwable e ){
throw( new TranscodeException( "Delete failed", e ));
}
}
}
protected void
fileDirty(
TranscodeFileImpl file,
int type,
Object data )
{
try{
synchronized( this ){
if ( device_files == null ){
loadDeviceFile();
}else{
device_files_last_mod = SystemTime.getMonotonousTime();
}
}
device_files_dirty = true;
}catch( Throwable e ){
Debug.out( "Failed to load device file", e );
}
for ( TranscodeTargetListener l: listeners ){
try{
l.fileChanged( file, type, data );
}catch( Throwable e ){
Debug.out( e );
}
}
}
protected void
saveDeviceFile()
{
device_files_dirty = false;
try{
loadDeviceFile();
if ( device_files == null || device_files.size()==0 ){
FileUtil.deleteResilientFile( getDeviceFile());
}else{
Map map = new HashMap();
map.put( "files", device_files );
FileUtil.writeResilientFile( getDeviceFile(), map );
}
}catch( Throwable e ){
Debug.out( "Failed to save device file", e );
}
}
protected File
getDeviceFile()
throws IOException
{
File dir = getDevicesDir();
return( new File( dir, FileUtil.convertOSSpecificChars(getID(),false) + ".dat" ));
}
protected File
getDevicesDir()
throws IOException
{
File dir = new File(SystemProperties.getUserPath());
dir = new File( dir, "devices" );
if ( !dir.exists()){
if ( !dir.mkdirs()){
throw( new IOException( "Failed to create '" + dir + "'" ));
}
}
return( dir );
}
protected DeviceManagerImpl
getManager()
{
return( manager );
}
public void
addListener(
TranscodeTargetListener listener )
{
if (!listeners.contains(listener)) {
listeners.add( listener );
}
}
public void
removeListener(
TranscodeTargetListener listener )
{
listeners.remove( listener );
}
protected void
fireChanged()
{
List<DeviceListener> l;
synchronized( this ){
if ( device_listeners != null ){
l = device_listeners.getList();
}else{
return;
}
}
for ( DeviceListener listener: l ){
try{
listener.deviceChanged( this );
}catch( Throwable e ){
Debug.out( e );
}
}
}
public void
addListener(
DeviceListener listener )
{
synchronized( this ){
if ( device_listeners == null ){
device_listeners = new CopyOnWriteList<DeviceListener>();
}
device_listeners.add( listener );
}
}
public void
removeListener(
DeviceListener listener )
{
synchronized( this ){
if ( device_listeners != null ){
device_listeners.remove( listener );
if ( device_listeners.size() == 0 ){
device_listeners = null;
}
}
}
}
protected void
log(
String str )
{
manager.log( str );
}
protected void
log(
String str,
Throwable e )
{
manager.log( str, e );
}
public String
getString()
{
return( "type=" + type + ",uid=" + uid + ",class=" + classification );
}
public void
generate(
IndentWriter writer )
{
writer.println( getName() + "/" + getID() + "/" + type );
try{
writer.indent();
writer.println(
"hidden=" + hidden +
", last_seen=" + new SimpleDateFormat().format(new Date(last_seen)) +
", online=" + online +
", transcoding=" + transcoding );
writer.println( "p_props=" + persistent_properties );
writer.println( "t_props=" + transient_properties );
writer.println( "errors=" + errors );
writer.println( "infos=" + infos );
}finally{
writer.exdent();
}
}
public void
generateTT(
IndentWriter writer )
{
TranscodeFileImpl[] files = getFiles();
int complete = 0;
int copied = 0;
int deleted = 0;
int template = 0;
for ( TranscodeFileImpl f: files ){
if ( f.isComplete()){
complete++;
}
if ( f.isCopiedToDevice()){
copied++;
}
if ( f.isDeleted()){
deleted++;
}
if ( f.isTemplate()){
template++;
}
}
writer.println( "files=" + files.length + ", comp=" + complete + ", copied=" + copied + ", deleted=" + deleted + ", template=" + template );
}
protected class
browseLocationImpl
implements browseLocation
{
private String name;
private URL url;
protected
browseLocationImpl(
String _name,
URL _url )
{
name = _name;
url = _url;
}
public String
getName()
{
return( name );
}
public URL
getURL()
{
return( url );
}
}
}