[conduit/iphone: 6/20] Split ipod classes out into own module



commit c9e71a1f276978b4363be866549ac181eb5f9b6c
Author: John Stowers <john stowers gmail com>
Date:   Thu Sep 23 10:16:20 2010 +1200

    Split ipod classes out into own module

 conduit/modules/iPodModule/iPodModule.py    |  886 +--------------------------
 conduit/modules/iPodModule/ipod/__init__.py |  863 ++++++++++++++++++++++++++
 2 files changed, 878 insertions(+), 871 deletions(-)
---
diff --git a/conduit/modules/iPodModule/iPodModule.py b/conduit/modules/iPodModule/iPodModule.py
index 8212967..22ab4c5 100644
--- a/conduit/modules/iPodModule/iPodModule.py
+++ b/conduit/modules/iPodModule/iPodModule.py
@@ -1,15 +1,3 @@
-"""
-Provides a number of dataproviders which are associated with
-removable devices such as USB keys.
-
-It also includes classes specific to the ipod.
-This file is not dynamically loaded at runtime in the same
-way as the other dataproviders as it needs to be loaded all the time in
-order to listen to HAL events
-
-Copyright: John Stowers, 2006
-License: GPLv2
-"""
 import sys
 import os
 import pickle
@@ -38,22 +26,14 @@ import conduit.datatypes.Video as Video
 
 from gettext import gettext as _
 
-errormsg = ""
-try:
-    import gpod
-    if gpod.version_info >= (0,7,0):
-        MODULES = {
-            "iPodFactory" :         { "type":   "dataprovider-factory"  },
-            "iPhoneFactory" :       { "type":   "dataprovider-factory"  },
-        }
-        log.info("Module Information: %s" % Utils.get_module_information(gpod, 'version_info'))
-except ImportError:
-    errormsg = "iPod support disabled (python-gpod not availiable)"
-except locale.Error:
-    errormsg = "iPod support disabled (Incorrect locale)"
-
 Utils.dataprovider_add_dir_to_path(__file__)
 from idevice import iPhoneContactsTwoWay, iPhoneCalendarsTwoWay
+from ipod import IPodMusicTwoWay, IPodVideoTwoWay, IPodNoteTwoWay, IPodContactsTwoWay, IPodCalendarTwoWay, IPodPhotoSink
+
+MODULES = {
+    "iPodFactory" :         { "type":   "dataprovider-factory"  },
+    "iPhoneFactory" :       { "type":   "dataprovider-factory"  },
+}
 
 PROPS_KEY_MOUNT = "CONDUIT_MOUNTPOINT"
 PROPS_KEY_NAME  = "CONDUIT_NAME"
@@ -61,17 +41,6 @@ PROPS_KEY_ICON  = "CONDUIT_ICON"
 PROPS_KEY_UUID  = "CONDUIT_UUID"
 PROPS_KEY_TYPE  = "CONDUIT_TYPE"
 
-if errormsg:
-    MODULES = {}
-    log.warn(errormsg)
-    #Solve the initialization problem without gpod
-    class gpod():
-        ITDB_MEDIATYPE_MUSICVIDEO = 0
-        ITDB_MEDIATYPE_MOVIE = 1
-        ITDB_MEDIATYPE_TVSHOW = 2
-        ITDB_MEDIATYPE_AUDIO = 3
-        ITDB_MEDIATYPE_PODCAST = 4
-
 def _string_to_unqiue_file(txt, base_uri, prefix, postfix=''):
     temp = Utils.new_tempfile(txt)
     uri = os.path.join(base_uri, prefix+temp.get_filename()+postfix)
@@ -79,13 +48,6 @@ def _string_to_unqiue_file(txt, base_uri, prefix, postfix=''):
     temp.set_UID(os.path.basename(uri))
     return temp.get_rid()
 
-def _supports_photos(db):
-    if isinstance(p, gpod.PhotoDatabase) or isinstance(m, gpod.Database):
-        return gpod.itdb_device_supports_photo(db._itdb.device)
-    else:
-        log.critical("could not determine if device supports photos")
-        return False
-
 def _get_apple_label(props):
     return props.get(PROPS_KEY_NAME,
             "Apple " + props.get("ID_MODEL", "Device"))
@@ -183,15 +145,15 @@ class iPodFactory(MediaPlayerFactory.MediaPlayerFactory):
     def get_dataproviders(self, udi, **props):
         #Read information about the ipod, like if it supports
         #photos or not
-        d = gpod.itdb_device_new()
-        gpod.itdb_device_set_mountpoint(d, props[PROPS_KEY_MOUNT])
-        supportsPhotos = gpod.itdb_device_supports_photo(d)
-        gpod.itdb_device_free(d)
-        if supportsPhotos:
-            return [IPodMusicTwoWay, IPodVideoTwoWay, IPodNoteTwoWay, IPodContactsTwoWay, IPodCalendarTwoWay, IPodPhotoSink]
-        else:
-            log.info("iPod does not report photo support")
-            return [IPodMusicTwoWay, IPodVideoTwoWay, IPodNoteTwoWay, IPodContactsTwoWay, IPodCalendarTwoWay]
+        #d = gpod.itdb_device_new()
+        #gpod.itdb_device_set_mountpoint(d, props[PROPS_KEY_MOUNT])
+        #supportsPhotos = gpod.itdb_device_supports_photo(d)
+        #gpod.itdb_device_free(d)
+        #if supportsPhotos:
+        return [IPodMusicTwoWay, IPodVideoTwoWay, IPodNoteTwoWay, IPodContactsTwoWay, IPodCalendarTwoWay, IPodPhotoSink]
+        #else:
+        #    log.info("iPod does not report photo support")
+        #    return [IPodMusicTwoWay, IPodVideoTwoWay, IPodNoteTwoWay, IPodContactsTwoWay, IPodCalendarTwoWay]
 
     def get_args(self, key, **props):
         return (props[PROPS_KEY_MOUNT], key)
@@ -212,822 +174,4 @@ class IPodDummy(DataProvider.TwoWay):
     def get_UID(self):
         print "-----".join(self.args)
 
