Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
path: root/src/gdatastore/datastore.py
diff options
context:
space:
mode:
Diffstat (limited to 'src/gdatastore/datastore.py')
-rw-r--r--src/gdatastore/datastore.py264
1 files changed, 264 insertions, 0 deletions
diff --git a/src/gdatastore/datastore.py b/src/gdatastore/datastore.py
new file mode 100644
index 0000000..5593432
--- /dev/null
+++ b/src/gdatastore/datastore.py
@@ -0,0 +1,264 @@
+#
+# Author: Sascha Silbe <sascha-pgp@silbe.org>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 3
+# as published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+"""
+Gdatastore D-Bus service API
+"""
+
+import hashlib
+import logging
+import uuid
+
+import dbus
+import dbus.service
+import gconf
+
+
+DS_SERVICE_SUGAR_V1 = 'org.laptop.sugar.DataStore'
+DS_DBUS_INTERFACE_SUGAR_V1 = 'org.laptop.sugar.DataStore'
+DS_OBJECT_PATH_SUGAR_V1 = '/org/laptop/sugar/DataStore'
+DS_DBUS_INTERFACE_SUGAR_V2 = 'org.laptop.sugar.DataStore2'
+DS_OBJECT_PATH_SUGAR_V2 = '/org/laptop/sugar/DataStore2'
+
+
+class DBusApiSugarV1(dbus.service.Object):
+ """Compatibility layer for the old Sugar data store D-Bus API
+ """
+
+ def __init__(self, internal_api):
+ self._internal_api = internal_api
+ bus_name = dbus.service.BusName(DS_SERVICE_SUGAR_V1,
+ bus=dbus.SessionBus(),
+ replace_existing=False,
+ allow_replacement=False)
+ dbus.service.Object.__init__(self, bus_name, DS_OBJECT_PATH_SUGAR_V1)
+
+ @dbus.service.method(DS_DBUS_INTERFACE_SUGAR_V1,
+ in_signature='a{sv}sb', out_signature='s',
+ async_callbacks=('async_cb', 'async_err_cb'),
+ byte_arrays=True)
+ def create(self, props, file_path, transfer_ownership,
+ async_cb, async_err_cb):
+ def success_cb(tree_id, child_id):
+ self.Created(tree_id)
+ async_cb(tree_id)
+
+ self._internal_api.save(tree_id='', parent_id='', metadata=props,
+ path=file_path,
+ delete_after=transfer_ownership,
+ async_cb=success_cb,
+ async_err_cb=async_err_cb)
+
+ @dbus.service.signal(DS_DBUS_INTERFACE_SUGAR_V1, signature='s')
+ def Created(self, uid):
+ # pylint: disable-msg=C0103
+ pass
+
+ @dbus.service.method(DS_DBUS_INTERFACE_SUGAR_V1,
+ in_signature='sa{sv}sb', out_signature='',
+ async_callbacks=('async_cb', 'async_err_cb'),
+ byte_arrays=True)
+ def update(self, uid, props, file_path, transfer_ownership,
+ async_cb, async_err_cb):
+ def success_cb(tree_id, child_id):
+ self.Updated(tree_id)
+ async_cb()
+
+ latest_versions = self._get_latest(uid)
+ if not latest_versions:
+ raise ValueError('Trying to update non-existant entry %s - wanted'
+ ' to use create()?' % (uid, ))
+
+ parent = latest_versions[0]
+ if self._compare_checksums(parent, file_path):
+ self._internal_api.change_metadata(parent['tree_id'],
+ parent['version_id'], props)
+ return success_cb(uid, None)
+
+ self._internal_api.save(tree_id=uid,
+ parent_id=parent['version_id'], metadata=props,
+ path=file_path, delete_after=transfer_ownership,
+ async_cb=success_cb, async_err_cb=async_err_cb)
+
+ @dbus.service.signal(DS_DBUS_INTERFACE_SUGAR_V1, signature='s')
+ def Updated(self, uid):
+ # pylint: disable-msg=C0103
+ pass
+
+ @dbus.service.method(DS_DBUS_INTERFACE_SUGAR_V1,
+ in_signature='a{sv}as', out_signature='aa{sv}u')
+ def find(self, query, properties):
+ if 'uid' in properties:
+ properties.append('tree_id')
+ properties.remove('uid')
+
+ options = {'metadata': properties}
+ for name in ['offset', 'limit', 'order_by']:
+ if name in query:
+ options[name] = query.pop(name)
+
+ if 'uid' in query:
+ query['tree_id'] = query.pop('uid')
+
+ results, count = self._internal_api.find(query, options,
+ query.get('query'))
+
+ if 'tree_id' in properties:
+ for entry in results:
+ entry['uid'] = entry.pop('tree_id')
+
+ return results, count
+
+ @dbus.service.method(DS_DBUS_INTERFACE_SUGAR_V1,
+ in_signature='s', out_signature='s',
+ sender_keyword='sender')
+ def get_filename(self, uid, sender=None):
+ latest_versions = self._get_latest(uid)
+ if not latest_versions:
+ raise ValueError('Entry %s does not exist' % (uid, ))
+
+ return self._internal_api.get_data(uid,
+ latest_versions[0]['version_id'], sender=sender)
+
+ @dbus.service.method(DS_DBUS_INTERFACE_SUGAR_V1,
+ in_signature='s', out_signature='a{sv}')
+ def get_properties(self, uid):
+ latest_versions = self._get_latest(uid)
+ if not latest_versions:
+ raise ValueError('Entry %s does not exist' % (uid, ))
+
+ latest_versions[0]['uid'] = latest_versions[0].pop('tree_id')
+ del latest_versions[0]['version_id']
+ return latest_versions[0]
+
+ @dbus.service.method(DS_DBUS_INTERFACE_SUGAR_V1,
+ in_signature='sa{sv}', out_signature='as')
+ def get_uniquevaluesfor(self, propertyname, query=None):
+ return self._internal_api.find_unique_values(query, propertyname)
+
+ @dbus.service.method(DS_DBUS_INTERFACE_SUGAR_V1,
+ in_signature='s', out_signature='')
+ def delete(self, uid):
+ latest_versions = self._get_latest(uid)
+ if not latest_versions:
+ raise ValueError('Entry %s does not exist' % (uid, ))
+
+ self._internal_api.delete((uid, latest_versions[0]['version_id']))
+ self.Deleted(uid)
+
+ @dbus.service.signal(DS_DBUS_INTERFACE_SUGAR_V1, signature='s')
+ def Deleted(self, uid):
+ # pylint: disable-msg=C0103
+ pass
+
+ @dbus.service.method(DS_DBUS_INTERFACE_SUGAR_V1,
+ in_signature='', out_signature='aa{sv}')
+ def mounts(self):
+ return [{'id': 1}]
+
+ @dbus.service.signal(DS_DBUS_INTERFACE_SUGAR_V1, signature='a{sv}')
+ def Mounted(self, descriptior):
+ # pylint: disable-msg=C0103
+ pass
+
+ @dbus.service.signal(DS_DBUS_INTERFACE_SUGAR_V1, signature='a{sv}')
+ def Unmounted(self, descriptor):
+ # pylint: disable-msg=C0103
+ pass
+
+ def _get_latest(self, uid):
+ return self._internal_api.find({'tree_id': uid},
+ {'limit': 1, 'order_by': ['+timestamp']})[0]
+
+ def _compare_checksums(self, parent, child_data_path):
+ parent_object_id = (parent['tree_id'], parent['version_id'])
+ parent_data_path = self._internal_api.get_data_path(parent_object_id)
+ if bool(child_data_path) ^ bool(parent_data_path):
+ return False
+ elif not child_data_path:
+ return True
+
+ parent_checksum = self._internal_api.get_data_checksum(
+ parent_object_id)
+ child_checksum = calculate_checksum(child_data_path)
+ return parent_checksum == child_checksum
+
+
+class InternalApi(object):
+ def __init__(self, base_dir):
+ self._base_dir = base_dir
+ gconf_client = gconf.client_get_default()
+ self._max_versions = gconf_client.get_int(
+ '/desktop/sugar/datastore/max_versions')
+ logging.debug('max_versions=%r', self._max_versions)
+
+ def stop(self):
+ return
+
+ def save(self, tree_id, parent_id, metadata, path, delete_after, async_cb,
+ async_err_cb):
+ logging.debug('save(%r, %r, %r, &r, %r)', tree_id, parent_id,
+ metadata, path, delete_after)
+ child_id = metadata.get('version_id')
+ if not child_id:
+ child_id = self._gen_uuid()
+
+ async_cb(tree_id, child_id)
+
+ def change_metadata(self, (tree_id, version_id), metadata):
+ logging.debug('change_metadata((%r, %r), %r)', tree_id, version_id,
+ metadata)
+ return
+
+ def find(self, query, options, querystring=None):
+ logging.debug('find(%r, %r, %r)', query, options, querystring)
+ return [], 0
+
+ def get_data_path(self, (tree_id, version_id), sender=None):
+ logging.debug('get_data_path((%r, %r), %r)', tree_id, version_id,
+ sender)
+ return ''
+
+ def find_unique_values(self, query, name):
+ logging.debug('find_unique_values(%r, %r)', query, name)
+ return []
+
+ def delete(self, (tree_id, version_id)):
+ logging.debug('delete((%r, %r))', tree_id, version_id)
+
+ def _check_max_versions(self, tree_id):
+ if not self._max_versions:
+ return
+
+ options = {'all_versions': True, 'offset': self._max_versions,
+ 'metadata': ['tree_id', 'version_id', 'timestamp'],
+ 'order_by': ['+timestamp']}
+ old_versions = self.find({'tree_id': tree_id}, options)[0]
+ logging.info('Deleting old versions: %r', old_versions)
+ for entry in old_versions:
+ self.delete((entry['tree_id'], entry['version_id']))
+
+ def _gen_uuid(self):
+ return str(uuid.uuid4())
+
+
+def calculate_checksum(path):
+ checksum = hashlib.sha1()
+ f = file(path)
+ while True:
+ chunk = f.read(65536)
+ if not chunk:
+ return checksum.hexdigest()
+
+ checksum.update(chunk)