Use the new DataStore and remove the old one.

This commit is contained in:
Tomeu Vizoso 2007-05-10 11:01:32 +02:00
parent ac4338e3c0
commit 929dabd165
14 changed files with 438 additions and 1032 deletions

View File

@ -1 +1 @@
SUBDIRS = presence clipboard datastore console SUBDIRS = presence clipboard console

View File

@ -1 +0,0 @@
org.laptop.sugar.DataStore.service

View File

@ -1,19 +0,0 @@
servicedir = $(datadir)/dbus-1/services
service_in_files = org.laptop.sugar.DataStore.service.in
service_DATA = $(service_in_files:.service.in=.service)
$(service_DATA): $(service_in_files) Makefile
@sed -e "s|\@bindir\@|$(bindir)|" $< > $@
sugardir = $(pkgdatadir)/services/datastore
sugar_PYTHON = \
__init__.py \
datastore.py \
dbus_helpers.py \
demodata.py
bin_SCRIPTS = sugar-data-store
DISTCLEANFILES = $(service_DATA)
EXTRA_DIST = $(bin_SCRIPTS) org.laptop.sugar.DataStore.service.in

View File

@ -1,365 +0,0 @@
#!/bin/python
# Copyright (C) 2006, Red Hat, Inc.
#
# 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
import os
import dbus, dbus.glib, gobject
import logging
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:
from sugar import env
have_sugar = True
except ImportError:
pass
def is_hex(s):
return s.strip(string.hexdigits) == ''
ACTIVITY_ID_LEN = 40
def validate_activity_id(actid):
"""Validate an activity ID."""
if not isinstance(actid, str) and not isinstance(actid, unicode):
return False
if len(actid) != ACTIVITY_ID_LEN:
return False
if not is_hex(actid):
return False
return True
_DS_SERVICE = "org.laptop.sugar.DataStore"
_DS_DBUS_INTERFACE = "org.laptop.sugar.DataStore"
_DS_OBJECT_PATH = "/org/laptop/sugar/DataStore"
_DS_OBJECT_DBUS_INTERFACE = "org.laptop.sugar.DataStore.Object"
_DS_OBJECT_OBJECT_PATH = "/org/laptop/sugar/DataStore/Object"
class NotFoundError(Exception):
pass
def _create_op(uid):
return "%s/%d" % (_DS_OBJECT_OBJECT_PATH, uid)
def _get_uid_from_op(op):
if not op.startswith(_DS_OBJECT_OBJECT_PATH + "/"):
raise ValueError("Invalid object path %s." % op)
item = op[len(_DS_OBJECT_OBJECT_PATH + "/"):]
return int(item)
def _get_data_as_string(data):
if isinstance(data, list):
data_str = ""
for item in data:
data_str += chr(item)
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: %s" % type(data))
class DataStoreDBusHelper(dbus.service.Object):
def __init__(self, parent, bus_name):
self._parent = parent
self._bus_name = bus_name
dbus.service.Object.__init__(self, bus_name, _DS_OBJECT_PATH)
@dbus.service.method(_DS_DBUS_INTERFACE,
in_signature="x", out_signature="o")
def get(self, uid):
return _create_op(self._parent.get(uid))
@dbus.service.method(_DS_DBUS_INTERFACE,
in_signature="s", out_signature="o")
def getActivityObject(self, activity_id):
if not validate_activity_id(activity_id):
raise ValueError("invalid activity id")
return _create_op(self._parent.get_activity_object(activity_id))
@dbus.service.method(_DS_DBUS_INTERFACE,
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,
in_signature="ia{sv}", out_signature="o")
def update(self, uid, prop_dict):
self._parent.update(uid, prop_dict)
return _create_op(uid)
@dbus.service.method(_DS_DBUS_INTERFACE,
in_signature="o", out_signature="i")
def delete(self, op):
uid = _get_uid_from_op(op)
self._parent.delete(uid)
return 0
@dbus.service.method(_DS_DBUS_INTERFACE,
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))
return ops
class ObjectDBusHelper(dbus_helpers.FallbackObject):
def __init__(self, parent, bus_name):
self._parent = parent
self._bus_name = bus_name
dbus_helpers.FallbackObject.__init__(self, bus_name, _DS_OBJECT_OBJECT_PATH)
@dbus_helpers.method(_DS_OBJECT_DBUS_INTERFACE,
in_signature="", out_signature="ay", object_path_keyword="dbus_object_path")
def get_data(self, dbus_object_path=None):
if not dbus_object_path:
raise RuntimeError("Need the dbus object path.")
uid = _get_uid_from_op(dbus_object_path)
return dbus.ByteArray(self._parent.get_data(uid))
@dbus_helpers.method(_DS_OBJECT_DBUS_INTERFACE,
in_signature="ay", out_signature="i", object_path_keyword="dbus_object_path")
def set_data(self, data, dbus_object_path=None):
if not dbus_object_path:
raise RuntimeError("Need the dbus object path.")
uid = _get_uid_from_op(dbus_object_path)
self._parent.set_data(uid, data)
return 0
@dbus_helpers.method(_DS_OBJECT_DBUS_INTERFACE,
in_signature="as", out_signature="a{sv}", object_path_keyword="dbus_object_path")
def get_properties(self, keys, dbus_object_path=None):
if not dbus_object_path:
raise RuntimeError("Need the dbus object path.")
uid = _get_uid_from_op(dbus_object_path)
return self._parent.get_properties(uid, keys)
@dbus_helpers.method(_DS_OBJECT_DBUS_INTERFACE,
in_signature="a{sv}", out_signature="i", object_path_keyword="dbus_object_path")
def set_properties(self, prop_dict, dbus_object_path=None):
if not dbus_object_path:
raise RuntimeError("Need the dbus object path.")
uid = _get_uid_from_op(dbus_object_path)
self._parent.set_properties(uid, prop_dict)
return 0
@dbus_helpers.fallback_signal(_DS_OBJECT_DBUS_INTERFACE,
signature="ba{sv}b", ignore_args=["uid"])
def Updated(self, data, prop_dict, deleted, uid=None):
# Return the object path so the signal decorator knows what
# object this signal should be fore
if not uid:
raise RuntimeError("Need a UID.")
op = _create_op(uid)
return op
class DataStore(object):
def __init__(self):
self._session_bus = dbus.SessionBus()
self._bus_name = dbus.service.BusName(_DS_SERVICE, bus=self._session_bus)
self._dbus_helper = DataStoreDBusHelper(self, self._bus_name)
self._dbus_obj_helper = ObjectDBusHelper(self, self._bus_name)
ppath = "/tmp"
if have_sugar:
ppath = env.get_profile_path()
self._dbfile = os.path.join(ppath, "ds", "data-store.db")
if not os.path.exists(os.path.dirname(self._dbfile)):
os.makedirs(os.path.dirname(self._dbfile), 0755)
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)
def __del__(self):
self._dbcx.close()
del self._dbcx
def _ensure_table(self):
curs = self._dbcx.cursor()
try:
curs.execute('SELECT * FROM properties LIMIT 4')
self._dbcx.commit()
except Exception, e:
# If table wasn't created, try to create it
self._dbcx.commit()
curs.execute('CREATE TABLE objects (' \
'uid INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT);')
curs.execute('CREATE TABLE properties (' \
'objid INTEGER NOT NULL, ' \
'key VARCHAR(100),' \
'value VARCHAR(200)' \
');')
curs.execute('CREATE INDEX objid_idx ON properties(objid);')
self._dbcx.commit()
demodata.insert_demo_data(self)
del curs
def get(self, uid):
curs = self._dbcx.cursor()
curs.execute('SELECT uid FROM objects WHERE uid=?;', (uid,))
res = curs.fetchall()
self._dbcx.commit()
del curs
if len(res) > 0:
return uid
raise NotFoundError("Object %d was not found." % uid)
def get_activity_object(self, activity_id):
curs = self._dbcx.cursor()
curs.execute("SELECT uid FROM objects WHERE activity_id=?;", (activity_id,))
res = curs.fetchall()
self._dbcx.commit()
if len(res) > 0:
del curs
return res[0][0]
del curs
raise NotFoundError("Object for activity %s was not found." % activity_id)
def create(self, prop_dict):
curs = self._dbcx.cursor()
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():
value = _get_data_as_string(value)
curs.execute("INSERT INTO properties (objid, key, value) VALUES (?, ?, ?);", (uid, key, value))
self._dbcx.commit()
del curs
return uid
def delete(self, uid):
curs = self._dbcx.cursor()
curs.execute("DELETE FROM objects WHERE (uid=?);", (uid,))
curs.execute("DELETE FROM properties WHERE (objid=?);", (uid,))
self._dbcx.commit()
del curs
self._dbus_obj_helper.Updated(False, {}, True, uid=uid)
return 0
def find(self, query):
sql_query = "SELECT props1.objid objid," \
" props1.value date," \
" props2.value object_type," \
" props3.value buddies " \
"FROM properties props1," \
" properties props2," \
" properties props3 " \
"WHERE props1.objid = props2.objid AND" \
" props2.objid = props3.objid AND" \
" props1.key = 'date' AND" \
" props2.key = 'object-type' AND" \
" props3.key = 'buddies' " \
"ORDER BY date DESC"
if query:
# TODO: parse the query for avoiding sql injection attacks.
sql_query = "SELECT objid FROM (%s) WHERE (%s)" % (sql_query, query)
sql_query += ";"
curs = self._dbcx.cursor()
logging.debug(sql_query)
curs.execute(sql_query)
rows = curs.fetchall()
self._dbcx.commit()
# FIXME: ensure that each properties.objid has a match in objects.uid
uids = []
for row in rows:
uids.append(row['objid'])
del curs
return uids
def update(self, uid, prop_dict):
curs = self._dbcx.cursor()
curs.execute('SELECT uid FROM objects WHERE uid=?;', (uid,))
res = curs.fetchall()
self._dbcx.commit()
if len(res) <= 0:
del curs
raise NotFoundError("Object %d was not found." % uid)
for (key, value) in prop_dict.items():
value = _get_data_as_string(value)
if not len(value):
# delete the property
curs.execute("DELETE FROM properties WHERE (objid=? AND key=?);", (uid, key))
else:
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, key))
else:
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)
def get_properties(self, uid, keys):
query = "SELECT objid, key, value FROM properties WHERE (objid=%d" % uid
subquery = ""
if len(keys) > 0:
for key in keys:
if not subquery:
subquery += " AND ("
else:
subquery += " OR "
subquery += "key='%s'" % key
subquery += ")"
query += subquery + ");"
curs = self._dbcx.cursor()
curs.execute(query)
rows = curs.fetchall()
self._dbcx.commit()
prop_dict = {}
for row in rows:
conv_key = row['key'].replace("''", "'")
prop_dict[conv_key] = row['value']
prop_dict['handle'] = str(uid)
del curs
return prop_dict
def main():
loop = gobject.MainLoop()
ds = DataStore()
try:
loop.run()
except KeyboardInterrupt:
print 'Ctrl+C pressed, exiting...'
if __name__ == "__main__":
main()