-class IPodBase(DataProvider.TwoWay):
-    def __init__(self, *args):
-        DataProvider.TwoWay.__init__(self)
-        self.mountPoint = args[0]
-        self.uid = args[1]
-        self.objects = None
-
-        log.debug("Created ipod %s at %s" % (self.__class__.__name__, self.mountPoint))
-
-    def refresh(self):
-        DataProvider.TwoWay.refresh(self)
-        self.objects = []
-
-        #Also checks directory exists
-        if not os.path.exists(self.dataDir):
-            os.mkdir(self.dataDir)
-
-        #When acting as a source, only notes in the Notes dir are
-        #considered
-        for f in os.listdir(self.dataDir):
-            fullpath = os.path.join(self.dataDir, f)
-            if os.path.isfile(fullpath):
-                self.objects.append(f)
-
-    def get_all(self):
-        DataProvider.TwoWay.get_all(self)
-        return self.objects
-
-    def delete(self, LUID):
-        obj = File.File(URI=os.path.join(self.dataDir, LUID))
-        if obj.exists():
-            obj.delete()
-
-    def finish(self, aborted, error, conflict):
-        DataProvider.TwoWay.finish(self)
-        self.objects = None
-
-    def get_UID(self):
-        return self.uid
-
-    def _get_unique_filename(self, directory):
-        """
-        Returns the name of a non-existant file on the
-        ipod within directory
-
-        @param directory: Name of the directory within the device root to make
-        the random file in
-        """
-        done = False
-        while not done:
-            f = os.path.join(self.mountPoint,directory,Utils.random_string())
-            if not os.path.exists(f):
-                done = True
-        return f
-
-class IPodNoteTwoWay(IPodBase):
-    """
-    Stores Notes on the iPod.
-    Rather than requiring a perfect transform to and from notes to the
-    ipod note format I also store the original note data in a
-    .conduit directory in the root of the iPod.
-
-    Notes are saved as title.txt and a copy of the raw note is saved as
-    title.note
-
-    LUID is the note title
-    """
-
-    _name_ = _("Notes")
-    _description_ = _("Synchronize your iPod notes")
-    _module_type_ = "twoway"
-    _in_type_ = "note"
-    _out_type_ = "note"
-    _icon_ = "note"
-
-    # datatypes.Note doesn't care about encoding,
-    # lets be naive and assume that all notes are utf-8
-    ENCODING_DECLARATION = '<?xml encoding="utf-8"?>'
-
-    def __init__(self, *args):
-        IPodBase.__init__(self, *args)
-
-        self.dataDir = os.path.join(self.mountPoint, 'Notes')
-        self.objects = []
-
-    def _get_shadow_dir(self):
-        shadowDir = os.path.join(self.mountPoint, '.conduit')
-        if not os.path.exists(shadowDir):
-            os.mkdir(shadowDir)
-        return shadowDir
-
-    def _get_note_from_ipod(self, uid):
-        """
-        Gets a note from the ipod, If the pickled shadow copy exists
-        then return that
-        """
-        rawNoteURI = os.path.join(self._get_shadow_dir(),uid)
-        if os.path.exists(rawNoteURI):
-            raw = open(rawNoteURI,'rb')
-            try:
-                n = pickle.load(raw)
-                raw.close()
-                return n
-            except:
-                raw.close()
-
-        noteURI = os.path.join(self.dataDir, uid)
-        noteFile = File.File(URI=noteURI)
-        #get the contents from the note, get the raw from the raw copy.
-        #the UID for notes from the ipod is the filename
-        n = Note.Note(
-                    title=uid,
-                    contents=noteFile.get_contents_as_text().replace(
-                        self.ENCODING_DECLARATION, '', 1),
-                    )
-        n.set_UID(uid)
-        n.set_mtime(noteFile.get_mtime())
-        n.set_open_URI(noteURI)
-        return n
-
-    def _save_note_to_ipod(self, uid, note):
-        """
-        Save a simple iPod note in /Notes
-        If the note has raw then also save that in shadowdir
-        uid is the note title.
-        """
-        # the normal note viewed by the iPod
-        # inject an encoding declaration if it is missing.
-        contents = note.get_contents()
-        if not self.ENCODING_DECLARATION in contents:
-            contents = ''.join([self.ENCODING_DECLARATION, contents])
-        ipodnote = Utils.new_tempfile(contents)
-
-        ipodnote.transfer(os.path.join(self.dataDir,uid), overwrite=True)
-        ipodnote.set_mtime(note.get_mtime())
-        ipodnote.set_UID(uid)
-
-        #the raw pickled note for sync
-        raw = open(os.path.join(self._get_shadow_dir(),uid),'wb')
-        pickle.dump(note, raw, -1)
-        raw.close()
-
-        return ipodnote.get_rid()
-
-    def _note_exists(self, uid):
-        #Check if both the shadow copy and the ipodified version exists
-        shadowDir = self._get_shadow_dir()
-        return os.path.exists(os.path.join(shadowDir,uid)) and os.path.exists(os.path.join(self.dataDir,uid))
-
-    def get(self, LUID):
-        DataProvider.TwoWay.get(self, LUID)
-        return self._get_note_from_ipod(LUID)
-
-    def put(self, note, overwrite, LUID=None):
-        """
-        The LUID for a note in the iPod is the note title
-        """
-        DataProvider.TwoWay.put(self, note, overwrite, LUID)
-
-        if LUID != None:
-            #Check if both the shadow copy and the ipodified version exists
-            if self._note_exists(LUID):
-                if overwrite == True:
-                    #replace the note
-                    log.debug("Replacing Note %s" % LUID)
-                    return self._save_note_to_ipod(LUID, note)
-                else:
-                    #only overwrite if newer
-                    log.warn("OVERWRITE IF NEWER NOT IMPLEMENTED")
-                    return self._save_note_to_ipod(LUID, note)
-
-        #make a new note
-        log.warn("CHECK IF EXISTS, COMPARE, SAVE")
-        return self._save_note_to_ipod(note.title, note)
-
-    def delete(self, LUID):
-        IPodBase.delete(self, LUID)
-
-        raw = File.File(URI=os.path.join(self._get_shadow_dir(), LUID))
-        if raw.exists():
-            raw.delete()
-
-class IPodContactsTwoWay(IPodBase):
-
-    _name_ = _("Contacts")
-    _description_ = _("Synchronize your iPod contacts")
-    _module_type_ = "twoway"
-    _in_type_ = "contact"
-    _out_type_ = "contact"
-    _icon_ = "contact-new"
-
-    def __init__(self, *args):
-        IPodBase.__init__(self, *args)
-        self.dataDir = os.path.join(self.mountPoint, 'Contacts')
-
-    def get(self, LUID):
-        DataProvider.TwoWay.get(self, LUID)
-        fullpath = os.path.join(self.dataDir, LUID)
-        f = File.File(URI=fullpath)
-
-        contact = Contact.Contact()
-        contact.set_from_vcard_string(f.get_contents_as_text())
-        contact.set_open_URI(fullpath)
-        contact.set_mtime(f.get_mtime())
-        contact.set_UID(LUID)
-        return contact
-
-    def put(self, contact, overwrite, LUID=None):
-        DataProvider.TwoWay.put(self, contact, overwrite, LUID)
-
-        if LUID != None:
-            f = Utils.new_tempfile(contact.get_vcard_string())
-            f.transfer(os.path.join(self.dataDir, LUID), overwrite=True)
-            f.set_UID(LUID)
-            return f.get_rid()
-
-        return _string_to_unqiue_file(contact.get_vcard_string(), self.dataDir, 'contact')
-
-class IPodCalendarTwoWay(IPodBase):
-
-    _name_ = _("Calendar")
-    _description_ = _("Synchronize your iPod calendar")
-    _module_type_ = "twoway"
-    _in_type_ = "event"
-    _out_type_ = "event"
-    _icon_ = "contact-new"
-
-    def __init__(self, *args):
-        IPodBase.__init__(self, *args)
-        self.dataDir = os.path.join(self.mountPoint, 'Calendars')
-
-    def get(self, LUID):
-        DataProvider.TwoWay.get(self, LUID)
-        fullpath = os.path.join(self.dataDir, LUID)
-        f = File.File(URI=fullpath)
-
-        event = Event.Event()
-        event.set_from_ical_string(f.get_contents_as_text())
-        event.set_open_URI(fullpath)
-        event.set_mtime(f.get_mtime())
-        event.set_UID(LUID)
-        return event
-
-    def put(self, event, overwrite, LUID=None):
-        DataProvider.TwoWay.put(self, event, overwrite, LUID)
-
-        if LUID != None:
-            f = Utils.new_tempfile(event.get_ical_string())
-            f.transfer(os.path.join(self.dataDir, LUID), overwrite=True)
-            f.set_UID(LUID)
-            return f.get_rid()
-
-        return _string_to_unqiue_file(event.get_ical_string(), self.dataDir, 'event')
-
-class IPodPhotoSink(IPodBase):
-
-    _name_ = _("Photos")
-    _description_ = _("Synchronize your iPod photos")
-    _module_type_ = "sink"
-    _in_type_ = "file/photo"
-    _out_type_ = "file/photo"
-    _icon_ = "image-x-generic"
-    _configurable_ = True
-
-    SAFE_PHOTO_ALBUM = "Photo Library"
-
-    def __init__(self, *args):
-        IPodBase.__init__(self, *args)
-        self.db = gpod.PhotoDatabase(self.mountPoint)
-        self.album = None
-        self.update_configuration(
-            albumName = "Conduit"
-        )
-
-    def _set_sysinfo(self, modelnumstr, model):
-        #this must only be used from TestDataProvideriPod.py
-        gpod.itdb_device_set_sysinfo(self.db._itdb.device, modelnumstr, model)
-
-    def _get_photo_album(self, albumName):
-        for album in self.db.PhotoAlbums:
-            if album.name == albumName:
-                log.debug("Found album: %s" % albumName)
-                return album
-
-        log.debug("Creating album: %s" % albumName)
-        return self._create_photo_album(albumName)
-
-    def _create_photo_album(self, albumName):
-        if albumName in [a.name for a in self.db.PhotoAlbums]:
-            log.warn("Album already exists: %s" % albumName)
-            album = self._get_photo_album(albumName)
-        else:
-            album = self.db.new_PhotoAlbum(title=albumName)
-        return album
-
-    def _get_photo_by_id(self, id):
-        for album in self.db.PhotoAlbums:
-            for photo in album:
-                if str(photo['id']) == str(id):
-                    return photo
-        return None
-
-    def _delete_album(self, albumName):
-        if albumName == self.SAFE_PHOTO_ALBUM:
-            log.warn("Cannot delete album: %s" % self.SAFE_PHOTO_ALBUM)
-        else:
-            for album in self.db.PhotoAlbums:
-                if album.name == albumName:
-                    for photo in album[:]:
-                        album.remove(photo)
-                    self.db.remove(album)
-
-    def _delete_all_photos(self):
-        for album in self.db.PhotoAlbums:
-            for photo in album[:]:
-                album.remove(photo)
-            if album.name != self.SAFE_PHOTO_ALBUM:
-                self.db.remove(album)
-        gpod.itdb_photodb_write(self.db._itdb, None)
-
-    def _get_photo_albums(self):
-        i = []
-        for album in self.db.PhotoAlbums:
-            i.append(album.name)
-        return i
-
-    def refresh(self):
-        DataProvider.TwoWay.refresh(self)
-        self.album = self._get_photo_album(self.albumName)
-
-    def get_all(self):
-        uids = []
-        for photo in self.album:
-            uids.append(str(photo['id']))
-        return uids
-
-    def put(self, f, overwrite, LUID=None):
-        photo = self.db.new_Photo(filename=f.get_local_uri())
-        self.album = self._get_photo_album(self.albumName)
-        self.album.add(photo)
-        gpod.itdb_photodb_write(self.db._itdb, None)
-        return conduit.datatypes.Rid(str(photo['id']), None, hash(None))
-
-    def delete(self, LUID):
-        photo = self._get_photo_by_id(LUID)
-        if photo != None:
-            self.db.remove(photo)
-            gpod.itdb_photodb_write(self.db._itdb, None)
-
-    def config_setup(self, config):
-
-        def _delete_click(button):
-            self._delete_album(album_config.get_value())
-            album_config.choices = self._get_photo_albums()
-
-        def _delete_all_click(button):
-            self._delete_all_photos()
-
-        album_config = config.add_item(_('Album'), 'combotext',
-            config_name = 'albumName',
-            choices = self._get_photo_albums(),
-        )
-        config.add_item(_("Delete"), "button",
-            initial_value = _delete_click
-        )
-        config.add_item(_("Delete All Photos"), "button",
-            initial_value = _delete_all_click
-        )
-
-
-    def is_configured (self, isSource, isTwoWay):
-        return len(self.albumName) > 0
-
-    def uninitialize(self):
-        self.db.close()
-
-unicode_conv = lambda v: unicode(v).encode('UTF-8','replace')
-
-class IPodFileBase:
-    '''
-    A wrapper around an iPod track. iPod track properties are converted into
-    Media properties, and vice-versa.
-    '''
-    
-    #Mappings from the Media metadata to the iPod metadata and vice-versa, 
-    #including type-checking
-    media_to_ipod = {
-        'title' : ('title', unicode_conv),
-        'artist' : ('artist', unicode_conv),
-        'album' : ('album', unicode_conv),
-        'composer' : ('composer', unicode_conv),
-        'rating' : ('rating', lambda v: float(v) / 0.05),
-        'genre' : ('genre', unicode_conv),
-        'track_nr' : ('track-number', int),
-        'tracks' : ('track-count', int),
-        'bitrate' : ('bitrate', int),
-        'tracklen' : ('duration', int),
-        'samplerate' : ('samplerate', int),
-        'width' : ('width', int),
-        'height' : ('height', int),
-    }
-    
-    ipod_to_media = {
-        'title' : ('title', unicode_conv),
-        'artist' : ('artist', unicode_conv),
-        'album' : ('album', unicode_conv),
-        'composer' : ('composer', unicode_conv),
-        'rating' : ('rating', lambda v: float(v) * 0.05),
-        'genre' : ('genre', unicode_conv),
-        'track-number' : ('track_nr', int),
-        'track-count' : ('tracks', int),
-        'bitrate' : ('bitrate', int),
-        'duration' : ('tracklen', int),
-        'samplerate' : ('samplerate', int),
-        'width' : ('width', int),
-        'height' : ('height', int),        
-    }
-
-    def __init__(self, db, track = None, f = None):
-        '''
-        Wraps an iPod track in a Datatype. 
-        Passing a file creates a new track in the iPod db, with media information 
-        from that file. Use copy_ipod to transfer it into the iPod.
-        Passing an existing iPod track exports the track's information as a 
-        Media datatype.
-        
-        @param ipod_track: An iPod track to wrap
-        @param f: A File to extract the information from
-        '''
-        self.db = db
-        if track:
-            self.track = track
-        else:
-            self.track = self.db.new_Track()
-        if f:
-            self.set_info_from_file(f)
-        
-    def get_UID(self):
-        '''
-        Returns the database ID (usually a random number, which is always valid
-        for this track in this db, even across application restarts)
-        '''
-        return str(self.track['dbid'])
-
-    def _convert_tags(self, from_tags, mapping):
-        '''
-        Convert from one mapping to another.
-        Returns an iterator with (name, value) for each tag in from_tags
-        '''
-        for from_name, from_value in from_tags.iteritems():
-            if from_name in mapping:
-                to_name, to_converter = mapping[from_name]
-                try:
-                    to_value = to_converter(from_value)
-                    yield to_name, to_value
-                except Exception, e:
-                    log.warn("Could not convert property %s: %s as %s. (Error: %s)" % (from_name, from_value, to_converter, e))
-
-    def set_info_from_file(self, f):
-        '''
-        Get the track information from a file, including the metadata.
-        Works best with GStreamer metadata in MediaFile.
-        '''
-        tags = f.get_media_tags()
-        for name, value in self._convert_tags(tags, self.media_to_ipod):
-            #log.debug("Got %s = %s" % (name, value))
-            self.track[name] = value
-        #Make sure we have a title to this song, even if it's just the filename
-        if self.track['title'] is None:
-            self.track['title'] = os.path.basename(f.get_local_uri())
-        self.track['time_modified'] = os.stat(f.get_local_uri()).st_mtime
-        self.track['time_added'] = int(time.time())
-        self.track['userdata'] = {'transferred': 0,
-                                  'hostname': socket.gethostname(),
-                                  'charset': locale.getpreferredencoding()}
-        self.track._set_userdata_utf8('filename', f.get_local_uri())
-        
-    def get_track_filename(self):
-        filename = self.track.ipod_filename()
-        if not filename or not os.path.exists(filename):
-            filename = self.track._userdata_into_default_locale('filename')
-        return filename
-        
-    def get_hash(self):
-        return str(hash(tuple(self.get_media_tags())))
-
-    def get_snippet(self):
-        return "%(artist)s - %(title)s" % track
-
-    def get_media_tags(self):
-        '''
-        Extends the MediaFile class to include the iPod metadata, instead of
-        calling the GStreamer loader. It's much faster this way, and provides
-        some nice information to other dataproviders, like ratings.
-        '''
-        #FIXME: Cache this information
-        
-        #Get the information from the iPod track.
-        #The track might look like a dict, but it isnt, so we make it into one.
-        track_tags = dict(self.track.pairs())
-        return dict(self._convert_tags(track_tags, self.ipod_to_media))
-
-    #FIXME: Remove this. Use native operations from Conduit instead.
-    #       We would have to define the transfered userdata as 1 and then call
-    #       Conduit to copy the file. 
-    #       But that is Conduit's copy file way?
-    def copy_ipod(self):
-        self.track.copy_to_ipod()
-
-class IPodAudio(IPodFileBase, Audio.Audio):
-    def __init__(self, *args, **kwargs):
-        '''
-        Initialize a new Audio track for this db and file.
-        '''
-        IPodFileBase.__init__(self, *args, **kwargs)
-        Audio.Audio.__init__(self, URI = self.get_track_filename())
-
-    def set_info_from_file(self, audio):
-        IPodFileBase.set_info_from_file(self, audio)
-        self.track['mediatype'] = gpod.ITDB_MEDIATYPE_AUDIO
-        cover_location = audio.get_audio_cover_location()
-        if cover_location:
-            self.track.set_coverart_from_file(str(cover_location))
-
-class IPodVideo(IPodFileBase, Video.Video):
-    def __init__(self, *args, **kwargs):
-        '''
-        Initialize a new Video track for this db and file.
-        '''
-        IPodFileBase.__init__(self, *args, **kwargs)
-        Video.Video.__init__(self, URI = self.get_track_filename())
-        
-        log.debug('Video kind selected: %s' % (kwargs['video_kind']))
-        self.video_kind = kwargs['video_kind']
-        
-    def set_info_from_file(self, video):
-        IPodFileBase.set_info_from_file(video)
-        self.track['mediatype'] = {'movie': gpod.ITDB_MEDIATYPE_MOVIE,
-                                   'musicvideo': gpod.ITDB_MEDIATYPE_MUSICVIDEO,
-                                   'tvshow': gpod.ITDB_MEDIATYPE_TVSHOW,
-                                   'podcast': gpod.ITDB_MEDIATYPE_PODCAST
-                                   } [self.video_kind]
-
-class DBCache:
-    '''
-    Keeps a list of open GPod databases.
-
-    Keeps one database open for each mount-point.
-    Automatically disposes unused databases.
-    '''
-    __db_list = weakref.WeakValueDictionary()
-    __db_locks = weakref.WeakKeyDictionary()
-    __lock = threading.Lock()
-
-    @classmethod
-    def get_db(self, mount_point):
-        self.__lock.acquire()
-        try:
-            if mount_point in self.__db_list:
-                log.debug('Getting DB in cache for %s' % (mount_point))
-                db = self.__db_list[mount_point]
-            else:
-                if mount_point:
-                    log.debug('Creating DB for %s' % mount_point)
-                    db = gpod.Database(mount_point)
-                else:
-                    log.debug('Creating local DB')
-                    db = gpod.Database(local=True)
-                self.__db_list[mount_point] = db
-                self.__db_locks[db] = threading.Lock()
-            return db
-        finally:
-            self.__lock.release()
-
-    @classmethod
-    def release_db(self, db):
-        assert db in self.__db_locks
-        # We dont do nothing here yet, but we could use to release resources.
-        # The db is automatically removed from the list because of the weak 
-        # reference.
-        log.debug('Releasing DB for %s' % db)
-
-    @classmethod
-    def lock_db(self, db):
-        assert db in self.__db_locks
-        log.debug('Locking DB %s' % db)
-        self.__db_locks[db].acquire()
-
-    @classmethod
-    def unlock_db(self, db):
-        assert db in self.__db_locks
-        log.debug('Unlocking DB %s' % db)
-        self.__db_locks[db].release()
-
-class IPodMediaTwoWay(IPodBase):
-    FORMAT_CONVERSION_STRING = _("Encoding")
-
-    def __init__(self, *args):
-        self.local_db = (len(args) == 0)
-        if not self.local_db:
-            IPodBase.__init__(self, *args)
-        else:
-            # Use local database for testing
-            DataProvider.TwoWay.__init__(self)        
-            self.uid = "Local"
-        self.db = None
-        #self.tracks = {}
-        self.tracks_id = {}
-        self.track_args = {}
-        self.update_configuration(
-            keep_converted = True,
-        )
-        
-    def get_db(self):
-        if self.db:
-            DBCache.lock_db(self.db)
-            return self.db
-        if not self.local_db:
-            self.db = DBCache.get_db(self.mountPoint)
-        else:
-            self.db = DBCache.get_db(None)        
-        DBCache.lock_db(self.db)
-        return self.db
-    
-    def unlock_db(self):
-        DBCache.unlock_db(self.db)
-        
-    def release_db(self):
-        if not self.db:
-            return
-        self.db.close()
-        DBCache.release_db(self.db)
-        self.db = None        
-
-    def refresh(self):
-        DataProvider.TwoWay.refresh(self)
-        self.tracks = {}
-        self.tracks_id = {}
-        self.get_db()
-        try:
-            def add_track(track):
-                self.tracks_id[str(track['dbid'])] = track
-            [add_track(track) for track in self.db \
-                if track['mediatype'] in self._mediatype_]
-        finally:
-            self.unlock_db()
-
-    def get_all(self):
-        return self.tracks_id.keys()
-
-    def get(self, LUID = None):
-        self.get_db()
-        try:
-            if LUID not in self.tracks_id:
-                raise Exceptions.SyncronizeError('Track ID %s not found in iPod DB %s' % (LUID, self.db))
-            track = self.tracks_id[LUID]
-            ipod_file = self._ipodmedia_(self.db, track = track)
-            filename = ipod_file.get_track_filename()
-            if not os.path.exists(filename):
-                raise Exceptions.SyncronizeError("Could not find iPod track file %s" % (filename))
-            #Set a nice "Artist - Title" name with the original filename
-            #extension
-            #FIXME: Doesnt work as expected anymore, the original filename is
-            #renamed instead
-            #if track.ipod_filename() and track['artist'] and track['title']:
-            #    ipod_file.force_new_filename("%(artist)s - %(title)s" % track + \
-            #        os.path.splitext(filename)[1])
-            return ipod_file
-        finally:
-            self.unlock_db()
-        return None
-
-    def put(self, f, overwrite, LUID=None):
-        self.get_db()
-        try:
-            if LUID and LUID in self.tracks_id:
-                track = self.tracks_id[LUID]
-                media_file = self._ipodmedia_(db = self.db, track = track, f = f, **self.track_args)
-            else:
-                media_file = self._ipodmedia_(db = self.db, f = f, **self.track_args)
-            #FIXME: We keep the db locked while we copy the file. Not good.
-            media_file.copy_ipod()
-            self.tracks_id[str(media_file.track['dbid'])] = media_file.track
-            #FIXME: Writing the db here is for debug only. Closing does not actually
-            # close the db, it only writes it's contents to disk.            
-            # Sometimes, if we only close the db when the sync is over, it might
-            # take a long time to close the db, because many files are being 
-            # copied to the iPod. Closing the DB every time not only keeps
-            # this time small, but also keeps the db more consistent in case of 
-            # a crash. But it also incurs a big overhead. 
-            # Maybe a batch update could be a better solution (close after 5 tracks?)
-            self.db.close()
-            return media_file
-        finally:
-            self.unlock_db()
-
-    def delete(self, LUID):
-        track = self.tracks_id[LUID]
-        if track:
-            self.get_db()
-            try:
-                self.db.remove(track)
-                self.db.close()
-            finally:
-                self.unlock_db()
-
-    def config_setup(self, config):
-        #Get an array of encodings, so it can be indexed inside a combobox
-        encodings = [(enc_name, enc_opts.get('description', None) or enc_name)
-                      for enc_name, enc_opts in self.encodings.iteritems()]
-        
-        config.add_section(_("Conversion options"))
-        config.add_item(_("Encoding"), "combo", 
-            config_name = "encoding",
-            choices = encodings
-        )
-        config.add_item(_("Keep converted files"), "check",
-            config_name = "keep_converted"
-        )
-
-    def get_input_conversion_args(self):
-        try:
-            args = self.encodings[self.encoding]
-            # FIXME
-            # If we pass the bool in the args, it will become a string, and 
-            # will always return True later in the converter.
-            # So we only pass it if is True. When it's False, not being there
-            # tells the converter it isn't True.
-            # I'm not sure it was supposed to work like this.
-            if self.keep_converted:
-                args['keep_converted'] = True
-            return args
-        except KeyError:
-            return {}
-
-    def uninitialize(self):
-        self.release_db()
-
-IPOD_AUDIO_ENCODINGS = {
-    "mp3": {"description": "Mp3", "acodec": "lame", "file_extension": "mp3"},
-    #FIXME: AAC needs a MP4 mux
-    #"aac": {"description": "AAC", "acodec": "faac", "file_extension": "m4a"},
-    }
-
-class IPodMusicTwoWay(IPodMediaTwoWay):
-
-    _name_ = _("Music")
-    _description_ = _("Synchronize your iPod music")
-    _module_type_ = "twoway"
-    _in_type_ = "file/audio"
-    _out_type_ = "file/audio"
-    _icon_ = "audio-x-generic"
-    _configurable_ = True
-
-    _mediatype_ = (gpod.ITDB_MEDIATYPE_AUDIO,)
-    _ipodmedia_ = IPodAudio
-
-    def __init__(self, *args):
-        IPodMediaTwoWay.__init__(self, *args)
-        self.encodings = IPOD_AUDIO_ENCODINGS
-        self.update_configuration(
-            encoding = 'mp3',
-        )
-
-IPOD_VIDEO_ENCODINGS = {
-    #FIXME: Add iPod mpeg4 restrictions. Follow:
-    # http://rob.opendot.cl/index.php/useful-stuff/ffmpeg-x264-encoding-guide/
-    "mp4_x264":{"description": "MP4 (Better quality - H.264)","vcodec":"x264enc", "acodec":"faac", 
-        "format":"ffmux_mp4", "file_extension":"m4v", "width": 320, "height": 240, 
-        "mimetype": "video/mp4"},
-    #FIXME: Two-pass encoding is not working. The first pass never finishes.
-    #"mp4_x264_twopass":{"description": "MP4 (H.264, Two-pass EXPERIMENTAL)", 
-    #    "vcodec_pass1":"x264enc pass=1", "vcodec_pass2":"x264enc pass=2", 
-    #    "acodec":"faac", "format":"ffmux_mp4", "file_extension":"m4v", 
-    #    "width": 320, "height": 240, "mimetype": "video/mp4", 'twopass':True},
-    "mp4_xvid":{"description": "MP4 (Faster conversion - XVid)","vcodec":"ffenc_mpeg4", "acodec":"faac",
-        "format":"ffmux_mp4", "file_extension":"m4v", "width": 320, "height": 240, 
-        "mimetype": "video/mp4"},
-    }
-
-class IPodVideoTwoWay(IPodMediaTwoWay):
-
-    _name_ = _("Video")
-    _description_ = _("Synchronize your iPod videos")
-    _module_type_ = "twoway"
-    _in_type_ = "file/video"
-    _out_type_ = "file/video"
-    _icon_ = "video-x-generic"
-    _configurable_ = True
-
-    _mediatype_ = (gpod.ITDB_MEDIATYPE_MUSICVIDEO, gpod.ITDB_MEDIATYPE_MOVIE, gpod.ITDB_MEDIATYPE_TVSHOW)
-    _ipodmedia_ = IPodVideo
-
-    def __init__(self, *args):
-        IPodMediaTwoWay.__init__(self, *args)
-        self.encodings = IPOD_VIDEO_ENCODINGS
-        self.update_configuration(
-            encoding = 'mp4_x264',
-            video_kind = 'movie',
-        )
-        self._update_track_args()
-        
-    def _update_track_args(self):
-        self.track_args['video_kind'] = self.video_kind
 
