# Licensed for distribution under the GPL version 2, check COPYING for details
import logging
+import os
+import sys
+import traceback
+import uuid
import objc
from Foundation import *
self.root = []
self.playlists = {}
self.outlineView.setDataSource_(self)
+ self.outlineView.setEnabled_(False)
def setPlaylists(self, playlists):
self.root = []
return len(playlist.children)
def outlineView_objectValueForTableColumn_byItem_(self, _, col, playlist):
- col = col.identifier() if col else "playlist"
+ if not col:
+ return
+ col = col.identifier()
if col == "selected":
selected = NSApp.delegate().playlists()
return playlist.pid in selected
- if col == None or col == "playlist":
+ if col == "icon":
+ return NSImage.imageNamed_("playlist-" + playlist.ptype)
+ if col == "playlist":
return playlist.name
def outlineView_setObjectValue_forTableColumn_byItem_(self, _, v, col, playlist):
- col = col.identifier() if col else "playlist"
+ if not col:
+ return
+ col = col.identifier()
+
if col != "selected":
return
window = objc.IBOutlet()
folderPopup = objc.IBOutlet()
- def awakeFromNib(self):
- folders = NSApp.delegate().folders()
+ def loadFolders_(self, folders):
self.folderPopup.addItemsWithTitles_(folders)
if len(folders) > 0:
self.folderPopup.selectItemAtIndex_(2)
currentIndex = self.folderPopup.indexOfSelectedItem()
if currentIndex >= 2:
self.lastIndex = currentIndex
- NSApp.delegate().addFolder_(self.folderPopup.titleOfSelectedItem())
+ NSApp.delegate().setFolder_(self.folderPopup.titleOfSelectedItem())
return
panel = NSOpenPanel.openPanel()
panel.setCanChooseFiles_(False)
panel.setCanChooseDirectories_(True)
+ panel.setCanCreateDirectories_(True)
panel.setAllowsMultipleSelection_(False)
panel.beginSheetForDirectory_file_types_modalForWindow_modalDelegate_didEndSelector_contextInfo_(
None, None, [], self.window, self, self.selectFolderEnd_returnCode_contextInfo_, None)
if ret == NSOKButton:
assert len(panel.filenames()) == 1
folder = panel.filenames()[0]
- NSApp.delegate().addFolder_(folder)
+ NSApp.delegate().setFolder_(folder)
self.folderPopup.insertItemWithTitle_atIndex_(folder, 2)
self.folderPopup.selectItemAtIndex_(2)
else:
class NotiPodController(NSObject):
window = objc.IBOutlet()
- playlistModel = objc.IBOutlet()
- folderModel = objc.IBOutlet()
+
loadingSheet = objc.IBOutlet()
loadingLabel = objc.IBOutlet()
+ loadingIndicator = objc.IBOutlet()
+
+ previewWindow = objc.IBOutlet()
+ previewText = objc.IBOutlet()
+
+ playlistModel = objc.IBOutlet()
+ folderModel = objc.IBOutlet()
+
def awakeFromNib(self):
self.runningGenerator = False
pass
def applicationDidFinishLaunching_(self, _):
+ self._loadPrefs()
+
+ folders = []
+ for target in self.targets:
+ folders.append(target["folder"])
+ self.folderModel.loadFolders_(folders)
+
self.library = libnotipod.ITunesLibrary.alloc().init()
def finish():
self.playlistModel.setPlaylists(self.library.get_playlists())
- self.runGenerator(lambda: self.library.load_(None), finish)
+ def fail():
+ sys.exit(0)
+ self.runGenerator(lambda: self.library.load_(None), finish, fail)
def applicationWillTerminate_(self, _):
self.prefs().synchronize()
# Utility methods
- def runGenerator(self, func, finish):
+ def runGenerator(self, func, finish, fail):
assert not self.runningGenerator
self.runningGenerator = True
+ self.loadingIndicator.startAnimation_(self)
NSApp.beginSheet_modalForWindow_modalDelegate_didEndSelector_contextInfo_(self.loadingSheet, self.window, None, None, None)
- arg = (func(), finish)
+ arg = (func(), finish, fail)
self.performSelectorInBackground_withObject_(self.runGeneratorThread, arg)
- def runGeneratorThread(self, (gen, finish)):
+ def runGeneratorThread(self, (gen, finish, fail)):
pool = NSAutoreleasePool.alloc().init()
- for msg in gen:
- if not self.runningGenerator:
- break
- self.loadingLabel.performSelectorOnMainThread_withObject_waitUntilDone_(
- self.loadingLabel.setStringValue_, msg, True)
+ try:
+ for msg in gen:
+ if not self.runningGenerator:
+ break
+ self.loadingLabel.performSelectorOnMainThread_withObject_waitUntilDone_(
+ self.loadingLabel.setStringValue_, msg, True)
+ except Exception, e:
+ NSRunAlertPanel("Error!", str(e), "Ok", None, None)
+ traceback.print_exc()
+ finish = fail
self.performSelectorOnMainThread_withObject_waitUntilDone_(
self.stopGenerator, finish, True)
self.runningGenerator = False
- del pool
-
+
def stopGenerator(self, finish):
self.runningGenerator = False
NSApp.endSheet_(self.loadingSheet)
self.loadingSheet.orderOut_(self)
- finish()
+ self.loadingIndicator.stopAnimation_(self)
+ if finish:
+ finish()
@objc.IBAction
def doCancel_(self, sender):
self.runningGenerator = False
+ def getDestFolder(self):
+ target = self.getCurrentTarget()
+ if not target:
+ NSRunAlertPanel("Error!", "You must choose a folder first!", "Ok", None, None)
+ return
+ folder = target["folder"]
+ if not os.path.isdir(folder.encode("utf-8")):
+ NSRunAlertPanel("Error!", "Destination " + folder + " does not exist, try mounting it first?", "Ok", None, None)
+ return
+ return folder
+
+ def doPreviewThread(self):
+ yield "Calculating changes..."
+
+ folder = self.getDestFolder()
+ if not folder:
+ return
+
+ all_tracks = set()
+ for playlist_id in self.playlists():
+ playlist = self.library.get_playlist_pid(playlist_id)
+ if playlist is not None:
+ all_tracks.update(set(playlist.tracks))
+
+ all_filenames = []
+ for trackID in all_tracks:
+ all_filenames.append(self.library.get_track_filename(trackID))
+
+ gen = libnotipod.sync(
+ dry_run=True,
+ source=self.library.folder,
+ dest=folder,
+ files_to_copy=all_filenames,
+ )
+ self.previewResult = "\n".join(gen)
+
+ @objc.IBAction
+ def doPreview_(self, sender):
+ self.previewResult = ""
+ self.previewWindow.orderOut_(self)
+
+ def finish():
+ self.previewText.textStorage().mutableString().setString_(self.previewResult)
+ self.previewWindow.center()
+ self.previewWindow.makeKeyAndOrderFront_(self)
+
+ self.runGenerator(self.doPreviewThread, finish, None)
+
@objc.IBAction
def doSync_(self, sender):
- folder = self.folders()[0]
- playlists = [self.library.get_playlist_pid(pid) for pid in self.playlists()]
+ folder = self.getDestFolder()
+ if not folder:
+ return
- all_tracks = []
- for playlist in playlists:
- all_tracks.extend(playlist.tracks)
+ all_tracks = set()
+ orig_playlists = set(self.playlists())
+ all_playlists = orig_playlists.copy()
+ for playlist_id in all_playlists:
+ playlist = self.library.get_playlist_pid(playlist_id)
+ if playlist is None:
+ print "Forgetting unknown playlist:", playlist_id
+ self.setPlaylist_selected_(playlist_id, False)
+ continue
+ all_tracks.update(set(playlist.tracks))
+
+ all_filenames = []
+ for trackID in all_tracks:
+ all_filenames.append(self.library.get_track_filename(trackID))
+ all_playlists.update(self.library.get_track_playlists(trackID))
+
+ for playlist_id in all_playlists:
+ playlist = self.library.get_playlist_pid(playlist_id)
+ if playlist is None:
+ continue
+ tracks = []
+ for trackID in playlist.tracks:
+ if trackID in all_tracks:
+ tracks.append(self.library.get_track_filename(trackID))
+ if playlist_id not in orig_playlists and len(tracks) < 10:
+ continue
libnotipod.export_m3u(dry_run=False, dest=folder, path_prefix="",
- playlist_name=playlist.name, files=playlist.tracks)
+ playlist_name=playlist.name, files=tracks)
def finish():
NSRunAlertPanel("Complete!", "Synchronisation is complete", "Ok", None, None)
dry_run=False,
source=self.library.folder,
dest=folder,
- files_to_copy=all_tracks
+ files_to_copy=all_filenames,
)
,
- finish
+ finish,
+ None
)
- # Public accessors
+ # Preferences
def prefs(self):
return NSUserDefaults.standardUserDefaults()
- def _getArray(self, key):
- res = self.prefs().stringArrayForKey_(key)
- return list(res) if res else []
+ def _migratePrefs(self):
+ p = self.prefs()
+
+ playlists = p.stringArrayForKey_("playlists")
+ if playlists is not None:
+ p.removeObjectForKey_("playlists")
+ else:
+ playlists = []
- def _saveArray(self, key, array):
- self.prefs().setObject_forKey_(array, key)
+ folders = p.stringArrayForKey_("folders")
+ if not folders:
+ return
+ p.removeObjectForKey_("folders")
+
+ first = True
+ for f in folders:
+ target = {}
+ target["folder"] = f
+ target["playlists"] = list(playlists)
+ target["uuid"] = uuid.uuid1().get_hex()
+ if first:
+ first = False
+ self.setCurrentTarget_(target["uuid"])
+ self.targets.addObject_(target)
+
+ self._savePrefs()
+
+ def _loadPrefs(self):
+ p = self.prefs()
+
+ self.current_target = None
+ self.setCurrentTarget_(p.stringForKey_("current_target"))
+
+ self.targets = self.prefs().arrayForKey_("targets")
+ if self.targets is None:
+ self.targets = NSMutableArray.array()
+ else:
+ self.targets = NSMutableArray.arrayWithArray_(self.targets)
+
+ if self.getCurrentTarget() is None:
+ self._migratePrefs()
+
+ def _savePrefs(self):
+ p = self.prefs()
+ p.setObject_forKey_(self.current_target, "current_target")
+ p.setObject_forKey_(self.targets, "targets")
+ p.synchronize()
+
+ def getCurrentTarget(self):
+ for target in self.targets:
+ if target["uuid"] == self.current_target:
+ return target
+ return None
+
+ def setCurrentTarget_(self, target_uuid):
+ old_uuid = self.current_target
+ self.current_target = target_uuid
+ if old_uuid is None and target_uuid is not None:
+ self.playlistModel.outlineView.setEnabled_(True)
+ if old_uuid != target_uuid:
+ self.playlistModel.outlineView.reloadItem_reloadChildren_(None, True)
def playlists(self):
- return self._getArray("playlists")
+ target = self.getCurrentTarget()
+ if not target:
+ return []
+ return list(target["playlists"])
+
+ def setFolder_(self, folder):
+ for i, target in enumerate(self.targets):
+ if target["folder"] == folder:
+ self.targets.removeObjectAtIndex_(i)
+ self.targets.insertObject_atIndex_(target, 0)
+ break
+ else:
+ target = {}
+ target["folder"] = folder
+ target["playlists"] = self.playlists()
+ target["uuid"] = uuid.uuid1().get_hex()
+ self.targets.insertObject_atIndex_(target, 0)
- def folders(self):
- return self._getArray("folders")
+ self.setCurrentTarget_(target["uuid"])
- def addFolder_(self, folder):
- folders = self.folders()
- while folder in folders:
- folders.remove(folder)
- folders.insert(0, folder)
- folders = folders[:10]
- self._saveArray("folders", folders)
+ self._savePrefs()
def setPlaylist_selected_(self, playlist, selected):
- playlists = self.playlists()
+ target = self.getCurrentTarget()
+ if not target:
+ raise AssertionError("No target selected when editing playlists")
+
+ playlists = target["playlists"]
if selected:
playlists.append(playlist)
else:
playlists.remove(playlist)
- playlists = list(set(playlists))
- self._saveArray("playlists", list(set(playlists)))
+ target["playlists"] = list(set(playlists))
+
+ self._savePrefs()
def main():