View File

@ -1,235 +0,0 @@
# Copyright (C) 2006, Red Hat, Inc.
#
# 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
# Mostly taken from dbus-python's service.py
import dbus
import _dbus_bindings
from dbus import service
import inspect
def method(dbus_interface, in_signature=None, out_signature=None, async_callbacks=None, sender_keyword=None, utf8_strings=False, byte_arrays=False, object_path_keyword=None):
_dbus_bindings.validate_interface_name(dbus_interface)
def decorator(func):
args = inspect.getargspec(func)[0]
args.pop(0)
if async_callbacks:
if type(async_callbacks) != tuple:
raise TypeError('async_callbacks must be a tuple of (keyword for return callback, keyword for error callback)')
if len(async_callbacks) != 2:
raise ValueError('async_callbacks must be a tuple of (keyword for return callback, keyword for error callback)')
args.remove(async_callbacks[0])
args.remove(async_callbacks[1])
if sender_keyword:
args.remove(sender_keyword)
if object_path_keyword:
args.remove(object_path_keyword)
if in_signature:
in_sig = tuple(_dbus_bindings.Signature(in_signature))
if len(in_sig) > len(args):
raise ValueError, 'input signature is longer than the number of arguments taken'
elif len(in_sig) < len(args):
raise ValueError, 'input signature is shorter than the number of arguments taken'
func._dbus_is_method = True
func._dbus_async_callbacks = async_callbacks
func._dbus_interface = dbus_interface
func._dbus_in_signature = in_signature
func._dbus_out_signature = out_signature
func._dbus_sender_keyword = sender_keyword
func._dbus_args = args
func._dbus_get_args_options = {'byte_arrays': byte_arrays,
'utf8_strings': utf8_strings}
func._dbus_object_path_keyword = object_path_keyword
return func
return decorator
def fallback_signal(dbus_interface, signature=None, ignore_args=None):
_dbus_bindings.validate_interface_name(dbus_interface)
def decorator(func):
def emit_signal(self, *args, **keywords):
obj_path = func(self, *args, **keywords)
message = _dbus_bindings.SignalMessage(obj_path, dbus_interface, func.__name__)
if emit_signal._dbus_signature:
message.append(signature=emit_signal._dbus_signature,
*args)
else:
message.append(*args)
self._connection.send_message(message)
temp_args = inspect.getargspec(func)[0]
temp_args.pop(0)
args = []
for arg in temp_args:
if arg not in ignore_args:
args.append(arg)
if signature:
sig = tuple(_dbus_bindings.Signature(signature))
if len(sig) > len(args):
raise ValueError, 'signal signature is longer than the number of arguments provided'
elif len(sig) < len(args):
raise ValueError, 'signal signature is shorter than the number of arguments provided'
emit_signal.__name__ = func.__name__
emit_signal.__doc__ = func.__doc__
emit_signal._dbus_is_signal = True
emit_signal._dbus_interface = dbus_interface
emit_signal._dbus_signature = signature
emit_signal._dbus_args = args
return emit_signal
return decorator
class FallbackObject(dbus.service.Object):
"""A base class for exporting your own Objects across the Bus.
Just inherit from Object and provide a list of methods to share
across the Bus
"""
def __init__(self, conn=None, fallback_object_path=None, bus_name=None):
if fallback_object_path is None:
raise TypeError('The fallback_object_path argument is required')
if isinstance(conn, dbus.service.BusName):
# someone's using the old API; don't gratuitously break them
bus_name = conn
conn = bus_name.get_bus()
elif conn is None:
# someone's using the old API but naming arguments, probably
if bus_name is None:
raise TypeError('Either conn or bus_name is required')
conn = bus_name.get_bus()
self._object_path = fallback_object_path
self._name = bus_name
self._bus = conn
self._connection = self._bus.get_connection()
self._connection._register_object_path(fallback_object_path, self._message_cb, self._unregister_cb, fallback=True)
def _message_cb(self, connection, message):
try:
# lookup candidate method and parent method
method_name = message.get_member()
interface_name = message.get_interface()
(candidate_method, parent_method) = dbus.service._method_lookup(self, method_name, interface_name)
# set up method call parameters
args = message.get_args_list(**parent_method._dbus_get_args_options)
keywords = {}
if parent_method._dbus_out_signature is not None:
signature = _dbus_bindings.Signature(parent_method._dbus_out_signature)
else:
signature = None
# set up async callback functions
if parent_method._dbus_async_callbacks:
(return_callback, error_callback) = parent_method._dbus_async_callbacks
keywords[return_callback] = lambda *retval: dbus.service._method_reply_return(connection, message, method_name, signature, *retval)
keywords[error_callback] = lambda exception: dbus.service._method_reply_error(connection, message, exception)
# include the sender if desired
if parent_method._dbus_sender_keyword:
keywords[parent_method._dbus_sender_keyword] = message.get_sender()
if parent_method._dbus_object_path_keyword:
keywords[parent_method._dbus_object_path_keyword] = message.get_path()
# call method
retval = candidate_method(self, *args, **keywords)
# we're done - the method has got callback functions to reply with
if parent_method._dbus_async_callbacks:
return
# otherwise we send the return values in a reply. if we have a
# signature, use it to turn the return value into a tuple as
# appropriate
if signature is not None:
signature_tuple = tuple(signature)
# if we have zero or one return values we want make a tuple
# for the _method_reply_return function, otherwise we need
# to check we're passing it a sequence
if len(signature_tuple) == 0:
if retval == None:
retval = ()
else:
raise TypeError('%s has an empty output signature but did not return None' %
method_name)
elif len(signature_tuple) == 1:
retval = (retval,)
else:
if operator.isSequenceType(retval):
# multi-value signature, multi-value return... proceed unchanged
pass
else:
raise TypeError('%s has multiple output values in signature %s but did not return a sequence' %
(method_name, signature))
# no signature, so just turn the return into a tuple and send it as normal
else:
if retval == None:
retval = ()
else:
retval = (retval,)
dbus.service._method_reply_return(connection, message, method_name, signature, *retval)
except Exception, exception:
# send error reply
dbus.service._method_reply_error(connection, message, exception)
@method('org.freedesktop.DBus.Introspectable', in_signature='', out_signature='s', object_path_keyword="dbus_object_path")
def Introspect(self, dbus_object_path=None):
reflection_data = '<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN" "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">\n'
reflection_data += '<node name="%s">\n' % (dbus_object_path)
interfaces = self._dbus_class_table[self.__class__.__module__ + '.' + self.__class__.__name__]
for (name, funcs) in interfaces.iteritems():
reflection_data += ' <interface name="%s">\n' % (name)
for func in funcs.values():
if getattr(func, '_dbus_is_method', False):
reflection_data += self.__class__._reflect_on_method(func)
elif getattr(func, '_dbus_is_signal', False):
reflection_data += self.__class__._reflect_on_signal(func)
reflection_data += ' </interface>\n'
reflection_data += '</node>\n'
return reflection_data
def __repr__(self):
return '<dbus.service.FallbackObject %s on %r at %#x>' % (self._object_path, self._name, id(self))
__str__ = __repr__

