[conduit] Add Shotwell support



commit fb32c54745b9d48af2c673bf27f1e589ae71af46
Author: Nathaniel Harward <nharward gmail com>
Date:   Wed Sep 8 21:31:36 2010 +1200

    Add Shotwell support

 conduit/modules/Makefile.am                        |    1 +
 conduit/modules/ShotwellModule/Makefile.am         |    7 +
 conduit/modules/ShotwellModule/ShotwellModule.py   |  100 +++++++++
 .../modules/ShotwellModule/shotwell/Makefile.am    |    5 +
 .../modules/ShotwellModule/shotwell/__init__.py    |  233 ++++++++++++++++++++
 5 files changed, 346 insertions(+), 0 deletions(-)
---
diff --git a/conduit/modules/Makefile.am b/conduit/modules/Makefile.am
index c39932c..4e059a2 100644
--- a/conduit/modules/Makefile.am
+++ b/conduit/modules/Makefile.am
@@ -8,6 +8,7 @@ SUBDIRS = \
 	FlickrModule \
 	FspotModule \
 	iPodModule \
+	ShotwellModule \
 	SmugMugModule \
 	EvolutionModule \
 	GoogleModule \
diff --git a/conduit/modules/ShotwellModule/Makefile.am b/conduit/modules/ShotwellModule/Makefile.am
new file mode 100644
index 0000000..cb4f5c2
--- /dev/null
+++ b/conduit/modules/ShotwellModule/Makefile.am
@@ -0,0 +1,7 @@
+SUBDIRS = shotwell
+
+conduit_handlersdir = $(libdir)/conduit/modules/ShotwellModule
+conduit_handlers_PYTHON = ShotwellModule.py
+
+clean-local:
+	rm -rf *.pyc *.pyo
diff --git a/conduit/modules/ShotwellModule/ShotwellModule.py b/conduit/modules/ShotwellModule/ShotwellModule.py
new file mode 100644
index 0000000..b35a54d
--- /dev/null
+++ b/conduit/modules/ShotwellModule/ShotwellModule.py
@@ -0,0 +1,100 @@
+import conduit
+import conduit.dataproviders.DataProvider as DataProvider
+import conduit.datatypes.Photo as Photo
+import conduit.utils as Utils
+
+import logging
+log = logging.getLogger('modules.Shotwell')
+
+from gettext import gettext as _
+
+try:
+    import shotwell
+except ImportError:
+    Utils.dataprovider_add_dir_to_path(__file__)
+    import shotwell
+
+MODULES = {
+    "ShotwellDataProvider" : { "type": "dataprovider" }
+}
+
+# Why is this not in the standard library?
+def _flatten(lst):
+    for elem in lst:
+        if type(elem) in (tuple, list):
+            for i in _flatten(elem):
+                yield i
+        else:
+            yield elem
+
+class ShotwellDataProvider(DataProvider.DataSource):
+
+
+    _name_ = _('Shotwell')
+    _description_ = _('Sync from your Shotwell photo library')
+    _icon_ = 'shotwell'
+    _category_ = conduit.dataproviders.CATEGORY_PHOTOS
+    _module_type_ = 'source'
+    _configurable_ = True
+    _out_type_ = 'file/photo'
+
+    _enabled = False
+    _selected_tag_names = []
+    _shotwell_photos = []
+
+    def __init__(self, *args):
+        DataProvider.DataSource.__init__(self)
+        self.update_configuration(tags = ([], self.set_tags, self.get_tags))
+        try:
+            shotwell_db = shotwell.ShotwellDB()
+            shotwell_db.close()
+            self._enabled = True
+        except:
+            log.warn('Disabling Shotwell module, open of sqlite3 DB failed')
+            self._enabled = False
+
+    def initialize(self):
+        DataProvider.DataSource.initialize(self)
+        return self._enabled
+
+    def set_tags(self, tags):
+        self._selected_tag_names = map(lambda x: str(x), tags)
+
+    def get_tags(self):
+        return self._selected_tag_names
+
+    def config_setup(self, config):
+        shotwell_db = shotwell.ShotwellDB()
+        config.add_section(_('Tags'))
+        all_tag_names = map(lambda sTag: sTag.name, shotwell_db.tags())
+        config.add_item(_('Tags'), 'list', config_name = 'tags',
+                        choices = all_tag_names)
+        shotwell_db.close()
+
+    def refresh(self):
+        DataProvider.DataSource.refresh(self)
+        shotwell_db = shotwell.ShotwellDB()
+        tags = filter(lambda sTag: sTag.name in self._selected_tag_names,
+                      shotwell_db.tags())
+        tagged_photos = list(_flatten(map(lambda sTag: sTag.photoIDs, tags)))
+        self._shotwell_photos = filter(lambda sPhoto: str(sPhoto.id) in \
+                                       tagged_photos, shotwell_db.photos())
+        log.debug('Found %i photos to sync', len(self._shotwell_photos))
+        shotwell_db.close()
+
+    def get_all(self):
+        DataProvider.DataSource.get_all(self)
+        return map(lambda sPhoto: str(sPhoto.id), self._shotwell_photos)
+
+    def get(self, LUID):
+        DataProvider.DataSource.get(self, LUID)
+        sPhoto = filter(lambda sPhoto: str(sPhoto.id) == LUID,
+                        self._shotwell_photos)[0]
+        photo = Photo.Photo('file://' + sPhoto.filename)
+        photo.set_UID(LUID)
+        photo.set_caption(sPhoto.title)
+        return photo
+
+    def get_UID(self):
+        return Utils.get_user_string()
+
diff --git a/conduit/modules/ShotwellModule/shotwell/Makefile.am b/conduit/modules/ShotwellModule/shotwell/Makefile.am
new file mode 100644
index 0000000..e370439
--- /dev/null
+++ b/conduit/modules/ShotwellModule/shotwell/Makefile.am
@@ -0,0 +1,5 @@
+conduit_handlersdir = $(libdir)/conduit/modules/ShotwellModule/shotwell
+conduit_handlers_PYTHON = __init__.py
+
+clean-local:
+	rm -rf *.pyc *.pyo
diff --git a/conduit/modules/ShotwellModule/shotwell/__init__.py b/conduit/modules/ShotwellModule/shotwell/__init__.py
new file mode 100644
index 0000000..4ef3434
--- /dev/null
+++ b/conduit/modules/ShotwellModule/shotwell/__init__.py
@@ -0,0 +1,233 @@
+import os.path
+import sqlite3
+import string
+import sys
+
+class Version(object):
+
+
+    def __init__(self, schemaVersion, appVersion, userData=None):
+        self._schemaVersion = schemaVersion
+        self._appVersion = appVersion
+        self._userData = userData
+
+    def __str__(self):
+        return 'Version(' + str(map(lambda key: str(key) + '=' +
+               str(self.__dict__[key]), self.__dict__.keys())) + ')'
+
+    @property
+    def schemaVersion(self):
+        return self._schemaVersion
+
+    @property
+    def appVersion(self):
+        return self._appVersion
+
+    @property
+    def userData(self):
+        return self._userData
+
+
+class Event(object):
+
+
+    def __init__(self, id, name):
+        self._id = id
+        self._name = name
+
+    def __str__(self):
+        return 'Event(' + str(map(lambda key: str(key) + '=' +
+               str(self.__dict__[key]), self.__dict__.keys())) + ')'
+
+    @property
+    def id(self):
+        return self._id
+
+    @property
+    def name(self):
+        return self._name
+
+
+class Tag(object):
+
+
+    def __init__(self, id, name, photo_id_list_csv):
+        self._id = id
+        self._name = name
+        self._photoIDs = filter(lambda x: x != None and len(x) > 0,
+                                string.split(photo_id_list_csv, ','))
+
+    def __str__(self):
+        return 'Tag(id=' + str(self.id) + ';name=' + str(self.name) + \
+               ';num photos=' + str(len(self.photoIDs)) + ')'
+
+    @property
+    def id(self):
+        return self._id
+
+    @property
+    def name(self):
+        return self._name
+
+    @property
+    def photoIDs(self):
+        return self._photoIDs
+
+
+class Photo(object):
+
+
+    def __init__(self, id, filename, width=None, height=None, filesize=None,
+                 timestamp=None, eventID=None, title=''):
+        self._id = id
+        self._filename = filename
+        self._width = width
+        self._height = height
+        self._filesize = filesize
+        self._timestamp = timestamp
+        self._eventID = eventID
+        self._title = title
+
+    def __str__(self):
+        return 'Photo(' + str(map(lambda key: str(key) + '=' +
+               str(self.__dict__[key]), self.__dict__.keys())) + ')'
+
+    @property
+    def id(self):
+        return self._id
+
+    @property
+    def filename(self):
+        return self._filename
+
+    @property
+    def width(self):
+        return self._width
+
+    @property
+    def height(self):
+        return self._height
+
+    @property
+    def filesize(self):
+        return self._filesize
+
+    @property
+    def timestamp(self):
+        return self._timestamp
+
+    @property
+    def eventID(self):
+        return self._eventID
+
+    @property
+    def title(self):
+        return self._title
+
+
+class _ReadOnlySqlite3Database(object):
+
+
+    def __init__(self, dbFile):
+        self._con = sqlite3.connect(dbFile)
+        self._con.row_factory = sqlite3.Row
+
+    def _selectOne(self, selectSQL, rowMapper):
+        return self._selectMany(selectSQL, rowMapper)[0]
+
+    def _selectMany(self, selectSQL, rowMapper):
+        cursor = self._con.cursor()
+        try:
+            cursor.execute(selectSQL)
+            return map(rowMapper, cursor.fetchall())
+        finally:
+            cursor.close()
+
+    def close(self):
+        if self._con != None:
+            self._con.close()
+            self._con = None
+
+
+class RowMapper:
+
+
+    @staticmethod
+    def version(row):
+        return Version(row['schema_version'], row['app_version'],
+                       row['user_data'])
+
+    @staticmethod
+    def event(row):
+        return Event(row['id'], row['name'])
+
+    @staticmethod
+    def tag(row):
+        return Tag(row['id'], row['name'], row['photo_id_list'])
+
+    @staticmethod
+    def photo(row):
+        return Photo(row['id'], row['filename'], row['width'], row['height'],
+                     row['filesize'], row['timestamp'], row['event_id'])
+
+    @staticmethod
+    def photo_with_title(row):
+        return Photo(row['id'], row['filename'], row['width'], row['height'],
+                     row['filesize'], row['timestamp'], row['event_id'],
+                     row['title'])
+
+
+class ShotwellDB(_ReadOnlySqlite3Database):
+
+
+    _PHOTO_SQL_NO_TITLE   = 'select id, filename, width, height, filesize,' + \
+                            ' timestamp, event_id from PhotoTable'
+    _PHOTO_SQL_WITH_TITLE = 'select id, filename, width, height, filesize,' + \
+                            ' timestamp, event_id, title from PhotoTable'
+
+    def __init__(self, sqlitePath=os.path.join(os.path.expanduser('~'),
+                                               '.shotwell', 'data',
+                                               'photo.db')):
+        _ReadOnlySqlite3Database.__init__(self, sqlitePath)
+
+    def __str__(self):
+        return 'ShotwellDB(' + str(map(lambda key: str(key) + '=' +
+               str(self.__dict__[key]), self.__dict__.keys())) + ')'
+
+    def version(self):
+        return self._selectOne('select schema_version, app_version,' +
+                               ' user_data from VersionTable',
+                               RowMapper.version)
+
+    def event(self, id):
+        return self._selectOne('select id, name from EventTable where id = ' +
+                               str(id), RowMapper.event)
+
+    def events(self):
+        return self._selectMany('select id, name from EventTable',
+                                RowMapper.event)
+
+    def tag(self, id):
+        return self._selectOne('select id, name, photo_id_list from' +
+                               ' TagTable where id = ' + str(id),
+                               RowMapper.tag)
+
+    def tags(self):
+        return self._selectMany('select id, name, photo_id_list from TagTable',
+                                RowMapper.tag)
+
+    def photo(self, id):
+        if self.version().schemaVersion < 5:
+            return self._selectOne(self._PHOTO_SQL_NO_TITLE + ' where id = ' +
+                                   str(id), RowMapper.photo)
+        else:
+            return self._selectOne(self._PHOTO_SQL_WITH_TITLE +
+                                   ' where id = ' + str(id),
+                                   RowMapper.photo_with_title)
+
+    def photos(self):
+        if self.version().schemaVersion < 5:
+            return self._selectMany(self._PHOTO_SQL_NO_TITLE, RowMapper.photo)
+        else:
+            return self._selectMany(self._PHOTO_SQL_WITH_TITLE,
+                                    RowMapper.photo_with_title)



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