[hamster-applet] warning - unstable if not unusable! splitting up whole thing in server (storage & db backend) and cl



commit 8dc9596d8b130da56477c4ffd3670bf97bfba9b4
Author: Toms Bauģis <toms baugis gmail com>
Date:   Mon Apr 12 22:33:13 2010 +0100

    warning - unstable if not unusable! splitting up whole thing in server (storage & db backend) and client (the rest) and communicating via a wakeup daemon.

 .gitignore                                |    1 +
 Makefile.am                               |   19 ++-
 bye-hamster.sh                            |    1 +
 configure.ac                              |    2 -
 org.gnome.hamster.service.in              |    3 +
 src/Makefile.am                           |   22 +--
 src/{hamster-applet.py => hamster-applet} |    0
 src/{hamster-client.py => hamster-client} |   10 +-
 src/hamster-service                       |   40 +++
 src/hamster-standalone                    |   19 --
 src/hamster/Makefile.am                   |    2 +-
 src/hamster/applet.py                     |   20 +--
 src/hamster/client.py                     |  193 +++++++++++++++
 src/hamster/configuration.py              |    4 +-
 src/hamster/db.py                         |   39 ++--
 src/hamster/hamsterdbus.py                |  290 ----------------------
 src/hamster/overview_totals.py            |    7 +-
 src/hamster/storage.py                    |  375 ++++++++++++++++++++++-------
 tests/hamsterdbus_test.py                 |  169 -------------
 19 files changed, 581 insertions(+), 635 deletions(-)
---
diff --git a/.gitignore b/.gitignore
index ba0b051..796d2eb 100644
--- a/.gitignore
+++ b/.gitignore
@@ -20,3 +20,4 @@ autom4te.cache
 m4
 gnome-doc-utils.make
 hamster-applet-*.tar.gz
+org.gnome.hamster.service
diff --git a/Makefile.am b/Makefile.am
index e703d85..e2116d8 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -3,6 +3,15 @@ ACLOCAL_AMFLAGS = -I m4
 
 DISTCHECK_CONFIGURE_FLAGS = --disable-scrollkeeper
 
+# Dbus service file
+servicedir = $(datadir)/dbus-1/services
+nodist_service_DATA = org.gnome.hamster.service
+
+org.gnome.hamster.service: org.gnome.hamster.service.in
+	sed	-e s!\ prefix\@!$(prefix)! < $< > $@
+org.gnome.hamster.service: Makefile
+
+
 release: dist
 	scp $(PACKAGE)-$(VERSION).tar.gz tbaugis master gnome org:
 	ssh tbaugis master gnome org install-module $(PACKAGE)-$(VERSION).tar.gz
@@ -12,10 +21,16 @@ DISTCLEANFILES =		\
 	intltool-merge		\
 	intltool-update
 
+CLEANFILES = org.gnome.hamster.service
 EXTRA_DIST = \
 	MAINTAINERS \
 	TODO \
 	intltool-extract.in	\
 	intltool-merge.in	\
-	intltool-update.in
-	gnome-doc-utils.make
+	intltool-update.in  \
+	gnome-doc-utils.make    \
+	org.gnome.hamster.service.in
+
+
+
+all-local: org.gnome.hamster.service
diff --git a/bye-hamster.sh b/bye-hamster.sh
index 3171b85..d75042e 100755
--- a/bye-hamster.sh
+++ b/bye-hamster.sh
@@ -10,3 +10,4 @@ sudo rm -Rf /usr/lib/python2.6/dist-packages/hamster/
 sudo rm -Rf /usr/local/lib/python2.6/dist-packages/hamster/
 sudo rm -Rf /usr/local/lib/python2.6/site-packages/hamster/
 sudo rm -Rf /usr/share/gnome/help/hamster-applet/
+sudo rm -f /usr/bin/hamster-*
diff --git a/configure.ac b/configure.ac
index 66fcdbd..18cd08e 100644
--- a/configure.ac
+++ b/configure.ac
@@ -144,8 +144,6 @@ except:
     fi
 fi
 
-
-
 dnl ==========================
 dnl Control-Center Keybindings
 dnl ==========================
diff --git a/org.gnome.hamster.service.in b/org.gnome.hamster.service.in
new file mode 100644
index 0000000..f0bb9f2
--- /dev/null
+++ b/org.gnome.hamster.service.in
@@ -0,0 +1,3 @@
+[D-BUS Service]
+Name=org.gnome.Hamster
+Exec= prefix@/bin/hamster-service
diff --git a/src/Makefile.am b/src/Makefile.am
index 4da980c..16331ed 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -4,14 +4,6 @@ ACLOCAL_AMFLAGS = -I m4
 CPPFLAGS = \
 	$(PYTHON_INCLUDES)
 
