[hamster-applet] cleaned up the storage API and added documentation strings to client.py so it can be used externally



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]