[hamster-applet] cleaned up the storage API and added documentation strings to client.py so it can be used externally
- From: Toms Baugis <tbaugis src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [hamster-applet] cleaned up the storage API and added documentation strings to client.py so it can be used externally
- Date: Fri, 16 Apr 2010 00:03:11 +0000 (UTC)
commit a4cf6335d60ee1cf7345a85bf7792a2d15c1c074
Author: Toms Bauģis <toms baugis gmail com>
Date: Fri Apr 16 01:02:24 2010 +0100
cleaned up the storage API and added documentation strings to client.py so it can be used externally too
src/hamster-applet | 4 +-
src/hamster-client | 2 +-
src/hamster-standalone | 2 +-
src/hamster/applet.py | 2 +-
src/hamster/client.py | 135 ++++++++++++++++++++++------------
src/hamster/db.py | 25 ++-----
src/hamster/preferences.py | 6 +-
src/hamster/storage.py | 51 +++----------
src/hamster/widgets/activityentry.py | 6 +-
src/hamster/widgets/tags.py | 2 +-
10 files changed, 120 insertions(+), 115 deletions(-)
---
diff --git a/src/hamster-applet b/src/hamster-applet
index d739609..54244c2 100755
--- a/src/hamster-applet
+++ b/src/hamster-applet
@@ -45,8 +45,8 @@ def on_destroy(event):
# handle config option to stop tracking on shutdown
if conf.get("stop_on_shutdown"):
- last_activity = runtime.storage.get_last_activity()
- if last_activity and last_activity['end_time'] is None:
+ todays_facts = runtime.storage.get_todays_facts()
+ if todays_facts and todays_facts[-1]['end_time'] is None:
runtime.storage.stop_tracking()
if gtk.main_level():
diff --git a/src/hamster-client b/src/hamster-client
index 23f2579..bad9ab9 100755
--- a/src/hamster-client
+++ b/src/hamster-client
@@ -111,7 +111,7 @@ class HamsterClient(object):
def list_categories(self):
'''Print the names of all the categories.'''
- for category in self.storage.get_category_list():
+ for category in self.storage.get_categories():
print category['name'].encode('utf8')
diff --git a/src/hamster-standalone b/src/hamster-standalone
index 0a4dd3e..1a1f1c6 100755
--- a/src/hamster-standalone
+++ b/src/hamster-standalone
@@ -381,7 +381,7 @@ class ProjectHamster(object):
if parsed_activity:
category_id = None
if parsed_activity.category_name:
- category_id = runtime.storage.get_category_by_name(parsed_activity.category_name)
+ category_id = runtime.storage.get_category_id(parsed_activity.category_name)
activity = runtime.storage.get_activity_by_name(parsed_activity.activity_name,
category_id,
diff --git a/src/hamster/applet.py b/src/hamster/applet.py
index 310db03..b02288e 100755
--- a/src/hamster/applet.py
+++ b/src/hamster/applet.py
@@ -610,7 +610,7 @@ class HamsterApplet(object):
if parsed_activity:
category_id = None
if parsed_activity.category_name:
- category_id = runtime.storage.get_category_by_name(parsed_activity.category_name)
+ category_id = runtime.storage.get_category_id(parsed_activity.category_name)
activity = runtime.storage.get_activity_by_name(parsed_activity.activity_name,
category_id,
diff --git a/src/hamster/client.py b/src/hamster/client.py
index ecd7377..576d9c1 100644
--- a/src/hamster/client.py
+++ b/src/hamster/client.py
@@ -24,23 +24,6 @@ from calendar import timegm
import dbus, dbus.mainloop.glib
import gobject
-def debus(value):
- """recasts dbus types to the basic ones. should be quite an overhead"""
-
- if isinstance(value, dbus.Array):
- return [debus(val) for val in value]
-
- elif isinstance(value, dbus.Dictionary):
- return dict([(debus(key), debus(val)) for key, val in value.items()])
-
- elif isinstance(value, unicode):
- return unicode(value)
- elif isinstance(value, int):
- return int(value)
- elif isinstance(value, bool):
- return bool(value)
-
- return value
def from_dbus_fact(fact):
"""unpack the struct into a proper dict"""
@@ -59,6 +42,18 @@ def from_dbus_fact(fact):
class Storage(gobject.GObject):
+ """Hamster client class, communicating to hamster storage daemon via d-bus.
+ Subscribe to the `tags-changed`, `facts-changed` and `activities-changed`
+ signals to be notified when an appropriate factoid of interest has been
+ changed.
+
+ In storage a distinguishment is made between the classificator of
+ activities and the event in tracking log.
+ When talking about the event we use term 'fact'. For the classificator
+ we use term 'activity'.
+ The relationship is - one activity can be used in several facts.
+ The rest is hopefully obvious. But if not, please file bug reports!
+ """
__gsignals__ = {
"tags-changed": (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()),
"facts-changed": (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()),
@@ -78,26 +73,35 @@ class Storage(gobject.GObject):
self.conn = hamster_conn
if parent:
- bus.add_signal_receiver(self.on_tags_changed, 'TagsChanged', 'org.gnome.Hamster')
- bus.add_signal_receiver(self.on_facts_changed, 'FactsChanged', 'org.gnome.Hamster')
- bus.add_signal_receiver(self.on_activities_changed, 'ActivitiesChanged', 'org.gnome.Hamster')
+ bus.add_signal_receiver(self._on_tags_changed, 'TagsChanged', 'org.gnome.Hamster')
+ bus.add_signal_receiver(self._on_facts_changed, 'FactsChanged', 'org.gnome.Hamster')
+ bus.add_signal_receiver(self._on_activities_changed, 'ActivitiesChanged', 'org.gnome.Hamster')
- def on_tags_changed(self):
+ def _on_tags_changed(self):
self.emit("tags-changed")
- def on_facts_changed(self):
+ def _on_facts_changed(self):
self.emit("facts-changed")
- def on_activities_changed(self):
+ def _on_activities_changed(self):
self.emit("activities-changed")
def get_todays_facts(self):
+ """returns facts of the current date, respecting hamster midnight
+ hamster midnight is stored in gconf, and presented in minutes
+ """
return [from_dbus_fact(fact) for fact in self.conn.GetTodaysFacts()]
- def get_facts(self, date, end_date = 0, search_terms = ""):
+ def get_facts(self, date, end_date = None, search_terms = ""):
+ """Returns facts for the time span matching the optional filter criteria.
+ In search terms comma (",") translates to boolean OR and space (" ")
+ to boolean AND.
+ Filter is applied to tags, categories, activity names and description
+ """
date = timegm(date.timetuple())
+ end_date = end_date or 0
if end_date:
end_date = timegm(end_date.timetuple())
@@ -105,38 +109,61 @@ class Storage(gobject.GObject):
end_date,
search_terms)]
-
def get_autocomplete_activities(self, search = ""):
+ """returns list of activities name matching search criteria.
+ results are sorted by most recent usage.
+ search is case insensitive
+ """
return self.conn.GetAutocompleteActivities(search)
- def get_category_list(self):
+ def get_categories(self):
+ """returns list of categories"""
return self.conn.GetCategories()
- def get_tags(self, autocomplete = None):
- return self.conn.GetTags(True)
+ def get_tags(self):
+ """returns list of all tags. by default only those that have been set for autocomplete"""
+ return self.conn.GetTags()
def get_tag_ids(self, tags):
+ """find tag IDs by name. tags should be a list of labels
+ if a requested tag had been removed from the autocomplete list, it
+ will be ressurrected. if tag with such label does not exist, it will
+ be created.
+ on database changes the `tags-changed` signal is emitted.
+ """
return self.conn.GetTagIds(tags)
def update_autocomplete_tags(self, tags):
+ """update list of tags that should autocomplete. this list replaces
+ anything that is currently set"""
self.conn.SetTagsAutocomplete(tags)
def get_fact(self, id):
+ """returns fact by it's ID"""
return from_dbus_fact(self.conn.GetFact(id))
- def add_fact(self, activity_name, tags = '', start_time = None, end_time = 0,
+ def add_fact(self, activity_name, tags = '', start_time = None, end_time = None,
category_name = None, description = None):
+ """Add fact. activity name can use `[-]start_time[-end_time] activity category, description #tag1 #tag2`
+ syntax, or params can be stated explicitly.
+ Params, except for the start and end times will take precedence over
+ derived values.
+ start_time defaults to current moment.
+ """
+
+ start_time = start_time or 0
if start_time:
start_time = timegm(start_time.timetuple())
- else:
- start_time = 0
+ end_time = end_time or 0
if end_time:
end_time = timegm(end_time.timetuple())
- else:
- end_time = 0
+
+ if isinstance(tags, list): #make sure we send what storage expects
+ tags = ", ".join(tags)
+ tags = tags or ''
category_name = category_name or ''
description = description or ''
@@ -144,34 +171,58 @@ class Storage(gobject.GObject):
return self.conn.AddFact(activity_name, tags, start_time, end_time, category_name, description)
def stop_tracking(self, end_time = None):
+ """Stop tracking current activity. end_time can be passed in if the
+ activity should have other end time than the current moment"""
end_time = timegm((end_time or dt.datetime.now()).timetuple())
return self.conn.StopTracking(end_time)
def remove_fact(self, fact_id):
+ "delete fact from database"
self.conn.RemoveFact(fact_id)
- def update_fact(self, fact_id, activity_name, tags, start_time = 0, end_time = 0, category_name = '', description = ''):
+ def update_fact(self, fact_id, activity_name, tags = None, start_time = None, end_time = None, category_name = None, description = None):
+ """Update fact values. See add_fact for rules.
+ Update is performed via remove/insert, so the
+ fact_id after update should not be used anymore. Instead use the ID
+ from the fact dict that is returned by this function"""
+
category_name = category_name or '' # so to override None
description = description or '' # so to override None
+ start_time = start_time or 0
if start_time:
start_time = timegm(start_time.timetuple())
+
+ end_time = end_time or 0
if end_time:
end_time = timegm(end_time.timetuple())
- else:
- end_time = 0
+
+ if tags and isinstance(tags, list):
+ tags = ", ".join(tags)
+ tags = tags or ''
return self.conn.UpdateFact(fact_id, activity_name, tags, start_time, end_time, category_name, description)
def get_activities(self, category_id = None):
+ """Return activities for category. If category is not specified, will
+ return activities that have no category"""
category_id = category_id or -1
return self.conn.GetActivities(category_id)
+ def get_category_id(self, category_name):
+ """returns category id by name"""
+ return self.conn.GetCategoryId(category_name)
- def get_last_activity(self):
- return self.conn.GetLastActivity()
+ def get_activity_by_name(self, activity, category_id = None, ressurect = True):
+ """returns activity dict by name and optionally filtering by category.
+ if activity is found but is marked as deleted, it will be resurrected
+ unless told otherise in the ressurect param
+ """
+ category_id = category_id or 0
+ return self.conn.GetActivityByName(activity, category_id, ressurect)
+ # category and activity manipulations (normally just via preferences)
def remove_activity(self, id):
self.conn.RemoveActivity(id)
@@ -198,11 +249,3 @@ class Storage(gobject.GObject):
def add_category(self, name):
return self.conn.AddCategory(name)
-
-
- def get_category_by_name(self, category):
- return self.conn.GetCategoryByName(category)
-
- def get_activity_by_name(self, activity, category_id = None, ressurect = True):
- category_id = category_id or 0
- return self.conn.GetActivityByName(activity, category_id, ressurect)
diff --git a/src/hamster/db.py b/src/hamster/db.py
index 099452a..249e67e 100644
--- a/src/hamster/db.py
+++ b/src/hamster/db.py
@@ -113,13 +113,8 @@ class Storage(storage.Storage):
self.__last_etag = self.__database_file.query_info(gio.FILE_ATTRIBUTE_ETAG_VALUE).get_etag()
#tags, here we come!
- def __get_tags(self, autocomplete = None):
- query = "select * from tags"
- if autocomplete:
- query += " where autocomplete='true'"
-
- query += " order by name"
- return self.fetchall(query)
+ def __get_tags(self):
+ return self.fetchall("select * from tags order by name")
def __get_tag_ids(self, tags):
"""look up tags by their name. create if not found"""
@@ -177,7 +172,7 @@ class Storage(storage.Storage):
return changes or len(to_delete + to_uncomplete) > 0
- def __get_category_list(self):
+ def __get_categories(self):
return self.fetchall("SELECT * FROM categories ORDER BY category_order")
def __change_category(self, id, category_id):
@@ -299,7 +294,7 @@ class Storage(storage.Storage):
return None
- def __get_category_by_name(self, name):
+ def __get_category_id(self, name):
"""returns category by it's name"""
query = """
@@ -357,12 +352,6 @@ class Storage(storage.Storage):
grouped_facts.append(grouped_fact)
return grouped_facts
- def __get_last_activity(self):
- facts = self.__get_todays_facts()
- last_activity = None
- if facts and facts[-1]["end_time"] == None:
- last_activity = facts[-1]
- return last_activity
def __touch_fact(self, fact, end_time):
end_time = end_time or dt.datetime.now()
@@ -535,7 +524,7 @@ class Storage(storage.Storage):
# now check if maybe there is also a category
category_id = None
if activity.category_name:
- category_id = self.__get_category_by_name(activity.category_name)
+ category_id = self.__get_category_id(activity.category_name)
if not category_id:
category_id = self.__add_category(activity.category_name)
@@ -793,12 +782,12 @@ class Storage(storage.Storage):
activity names converted to lowercase"""
query = """
- SELECT lower(a.name) AS name, b.name AS category
+ SELECT a.name AS name, b.name AS category
FROM activities a
LEFT JOIN categories b ON coalesce(b.id, -1) = a.category_id
LEFT JOIN facts f ON a.id = f.activity_id
WHERE deleted IS NULL
- AND a.name LIKE ? ESCAPE '\\'
+ AND lower(a.name) LIKE ? ESCAPE '\\'
GROUP BY a.id
ORDER BY max(f.start_time) DESC, lower(a.name)
LIMIT 50
diff --git a/src/hamster/preferences.py b/src/hamster/preferences.py
index ea2a033..0ecec77 100755
--- a/src/hamster/preferences.py
+++ b/src/hamster/preferences.py
@@ -51,7 +51,7 @@ class CategoryStore(gtk.ListStore):
""" Loads activity list from database, ordered by
activity_order """
- category_list = runtime.storage.get_category_list()
+ category_list = runtime.storage.get_categories()
for category in category_list:
self.append([category['id'],
@@ -277,7 +277,7 @@ class PreferencesEditor:
day_start = dt.time(day_start / 60, day_start % 60)
self.day_start.set_time(day_start)
- self.tags = [tag["name"] for tag in runtime.storage.get_tags(autocomplete=True)]
+ self.tags = [tag["name"] for tag in runtime.storage.get_tags()]
self.get_widget("autocomplete_tags").set_text(", ".join(self.tags))
self.workspace_mapping = conf.get("workspace_mapping")
@@ -424,7 +424,7 @@ class PreferencesEditor:
return False #ignoring unsorted category
#look for dupes
- categories = runtime.storage.get_category_list()
+ categories = runtime.storage.get_categories()
for category in categories:
if category['name'].lower() == new_text.lower():
if id == -2: # that was a new category
diff --git a/src/hamster/storage.py b/src/hamster/storage.py
index 43ef547..0ef1fbb 100644
--- a/src/hamster/storage.py
+++ b/src/hamster/storage.py
@@ -26,7 +26,6 @@ def to_dbus_fact(fact):
"""Perform the conversion between fact database query and
dbus supported data types
"""
-
return (fact['id'],
timegm(fact['start_time'].timetuple()),
timegm(fact['end_time'].timetuple()) if fact['end_time'] else 0,
@@ -109,18 +108,7 @@ class Storage(dbus.service.Object):
@dbus.service.method("org.gnome.Hamster", in_signature='i', out_signature='(iiissisasii)')
def GetFact(self, fact_id):
- """Gets the current displaying fact
- Parameters:
- i id: Unique fact identifier
- Returns Dict of:
- i id: Unique fact identifier
- s name: Activity name
- s category: Category name
- s description: Description of the fact
- u start_time: Seconds since epoch (timestamp)
- u end_time: Seconds since epoch (timestamp)
- as tags: List of tags used
- """
+ """Get fact by id. For output format see GetFacts"""
fact = dict(self.__get_fact(fact_id))
fact['date'] = fact['start_time'].date()
fact['delta'] = dt.timedelta()
@@ -155,14 +143,15 @@ class Storage(dbus.service.Object):
"""Stops tracking the current activity"""
end_time = dt.datetime.utcfromtimestamp(end_time)
- fact = self.__get_last_activity()
- if fact:
- self.__touch_fact(fact, end_time)
+ facts = self.__get_todays_facts()
+ if facts:
+ self.__touch_fact(facts[-1], end_time)
self.FactsChanged()
@dbus.service.method("org.gnome.Hamster", in_signature='i')
def RemoveFact(self, fact_id):
+ """Remove fact from storage by it's ID"""
self.start_transaction()
fact = self.__get_fact(fact_id)
if fact:
@@ -219,9 +208,9 @@ class Storage(dbus.service.Object):
self.ActivitiesChanged()
return res
- @dbus.service.method("org.gnome.Hamster", in_signature='s', out_signature='a{sv}')
- def GetCategoryByName(self, category):
- return dict(self.__get_category_by_name(category))
+ @dbus.service.method("org.gnome.Hamster", in_signature='s', out_signature='i')
+ def GetCategoryId(self, category):
+ return self.__get_category_id(category)
@dbus.service.method("org.gnome.Hamster", in_signature='is')
def UpdateCategory(self, id, name):
@@ -238,7 +227,7 @@ class Storage(dbus.service.Object):
@dbus.service.method("org.gnome.Hamster", out_signature='aa{sv}')
def GetCategories(self):
res = []
- for category in self.__get_category_list():
+ for category in self.__get_categories():
category = dict(category)
category['color_code'] = category['color_code'] or ''
res.append(category)
@@ -269,22 +258,6 @@ class Storage(dbus.service.Object):
self.ActivitiesChanged()
return result
- @dbus.service.method("org.gnome.Hamster", out_signature='a{sv}')
- def GetLastActivity(self):
- """Gets the current displaying fact
- Returns Dict of:
- i id: Unique fact identifier
- s name: Activity name
- s category: Category name
- s description: Description of the fact
- u start_time: Seconds since epoch (timestamp)
- u end_time: Seconds since epoch (timestamp)
- u end_time: Seconds since epoch (timestamp)
- as tags: List of tags used
- """
- return to_dbus_fact(self__.get_last_activity())
-
-
@dbus.service.method("org.gnome.Hamster", in_signature='i', out_signature='aa{sv}')
def GetActivities(self, category_id = None):
if not category_id or category_id == -1:
@@ -342,9 +315,9 @@ class Storage(dbus.service.Object):
return {}
# tags
- @dbus.service.method("org.gnome.Hamster", in_signature='b', out_signature='aa{sv}')
- def GetTags(self, autocomplete = None):
- return [dict(tag) for tag in self.__get_tags(autocomplete)]
+ @dbus.service.method("org.gnome.Hamster", out_signature='aa{sv}')
+ def GetTags(self):
+ return [dict(tag) for tag in self.__get_tags()]
@dbus.service.method("org.gnome.Hamster", in_signature='as', out_signature='aa{sv}')
diff --git a/src/hamster/widgets/activityentry.py b/src/hamster/widgets/activityentry.py
index 50b2c8d..aefc638 100644
--- a/src/hamster/widgets/activityentry.py
+++ b/src/hamster/widgets/activityentry.py
@@ -185,7 +185,7 @@ class ActivityEntry(gtk.Entry):
# do not cache as ordering and available options change over time
self.activities = runtime.storage.get_autocomplete_activities(input_activity.activity_name)
- self.categories = self.categories or runtime.storage.get_category_list()
+ self.categories = self.categories or runtime.storage.get_categories()
time = ''
@@ -210,14 +210,14 @@ class ActivityEntry(gtk.Entry):
else:
key = input_activity.activity_name.decode('utf8', 'replace').lower()
for activity in self.activities:
- fillable = activity['name']
+ fillable = activity['name'].lower()
if activity['category']:
fillable += "@%s" % activity['category']
if time: #as we also support deltas, for the time we will grab anything up to first space
fillable = "%s %s" % (self.filter.split(" ", 1)[0], fillable)
- store.append([fillable, activity['name'], activity['category'], time])
+ store.append([fillable, activity['name'].lower(), activity['category'], time])
def after_activity_update(self, widget, event):
self.refresh_activities()
diff --git a/src/hamster/widgets/tags.py b/src/hamster/widgets/tags.py
index e1c5148..a724b35 100644
--- a/src/hamster/widgets/tags.py
+++ b/src/hamster/widgets/tags.py
@@ -120,7 +120,7 @@ class TagsEntry(gtk.Entry):
self.categories = None
def populate_suggestions(self):
- self.tags = self.tags or [tag["name"] for tag in runtime.storage.get_tags(autocomplete = True)]
+ self.tags = self.tags or [tag["name"] for tag in runtime.storage.get_tags()]
cursor_tag = self.get_cursor_tag()
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]