package org.limewire.core.impl.itunes; import java.io.File; import java.io.IOException; import java.util.concurrent.ExecutorService; import java.util.concurrent.TimeUnit; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.limewire.concurrent.ExecutorsHelper; import org.limewire.core.settings.iTunesSettings; import org.limewire.external.itunes.windows.com.IITLibraryPlaylist; import org.limewire.external.itunes.windows.com.IITOperationStatus; import org.limewire.external.itunes.windows.com.IiTunes; import org.limewire.util.FileUtils; import org.limewire.util.OSUtils; import com.google.inject.Singleton; import com.jacob.activeX.ActiveXComponent; import com.jacob.com.ComFailException; import com.jacob.com.ComThread; import com.jacob.com.Dispatch; /** * Handles sending completed downloads into iTunes. */ @Singleton public final class ItunesMediatorImpl implements ItunesMediator { private static final Log LOG = LogFactory.getLog(ItunesMediatorImpl.class); /** * The queue that will process the tunes to add. */ private final ExecutorService QUEUE = ExecutorsHelper.newProcessingQueue("iTunesAdderThread"); @Override public void addSong(File file) { // If not on Windows or OSX don't do anything. if (!OSUtils.isWindows() && !OSUtils.isMacOSX()) return; // Make sure we convert any uppercase to lowercase or vice versa. try { file = FileUtils.getCanonicalFile(file); } catch (IOException ignored) { } // Verify that we're adding a real file. if (!file.exists()) { if (LOG.isDebugEnabled()) LOG.debug("File: '" + file + "' does not exist"); return; } File[] files; if (file.isDirectory()) { files = FileUtils.getFilesRecursive(file, iTunesSettings.ITUNES_SUPPORTED_FILE_TYPES.get()); } else if (file.isFile() && isSupported(FileUtils.getFileExtension(file))) files = new File[] { file }; else return; for (File toAdd : files) { if (LOG.isTraceEnabled()) LOG.trace("Will add '" + toAdd + "' to Playlist"); if (OSUtils.isWindows()) QUEUE.execute(new AddFileToWindowsITunes(toAdd)); else if (OSUtils.isMacOSX()) QUEUE.execute(new ExecOSAScriptCommand(toAdd)); } } /** * Returns true if the extension of name is a supported file type. */ private static boolean isSupported(String extension) { if (extension.isEmpty()) return false; String[] types = iTunesSettings.ITUNES_SUPPORTED_FILE_TYPES.get(); for (int i = 0; i < types.length; i++) if (extension.equalsIgnoreCase(types[i])) return true; return false; } /** Uses JACOB and the iTunes COM SDK to add files to iTunes. */ private static class AddFileToWindowsITunes implements Runnable { private static final String ITUNES_ACTIVEX_NAME = "iTunes.Application"; /** * Set to a value the first time this class executes an add operation. * If false, all subsequent adds will be ignored. */ private static Boolean ready = null; private final File file; /** * Constructs a new AddFileToWindowsITunes for the specified file. */ public AddFileToWindowsITunes(File file) { this.file = file; } public void run() { try { addFileToLibrary(file); } catch (com.jacob.com.ComFailException e) { LOG.warn("Error adding file to itunes library", e); } catch (IllegalStateException e) { LOG.warn("Error adding file to itunes library", e); } } /** * @return true if: * <li>The JACOB library is correctly linked * <li>iTunes seems to be responsive */ public synchronized boolean isReady() { if (ready != null) return ready; // Check for JACOB & iTunes try { ComThread.InitMTA(); try { // Check for iTunes new ActiveXComponent(ITUNES_ACTIVEX_NAME); } finally { ComThread.Release(); } } catch (UnsatisfiedLinkError ex) { // JACOB is not in the java.library.path LOG.error("JACOB is not in the java.library.path.", ex); ready = Boolean.FALSE; return false; } catch (ComFailException ex) { // iTunes ActiveX wasn't available... LOG.error("iTunes ActiveX component not found.", ex); ready = Boolean.FALSE; return false; } ready = Boolean.TRUE; return true; } /** * Does the heavy lifting, activating COM and inserting the file into * the iTunes DB. Does not add duplicates. */ private boolean addFileToLibrary(File newFile) { if (!isReady()) return false; ComThread.InitMTA(); try { ActiveXComponent iTunesCom = new ActiveXComponent(ITUNES_ACTIVEX_NAME); Dispatch iTunesController = iTunesCom.getObject(); IiTunes it = new IiTunes(iTunesController); IITLibraryPlaylist pl = it.getLibraryPlaylist(); // Add the file IITOperationStatus status = pl.addFile(newFile.getAbsolutePath()); if (status == null) return false; while (status.getInProgress()) { // Waiting for add operation to complete (This is usually // instantaneous)... try { TimeUnit.MILLISECONDS.sleep(10); } catch (InterruptedException ignored) { break; } } } finally { ComThread.Release(); } return true; } } /** * Executes the osascript CLI command (Mac OSX) */ private static class ExecOSAScriptCommand implements Runnable { /** * The file to add. */ private final File file; /** * Constructs a new ExecOSAScriptCommand for the specified file. */ public ExecOSAScriptCommand(File file) { this.file = file; } /** * Constructs and returns a osascript command. */ private String[] createOSAScriptCommand(File file) { String path = file.getAbsolutePath(); String playlist = iTunesSettings.ITUNES_PLAYLIST.get(); String[] command = new String[] { "osascript", "-e", "tell application \"Finder\"", "-e", "set hfsFile to (POSIX file \"" + path + "\")", "-e", "set thePlaylist to \"" + playlist + "\"", "-e", "tell application \"iTunes\"", // "-e", "activate", // launch and bring to front "-e", "launch", // launch in background "-e", "if not (exists playlist thePlaylist) then", "-e", "set thisPlaylist to make new playlist", "-e", "set name of thisPlaylist to thePlaylist", "-e", "end if", "-e", "add hfsFile to playlist thePlaylist", "-e", "end tell", "-e", "end tell" }; return command; } /** * Runs the osascript command */ public void run() { try { Runtime.getRuntime().exec(createOSAScriptCommand(file)); } catch (IOException err) { LOG.debug(err); } } } }