View File

@ -1,95 +0,0 @@
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, 'my_cat_and_my_fishes.jpeg'),
'object-type' : 'picture',
'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-image',
'icon-color' : '#472E17, #AB3DAB',
'keep' : '1',
'buddies' : str([ { 'name' : 'Blizzard',
'color' : '#472E17,#AB3DAB' },
{ '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' : '#6E3D1E,#F8C2F8',
'keep' : '0',
'buddies' : str([ { 'name' : 'Tomeu',
'color' : '#6E3D1E,#F8C2F8' },
{ 'name' : 'Eben',
'color' : '#193828,#216E21' },
{ 'name' : 'Dan',
'color' : '#75C228,#3A6E3A' } ])
},
{ 'file-path' : os.path.join(journal_dir, 'thai_story.hist'),
'object-type' : 'link',
'date' : str(time.time() - 450000),
'title' : 'Thai history',
'preview' : "The history of Thailand begins with the migration of the Thais from their ancestoral home in southern China into mainland southeast asia around the 10th century AD.",
'icon' : 'theme:object-link',
'icon-color' : '#75C228,#3A6E3A',
'keep' : '1',
'buddies' : str([ { 'name' : 'Dan',
'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' : 'Dan',
'color' : '#75C228,#3A6E3A' } ])
},
{ 'file-path' : os.path.join(journal_dir, 'thai_prince.abw'),
'object-type' : 'link',
'date' : str(time.time() - 450000),
'title' : 'The Thai Prince',
'preview' : "Prince Dipangkara Rasmijoti of Thailand, (born 29 April 2005), is a member of the Thailand Royal Family, a grandson of King Bhumibol Adulyadej (Rama IX) of Thailand is the fifth son of Maha Vajiralongkorn, Crown Prince of Thailand.",
'icon' : 'theme:object-link',
'icon-color' : '#193828,#216E21',
'keep' : '0',
'buddies' : str([ { 'name' : 'Eben',
'color' : '#193828,#216E21' },
{ 'name' : 'Dan',
'color' : '#75C228,#3A6E3A' },
{ 'name' : 'Blizzard',
'color' : '#472E17,#AB3DAB' } ])
},
{ 'file-path' : os.path.join(journal_dir, 'fishes_in_the_sea.history'),
'object-type' : 'picture',
'date' : str(time.time() - 100000),
'title' : 'Fishes in the Sea',
'preview' : 'There are many fishes in the sea, and not only...',
'icon' : 'theme:object-image',
'icon-color' : '#C2B00C,#785C78',
'keep' : '0',
'buddies' : str([ { 'name' : 'Marco',
'color' : '#C2B00C,#785C78' },
{ 'name' : 'Dan',
'color' : '#75C228,#3A6E3A' },
{ 'name' : 'Blizzard',
'color' : '#472E17,#AB3DAB' } ])
}
]
for obj in data:
data_store.create(obj)

View File

@ -1,4 +0,0 @@
[D-BUS Service]
Name = org.laptop.sugar.DataStore
Exec = @bindir@/sugar-data-store

View File

@ -1,34 +0,0 @@
#!/usr/bin/env python
# vi: ts=4 ai noet
#
# Copyright (C) 2006, Red Hat, Inc.
#
# 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
import sys
import os
import logging
from sugar import logger
from sugar import env
sys.path.append(env.get_service_path('datastore'))
logger.start('data-store')
logging.info('Starting the data store...')
import datastore
datastore.main()

View File

@ -22,6 +22,7 @@ activity must do to participate in the Sugar desktop.
import logging import logging
import os import os
import time
import gtk, gobject import gtk, gobject
@ -30,6 +31,8 @@ from sugar.activity.activityservice import ActivityService
from sugar.graphics.window import Window from sugar.graphics.window import Window
from sugar.graphics.toolbox import Toolbox from sugar.graphics.toolbox import Toolbox
from sugar.graphics.toolbutton import ToolButton from sugar.graphics.toolbutton import ToolButton
from sugar.datastore import datastore
from sugar import profile
class ActivityToolbar(gtk.Toolbar): class ActivityToolbar(gtk.Toolbar):
__gsignals__ = { __gsignals__ = {
@ -53,6 +56,30 @@ class ActivityToolbar(gtk.Toolbar):
if activity.get_shared(): if activity.get_shared():
self.share.set_sensitive(False) self.share.set_sensitive(False)
self.share.show() self.share.show()
if activity.jobject:
self.title = gtk.Entry()
self.title.set_text(activity.jobject['title'])
self.title.connect('activate', self._title_activate_cb)
self._add_widget(self.title, expand=True)
activity.jobject.connect('updated', self._jobject_updated_cb)
def _jobject_updated_cb(self, jobject):
self.title.set_text(jobject['title'])
def _title_activate_cb(self, entry):
self._activity.jobject['title'] = self.title.get_text()
self._activity.save()
def _add_widget(self, widget, expand=False):
tool_item = gtk.ToolItem()
tool_item.set_expand(expand)
tool_item.add(widget)
widget.show()
self.insert(tool_item, -1)
tool_item.show()
def _activity_shared_cb(self, activity): def _activity_shared_cb(self, activity):
self.share.set_sensitive(False) self.share.set_sensitive(False)
@ -102,7 +129,7 @@ class Activity(Window, gtk.Container):
'joined': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ([])) 'joined': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ([]))
} }
def __init__(self, handle): def __init__(self, handle, create_jobject=True):
"""Initialise the Activity """Initialise the Activity
handle -- sugar.activity.activityhandle.ActivityHandle handle -- sugar.activity.activityhandle.ActivityHandle
@ -110,6 +137,10 @@ class Activity(Window, gtk.Container):
presence service which *may* provide sharing for this presence service which *may* provide sharing for this
application application
create_jobject -- boolean
define if it should create a journal object if we are
not resuming
Side effects: Side effects:
Sets the gdk screen DPI setting (resolution) to the Sets the gdk screen DPI setting (resolution) to the
@ -145,6 +176,61 @@ class Activity(Window, gtk.Container):
self._bus = ActivityService(self) self._bus = ActivityService(self)
if handle.object_id:
self.jobject = datastore.get(handle.object_id)
elif create_jobject:
logging.debug('Creating a jobject.')
self.jobject = datastore.create()
self.jobject['title'] = 'New entry'
self.jobject['activity'] = self.get_service_name()
self.jobject['date'] = str(time.time())
self.jobject['icon'] = 'theme:object-text'
self.jobject['keep'] = '0'
self.jobject['buddies'] = ''
self.jobject['preview'] = ''
self.jobject['icon-color'] = profile.get_color().to_string()
self.jobject.file_path = '/tmp/teste'
f = open(self.jobject.file_path, 'w')
f.write('mec')
f.close()
datastore.write(self.jobject)
else:
self.jobject = None
self.connect('realize', self._realize_cb)
def _realize_cb(self, activity):
try:
self.read_file()
except NotImplementedError:
logging.debug('read_file() not implemented.')
pass
def read_file(self):
"""
Subclasses implement this method if they support resuming objects from
the journal. Can access the object through the jobject attribute.
"""
raise NotImplementedError
def write_file(self):
"""
Subclasses implement this method if they support saving data to objects
in the journal. Can access the object through the jobject attribute.
Must return the file path the data was saved to.
"""
raise NotImplementedError
def save(self):
"""Request that the activity is saved to the Journal."""
try:
file_path = self.write_file()
self.jobject.file_path = file_path
except NotImplementedError:
pass
datastore.write(self.jobject)
def _internal_joined_cb(self, activity, success, err): def _internal_joined_cb(self, activity, success, err):
"""Callback when join has finished""" """Callback when join has finished"""
self._shared_activity.disconnect(self._join_id) self._shared_activity.disconnect(self._join_id)
@ -200,6 +286,12 @@ class Activity(Window, gtk.Container):
self._shared_activity.leave() self._shared_activity.leave()
def _handle_close_cb(self, toolbar): def _handle_close_cb(self, toolbar):
if self.jobject:
try:
self.save()
except:
self.destroy()
raise
self.destroy() self.destroy()
def _handle_share_cb(self, toolbar): def _handle_share_cb(self, toolbar):

