gt: 		if ( itunes == null ){
context: /*
   * Created on Feb 10, 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.net.InetAddress;
  import java.net.URL;
  import java.text.SimpleDateFormat;
  import java.util.ArrayList;
  import java.util.Date;
  import java.util.List;
  import java.util.Map;
  
  import org.gudy.azureus2.core3.internat.MessageText;
  import org.gudy.azureus2.core3.util.AERunnable;
  import org.gudy.azureus2.core3.util.AESemaphore;
  import org.gudy.azureus2.core3.util.AEThread2;
  import org.gudy.azureus2.core3.util.AsyncDispatcher;
  import org.gudy.azureus2.core3.util.Debug;
  import org.gudy.azureus2.core3.util.IndentWriter;
  import org.gudy.azureus2.core3.util.SystemTime;
  import org.gudy.azureus2.plugins.PluginInterface;
  import org.gudy.azureus2.plugins.ipc.IPCInterface;
  
  import com.aelitis.azureus.core.devices.*;
  import com.aelitis.azureus.core.devices.DeviceManager.UnassociatedDevice;
  
  public class 
  DeviceiTunes
  	extends DeviceMediaRendererImpl
  	implements DeviceMediaRenderer
  {
  	private static final String UID = "a5d7869e-1ab9-6098-fef9-88476d988455";
  	
  	private static final Object	ERRROR_KEY_ITUNES 	= new Object();
  	private static final Object	COPY_PENDING_KEY 	= new Object();
  
  	private static final int INSTALL_CHECK_PERIOD	= 60*1000;
  	private static final int RUNNING_CHECK_PERIOD	= 30*1000;
  	private static final int DEVICE_CHECK_PERIOD	= 10*1000;
  	
  	private static final int INSTALL_CHECK_TICKS	= INSTALL_CHECK_PERIOD / DeviceManagerImpl.DEVICE_UPDATE_PERIOD;
  	private static final int RUNNING_CHECK_TICKS	= RUNNING_CHECK_PERIOD / DeviceManagerImpl.DEVICE_UPDATE_PERIOD;
  	private static final int DEVICE_CHECK_TICKS		= DEVICE_CHECK_PERIOD / DeviceManagerImpl.DEVICE_UPDATE_PERIOD;
  	
  	private static final Object	COPY_ERROR_KEY = new Object();
  	
  	private PluginInterface		itunes;
  	
  	private volatile boolean				is_installed;
  	private volatile boolean				is_running;
  	
  	private boolean				copy_outstanding;
  	private boolean				copy_outstanding_set;
  	private AEThread2			copy_thread;
  	private AESemaphore			copy_sem = new AESemaphore( "Device:copy" );
  	private AsyncDispatcher		async_dispatcher = new AsyncDispatcher( 5000 );
  	
  	private long				last_update_fail;
  	private int					consec_fails;
  	
  	private volatile boolean	manual_copy_activated;
  	
  	protected
  	DeviceiTunes(
  		DeviceManagerImpl	_manager,
  		PluginInterface		_itunes )
  	{
  		super( _manager, UID, "iTunes", true );
  		
  		itunes	= _itunes;
  	}
  
  	protected
  	DeviceiTunes(
  		DeviceManagerImpl	_manager,
  		Map					_map )
  	
  		throws IOException
  	{
  		super( _manager, _map );
  	}
  	
  	protected boolean
  	updateFrom(
  		DeviceImpl		_other,
  		boolean			_is_alive )
  	{
  		if ( !super.updateFrom( _other, _is_alive )){
  			
  			return( false );
  		}
  		
  		if ( !( _other instanceof DeviceiTunes )){
  			
  			Debug.out( "Inconsistent" );
  			
  			return( false );
  		}
  		
  		DeviceiTunes other = (DeviceiTunes)_other;
  		
  		itunes = other.itunes;
  		
  		return( true );
  	}
  	
  	protected void
  	initialise()
  	{
  		super.initialise();
  		
  		if ( getPersistentBooleanProperty( PP_COPY_OUTSTANDING, false )){
  		
  			setCopyOutstanding();
  		}
  		
  		addListener( 
  			new TranscodeTargetListener()
  			{
  				public void
  				fileAdded(
  					TranscodeFile		file )
  				{
  					if ( file.isComplete() && !file.isCopiedToDevice()){
  						
  						setCopyOutstanding();
  					}
  				}
  				
  				public void
  				fileChanged(
  					TranscodeFile		file,
  					int					type,
  					Object				data )
  				{
  					if ( file.isComplete() && !file.isCopiedToDevice()){
  						
  						setCopyOutstanding();
  					}
  				}
  				
  				public void
  				fileRemoved(
  					TranscodeFile		file )
  				{
  					copy_sem.release();
  				}
  			});
  	}
  	
  	protected String
  	getDeviceClassification()
  	{
  		return( "apple." );
  	}
  	
  	public int
  	getRendererSpecies()
  	{
  		return( RS_ITUNES );
  	}
  	
  	public InetAddress
  	getAddress()
  	{
  		return( null );
  	}
  	
  	public boolean
  	canRemove()
  	{
  			// no user-initiated removal, they need to uninstall the plugin
  		
  		return( false );
  	}
  	
  	public boolean 
  	isLivenessDetectable() 
  	{
  		return( true );
  	}
  	
  	@Override
  	public URL
  	getWikiURL()
  	{
  		try{
  			return( new URL( MessageText.getString( "device.wiki.itunes" )) );
  			
  		}catch( Throwable e ){
  			
  			Debug.out( e );
  			
  			return( null );
  		}
  	}
  	
  	protected void
  	destroy()
  	{
  		super.destroy();
  	}
  	
  	protected void
  	updateStatus(
  		int		tick_count )
  	{
  		super.updateStatus( tick_count );
  		
  		updateStatusSupport( tick_count );
  		
  		if ( is_installed && is_running ){
  			
  			alive();
  			
  		}else{
  			
  			dead();
  		}
  	}
  	
  	protected void
  	updateStatusSupport(
  		int		tick_count )
  	{
  		if ( itunes == null ){
  			
  			return;
  		}
  		
  		if ( !is_installed ){
  			
  			if ( tick_count % INSTALL_CHECK_TICKS == 0 ){
  				
  				updateiTunesStatus();
  				
  				return;
  			}
  		}
  		
  		if ( !is_running ){
  			
  			if ( tick_count % RUNNING_CHECK_TICKS == 0 ){
  				
  				updateiTunesStatus();
  				
  				return;
  			}
  		}
  		
  		if ( tick_count % DEVICE_CHECK_TICKS == 0 ){
  
  			updateiTunesStatus();
  		}
  	}
  	
  	protected void
  	updateiTunesStatus()
  	{	
  		if ( getManager().isClosing()){
  			
  			return;
  		}
  		
  		IPCInterface	ipc = itunes.getIPC();
  		
  		try{
  			Map<String,Object> properties = (Map<String,Object>)ipc.invoke( "getProperties", new Object[]{} );
  
  			is_installed = (Boolean)properties.get( "installed" );
  			
  			boolean	was_running = is_running;
  			
  			is_running	 = (Boolean)properties.get( "running" );
  			
  			if ( is_running && !was_running ){
  				
  				copy_sem.release();
  			}
  			
  			if ( !( is_installed || is_running )){
  				
  				last_update_fail = 0;
  			}
  			
  			String	info = null;
  			
  			if ( getCopyToDevicePending() > 0 ){
  				
  				if ( !is_installed ){
  					
  					info = MessageText.getString( "device.itunes.install" );
  					
  				}else if ( !is_running ){
  					
  					if ( !getAutoStartDevice()){
  					
  						info = MessageText.getString( "device.itunes.start" );
  					}
  				}
  			}
  				
  			setInfo( ERRROR_KEY_ITUNES, info );
  			
  			Throwable error = (Throwable)properties.get( "error" );
  			
  			if ( error != null ){
  				
  				throw( error );
  			}
  			
  			/*
  			List<Map<String,Object>> sources = (List<Map<String,Object>>)properties.get( "sources" );
  			
  			if ( sources != null ){
  				
  				for ( Map<String,Object> source: sources ){
  					
  					System.out.println( source );
  				}
  			}
  			*/
  			
  			last_update_fail 	= 0;
  			consec_fails		= 0;
  			
  			setError( ERRROR_KEY_ITUNES, null );
  			
  		}catch( Throwable e ){
  			
  			long	now = SystemTime.getMonotonousTime();
  			
  			consec_fails++;
  			
  			if ( last_update_fail == 0 ){
  				
  				last_update_fail = now;
  				
  			}else if ( now - last_update_fail > 60*1000 && consec_fails >= 3 ){
  							
  				setError( ERRROR_KEY_ITUNES, MessageText.getString( "device.itunes.install_problem" ));
  			}
  			
  			log( "iTunes IPC failed", e );
  		}
  	}
  	
  	public boolean
  	canCopyToDevice()
  	{
  		return( true );
  	}
  	
  	public boolean
  	getAutoCopyToDevice()
  	{
  			// default is true for itunes
  		
  		return( getPersistentBooleanProperty( PP_AUTO_COPY, true  ));
  	}
  		
  	public void
  	setAutoCopyToDevice(
  		boolean		auto )
  	{
  		setPersistentBooleanProperty( PP_AUTO_COPY, auto );
  		
  		setCopyOutstanding();
  	}
  	
  	public int
  	getCopyToDevicePending()
  	{
  		synchronized( this ){
  		
  			if ( !copy_outstanding ){
  				
  				return( 0 );
  			}
  		}
  
  		TranscodeFileImpl[] files = getFiles();
  		
  		int result = 0;
  			
  		for ( TranscodeFileImpl file: files ){
  
  			if ( file.isComplete() && !file.isCopiedToDevice()){
  				
  				result++;
  			}
  		}
  		
  		return( result );
  	}
  	
  	public void 
  	manualCopy() 
  	
  		throws DeviceManagerException 
  	{
  		if ( getAutoCopyToDevice()){
  			
  			throw( new DeviceManagerException( "Operation prohibited - auto copy enabled" ));
  		}
  		
  		manual_copy_activated = true;
  		
  		setCopyOutstanding();
  	}
  	
  	protected void
  	setCopyOutstanding()
  	{
  		synchronized( this ){
  			
  			copy_outstanding_set = true;
  			
  			if ( copy_thread == null ){
  				
  				copy_thread = 
  					new AEThread2( "Device:copier", true )
  					{
  						public void
  						run()
  						{
  							performCopy();
  						}
  					};
  									
  				copy_thread.start();
  			}
  			
  			copy_sem.release();
  		}
  	}
  	
  	public boolean
  	canAutoStartDevice()
  	{
  		return( true );
  	}
  	
  	public boolean
  	getAutoStartDevice()
  	{
  		return( getPersistentBooleanProperty( PP_AUTO_START, PR_AUTO_START_DEFAULT ));
  	}
  	
  	public void
  	setAutoStartDevice(
  		boolean		auto )
  	{
  		setPersistentBooleanProperty( PP_AUTO_START, auto );
  		
  		if ( auto ){
  			
  			copy_sem.release();
  		}
  	}
  	
  	public boolean
  	canAssociate()
  	{
  		return( false );
  	}
  	
  	public boolean
  	canRestrictAccess()
  	{
  		return( false );
  	}
  	
  	public void
  	associate(
  		UnassociatedDevice	assoc )
  	{
  	}
  	
  	protected void
  	performCopy()
  	{
  		synchronized( this ){
  
  			copy_outstanding = true;
  		
  			async_dispatcher.dispatch(
  				new AERunnable()
  				{
  					public void
  					runSupport()
  					{
  						setPersistentBooleanProperty( PP_COPY_OUTSTANDING, true );
  					}
  				});
  		}
  		
  		while( true ){
  			
  			if ( copy_sem.reserve( 60*1000 )){
  				
  				while( copy_sem.reserveIfAvailable());
  			}
  				
  			if ( !getAutoCopyToDevice()){
  				
  				if ( manual_copy_activated ){
  					
  					manual_copy_activated = false;
  					
  				}else{
  					
  					TranscodeFileImpl[] files = getFiles();
  					
  					int to_copy = 0;
  					
  					for ( TranscodeFileImpl file: files ){
  							
  						if ( file.isComplete() && !file.isCopiedToDevice()){
  							
  							to_copy++;
  						}
  					}
  					
  					if ( to_copy == 0 ){
  						
  						setInfo( COPY_PENDING_KEY, null );
  					}else{
  					
  						String str = MessageText.getString( "devices.info.copypending3", new String[]{ String.valueOf( to_copy ) });
  	
  						setInfo( COPY_PENDING_KEY, str );
  					}
  					
  					continue;
  				}
  			}
  			
  			setInfo( COPY_PENDING_KEY, null );
  
  			boolean	auto_start = getAutoStartDevice();
  			
  			synchronized( this ){
  
  				if ( itunes == null || ( !is_running && !( auto_start && is_installed ))){
  					
  					if ( !( copy_outstanding || copy_outstanding_set )){
  						
  						copy_thread = null;
  						
  						break;
  					}
  					
  					continue;
  				}
  
  				copy_outstanding_set = false;
  			}
  						
  			TranscodeFileImpl[] files = getFiles();
  				
  			List<TranscodeFileImpl>	to_copy = new ArrayList<TranscodeFileImpl>();
  				
  			boolean	borked_exist = false;
  			
  			for ( TranscodeFileImpl file: files ){
  					
  				if ( file.isComplete() && !file.isCopiedToDevice()){
  					
  					if ( file.getCopyToDeviceFails() < 3 ){
  					
  						to_copy.add( file );
  						
  					}else{
  						
  						borked_exist = true;
  					}
  				}
  			}
  				
  			if ( borked_exist ){
  				
  				setError( COPY_ERROR_KEY, MessageText.getString( "device.error.copyfail2") );
  			}
  			
  			synchronized( this ){
  
  				if ( to_copy.size() == 0 && !copy_outstanding_set && !borked_exist ){
  						
  					copy_outstanding = false;
  					
  					async_dispatcher.dispatch(
  						new AERunnable()
  						{
  							public void
  							runSupport()
  							{
  								setError( COPY_ERROR_KEY, null );
  
  								setPersistentBooleanProperty( PP_COPY_OUTSTANDING, false );
  							}
  						});
  					
  					copy_thread = null;
  					
  					break;
  				}
  			}
  			
  			for ( TranscodeFileImpl transcode_file: to_copy ){
  				
  				try{
  					File	file = transcode_file.getTargetFile().getFile();
  					
  					try{
  						IPCInterface	ipc = itunes.getIPC();
  						
  						if ( !is_running ){
  							
  							log( "Auto-starting iTunes" );
  						}
  
  						Map<String,Object> result = (Map<String,Object>)ipc.invoke( "addFileToLibrary", new Object[]{ file } );
  		
  						Throwable error = (Throwable)result.get( "error" );
  						
  						if ( error != null ){
  							
  							throw( error );
  						}
  						
  						is_running = true;
  						
  						log( "Added file '" + file + ": " + result );
  						
  						transcode_file.setCopiedToDevice( true );
  						
  					}catch( Throwable e ){
  						
  						transcode_file.setCopyToDeviceFailed();
  						
  						log( "Failed to copy file " + file, e );
  					}
  				}catch( TranscodeException e ){
  
  					// file has been deleted
  				}
  			}
  		}
  	}
  	
  	public boolean 
  	isBrowsable()
  	{
  		return( false );
  	}
  	
  	public browseLocation[] 
  	getBrowseLocations() 
  	{
  		return null;
  	}
  	
  	protected void
  	getDisplayProperties(
  		List<String[]>	dp )
  	{
  		super.getDisplayProperties( dp );
  		
  