/*
* 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 );
if ( itunes == null ){
addDP( dp, "devices.comp.missing", "<null>" );
}else{
updateiTunesStatus();
addDP( dp, "devices.installed", is_installed );
addDP( dp, "MyTrackerView.status.started", is_running );
addDP( dp, "devices.copy.pending", copy_outstanding );
addDP( dp, "devices.auto.start", getAutoStartDevice());
}
}
public String
getStatus()
{
if ( is_running ){
return( MessageText.getString( "device.itunes.status.running" ));
}else if ( is_installed ){
return( MessageText.getString( "device.itunes.status.notrunning" ));
}else{
return( MessageText.getString( "device.itunes.status.notinstalled" ));
}
}
public void
generate(
IndentWriter writer )
{
super.generate( writer );
try{
writer.indent();
writer.println( "itunes=" + itunes + ", installed=" + is_installed + ", running=" + is_running + ", auto_start=" + getAutoStartDevice());
writer.println( "copy_os=" + copy_outstanding + ", last_fail=" + new SimpleDateFormat().format( new Date( last_update_fail )));
}finally{
writer.exdent();
}
}
}