-hamster-applet: hamster-applet.py
-	$(AM_V_GEN)sed -e "s|\ PYTHONDIR\@|$(pyexecdir)|" $< > $@
-
-hamster-client: hamster-client.py
-	$(AM_V_GEN)sed -e "s|__DEBUG__ = True|__DEBUG__ = False|" \
-				   -e "s|\ PYTHONDIR\@|$(pyexecdir)|" \
-				   $< > $@
-
 hamsterlibdir = $(libdir)/hamster-applet
 hamsterlib_SCRIPTS = hamster-applet
 
@@ -20,20 +12,14 @@ hamsterbindir = $(bindir)
 hamsterbin_SCRIPTS = \
 	hamster-standalone \
 	gnome-time-tracker \
-	hamster-client
-
-BUILT_SOURCES = \
-	hamster-applet \
-	hamster-client
-
-CLEANFILES = \
-	$(BUILT_SOURCES)
+	hamster-client \
+        hamster-service
 
 DISTCLEANFILES = \
 	$(CLEANFILES)
 
 EXTRA_DIST = \
-	hamster-applet.py \
+	hamster-applet \
 	hamster-standalone \
 	gnome-time-tracker \
-	hamster-client.py
+	hamster-client
diff --git a/src/hamster-applet.py b/src/hamster-applet
similarity index 100%
rename from src/hamster-applet.py
rename to src/hamster-applet
diff --git a/src/hamster-client.py b/src/hamster-client
similarity index 98%
rename from src/hamster-client.py
rename to src/hamster-client
index 4893f65..072f5c6 100755
--- a/src/hamster-client.py
+++ b/src/hamster-client
@@ -25,7 +25,7 @@ import sys, os
 import optparse
 import re
 import locale, gettext
-import time
+from calendar import timegm
 import datetime as dt
 
 import dbus
@@ -111,7 +111,7 @@ class HamsterClient(object):
                               len(headers['tags'])
 
         print "%s+%s" % ('-' * first_column_width, '-' * second_column_width)
-        for fact in self.conn.GetFacts(start_stamp, end_stamp):
+        for fact in self.conn.GetFacts(start_stamp, end_stamp, ""):
             if fact['start_time'] < start_stamp or fact['start_time'] > end_stamp:
                 # Hamster returns activities for the whole day, not just the
                 # time range we sent
@@ -140,8 +140,7 @@ class HamsterClient(object):
 def timestamp_from_datetime(date):
     '''Convert a local time datetime into an utc timestamp.'''
     if date:
-        tz_offset =  dt.timedelta(seconds=time.timezone)
-        return time.mktime((date-tz_offset).timetuple())
+        return timegm(date.timetuple())
     else:
         return 0
 
@@ -321,6 +320,3 @@ Time formats:
 
     elif command == 'list-categories':
         hamster_client.list_categories()
-
-
-
diff --git a/src/hamster-service b/src/hamster-service
new file mode 100755
index 0000000..49c95ff
--- /dev/null
+++ b/src/hamster-service
@@ -0,0 +1,40 @@
+#!/usr/bin/python
+# nicked off gwibber
+
+import sys, optparse, gobject, dbus
+from os.path import join, dirname, exists, realpath, abspath
+from os import popen, getpid
+from dbus.mainloop.glib import DBusGMainLoop
+
+DBusGMainLoop(set_as_default=True)
+loop = gobject.MainLoop()
+
+# if gwibber-serivce is already running, don't start
+if "org.gnome.Hamster.Connection" in dbus.SessionBus().list_names():
+    print "Found hamster-service already running, exiting"
+    quit()
+
+
+LAUNCH_DIR = abspath(sys.path[0])
+SOURCE_DIR = LAUNCH_DIR
+STORAGE = join(SOURCE_DIR, "hamster", "storage.py")
+
+######################################################################
+# Setup path
+if exists(SOURCE_DIR):
+    sys.path.insert(0, realpath(dirname(SOURCE_DIR)))
+    try:
+        from hamster import db
+    finally:
+        del sys.path[0]
+
+else:
+    from hamster import db
+
+#TODO - add db monitor (or maybe do that in db itself)
+
+
+print "Starting up service..."
+
+storage = db.Storage(loop)
+loop.run()
diff --git a/src/hamster-standalone b/src/hamster-standalone
index 14c7d7e..7ff890a 100755
--- a/src/hamster-standalone
+++ b/src/hamster-standalone
@@ -33,7 +33,6 @@ from hamster import eds
 from hamster.configuration import conf, runtime, dialogs
 
 from hamster import stuff, keybinder
-from hamster.hamsterdbus import HAMSTER_URI, HamsterDbusController
 
 # controllers for other windows
 from hamster import widgets