View File

@ -1,4 +1,5 @@
sugardir = $(pythondir)/sugar/datastore sugardir = $(pythondir)/sugar/datastore
sugar_PYTHON = \ sugar_PYTHON = \
__init__.py \ __init__.py \
dbus_helpers.py \
datastore.py datastore.py

View File

@ -1,4 +1,4 @@
# Copyright (C) 2006, Red Hat, Inc. # Copyright (C) 2007, One Laptop Per Child
# #
# This program is free software; you can redistribute it and/or modify # 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 # it under the terms of the GNU General Public License as published by
@ -15,289 +15,55 @@
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
import logging import logging
import dbus
import dbus.glib
import gobject import gobject
from sugar import util from sugar.datastore import dbus_helpers
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'
class WebSession(DataStoreObject):
def get_object_type(self):
return 'web_session'
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)
elif object_type == 'web_session':
return WebSession(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()
if obj.get_handle():
_data_store.update(int(obj.get_handle()), dbus.Dictionary(metadata))
return obj.get_handle()
else:
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 = {}
def get(self, object_path):
try:
return self._cache[object_path]
except KeyError:
return None
def add(self, obj):
op = obj.object_path()
if not self._cache.has_key(op):
self._cache[op] = obj
def remove(self, object_path):
try:
del self._cache[object_path]
except IndexError:
pass
class DSObject(gobject.GObject): class DSObject(gobject.GObject):
__gsignals__ = { __gsignals__ = {
'updated': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, 'updated': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
([gobject.TYPE_PYOBJECT, gobject.TYPE_PYOBJECT, gobject.TYPE_PYOBJECT])) ([gobject.TYPE_PYOBJECT]))
} }
_DS_OBJECT_DBUS_INTERFACE = "org.laptop.sugar.DataStore.Object" def __init__(self, object_id, metadata, file_path):
def __init__(self, bus, new_obj_cb, del_obj_cb, object_path):
gobject.GObject.__init__(self) gobject.GObject.__init__(self)
self._object_path = object_path self.object_id = object_id
self._ps_new_object = new_obj_cb self.metadata = metadata
self._ps_del_object = del_obj_cb self.file_path = file_path
bobj = bus.get_object(DS_DBUS_SERVICE, object_path)
self._dsobj = dbus.Interface(bobj, self._DS_OBJECT_DBUS_INTERFACE)
self._dsobj.connect_to_signal('Updated', self._updated_cb)
self._data = None
self._data_needs_update = True
self._properties = None
self._deleted = False
def object_path(self): def __getitem__(self, key):
return self._object_path return self.metadata[key]
def uid(self): def __setitem__(self, key, value):
if not self._properties: self.metadata[key] = value
self._properties = self._dsobj.get_properties([])
return self._properties['uid']
def _emit_updated_signal(self, data, prop_dict, deleted): def get(object_id):
self.emit('updated', data, prop_dict, deleted) logging.debug('datastore.get')
return False metadata = dbus_helpers.get_properties(object_id)
file_path = dbus_helpers.get_filename(object_id)
logging.debug('filepath: ' + file_path)
ds_object = DSObject(object_id, metadata, file_path)
# TODO: register the object for updates
return ds_object
def _update_internal_properties(self, prop_dict): def create():
did_update = False return DSObject(object_id=None, metadata={}, file_path=None)
for (key, value) in prop_dict.items():
if not len(value):
if self._properties.has_key(ley):
did_update = True
del self._properties[key]
else:
if self._properties.has_key(key):
if self._properties[key] != value:
did_update = True
self._properties[key] = value
else:
did_update = True
self._properties[key] = value
return did_update
def _updated_cb(self, data=False, prop_dict={}, deleted=False): def write(ds_object):
if self._update_internal_properties(prop_dict): logging.debug('datastore.write')
gobject.idle_add(self._emit_updated_signal, data, prop_dict, deleted) if ds_object.object_id:
self._deleted = deleted dbus_helpers.update(ds_object.object_id,
ds_object.metadata,
ds_object.file_path)
else:
ds_object.object_id = dbus_helpers.create(ds_object.metadata,
ds_object.file_path)
# TODO: register the object for updates
logging.debug('Written object %s to the datastore.' % ds_object.object_id)
def get_data(self): def find(query):
if self._data_needs_update: object_ids = dbus_helpers.find({})
data = self._dsobj.get_data() objects = []
self._data = "" for object_id in object_ids:
for c in data: objects.append(get(object_id))
self._data += chr(c) return objects
return self._data
def set_data(self, data):
old_data = self._data
self._data = data
try:
self._dsobj.set_data(dbus.ByteArray(data))
del old_data
except dbus.DBusException, e:
self._data = old_data
raise e
def set_properties(self, prop_dict):
old_props = self._properties
self._update_internal_properties(prop_dict)
try:
self._dsobj.set_properties(prop_dict)
del old_props
except dbus.DBusException, e:
self._properties = old_props
raise e
def get_properties(self, prop_list=[]):
if not self._properties:
self._properties = self._dsobj.get_properties(prop_list)
return self._properties
class DataStore(gobject.GObject):
_DS_DBUS_OBJECT_PATH = DS_DBUS_PATH + "/Object/"
def __init__(self):
gobject.GObject.__init__(self)
self._objcache = ObjectCache()
self._bus = dbus.SessionBus()
self._ds = dbus.Interface(self._bus.get_object(DS_DBUS_SERVICE,
DS_DBUS_PATH), DS_DBUS_INTERFACE)
def _new_object(self, object_path):
obj = self._objcache.get(object_path)
if obj:
return obj
if object_path.startswith(self._DS_DBUS_OBJECT_PATH):
obj = DSObject(self._bus, self._new_object,
self._del_object, object_path)
else:
raise RuntimeError("Unknown object type")
self._objcache.add(obj)
return obj
def _del_object(self, object_path):
# FIXME
pass
def get(self, uid=None, activity_id=None):
if not activity_id and not uid:
raise ValueError("At least one of activity_id or uid must be specified")
if activity_id and uid:
raise ValueError("Only one of activity_id or uid can be specified")
if activity_id:
if not util.validate_activity_id(activity_id):
raise ValueError("activity_id must be valid")
return self._new_object(self._ds.getActivityObject(activity_id))
elif uid:
if not len(uid):
raise ValueError("uid must be valid")
return self._new_object(self._ds.get(int(uid)))
raise RuntimeError("At least one of activity_id or uid must be specified")
def create(self, data, prop_dict={}, activity_id=None):
if activity_id and not util.validate_activity_id(activity_id):
raise ValueError("activity_id must be valid")
if not activity_id:
activity_id = ""
op = self._ds.create(dbus.ByteArray(data), dbus.Dictionary(prop_dict), activity_id)
return self._new_object(op)
def delete(self, obj):
op = obj.object_path()
obj = self._objcache.get(op)
if not obj:
raise RuntimeError("Object not found.")
self._ds.delete(op)
def find(self, prop_dict):
ops = self._ds.find(dbus.Dictionary(prop_dict))
objs = []
for op in ops:
objs.append(self._new_object(op))
return objs
_ds = None
def get_instance():
global _ds
if not _ds:
_ds = DataStore()
return _ds

