/* * Created on Feb 4, 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.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import org.gudy.azureus2.core3.category.Category; import org.gudy.azureus2.core3.category.CategoryListener; import org.gudy.azureus2.core3.category.CategoryManager; import org.gudy.azureus2.core3.category.CategoryManagerListener; import org.gudy.azureus2.core3.download.DownloadManager; import org.gudy.azureus2.core3.global.GlobalManagerAdapter; import org.gudy.azureus2.core3.global.GlobalManagerListener; import org.gudy.azureus2.core3.util.*; import org.gudy.azureus2.plugins.*; import org.gudy.azureus2.plugins.disk.DiskManagerFileInfo; import org.gudy.azureus2.plugins.download.Download; import org.gudy.azureus2.plugins.torrent.TorrentAttribute; import org.gudy.azureus2.pluginsimpl.local.PluginCoreUtils; import org.gudy.azureus2.pluginsimpl.local.PluginInitializer; import com.aelitis.azureus.core.AzureusCore; import com.aelitis.azureus.core.AzureusCoreFactory; import com.aelitis.azureus.core.devices.*; import com.aelitis.azureus.core.tag.Tag; import com.aelitis.azureus.core.tag.TagDownload; import com.aelitis.azureus.core.tag.TagFeature; import com.aelitis.azureus.core.tag.TagFeatureListener; import com.aelitis.azureus.core.tag.TagFeatureTranscode; import com.aelitis.azureus.core.tag.TagListener; import com.aelitis.azureus.core.tag.TagManager; import com.aelitis.azureus.core.tag.TagManagerFactory; import com.aelitis.azureus.core.tag.TagType; import com.aelitis.azureus.core.tag.Taggable; import com.aelitis.azureus.core.util.CopyOnWriteList; public class TranscodeManagerImpl implements TranscodeManager { private DeviceManagerImpl device_manager; private AzureusCore azureus_core; private volatile TranscodeProviderVuze vuzexcode_provider; private CopyOnWriteList<TranscodeManagerListener> listeners = new CopyOnWriteList<TranscodeManagerListener>(); private TranscodeQueueImpl queue = new TranscodeQueueImpl( this ); private AESemaphore init_sem = new AESemaphore( "TM:init" ); private boolean hooked_categories; private Map<Category,Object[]> category_map = new HashMap<Category, Object[]>(); private CategoryListener category_listener; private GlobalManagerListener category_dl_listener; private TorrentAttribute category_ta; private boolean hooked_tags; private Map<Tag,Object[]> tag_map = new HashMap<Tag, Object[]>(); private TagListener tag_listener; private TorrentAttribute tag_ta; protected TranscodeManagerImpl( DeviceManagerImpl _dm ) { device_manager = _dm; azureus_core = AzureusCoreFactory.getSingleton(); PluginInterface default_pi = PluginInitializer.getDefaultInterface(); category_ta = default_pi.getTorrentManager().getPluginAttribute( "xcode.cat.done" ); tag_ta = default_pi.getTorrentManager().getPluginAttribute( "xcode.tag.done" ); final AESemaphore plugin_sem = new AESemaphore( "TM:plugin" ); default_pi.addListener( new PluginListener() { public void initializationComplete() { try{ PluginInterface default_pi = PluginInitializer.getDefaultInterface(); default_pi.addEventListener( new PluginEventListener() { public void handleEvent( PluginEvent ev ) { int type = ev.getType(); if ( type == PluginEvent.PEV_PLUGIN_OPERATIONAL ){ pluginAdded((PluginInterface)ev.getValue()); } if ( type == PluginEvent.PEV_PLUGIN_NOT_OPERATIONAL ){ pluginRemoved((PluginInterface)ev.getValue()); } } }); PluginInterface[] plugins = default_pi.getPluginManager().getPlugins(); for ( PluginInterface pi: plugins ){ if ( pi.getPluginState().isOperational()){ pluginAdded( pi ); } } }finally{ plugin_sem.releaseForever(); } } public void closedownInitiated() { plugin_sem.releaseForever(); // we don't want things hanging around for init if we're closing init_sem.releaseForever(); } public void closedownComplete() { } }); if ( !plugin_sem.reserve( 30*1000 )){ Debug.out( "Timeout waiting for init" ); AEDiagnostics.dumpThreads(); } } protected void initialise() { queue.initialise(); init_sem.releaseForever(); } protected void pluginAdded( PluginInterface pi ) { if ( pi.getPluginState().isBuiltIn()){ return; } String plugin_id = pi.getPluginID(); if ( plugin_id.equals( "vuzexcode" )){ boolean added = false; boolean updated = false; TranscodeProviderVuze provider = null; synchronized( this ){ if ( vuzexcode_provider == null ){ vuzexcode_provider = new TranscodeProviderVuze( this, pi ); added = true; }else if ( pi != vuzexcode_provider.getPluginInterface()){ vuzexcode_provider.update( pi ); updated = true; } provider = vuzexcode_provider; } if ( added ){ for ( TranscodeManagerListener listener: listeners ){ try{ listener.providerAdded( provider ); }catch( Throwable e ){ Debug.out( e ); } } }else if ( updated ){ for ( TranscodeManagerListener listener: listeners ){ try{ listener.providerUpdated( provider ); }catch( Throwable e ){ Debug.out( e ); } } } } } protected void pluginRemoved( PluginInterface pi ) { String plugin_id = pi.getPluginID(); if ( plugin_id.equals( "vuzexcode" )){ TranscodeProviderVuze provider = null; synchronized( this ){ if ( vuzexcode_provider != null ){ provider = vuzexcode_provider; vuzexcode_provider.destroy(); vuzexcode_provider = null; } } if ( provider != null ){ for ( TranscodeManagerListener listener: listeners ){ try{ listener.providerRemoved( provider ); }catch( Throwable e ){ Debug.out( e ); } } } } } protected void updateStatus( int tick_count ) { if ( queue != null ){ queue.updateStatus( tick_count ); if ( !hooked_categories ){ hooked_categories = true; CategoryManager.addCategoryManagerListener( new CategoryManagerListener() { public void categoryAdded( Category category ) { } public void categoryRemoved( Category category ) { } public void categoryChanged( Category category ) { checkCategories(); } }); checkCategories(); } if ( !hooked_tags ){ hooked_tags = true; TagManagerFactory.getTagManager().addTagFeatureListener( TagFeature.TF_XCODE, new TagFeatureListener() { public void tagFeatureChanged( Tag tag, int feature ) { checkTags(); } }); checkTags(); } } } private void checkCategories() { Category[] cats = CategoryManager.getCategories(); Map<Category,Object[]> active_map = new HashMap<Category, Object[]>(); for ( Category cat: cats ){ String target = cat.getStringAttribute( Category.AT_AUTO_TRANSCODE_TARGET ); if ( target != null ){ String device_id = null; if ( target.endsWith( "/blank" )){ device_id = target.substring( 0, target.length() - 6 ); } DeviceMediaRenderer target_dmr = null; TranscodeProfile target_profile = null; for ( DeviceImpl device: device_manager.getDevices()){ if ( !( device instanceof DeviceMediaRenderer )){ continue; } DeviceMediaRenderer dmr = (DeviceMediaRenderer)device; if ( device_id != null ){ if ( device.getID().equals( device_id )){ target_dmr = dmr; target_profile = device.getBlankProfile(); break; } }else{ TranscodeProfile[] profs = device.getTranscodeProfiles(); for ( TranscodeProfile prof: profs ){ if ( prof.getUID().equals( target )){ target_dmr = dmr; target_profile = prof; break; } } } } if ( target_dmr != null ){ active_map.put( cat, new Object[]{ target_dmr, target_profile }); } } } Map<Category,Object[]> to_process = new HashMap<Category, Object[]>(); synchronized( category_map ){ if ( category_listener == null ){ category_listener = new CategoryListener() { public void downloadManagerAdded( Category cat, DownloadManager manager ) { Object[] details; synchronized( category_map ){ details = category_map.get( cat ); } if ( details != null ){ processCategory( cat, details, manager ); } } public void downloadManagerRemoved( Category cat, DownloadManager removed ) { } }; } Iterator<Category> it = category_map.keySet().iterator(); while( it.hasNext()){ Category c = it.next(); if ( !active_map.containsKey( c )){ c.removeCategoryListener( category_listener ); it.remove(); } } for ( final Category c: active_map.keySet()){ if ( !category_map.containsKey( c )){ to_process.put( c, active_map.get(c)); c.addCategoryListener( category_listener ); category_map.put( c, active_map.get(c)); if ( c.getType() == Category.TYPE_UNCATEGORIZED ){ if ( category_dl_listener == null ){ // new downloads don't get a category-change event fired when added // we also want to delay things a bit to allow other components // to set an initial category. there's no hurry anyways category_dl_listener = new GlobalManagerAdapter() { public void downloadManagerAdded( final DownloadManager dm ) { new DelayedEvent( "TM:cat-check", 10*1000, new AERunnable() { public void runSupport() { Category dm_c = dm.getDownloadState().getCategory(); if ( dm_c == null || dm_c == c ){ // still uncategorised Object[] details; synchronized( category_map ){ details = category_map.get( c ); } if ( details != null ){ processCategory( c, details, dm ); } } } }); } public void downloadManagerRemoved( DownloadManager dm ) { } }; azureus_core.getGlobalManager().addListener( category_dl_listener, false ); } } } } } if ( to_process.size() > 0 ){ List<DownloadManager> downloads = azureus_core.getGlobalManager().getDownloadManagers(); for ( Map.Entry<Category, Object[]> entry: to_process.entrySet()){ Category c = entry.getKey(); Object[] details = entry.getValue(); List<DownloadManager> list = c.getDownloadManagers( downloads ); for( DownloadManager dm: list ){ processCategory( c, details, dm ); } } } } private void processCategory( Category cat, Object[] details, DownloadManager dm ) { Download download = PluginCoreUtils.wrap( dm ); if ( download == null ){ return; } if ( download.getFlag( Download.FLAG_LOW_NOISE )){ return; } String str = download.getAttribute( category_ta ); String cat_name = cat.getName(); if ( cat.getType() == Category.TYPE_UNCATEGORIZED ){ cat_name = "<none>"; } String cat_tag = cat_name + ";"; if ( str != null && str.contains( cat_tag )){ return; } try{ DeviceMediaRenderer device = (DeviceMediaRenderer)details[0]; TranscodeProfile profile = (TranscodeProfile)details[1]; log( "Category " + cat_name + " - adding " + download.getName() + " to " + device.getName() + "/" + profile.getName()); DiskManagerFileInfo[] dm_files = download.getDiskManagerFileInfo(); int num_added = 0; for ( DiskManagerFileInfo dm_file: dm_files ){ // limit number of files we can add to avoid crazyness if ( num_added > 64 ){ break; } // could be smarter here and check extension or whatever if ( dm_files.length == 1 || dm_file.getLength() >= 128*1024 ){ try{ queue.add( device, profile, dm_file, false ); num_added++; }catch( Throwable e ){ log( " add failed", e ); } } } }finally{ download.setAttribute( category_ta, str==null?cat_tag:(str+cat_tag)); } } private void checkTags() { TagManager tm = TagManagerFactory.getTagManager(); Map<Tag,Object[]> active_map = new HashMap<Tag, Object[]>(); for ( TagType tt: tm.getTagTypes()){ if ( !tt.hasTagTypeFeature( TagFeature.TF_XCODE )){ continue; } for ( Tag tag: tt.getTags()){ TagFeatureTranscode tfx = (TagFeatureTranscode)tag; if ( !tfx.supportsTagTranscode()){ continue; } String[] target_details = tfx.getTagTranscodeTarget(); if ( target_details != null ){ String target = target_details[0]; String device_id = null; if ( target.endsWith( "/blank" )){ device_id = target.substring( 0, target.length() - 6 ); } DeviceMediaRenderer target_dmr = null; TranscodeProfile target_profile = null; for ( DeviceImpl device: device_manager.getDevices()){ if ( !( device instanceof DeviceMediaRenderer )){ continue; } DeviceMediaRenderer dmr = (DeviceMediaRenderer)device; if ( device_id != null ){ if ( device.getID().equals( device_id )){ target_dmr = dmr; target_profile = device.getBlankProfile(); break; } }else{ TranscodeProfile[] profs = device.getTranscodeProfiles(); for ( TranscodeProfile prof: profs ){ if ( prof.getUID().equals( target )){ target_dmr = dmr; target_profile = prof; break; } } } } if ( target_dmr != null ){ active_map.put( tag, new Object[]{ target_dmr, target_profile }); } } } } Map<Tag,Object[]> to_process = new HashMap<Tag, Object[]>(); synchronized( tag_map ){ if ( tag_listener == null ){ tag_listener = new TagListener() { public void taggableAdded( Tag tag, Taggable tagged ) { Object[] details; synchronized( tag_map ){ details = tag_map.get( tag ); } if ( details != null ){ processTag( tag, details, (DownloadManager)tagged ); } } public void taggableSync( Tag tag ) { } public void taggableRemoved( Tag tag, Taggable tagged ) { } }; } Iterator<Tag> it = tag_map.keySet().iterator(); while( it.hasNext()){ Tag t = it.next(); if ( !active_map.containsKey( t )){ t.removeTagListener( tag_listener ); it.remove(); } } for ( final Tag tag: active_map.keySet()){ if ( !tag_map.containsKey( tag )){ to_process.put( tag, active_map.get( tag )); tag.addTagListener( tag_listener, false ); tag_map.put( tag, active_map.get( tag )); } } } if ( to_process.size() > 0 ){ for ( Map.Entry<Tag, Object[]> entry: to_process.entrySet()){ Tag tag = entry.getKey(); Object[] details = entry.getValue(); Set<DownloadManager> list = ((TagDownload)tag).getTaggedDownloads(); for( DownloadManager dm: list ){ processTag( tag, details, dm ); } } } } private void processTag( Tag tag, Object[] details, DownloadManager dm ) { Download download = PluginCoreUtils.wrap( dm ); if ( download == null ){ return; } if ( download.getFlag( Download.FLAG_LOW_NOISE )){ return; } String str = download.getAttribute( tag_ta ); String tag_name = tag.getTagName( true ); String tag_tag = tag.getTagType().getTagType() + "." + tag.getTagID() + ";"; if ( str != null && str.contains( tag_tag )){ return; } try{ DeviceMediaRenderer device = (DeviceMediaRenderer)details[0]; TranscodeProfile profile = (TranscodeProfile)details[1]; log( "Tag " + tag_name + " - adding " + download.getName() + " to " + device.getName() + "/" + profile.getName()); DiskManagerFileInfo[] dm_files = download.getDiskManagerFileInfo(); int num_added = 0; for ( DiskManagerFileInfo dm_file: dm_files ){ // limit number of files we can add to avoid crazyness if ( num_added > 64 ){ break; } // could be smarter here and check extension or whatever if ( dm_files.length == 1 || dm_file.getLength() >= 128*1024 ){ try{ queue.add( device, profile, dm_file, false ); num_added++; }catch( Throwable e ){ log( " add failed", e ); } } } }finally{ download.setAttribute( tag_ta, str==null?tag_tag:(str+tag_tag)); } } public TranscodeProvider[] getProviders() { TranscodeProviderVuze vp = vuzexcode_provider; if ( vp == null ){ return( new TranscodeProvider[0] ); } return( new TranscodeProvider[]{ vp }); } protected TranscodeProvider getProvider( int p_id ) throws TranscodeException { TranscodeProviderVuze vp = vuzexcode_provider; if ( p_id == TranscodeProvider.TP_VUZE && vp != null ){ return( vp ); } throw( new TranscodeException( "Transcode provider not registered" )); } protected TranscodeProfile getProfileFromUID( String uid ) { for ( TranscodeProvider provider: getProviders()){ TranscodeProfile profile = provider.getProfile( uid ); if ( profile != null ){ return( profile ); } } return( null ); } public TranscodeQueueImpl getQueue() { if ( !init_sem.reserve(30*1000)){ Debug.out( "Timeout waiting for init" ); AEDiagnostics.dumpThreads(); } return( queue ); } protected DeviceManagerImpl getManager() { return( device_manager ); } protected TranscodeTarget lookupTarget( String target_id ) throws TranscodeException { Device device = device_manager.getDevice( target_id ); if ( device instanceof TranscodeTarget ){ return((TranscodeTarget)device); } throw( new TranscodeException( "Transcode target with id " + target_id + " not found" )); } protected DiskManagerFileInfo lookupFile( byte[] hash, int index ) throws TranscodeException { try{ Download download = PluginInitializer.getDefaultInterface().getDownloadManager().getDownload( hash ); if ( download == null ){ throw( new TranscodeException( "Download with hash " + ByteFormatter.encodeString( hash ) + " not found" )); } return( download.getDiskManagerFileInfo()[index]); }catch( Throwable e ){ throw( new TranscodeException( "Download with hash " + ByteFormatter.encodeString( hash ) + " not found", e )); } } protected void close() { queue.close(); } public void addListener( TranscodeManagerListener listener ) { listeners.add( listener ); } public void removeListener( TranscodeManagerListener listener ) { listeners.remove( listener ); } protected void log( String str ) { device_manager.log( "Trans: " + str ); } protected void log( String str, Throwable e ) { device_manager.log( "Trans: " + str, e ); } public void generate( IndentWriter writer ) { writer.println( "Transcode Manager: vuze provider=" + vuzexcode_provider ); queue.generate( writer ); } }