@@ -85,9 +84,6 @@ class ProjectHamster(object):
         # DBus Setup
         try:
             dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
-            name = dbus.service.BusName(HAMSTER_URI, dbus.SessionBus())
-            self.dbusController = HamsterDbusController(bus_name = name)
-
             # Set up connection to the screensaver
             self.dbusIdleListener = idle.DbusIdleListener(runtime.dispatcher)
             runtime.dispatcher.add_handler('active_changed', self.on_idle_changed)
@@ -110,9 +106,6 @@ class ProjectHamster(object):
         self.last_activity = None
         self.load_day()
 
-        # Hamster DBusController current fact initialising
-        self.__update_fact()
-
         # refresh hamster every 60 seconds to update duration
         gobject.timeout_add_seconds(60, self.refresh_hamster)
 
@@ -288,17 +281,6 @@ class ProjectHamster(object):
         self.window.present()
 
 
-    def __update_fact(self):
-        """dbus controller current fact updating"""
-        last_activity_id = 0
-
-        if not self.last_activity:
-            self.dbusController.TrackingStopped()
-        else:
-            last_activity_id = self.last_activity['id']
-
-        self.dbusController.FactUpdated(last_activity_id)
-
     def _delayed_display(self):
         """show window only when gtk has become idle. otherwise we get
         mixed results. TODO - this looks like a hack though"""
@@ -356,7 +338,6 @@ class ProjectHamster(object):
 
     def after_fact_update(self, event, date):
         self.load_day()
-        self.__update_fact()
 
     def on_idle_changed(self, event, state):
         # state values: 0 = active, 1 = idle
diff --git a/src/hamster/Makefile.am b/src/hamster/Makefile.am
index 0a3b9c1..acfeec9 100644
--- a/src/hamster/Makefile.am
+++ b/src/hamster/Makefile.am
@@ -24,7 +24,7 @@ hamster_PYTHON = \
 	edit_activity.py \
 	applet.py \
 	about.py \
-	hamsterdbus.py \
+	client.py \
 	idle.py \
         i18n.py \
         pytweener.py \
diff --git a/src/hamster/applet.py b/src/hamster/applet.py
index 35962a6..323ffd9 100755
--- a/src/hamster/applet.py
+++ b/src/hamster/applet.py
@@ -1,6 +1,6 @@
 # - coding: utf-8 -
 
-# Copyright (C) 2007-2009 Toms Bauģis <toms.baugis at gmail.com>
+# Copyright (C) 2007-2010 Toms Bauģis <toms.baugis at gmail.com>
 # Copyright (C) 2007-2009 Patryk Zawadzki <patrys at pld-linux.org>
 # Copyright (C) 2008 PÄ?teris Caune <cuu508 at gmail.com>
 
@@ -35,7 +35,6 @@ from configuration import conf, runtime, dialogs
 
 import stuff
 import keybinder
-from hamsterdbus import HAMSTER_URI, HamsterDbusController
 
 # controllers for other windows
 import widgets
@@ -225,9 +224,6 @@ class HamsterApplet(object):
         # DBus Setup
         try:
             dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
-            name = dbus.service.BusName(HAMSTER_URI, dbus.SessionBus())
-            self.dbusController = HamsterDbusController(bus_name = name)
-
             # Set up connection to the screensaver
             self.dbusIdleListener = idle.DbusIdleListener(runtime.dispatcher)
             runtime.dispatcher.add_handler('active_changed', self.on_idle_changed)
@@ -251,8 +247,6 @@ class HamsterApplet(object):
         self.load_day()
         self.update_label()
 
-        # Hamster DBusController current fact initialising
-        self.__update_fact()
 
         # refresh hamster every 60 seconds to update duration
         gobject.timeout_add_seconds(60, self.refresh_hamster)
@@ -447,17 +441,6 @@ class HamsterApplet(object):
         fact = self.treeview.get_selected_fact()
         runtime.storage.remove_fact(fact["id"])
 
-    def __update_fact(self):
-        """dbus controller current fact updating"""
-        last_activity_id = 0
-
-        if not self.last_activity:
-            self.dbusController.TrackingStopped()
-        else:
-            last_activity_id = self.last_activity['id']
-
-        self.dbusController.FactUpdated(last_activity_id)
-
     def __show_toggle(self, event, is_active):
         """main window display and positioning"""
         self.button.set_active(is_active)
@@ -584,7 +567,6 @@ class HamsterApplet(object):
     def after_fact_update(self, event, date):
         self.load_day()
         self.update_label()
-        self.__update_fact()
 
     def on_idle_changed(self, event, state):
         # state values: 0 = active, 1 = idle
