1 """
2 Provides a number of dataproviders which are associated with
3 removable devices such as USB keys.
4
5 It also includes classes specific to the ipod.
6 This file is not dynamically loaded at runtime in the same
7 way as the other dataproviders as it needs to be loaded all the time in
8 order to listen to HAL events
9
10 Copyright: John Stowers, 2006
11 License: GPLv2
12 """
13 import os
14 import pickle
15 import logging
16 log = logging.getLogger("modules.iPod")
17
18 import conduit
19 import conduit.dataproviders.DataProvider as DataProvider
20 import conduit.dataproviders.DataProviderCategory as DataProviderCategory
21 import conduit.dataproviders.VolumeFactory as VolumeFactory
22 import conduit.utils as Utils
23 import conduit.datatypes.Note as Note
24 import conduit.datatypes.Contact as Contact
25 import conduit.datatypes.Event as Event
26 import conduit.datatypes.File as File
27
28 MODULES = {
29 "iPodFactory" : { "type": "dataprovider-factory" }
30 }
31
32 try:
33 import gpod
34 LIBGPOD_PHOTOS = gpod.version_info >= (0,6,0)
35 log.info("Module Information: %s" % Utils.get_module_information(gpod, 'version_info'))
36 except ImportError:
37 LIBGPOD_PHOTOS = False
38 log.info("iPod photo support disabled")
39
41 for i in range(1, 10000):
42 filename = prefix + str(i) + postfix
43 uri = os.path.join(base_uri, filename)
44 f = File.File(uri)
45 if not f.exists():
46 break
47
48 temp = Utils.new_tempfile(txt)
49 temp.transfer(uri, True)
50 temp.set_UID(filename)
51 return temp.get_rid()
52
55 if props.has_key("info.parent") and props.has_key("info.parent")!="":
56 prop2 = self._get_properties(props["info.parent"])
57 if prop2.has_key("storage.model") and prop2["storage.model"]=="iPod":
58 return True
59 return False
60
66
79
80
87
89 DataProvider.TwoWay.refresh(self)
90 self.objects = []
91
92
93 if not os.path.exists(self.dataDir):
94 os.mkdir(self.dataDir)
95
96
97
98 for f in os.listdir(self.dataDir):
99 fullpath = os.path.join(self.dataDir, f)
100 if os.path.isfile(fullpath):
101 self.objects.append(f)
102
106
111
112 - def finish(self, aborted, error, conflict):
115
118
120 """
121 Returns the name of a non-existant file on the
122 ipod within directory
123
124 @param directory: Name of the directory within the device root to make
125 the random file in
126 """
127 done = False
128 while not done:
129 f = os.path.join(self.mountPoint,directory,Utils.random_string())
130 if not os.path.exists(f):
131 done = True
132 return f
133
135 """
136 Stores Notes on the iPod.
137 Rather than requiring a perfect transform to and from notes to the
138 ipod note format I also store the original note data in a
139 .conduit directory in the root of the iPod.
140
141 Notes are saved as title.txt and a copy of the raw note is saved as
142 title.note
143
144 LUID is the note title
145 """
146
147 _name_ = "Notes"
148 _description_ = "Sync your iPod notes"
149 _module_type_ = "twoway"
150 _in_type_ = "note"
151 _out_type_ = "note"
152 _icon_ = "tomboy"
153
154
155
156 ENCODING_DECLARATION = '<?xml encoding="utf-8"?>'
157
159 IPodBase.__init__(self, *args)
160
161 self.dataDir = os.path.join(self.mountPoint, 'Notes')
162 self.objects = []
163
165 shadowDir = os.path.join(self.mountPoint, '.conduit')
166 if not os.path.exists(shadowDir):
167 os.mkdir(shadowDir)
168 return shadowDir
169
171 """
172 Gets a note from the ipod, If the pickled shadow copy exists
173 then return that
174 """
175 rawNoteURI = os.path.join(self._get_shadow_dir(),uid)
176 if os.path.exists(rawNoteURI):
177 raw = open(rawNoteURI,'rb')
178 try:
179 n = pickle.load(raw)
180 raw.close()
181 return n
182 except:
183 raw.close()
184
185 noteURI = os.path.join(self.dataDir, uid)
186 noteFile = File.File(URI=noteURI)
187
188
189 n = Note.Note(
190 title=uid,
191 contents=noteFile.get_contents_as_text().replace(
192 self.ENCODING_DECLARATION, '', 1),
193 )
194 n.set_UID(uid)
195 n.set_mtime(noteFile.get_mtime())
196 n.set_open_URI(noteURI)
197 return n
198
200 """
201 Save a simple iPod note in /Notes
202 If the note has raw then also save that in shadowdir
203 uid is the note title.
204 """
205
206
207 contents = note.get_contents()
208 if not self.ENCODING_DECLARATION in contents:
209 contents = ''.join([self.ENCODING_DECLARATION, contents])
210 ipodnote = Utils.new_tempfile(contents)
211
212 ipodnote.transfer(os.path.join(self.dataDir,uid), overwrite=True)
213 ipodnote.set_mtime(note.get_mtime())
214 ipodnote.set_UID(uid)
215
216
217 raw = open(os.path.join(self._get_shadow_dir(),uid),'wb')
218 pickle.dump(note, raw, -1)
219 raw.close()
220
221 return ipodnote.get_rid()
222
224
225 shadowDir = self._get_shadow_dir()
226 return os.path.exists(os.path.join(shadowDir,uid)) and os.path.exists(os.path.join(self.dataDir,uid))
227
228 - def get(self, LUID):
231
232 - def put(self, note, overwrite, LUID=None):
233 """
234 The LUID for a note in the iPod is the note title
235 """
236 DataProvider.TwoWay.put(self, note, overwrite, LUID)
237
238 if LUID != None:
239
240 if self._note_exists(LUID):
241 if overwrite == True:
242
243 log.debug("Replacing Note %s" % LUID)
244 return self._save_note_to_ipod(LUID, note)
245 else:
246
247 log.warn("OVERWRITE IF NEWER NOT IMPLEMENTED")
248 return self._save_note_to_ipod(LUID, note)
249
250
251 log.warn("CHECK IF EXISTS, COMPARE, SAVE")
252 return self._save_note_to_ipod(note.title, note)
253
260
296
332
334
335 _name_ = "Photos"
336 _description_ = "Sync your iPod photos"
337 _module_type_ = "sink"
338 _in_type_ = "file/photo"
339 _out_type_ = "file/photo"
340 _icon_ = "image-x-generic"
341 _configurable_ = True
342
343 SAFE_PHOTO_ALBUM = "Photo Library"
344
346 IPodBase.__init__(self, *args)
347 self.db = gpod.PhotoDatabase(self.mountPoint)
348 self.albumName = "Conduit"
349 self.album = None
350
352 gpod.itdb_device_set_sysinfo(self.db._itdb.device, modelnumstr, model)
353
355 for album in self.db.PhotoAlbums:
356 if album.name == albumName:
357 log.debug("Found album: %s" % albumName)
358 return album
359
360 log.debug("Creating album: %s" % albumName)
361 return self._create_photo_album(albumName)
362
364 if albumName in [a.name for a in self.db.PhotoAlbums]:
365 log.warn("Album already exists: %s" % albumName)
366 album = self._get_photo_album(albumName)
367 else:
368 album = self.db.new_PhotoAlbum(title=albumName)
369 return album
370
372 for album in self.db.PhotoAlbums:
373 for photo in album:
374 if str(photo['id']) == str(id):
375 return photo
376 return None
377
386
388 for photo in self.db.PhotoAlbums[0][:]:
389 self.db.remove(photo)
390
392 i = []
393 for album in self.db.PhotoAlbums:
394 i.append(album.name)
395 return i
396
400
402 uids = []
403 for photo in self.album:
404 uids.append(str(photo['id']))
405 return uids
406
407 - def put(self, f, overwrite, LUID=None):
408 photo = self.db.new_Photo(filename=f.get_local_uri())
409 self.album.add(photo)
410 gpod.itdb_photodb_write(self.db._itdb, None)
411 return conduit.datatypes.Rid(str(photo['id']), None, hash(None))
412
414 photo = self._get_photo_by_id(LUID)
415 if photo != None:
416 self.db.remove(photo)
417 gpod.itdb_photodb_write(self.db._itdb, None)
418
420 import gobject
421 import gtk
422 def build_album_model(albumCombo):
423 self.album_store.clear()
424 album_count = 0
425 album_iter = None
426 for name in self._get_photo_albums():
427 iter = self.album_store.append((name,))
428 if name == self.albumName:
429 album_iter = iter
430 album_count += 1
431
432 if album_iter:
433 albumCombo.set_active_iter(album_iter)
434 elif self.albumName:
435 albumCombo.child.set_text(self.albumName)
436 elif album_count:
437 albumCombo.set_active(0)
438
439 def delete_click(sender, albumCombo):
440 albumName = albumCombo.get_active_text()
441 if albumName:
442 self._delete_album(albumName)
443 build_album_model(albumCombo)
444
445
446 tree = Utils.dataprovider_glade_get_widget(
447 __file__,
448 "config.glade",
449 "PhotoConfigDialog")
450 albumCombo = tree.get_widget("album_combobox")
451 delete_button = tree.get_widget("delete_button")
452
453
454 self.album_store = gtk.ListStore(gobject.TYPE_STRING)
455 albumCombo.set_model(self.album_store)
456 cell = gtk.CellRendererText()
457 albumCombo.pack_start(cell, True)
458 albumCombo.set_text_column(0)
459
460
461 build_album_model(albumCombo)
462 delete_button.connect('clicked', delete_click, albumCombo)
463
464
465 dlg = tree.get_widget("PhotoConfigDialog")
466 response = Utils.run_dialog(dlg, window)
467
468 if response == True:
469
470 self.albumName = albumCombo.get_active_text()
471 dlg.destroy()
472
473 del self.album_store
474
477
480