X-Git-Url: https://code.delx.au/notipod/blobdiff_plain/4df5b7a6f44e423bb38036187b7a1102865d1942..fd23beafb68339a9c4c5dd35e6c2d14fce0bb2ce:/notipod_gui.py diff --git a/notipod_gui.py b/notipod_gui.py index 4c175a9..52b4456 100644 --- a/notipod_gui.py +++ b/notipod_gui.py @@ -3,6 +3,10 @@ # 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 * @@ -19,6 +23,7 @@ class PlaylistModel(NSObject): self.root = [] self.playlists = {} self.outlineView.setDataSource_(self) + self.outlineView.setEnabled_(False) def setPlaylists(self, playlists): self.root = [] @@ -75,8 +80,7 @@ class FolderModel(NSObject): 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) @@ -89,11 +93,12 @@ class FolderModel(NSObject): 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) @@ -103,7 +108,7 @@ class FolderModel(NSObject): 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: @@ -132,10 +137,19 @@ class NotiPodController(NSObject): 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() @@ -145,52 +159,75 @@ class NotiPodController(NSObject): # 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) self.loadingIndicator.stopAnimation_(self) - finish() + 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.folders()[0] - playlists = [self.library.get_playlist_pid(pid) for pid in self.playlists()] + 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_tracks = [] - for playlist in playlists: - all_tracks.extend(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_tracks + files_to_copy=all_filenames, ) self.previewResult = "\n".join(gen) @@ -204,18 +241,42 @@ class NotiPodController(NSObject): self.previewWindow.center() self.previewWindow.makeKeyAndOrderFront_(self) - self.runGenerator(self.doPreviewThread, finish) + 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) @@ -225,47 +286,117 @@ class NotiPodController(NSObject): 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():