diff --git a/src/hamster/client.py b/src/hamster/client.py
new file mode 100644
index 0000000..e420b7f
--- /dev/null
+++ b/src/hamster/client.py
@@ -0,0 +1,193 @@
+# - coding: utf-8 -
+
+# Copyright (C) 2007 Patryk Zawadzki <patrys at pld-linux.org>
+# Copyright (C) 2007-2009 Toms Baugis <toms baugis gmail com>
+
+# This file is part of Project Hamster.
+
+# Project Hamster 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 3 of the License, or
+# (at your option) any later version.
+
+# Project Hamster 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 Project Hamster.  If not, see <http://www.gnu.org/licenses/>.
+
+
+import datetime as dt
+from calendar import timegm
+import dbus, dbus.mainloop.glib
+
+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):
+    fact['start_time'] = dt.datetime.utcfromtimestamp(fact['start_time'])
+    if fact['end_time']:
+        fact['end_time'] = dt.datetime.utcfromtimestamp(fact['end_time'])
+    else:
+        fact['end_time'] = None
+
+    if 'date' in fact:
+        fact['date'] = dt.datetime.utcfromtimestamp(fact['date']).date()
+
+    if 'delta' in fact:
+        days, seconds = divmod(fact['delta'], 24 * 60 * 60)
+        fact['delta'] = dt.timedelta(days = days, seconds = seconds)
+
+    return fact
+
+class Storage(object):
+    def __init__(self, parent):
+        self.parent = parent
+
+        dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
+        bus = dbus.SessionBus()
+        hamster_conn = dbus.Interface(bus.get_object('org.gnome.Hamster',
+                                                     '/org/gnome/Hamster'),
+                                      dbus_interface='org.gnome.Hamster')
+        self.conn = hamster_conn
+
+        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):
+        self.parent.dispatch('new_tags_added')
+
+    def on_facts_changed(self):
+        self.parent.dispatch('day_updated')
+
+    def on_activities_changed(self):
+        self.parent.dispatch('activity_updated')
+
+
+    def get_todays_facts(self):
+        return [from_dbus_fact(fact) for fact in self.conn.GetTodaysFacts()]
+
+    def get_facts(self, date, end_date = 0, search_terms = ""):
+        date = timegm(date.timetuple())
+        if end_date:
+            end_date = timegm(end_date.timetuple())
+
+        return [from_dbus_fact(fact) for fact in self.conn.GetFacts(date,
+                                                                    end_date,
+                                                                    search_terms)]
+
+
+    def get_autocomplete_activities(self, search = ""):
+        return self.conn.GetAutocompleteActivities(search)
+
+    def get_category_list(self):
+        return self.conn.GetCategories()
+
+    def get_tags(self, autocomplete = None):
+        return self.conn.GetTags(True)
+
+
+    def get_tag_ids(self, tags):
+        return self.conn.GetTagIds(tags)
+
+    def update_autocomplete_tags(self, tags):
+        self.conn.SetTagsAutocomplete(tags)
+
+    def get_fact(self, id):
+        return from_dbus_fact(self.conn.GetFact(id))
+
+    def add_fact(self, activity_name, tags, start_time = None, end_time = 0,
+                                      category_name = '', description = ''):
+
+        if start_time:
+            start_time = timegm(start_time).timetuple()
+        else:
+            start_time = 0
+
+        if end_time:
+            end_time = timegm(end_time.timetuple())
+        else:
+            end_time = 0
+
+        return self.conn.AddFact(activity_name, tags, start_time, end_time, category_name, description)
+
+    def touch_fact(self, fact, end_time = None):
+        # TODO - rename and remove all the attributes
+        return self.conn.StopTracking()
+
+    def remove_fact(self, fact_id):
+        self.conn.RemoveFact(fact_id)
+
+    def update_fact(self, fact_id, activity_name, tags, start_time = 0, end_time = 0, category_name = '', description = ''):
+        category_name = category_name or '' # so to override None
+        description = description or '' # so to override None
+
+        if start_time:
+            start_time = timegm(start_time.timetuple())
+        if end_time:
+            end_time = timegm(end_time.timetuple())
+        else:
+            end_time = 0
+
+        return self.conn.UpdateFact(fact_id, activity_name, tags, start_time, end_time, category_name, description)
+
+
+    def get_activities(self, category_id = None):
+        return self.conn.GetActivities(category_id)
+
+
+    def get_last_activity(self):
+        return self.conn.GetLastActivity()
+
+    def remove_activity(self, id):
+        self.conn.RemoveActivity(id)
+
+    def remove_category(self, id):
+        self.conn.RemoveCategory(id)
+
+    def move_activity(self, source_id, target_order, insert_after = True):
+        self.conn.MoveActivity(source_id, target_order, insert_after)
+
+    def change_category(self, id, category_id):
+        self.conn.ChangeCategory(id, category_id)
+
+    def swap_activities(self, id1, priority1, id2, priority2):
+        self.conn.SwapActivities(id1, priority1, id2, priority2)
+
+    def update_activity(self, id, name, category_id):
+        return self.conn.UpdateActivity(id, name, category_id)
+
+    def add_activity(self, name, category_id = -1):
+        return self.conn.AddActivity(name, category_id)
+
+    def update_category(self, id, name):
+        return self.conn.UpdateCategory(id, name)
+
+    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):
+        return self.conn.GetActivityByName(activity, category_id, ressurect)
diff --git a/src/hamster/configuration.py b/src/hamster/configuration.py
index 864fc8f..278cb69 100644
--- a/src/hamster/configuration.py
+++ b/src/hamster/configuration.py
@@ -26,7 +26,7 @@ import gconf
 import gettext
 import os
 import defs
