gtg team mailing list archive
-
gtg team
-
Mailing list archive
-
Message #03039
[Merge] lp:~gtg-user/gtg/tomboy-backend into lp:gtg
Luca Invernizzi has proposed merging lp:~gtg-user/gtg/tomboy-backend into lp:gtg with lp:~gtg-user/gtg/multibackends-halfgsoc_merge as a prerequisite.
Requested reviews:
Lionel Dricot (ploum)
Gtg developers (gtg)
The tomboy backend. Any tomboy note matching a particular tag will be inserted (r/w) in GTG.
It handles gracefully the change of the "attached tags", that are the tags to which the backend is looking for. The r/w synchronization engine is included.
It's complemented with a series of testcases and exception handling (when tomboy is put under stress - ~500 notes on my laptop - it begins to drop connections on dbus).
This is just to show the code for a review. I'll need to review the docstrings and use this backend for a couple of weeks before considering it stable - it works pretty well so far-.
--
https://code.launchpad.net/~gtg-user/gtg/tomboy-backend/+merge/32646
Your team Gtg developers is requested to review the proposed merge of lp:~gtg-user/gtg/tomboy-backend into lp:gtg.
=== modified file 'CHANGELOG'
--- CHANGELOG 2010-08-04 00:30:22 +0000
+++ CHANGELOG 2010-08-13 23:45:17 +0000
@@ -4,6 +4,7 @@
* Fixed bug with data consistency #579189, by Marko Kevac
* Added samba bugzilla to the bugzilla plugin, by Jelmer Vernoij
* Fixed bug #532392, a start date is later than a due date, by Volodymyr Floreskul
+ * New Tomboy/Gnote backend, by Luca Invernizzi
2010-03-01 Getting Things GNOME! 0.2.2
* Autostart on login, by Luca Invernizzi
=== added file 'GTG/backends/backend_gnote.py'
--- GTG/backends/backend_gnote.py 1970-01-01 00:00:00 +0000
+++ GTG/backends/backend_gnote.py 2010-08-13 23:45:17 +0000
@@ -0,0 +1,61 @@
+# -*- coding: utf-8 -*-
+# -----------------------------------------------------------------------------
+# Gettings Things Gnome! - a personal organizer for the GNOME desktop
+# Copyright (c) 2008-2009 - Lionel Dricot & Bertrand Rousseau
+#
+# 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 3 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, see <http://www.gnu.org/licenses/>.
+# -----------------------------------------------------------------------------
+
+'''
+The gnote backend. The actual backend is all in GenericTomboy, since it's
+shared with the tomboy backend.
+'''
+#To introspect tomboy: qdbus org.gnome.Tomboy /org/gnome/Tomboy/RemoteControl
+
+from GTG.backends.genericbackend import GenericBackend
+from GTG import _
+from GTG.backends.generictomboy import GenericTomboy
+
+
+
+class Backend(GenericTomboy):
+ '''
+ A simple class that adds some description to the GenericTomboy class.
+ It's done this way since Tomboy and Gnote backends have different
+ descriptions and Dbus addresses but the same backend behind them.
+ '''
+
+
+ _general_description = { \
+ GenericBackend.BACKEND_NAME: "backend_gnote", \
+ GenericBackend.BACKEND_HUMAN_NAME: _("Gnote"), \
+ GenericBackend.BACKEND_AUTHORS: ["Luca Invernizzi"], \
+ GenericBackend.BACKEND_TYPE: GenericBackend.TYPE_READWRITE, \
+ GenericBackend.BACKEND_DESCRIPTION: \
+ _("This backend can synchronize all or part of your Gnote"
+ " notes in GTG. If you decide it would be handy to"
+ " have one of your notes in your TODO list, just tag it "
+ "with the tag you have chosen (you'll configure it later"
+ "), and it will appear in GTG."),\
+ }
+
+ _static_parameters = { \
+ GenericBackend.KEY_ATTACHED_TAGS: {\
+ GenericBackend.PARAM_TYPE: GenericBackend.TYPE_LIST_OF_STRINGS, \
+ GenericBackend.PARAM_DEFAULT_VALUE: ["@GTG-Gnote"]}, \
+ }
+
+ _BUS_ADDRESS = ("org.gnome.Gnote",
+ "/org/gnome/Gnote/RemoteControl",
+ "org.gnome.Gnote.RemoteControl")
=== added file 'GTG/backends/backend_tomboy.py'
--- GTG/backends/backend_tomboy.py 1970-01-01 00:00:00 +0000
+++ GTG/backends/backend_tomboy.py 2010-08-13 23:45:17 +0000
@@ -0,0 +1,60 @@
+# -*- coding: utf-8 -*-
+# -----------------------------------------------------------------------------
+# Getting Things Gnome! - a personal organizer for the GNOME desktop
+# Copyright (c) 2008-2009 - Lionel Dricot & Bertrand Rousseau
+#
+# 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 3 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, see <http://www.gnu.org/licenses/>.
+# -----------------------------------------------------------------------------
+
+'''
+The tomboy backend. The actual backend is all in GenericTomboy, since it's
+shared with the Gnote backend.
+'''
+
+from GTG.backends.genericbackend import GenericBackend
+from GTG import _
+from GTG.backends.generictomboy import GenericTomboy
+
+
+
+class Backend(GenericTomboy):
+ '''
+ A simple class that adds some description to the GenericTomboy class.
+ It's done this way since Tomboy and Gnote backends have different
+ descriptions and Dbus addresses but the same backend behind them.
+ '''
+
+
+ _general_description = { \
+ GenericBackend.BACKEND_NAME: "backend_tomboy", \
+ GenericBackend.BACKEND_HUMAN_NAME: _("Tomboy"), \
+ GenericBackend.BACKEND_AUTHORS: ["Luca Invernizzi"], \
+ GenericBackend.BACKEND_TYPE: GenericBackend.TYPE_READWRITE, \
+ GenericBackend.BACKEND_DESCRIPTION: \
+ _("This backend can synchronize all or part of your Tomboy"
+ " notes in GTG. If you decide it would be handy to"
+ " have one of your notes in your TODO list, just tag it "
+ "with the tag you have chosen (you'll configure it later"
+ "), and it will appear in GTG."),\
+ }
+
+ _static_parameters = { \
+ GenericBackend.KEY_ATTACHED_TAGS: {\
+ GenericBackend.PARAM_TYPE: GenericBackend.TYPE_LIST_OF_STRINGS, \
+ GenericBackend.PARAM_DEFAULT_VALUE: ["@GTG-Tomboy"]}, \
+ }
+
+ _BUS_ADDRESS = ("org.gnome.Tomboy",
+ "/org/gnome/Tomboy/RemoteControl",
+ "org.gnome.Tomboy.RemoteControl")
=== added file 'GTG/backends/generictomboy.py'
--- GTG/backends/generictomboy.py 1970-01-01 00:00:00 +0000
+++ GTG/backends/generictomboy.py 2010-08-13 23:45:17 +0000
@@ -0,0 +1,583 @@
+# -*- coding: utf-8 -*-
+# -----------------------------------------------------------------------------
+# Getting Things Gnome! - a personal organizer for the GNOME desktop
+# Copyright (c) 2008-2009 - Lionel Dricot & Bertrand Rousseau
+#
+# 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 3 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, see <http://www.gnu.org/licenses/>.
+# -----------------------------------------------------------------------------
+
+'''
+Contains the Backend class for both Tomboy and Gnote
+'''
+#Note: To introspect tomboy, execute:
+# qdbus org.gnome.Tomboy /org/gnome/Tomboy/RemoteControl
+
+import os
+import re
+import threading
+import uuid
+import dbus
+import datetime
+
+from GTG.tools.testingmode import TestingMode
+from GTG.tools.borg import Borg
+from GTG.backends.genericbackend import GenericBackend
+from GTG.backends.backendsignals import BackendSignals
+from GTG.backends.syncengine import SyncEngine, SyncMeme
+from GTG.tools.logger import Log
+from GTG.tools.watchdog import Watchdog
+from GTG.tools.interruptible import interruptible
+
+
+
+class GenericTomboy(GenericBackend):
+ '''Backend class for Tomboy/Gnote'''
+
+
+###############################################################################
+### Backend standard methods ##################################################
+###############################################################################
+
+ def __init__(self, parameters):
+ """
+ See GenericBackend for an explanation of this function.
+ """
+ super(GenericTomboy, self).__init__(parameters)
+ #loading the saved state of the synchronization, if any
+ self.data_path = os.path.join('backends/tomboy/', \
+ "sync_engine-" + self.get_id())
+ self.sync_engine = self._load_pickled_file(self.data_path, \
+ SyncEngine())
+ #if the backend is being tested, we connect to a different DBus
+ # interface to avoid clashing with a running instance of Tomboy
+ if TestingMode().get_testing_mode():
+ #just used for testing purposes
+ self.BUS_ADDRESS = \
+ self._parameters["use this fake connection instead"]
+ else:
+ self.BUS_ADDRESS = self._BUS_ADDRESS
+ #we let some time pass before considering a tomboy task for importing,
+ # as the user may still be editing it. Here, we store the Timer objects
+ # that will execute after some time after each tomboy signal.
+ #NOTE: I'm not sure if this is the case anymore (but it shouldn't hurt
+ # anyway). (invernizzi)
+ self._tomboy_setting_timers = {}
+
+ def initialize(self):
+ '''
+ See GenericBackend for an explanation of this function.
+ Connects to the session bus and sets the callbacks for bus signals
+ '''
+ super(GenericTomboy, self).initialize()
+ with self.DbusWatchdog(self):
+ bus = dbus.SessionBus()
+ bus.add_signal_receiver(self.on_note_saved,
+ dbus_interface = self.BUS_ADDRESS[2],
+ signal_name = "NoteSaved")
+ bus.add_signal_receiver(self.on_note_deleted,
+ dbus_interface = self.BUS_ADDRESS[2],
+ signal_name = "NoteDeleted")
+
+ @interruptible
+ def start_get_tasks(self):
+ '''
+ See GenericBackend for an explanation of this function.
+ Gets all the notes from Tomboy and sees if they must be added in GTG
+ (and, if so, it adds them).
+ '''
+ with self.TomboyConnection(self, *self.BUS_ADDRESS) as tomboy:
+ with self.DbusWatchdog(self):
+ tomboy_notes = [str(note_id) for note_id in \
+ tomboy.ListAllNotes()]
+ #adding the new ones
+ for note in tomboy_notes:
+ self.cancellation_point()
+ self._process_tomboy_note(note)
+ #checking if some notes have been deleted while GTG was not running
+ stored_notes_ids = self.sync_engine.get_all_remote()
+ for note in set(stored_notes_ids).difference(set(tomboy_notes)):
+ self.on_note_deleted(note, None)
+
+ def save_state(self):
+ '''Saves the state of the synchronization'''
+ self._store_pickled_file(self.data_path, self.sync_engine)
+
+ def quit(self, disable = False):
+ '''
+ See GenericBackend for an explanation of this function.
+ '''
+ def quit_thread():
+ while True:
+ try:
+ [key, timer] = \
+ self._tomboy_setting_timers.iteritems().next()
+ except StopIteration:
+ break
+ timer.cancel()
+ del self._tomboy_setting_timers[key]
+ threading.Thread(target = quit_thread).start()
+ super(GenericTomboy, self).quit(disable)
+
+###############################################################################
+### Something got removed #####################################################
+###############################################################################
+
+ @interruptible
+ def on_note_deleted(self, note, something):
+ '''
+ Callback, executed when a tomboy note is deleted.
+ Deletes the related GTG task.
+
+ @param note: the id of the Tomboy note
+ @param something: not used, here for signal callback compatibility
+ '''
+ note = str(note)
+ with self.datastore.get_backend_mutex():
+ self.cancellation_point()
+ try:
+ tid = self.sync_engine.get_local_id(note)
+ except KeyError:
+ return
+ if self.datastore.has_task(tid):
+ self.datastore.request_task_deletion(tid)
+ self.break_relationship(remote_id = note)
+
+ @interruptible
+ def remove_task(self, tid):
+ '''
+ See GenericBackend for an explanation of this function.
+ '''
+ with self.datastore.get_backend_mutex():
+ self.cancellation_point()
+ try:
+ note = self.sync_engine.get_remote_id(tid)
+ except KeyError:
+ return
+ with self.TomboyConnection(self, *self.BUS_ADDRESS) as tomboy:
+ with self.DbusWatchdog(self):
+ if tomboy.NoteExists(note):
+ tomboy.DeleteNote(note)
+ self.break_relationship(local_id = tid)
+
+ def _exec_lost_syncability(self, tid, note):
+ '''
+ Executed when a relationship between tasks loses its syncability
+ property. See SyncEngine for an explanation of that.
+ This function finds out which object (task/note) is the original one
+ and which is the copy, and deletes the copy.
+
+ @param tid: a GTG task tid
+ #param note: a tomboy note id
+ '''
+ self.cancellation_point()
+ meme = self.sync_engine.get_meme_from_remote_id(note)
+ #First of all, the relationship is lost
+ self.sync_engine.break_relationship(remote_id = note)
+ if meme.get_origin() == "GTG":
+ with self.TomboyConnection(self, *self.BUS_ADDRESS) as tomboy:
+ with self.DbusWatchdog(self):
+ tomboy.DeleteNote(note)
+ else:
+ self.datastore.request_task_deletion(tid)
+
+###############################################################################
+### Process tasks #############################################################
+###############################################################################
+
+ def _process_tomboy_note(self, note):
+ '''
+ Given a tomboy note, finds out if it must be synced to a GTG note and,
+ if so, it carries out the synchronization (by creating or updating a GTG
+ task, or deleting itself if the related task has been deleted)
+
+ @param note: a Tomboy note id
+ '''
+ with self.datastore.get_backend_mutex():
+ self.cancellation_point()
+ is_syncable = self._tomboy_note_is_syncable(note)
+ with self.DbusWatchdog(self):
+ action, tid = self.sync_engine.analyze_remote_id(note, \
+ self.datastore.has_task, \
+ self._tomboy_note_exists, is_syncable)
+ Log.debug("processing tomboy (%s, %s)" % (action, is_syncable))
+
+ if action == SyncEngine.ADD:
+ tid = str(uuid.uuid4())
+ task = self.datastore.task_factory(tid)
+ self._populate_task(task, note)
+ self.record_relationship(local_id = tid,\
+ remote_id = note, \
+ meme = SyncMeme(task.get_modified(),
+ self.get_modified_for_note(note),
+ self.get_id()))
+ self.datastore.push_task(task)
+
+ elif action == SyncEngine.UPDATE:
+ task = self.datastore.get_task(tid)
+ meme = self.sync_engine.get_meme_from_remote_id(note)
+ newest = meme.which_is_newest(task.get_modified(),
+ self.get_modified_for_note(note))
+ if newest == "remote":
+ self._populate_task(task, note)
+ meme.set_local_last_modified(task.get_modified())
+ meme.set_remote_last_modified(\
+ self.get_modified_for_note(note))
+ self.save_state()
+
+ elif action == SyncEngine.REMOVE:
+ with self.TomboyConnection(self, *self.BUS_ADDRESS) as tomboy:
+ with self.DbusWatchdog(self):
+ tomboy.DeleteNote(note)
+ try:
+ self.sync_engine.break_relationship(remote_id = note)
+ except KeyError:
+ pass
+
+ elif action == SyncEngine.LOST_SYNCABILITY:
+ self._exec_lost_syncability(tid, note)
+
+ @interruptible
+ def set_task(self, task):
+ '''
+ See GenericBackend for an explanation of this function.
+ '''
+ self.cancellation_point()
+ is_syncable = self._gtg_task_is_syncable_per_attached_tags(task)
+ tid = task.get_id()
+ with self.datastore.get_backend_mutex():
+ with self.TomboyConnection(self, *self.BUS_ADDRESS) as tomboy:
+ with self.DbusWatchdog(self):
+ action, note = self.sync_engine.analyze_local_id(tid, \
+ self.datastore.has_task, tomboy.NoteExists, \
+ is_syncable)
+ Log.debug("processing gtg (%s, %d)" % (action, is_syncable))
+
+ if action == SyncEngine.ADD:
+ #GTG allows multiple tasks with the same name,
+ #Tomboy doesn't. we need to handle the renaming
+ #manually
+ title = task.get_title()
+ duplicate_counter = 1
+ with self.DbusWatchdog(self):
+ note = str(tomboy.CreateNamedNote(title))
+ while note == "":
+ duplicate_counter += 1
+ note = tomboy.CreateNamedNote(title + "(%d)" %
+ duplicate_counter)
+ if duplicate_counter != 1:
+ #if we needed to rename, we have to rename also
+ # the gtg task
+ task.set_title(title + " (%d)" % duplicate_counter)
+
+ self._populate_note(note, task)
+ self.record_relationship( \
+ local_id = tid, remote_id = note, \
+ meme = SyncMeme(task.get_modified(),
+ self.get_modified_for_note(note),
+ "GTG"))
+
+ elif action == SyncEngine.UPDATE:
+ meme = self.sync_engine.get_meme_from_local_id(\
+ task.get_id())
+ newest = meme.which_is_newest(task.get_modified(),
+ self.get_modified_for_note(note))
+ if newest == "local":
+ self._populate_note(note, task)
+ meme.set_local_last_modified(task.get_modified())
+ meme.set_remote_last_modified(\
+ self.get_modified_for_note(note))
+ self.save_state()
+
+ elif action == SyncEngine.REMOVE:
+ self.datastore.request_task_deletion(tid)
+ try:
+ self.sync_engine.break_relationship(local_id = tid)
+ self.save_state()
+ except KeyError:
+ pass
+
+ elif action == SyncEngine.LOST_SYNCABILITY:
+ self._exec_lost_syncability(tid, note)
+
+###############################################################################
+### Helper methods ############################################################
+###############################################################################
+
+ @interruptible
+ def on_note_saved(self, note):
+ '''
+ Callback, executed when a tomboy note is saved by Tomboy itself.
+ Updates the related GTG task (or creates one, if necessary).
+
+ @param note: the id of the Tomboy note
+ '''
+ note = str(note)
+ self.cancellation_point()
+ #NOTE: we let some seconds pass before executing the real callback, as
+ # the editing of the Tomboy note may still be in progress
+ @interruptible
+ def _execute_on_note_saved(self, note):
+ self.cancellation_point()
+ try:
+ del self._tomboy_setting_timers[note]
+ except:
+ pass
+ self._process_tomboy_note(note)
+ self.save_state()
+
+ try:
+ self._tomboy_setting_timers[note].cancel()
+ except KeyError:
+ pass
+ finally:
+ timer =threading.Timer(5, _execute_on_note_saved,
+ args = (self, note))
+ self._tomboy_setting_timers[note] = timer
+ timer.start()
+
+ def _tomboy_note_is_syncable(self, note):
+ '''
+ Returns True if this tomboy note should be synced into GTG tasks.
+
+ @param note: the note id
+ @returns Boolean
+ '''
+ attached_tags = self.get_attached_tags()
+ if GenericBackend.ALLTASKS_TAG in attached_tags:
+ return True
+ with self.TomboyConnection(self, *self.BUS_ADDRESS) as tomboy:
+ with self.DbusWatchdog(self):
+ content = tomboy.GetNoteContents(note)
+ syncable = False
+ for tag in attached_tags:
+ try:
+ content.index(tag)
+ syncable = True
+ break
+ except ValueError:
+ pass
+ return syncable
+
+ def _tomboy_note_exists(self, note):
+ '''
+ Returns True if a tomboy note exists with the given id.
+
+ @param note: the note id
+ @returns Boolean
+ '''
+ with self.TomboyConnection(self, *self.BUS_ADDRESS) as tomboy:
+ with self.DbusWatchdog(self):
+ return tomboy.NoteExists(note)
+
+ def get_modified_for_note(self, note):
+ '''
+ Returns the modification time for the given note id.
+
+ @param note: the note id
+ @returns datetime.datetime
+ '''
+ with self.TomboyConnection(self, *self.BUS_ADDRESS) as tomboy:
+ with self.DbusWatchdog(self):
+ return datetime.datetime.fromtimestamp( \
+ tomboy.GetNoteChangeDate(note))
+
+ def _tomboy_split_title_and_text(self, content):
+ '''
+ Tomboy does not have a "getTitle" and "getText" functions to get the
+ title and the text of a note separately. Instead, it has a getContent
+ function, that returns both of them.
+ This function splits up the output of getContent into a title string and
+ a text string.
+
+ @param content: a string, the result of a getContent call
+ @returns list: a list composed by [title, text]
+ '''
+ try:
+ end_of_title = content.index('\n')
+ except ValueError:
+ return content, ""
+ title = content[: end_of_title]
+ if len(content) > end_of_title:
+ return title, content[end_of_title +1 :]
+ else:
+ return title, ""
+
+ def _populate_task(self, task, note):
+ '''
+ Copies the content of a Tomboy note into a task.
+
+ @param task: a GTG Task
+ @param note: a Tomboy note
+ '''
+ #add tags objects (it's not enough to have @tag in the text to add a
+ # tag
+ with self.TomboyConnection(self, *self.BUS_ADDRESS) as tomboy:
+ with self.DbusWatchdog(self):
+ content = tomboy.GetNoteContents(note)
+ #update the tags list
+ matches = re.finditer("(?<![^|\s])(@\w+)", content)
+ new_tags_list = [content[g.start() : g.end()] for g in matches]
+ for tag in task.get_tags_name():
+ try:
+ new_tags_list.remove(tag)
+ task.remove_tag(tag)
+ except:
+ task.add_tag(tag)
+ for tag in new_tags_list:
+ task.add_tag(tag)
+ #extract title and text
+ [title, text] = self._tomboy_split_title_and_text(content)
+ task.set_title(title)
+ task.set_text(text)
+ task.add_remote_id(self.get_id(), note)
+
+ def _populate_note(self, note, task):
+ '''
+ Copies the content of a task into a Tomboy note.
+
+ @param note: a Tomboy note
+ @param task: a GTG Task
+ '''
+ title = task.get_title()
+ tested_title = title
+ duplicate_counter = 1
+ with self.TomboyConnection(self, *self.BUS_ADDRESS) as tomboy:
+ with self.DbusWatchdog(self):
+ tomboy.SetNoteContents(note, title + '\n' + \
+ task.get_excerpt(strip_tags = False))
+
+ def break_relationship(self, *args, **kwargs):
+ '''
+ Proxy method for SyncEngine.break_relationship, which also saves the
+ state of the synchronization.
+ '''
+ try:
+ self.sync_engine.break_relationship(*args, **kwargs)
+ #we try to save the state at each change in the sync_engine:
+ #it's slower, but it should avoid widespread task
+ #duplication
+ self.save_state()
+ except KeyError:
+ pass
+
+ def record_relationship(self, *args, **kwargs):
+ '''
+ Proxy method for SyncEngine.break_relationship, which also saves the
+ state of the synchronization.
+ '''
+ self.sync_engine.record_relationship(*args, **kwargs)
+ #we try to save the state at each change in the sync_engine:
+ #it's slower, but it should avoid widespread task
+ #duplication
+ self.save_state()
+
+###############################################################################
+### Connection handling #######################################################
+###############################################################################
+
+
+
+ class TomboyConnection(Borg):
+ '''
+ TomboyConnection creates a connection to TOMBOY via DBUS and
+ handles all the possible exceptions.
+ It is a class that can be used with a with statement.
+ Example:
+ with self.TomboyConnection(self, *self.BUS_ADDRESS) as tomboy:
+ #do something
+ '''
+
+
+ def __init__(self, backend, bus_name, bus_path, bus_interface):
+ '''
+ Sees if a TomboyConnection object already exists. If so, since we
+ are inheriting from a Borg object, the initialization already took
+ place.
+ If not, it tries to connect to Tomboy via Dbus. If the connection
+ is not possible, the user is notified about it.
+
+ @param backend: a reference to a Backend
+ @param bus_name: the DBUS address of Tomboy
+ @param bus_path: the DBUS path of Tomboy RemoteControl
+ @param bus_interface: the DBUS address of Tomboy RemoteControl
+ '''
+ super(GenericTomboy.TomboyConnection, self).__init__()
+ if hasattr(self, "tomboy_connection_is_ok") and \
+ self.tomboy_connection_is_ok:
+ return
+ self.backend = backend
+ with GenericTomboy.DbusWatchdog(backend):
+ bus = dbus.SessionBus()
+ obj = bus.get_object(bus_name, bus_path)
+ self.tomboy = dbus.Interface(obj, bus_interface)
+ self.tomboy_connection_is_ok = True
+
+ def __enter__(self):
+ '''
+ Returns the Tomboy connection
+
+ @returns dbus.Interface
+ '''
+ return self.tomboy
+
+ def __exit__(self, exception_type, value, traceback):
+ '''
+ Checks the state of the connection.
+ If something went wrong for the connection, notifies the user.
+
+ @param exception_type: the type of exception that occurred, or
+ None
+ @param value: the instance of the exception occurred, or None
+ @param traceback: the traceback of the error
+ @returns: False if some exception must be re-raised.
+ '''
+ if isinstance(value, dbus.DBusException):
+ self.tomboy_connection_is_ok = False
+ self.backend.quit(disable = True)
+ BackendSignals().backend_failed(self.backend.get_id(), \
+ BackendSignals.ERRNO_DBUS)
+ else:
+ return False
+ return True
+
+
+
+ class DbusWatchdog(Watchdog):
+ '''
+ A simple watchdog to detect stale dbus connections
+ '''
+
+
+ def __init__(self, backend):
+ '''
+ Simple constructor, which sets _when_taking_too_long as the function
+ to run when the connection is taking too long.
+
+ @param backend: a Backend object
+ '''
+ self.backend = backend
+ super(GenericTomboy.DbusWatchdog, self).__init__(3, \
+ self._when_taking_too_long)
+
+ def _when_taking_too_long(self):
+ '''
+ Function that is executed when the Dbus connection seems to be
+ hanging. It disables the backend and signals the error to the user.
+ '''
+ Log.error("Dbus connection is taking too long for the Tomboy/Gnote"
+ "backend!")
+ self.backend.quit(disable = True)
+ BackendSignals().backend_failed(self.backend.get_id(), \
+ BackendSignals.ERRNO_DBUS)
+
=== modified file 'GTG/gtk/browser/browser.py'
--- GTG/gtk/browser/browser.py 2010-08-10 17:30:24 +0000
+++ GTG/gtk/browser/browser.py 2010-08-13 23:45:17 +0000
@@ -953,7 +953,9 @@
text = \
text.replace("%s%s:%s" % (spaces, attribute, args), "")
# Create the new task
- task = self.req.new_task(tags=[t.get_name() for t in tags], newtask=True)
+ task = self.req.new_task( newtask=True)
+ for tag in tags:
+ task.add_tag(tag.get_name())
if text != "":
task.set_title(text.strip())
task.set_to_keep()
=== added file 'data/icons/hicolor/scalable/apps/backend_gnote.png'
Binary files data/icons/hicolor/scalable/apps/backend_gnote.png 1970-01-01 00:00:00 +0000 and data/icons/hicolor/scalable/apps/backend_gnote.png 2010-08-13 23:45:17 +0000 differ
=== added file 'data/icons/hicolor/scalable/apps/backend_tomboy.png'
Binary files data/icons/hicolor/scalable/apps/backend_tomboy.png 1970-01-01 00:00:00 +0000 and data/icons/hicolor/scalable/apps/backend_tomboy.png 2010-08-13 23:45:17 +0000 differ
Follow ups