]> code.delx.au - notipod/blob - notipod_gui.py
Don't sync to folders that don't exist
[notipod] / notipod_gui.py
1 #!/usr/bin/env python
2 # Copyright 2009 James Bunton <jamesbunton@fastmail.fm>
3 # Licensed for distribution under the GPL version 2, check COPYING for details
4
5 import logging
6
7 import objc
8 from Foundation import *
9 from AppKit import *
10 from PyObjCTools import AppHelper
11
12 import libnotipod
13
14
15 class PlaylistModel(NSObject):
16 outlineView = objc.IBOutlet()
17
18 def awakeFromNib(self):
19 self.root = []
20 self.playlists = {}
21 self.outlineView.setDataSource_(self)
22
23 def setPlaylists(self, playlists):
24 self.root = []
25 self.playlists = playlists
26 for playlist in self.playlists:
27 if playlist.parent is None:
28 self.root.append(playlist)
29 self.outlineView.reloadData()
30 self.outlineView.expandItem_expandChildren_(None, True)
31
32 def outlineView_child_ofItem_(self, _, childIndex, playlist):
33 if playlist == None:
34 return self.root[childIndex]
35 else:
36 return playlist.children[childIndex]
37
38 def outlineView_isItemExpandable_(self, _, playlist):
39 if playlist == None:
40 return True
41 else:
42 return len(playlist.children) > 0
43
44 def outlineView_numberOfChildrenOfItem_(self, _, playlist):
45 if playlist == None:
46 return len(self.root)
47 else:
48 return len(playlist.children)
49
50 def outlineView_objectValueForTableColumn_byItem_(self, _, col, playlist):
51 if not col:
52 return
53 col = col.identifier()
54
55 if col == "selected":
56 selected = NSApp.delegate().playlists()
57 return playlist.pid in selected
58 if col == "icon":
59 return NSImage.imageNamed_("playlist-" + playlist.ptype)
60 if col == "playlist":
61 return playlist.name
62
63 def outlineView_setObjectValue_forTableColumn_byItem_(self, _, v, col, playlist):
64 if not col:
65 return
66 col = col.identifier()
67
68
69 if col != "selected":
70 return
71 NSApp.delegate().setPlaylist_selected_(playlist.pid, v)
72
73
74 class FolderModel(NSObject):
75 window = objc.IBOutlet()
76 folderPopup = objc.IBOutlet()
77
78 def awakeFromNib(self):
79 folders = NSApp.delegate().folders()
80 self.folderPopup.addItemsWithTitles_(folders)
81 if len(folders) > 0:
82 self.folderPopup.selectItemAtIndex_(2)
83 self.lastIndex = 2
84 else:
85 self.lastIndex = 0
86
87 @objc.IBAction
88 def doSelectFolder_(self, sender):
89 currentIndex = self.folderPopup.indexOfSelectedItem()
90 if currentIndex >= 2:
91 self.lastIndex = currentIndex
92 NSApp.delegate().addFolder_(self.folderPopup.titleOfSelectedItem())
93 return
94 panel = NSOpenPanel.openPanel()
95 panel.setCanChooseFiles_(False)
96 panel.setCanChooseDirectories_(True)
97 panel.setAllowsMultipleSelection_(False)
98 panel.beginSheetForDirectory_file_types_modalForWindow_modalDelegate_didEndSelector_contextInfo_(
99 None, None, [], self.window, self, self.selectFolderEnd_returnCode_contextInfo_, None)
100
101 @objc.signature("v@:@ii")
102 def selectFolderEnd_returnCode_contextInfo_(self, panel, ret, _):
103 if ret == NSOKButton:
104 assert len(panel.filenames()) == 1
105 folder = panel.filenames()[0]
106 NSApp.delegate().addFolder_(folder)
107 self.folderPopup.insertItemWithTitle_atIndex_(folder, 2)
108 self.folderPopup.selectItemAtIndex_(2)
109 else:
110 self.folderPopup.selectItemAtIndex_(self.lastIndex)
111
112
113 class NotiPodController(NSObject):
114 window = objc.IBOutlet()
115
116 loadingSheet = objc.IBOutlet()
117 loadingLabel = objc.IBOutlet()
118 loadingIndicator = objc.IBOutlet()
119
120 previewWindow = objc.IBOutlet()
121 previewText = objc.IBOutlet()
122
123 playlistModel = objc.IBOutlet()
124 folderModel = objc.IBOutlet()
125
126
127 def awakeFromNib(self):
128 self.runningGenerator = False
129
130 # Delegate methods
131 def applicationWillFinishLaunching_(self, _):
132 pass
133
134 def applicationDidFinishLaunching_(self, _):
135 self.library = libnotipod.ITunesLibrary.alloc().init()
136 def finish():
137 self.playlistModel.setPlaylists(self.library.get_playlists())
138 self.runGenerator(lambda: self.library.load_(None), finish)
139
140 def applicationWillTerminate_(self, _):
141 self.prefs().synchronize()
142
143 def applicationShouldTerminateAfterLastWindowClosed_(self, _):
144 return True
145
146
147 # Utility methods
148 def runGenerator(self, func, finish):
149 assert not self.runningGenerator
150 self.runningGenerator = True
151 self.loadingIndicator.startAnimation_(self)
152 NSApp.beginSheet_modalForWindow_modalDelegate_didEndSelector_contextInfo_(self.loadingSheet, self.window, None, None, None)
153 arg = (func(), finish)
154 self.performSelectorInBackground_withObject_(self.runGeneratorThread, arg)
155
156 def runGeneratorThread(self, (gen, finish)):
157 pool = NSAutoreleasePool.alloc().init()
158 for msg in gen:
159 if not self.runningGenerator:
160 break
161 self.loadingLabel.performSelectorOnMainThread_withObject_waitUntilDone_(
162 self.loadingLabel.setStringValue_, msg, True)
163 self.performSelectorOnMainThread_withObject_waitUntilDone_(
164 self.stopGenerator, finish, True)
165 self.runningGenerator = False
166 del pool
167
168 def stopGenerator(self, finish):
169 self.runningGenerator = False
170 NSApp.endSheet_(self.loadingSheet)
171 self.loadingSheet.orderOut_(self)
172 self.loadingIndicator.stopAnimation_(self)
173 finish()
174
175 @objc.IBAction
176 def doCancel_(self, sender):
177 self.runningGenerator = False
178
179 def doPreviewThread(self):
180 yield "Calculating changes..."
181
182 folder = self.folders()[0]
183 playlists = [self.library.get_playlist_pid(pid) for pid in self.playlists()]
184
185 all_tracks = []
186 for playlist in playlists:
187 all_tracks.extend(playlist.tracks)
188
189 gen = libnotipod.sync(
190 dry_run=True,
191 source=self.library.folder,
192 dest=folder,
193 files_to_copy=all_tracks
194 )
195 self.previewResult = "\n".join(gen)
196
197 @objc.IBAction
198 def doPreview_(self, sender):
199 self.previewResult = ""
200 self.previewWindow.orderOut_(self)
201
202 def finish():
203 self.previewText.textStorage().mutableString().setString_(self.previewResult)
204 self.previewWindow.center()
205 self.previewWindow.makeKeyAndOrderFront_(self)
206
207 self.runGenerator(self.doPreviewThread, finish)
208
209 @objc.IBAction
210 def doSync_(self, sender):
211 folder = self.folders()[0]
212 playlists = [self.library.get_playlist_pid(pid) for pid in self.playlists()]
213
214 all_tracks = []
215 for playlist in playlists:
216 all_tracks.extend(playlist.tracks)
217 libnotipod.export_m3u(dry_run=False, dest=folder, path_prefix="",
218 playlist_name=playlist.name, files=playlist.tracks)
219
220 def finish():
221 NSRunAlertPanel("Complete!", "Synchronisation is complete", "Ok", None, None)
222 self.runGenerator(
223 lambda:
224 libnotipod.sync(
225 dry_run=False,
226 source=self.library.folder,
227 dest=folder,
228 files_to_copy=all_tracks
229 )
230 ,
231 finish
232 )
233
234
235 # Public accessors
236
237 def prefs(self):
238 return NSUserDefaults.standardUserDefaults()
239
240 def _getArray(self, key):
241 res = self.prefs().stringArrayForKey_(key)
242 return list(res) if res else []
243
244 def _saveArray(self, key, array):
245 self.prefs().setObject_forKey_(array, key)
246
247 def playlists(self):
248 return self._getArray("playlists")
249
250 def folders(self):
251 return self._getArray("folders")
252
253 def addFolder_(self, folder):
254 folders = self.folders()
255 while folder in folders:
256 folders.remove(folder)
257 folders.insert(0, folder)
258 folders = folders[:10]
259 self._saveArray("folders", folders)
260
261 def setPlaylist_selected_(self, playlist, selected):
262 playlists = self.playlists()
263 if selected:
264 playlists.append(playlist)
265 else:
266 playlists.remove(playlist)
267 playlists = list(set(playlists))
268 self._saveArray("playlists", list(set(playlists)))
269
270
271 def main():
272 ### logging.basicConfig(format="%(levelname)s: %(message)s")
273 ### logging.getLogger().setLevel(logging.DEBUG)
274 AppHelper.runEventLoop()
275
276 if __name__ == "__main__":
277 main()
278