-from db import Storage
+from client import Storage
 from dispatcher import Dispatcher
 from xdg.BaseDirectory import xdg_data_home
 import logging
@@ -96,7 +96,7 @@ class RuntimeStore(Singleton):
                     return
 
                 logging.info("DB file has been modified externally. Calling all stations")
-                self.storage.dispatch_overwrite()
+                #self.storage.dispatch_overwrite()
 
 
 
diff --git a/src/hamster/db.py b/src/hamster/db.py
index fb01c47..ce73ffa 100644
--- a/src/hamster/db.py
+++ b/src/hamster/db.py
@@ -334,7 +334,8 @@ class Storage(storage.Storage):
             last_activity = facts[-1]
         return last_activity
 
-    def __touch_fact(self, fact, end_time):
+    def __touch_fact(self, fact):
+        end_time = dt.datetime.now()
         # tasks under one minute do not count
         if end_time - fact['start_time'] < datetime.timedelta(minutes = 1):
             self.__remove_fact(fact['id'])
@@ -470,7 +471,7 @@ class Storage(storage.Storage):
         tags = [tag.strip() for tag in tags.split(",") if tag.strip()]  # split by comma
         tags = tags or activity.tags # explicitly stated tags take priority
 
-        tags = self.get_tag_ids(tags) #this will create any missing tags too
+        tags = self.GetTagIds(tags) #this will create any missing tags too
 
 
         if category_name:
@@ -480,14 +481,28 @@ class Storage(storage.Storage):
 
         start_time = activity.start_time or start_time or datetime.datetime.now()
 
-        if start_time > datetime.datetime.now():
-            return None #no facts in future, please
-
         start_time = start_time.replace(microsecond = 0)
         end_time = activity.end_time or end_time
         if end_time:
             end_time = end_time.replace(microsecond = 0)
 
+        now = datetime.datetime.now()
+        # if in future - roll back to past
+        if start_time > datetime.datetime.now():
+            start_time = dt.datetime.combine(now.date(),  start_time.time())
+            if start_time > now:
+                start_time -= dt.timedelta(days = 1)
+
+        if end_time and end_time > now:
+            end_time = dt.datetime.combine(now.date(),  end_time.time())
+            if end_time > now:
+                end_time -= dt.timedelta(days = 1)
+
+
+
+
+        print activity, start_time, end_time
+
         if not start_time or not activity.activity_name:  # sanity check
             return
 
@@ -710,18 +725,6 @@ class Storage(storage.Storage):
 
         return res
 
-    def __get_popular_categories(self):
-        """returns categories used in the specified interval"""
-        query = """
-                   SELECT coalesce(c.name, ?) as category, count(a.id) as popularity
-                     FROM facts a
-                LEFT JOIN activities b on a.activity_id = b.id
-                LEFT JOIN categories c on c.id = b.category_id
-                 GROUP BY b.category_id
-                 ORDER BY popularity desc
-        """
-        return self.fetchall(query, (_("Unsorted"), ))
-
     def __remove_fact(self, fact_id):
         statements = ["DELETE FROM fact_tags where fact_id = ?",
                       "DELETE FROM facts where id = ?"]
@@ -732,7 +735,7 @@ class Storage(storage.Storage):
            otherwise - by activity_order"""
         if category_id:
             query = """
-                       SELECT a.*, b.name as category
+                       SELECT a.id, a.name, a.activity_order, a.category_id, b.name as category
                          FROM activities a
                     LEFT JOIN categories b on coalesce(b.id, -1) = a.category_id
                         WHERE category_id = ?
diff --git a/src/hamster/overview_totals.py b/src/hamster/overview_totals.py
index 9a0e98c..6bcb6a4 100644
--- a/src/hamster/overview_totals.py
+++ b/src/hamster/overview_totals.py
@@ -191,9 +191,10 @@ class TotalsBox(gtk.VBox):
         # tag totals
         tag_sums = {}
         for fact in facts:
