From d504124cc29626876052eeb0c89e3bc106856766 Mon Sep 17 00:00:00 2001 From: Tomeu Vizoso Date: Fri, 2 Mar 2007 21:17:03 +0100 Subject: [PATCH] Hack Dan's DataStore for doing what we need for 6th March demo. --- services/datastore/Makefile.am | 3 +- services/datastore/datastore.py | 96 ++++++++++++++------------------- services/datastore/demodata.py | 65 ++++++++++++++++++++++ sugar/datastore/datastore.py | 96 +++++++++++++++++++++++++++++++-- sugar/graphics/optionmenu.py | 3 +- tests/test-datastore2.py | 50 +++++++++++++++++ 6 files changed, 252 insertions(+), 61 deletions(-) create mode 100644 services/datastore/demodata.py create mode 100755 tests/test-datastore2.py diff --git a/services/datastore/Makefile.am b/services/datastore/Makefile.am index 3ae64e47..2a49d2fc 100644 --- a/services/datastore/Makefile.am +++ b/services/datastore/Makefile.am @@ -9,7 +9,8 @@ sugardir = $(pkgdatadir)/services/datastore sugar_PYTHON = \ __init__.py \ datastore.py \ - dbus_helpers.py + dbus_helpers.py \ + demodata.py bin_SCRIPTS = sugar-data-store diff --git a/services/datastore/datastore.py b/services/datastore/datastore.py index 9792b0a2..c9c9c07b 100644 --- a/services/datastore/datastore.py +++ b/services/datastore/datastore.py @@ -18,9 +18,15 @@ import os import dbus, dbus.glib, gobject import logging -import sqlite3 + +try: + from sqlite3 import dbapi2 as sqlite +except ImportError: + from pysqlite2 import dbapi2 as sqlite + import dbus_helpers import string +import demodata have_sugar = False try: @@ -72,12 +78,14 @@ def _get_data_as_string(data): return data_str elif isinstance(data, int): return str(data) + elif isinstance(data, float): + return str(data) elif isinstance(data, str): return data elif isinstance(data, unicode): return str(data) else: - raise ValueError("Unsupported data type") + raise ValueError("Unsupported data type: %s" % type(data)) class DataStoreDBusHelper(dbus.service.Object): def __init__(self, parent, bus_name): @@ -98,14 +106,9 @@ class DataStoreDBusHelper(dbus.service.Object): return _create_op(self._parent.get_activity_object(activity_id)) @dbus.service.method(_DS_DBUS_INTERFACE, - in_signature="aya{sv}s", out_signature="o") - def create(self, data, prop_dict, activity_id): - if len(activity_id): - if not validate_activity_id(activity_id): - raise ValueError("invalid activity id") - else: - activity_id = None - uid = self._parent.create(data, prop_dict, activity_id) + in_signature="a{sv}", out_signature="o") + def create(self, prop_dict): + uid = self._parent.create(prop_dict) return _create_op(uid) @dbus.service.method(_DS_DBUS_INTERFACE, @@ -116,9 +119,9 @@ class DataStoreDBusHelper(dbus.service.Object): return 0 @dbus.service.method(_DS_DBUS_INTERFACE, - in_signature="a{sv}", out_signature="ao") - def find(self, prop_dict): - uids = self._parent.find(prop_dict) + in_signature="s", out_signature="ao") + def find(self, query): + uids = self._parent.find(query) ops = [] for uid in uids: ops.append(_create_op(uid)) @@ -188,13 +191,13 @@ class DataStore(object): if not os.path.exists(os.path.dirname(self._dbfile)): os.makedirs(os.path.dirname(self._dbfile), 0755) - self._dbcx = sqlite3.connect(self._dbfile, timeout=3) + self._dbcx = sqlite.connect(self._dbfile, timeout=3) + self._dbcx.row_factory = sqlite.Row try: self._ensure_table() except StandardError, e: logging.info("Could not access the data store. Reason: '%s'. Exiting..." % e) os._exit(1) - self._dbcx.row_factory = sqlite3.Row def __del__(self): self._dbcx.close() @@ -209,10 +212,7 @@ class DataStore(object): # If table wasn't created, try to create it self._dbcx.commit() curs.execute('CREATE TABLE objects (' \ - 'uid INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, ' \ - 'activity_id VARCHAR(50), ' \ - 'data BLOB' \ - ');') + 'uid INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT);') curs.execute('CREATE TABLE properties (' \ 'objid INTEGER NOT NULL, ' \ 'key VARCHAR(100),' \ @@ -220,6 +220,7 @@ class DataStore(object): ');') curs.execute('CREATE INDEX objid_idx ON properties(objid);') self._dbcx.commit() + demodata.insert_demo_data(self) del curs def get(self, uid): @@ -243,24 +244,17 @@ class DataStore(object): del curs raise NotFoundError("Object for activity %s was not found." % activity_id) - def create(self, data, prop_dict=None, activity_id=None): + def create(self, prop_dict): curs = self._dbcx.cursor() - data = _get_data_as_string(data) - logging.debug(type(data)) - logging.debug(data) - if not activity_id: - curs.execute("INSERT INTO objects (uid, data) VALUES (NULL, ?);", (data,)) - else: - curs.execute("INSERT INTO objects (uid, data, activity_id) VALUES (NULL, ?, ?);", (data, activity_id)) + curs.execute("INSERT INTO objects (uid) VALUES (NULL);") curs.execute("SELECT last_insert_rowid();") rows = curs.fetchall() self._dbcx.commit() last_row = rows[0] uid = last_row[0] for (key, value) in prop_dict.items(): - safe_key = key.replace("'", "''") value = _get_data_as_string(value) - curs.execute("INSERT INTO properties (objid, key, value) VALUES (?, ?, ?);", (uid, safe_key, value)) + curs.execute("INSERT INTO properties (objid, key, value) VALUES (?, ?, ?);", (uid, key, value)) self._dbcx.commit() del curs return uid @@ -274,23 +268,14 @@ class DataStore(object): self._dbus_obj_helper.Updated(False, {}, True, uid=uid) return 0 - def find(self, prop_dict): - query = "SELECT objid FROM properties" - subquery = "" - for (key, value) in prop_dict.items(): - safe_key = key.replace("'", "''") - value = _get_data_as_string(value) - if not len(value): - raise ValueError("Property values must not be blank.") - substr = "(key='%s' AND value='%s')" % (safe_key, value) - if len(subquery) > 0: - subquery += " OR " - subquery += substr - if len(subquery): - query += " WHERE (%s)" % subquery - query += ";" + def find(self, query): + sql_query = "SELECT DISTINCT(objid) FROM properties" + if query: + # TODO: parse the query for avoiding sql injection attacks. + sql_query += " WHERE (%s)" % query + sql_query += ";" curs = self._dbcx.cursor() - curs.execute(query) + curs.execute(sql_query) rows = curs.fetchall() self._dbcx.commit() # FIXME: ensure that each properties.objid has a match in objects.uid @@ -314,7 +299,8 @@ class DataStore(object): del curs self._dbus_obj_helper.Updated(True, {}, False, uid=uid) - _reserved_keys = ["uid", "objid", "data", "created", "modified"] + _reserved_keys = ["handle", "objid", "data", "created", "modified", + "object-type", "file-path"] def set_properties(self, uid, prop_dict): curs = self._dbcx.cursor() curs.execute('SELECT uid FROM objects WHERE uid=?;', (uid,)) @@ -329,17 +315,16 @@ class DataStore(object): raise ValueError("key %s is a reserved key." % key) for (key, value) in prop_dict.items(): - safe_key = key.replace("'", "''") value = _get_data_as_string(value) if not len(value): # delete the property - curs.execute("DELETE FROM properties WHERE (objid=? AND key=?);", (uid, safe_key)) + curs.execute("DELETE FROM properties WHERE (objid=? AND key=?);", (uid, key)) else: - curs.execute("SELECT objid FROM properties WHERE (objid=? AND key=?);", (uid, safe_key)) + curs.execute("SELECT objid FROM properties WHERE (objid=? AND key=?);", (uid, key)) if len(curs.fetchall()) > 0: - curs.execute("UPDATE properties SET value=? WHERE (objid=? AND key=?);", (value, uid, safe_key)) + curs.execute("UPDATE properties SET value=? WHERE (objid=? AND key=?);", (value, uid, key)) else: - curs.execute("INSERT INTO properties (objid, key, value) VALUES (?, ?, ?);", (uid, safe_key, value)) + curs.execute("INSERT INTO properties (objid, key, value) VALUES (?, ?, ?);", (uid, key, value)) self._dbcx.commit() del curs self._dbus_obj_helper.Updated(False, {}, False, uid=uid) @@ -360,10 +345,11 @@ class DataStore(object): subquery = "" if len(keys) > 0: for key in keys: - if len(subquery) > 0: + if not subquery: + subquery += " AND (" + else: subquery += " OR " - safe_key = key.replace("'", "''") - subquery += "key='%s'" % safe_key + subquery += "key='%s'" % key subquery += ")" query += subquery + ");" curs = self._dbcx.cursor() @@ -374,7 +360,7 @@ class DataStore(object): for row in rows: conv_key = row['key'].replace("''", "'") prop_dict[conv_key] = row['value'] - prop_dict['uid'] = str(uid) + prop_dict['handle'] = str(uid) del curs return prop_dict diff --git a/services/datastore/demodata.py b/services/datastore/demodata.py new file mode 100644 index 00000000..9265a6f5 --- /dev/null +++ b/services/datastore/demodata.py @@ -0,0 +1,65 @@ +import time +import os + +def insert_demo_data(data_store): + home_dir = os.path.expanduser('~') + journal_dir = os.path.join(home_dir, "Journal") + if not os.path.exists(journal_dir): + os.makedirs(journal_dir, 0755) + + data = [ + { 'file-path' : os.path.join(journal_dir, 'fishes_in_the_sea.rtf'), + 'object-type' : 'text', + 'date' : str(time.time() - 100000), + 'title' : 'Fishes in the Sea', + 'preview' : 'There are many fishes in the sea, and not only...', + 'icon' : 'theme:object-text', + 'icon-color' : '#C2B00C,#785C78', + 'keep' : '0', + 'buddies' : str([ { 'name' : 'Marco', + 'color' : '#C2B00C,#785C78' }, + { 'name' : 'Dan', + 'color' : '#75C228,#3A6E3A' } ]) + }, + { 'file-path' : os.path.join(journal_dir, 'my_cat_and_my_fishes.rtf'), + 'object-type' : 'text', + 'date' : str(time.time() - 200000), + 'title' : 'My cat and my fishes', + 'preview' : "Don't know why, but my cat looks to like my fishe...", + 'icon' : 'theme:object-text', + 'icon-color' : '#C2B00C,#785C78', + 'keep' : '1', + 'buddies' : str([ { 'name' : 'Eben', + 'color' : '#C2B00C,#785C78' }, + { 'name' : 'Dan', + 'color' : '#75C228,#3A6E3A' } ]) + }, + { 'file-path' : os.path.join(journal_dir, 'cat_browsing.hist'), + 'object-type' : 'link', + 'date' : str(time.time() - 300000), + 'title' : 'About cats', + 'preview' : "http://en.wikipedia.org/wiki/Cat", + 'icon' : 'theme:object-link', + 'icon-color' : '#C2B00C,#785C78', + 'keep' : '0', + 'buddies' : str([ { 'name' : 'Dan', + 'color' : '#C2B00C,#785C78' }, + { 'name' : 'Tomeu', + 'color' : '#75C228,#3A6E3A' } ]) + }, + { 'file-path' : os.path.join(journal_dir, 'our_school.jpeg'), + 'object-type' : 'picture', + 'date' : str(time.time() - 400000), + 'title' : 'Our school', + 'preview' : "Our school", + 'icon' : 'theme:object-image', + 'icon-color' : '#C2B00C,#785C78', + 'keep' : '0', + 'buddies' : str([ { 'name' : 'Marco', + 'color' : '#C2B00C,#785C78' }, + { 'name' : 'Eben', + 'color' : '#75C228,#3A6E3A' } ]) + } + ] + for obj in data: + data_store.create(obj) diff --git a/sugar/datastore/datastore.py b/sugar/datastore/datastore.py index 7a776870..33deadcc 100644 --- a/sugar/datastore/datastore.py +++ b/sugar/datastore/datastore.py @@ -13,10 +13,101 @@ # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +import logging + +import dbus +import dbus.glib +import gobject -import dbus, dbus.glib, gobject from sugar import util +DS_DBUS_SERVICE = "org.laptop.sugar.DataStore" +DS_DBUS_INTERFACE = "org.laptop.sugar.DataStore" +DS_DBUS_PATH = "/org/laptop/sugar/DataStore" + +_bus = dbus.SessionBus() +_data_store = dbus.Interface(_bus.get_object(DS_DBUS_SERVICE, DS_DBUS_PATH), + DS_DBUS_INTERFACE) + +class DataStoreObject: + def __init__(self, metadata, file_path=None, handle=None): + self._metadata = metadata + self._file_path = file_path + self._handle = handle + + def get_metadata(self): + return self._metadata + + def get_file_path(self): + return self._file_path + + def set_file_path(self, file_path): + self._file_path = file_path + + def get_handle(self): + return self._handle + + def get_object_type(self): + raise NotImplementedError() + +class Text(DataStoreObject): + def get_object_type(self): + return 'text' + +class Picture(DataStoreObject): + def get_object_type(self): + return 'picture' + +class Link(DataStoreObject): + def get_object_type(self): + return 'link' + +def _read_from_object_path(object_path): + dbus_object = _bus.get_object(DS_DBUS_SERVICE, object_path) + metadata = dbus_object.get_properties(dbus.Dictionary({}, signature='sv')) + + object_type = metadata['object-type'] + file_path = metadata['file-path'] + handle = metadata['handle'] + + del metadata['object-type'] + del metadata['file-path'] + del metadata['handle'] + + if object_type == 'text': + return Text(metadata, file_path, handle) + elif object_type == 'picture': + return Picture(metadata, file_path, handle) + elif object_type == 'link': + return Link(metadata, file_path, handle) + else: + raise NotImplementedError('Unknown object type.') + +def read(handle): + object_path = _data_store.get(handle) + return _read_from_object_path(object_path) + +def write(obj): + metadata = obj.get_metadata().copy() + metadata['file-path'] = obj.get_file_path() + metadata['object-type'] = obj.get_object_type() + logging.debug(str(metadata)) + object_path = _data_store.create(dbus.Dictionary(metadata)) + dbus_object = _bus.get_object(DS_DBUS_SERVICE, object_path) + return dbus_object.get_properties(['handle'])['handle'] + +def find(query): + object_paths = _data_store.find(query) + objects = [] + for object_path in object_paths: + objects.append(_read_from_object_path(object_path)) + return objects + +def delete(handle): + pass + +################################################################################ + class ObjectCache(object): def __init__(self): self._cache = {} @@ -39,9 +130,6 @@ class ObjectCache(object): pass -DS_DBUS_SERVICE = "org.laptop.sugar.DataStore" -DS_DBUS_INTERFACE = "org.laptop.sugar.DataStore" -DS_DBUS_PATH = "/org/laptop/sugar/DataStore" class DSObject(gobject.GObject): diff --git a/sugar/graphics/optionmenu.py b/sugar/graphics/optionmenu.py index ffe2d8e9..85800494 100644 --- a/sugar/graphics/optionmenu.py +++ b/sugar/graphics/optionmenu.py @@ -96,7 +96,8 @@ class OptionMenu(hippo.CanvasBox, hippo.CanvasItem): return self._value def add_item(self, menu_item): - if not self._value: + if self._value == None: + logging.debug('Setting default value to: ' + menu_item.props.label) self._value = menu_item.props.action_id self._canvas_text.props.text = menu_item.props.label diff --git a/tests/test-datastore2.py b/tests/test-datastore2.py new file mode 100755 index 00000000..e8747587 --- /dev/null +++ b/tests/test-datastore2.py @@ -0,0 +1,50 @@ +#!/usr/bin/env python +# Copyright (C) 2006, One Laptop Per Child +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# 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, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +from sugar.datastore import datastore +from sugar.datastore.datastore import Text + +# Write a text object +metadata = { 'date' : 1000900000, + 'title' : 'Thai history', + 'preview' : 'The subject of thai history...', + 'icon-color' : '#C2B00C,#785C78', + } +text = Text(metadata) +f = open("/tmp/hello.txt", 'w') +try: + f.write('The subject of thai history blah blah blah, blah blah blah and blah.') +finally: + f.close() +text.set_file_path(f.name) +handle = datastore.write(text) + +# Read back that object +thing = datastore.read(handle) +metadata = thing.get_metadata() +print metadata + +file_path = thing.get_file_path() +f = open(file_path) +try: + print f.read() +finally: + f.close() + +# Retrieve all the objects +objects = datastore.find('') +for obj in objects: + print obj.get_metadata()['title']