-    def config_setup(self, config):
-        IPodMediaTwoWay.config_setup(self, config)
-        video_kinds = [('movie', _('Movie')), 
-                       ('musicvideo', _('Music Video')),
-                       ('tvshow', _('TV Show'))]            
-        config.add_section()
-        config.add_item(_("Video kind"), "combo",
-            config_name = "video_kind",
-            choices = video_kinds)
-        
-    def set_configuration(self, config):
-        IPodMediaTwoWay.set_configuration(self, config)
-        #FIXME Move this to update_configuration callback
-        self._update_track_args()
diff --git a/conduit/modules/iPodModule/ipod/__init__.py b/conduit/modules/iPodModule/ipod/__init__.py
new file mode 100644
index 0000000..5ba936f
--- /dev/null
+++ b/conduit/modules/iPodModule/ipod/__init__.py
@@ -0,0 +1,863 @@
+import sys
+import os
+import pickle
+import logging
+import time
+import socket
+import locale
+import weakref
+import threading
+import gobject
+import gio
+log = logging.getLogger("modules.iPod")
+
+import conduit
+import conduit.dataproviders.DataProvider as DataProvider
+import conduit.dataproviders.DataProviderCategory as DataProviderCategory
+import conduit.dataproviders.MediaPlayerFactory as MediaPlayerFactory
+import conduit.dataproviders.HalFactory as HalFactory
+import conduit.utils as Utils
+import conduit.datatypes.Note as Note
+import conduit.datatypes.Contact as Contact
+import conduit.datatypes.Event as Event
+import conduit.datatypes.File as File
+import conduit.datatypes.Audio as Audio
+import conduit.datatypes.Video as Video
+
+from gettext import gettext as _
+
+import gpod
+assert gpod.version_info >= (0,7,0)
+
+PROPS_KEY_MOUNT = "CONDUIT_MOUNTPOINT"
+PROPS_KEY_NAME  = "CONDUIT_NAME"
+PROPS_KEY_ICON  = "CONDUIT_ICON"
+PROPS_KEY_UUID  = "CONDUIT_UUID"
+PROPS_KEY_TYPE  = "CONDUIT_TYPE"
+
+def _string_to_unqiue_file(txt, base_uri, prefix, postfix=''):
+    temp = Utils.new_tempfile(txt)
+    uri = os.path.join(base_uri, prefix+temp.get_filename()+postfix)
+    temp.transfer(uri, True)
+    temp.set_UID(os.path.basename(uri))
+    return temp.get_rid()
+
+class IPodBase(DataProvider.TwoWay):
+    def __init__(self, *args):
+        DataProvider.TwoWay.__init__(self)
+        self.mountPoint = args[0]
+        self.uid = args[1]
+        self.objects = None
+
+        log.debug("Created ipod %s at %s" % (self.__class__.__name__, self.mountPoint))
+
+    def refresh(self):
+        DataProvider.TwoWay.refresh(self)
+        self.objects = []
+
+        #Also checks directory exists
+        if not os.path.exists(self.dataDir):
+            os.mkdir(self.dataDir)
+
+        #When acting as a source, only notes in the Notes dir are
+        #considered
+        for f in os.listdir(self.dataDir):
+            fullpath = os.path.join(self.dataDir, f)
+            if os.path.isfile(fullpath):
+                self.objects.append(f)
+
+    def get_all(self):
+        DataProvider.TwoWay.get_all(self)
+        return self.objects
+
+    def delete(self, LUID):
+        obj = File.File(URI=os.path.join(self.dataDir, LUID))
+        if obj.exists():
+            obj.delete()
+
+    def finish(self, aborted, error, conflict):
+        DataProvider.TwoWay.finish(self)
+        self.objects = None
+
+    def get_UID(self):
+        return self.uid
+
+    def _get_unique_filename(self, directory):
+        """
+        Returns the name of a non-existant file on the
+        ipod within directory
+
+        @param directory: Name of the directory within the device root to make
+        the random file in
+        """
+        done = False
+        while not done:
+            f = os.path.join(self.mountPoint,directory,Utils.random_string())
+            if not os.path.exists(f):
+                done = True
+        return f
+
+class IPodNoteTwoWay(IPodBase):
+    """
+    Stores Notes on the iPod.
+    Rather than requiring a perfect transform to and from notes to the
+    ipod note format I also store the original note data in a
+    .conduit directory in the root of the iPod.
+
+    Notes are saved as title.txt and a copy of the raw note is saved as
+    title.note
+
+    LUID is the note title
+    """
+
+    _name_ = _("Notes")
+    _description_ = _("Synchronize your iPod notes")
+    _module_type_ = "twoway"
+    _in_type_ = "note"
+    _out_type_ = "note"
+    _icon_ = "note"
+
+    # datatypes.Note doesn't care about encoding,
+    # lets be naive and assume that all notes are utf-8
+    ENCODING_DECLARATION = '<?xml encoding="utf-8"?>'
+
+    def __init__(self, *args):
+        IPodBase.__init__(self, *args)
+
+        self.dataDir = os.path.join(self.mountPoint, 'Notes')
+        self.objects = []
+
+    def _get_shadow_dir(self):
+        shadowDir = os.path.join(self.mountPoint, '.conduit')
+        if not os.path.exists(shadowDir):
+            os.mkdir(shadowDir)
+        return shadowDir
+
+    def _get_note_from_ipod(self, uid):
+        """
+        Gets a note from the ipod, If the pickled shadow copy exists
+        then return that
+        """
+        rawNoteURI = os.path.join(self._get_shadow_dir(),uid)
+        if os.path.exists(rawNoteURI):
+            raw = open(rawNoteURI,'rb')
+            try:
+                n = pickle.load(raw)
+                raw.close()
+                return n
+            except:
+                raw.close()
+
+        noteURI = os.path.join(self.dataDir, uid)
+        noteFile = File.File(URI=noteURI)
+        #get the contents from the note, get the raw from the raw copy.
+        #the UID for notes from the ipod is the filename
+        n = Note.Note(
+                    title=uid,
+                    contents=noteFile.get_contents_as_text().replace(
+                        self.ENCODING_DECLARATION, '', 1),
+                    )
+        n.set_UID(uid)
+        n.set_mtime(noteFile.get_mtime())
+        n.set_open_URI(noteURI)
+        return n
+
+    def _save_note_to_ipod(self, uid, note):
+        """
+        Save a simple iPod note in /Notes
+        If the note has raw then also save that in shadowdir
+        uid is the note title.
+        """
+        # the normal note viewed by the iPod
+        # inject an encoding declaration if it is missing.
+        contents = note.get_contents()
+        if not self.ENCODING_DECLARATION in contents:
+            contents = ''.join([self.ENCODING_DECLARATION, contents])
+        ipodnote = Utils.new_tempfile(contents)
+
+        ipodnote.transfer(os.path.join(self.dataDir,uid), overwrite=True)
+        ipodnote.set_mtime(note.get_mtime())
+        ipodnote.set_UID(uid)
+
+        #the raw pickled note for sync
+        raw = open(os.path.join(self._get_shadow_dir(),uid),'wb')
+        pickle.dump(note, raw, -1)
+        raw.close()
+
+        return ipodnote.get_rid()
+
+    def _note_exists(self, uid):
+        #Check if both the shadow copy and the ipodified version exists
+        shadowDir = self._get_shadow_dir()
+        return os.path.exists(os.path.join(shadowDir,uid)) and os.path.exists(os.path.join(self.dataDir,uid))
+
+    def get(self, LUID):
+        DataProvider.TwoWay.get(self, LUID)
+        return self._get_note_from_ipod(LUID)
+
+    def put(self, note, overwrite, LUID=None):
+        """
+        The LUID for a note in the iPod is the note title
+        """
+        DataProvider.TwoWay.put(self, note, overwrite, LUID)
+
+        if LUID != None:
+            #Check if both the shadow copy and the ipodified version exists
+            if self._note_exists(LUID):
+                if overwrite == True:
+                    #replace the note
+                    log.debug("Replacing Note %s" % LUID)
+                    return self._save_note_to_ipod(LUID, note)
+                else:
+                    #only overwrite if newer
+                    log.warn("OVERWRITE IF NEWER NOT IMPLEMENTED")
+                    return self._save_note_to_ipod(LUID, note)
+
+        #make a new note
+        log.warn("CHECK IF EXISTS, COMPARE, SAVE")
+        return self._save_note_to_ipod(note.title, note)
+
+    def delete(self, LUID):
+        IPodBase.delete(self, LUID)
+
+        raw = File.File(URI=os.path.join(self._get_shadow_dir(), LUID))
+        if raw.exists():
+            raw.delete()
+
+class IPodContactsTwoWay(IPodBase):
+
+    _name_ = _("Contacts")
+    _description_ = _("Synchronize your iPod contacts")
+    _module_type_ = "twoway"
+    _in_type_ = "contact"
+    _out_type_ = "contact"
+    _icon_ = "contact-new"
+
+    def __init__(self, *args):
+        IPodBase.__init__(self, *args)
+        self.dataDir = os.path.join(self.mountPoint, 'Contacts')
+
+    def get(self, LUID):
+        DataProvider.TwoWay.get(self, LUID)
+        fullpath = os.path.join(self.dataDir, LUID)
+        f = File.File(URI=fullpath)
+
+        contact = Contact.Contact()
+        contact.set_from_vcard_string(f.get_contents_as_text())
+        contact.set_open_URI(fullpath)
+        contact.set_mtime(f.get_mtime())
+        contact.set_UID(LUID)
+        return contact
+
+    def put(self, contact, overwrite, LUID=None):
+        DataProvider.TwoWay.put(self, contact, overwrite, LUID)
+
+        if LUID != None:
+            f = Utils.new_tempfile(contact.get_vcard_string())
+            f.transfer(os.path.join(self.dataDir, LUID), overwrite=True)
+            f.set_UID(LUID)
+            return f.get_rid()
+
+        return _string_to_unqiue_file(contact.get_vcard_string(), self.dataDir, 'contact')
+
+class IPodCalendarTwoWay(IPodBase):
+
+    _name_ = _("Calendar")
+    _description_ = _("Synchronize your iPod calendar")
+    _module_type_ = "twoway"
+    _in_type_ = "event"
+    _out_type_ = "event"
+    _icon_ = "contact-new"
+
+    def __init__(self, *args):
+        IPodBase.__init__(self, *args)
+        self.dataDir = os.path.join(self.mountPoint, 'Calendars')
+
+    def get(self, LUID):
+        DataProvider.TwoWay.get(self, LUID)
+        fullpath = os.path.join(self.dataDir, LUID)
+        f = File.File(URI=fullpath)
+
+        event = Event.Event()
+        event.set_from_ical_string(f.get_contents_as_text())
+        event.set_open_URI(fullpath)
+        event.set_mtime(f.get_mtime())
+        event.set_UID(LUID)
+        return event
+
+    def put(self, event, overwrite, LUID=None):
+        DataProvider.TwoWay.put(self, event, overwrite, LUID)
+
+        if LUID != None:
+            f = Utils.new_tempfile(event.get_ical_string())
+            f.transfer(os.path.join(self.dataDir, LUID), overwrite=True)
+            f.set_UID(LUID)
+            return f.get_rid()
+
+        return _string_to_unqiue_file(event.get_ical_string(), self.dataDir, 'event')
+
+class IPodPhotoSink(IPodBase):
+
+    _name_ = _("Photos")
+    _description_ = _("Synchronize your iPod photos")
+    _module_type_ = "sink"
+    _in_type_ = "file/photo"
+    _out_type_ = "file/photo"
+    _icon_ = "image-x-generic"
+    _configurable_ = True
+
+    SAFE_PHOTO_ALBUM = "Photo Library"
+
+    def __init__(self, *args):
+        IPodBase.__init__(self, *args)
+        self.db = gpod.PhotoDatabase(self.mountPoint)
+        self.album = None
+        self.update_configuration(
+            albumName = "Conduit"
+        )
+
+    def _set_sysinfo(self, modelnumstr, model):
+        #this must only be used from TestDataProvideriPod.py
+        gpod.itdb_device_set_sysinfo(self.db._itdb.device, modelnumstr, model)
+
+    def _get_photo_album(self, albumName):
+        for album in self.db.PhotoAlbums:
+            if album.name == albumName:
+                log.debug("Found album: %s" % albumName)
+                return album
+
+        log.debug("Creating album: %s" % albumName)
+        return self._create_photo_album(albumName)
+
+    def _create_photo_album(self, albumName):
+        if albumName in [a.name for a in self.db.PhotoAlbums]:
+            log.warn("Album already exists: %s" % albumName)
+            album = self._get_photo_album(albumName)
+        else:
+            album = self.db.new_PhotoAlbum(title=albumName)
+        return album
+
+    def _get_photo_by_id(self, id):
+        for album in self.db.PhotoAlbums:
+            for photo in album:
+                if str(photo['id']) == str(id):
+                    return photo
+        return None
+
+    def _delete_album(self, albumName):
+        if albumName == self.SAFE_PHOTO_ALBUM:
+            log.warn("Cannot delete album: %s" % self.SAFE_PHOTO_ALBUM)
+        else:
+            for album in self.db.PhotoAlbums:
+                if album.name == albumName:
+                    for photo in album[:]:
+                        album.remove(photo)
+                    self.db.remove(album)
+
+    def _delete_all_photos(self):
+        for album in self.db.PhotoAlbums:
+            for photo in album[:]:
+                album.remove(photo)
+            if album.name != self.SAFE_PHOTO_ALBUM:
+                self.db.remove(album)
+        gpod.itdb_photodb_write(self.db._itdb, None)
+
+    def _get_photo_albums(self):
+        i = []
+        for album in self.db.PhotoAlbums:
+            i.append(album.name)
+        return i
+
+    def refresh(self):
+        DataProvider.TwoWay.refresh(self)
+        self.album = self._get_photo_album(self.albumName)
+
+    def get_all(self):
+        uids = []
+        for photo in self.album:
+            uids.append(str(photo['id']))
+        return uids
+
+    def put(self, f, overwrite, LUID=None):
+        photo = self.db.new_Photo(filename=f.get_local_uri())
+        self.album = self._get_photo_album(self.albumName)
+        self.album.add(photo)
+        gpod.itdb_photodb_write(self.db._itdb, None)
+        return conduit.datatypes.Rid(str(photo['id']), None, hash(None))
+
+    def delete(self, LUID):
+        photo = self._get_photo_by_id(LUID)
+        if photo != None:
+            self.db.remove(photo)
+            gpod.itdb_photodb_write(self.db._itdb, None)
+
+    def config_setup(self, config):
+
+        def _delete_click(button):
+            self._delete_album(album_config.get_value())
+            album_config.choices = self._get_photo_albums()
+
+        def _delete_all_click(button):
+            self._delete_all_photos()
+
+        album_config = config.add_item(_('Album'), 'combotext',
+            config_name = 'albumName',
+            choices = self._get_photo_albums(),
+        )
+        config.add_item(_("Delete"), "button",
+            initial_value = _delete_click
+        )
+        config.add_item(_("Delete All Photos"), "button",
+            initial_value = _delete_all_click
+        )
+
+
+    def is_configured (self, isSource, isTwoWay):
+        return len(self.albumName) > 0
+
+    def uninitialize(self):
+        self.db.close()
+
+unicode_conv = lambda v: unicode(v).encode('UTF-8','replace')
+
+class IPodFileBase:
+    '''
+    A wrapper around an iPod track. iPod track properties are converted into
+    Media properties, and vice-versa.
+    '''
+    
+    #Mappings from the Media metadata to the iPod metadata and vice-versa, 
+    #including type-checking
+    media_to_ipod = {
+        'title' : ('title', unicode_conv),
+        'artist' : ('artist', unicode_conv),
+        'album' : ('album', unicode_conv),
+        'composer' : ('composer', unicode_conv),
+        'rating' : ('rating', lambda v: float(v) / 0.05),
+        'genre' : ('genre', unicode_conv),
+        'track_nr' : ('track-number', int),
+        'tracks' : ('track-count', int),
+        'bitrate' : ('bitrate', int),
+        'tracklen' : ('duration', int),
+        'samplerate' : ('samplerate', int),
+        'width' : ('width', int),
+        'height' : ('height', int),
+    }
+    
+    ipod_to_media = {
+        'title' : ('title', unicode_conv),
+        'artist' : ('artist', unicode_conv),
+        'album' : ('album', unicode_conv),
+        'composer' : ('composer', unicode_conv),
+        'rating' : ('rating', lambda v: float(v) * 0.05),
+        'genre' : ('genre', unicode_conv),
+        'track-number' : ('track_nr', int),
+        'track-count' : ('tracks', int),
+        'bitrate' : ('bitrate', int),
+        'duration' : ('tracklen', int),
+        'samplerate' : ('samplerate', int),
+        'width' : ('width', int),
+        'height' : ('height', int),        
+    }
+
+    def __init__(self, db, track = None, f = None):
+        '''
+        Wraps an iPod track in a Datatype. 
+        Passing a file creates a new track in the iPod db, with media information 
+        from that file. Use copy_ipod to transfer it into the iPod.
+        Passing an existing iPod track exports the track's information as a 
+        Media datatype.
+        
+        @param ipod_track: An iPod track to wrap
+        @param f: A File to extract the information from
+        '''
+        self.db = db
+        if track:
+            self.track = track
+        else:
+            self.track = self.db.new_Track()
+        if f:
+            self.set_info_from_file(f)
+        
+    def get_UID(self):
+        '''
+        Returns the database ID (usually a random number, which is always valid
+        for this track in this db, even across application restarts)
+        '''
+        return str(self.track['dbid'])
+
+    def _convert_tags(self, from_tags, mapping):
+        '''
+        Convert from one mapping to another.
+        Returns an iterator with (name, value) for each tag in from_tags
+        '''
+        for from_name, from_value in from_tags.iteritems():
+            if from_name in mapping:
+                to_name, to_converter = mapping[from_name]
+                try:
+                    to_value = to_converter(from_value)
+                    yield to_name, to_value
+                except Exception, e:
+                    log.warn("Could not convert property %s: %s as %s. (Error: %s)" % (from_name, from_value, to_converter, e))
+
+    def set_info_from_file(self, f):
+        '''
+        Get the track information from a file, including the metadata.
+        Works best with GStreamer metadata in MediaFile.
+        '''
+        tags = f.get_media_tags()
+        for name, value in self._convert_tags(tags, self.media_to_ipod):
+            #log.debug("Got %s = %s" % (name, value))
+            self.track[name] = value
+        #Make sure we have a title to this song, even if it's just the filename
+        if self.track['title'] is None:
+            self.track['title'] = os.path.basename(f.get_local_uri())
+        self.track['time_modified'] = os.stat(f.get_local_uri()).st_mtime
+        self.track['time_added'] = int(time.time())
+        self.track['userdata'] = {'transferred': 0,
+                                  'hostname': socket.gethostname(),
+                                  'charset': locale.getpreferredencoding()}
+        self.track._set_userdata_utf8('filename', f.get_local_uri())
+        
+    def get_track_filename(self):
+        filename = self.track.ipod_filename()
+        if not filename or not os.path.exists(filename):
+            filename = self.track._userdata_into_default_locale('filename')
+        return filename
+        
+    def get_hash(self):
+        return str(hash(tuple(self.get_media_tags())))
+
+    def get_snippet(self):
+        return "%(artist)s - %(title)s" % track
+
+    def get_media_tags(self):
+        '''
+        Extends the MediaFile class to include the iPod metadata, instead of
+        calling the GStreamer loader. It's much faster this way, and provides
+        some nice information to other dataproviders, like ratings.
+        '''
+        #FIXME: Cache this information
+        
+        #Get the information from the iPod track.
+        #The track might look like a dict, but it isnt, so we make it into one.
+        track_tags = dict(self.track.pairs())
+        return dict(self._convert_tags(track_tags, self.ipod_to_media))
+
+    #FIXME: Remove this. Use native operations from Conduit instead.
+    #       We would have to define the transfered userdata as 1 and then call
+    #       Conduit to copy the file. 
+    #       But that is Conduit's copy file way?
+    def copy_ipod(self):
+        self.track.copy_to_ipod()
+
+class IPodAudio(IPodFileBase, Audio.Audio):
+    def __init__(self, *args, **kwargs):
+        '''
+        Initialize a new Audio track for this db and file.
+        '''
+        IPodFileBase.__init__(self, *args, **kwargs)
+        Audio.Audio.__init__(self, URI = self.get_track_filename())
+
+    def set_info_from_file(self, audio):
+        IPodFileBase.set_info_from_file(self, audio)
+        self.track['mediatype'] = gpod.ITDB_MEDIATYPE_AUDIO
+        cover_location = audio.get_audio_cover_location()
+        if cover_location:
+            self.track.set_coverart_from_file(str(cover_location))
+
+class IPodVideo(IPodFileBase, Video.Video):
+    def __init__(self, *args, **kwargs):
+        '''
+        Initialize a new Video track for this db and file.
+        '''
+        IPodFileBase.__init__(self, *args, **kwargs)
+        Video.Video.__init__(self, URI = self.get_track_filename())
+        
+        log.debug('Video kind selected: %s' % (kwargs['video_kind']))
+        self.video_kind = kwargs['video_kind']
+        
+    def set_info_from_file(self, video):
+        IPodFileBase.set_info_from_file(video)
+        self.track['mediatype'] = {'movie': gpod.ITDB_MEDIATYPE_MOVIE,
+                                   'musicvideo': gpod.ITDB_MEDIATYPE_MUSICVIDEO,
+                                   'tvshow': gpod.ITDB_MEDIATYPE_TVSHOW,
+                                   'podcast': gpod.ITDB_MEDIATYPE_PODCAST
+                                   } [self.video_kind]
+
+class DBCache:
+    '''
+    Keeps a list of open GPod databases.
+
+    Keeps one database open for each mount-point.
+    Automatically disposes unused databases.
+    '''
+    __db_list = weakref.WeakValueDictionary()
+    __db_locks = weakref.WeakKeyDictionary()
+    __lock = threading.Lock()
+
+    @classmethod
+    def get_db(self, mount_point):
+        self.__lock.acquire()
+        try:
+            if mount_point in self.__db_list:
+                log.debug('Getting DB in cache for %s' % (mount_point))
+                db = self.__db_list[mount_point]
+            else:
+                if mount_point:
+                    log.debug('Creating DB for %s' % mount_point)
+                    db = gpod.Database(mount_point)
+                else:
+                    log.debug('Creating local DB')
+                    db = gpod.Database(local=True)
+                self.__db_list[mount_point] = db
+                self.__db_locks[db] = threading.Lock()
+            return db
+        finally:
+            self.__lock.release()
+
+    @classmethod
+    def release_db(self, db):
+        assert db in self.__db_locks
+        # We dont do nothing here yet, but we could use to release resources.
+        # The db is automatically removed from the list because of the weak 
+        # reference.
+        log.debug('Releasing DB for %s' % db)
+
+    @classmethod
+    def lock_db(self, db):
+        assert db in self.__db_locks
+        log.debug('Locking DB %s' % db)
+        self.__db_locks[db].acquire()
+
+    @classmethod
+    def unlock_db(self, db):
+        assert db in self.__db_locks
+        log.debug('Unlocking DB %s' % db)
+        self.__db_locks[db].release()
+
+class IPodMediaTwoWay(IPodBase):
+    FORMAT_CONVERSION_STRING = _("Encoding")
+
+    def __init__(self, *args):
+        self.local_db = (len(args) == 0)
+        if not self.local_db:
+            IPodBase.__init__(self, *args)
+        else:
+            # Use local database for testing
+            DataProvider.TwoWay.__init__(self)        
+            self.uid = "Local"
+        self.db = None
+        #self.tracks = {}
+        self.tracks_id = {}
+        self.track_args = {}
+        self.update_configuration(
+            keep_converted = True,
+        )
+        
+    def get_db(self):
+        if self.db:
+            DBCache.lock_db(self.db)
+            return self.db
+        if not self.local_db:
+            self.db = DBCache.get_db(self.mountPoint)
+        else:
+            self.db = DBCache.get_db(None)        
+        DBCache.lock_db(self.db)
+        return self.db
+    
+    def unlock_db(self):
+        DBCache.unlock_db(self.db)
+        
+    def release_db(self):
+        if not self.db:
+            return
+        self.db.close()
+        DBCache.release_db(self.db)
+        self.db = None        
+
+    def refresh(self):
+        DataProvider.TwoWay.refresh(self)
+        self.tracks = {}
+        self.tracks_id = {}
+        self.get_db()
+        try:
+            def add_track(track):
+                self.tracks_id[str(track['dbid'])] = track
+            [add_track(track) for track in self.db \
+                if track['mediatype'] in self._mediatype_]
+        finally:
+            self.unlock_db()
+
+    def get_all(self):
+        return self.tracks_id.keys()
+
+    def get(self, LUID = None):
+        self.get_db()
+        try:
+            if LUID not in self.tracks_id:
+                raise Exceptions.SyncronizeError('Track ID %s not found in iPod DB %s' % (LUID, self.db))
+            track = self.tracks_id[LUID]
+            ipod_file = self._ipodmedia_(self.db, track = track)
+            filename = ipod_file.get_track_filename()
+            if not os.path.exists(filename):
+                raise Exceptions.SyncronizeError("Could not find iPod track file %s" % (filename))
+            #Set a nice "Artist - Title" name with the original filename
+            #extension
+            #FIXME: Doesnt work as expected anymore, the original filename is
+            #renamed instead
+            #if track.ipod_filename() and track['artist'] and track['title']:
+            #    ipod_file.force_new_filename("%(artist)s - %(title)s" % track + \
+            #        os.path.splitext(filename)[1])
+            return ipod_file
+        finally:
+            self.unlock_db()
+        return None
+
+    def put(self, f, overwrite, LUID=None):
+        self.get_db()
+        try:
+            if LUID and LUID in self.tracks_id:
+                track = self.tracks_id[LUID]
+                media_file = self._ipodmedia_(db = self.db, track = track, f = f, **self.track_args)
+            else:
+                media_file = self._ipodmedia_(db = self.db, f = f, **self.track_args)
+            #FIXME: We keep the db locked while we copy the file. Not good.
+            media_file.copy_ipod()
+            self.tracks_id[str(media_file.track['dbid'])] = media_file.track
+            #FIXME: Writing the db here is for debug only. Closing does not actually
+            # close the db, it only writes it's contents to disk.            
+            # Sometimes, if we only close the db when the sync is over, it might
+            # take a long time to close the db, because many files are being 
+            # copied to the iPod. Closing the DB every time not only keeps
+            # this time small, but also keeps the db more consistent in case of 
+            # a crash. But it also incurs a big overhead. 
+            # Maybe a batch update could be a better solution (close after 5 tracks?)
+            self.db.close()
+            return media_file
+        finally:
+            self.unlock_db()
+
+    def delete(self, LUID):
+        track = self.tracks_id[LUID]
+        if track:
+            self.get_db()
+            try:
+                self.db.remove(track)
+                self.db.close()
+            finally:
+                self.unlock_db()
+
+    def config_setup(self, config):
+        #Get an array of encodings, so it can be indexed inside a combobox
+        encodings = [(enc_name, enc_opts.get('description', None) or enc_name)
+                      for enc_name, enc_opts in self.encodings.iteritems()]
+        
+        config.add_section(_("Conversion options"))
+        config.add_item(_("Encoding"), "combo", 
+            config_name = "encoding",
+            choices = encodings
+        )
+        config.add_item(_("Keep converted files"), "check",
+            config_name = "keep_converted"
+        )
+
+    def get_input_conversion_args(self):
+        try:
+            args = self.encodings[self.encoding]
+            # FIXME
+            # If we pass the bool in the args, it will become a string, and 
+            # will always return True later in the converter.
+            # So we only pass it if is True. When it's False, not being there
+            # tells the converter it isn't True.
+            # I'm not sure it was supposed to work like this.
+            if self.keep_converted:
+                args['keep_converted'] = True
+            return args
+        except KeyError:
+            return {}
+
+    def uninitialize(self):
+        self.release_db()
+
+IPOD_AUDIO_ENCODINGS = {
+    "mp3": {"description": "Mp3", "acodec": "lame", "file_extension": "mp3"},
+    #FIXME: AAC needs a MP4 mux
+    #"aac": {"description": "AAC", "acodec": "faac", "file_extension": "m4a"},
+    }
+
+class IPodMusicTwoWay(IPodMediaTwoWay):
+
+    _name_ = _("Music")
+    _description_ = _("Synchronize your iPod music")
+    _module_type_ = "twoway"
+    _in_type_ = "file/audio"
+    _out_type_ = "file/audio"
+    _icon_ = "audio-x-generic"
+    _configurable_ = True
+
+    _mediatype_ = (gpod.ITDB_MEDIATYPE_AUDIO,)
+    _ipodmedia_ = IPodAudio
+
+    def __init__(self, *args):
+        IPodMediaTwoWay.__init__(self, *args)
+        self.encodings = IPOD_AUDIO_ENCODINGS
+        self.update_configuration(
+            encoding = 'mp3',
+        )
+
+IPOD_VIDEO_ENCODINGS = {
+    #FIXME: Add iPod mpeg4 restrictions. Follow:
+    # http://rob.opendot.cl/index.php/useful-stuff/ffmpeg-x264-encoding-guide/
+    "mp4_x264":{"description": "MP4 (Better quality - H.264)","vcodec":"x264enc", "acodec":"faac", 
+        "format":"ffmux_mp4", "file_extension":"m4v", "width": 320, "height": 240, 
+        "mimetype": "video/mp4"},
+    #FIXME: Two-pass encoding is not working. The first pass never finishes.
+    #"mp4_x264_twopass":{"description": "MP4 (H.264, Two-pass EXPERIMENTAL)", 
+    #    "vcodec_pass1":"x264enc pass=1", "vcodec_pass2":"x264enc pass=2", 
+    #    "acodec":"faac", "format":"ffmux_mp4", "file_extension":"m4v", 
+    #    "width": 320, "height": 240, "mimetype": "video/mp4", 'twopass':True},
+    "mp4_xvid":{"description": "MP4 (Faster conversion - XVid)","vcodec":"ffenc_mpeg4", "acodec":"faac",
+        "format":"ffmux_mp4", "file_extension":"m4v", "width": 320, "height": 240, 
+        "mimetype": "video/mp4"},
+    }
+
+class IPodVideoTwoWay(IPodMediaTwoWay):
+
+    _name_ = _("Video")
+    _description_ = _("Synchronize your iPod videos")
+    _module_type_ = "twoway"
+    _in_type_ = "file/video"
+    _out_type_ = "file/video"
+    _icon_ = "video-x-generic"
+    _configurable_ = True
+
+    _mediatype_ = (gpod.ITDB_MEDIATYPE_MUSICVIDEO, gpod.ITDB_MEDIATYPE_MOVIE, gpod.ITDB_MEDIATYPE_TVSHOW)
+    _ipodmedia_ = IPodVideo
+
+    def __init__(self, *args):
+        IPodMediaTwoWay.__init__(self, *args)
+        self.encodings = IPOD_VIDEO_ENCODINGS
+        self.update_configuration(
+            encoding = 'mp4_x264',
+            video_kind = 'movie',
+        )
+        self._update_track_args()
+        
+    def _update_track_args(self):
+        self.track_args['video_kind'] = self.video_kind
+
+    def config_setup(self, config):
+        IPodMediaTwoWay.config_setup(self, config)
+        video_kinds = [('movie', _('Movie')), 
+                       ('musicvideo', _('Music Video')),
+                       ('tvshow', _('TV Show'))]            
+        config.add_section()
+        config.add_item(_("Video kind"), "combo",
+            config_name = "video_kind",
+            choices = video_kinds)
+        
+    def set_configuration(self, config):
+        IPodMediaTwoWay.set_configuration(self, config)
+        #FIXME Move this to update_configuration callback
+        self._update_track_args()



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]