-            for tag in fact["tags"]:
-                tag_sums.setdefault(tag, 0)
-                tag_sums[tag] += fact["delta"].seconds + fact["delta"].days * 24 * 60 * 60
+            if fact["tags"]:
+                for tag in fact["tags"]:
+                    tag_sums.setdefault(tag, 0)
+                    tag_sums[tag] += fact["delta"].seconds + fact["delta"].days * 24 * 60 * 60
 
         for entry in tag_sums:
             tag_sums[entry] = tag_sums[entry] / 60 / 60.0
diff --git a/src/hamster/storage.py b/src/hamster/storage.py
index 0c6ceb8..9ab4f37 100644
--- a/src/hamster/storage.py
+++ b/src/hamster/storage.py
@@ -18,142 +18,347 @@
 # You should have received a copy of the GNU General Public License
 # along with Project Hamster.  If not, see <http://www.gnu.org/licenses/>.
 
+import dbus, dbus.service
+import datetime as dt
+from calendar import timegm
 
-import datetime
+def to_dbus_fact(fact):
+    """Perform the conversion between fact database query and
+    dbus supported data types
+    """
+    if not fact:
+        return dbus.Dictionary({}, signature='sv')
+
+    fact = dict(fact)
+    for key in fact.keys():
+        fact[key] = fact[key] or 0
+
+        # make sure we return correct type where strings are expected
+        if not fact[key] and key in ('name', 'category', 'description'):
+            fact[key] = ''
+
+        # convert times to gmtime
+        if isinstance(fact[key], dt.datetime) or isinstance(fact[key], dt.date):
+            fact[key] = timegm(fact[key].timetuple())
+        elif isinstance(fact[key], dt.timedelta) :
+            fact[key] = fact[key].days * 24 * 60 * 60 + fact[key].seconds
+    return fact
+
+
+class Storage(dbus.service.Object):
+    __dbus_object_path__ = "/org/gnome/Hamster"
+
+    def __init__(self, loop):
+        self.bus = dbus.SessionBus()
+        bus_name = dbus.service.BusName("org.gnome.Hamster", bus=self.bus)
+        dbus.service.Object.__init__(self, bus_name, self.__dbus_object_path__)
+        self.mainloop = loop
 
-class Storage(object):
-    def __init__(self, parent):
-        self.parent = parent
 
     def run_fixtures(self):
         pass
 
-    def dispatch(self, event, data = None):
-        self.parent.dispatch(event, data)
+    @dbus.service.signal("org.gnome.Hamster")
+    def TagsChanged(self): pass
+
+    @dbus.service.signal("org.gnome.Hamster")
+    def FactsChanged(self): pass
+
+    @dbus.service.signal("org.gnome.Hamster")
+    def ActivitiesChanged(self): pass
 
     def dispatch_overwrite(self):
-        self.dispatch('new_tags_added')
-        self.dispatch('day_updated')
-        self.dispatch('activity_updated')
+        self.TagsChanged()
+        self.FactsChanged()
+        self.ActivitiesChanged()
 
-    def get_tags(self, autocomplete = None):
-        return self.__get_tags(autocomplete)
 
-    def get_tag_ids(self, tags):
-        tags, new_added = self.__get_tag_ids(tags)
-        if new_added:
-            self.dispatch('new_tags_added')
-        return tags
 
-    def update_autocomplete_tags(self, tags):
-        changes = self.__update_autocomplete_tags(tags)
-        if changes:
-            self.dispatch('new_tags_added')
+    @dbus.service.method("org.gnome.Hamster")
+    def Quit(self):
+        """
+        Shutdown the service
+        example:
+            import dbus
+            obj = dbus.SessionBus().get_object("org.gnome.Hamster", "/org/gnome/Hamster")
+            service = dbus.Interface(obj, "org.gnome.Hamster")
+            service.Quit()
+        """
+        #log.logger.info("Hamster Service is being shutdown")
+        self.mainloop.quit()
+
 
-    def get_fact(self, id):
-        return self.__get_fact(id)
+    # facts
 
