Merge lp:~zeitgeist/zeitgeist/remove-datahub into lp:zeitgeist/0.1

Proposed by Seif Lotfy
Status: Merged
Merged at revision: 1617
Proposed branch: lp:~zeitgeist/zeitgeist/remove-datahub
Merge into: lp:zeitgeist/0.1
Diff against target: 1349 lines (+25/-1166)
14 files modified
Makefile.am (+3/-13)
_zeitgeist/Makefile.am (+1/-1)
_zeitgeist/loggers/Makefile.am (+0/-9)
_zeitgeist/loggers/datasources/Makefile.am (+0/-6)
_zeitgeist/loggers/datasources/_recentmanager.py (+0/-146)
_zeitgeist/loggers/datasources/recent.py (+0/-371)
_zeitgeist/loggers/iso_strptime.py (+0/-86)
_zeitgeist/loggers/zeitgeist_base.py (+0/-91)
_zeitgeist/loggers/zeitgeist_setup_service.py (+0/-243)
configure.ac (+0/-2)
po/POTFILES.in (+0/-4)
test/loggers-datasources-recent-test.py (+0/-49)
zeitgeist-daemon.py (+21/-10)
zeitgeist-datahub.py (+0/-135)
To merge this branch: bzr merge lp:~zeitgeist/zeitgeist/remove-datahub
Reviewer Review Type Date Requested Status
Markus Korn Approve
Review via email: mp+38339@code.launchpad.net

Description of the change

initial fix for bzr bug #655164
removed zeitgeist-datahub.py and loggers

To post a comment you must log in.
Revision history for this message
Markus Korn (thekorn) wrote :

Hi Seif,
thanks for working on this. But why are you proposing branch to be merged into lp:zeitgeist although they are obviously not working. A simple "./zeitgeist-daemon" and a close look to zeitgeist-daemon.py will show you at least two *very* obvious errors.
I personally don't find it motivating being ask for a review of a branch which obviously has not been tested by the author </rant>

Revision history for this message
Seif Lotfy (seif) wrote :

Hey Markus,
Sorry about that. I ran the make check and forgot to run the proper dameon. Very sorry. The fix is up. Cheers
Seif

1618. By Seif Lotfy

fix stupid mistake in zeitgeist-daemon.py

1619. By Markus Korn

Some modification to how the datahub is invoked iwthin the daemon:
 - OSError means that there is no datahub found in $PATH
 - we tell the user which datahub binary is executed (this makes debugging
   in dev environments easier)
 - we are not translating debugging output

Revision history for this message
Markus Korn (thekorn) wrote :

Seif, that's looking much better ;)
I could not find you on irc, so I did a few changes to zeitgeist-daemon.py which came into my mind while reading the code, please contact me if you think they don't make sense...

Revision history for this message
Markus Korn (thekorn) wrote :

