2 # Copyright 2009 James Bunton <jamesbunton@fastmail.fm>
3 # Licensed for distribution under the GPL version 2, check COPYING for details
11 from Foundation
import *
13 from PyObjCTools
import AppHelper
18 class PlaylistModel(NSObject
):
19 outlineView
= objc
.IBOutlet()
21 def awakeFromNib(self
):
24 self
.outlineView
.setDataSource_(self
)
26 def setPlaylists(self
, playlists
):
28 self
.playlists
= playlists
29 for playlist
in self
.playlists
:
30 if playlist
.parent
is None:
31 self
.root
.append(playlist
)
32 self
.outlineView
.reloadData()
33 self
.outlineView
.expandItem_expandChildren_(None, True)
35 def outlineView_child_ofItem_(self
, _
, childIndex
, playlist
):
37 return self
.root
[childIndex
]
39 return playlist
.children
[childIndex
]
41 def outlineView_isItemExpandable_(self
, _
, playlist
):
45 return len(playlist
.children
) > 0
47 def outlineView_numberOfChildrenOfItem_(self
, _
, playlist
):
51 return len(playlist
.children
)
53 def outlineView_objectValueForTableColumn_byItem_(self
, _
, col
, playlist
):
56 col
= col
.identifier()
59 selected
= NSApp
.delegate().playlists()
60 return playlist
.pid
in selected
62 return NSImage
.imageNamed_("playlist-" + playlist
.ptype
)
66 def outlineView_setObjectValue_forTableColumn_byItem_(self
, _
, v
, col
, playlist
):
69 col
= col
.identifier()
74 NSApp
.delegate().setPlaylist_selected_(playlist
.pid
, v
)
77 class FolderModel(NSObject
):
78 window
= objc
.IBOutlet()
79 folderPopup
= objc
.IBOutlet()
81 def awakeFromNib(self
):
82 folders
= NSApp
.delegate().folders()
83 self
.folderPopup
.addItemsWithTitles_(folders
)
85 self
.folderPopup
.selectItemAtIndex_(2)
91 def doSelectFolder_(self
, sender
):
92 currentIndex
= self
.folderPopup
.indexOfSelectedItem()
94 self
.lastIndex
= currentIndex
95 NSApp
.delegate().addFolder_(self
.folderPopup
.titleOfSelectedItem())
97 panel
= NSOpenPanel
.openPanel()
98 panel
.setCanChooseFiles_(False)
99 panel
.setCanChooseDirectories_(True)
100 panel
.setCanCreateDirectories_(True)
101 panel
.setAllowsMultipleSelection_(False)
102 panel
.beginSheetForDirectory_file_types_modalForWindow_modalDelegate_didEndSelector_contextInfo_(
103 None, None, [], self
.window
, self
, self
.selectFolderEnd_returnCode_contextInfo_
, None)
105 @objc.signature("v@:@ii")
106 def selectFolderEnd_returnCode_contextInfo_(self
, panel
, ret
, _
):
107 if ret
== NSOKButton
:
108 assert len(panel
.filenames()) == 1
109 folder
= panel
.filenames()[0]
110 NSApp
.delegate().addFolder_(folder
)
111 self
.folderPopup
.insertItemWithTitle_atIndex_(folder
, 2)
112 self
.folderPopup
.selectItemAtIndex_(2)
114 self
.folderPopup
.selectItemAtIndex_(self
.lastIndex
)
117 class NotiPodController(NSObject
):
118 window
= objc
.IBOutlet()
120 loadingSheet
= objc
.IBOutlet()
121 loadingLabel
= objc
.IBOutlet()
122 loadingIndicator
= objc
.IBOutlet()
124 previewWindow
= objc
.IBOutlet()
125 previewText
= objc
.IBOutlet()
127 playlistModel
= objc
.IBOutlet()
128 folderModel
= objc
.IBOutlet()
131 def awakeFromNib(self
):
132 self
.runningGenerator
= False
135 def applicationWillFinishLaunching_(self
, _
):
138 def applicationDidFinishLaunching_(self
, _
):
139 self
.library
= libnotipod
.ITunesLibrary
.alloc().init()
141 self
.playlistModel
.setPlaylists(self
.library
.get_playlists())
144 self
.runGenerator(lambda: self
.library
.load_(None), finish
, fail
)
146 def applicationWillTerminate_(self
, _
):
147 self
.prefs().synchronize()
149 def applicationShouldTerminateAfterLastWindowClosed_(self
, _
):
154 def runGenerator(self
, func
, finish
, fail
):
155 assert not self
.runningGenerator
156 self
.runningGenerator
= True
157 self
.loadingIndicator
.startAnimation_(self
)
158 NSApp
.beginSheet_modalForWindow_modalDelegate_didEndSelector_contextInfo_(self
.loadingSheet
, self
.window
, None, None, None)
159 arg
= (func(), finish
, fail
)
160 self
.performSelectorInBackground_withObject_(self
.runGeneratorThread
, arg
)
162 def runGeneratorThread(self
, (gen
, finish
, fail
)):
163 pool
= NSAutoreleasePool
.alloc().init()
166 if not self
.runningGenerator
:
168 self
.loadingLabel
.performSelectorOnMainThread_withObject_waitUntilDone_(
169 self
.loadingLabel
.setStringValue_
, msg
, True)
171 NSRunAlertPanel("Error!", str(e
), "Ok", None, None)
172 traceback
.print_exc()
174 self
.performSelectorOnMainThread_withObject_waitUntilDone_(
175 self
.stopGenerator
, finish
, True)
176 self
.runningGenerator
= False
178 def stopGenerator(self
, finish
):
179 self
.runningGenerator
= False
180 NSApp
.endSheet_(self
.loadingSheet
)
181 self
.loadingSheet
.orderOut_(self
)
182 self
.loadingIndicator
.stopAnimation_(self
)
187 def doCancel_(self
, sender
):
188 self
.runningGenerator
= False
190 def getDestFolder(self
):
191 folders
= self
.folders()
193 NSRunAlertPanel("Error!", "You must choose a folder first!", "Ok", None, None)
196 if not os
.path
.isdir(folder
.encode("utf-8")):
197 NSRunAlertPanel("Error!", "Destination " + folder
+ " does not exist, try mounting it first?", "Ok", None, None)
201 def doPreviewThread(self
):
202 yield "Calculating changes..."
204 folder
= self
.getDestFolder()
209 for playlist_id
in self
.playlists():
210 playlist
= self
.library
.get_playlist_pid(playlist_id
)
211 if playlist
is not None:
212 all_tracks
.update(set(playlist
.tracks
))
215 for trackID
in all_tracks
:
216 all_filenames
.append(self
.library
.get_track_filename(trackID
))
218 gen
= libnotipod
.sync(
220 source
=self
.library
.folder
,
222 files_to_copy
=all_filenames
,
224 self
.previewResult
= "\n".join(gen
)
227 def doPreview_(self
, sender
):
228 self
.previewResult
= ""
229 self
.previewWindow
.orderOut_(self
)
232 self
.previewText
.textStorage().mutableString().setString_(self
.previewResult
)
233 self
.previewWindow
.center()
234 self
.previewWindow
.makeKeyAndOrderFront_(self
)
236 self
.runGenerator(self
.doPreviewThread
, finish
, None)
239 def doSync_(self
, sender
):
240 folder
= self
.getDestFolder()
245 orig_playlists
= set(self
.playlists())
246 all_playlists
= orig_playlists
.copy()
247 for playlist_id
in all_playlists
:
248 playlist
= self
.library
.get_playlist_pid(playlist_id
)
250 print "Forgetting unknown playlist:", playlist_id
251 self
.setPlaylist_selected_(playlist_id
, False)
253 all_tracks
.update(set(playlist
.tracks
))
256 for trackID
in all_tracks
:
257 all_filenames
.append(self
.library
.get_track_filename(trackID
))
258 all_playlists
.update(self
.library
.get_track_playlists(trackID
))
260 for playlist_id
in all_playlists
:
261 playlist
= self
.library
.get_playlist_pid(playlist_id
)
265 for trackID
in playlist
.tracks
:
266 if trackID
in all_tracks
:
267 tracks
.append(self
.library
.get_track_filename(trackID
))
268 if playlist_id
not in orig_playlists
and len(tracks
) < 10:
270 libnotipod
.export_m3u(dry_run
=False, dest
=folder
, path_prefix
="",
271 playlist_name
=playlist
.name
, files
=tracks
)
274 NSRunAlertPanel("Complete!", "Synchronisation is complete", "Ok", None, None)
279 source
=self
.library
.folder
,
281 files_to_copy
=all_filenames
,
292 return NSUserDefaults
.standardUserDefaults()
294 def _getArray(self
, key
):
295 res
= self
.prefs().stringArrayForKey_(key
)
296 return list(res
) if res
else []
298 def _saveArray(self
, key
, array
):
299 self
.prefs().setObject_forKey_(array
, key
)
302 return self
._getArray
("playlists")
305 return self
._getArray
("folders")
307 def addFolder_(self
, folder
):
308 folders
= self
.folders()
309 while folder
in folders
:
310 folders
.remove(folder
)
311 folders
.insert(0, folder
)
312 folders
= folders
[:10]
313 self
._saveArray
("folders", folders
)
315 def setPlaylist_selected_(self
, playlist
, selected
):
316 playlists
= self
.playlists()
318 playlists
.append(playlist
)
320 playlists
.remove(playlist
)
321 playlists
= list(set(playlists
))
322 self
._saveArray
("playlists", list(set(playlists
)))
326 ### logging.basicConfig(format="%(levelname)s: %(message)s")
327 ### logging.getLogger().setLevel(logging.DEBUG)
328 AppHelper
.runEventLoop()
330 if __name__
== "__main__":