-    def add_fact(self, activity_name, tags, start_time = None, end_time = None,
+    @dbus.service.method("org.gnome.Hamster", in_signature='ssiiss', out_signature='i')
+    def AddFact(self, activity_name, tags, start_time, end_time,
                                       category_name = None, description = None):
 
+        if start_time:
+            start_time = dt.datetime.utcfromtimestamp(start_time)
+        else:
+            start_time = None
+
+        if end_time:
+            end_time = dt.datetime.utcfromtimestamp(end_time)
+        else:
+            end_time = None
+
+        print activity_name, tags, start_time, end_time, category_name, description
         self.start_transaction()
         result = self.__add_fact(activity_name, tags, start_time, end_time, category_name, description)
         self.end_transaction()
 
         if result:
-            self.dispatch('day_updated')
+            print "emiting factschanged"
+            self.FactsChanged()
         return result
 
-    def touch_fact(self, fact, end_time = None):
-        end_time = end_time or datetime.datetime.now()
-        result = self.__touch_fact(fact, end_time)
-        self.dispatch('day_updated', fact['start_time'])
+
+    @dbus.service.method("org.gnome.Hamster", in_signature='i', out_signature='a{sv}')
+    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
+        """
+        return to_dbus_fact(self.__get_fact(fact_id))
+
+
+    @dbus.service.method("org.gnome.Hamster", in_signature='issiiss', out_signature='i')
+    def UpdateFact(self, fact_id, activity_name, tags, start_time, end_time, category_name = None, description = None):
+        if start_time:
+            start_time = dt.datetime.utcfromtimestamp(start_time)
+        else:
+            start_time = None
+
+        if end_time:
+            end_time = dt.datetime.utcfromtimestamp(end_time)
+        else:
+            end_time = None
+
+
+        self.start_transaction()
+        self.__remove_fact(fact_id)
+        result = self.__add_fact(activity_name, tags, start_time, end_time, category_name, description)
+        self.end_transaction()
+
+        if result:
+            self.FactsChanged()
         return result
 
-    def get_facts(self, date, end_date = None, search_terms = ""):
-        return self.__get_facts(date, end_date, search_terms)
 
-    def get_todays_facts(self):
-        return self.__get_todays_facts()
+    @dbus.service.method("org.gnome.Hamster")
+    def StopTracking(self):
+        """Stops the current fact tracking"""
+        fact = self.__get_last_activity()
+        if fact:
+            self.__touch_fact(fact)
+            self.FactsChanged()
 
-    def get_popular_categories(self):
-        return self.__get_popular_categories()
 
-    def remove_fact(self, fact_id):
+    @dbus.service.method("org.gnome.Hamster", in_signature='i')
+    def RemoveFact(self, fact_id):
         self.start_transaction()
-        fact = self.get_fact(fact_id)
+        fact = self.__get_fact(fact_id)
         if fact:
             self.__remove_fact(fact_id)
-            self.dispatch('day_updated', fact['start_time'])
+            self.FactsChanged()
         self.end_transaction()
 
-    def update_fact(self, fact_id, activity_name, tags, start_time, end_time, description = None):
-        self.start_transaction()
-        self.__remove_fact(fact_id)
-        result = self.__add_fact(activity_name, tags, start_time, end_time, description = description)
-        self.end_transaction()
 
-        if result:
-            self.dispatch('day_updated')
-        return result
+    @dbus.service.method("org.gnome.Hamster", in_signature='uus', out_signature='aa{sv}')
+    def GetFacts(self, start_date, end_date, search_terms):
+        """Gets facts between the day of start_date and the day of end_date.
+        Parameters:
+        u start_date: Seconds since epoch (timestamp). Use 0 for today
+        u end_date: Seconds since epoch (timestamp). Use 0 for today
+        s search_terms: Bleh
+        Returns Array of fact where fact it's 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
+        """
+        #TODO: Assert start > end ?
+        if start_date:
+            start = dt.datetime.utcfromtimestamp(start_date).date()
+        else:
+            start = dt.date.today()
+
+        if end_date:
+            end = dt.datetime.utcfromtimestamp(end_date).date()
+        else:
+            end = dt.date.today()
+
+        facts = self.__get_facts(start, end, search_terms)
+        return [to_dbus_fact(fact) for fact in facts]
+
+
+    @dbus.service.method("org.gnome.Hamster", out_signature='aa{sv}')
+    def GetTodaysFacts(self):
+        """Gets facts of today, respecting hamster midnight.
+        Returns Array of fact where fact it's 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
+        """
+
+        facts = dbus.Array([], signature='a{sv}')
+
+        for fact in self.__get_todays_facts():
+            facts.append(to_dbus_fact(fact))
+
+        return facts
+
+
+    # categories
+
+    @dbus.service.method("org.gnome.Hamster", in_signature='s', out_signature = 'i')
+    def AddCategory(self, name):
+        res = self.__add_category(name)
+        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='is')
+    def UpdateCategory(self, id, name):
+        self.__update_category(id, name)
+        self.ActivitiesChanged()
+
+
+    @dbus.service.method("org.gnome.Hamster", in_signature='i')
+    def RemoveCategory(self, id):
+        self.__remove_category(id)
+        self.ActivitiesChanged()
+
+
+    @dbus.service.method("org.gnome.Hamster", out_signature='aa{sv}')
+    def GetCategories(self):
+        res = []
+        for category in self.__get_category_list():
+            category = dict(category)
+            category['color_code'] = category['color_code'] or ''
+            res.append(category)
+
+
+        return res
 
-    def get_activities(self, category_id = None):
-        return self.__get_activities(category_id = category_id)
 
-    def get_autocomplete_activities(self, search = ""):
-        return self.__get_autocomplete_activities(search)
+    # activities
 
-    def get_last_activity(self):
-        return self.__get_last_activity()
+    @dbus.service.method("org.gnome.Hamster", in_signature='si', out_signature = 'i')
+    def AddActivity(self, name, category_id = -1):
+        new_id = self.__add_activity(name, category_id)
+        self.ActivitiesChanged()
+        return new_id
 
-    def remove_activity(self, id):
+
+    @dbus.service.method("org.gnome.Hamster", in_signature='isi')
+    def UpdateActivity(self, id, name, category_id):
+        self.__update_activity(id, name, category_id)
+        self.ActivitiesChanged()
+
+
+
+    @dbus.service.method("org.gnome.Hamster", in_signature='i')
+    def RemoveActivity(self, id):
         result = self.__remove_activity(id)
-        self.dispatch('activity_updated')
+        self.ActivitiesChanged()
         return result
 
-    def remove_category(self, id):
-        self.__remove_category(id)
-        self.dispatch('activity_updated')
+    @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):
+        print self.__get_activities(category_id = category_id)
+        return [dict(activity) for activity in self.__get_activities(category_id = category_id)]
+
+
+
+    @dbus.service.method("org.gnome.Hamster", in_signature='s', out_signature='aa{sv}')
+    def GetAutocompleteActivities(self, search = ""):
+        res = []
+        for activity in self.__get_autocomplete_activities(search):
+            activity = dict(activity)
+            activity['category'] = activity['category'] or ''
 
-    def move_activity(self, source_id, target_order, insert_after = True):
+        return res
+
+
+
+    @dbus.service.method("org.gnome.Hamster", in_signature='iib')
+    def MoveActivity(self, source_id, target_order, insert_after = True):
         self.__move_activity(source_id, target_order, insert_after)
-        self.dispatch('activity_updated')
+        self.ActivitiesChanged()
+
 
-    def change_category(self, id, category_id):
+    @dbus.service.method("org.gnome.Hamster", in_signature='ii')
+    def ChangeCategory(self, id, category_id):
         changed = self.__change_category(id, category_id)
         if changed:
-            self.dispatch('activity_updated')
-        return changed
+            self.ActivitiesChanged()
 
-    def swap_activities(self, id1, priority1, id2, priority2):
-        res = self.__swap_activities(id1, priority1, id2, priority2)
-        self.dispatch('activity_updated')
-        return res
 
-    def update_activity(self, id, name, category_id):
-        self.__update_activity(id, name, category_id)
-        self.dispatch('activity_updated')
+    @dbus.service.method("org.gnome.Hamster", in_signature='iiii')
+    def SwapActivities(self, id1, priority1, id2, priority2):
+        self.__swap_activities(id1, priority1, id2, priority2)
+        self.ActivitiesChanged()
 
-    def add_activity(self, name, category_id = -1):
-        new_id = self.__add_activity(name, category_id)
-        self.dispatch('activity_updated')
-        return new_id
 
-    def update_category(self, id, name):
-        self.__update_category(id, name)
-        self.dispatch('activity_updated')
+    @dbus.service.method("org.gnome.Hamster", in_signature='sib', out_signature='a{sv}')
+    def GetActivityByName(self, activity, category_id = None, ressurect = True):
+        return dict(self.__get_activity_by_name(activity, category_id, ressurect))
 
-    def add_category(self, name):
-        res = self.__add_category(name)
-        self.dispatch('activity_updated')
-        return res
 
 
-    def get_category_list(self):
-        return self.__get_category_list()
+    # 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)]
 
-    def get_category_by_name(self, category):
-        return self.__get_category_by_name(category)
 
-    def get_activity_by_name(self, activity, category_id = None, ressurect = True):
-        return self.__get_activity_by_name(activity, category_id, ressurect)
+    @dbus.service.method("org.gnome.Hamster", in_signature='as', out_signature='aa{sv}')
+    def GetTagIds(self, tags):
+        tags, new_added = self.__get_tag_ids(tags)
+        if new_added:
+            self.TagsChanged()
+        return [dict(tag) for tag in tags]
+
+
+    @dbus.service.method("org.gnome.Hamster", in_signature='as')
+    def SetTagsAutocomplete(self, tags):
+        changes = self.__update_autocomplete_tags(tags)
+        if changes:
+            self.TagsChanged()



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]