package com.robonobo.gui.itunes.mac;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileFilter;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import com.robonobo.core.api.model.Playlist;
import com.robonobo.core.api.model.SharedTrack;
import com.robonobo.core.api.model.User;
import com.robonobo.core.itunes.ITunesService;
public class MacITunesService extends ITunesService {
static final String OSASCRIPT = "osascript";
private String[] scriptNames = { "listTracksInLibrary.scpt", "syncPlaylist.scpt", "createRoboFolder.scpt", "listAllPlaylists.scpt" };
private File scriptsDir;
private boolean iTunesReady = false;
@Override
public void startup() throws Exception {
// Don't init iTunes here, do it on-demand (improves startup time)
}
private synchronized void checkReady() throws IOException {
if (!iTunesReady) {
createScriptsDir();
copyScriptsIntoDir(scriptsDir);
// TODO iTunes will pop open if it's not already done, so make sure
// we get the focus back afterwards
iTunesReady = true;
}
}
private void createRobonoboFolder() throws IOException {
runScript("createRoboFolder.scpt", null);
}
private void copyScriptsIntoDir(File itScriptsDir) throws IOException {
for (String scriptName : scriptNames) {
copyScriptFromJar(scriptName, itScriptsDir);
}
}
private void createScriptsDir() {
scriptsDir = new File(new File(getRobonobo().getHomeDir(), "scripts"), "iTunes");
if (!scriptsDir.exists())
scriptsDir.mkdirs();
}
private List<String> runScript(String script, List<String> scriptArgs) throws IOException {
List<String> result = new ArrayList<String>();
List<String> procArgs = new ArrayList<String>();
procArgs.add(OSASCRIPT);
procArgs.add(script);
if (scriptArgs != null)
procArgs.addAll(scriptArgs);
ProcessBuilder pb = new ProcessBuilder(procArgs);
pb.directory(scriptsDir);
Process proc = pb.start();
String line;
BufferedReader reader = new BufferedReader(new InputStreamReader(proc.getInputStream()));
// Haven't been able to output to stdout from applescript - some pages
// suggested writing to the file /dev/fd/0, but applescript claims that
// file doesn't exist... instead we return a list of items and newlines.
// So we need to strip off the first line, which is blank, and strip off
// the commas from the end of subsequent lines (except the last one)
boolean first = true;
while ((line = reader.readLine()) != null) {
if (first)
first = false;
else {
if (line.endsWith(","))
line = line.substring(0, line.length() - 1);
result.add(line);
}
}
// Thread t = new Thread(new LogReader(proc.getInputStream()));
// t.start();
// Wait for it to finish, then check the return status is ok
int retCode;
try {
retCode = proc.waitFor();
} catch (InterruptedException e) {
log.error("Process was interrupted: " + script);
return result;
}
if (retCode != 0) {
BufferedReader errReader = new BufferedReader(new InputStreamReader(proc.getErrorStream()));
String errLine;
List<String> errLines = new ArrayList<String>();
while ((errLine = errReader.readLine()) != null) {
errLines.add(errLine);
}
log.error("Error executing script '" + script + "': return code was " + retCode);
return result;
}
return clean(result);
}
private void copyScriptFromJar(String scriptName, File targetDir) throws IOException {
String fqName = "com/robonobo/gui/itunes/mac/" + scriptName;
InputStream is = getClass().getClassLoader().getResourceAsStream(fqName);
OutputStream os = new FileOutputStream(new File(targetDir, scriptName));
byte[] buf = new byte[1024];
int bytesRead;
while ((bytesRead = is.read(buf)) >= 0) {
os.write(buf, 0, bytesRead);
}
is.close();
os.close();
}
@Override
public void shutdown() throws Exception {
// Do nothing
}
private List<String> clean(List<String> scriptOut) {
// Because I hate applescript and want to write as little of it as
// possible, the output lines that we get back have a comma at the
// end, which we remove
List<String> result = new ArrayList<String>();
for(String line : scriptOut) {
String tLine = line.trim();
if(tLine.endsWith(","))
tLine = tLine.substring(0, tLine.length()-1);
result.add(tLine);
}
return result;
}
@Override
public List<File> getAllITunesFiles(FileFilter filter) throws IOException {
checkReady();
List<File> result = new ArrayList<File>();
List<String> trackPaths = runScript("listTracksInLibrary.scpt", null);
for (String trackPath : trackPaths) {
File f = new File(trackPath);
if (filter == null || filter.accept(f))
result.add(f);
}
return result;
}
@Override
public Map<String, List<File>> getAllITunesPlaylists(FileFilter filter) throws IOException {
checkReady();
Map<String, List<File>> result = new HashMap<String, List<File>>();
List<String> scriptOutput = runScript("listAllPlaylists.scpt", null);
// This returns the playlist title on its own line, then the fq path of each track in the playlist on its own line, then a blank line... and repeat
String playlistName = null;
List<File> tracks = null;
for(String line : scriptOutput) {
if(playlistName == null) {
playlistName = line;
tracks = new ArrayList<File>();
continue;
}
if(line.trim().length() == 0) {
if(tracks.size() > 0)
result.put(playlistName, tracks);
playlistName = null;
continue;
}
File track = new File(line);
if(track.exists() && ((filter == null) || filter.accept(track)))
tracks.add(track);
}
return result;
}
@Override
public synchronized void syncPlaylist(User u, Playlist p) throws IOException {
checkReady();
createRobonoboFolder();
log.debug("Syncing with iTunes: user " + u.getEmail() + ", playlist '" + p.getTitle() + "'");
// Create a temp file with a list of the shared tracks in this playlist
File trackListFile = File.createTempFile("robotracklist", "tmp");
PrintWriter writer = new PrintWriter(trackListFile);
for (String streamId : p.getStreamIds()) {
SharedTrack sh = getRobonobo().getDbService().getShare(streamId);
if (sh != null) {
writer.println(sh.getFile().getAbsolutePath());
}
}
writer.close();
// This is much easier than the windows version, it's all done in
// applescript
List<String> scriptArgs = new ArrayList<String>();
scriptArgs.add(getFolderName(u));
scriptArgs.add(p.getTitle());
scriptArgs.add(trackListFile.getAbsolutePath());
runScript("syncPlaylist.scpt", scriptArgs);
trackListFile.delete();
}
private String getFolderName(User u) {
return u.getFriendlyName();
}
public String getName() {
return "Macintosh iTunes Service";
}
}