2 # Copyright 2009 James Bunton <jamesbunton@fastmail.fm>
3 # Licensed for distribution under the GPL version 2, check COPYING for details
9 from Foundation
import *
11 from PyObjCTools
import AppHelper
16 class PlaylistModel(NSObject
):
17 outlineView
= objc
.IBOutlet()
19 def awakeFromNib(self
):
22 self
.outlineView
.setDataSource_(self
)
24 def setPlaylists(self
, playlists
):
26 self
.playlists
= playlists
27 for playlist
in self
.playlists
:
28 if playlist
.parent
is None:
29 self
.root
.append(playlist
)
30 self
.outlineView
.reloadData()
31 self
.outlineView
.expandItem_expandChildren_(None, True)
33 def outlineView_child_ofItem_(self
, _
, childIndex
, playlist
):
35 return self
.root
[childIndex
]
37 return playlist
.children
[childIndex
]
39 def outlineView_isItemExpandable_(self
, _
, playlist
):
43 return len(playlist
.children
) > 0
45 def outlineView_numberOfChildrenOfItem_(self
, _
, playlist
):
49 return len(playlist
.children
)
51 def outlineView_objectValueForTableColumn_byItem_(self
, _
, col
, playlist
):
54 col
= col
.identifier()
57 selected
= NSApp
.delegate().playlists()
58 return playlist
.pid
in selected
60 return NSImage
.imageNamed_("playlist-" + playlist
.ptype
)
64 def outlineView_setObjectValue_forTableColumn_byItem_(self
, _
, v
, col
, playlist
):
67 col
= col
.identifier()
72 NSApp
.delegate().setPlaylist_selected_(playlist
.pid
, v
)
75 class FolderModel(NSObject
):
76 window
= objc
.IBOutlet()
77 folderPopup
= objc
.IBOutlet()
79 def awakeFromNib(self
):
80 folders
= NSApp
.delegate().folders()
81 self
.folderPopup
.addItemsWithTitles_(folders
)
83 self
.folderPopup
.selectItemAtIndex_(2)
89 def doSelectFolder_(self
, sender
):
90 currentIndex
= self
.folderPopup
.indexOfSelectedItem()
92 self
.lastIndex
= currentIndex
93 NSApp
.delegate().addFolder_(self
.folderPopup
.titleOfSelectedItem())
95 panel
= NSOpenPanel
.openPanel()
96 panel
.setCanChooseFiles_(False)
97 panel
.setCanChooseDirectories_(True)
98 panel
.setAllowsMultipleSelection_(False)
99 panel
.beginSheetForDirectory_file_types_modalForWindow_modalDelegate_didEndSelector_contextInfo_(
100 None, None, [], self
.window
, self
, self
.selectFolderEnd_returnCode_contextInfo_
, None)
102 @objc.signature("v@:@ii")
103 def selectFolderEnd_returnCode_contextInfo_(self
, panel
, ret
, _
):
104 if ret
== NSOKButton
:
105 assert len(panel
.filenames()) == 1
106 folder
= panel
.filenames()[0]
107 NSApp
.delegate().addFolder_(folder
)
108 self
.folderPopup
.insertItemWithTitle_atIndex_(folder
, 2)
109 self
.folderPopup
.selectItemAtIndex_(2)
111 self
.folderPopup
.selectItemAtIndex_(self
.lastIndex
)
114 class NotiPodController(NSObject
):
115 window
= objc
.IBOutlet()
117 loadingSheet
= objc
.IBOutlet()
118 loadingLabel
= objc
.IBOutlet()
119 loadingIndicator
= objc
.IBOutlet()
121 previewWindow
= objc
.IBOutlet()
122 previewText
= objc
.IBOutlet()
124 playlistModel
= objc
.IBOutlet()
125 folderModel
= objc
.IBOutlet()
128 def awakeFromNib(self
):
129 self
.runningGenerator
= False
130 self
.previewWindow
.setReleasedWhenClosed_(False)
133 def applicationWillFinishLaunching_(self
, _
):
136 def applicationDidFinishLaunching_(self
, _
):
137 self
.library
= libnotipod
.ITunesLibrary
.alloc().init()
139 self
.playlistModel
.setPlaylists(self
.library
.get_playlists())
142 self
.runGenerator(lambda: self
.library
.load_(None), finish
, fail
)
144 def applicationWillTerminate_(self
, _
):
145 self
.prefs().synchronize()
147 def applicationShouldTerminateAfterLastWindowClosed_(self
, _
):
152 def runGenerator(self
, func
, finish
, fail
):
153 assert not self
.runningGenerator
154 self
.runningGenerator
= True
155 self
.loadingIndicator
.startAnimation_(self
)
156 NSApp
.beginSheet_modalForWindow_modalDelegate_didEndSelector_contextInfo_(self
.loadingSheet
, self
.window
, None, None, None)
157 arg
= (func(), finish
, fail
)
158 self
.performSelectorInBackground_withObject_(self
.runGeneratorThread
, arg
)
160 def runGeneratorThread(self
, (gen
, finish
, fail
)):
161 pool
= NSAutoreleasePool
.alloc().init()
164 if not self
.runningGenerator
:
166 self
.loadingLabel
.performSelectorOnMainThread_withObject_waitUntilDone_(
167 self
.loadingLabel
.setStringValue_
, msg
, True)
169 NSRunAlertPanel("Error!", str(e
), "Ok", None, None)
171 self
.performSelectorOnMainThread_withObject_waitUntilDone_(
172 self
.stopGenerator
, finish
, True)
173 self
.runningGenerator
= False
175 def stopGenerator(self
, finish
):
176 self
.runningGenerator
= False
177 NSApp
.endSheet_(self
.loadingSheet
)
178 self
.loadingSheet
.orderOut_(self
)
179 self
.loadingIndicator
.stopAnimation_(self
)
184 def doCancel_(self
, sender
):
185 self
.runningGenerator
= False
187 def doPreviewThread(self
):
188 yield "Calculating changes..."
190 folder
= self
.folders()[0]
191 playlists
= [self
.library
.get_playlist_pid(pid
) for pid
in self
.playlists()]
194 for playlist
in playlists
:
195 all_tracks
.extend(playlist
.tracks
)
197 gen
= libnotipod
.sync(
199 source
=self
.library
.folder
,
201 files_to_copy
=all_tracks
203 self
.previewResult
= "\n".join(gen
)
206 def doPreview_(self
, sender
):
207 self
.previewResult
= ""
208 self
.previewWindow
.orderOut_(self
)
211 self
.previewText
.textStorage().mutableString().setString_(self
.previewResult
)
212 self
.previewWindow
.center()
213 self
.previewWindow
.makeKeyAndOrderFront_(self
)
215 self
.runGenerator(self
.doPreviewThread
, finish
, None)
218 def doSync_(self
, sender
):
219 folder
= self
.folders()[0]
220 playlists
= [self
.library
.get_playlist_pid(pid
) for pid
in self
.playlists()]
222 if not os
.path
.isdir(folder
.encode("utf-8")):
223 NSRunAlertPanel("Error!", "Destination " + folder
+ " does not exist, try mounting it first?", "Ok", None, None)
227 for playlist
in playlists
:
228 all_tracks
.extend(playlist
.tracks
)
229 libnotipod
.export_m3u(dry_run
=False, dest
=folder
, path_prefix
="",
230 playlist_name
=playlist
.name
, files
=playlist
.tracks
)
233 NSRunAlertPanel("Complete!", "Synchronisation is complete", "Ok", None, None)
238 source
=self
.library
.folder
,
240 files_to_copy
=all_tracks
251 return NSUserDefaults
.standardUserDefaults()
253 def _getArray(self
, key
):
254 res
= self
.prefs().stringArrayForKey_(key
)
255 return list(res
) if res
else []
257 def _saveArray(self
, key
, array
):
258 self
.prefs().setObject_forKey_(array
, key
)
261 return self
._getArray
("playlists")
264 return self
._getArray
("folders")
266 def addFolder_(self
, folder
):
267 folders
= self
.folders()
268 while folder
in folders
:
269 folders
.remove(folder
)
270 folders
.insert(0, folder
)
271 folders
= folders
[:10]
272 self
._saveArray
("folders", folders
)
274 def setPlaylist_selected_(self
, playlist
, selected
):
275 playlists
= self
.playlists()
277 playlists
.append(playlist
)
279 playlists
.remove(playlist
)
280 playlists
= list(set(playlists
))
281 self
._saveArray
("playlists", list(set(playlists
)))
285 ### logging.basicConfig(format="%(levelname)s: %(message)s")
286 ### logging.getLogger().setLevel(logging.DEBUG)
287 AppHelper
.runEventLoop()
289 if __name__
== "__main__":