View File

@ -0,0 +1,302 @@
# Copyright (C) 2006, Red Hat, Inc.
# Copyright (C) 2007, 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
import logging
import dbus
import dbus.glib
import 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)
def create(properties, filename):
logging.debug('dbus_helpers.create: %s, %s' % (properties, filename))
object_id = _data_store.create(dbus.Dictionary(properties), filename)
logging.debug('dbus_helpers.create: ' + object_id)
return object_id
def update(uid, properties, filename):
_data_store.update(uid, dbus.Dictionary(properties), filename)
def get_properties(uid):
return _data_store.get_properties(uid)
def get_filename(uid):
return _data_store.get_filename(uid)
def find(query):
return _data_store.find(query)
"""
class DataStoreObject(gobject.GObject):
__gsignals__ = {
'updated': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
([gobject.TYPE_PYOBJECT, gobject.TYPE_PYOBJECT, gobject.TYPE_PYOBJECT]))
}
def __init__(self, metadata, file_path=None, handle=None):
self._metadata = metadata
self.file_path = file_path
self.handle = handle
def __getitem__(self, key):
return self._metadata[key]
def __setitem__(self, key, value):
self._metadata[key] = value
def get_metadata(self):
return self._metadata
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']
return DataStoreObject(metadata, file_path, handle)
def create():
return DataStoreObject({})
def read(handle):
object_path = _data_store.get(handle)
return _read_from_object_path(object_path)
def write(obj):
if obj.handle:
_data_store.update(int(obj.handle),
dbus.Dictionary(obj.get_metadata().copy()),
obj.get_file_path())
else:
object_path = _data_store.create(dbus.Dictionary(metadata),
obj.get_file_path())
dbus_object = _bus.get_object(DS_DBUS_SERVICE, object_path)
obj.handle = 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 = {}
def get(self, object_path):
try:
return self._cache[object_path]
except KeyError:
return None
def add(self, obj):
op = obj.object_path()
if not self._cache.has_key(op):
self._cache[op] = obj
def remove(self, object_path):
try:
del self._cache[object_path]
except IndexError:
pass
class DSObject(gobject.GObject):
__gsignals__ = {
'updated': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
([gobject.TYPE_PYOBJECT, gobject.TYPE_PYOBJECT, gobject.TYPE_PYOBJECT]))
}
_DS_OBJECT_DBUS_INTERFACE = "org.laptop.sugar.DataStore.Object"
def __init__(self, bus, new_obj_cb, del_obj_cb, object_path):
gobject.GObject.__init__(self)
self._object_path = object_path
self._ps_new_object = new_obj_cb
self._ps_del_object = del_obj_cb
bobj = bus.get_object(DS_DBUS_SERVICE, object_path)
self._dsobj = dbus.Interface(bobj, self._DS_OBJECT_DBUS_INTERFACE)
self._dsobj.connect_to_signal('Updated', self._updated_cb)
self._data = None
self._data_needs_update = True
self._properties = None
self._deleted = False
def object_path(self):
return self._object_path
def uid(self):
if not self._properties:
self._properties = self._dsobj.get_properties([])
return self._properties['uid']
def _emit_updated_signal(self, data, prop_dict, deleted):
self.emit('updated', data, prop_dict, deleted)
return False
def _update_internal_properties(self, prop_dict):
did_update = False
for (key, value) in prop_dict.items():
if not len(value):
if self._properties.has_key(ley):
did_update = True
del self._properties[key]
else:
if self._properties.has_key(key):
if self._properties[key] != value:
did_update = True
self._properties[key] = value
else:
did_update = True
self._properties[key] = value
return did_update
def _updated_cb(self, data=False, prop_dict={}, deleted=False):
if self._update_internal_properties(prop_dict):
gobject.idle_add(self._emit_updated_signal, data, prop_dict, deleted)
self._deleted = deleted
def get_data(self):
if self._data_needs_update:
data = self._dsobj.get_data()
self._data = ""
for c in data:
self._data += chr(c)
return self._data
def set_data(self, data):
old_data = self._data
self._data = data
try:
self._dsobj.set_data(dbus.ByteArray(data))
del old_data
except dbus.DBusException, e:
self._data = old_data
raise e
def set_properties(self, prop_dict):
old_props = self._properties
self._update_internal_properties(prop_dict)
try:
self._dsobj.set_properties(prop_dict)
del old_props
except dbus.DBusException, e:
self._properties = old_props
raise e
def get_properties(self, prop_list=[]):
if not self._properties:
self._properties = self._dsobj.get_properties(prop_list)
return self._properties
class DataStore(gobject.GObject):
_DS_DBUS_OBJECT_PATH = DS_DBUS_PATH + "/Object/"
def __init__(self):
gobject.GObject.__init__(self)
self._objcache = ObjectCache()
self._bus = dbus.SessionBus()
self._ds = dbus.Interface(self._bus.get_object(DS_DBUS_SERVICE,
DS_DBUS_PATH), DS_DBUS_INTERFACE)
def _new_object(self, object_path):
obj = self._objcache.get(object_path)
if obj:
return obj
if object_path.startswith(self._DS_DBUS_OBJECT_PATH):
obj = DSObject(self._bus, self._new_object,
self._del_object, object_path)
else:
raise RuntimeError("Unknown object type")
self._objcache.add(obj)
return obj
def _del_object(self, object_path):
# FIXME
pass
def get(self, uid=None, activity_id=None):
if not activity_id and not uid:
raise ValueError("At least one of activity_id or uid must be specified")
if activity_id and uid:
raise ValueError("Only one of activity_id or uid can be specified")
if activity_id:
if not util.validate_activity_id(activity_id):
raise ValueError("activity_id must be valid")
return self._new_object(self._ds.getActivityObject(activity_id))
elif uid:
if not len(uid):
raise ValueError("uid must be valid")
return self._new_object(self._ds.get(int(uid)))
raise RuntimeError("At least one of activity_id or uid must be specified")
def create(self, data, prop_dict={}, activity_id=None):
if activity_id and not util.validate_activity_id(activity_id):
raise ValueError("activity_id must be valid")
if not activity_id:
activity_id = ""
op = self._ds.create(dbus.ByteArray(data), dbus.Dictionary(prop_dict), activity_id)
return self._new_object(op)
def delete(self, obj):
op = obj.object_path()
obj = self._objcache.get(op)
if not obj:
raise RuntimeError("Object not found.")
self._ds.delete(op)
def find(self, prop_dict):
ops = self._ds.find(dbus.Dictionary(prop_dict))
objs = []
for op in ops:
objs.append(self._new_object(op))
return objs
_ds = None
def get_instance():
global _ds
if not _ds:
_ds = DataStore()
return _ds
"""

View File

@ -55,13 +55,11 @@ class ComboBox(gtk.ComboBox):
def append_item(self, action_id, text, icon_name=None): def append_item(self, action_id, text, icon_name=None):
if not self._icon_renderer and icon_name: if not self._icon_renderer and icon_name:
logging.debug('Adding icon renderer.')
self._icon_renderer = gtk.CellRendererPixbuf() self._icon_renderer = gtk.CellRendererPixbuf()
self.pack_start(self._icon_renderer, False) self.pack_start(self._icon_renderer, False)
self.add_attribute(self._icon_renderer, 'icon-name', 2) self.add_attribute(self._icon_renderer, 'icon-name', 2)
if not self._text_renderer and text: if not self._text_renderer and text:
logging.debug('Adding text renderer.')
self._text_renderer = gtk.CellRendererText() self._text_renderer = gtk.CellRendererText()
self.pack_end(self._text_renderer, True) self.pack_end(self._text_renderer, True)
self.add_attribute(self._text_renderer, 'text', 1) self.add_attribute(self._text_renderer, 'text', 1)