From c7d8a1b1864116159d82e6fc90fe1bac218cd8f9 Mon Sep 17 00:00:00 2001 From: Sascha Silbe Date: Fri, 08 Apr 2011 22:13:20 +0000 Subject: add framework for Sugar data store API v1 --- (limited to 'src/gdatastore/datastore.py') 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 +# +# 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 . +""" +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) -- cgit v0.9.1