All tests are running fine, zeitgeist-daemon is looking good, and it seems to me that you managed to remove everything related to the old datahub, Good work.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'Makefile.am'
2--- Makefile.am 2010-09-03 10:16:51 +0000
3+++ Makefile.am 2010-10-14 09:46:58 +0000
4@@ -6,8 +6,7 @@
5 intltool-update.in
6
7 bin_SCRIPTS = \
8- zeitgeist-daemon \
9- zeitgeist-datahub
10+ zeitgeist-daemon
11
12 EXTRA_DIST = \
13 $(bin_SCRIPTS) \
14@@ -16,7 +15,6 @@
15 COPYRIGHT \
16 NEWS \
17 zeitgeist-daemon.py \
18- zeitgeist-datahub.py \
19 zeitgeist-daemon.pc.in
20
21 DISTCLEANFILES = \
22@@ -25,8 +23,7 @@
23 intltool-update
24
25 CLEANFILES = \
26- zeitgeist-daemon \
27- zeitgeist-datahub
28+ zeitgeist-daemon
29
30 pkgconfigdir = $(libdir)/pkgconfig
31 pkgconfig_DATA = zeitgeist-daemon.pc
32@@ -34,19 +31,12 @@
33 zeitgeist-daemon: zeitgeist-daemon.py
34 sed \
35 -e "s!\/usr\/bin\/env python!$(PYTHON)!" \
36- -e "s!zeitgeist-datahub\.py!zeitgeist-datahub!" \
37 < $< > $@
38 chmod +x zeitgeist-daemon
39 zeitgeist-daemon: Makefile
40
41-zeitgeist-datahub: zeitgeist-datahub.py
42- sed \
43- -e "s!\/usr\/bin\/env python!$(PYTHON)!" \
44- < $< > $@
45- chmod +x zeitgeist-datahub
46-zeitgeist-datahub: Makefile
47
48-all-local: zeitgeist-daemon zeitgeist-datahub
49+all-local: zeitgeist-daemon
50
51 # Generate ChangeLog
52 dist-hook:
53
54=== modified file '_zeitgeist/Makefile.am'
55--- _zeitgeist/Makefile.am 2010-06-22 19:53:09 +0000
56+++ _zeitgeist/Makefile.am 2010-10-14 09:46:58 +0000
57@@ -1,4 +1,4 @@
58-SUBDIRS = engine loggers
59+SUBDIRS = engine
60
61 appdir = $(datadir)/zeitgeist/_zeitgeist/
62
63
64=== removed directory '_zeitgeist/loggers'
65=== removed file '_zeitgeist/loggers/Makefile.am'
66--- _zeitgeist/loggers/Makefile.am 2010-06-17 20:43:34 +0000
67+++ _zeitgeist/loggers/Makefile.am 1970-01-01 00:00:00 +0000
68@@ -1,9 +0,0 @@
69-SUBDIRS = datasources
70-
71-appdir = $(datadir)/zeitgeist/_zeitgeist/loggers/
72-
73-app_PYTHON = \
74- __init__.py \
75- iso_strptime.py \
76- zeitgeist_setup_service.py \
77- zeitgeist_base.py
78
79=== removed file '_zeitgeist/loggers/__init__.py'
80=== removed directory '_zeitgeist/loggers/datasources'
81=== removed file '_zeitgeist/loggers/datasources/Makefile.am'
82--- _zeitgeist/loggers/datasources/Makefile.am 2010-06-17 20:43:34 +0000
83+++ _zeitgeist/loggers/datasources/Makefile.am 1970-01-01 00:00:00 +0000
84@@ -1,6 +0,0 @@
85-appdir = $(datadir)/zeitgeist/_zeitgeist/loggers/datasources
86-
87-app_PYTHON = \
88- __init__.py \
89- _recentmanager.py \
90- recent.py
91
92=== removed file '_zeitgeist/loggers/datasources/__init__.py'
93=== removed file '_zeitgeist/loggers/datasources/_recentmanager.py'
94--- _zeitgeist/loggers/datasources/_recentmanager.py 2010-01-16 18:21:11 +0000
95+++ _zeitgeist/loggers/datasources/_recentmanager.py 1970-01-01 00:00:00 +0000
96@@ -1,146 +0,0 @@
97-# -.- coding: utf-8 -.-
98-
99-# Zeitgeist
100-#
101-# Copyright © 2009 Markus Korn <thekorn@gmx.de>
102-# Copyright © 2009 Siegfried-Angel Gevatter Pujals <rainct@ubuntu.com>
103-#
104-# This program is free software: you can redistribute it and/or modify
105-# it under the terms of the GNU Lesser General Public License as published by
106-# the Free Software Foundation, either version 3 of the License, or
107-# (at your option) any later version.
108-#
109-# This program is distributed in the hope that it will be useful,
110-# but WITHOUT ANY WARRANTY; without even the implied warranty of
111-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
112-# GNU Lesser General Public License for more details.
113-#
114-# You should have received a copy of the GNU Lesser General Public License
115-# along with this program. If not, see <http://www.gnu.org/licenses/>.
116-
117-import urllib
118-import gobject
119-import gio
120-import os.path
121-import time
122-import logging
123-from xml.dom.minidom import parse as minidom_parse
124-
125-from _zeitgeist.loggers.iso_strptime import iso_strptime
126-
127-DST = bool(time.mktime(time.gmtime(0)))
128-log = logging.getLogger("zeitgeist.logger._recentmanager")
129-
130-class FileInfo(object):
131-
132- @staticmethod
133- def convert_timestring(time_str):
134- # My observation is that all times in self.RECENTFILE are in UTC (I might be wrong here)
135- # so we need to parse the time string into a timestamp
136- # and correct the result by the timezone difference
137- try:
138- timetuple = time.strptime(time_str, "%Y-%m-%dT%H:%M:%SZ")
139- except ValueError:
140- timetuple = iso_strptime(time_str.rstrip("Z")).timetuple()
141- result = int(time.mktime(timetuple))
142- if DST:
143- result -= time.altzone
144- return result
145-
146- def __init__(self, node):
147- self._uri = node.getAttribute("href")
148- self._path = "/%s" % self._uri.split("///", 1)[-1]
149- self._added = self.convert_timestring(node.getAttribute("added"))
150- self._modified = self.convert_timestring(node.getAttribute("modified"))
151- self._visited = self.convert_timestring(node.getAttribute("visited"))
152-
153- mimetype = node.getElementsByTagNameNS(
154- "http://www.freedesktop.org/standards/shared-mime-info",
155- "mime-type")
156- if not mimetype:
157- raise ValueError, "Could not find mimetype for item: %s" % self._uri
158- self._mimetype = mimetype[-1].getAttribute("type")
159-
160- applications = node.getElementsByTagNameNS(
161- "http://www.freedesktop.org/standards/desktop-bookmarks",
162- "applications")
163- assert applications
164- application = applications[0].getElementsByTagNameNS(
165- "http://www.freedesktop.org/standards/desktop-bookmarks",
166- "application")
167- if not application:
168- raise ValueError, "Could not find application for item: %s" % self._uri
169- self._application = application[-1].getAttribute("exec").strip("'")
170-
171- def get_mime_type(self):
172- return self._mimetype
173-
174- def get_visited(self):
175- return self._visited
176-
177- def get_added(self):
178- return self._added
179-
180- def get_modified(self):
181- return self._modified
182-
183- def get_uri_display(self):
184- return self._path
185-
186- def get_uri(self):
187- return self._uri
188-
189- def get_display_name(self):
190- return unicode(os.path.basename(urllib.unquote(str(self._path))))
191-
192- def exists(self):
193- if not self._uri.startswith("file:///"):
194- return True # Don't check online resources
195- return gio.File(self._path).get_path() is not None
196-
197- def get_private_hint(self):
198- return False # FIXME: How to get this?
199-
200- def last_application(self):
201- # Not necessary, our get_application_info always returns the info of
202- # the last application
203- return ""
204-
205- def get_application_info(self, app):
206- return (self._application, None, None)
207-
208-class RecentManager(gobject.GObject):
209-
210- RECENTFILE = os.path.expanduser("~/.recently-used.xbel")
211-
212- def __init__(self):
213- super(RecentManager, self).__init__()
214- if not os.path.exists(self.RECENTFILE):
215- raise OSError("Can't use alternative RecentManager, '%s' not found" % self.RECENTFILE)
216-
217- self._fetching_items = None
218- file_object = gio.File(self.RECENTFILE)
219- self.file_monitor = file_object.monitor_file()
220- self.file_monitor.set_rate_limit(1600) # for to high rates RecentManager
221- # gets hickup, not sure what's optimal here
222- self.file_monitor.connect("changed", self._content_changed)
223-
224- def _content_changed(self, monitor, fileobj, _, event):
225- # Only emit the signal if we aren't already parsing RECENTFILE
226- if not self._fetching_items:
227- self.emit("changed")
228-
229- def get_items(self):
230- self._fetching_items = True
231- xml = minidom_parse(self.RECENTFILE)
232- for bookmark in xml.getElementsByTagName("bookmark"):
233- yield FileInfo(bookmark)
234- self._fetching_items = False
235-
236- def set_limit(self, limit):
237- pass
238-
239-gobject.type_register(RecentManager)
240-
241-gobject.signal_new("changed", RecentManager,
242- gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ())
243
244=== removed file '_zeitgeist/loggers/datasources/recent.py'
245--- _zeitgeist/loggers/datasources/recent.py 2010-09-25 13:19:51 +0000
246+++ _zeitgeist/loggers/datasources/recent.py 1970-01-01 00:00:00 +0000
247@@ -1,371 +0,0 @@
248-# -.- coding: utf-8 -.-
249-
250-# Zeitgeist
251-#
252-# Copyright © 2009 Alex Graveley <alex.graveley@beatniksoftewarel.com>
253-# Copyright © 2009 Markus Korn <thekorn@gmx.de>
254-# Copyright © 2009 Natan Yellin <aantny@gmail.com>
255-# Copyright © 2009 Seif Lotfy <seif@lotfy.com>
256-# Copyright © 2009 Shane Fagan <shanepatrickfagan@yahoo.ie>
257-# Copyright © 2009-2010 Siegfried-Angel Gevatter Pujals <rainct@ubuntu.com>
258-#
259-# This program is free software: you can redistribute it and/or modify
260-# it under the terms of the GNU Lesser General Public License as published by
261-# the Free Software Foundation, either version 3 of the License, or
262-# (at your option) any later version.
263-#
264-# This program is distributed in the hope that it will be useful,
265-# but WITHOUT ANY WARRANTY; without even the implied warranty of
266-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
267-# GNU Lesser General Public License for more details.
268-#
269-# You should have received a copy of the GNU Lesser General Public License
270-# along with this program. If not, see <http://www.gnu.org/licenses/>.
271-
272-from __future__ import with_statement
273-import os
274-import re
275-import fnmatch
276-import urllib
277-import time
278-import logging
279-from xdg import BaseDirectory
280-
281-from zeitgeist import _config
282-from zeitgeist.datamodel import Event, Subject, Interpretation, Manifestation, \
283- DataSource, get_timestamp_for_now
284-from _zeitgeist.loggers.zeitgeist_base import DataProvider
285-
286-log = logging.getLogger("zeitgeist.logger.datasources.recent")
287-
288-try:
289- import gtk
290- if gtk.pygtk_version >= (2, 15, 2):
291- recent_manager = gtk.recent_manager_get_default
292- else:
293- from _recentmanager import RecentManager
294- recent_manager = RecentManager
295-except ImportError:
296- log.exception(_("Could not import GTK; data source disabled."))
297- enabled = False
298-else:
299- enabled = True
300-
301-class SimpleMatch(object):
302- """ Wrapper around fnmatch.fnmatch which allows to define mimetype
303- patterns by using shell-style wildcards.
304- """
305-
306- def __init__(self, pattern):
307- self.__pattern = pattern
308-
309- def match(self, text):
310- return fnmatch.fnmatch(text, self.__pattern)
311-
312- def __repr__(self):
313- return "%s(%r)" %(self.__class__.__name__, self.__pattern)
314-
315-DOCUMENT_MIMETYPES = [
316- # Covers:
317- # vnd.corel-draw
318- # vnd.ms-powerpoint
319- # vnd.ms-excel
320- # vnd.oasis.opendocument.*
321- # vnd.stardivision.*
322- # vnd.sun.xml.*
323- SimpleMatch(u"application/vnd.*"),
324- # Covers: x-applix-word, x-applix-spreadsheet, x-applix-presents
325- SimpleMatch(u"application/x-applix-*"),
326- # Covers: x-kword, x-kspread, x-kpresenter, x-killustrator
327- re.compile(u"application/x-k(word|spread|presenter|illustrator)"),
328- u"application/ms-powerpoint",
329- u"application/msword",
330- u"application/pdf",
331- u"application/postscript",
332- u"application/ps",
333- u"application/rtf",
334- u"application/x-abiword",
335- u"application/x-gnucash",
336- u"application/x-gnumeric",
337- SimpleMatch(u"application/x-java*"),
338- SimpleMatch(u"*/x-tex"),
339- SimpleMatch(u"*/x-latex"),
340- SimpleMatch(u"*/x-dvi"),
341- u"text/plain"
342-]
343-
344-IMAGE_MIMETYPES = [
345- # Covers:
346- # vnd.corel-draw
347- u"application/vnd.corel-draw",
348- # Covers: x-kword, x-kspread, x-kpresenter, x-killustrator
349- re.compile(u"application/x-k(word|spread|presenter|illustrator)"),
350- SimpleMatch(u"image/*"),
351-]
352-
353-AUDIO_MIMETYPES = [
354- SimpleMatch(u"audio/*"),
355- u"application/ogg"
356-]
357-
358-VIDEO_MIMETYPES = [
359- SimpleMatch(u"video/*"),
360- u"application/ogg"
361-]
362-
363-DEVELOPMENT_MIMETYPES = [
364- u"application/ecmascript",
365- u"application/javascript",
366- u"application/x-csh",
367- u"application/x-designer",
368- u"application/x-desktop",
369- u"application/x-dia-diagram",
370- u"application/x-fluid",
371- u"application/x-glade",
372- u"application/xhtml+xml",
373- u"application/x-java-archive",
374- u"application/x-m4",
375- u"application/xml",
376- u"application/x-object",
377- u"application/x-perl",
378- u"application/x-php",
379- u"application/x-ruby",
380- u"application/x-shellscript",
381- u"application/x-sql",
382- u"text/css",
383- u"text/html",
384- u"text/x-c",
385- u"text/x-c++",
386- u"text/x-chdr",
387- u"text/x-copying",
388- u"text/x-credits",
389- u"text/x-csharp",
390- u"text/x-c++src",
391- u"text/x-csrc",
392- u"text/x-dsrc",
393- u"text/x-eiffel",
394- u"text/x-gettext-translation",
395- u"text/x-gettext-translation-template",
396- u"text/x-haskell",
397- u"text/x-idl",
398- u"text/x-java",
399- u"text/x-lisp",
400- u"text/x-lua",
401- u"text/x-makefile",
402- u"text/x-objcsrc",
403- u"text/x-ocaml",
404- u"text/x-pascal",
405- u"text/x-patch",
406- u"text/x-python",
407- u"text/x-sql",
408- u"text/x-tcl",
409- u"text/x-troff",
410- u"text/x-vala",
411- u"text/x-vhdl",
412-]
413-
414-ALL_MIMETYPES = DOCUMENT_MIMETYPES + IMAGE_MIMETYPES + AUDIO_MIMETYPES + \
415- VIDEO_MIMETYPES + DEVELOPMENT_MIMETYPES
416-
417-class MimeTypeSet(set):
418- """ Set which allows to match against a string or an object with a
419- match() method.
420- """
421-
422- def __init__(self, *items):
423- super(MimeTypeSet, self).__init__()
424- self.__pattern = set()
425- for item in items:
426- if isinstance(item, (str, unicode)):
427- self.add(item)
428- elif hasattr(item, "match"):
429- self.__pattern.add(item)
430- else:
431- raise ValueError("Bad mimetype '%s'" %item)
432-
433- def __contains__(self, mimetype):
434- result = super(MimeTypeSet, self).__contains__(mimetype)
435- if not result:
436- for pattern in self.__pattern:
437- if pattern.match(mimetype):
438- return True
439- return result
440-
441- def __len__(self):
442- return super(MimeTypeSet, self).__len__() + len(self.__pattern)
443-
444- def __repr__(self):
445- items = ", ".join(sorted(map(repr, self | self.__pattern)))
446- return "%s(%s)" %(self.__class__.__name__, items)
447-
448-
449-class RecentlyUsedManagerGtk(DataProvider):
450-
451- FILTERS = {
452- # dict of name as key and the matching mimetypes as value
453- # if the value is None this filter matches all mimetypes
454- "DOCUMENT": MimeTypeSet(*DOCUMENT_MIMETYPES),
455- "IMAGE": MimeTypeSet(*IMAGE_MIMETYPES),
456- "AUDIO": MimeTypeSet(*AUDIO_MIMETYPES),
457- "VIDEO": MimeTypeSet(*VIDEO_MIMETYPES),
458- "SOURCE_CODE": MimeTypeSet(*DEVELOPMENT_MIMETYPES),
459- }
460-
461- def __init__(self, client):
462- DataProvider.__init__(self,
463- unique_id="com.zeitgeist-project,datahub,recent",
464- name="Recently Used Documents",
465- description="Logs events from GtkRecentlyUsed",
466- event_templates=[Event.new_for_values(interpretation=i) for i in (
467- Interpretation.CREATE_EVENT,
468- Interpretation.ACCESS_EVENT,
469- Interpretation.MODIFY_EVENT
470- )],
471- client=client)
472- self._load_data_sources_registry()
473- self.recent_manager = recent_manager()
474- self.recent_manager.set_limit(-1)
475- self.recent_manager.connect("changed", lambda m: self.emit("reload"))
476- self.config.connect("configured", lambda m: self.emit("reload"))
477-
478- def _load_data_sources_registry(self):
479- self._ignore_apps = {}
480- def _data_source_registered(datasource):
481- for tmpl in datasource[DataSource.EventTemplates]:
482- actor = tmpl[0][Event.Actor]
483- if actor:
484- if not actor in self._ignore_apps:
485- self._ignore_apps[actor] = set()
486- interp = tmpl[0][Event.Interpretation]
487- if interp:
488- self._ignore_apps[actor].add(interp)
489- for datasource in self._registry.GetDataSources():
490- _data_source_registered(datasource)
491- self._registry.connect("DataSourceRegistered", _data_source_registered)
492-
493- @staticmethod
494- def _desktop_file_matches_app(filename, application, mimetype):
495- """ Checks whether the given .desktop file represents the indicated
496- application.
497-
498- If a mimetype is also given, only .desktop files listing it as
499- supported will be considered. This is needed to differentiate
500- between the different OpenOffice.org components, for instance.
501- """
502-
503- try:
504- with open(filename) as desktopfile:
505- _exec = False
506- _mime = False
507- for line in desktopfile:
508- if line.startswith("Exec=") and \
509- line.split("=", 1)[-1].strip().split()[0] == application:
510- if mimetype is None or _mime:
511- return True
512- else:
513- _exec = True
514- elif line.startswith("MimeType=") and mimetype in \
515- line.split("=", 1)[-1].split(";"):
516- if _exec:
517- return True
518- else:
519- _mime = True
520-
521- except IOError:
522- pass # file may be a broken symlink (LP: #523761)
523- except Exception, e:
524- log.warning('Corrupt .desktop file: %s', filename)
525-
526- return False
527-
528- @staticmethod
529- def _find_desktop_file_for_application(application, mimetype):
530- """ Searches for a .desktop file for the given application in
531- $XDG_DATADIRS and returns the path to the found file. If no file
532- is found, returns None.
533- """
534- desktopfiles = \
535- list(BaseDirectory.load_data_paths("applications", "%s.desktop" % application))
536- if desktopfiles:
537- return unicode(desktopfiles[0])
538- else:
539- is_match = RecentlyUsedManagerGtk._desktop_file_matches_app # local stuff is accessed faster
540- for path in BaseDirectory.load_data_paths("applications"):
541- for filename in (name for name in os.listdir(path) if name.endswith(".desktop")):
542- fullname = os.path.join(path, filename)
543- if (is_match(fullname, application, mimetype)):
544- return unicode(fullname)
545-
546- return None
547-
548- def _get_interpretation_for_mimetype(self, mimetype):
549- matching_filter = None
550- for filter_name, mimetypes in self.FILTERS.iteritems():
551- if mimetype and mimetype in mimetypes:
552- matching_filter = filter_name
553- break
554- if matching_filter:
555- return getattr(Interpretation, matching_filter).uri
556- return ""
557-
558- def _get_items(self):
559- # We save the start timestamp to avoid race conditions
560- last_seen = get_timestamp_for_now()
561-
562- events = []
563-
564- for (num, info) in enumerate(self.recent_manager.get_items()):
565- uri = info.get_uri()
566- if info.exists() and not info.get_private_hint() and not uri.startswith("file:///tmp/"):
567- last_application = info.last_application().strip()
568- application = info.get_application_info(last_application)[0].split()[0]
569- mimetype = unicode(info.get_mime_type())
570- if application in ("ooffice", "soffice"):
571- # Special case OpenOffice.org *sigh*
572- desktopfile = self._find_desktop_file_for_application("ooffice", mimetype)
573- else:
574- desktopfile = self._find_desktop_file_for_application(application, None)
575- if not desktopfile:
576- continue
577- actor = u"application://%s" % os.path.basename(desktopfile)
578-
579- subject = Subject.new_for_values(
580- uri = unicode(uri),
581- interpretation = self._get_interpretation_for_mimetype(
582- unicode(info.get_mime_type())),
583- manifestation = Manifestation.FILE_DATA_OBJECT.uri,
584- text = info.get_display_name(),
585- mimetype = mimetype,
586- origin = uri.rpartition("/")[0]
587- )
588-
589- times = set()
590- for meth, interp in (
591- (info.get_added, Interpretation.CREATE_EVENT.uri),
592- (info.get_visited, Interpretation.ACCESS_EVENT.uri),
593- (info.get_modified, Interpretation.MODIFY_EVENT.uri)
594- ):
595- if actor not in self._ignore_apps or \
596- (self._ignore_apps[actor] and
597- interp not in self._ignore_apps[actor]):
598- times.add((meth() * 1000, interp))
599-
600- is_new = False
601- for timestamp, use in times:
602- if timestamp <= self._last_seen:
603- continue
604- is_new = True
605- events.append(Event.new_for_values(
606- timestamp = timestamp,
607- interpretation = use,
608- manifestation = Manifestation.USER_ACTIVITY.uri,
609- actor = actor,
610- subjects = [subject]
611- ))
612- if num % 50 == 0:
613- self._process_gobject_events()
614- self._last_seen = last_seen
615- return events
616-
617-if enabled:
618- __datasource__ = RecentlyUsedManagerGtk
619
620=== removed file '_zeitgeist/loggers/iso_strptime.py'
621--- _zeitgeist/loggers/iso_strptime.py 2009-07-20 17:10:41 +0000
622+++ _zeitgeist/loggers/iso_strptime.py 1970-01-01 00:00:00 +0000
623@@ -1,86 +0,0 @@
624-# -.- coding: utf-8 -.-
625-
626-# This file is part of wadllib.
627-#
628-# Copyright © 2009 Canonical Ltd.
629-#
630-# wadllib is free software: you can redistribute it and/or modify it under the
631-# terms of the GNU Lesser General Public License as published by the Free
632-# Software Foundation, version 3 of the License.
633-#
634-# wadllib is distributed in the hope that it will be useful, but WITHOUT ANY
635-# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
636-# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
637-# details.
638-#
639-# You should have received a copy of the GNU Lesser General Public License
640-# along with wadllib. If not, see <http://www.gnu.org/licenses/>.
641-
642-"""
643-Parser for ISO 8601 time strings
644-================================
645-
646->>> d = iso_strptime("2008-01-07T05:30:30.345323+03:00")
647->>> d
648-datetime.datetime(2008, 1, 7, 5, 30, 30, 345323, tzinfo=TimeZone(10800))
649->>> d.timetuple()
650-(2008, 1, 7, 5, 30, 30, 0, 7, 0)
651->>> d.utctimetuple()
652-(2008, 1, 7, 2, 30, 30, 0, 7, 0)
653->>> iso_strptime("2008-01-07T05:30:30.345323-03:00")
654-datetime.datetime(2008, 1, 7, 5, 30, 30, 345323, tzinfo=TimeZone(-10800))
655->>> iso_strptime("2008-01-07T05:30:30.345323")
656-datetime.datetime(2008, 1, 7, 5, 30, 30, 345323)
657->>> iso_strptime("2008-01-07T05:30:30")
658-datetime.datetime(2008, 1, 7, 5, 30, 30)
659->>> iso_strptime("2008-01-07T05:30:30+02:00")
660-datetime.datetime(2008, 1, 7, 5, 30, 30, tzinfo=TimeZone(7200))
661-"""
662-
663-import re
664-import datetime
665-
666-RE_TIME = re.compile(r"""^
667- # pattern matching date
668- (?P<year>\d{4})\-(?P<month>\d{2})\-(?P<day>\d{2})
669- # separator
670- T
671- # pattern matching time
672- (?P<hour>\d{2})\:(?P<minutes>\d{2})\:(?P<seconds>\d{2})
673- # pattern matching optional microseconds
674- (\.(?P<microseconds>\d{6})\d*)?
675- # pattern matching optional timezone offset
676- (?P<tz_offset>[\-\+]\d{2}\:\d{2})?
677- $""", re.VERBOSE)
678-
679-class TimeZone(datetime.tzinfo):
680-
681- def __init__(self, tz_string):
682- hours, minutes = tz_string.lstrip("-+").split(":")
683- self.stdoffset = datetime.timedelta(hours=int(hours),
684- minutes=int(minutes))
685- if tz_string.startswith("-"):
686- self.stdoffset *= -1
687-
688- def __repr__(self):
689- return "TimeZone(%s)" % (
690- self.stdoffset.days*24*60*60 + self.stdoffset.seconds)
691-
692- def utcoffset(self, dt):
693- return self.stdoffset
694-
695- def dst(self, dt):
696- return datetime.timedelta(0)
697-
698-def iso_strptime(time_str):
699- x = RE_TIME.match(time_str)
700- if not x:
701- raise ValueError("unable to parse time '%s'" %time_str)
702- d = datetime.datetime(int(x.group("year")), int(x.group("month")),
703- int(x.group("day")), int(x.group("hour")), int(x.group("minutes")),
704- int(x.group("seconds")))
705- if x.group("microseconds"):
706- d = d.replace(microsecond=int(x.group("microseconds")))
707- if x.group("tz_offset"):
708- d = d.replace(tzinfo=TimeZone(x.group("tz_offset")))
709- return d
710
711=== removed file '_zeitgeist/loggers/zeitgeist_base.py'
712--- _zeitgeist/loggers/zeitgeist_base.py 2010-04-22 18:28:40 +0000
713+++ _zeitgeist/loggers/zeitgeist_base.py 1970-01-01 00:00:00 +0000
714@@ -1,91 +0,0 @@
715-# -.- coding: utf-8 -.-
716-
717-# Zeitgeist
718-#
719-# Copyright © 2009 Seif Lotfy <seif@lotfy.com>
720-# Copyright © 2009-2010 Siegfried-Angel Gevatter Pujals <rainct@ubuntu.com>
721-# Copyright © 2009 Natan Yellin <aantny@gmail.com>
722-# Copyright © 2009 Alex Graveley <alex@beatniksoftware.com>
723-# Copyright © 2009 Markus Korn <thekorn@gmx.de>
724-#
725-# This program is free software: you can redistribute it and/or modify
726-# it under the terms of the GNU Lesser General Public License as published by
727-# the Free Software Foundation, either version 3 of the License, or
728-# (at your option) any later version.
729-#
730-# This program is distributed in the hope that it will be useful,
731-# but WITHOUT ANY WARRANTY; without even the implied warranty of
732-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
733-# GNU Lesser General Public License for more details.
734-#
735-# You should have received a copy of the GNU Lesser General Public License
736-# along with this program. If not, see <http://www.gnu.org/licenses/>.
737-
738-from threading import Thread
739-import gobject
740-import logging
741-
742-from zeitgeist.datamodel import DataSource
743-from _zeitgeist.loggers.zeitgeist_setup_service import _Configuration, DefaultConfiguration
744-
745-class DataProvider(gobject.GObject, Thread):
746-
747- __gsignals__ = {
748- "reload" : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()),
749- }
750-
751- def __init__(self, unique_id, name, description="", event_templates=[],
752- client=None, config=None):
753-
754- # Initialize superclasses
755- Thread.__init__(self)
756- gobject.GObject.__init__(self)
757-
758- self._name = name
759- self._client = client
760- self._ctx = gobject.main_context_default()
761-
762- if client:
763- self._registry = self._client.get_extension("DataSourceRegistry",
764- "data_source_registry")
765- try:
766- self._last_seen = [ds[DataSource.LastSeen] for ds in \
767- self._registry.GetDataSources() if \
768- ds[DataSource.UniqueId] == unique_id][0] - 1800000
769- # We substract 30 minutes to make sure no events get missed.
770- except IndexError:
771- self._last_seen = 0
772- self._enabled = self._registry.RegisterDataSource(unique_id, name,
773- description, event_templates)
774-
775- if not config:
776- self.config = DefaultConfiguration(self._name)
777- else:
778- if not isinstance(config, _Configuration):
779- raise TypeError
780- self.config = config
781-
782- def get_name(self):
783- return self._name
784-
785- def get_items(self):
786- if not self._enabled:
787- return []
788- # FIXME: We need to figure out what to do with this configuration stuff
789- # Maybe merge it into the DataSource registry so that everyone
790- # can benefit from it, or just throw it out.
791- if not self.config.isConfigured() or not self.config.enabled:
792- logging.warning("'%s' isn't enabled or configured." % \
793- self.config.get_internal_name())
794- return []
795- return self._get_items()
796-
797- def _get_items(self):
798- """ Subclasses should override this to return data. """
799- raise NotImplementedError
800-
801- def _process_gobject_events(self):
802- """ Check for pending gobject events. This should be called in some
803- meaningful place in _get_items on long running updates. """
804- while self._ctx.pending():
805- self._ctx.iteration()
806
807=== removed file '_zeitgeist/loggers/zeitgeist_setup_service.py'
808--- _zeitgeist/loggers/zeitgeist_setup_service.py 2009-09-04 15:13:13 +0000
809+++ _zeitgeist/loggers/zeitgeist_setup_service.py 1970-01-01 00:00:00 +0000
810@@ -1,243 +0,0 @@
811-# -.- coding: utf-8 -.-
812-
813-# Zeitgeist
814-#
815-# Copyright © 2009 Markus Korn <thekorn@gmx.de>
816-#
817-# This program is free software: you can redistribute it and/or modify
818-# it under the terms of the GNU Lesser General Public License as published by
819-# the Free Software Foundation, either version 3 of the License, or
820-# (at your option) any later version.
821-#
822-# This program is distributed in the hope that it will be useful,
823-# but WITHOUT ANY WARRANTY; without even the implied warranty of
824-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
825-# GNU Lesser General Public License for more details.
826-#
827-# You should have received a copy of the GNU Lesser General Public License
828-# along with this program. If not, see <http://www.gnu.org/licenses/>.
829-
830-import dbus
831-import dbus.service
832-import gobject
833-import gconf
834-import glib
835-import dbus.mainloop.glib
836-from ConfigParser import SafeConfigParser
837-from xdg import BaseDirectory
838-from StringIO import StringIO
839-
840-dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
841-
842-class DataProviderService(dbus.service.Object):
843-
844- def __init__(self, datasources, mainloop=None):
845- bus_name = dbus.service.BusName("org.gnome.zeitgeist.datahub", dbus.SessionBus())
846- dbus.service.Object.__init__(self, bus_name, "/org/gnome/zeitgeist/datahub")
847- self._mainloop = mainloop
848- self.__datasources = datasources
849-
850- @dbus.service.method("org.gnome.zeitgeist.DataHub",
851- out_signature="as")
852- def GetDataProviders(self):
853- return [i.config.get_internal_name() for i in self.__datasources if i.config.has_dbus_service()]
854-
855- def needs_setup(self):
856- return not self.__configuration.isConfigured()
857-
858-
859-class SetupService(dbus.service.Object):
860-
861- def __init__(self, datasource, root_config, mainloop=None):
862- bus_name = dbus.service.BusName("org.gnome.zeitgeist.datahub", dbus.SessionBus())
863- dbus.service.Object.__init__(self,
864- bus_name, "/org/gnome/zeitgeist/datahub/dataprovider/%s" %datasource)
865- self._mainloop = mainloop
866- self.__configuration = root_config
867- if not isinstance(self.__configuration, _Configuration):
868- raise TypeError
869- self.__setup_is_running = None
870-
871- @dbus.service.method("org.gnome.zeitgeist.DataHub",
872- in_signature="iss")
873- def SetConfiguration(self, token, option, value):
874- if token != self.__setup_is_running:
875- raise RuntimeError("wrong client")
876- self.__configuration.set_attribute(option, value)
877-
878- @dbus.service.signal("org.gnome.zeitgeist.DataHub")
879- def NeedsSetup(self):
880- pass
881-
882- @dbus.service.method("org.gnome.zeitgeist.DataHub",
883- in_signature="i", out_signature="b")
884- def RequestSetupRun(self, token):
885- if self.__setup_is_running is None:
886- self.__setup_is_running = token
887- return True
888- else:
889- raise False
890-
891- @dbus.service.method("org.gnome.zeitgeist.DataHub",
892- out_signature="a(sb)")
893- def GetOptions(self, token):
894- if token != self.__setup_is_running:
895- raise RuntimeError("wrong client")
896- return self.__configuration.get_options()
897-
898- def needs_setup(self):
899- return not self.__configuration.isConfigured()
900-
901-
902-class _Configuration(gobject.GObject):
903-
904- @staticmethod
905- def like_bool(value):
906- if isinstance(value, bool):
907- return value
908- elif value.lower() in ("true", "1", "on"):
909- return True
910- elif value.lower() in ("false", "0", "off"):
911- return False
912- else:
913- raise ValueError
914-
915- def __init__(self, internal_name, use_dbus=True, mainloop=None):
916- gobject.GObject.__init__(self)
917- self.__required = set()
918- self.__items = dict()
919- self.__internal_name = internal_name.replace(" ", "_").lower()
920- if use_dbus:
921- self.__dbus_service = SetupService(self.__internal_name, self, mainloop)
922- else:
923- self.__dbus_service = None
924-
925- def has_dbus_service(self):
926- return self.__dbus_service is not None
927-
928- def get_internal_name(self):
929- return self.__internal_name
930-
931- def add_option(self, name, to_type=str, to_string=str, default=None,
932- required=True, secret=False):
933- if name in self.__items:
934- raise ValueError
935- if required:
936- self.__required.add(name)
937- if to_type is None:
938- to_type = lambda x: x
939- self.__items[name] = (to_type(default), (to_type, to_string), secret)
940-
941- def __getattr__(self, name):
942- if not self.isConfigured():
943- raise RuntimeError
944- return self.__items[name][0]
945-
946- def get_as_string(self, name):
947- if not self.isConfigured():
948- raise RuntimeError
949- try:
950- value, (_, to_string), _ = self.__items[name]
951- except KeyError:
952- raise AttributeError
953- return str(to_string(value))
954-
955- def set_attribute(self, name, value, check_configured=True):
956- if name not in self.__items:
957- raise ValueError
958- _, (to_type, to_string), secret = self.__items[name]
959- self.__items[name] = (to_type(value), (to_type, to_string), secret)
960- if name in self.__required:
961- self.remove_requirement(name)
962- if check_configured and self.isConfigured():
963- glib.idle_add(self.emit, "configured")
964-
965- def remove_requirement(self, name):
966- self.__required.remove(name)
967-
968- def add_requirement(self, name):
969- if not name in self.__items:
970- raise ValueError
971- self.__required.add(name)
972-
973- def isConfigured(self):
974- return not self.__required
975-
976- def read_config(self, filename, section):
977- config = SafeConfigParser()
978- config.readfp(open(filename))
979- if config.has_section(section):
980- for name, value in config.items(section):
981- self.set_attribute(name, value)
982-
983- def dump_config(self, config=None):
984- section = self.get_internal_name()
985- if config is None:
986- config = SafeConfigParser()
987- try:
988- config.add_section(section)
989- except ConfigParser.DuplicateSectionError:
990- pass
991- for key, value in self.__items.iteritems():
992- value, _, secret = value
993- if not secret:
994- config.set(section, key, str(value))
995- f = StringIO()
996- config.write(f)
997- return f.getvalue()
998-
999- def get_requirements(self):
1000- return self.__required
1001-
1002- def get_options(self):
1003- return [(str(key), key in self.__required) for key in self.__items]
1004-
1005-
1006-gobject.signal_new("configured", _Configuration,
1007- gobject.SIGNAL_RUN_LAST,
1008- gobject.TYPE_NONE,
1009- tuple())
1010-
1011-
1012-class DefaultConfiguration(_Configuration):
1013-
1014- CONFIGFILE = BaseDirectory.load_first_config("zeitgeist", "dataprovider.conf")
1015- DEFAULTS = [
1016- ("enabled", _Configuration.like_bool, str, True, False),
1017- ]
1018-
1019- def __init__(self, dataprovider):
1020- super(DefaultConfiguration, self).__init__(dataprovider)
1021- for default in self.DEFAULTS:
1022- self.add_option(*default)
1023- if self.CONFIGFILE:
1024- self.read_config(self.CONFIGFILE, self.get_internal_name())
1025-
1026- def save_config(self):
1027- if self.CONFIGFILE:
1028- config = SafeConfigParser()
1029- config.readfp(open(self.CONFIGFILE))
1030- self.dump_config(config)
1031- f = StringIO()
1032- config.write(f)
1033- configfile = open(self.CONFIGFILE, "w")
1034- try:
1035- config.write(configfile)
1036- finally:
1037- configfile.close()
1038-
1039-if __name__ == "__main__":
1040-
1041- # TODO: Move this to test/.
1042-
1043- def test(config):
1044- for option, required in config.get_options():
1045- print option, getattr(config, option)
1046-
1047- dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
1048- mainloop = gobject.MainLoop()
1049-
1050- config = _Configuration("test", True, mainloop)
1051- config.add_option("enabled", _Configuration.like_bool, default=False)
1052- config.connect("configured", test)
1053- mainloop.run()
1054
1055=== modified file 'configure.ac'
1056--- configure.ac 2010-09-26 18:16:01 +0000
1057+++ configure.ac 2010-10-14 09:46:58 +0000
1058@@ -26,8 +26,6 @@
1059 zeitgeist-daemon.pc
1060 zeitgeist/Makefile
1061 _zeitgeist/Makefile
1062- _zeitgeist/loggers/Makefile
1063- _zeitgeist/loggers/datasources/Makefile
1064 _zeitgeist/engine/Makefile
1065 _zeitgeist/engine/extensions/Makefile
1066 _zeitgeist/engine/upgrades/Makefile
1067
1068=== modified file 'po/POTFILES.in'
1069--- po/POTFILES.in 2009-11-27 20:32:54 +0000
1070+++ po/POTFILES.in 2010-10-14 09:46:58 +0000
1071@@ -1,8 +1,4 @@
1072 zeitgeist-daemon.py
1073-zeitgeist-datahub.py
1074 zeitgeist/datamodel.py
1075 _zeitgeist/singleton.py
1076-_zeitgeist/loggers/zeitgeist_base.py
1077-_zeitgeist/loggers/zeitgeist_setup_service.py
1078-_zeitgeist/loggers/datasources/recent.py
1079 _zeitgeist/engine/remote.py
1080
1081=== removed file 'test/loggers-datasources-recent-test.py'
1082--- test/loggers-datasources-recent-test.py 2010-09-21 09:17:41 +0000
1083+++ test/loggers-datasources-recent-test.py 1970-01-01 00:00:00 +0000
1084@@ -1,49 +0,0 @@
1085-#!/usr/bin/python
1086-
1087-# Update python path to use local zeitgeist module
1088-import sys
1089-import os
1090-import re
1091-import unittest
1092-
1093-sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))
1094-
1095-SimpleMatch = None
1096-MimeTypeSet = None
1097-
1098-class BaseTestCase(unittest.TestCase):
1099-
1100- def setUp(self):
1101- global SimpleMatch
1102- global MimeTypeSet
1103- if None in (SimpleMatch, MimeTypeSet):
1104- from _zeitgeist.loggers.datasources.recent import SimpleMatch as _SM, MimeTypeSet as _MTS
1105- SimpleMatch = _SM
1106- MimeTypeSet = _MTS
1107-
1108-class SimpleMatchTest(BaseTestCase):
1109-
1110- def testmatch(self):
1111- self.assertTrue(SimpleMatch("boo/*").match("boo/bar"))
1112- self.assertTrue(SimpleMatch("boo/bar.*").match("boo/bar.foo"))
1113- self.assertFalse(SimpleMatch("boo/bar.*").match("boo/barfoo"))
1114-
1115-class MimeTypeSetTest(BaseTestCase):
1116-
1117- def testinit(self):
1118- self.assertEquals(repr(MimeTypeSet("boo", "bar", "foo")), "MimeTypeSet('bar', 'boo', 'foo')")
1119- self.assertEquals(repr(MimeTypeSet("boo", "foo", "foo")), "MimeTypeSet('boo', 'foo')")
1120- m = MimeTypeSet("boo", SimpleMatch("bar/*"), re.compile("test.*"))
1121- self.assertEquals(len(m), 3)
1122- self.assertRaises(ValueError, MimeTypeSet, 1)
1123-
1124- def testcontains(self):
1125- m = MimeTypeSet("boo", SimpleMatch("bar/*"), re.compile("test.*"))
1126- self.assertTrue("boo" in m)
1127- self.assertTrue("bar/boo" in m)
1128- self.assertTrue("testboo" in m)
1129- self.assertFalse("boobar" in m)
1130- self.assertFalse("bar" in m)
1131-
1132-if __name__ == '__main__':
1133- unittest.main()
1134
1135=== modified file 'zeitgeist-daemon.py'
1136--- zeitgeist-daemon.py 2010-09-15 12:56:45 +0000
1137+++ zeitgeist-daemon.py 2010-10-14 09:46:58 +0000
1138@@ -21,13 +21,13 @@
1139 import sys
1140 import os
1141 import gobject
1142-import subprocess
1143 import dbus.mainloop.glib
1144 import gettext
1145 import logging
1146 import optparse
1147 import signal
1148 from copy import copy
1149+from subprocess import Popen, PIPE
1150
1151 # Make sure we can find the private _zeitgeist namespace
1152 from zeitgeist import _config
1153@@ -39,6 +39,7 @@
1154 sys.path.insert(0, constants.USER_EXTENSION_PATH)
1155
1156 gettext.install("zeitgeist", _config.localedir, unicode=1)
1157+DATAHUB = "zeitgeist-datahub"
1158
1159 def check_loglevel(option, opt, value):
1160 value = value.upper()
1161@@ -46,6 +47,11 @@
1162 return value
1163 raise optparse.OptionValueError(
1164 "option %s: invalid value: %s" % (opt, value))
1165+
1166+def which(executable):
1167+ p = Popen(["which", str(executable)], stderr=PIPE, stdout=PIPE)
1168+ p.wait()
1169+ return p.stdout.read().strip() or None
1170
1171 class Options(optparse.Option):
1172
1173@@ -100,16 +106,21 @@
1174 logging.error(unicode(e))
1175 sys.exit(1)
1176
1177-passive_loggers = os.path.join(_config.bindir, "zeitgeist-datahub.py")
1178 if _config.options.start_datahub:
1179- if os.path.isfile(passive_loggers):
1180- devnull = open(os.devnull, 'w')
1181- subprocess.Popen(passive_loggers, stdin=devnull, stdout=devnull,
1182- stderr=devnull)
1183- del devnull
1184+ # hide all output of the datahub for now,
1185+ # in the future we might want to be more verbose here to make
1186+ # debugging easier in case sth. goes wrong with the datahub
1187+ devnull = open(os.devnull, "w")
1188+ try:
1189+ # we assume to find the datahub somewhere in PATH
1190+ p = Popen(DATAHUB, stdin=devnull, stdout=devnull, stderr=devnull)
1191+ except OSError:
1192+ logging.warning("Unable to start the datahub, no binary found")
1193 else:
1194- logging.warning(
1195- _("File \"%s\" not found, not starting datahub") % passive_loggers)
1196+ # TODO: delayed check if the datahub is still running after some time
1197+ # and not failed because of some error
1198+ # tell the user which datahub we are running
1199+ logging.debug("Running datahub (%s) with PID=%i" %(which(DATAHUB), p.pid))
1200
1201 def handle_sighup(signum, frame):
1202 """We are using the SIGHUP signal to shutdown zeitgeist in a clean way"""
1203@@ -117,5 +128,5 @@
1204 interface.Quit()
1205 signal.signal(signal.SIGHUP, handle_sighup)
1206
1207-logging.info(_(u"Starting Zeitgeist service..."))
1208+logging.info("Starting Zeitgeist service...")
1209 mainloop.run()
1210
1211=== removed file 'zeitgeist-datahub.py'
1212--- zeitgeist-datahub.py 2010-01-21 14:57:23 +0000
1213+++ zeitgeist-datahub.py 1970-01-01 00:00:00 +0000
1214@@ -1,135 +0,0 @@
1215-#! /usr/bin/env python
1216-# -.- coding: utf-8 -.-
1217-
1218-# Zeitgeist
1219-#
1220-# Copyright © 2009 Siegfried-Angel Gevatter Pujals <rainct@ubuntu.com>
1221-#
1222-# This program is free software: you can redistribute it and/or modify
1223-# it under the terms of the GNU Lesser General Public License as published by
1224-# the Free Software Foundation, either version 3 of the License, or
1225-# (at your option) any later version.
1226-#
1227-# This program is distributed in the hope that it will be useful,
1228-# but WITHOUT ANY WARRANTY; without even the implied warranty of
1229-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1230-# GNU Lesser General Public License for more details.
1231-#
1232-# You should have received a copy of the GNU Lesser General Public License
1233-# along with this program. If not, see <http://www.gnu.org/licenses/>.
1234-
1235-import sys
1236-import os
1237-import glob
1238-import gettext
1239-import logging
1240-import gobject
1241-import dbus.exceptions
1242-
1243-from zeitgeist import _config
1244-_config.setup_path()
1245-
1246-from zeitgeist.client import ZeitgeistDBusInterface
1247-from _zeitgeist.loggers.zeitgeist_setup_service import DataProviderService
1248-
1249-gettext.install("zeitgeist", _config.localedir, unicode=1)
1250-logging.basicConfig(level=logging.DEBUG)
1251-
1252-sys.path.insert(0, _config.datasourcedir)
1253-
1254-class DataHub(gobject.GObject):
1255-
1256- __gsignals__ = {
1257- "reload" : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()),
1258- }
1259-
1260- def __init__(self):
1261-
1262- gobject.GObject.__init__(self)
1263-
1264- self._client = ZeitgeistDBusInterface()
1265- self._client.connect_exit(self._daemon_exit)
1266-
1267- # Load the data sources
1268- self._sources = []
1269- for datasource_file in glob.glob(_config.datasourcedir + '/*.py'):
1270- if not datasource_file.startswith('_'):
1271- self._load_datasource_file(os.path.basename(datasource_file))
1272-
1273- # Start by fetch new items from all sources
1274- self._sources_queue = list(self._sources)
1275- if not self._sources_queue:
1276- logging.warning(_("No passive loggers found, bye."))
1277- sys.exit(1) # Mainloop doesn't exist yet, exit directly
1278- self._db_update_in_progress = True
1279- gobject.idle_add(self._update_db_async)
1280-
1281- for source in self._sources:
1282- source.connect("reload", self._update_db_with_source)
1283-
1284- self._mainloop = gobject.MainLoop()
1285- self.dbus_service = DataProviderService(self._sources, None)
1286- self._mainloop.run()
1287-
1288- def _daemon_exit(self):
1289- self._mainloop.quit()
1290-
1291- def _load_datasource_file(self, datasource_file):
1292-
1293- try:
1294- datasource_object = __import__(datasource_file[:-3])
1295- except ImportError, err:
1296- logging.exception(_("Could not load file: %s" % datasource_file))
1297- return False
1298-
1299- if hasattr(datasource_object, "__datasource__"):
1300- objs = datasource_object.__datasource__
1301- for obj in objs if hasattr(objs, "__iter__") else (objs,):
1302- self._sources.append(obj(self._client))
1303-
1304- def _update_db_with_source(self, source):
1305- """
1306- Add new items into the database. This funcion should not be
1307- called directly, but instead activated through the "reload"
1308- signal.
1309- """
1310-
1311- if not source in self._sources_queue:
1312- self._sources_queue.append(source)
1313- if not self._db_update_in_progress:
1314- self._db_update_in_progress = True
1315- gobject.idle_add(self._update_db_async)
1316-
1317- def _update_db_async(self):
1318-
1319- logging.debug(_("Updating database with new %s items") % \
1320- self._sources_queue[0].get_name())
1321-
1322- events = self._sources_queue[0].get_items()
1323- if events:
1324- self._insert_events(self._sources_queue[0].get_name(), events)
1325-
1326- del self._sources_queue[0]
1327-
1328- if len(self._sources_queue) == 0:
1329- self._db_update_in_progress = False
1330- return False # Return False to stop this callback
1331-
1332- # Otherwise, if there are more items in the queue return True so
1333- # that GTK+ will continue to call this function in idle CPU time
1334- return True
1335-
1336- def _insert_events(self, source_name, events):
1337- try:
1338- self._client.InsertEvents(events)
1339- except dbus.exceptions.DBusException, error:
1340- error = error.get_dbus_name()
1341- if error == "org.freedesktop.DBus.Error.ServiceUnknown":
1342- logging.warning(
1343- _("Lost connection to zeitgeist-daemon, terminating."))
1344- self._daemon_exit()
1345- else:
1346- logging.exception(_("Error logging item from \"%s\": %s" % \
1347- (source_name, error)))
1348-
1349-datahub = DataHub()