# # 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)