Merge lp:~thekorn/zeitgeist/flexible.engine.backend into lp:zeitgeist/0.1
- flexible.engine.backend
- Merge into 0.8-python
Proposed by
Markus Korn
Status: | Merged |
---|---|
Approved by: | Siegfried Gevatter |
Approved revision: | 1052 |
Merged at revision: | not available |
Proposed branch: | lp:~thekorn/zeitgeist/flexible.engine.backend |
Merge into: | lp:zeitgeist/0.1 |
Diff against target: | None lines |
To merge this branch: | bzr merge lp:~thekorn/zeitgeist/flexible.engine.backend |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Mikkel Kamstrup Erlandsen | Approve | ||
Siegfried Gevatter | Approve | ||
Seif Lotfy | Pending | ||
Zeitgeist Framework Team | Pending | ||
Review via email: mp+9499@code.launchpad.net |
Commit message
Description of the change
To post a comment you must log in.
Revision history for this message
Markus Korn (thekorn) wrote : | # |
Revision history for this message
Siegfried Gevatter (rainct) wrote : | # |
Just had a quick look at the diff, but looks good.
review:
Approve
Revision history for this message
Mikkel Kamstrup Erlandsen (kamstrup) wrote : | # |
Review: Approve
Looks good, but I didn't run it to double check
> --
> https:/
> You are requested to review the proposed merge of lp:~thekorn/zeitgeist/flexible.engine.backend into lp:zeitgeist.
>
--
Cheers,
Mikkel
Revision history for this message
Mikkel Kamstrup Erlandsen (kamstrup) wrote : | # |
Looks good, but I didn't do a test run of it
review:
Approve
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file '_zeitgeist/engine/__init__.py' | |||
2 | --- _zeitgeist/engine/__init__.py 2008-11-19 18:43:27 +0000 | |||
3 | +++ _zeitgeist/engine/__init__.py 2009-07-31 09:33:22 +0000 | |||
4 | @@ -0,0 +1,49 @@ | |||
5 | 1 | import os | ||
6 | 2 | import logging | ||
7 | 3 | |||
8 | 4 | ENGINE_FALLBACK = "storm" | ||
9 | 5 | |||
10 | 6 | _engine = None | ||
11 | 7 | def create_engine(engine_type=None): | ||
12 | 8 | """ Creates an engine instance of the type defined by 'engine_type'. | ||
13 | 9 | If 'engine_type' is None 'ENGINE_FALLBACK' is used. | ||
14 | 10 | This function looks at _zeitgeist.engine to find the engine implementation. | ||
15 | 11 | Each engine implementation has to follow the following conventions: | ||
16 | 12 | 1.) it has to be in _zeitgeist/engine/SOMENAME_engine.py | ||
17 | 13 | (where SOMENAME defines the type) | ||
18 | 14 | 2.) the name of the class has to be ZeitgeistEngine and the class | ||
19 | 15 | itself has to be a sublass of _zeitgeist.engine.engine_base.BaseEngine | ||
20 | 16 | """ | ||
21 | 17 | global _engine | ||
22 | 18 | if engine_type is None: | ||
23 | 19 | engine_type = ENGINE_FALLBACK | ||
24 | 20 | engine_type = engine_type.lower() | ||
25 | 21 | if _engine is not None: | ||
26 | 22 | running_type = engine.__module__.split(".").pop().lower() | ||
27 | 23 | if not running_type == engine_type: | ||
28 | 24 | raise RuntimeError( | ||
29 | 25 | ("There is already a zeitgeist engine running. But this " | ||
30 | 26 | "engine has another than the requested type " | ||
31 | 27 | "(requested='%s', running='%s')" %(engine_type, running_type)) | ||
32 | 28 | ) | ||
33 | 29 | return _engine | ||
34 | 30 | try: | ||
35 | 31 | engine_cls = __import__( | ||
36 | 32 | "_zeitgeist.engine.%s_engine" %engine_type, | ||
37 | 33 | globals(), locals(), ["ZeitgeistEngine",], -1 | ||
38 | 34 | ) | ||
39 | 35 | except ImportError, err: | ||
40 | 36 | logging.exception("Could not load engine implementation for %r" %engine_type) | ||
41 | 37 | raise RuntimeError("Could not load engine implementation for %r" %engine_type) | ||
42 | 38 | _engine = engine_cls.ZeitgeistEngine() | ||
43 | 39 | return _engine | ||
44 | 40 | |||
45 | 41 | def get_default_engine(): | ||
46 | 42 | """ Get the running engine instance or create a new one. | ||
47 | 43 | To get the type of the new engine instance it looks at the 'ZEITGEIST_ENGINE' | ||
48 | 44 | environment variable. If this is not defined, it uses the engine type | ||
49 | 45 | defined by ENGINE_FALLBACK. | ||
50 | 46 | """ | ||
51 | 47 | if _engine is not None: | ||
52 | 48 | return _engine | ||
53 | 49 | return create_engine(engine_type=os.environ.get("ZEITGEIST_ENGINE", ENGINE_FALLBACK)) | ||
54 | 0 | 50 | ||
55 | === added file '_zeitgeist/engine/engine_base.py' | |||
56 | --- _zeitgeist/engine/engine_base.py 1970-01-01 00:00:00 +0000 | |||
57 | +++ _zeitgeist/engine/engine_base.py 2009-07-31 06:35:29 +0000 | |||
58 | @@ -0,0 +1,144 @@ | |||
59 | 1 | import time | ||
60 | 2 | import logging | ||
61 | 3 | import gobject | ||
62 | 4 | import sys | ||
63 | 5 | |||
64 | 6 | from functools import wraps | ||
65 | 7 | |||
66 | 8 | from _zeitgeist.lrucache import LRUCache | ||
67 | 9 | |||
68 | 10 | logging.basicConfig(level=logging.DEBUG) | ||
69 | 11 | log = logging.getLogger("zeitgeist.engine.engine_base") | ||
70 | 12 | |||
71 | 13 | def time_insert(function): | ||
72 | 14 | @wraps(function) | ||
73 | 15 | def wrapper(*args, **kwargs): | ||
74 | 16 | t1 = time.time() | ||
75 | 17 | result = function(*args, **kwargs) | ||
76 | 18 | log.debug("Inserted %s items in %.5f s." % (len(result), time.time() - t1)) | ||
77 | 19 | return result | ||
78 | 20 | return wrapper | ||
79 | 21 | |||
80 | 22 | class BaseEngine(gobject.GObject): | ||
81 | 23 | |||
82 | 24 | ALLOWED_FILTER_KEYS = set(["name", "uri", "tags", "mimetypes", | ||
83 | 25 | "source", "content", "application", "bookmarked"]) | ||
84 | 26 | |||
85 | 27 | def __init__(self): | ||
86 | 28 | |||
87 | 29 | gobject.GObject.__init__(self) | ||
88 | 30 | |||
89 | 31 | self._apps = set() | ||
90 | 32 | self._last_time_from_app = {} | ||
91 | 33 | self._applications = LRUCache(100) | ||
92 | 34 | |||
93 | 35 | def insert_event(self, ritem, commit=True, force=False): | ||
94 | 36 | """ | ||
95 | 37 | Inserts an item into the database. Returns a positive number on success, | ||
96 | 38 | zero otherwise (for example, if the item already is in the | ||
97 | 39 | database). In case the positive number is 1, the inserted event is new, | ||
98 | 40 | in case it's 2 the event already existed and was updated (this only | ||
99 | 41 | happens when `force' is True). | ||
100 | 42 | """ | ||
101 | 43 | |||
102 | 44 | # check for required items and make sure all items have the correct type | ||
103 | 45 | EventDict.check_missing_items(ritem, True) | ||
104 | 46 | |||
105 | 47 | # FIXME: uri, content, source are now required items, the statement above | ||
106 | 48 | # will raise a KeyError if they are not there. What about mimetype? | ||
107 | 49 | # and why are we printing a warning and returning False here instead of raising | ||
108 | 50 | # an error at all? - Markus Korn | ||
109 | 51 | if not ritem["uri"].strip(): | ||
110 | 52 | raise ValueError("Discarding item without a URI: %s" % ritem) | ||
111 | 53 | if not ritem["content"].strip(): | ||
112 | 54 | raise ValueError("Discarding item without a Content type: %s" % ritem) | ||
113 | 55 | if not ritem["source"].strip(): | ||
114 | 56 | raise ValueError("Discarding item without a Source type: %s" % ritem) | ||
115 | 57 | if not ritem["mimetype"].strip(): | ||
116 | 58 | raise ValueError("Discarding item without a mimetype: %s" % ritem) | ||
117 | 59 | return 0 | ||
118 | 60 | |||
119 | 61 | @time_insert | ||
120 | 62 | def insert_events(self, items): | ||
121 | 63 | """ | ||
122 | 64 | Inserts items into the database and returns those items which were | ||
123 | 65 | successfully inserted. If an item fails, that's usually because it | ||
124 | 66 | already was in the database. | ||
125 | 67 | """ | ||
126 | 68 | |||
127 | 69 | inserted_items = [] | ||
128 | 70 | for item in items: | ||
129 | 71 | # This is always 0 or 1, no need to consider 2 as we don't | ||
130 | 72 | # use the `force' option. | ||
131 | 73 | if self.insert_event(item, commit=False): | ||
132 | 74 | inserted_items.append(item) | ||
133 | 75 | return inserted_items | ||
134 | 76 | |||
135 | 77 | def get_item(self, uri): | ||
136 | 78 | """ Returns basic information about the indicated URI. As we are | ||
137 | 79 | fetching an item, and not an event, `timestamp' is 0 and `use' | ||
138 | 80 | and `app' are empty strings.""" | ||
139 | 81 | |||
140 | 82 | raise NotImplementedError | ||
141 | 83 | |||
142 | 84 | def find_events(self, min=0, max=sys.maxint, limit=0, | ||
143 | 85 | sorting_asc=True, mode="event", filters=(), return_mode=0): | ||
144 | 86 | """ | ||
145 | 87 | Returns all items from the database between the indicated | ||
146 | 88 | timestamps `min' and `max'. Optionally the argument `tags' | ||
147 | 89 | may be used to filter on tags or `mimetypes' to filter on | ||
148 | 90 | mimetypes. | ||
149 | 91 | |||
150 | 92 | Parameter `mode' can be one of "event", "item" or "mostused". | ||
151 | 93 | The first mode returns all events, the second one only returns | ||
152 | 94 | the last event when items are repeated and the "mostused" mode | ||
153 | 95 | is like "item" but returns the results sorted by the number of | ||
154 | 96 | events. | ||
155 | 97 | |||
156 | 98 | Parameter `filters' is an array of structs containing: (text | ||
157 | 99 | to search in the name, text to search in the URI, tags, | ||
158 | 100 | mimetypes, source, content). The filter between characteristics | ||
159 | 101 | inside the same struct is of type AND (all need to match), but | ||
160 | 102 | between diferent structs it is OR-like (only the conditions | ||
161 | 103 | described in one of the structs need to match for the item to | ||
162 | 104 | be returned). | ||
163 | 105 | |||
164 | 106 | Possible values for return_mode, which is an internal variable | ||
165 | 107 | not exposed in the API: | ||
166 | 108 | - 0: Return the events/items. | ||
167 | 109 | - 1: Return the amount of events/items which would be returned. | ||
168 | 110 | - 2: Return only the applications for the matching events. | ||
169 | 111 | """ | ||
170 | 112 | raise NotImplementedError | ||
171 | 113 | |||
172 | 114 | def update_items(self, items): | ||
173 | 115 | raise NotImplementedError | ||
174 | 116 | |||
175 | 117 | def delete_items(self, items): | ||
176 | 118 | raise NotImplementedError | ||
177 | 119 | |||
178 | 120 | def get_types(self): | ||
179 | 121 | """ | ||
180 | 122 | Returns a list of all different types in the database. | ||
181 | 123 | """ | ||
182 | 124 | raise NotImplementedError | ||
183 | 125 | |||
184 | 126 | def get_tags(self, min_timestamp=0, max_timestamp=0, limit=0, name_filter=""): | ||
185 | 127 | """ | ||
186 | 128 | Returns a list containing tuples with the name and the number of | ||
187 | 129 | occurencies of the tags matching `name_filter', or all existing | ||
188 | 130 | tags in case it's empty, sorted from most used to least used. `limit' | ||
189 | 131 | can base used to limit the amount of results. | ||
190 | 132 | |||
191 | 133 | Use `min_timestamp' and `max_timestamp' to limit the time frames you | ||
192 | 134 | want to consider. | ||
193 | 135 | """ | ||
194 | 136 | raise NotImplementedError | ||
195 | 137 | |||
196 | 138 | def get_last_insertion_date(self, application): | ||
197 | 139 | """ | ||
198 | 140 | Returns the timestamp of the last item which was inserted | ||
199 | 141 | related to the given application. If there is no such record, | ||
200 | 142 | 0 is returned. | ||
201 | 143 | """ | ||
202 | 144 | raise NotImplementedError | ||
203 | 0 | 145 | ||
204 | === modified file '_zeitgeist/engine/remote.py' | |||
205 | --- _zeitgeist/engine/remote.py 2009-07-20 17:10:41 +0000 | |||
206 | +++ _zeitgeist/engine/remote.py 2009-07-23 17:37:06 +0000 | |||
207 | @@ -21,7 +21,7 @@ | |||
208 | 21 | import dbus.service | 21 | import dbus.service |
209 | 22 | import logging | 22 | import logging |
210 | 23 | 23 | ||
212 | 24 | from _zeitgeist.engine.engine import get_default_engine | 24 | from _zeitgeist.engine import get_default_engine |
213 | 25 | from zeitgeist.dbusutils import DBusInterface | 25 | from zeitgeist.dbusutils import DBusInterface |
214 | 26 | from _zeitgeist.singleton import SingletonApplication | 26 | from _zeitgeist.singleton import SingletonApplication |
215 | 27 | 27 | ||
216 | 28 | 28 | ||
217 | === renamed file '_zeitgeist/engine/base.py' => '_zeitgeist/engine/storm_base.py' | |||
218 | === renamed file '_zeitgeist/engine/engine.py' => '_zeitgeist/engine/storm_engine.py' | |||
219 | --- _zeitgeist/engine/engine.py 2009-07-29 12:16:43 +0000 | |||
220 | +++ _zeitgeist/engine/storm_engine.py 2009-07-31 06:35:29 +0000 | |||
221 | @@ -25,7 +25,6 @@ | |||
222 | 25 | import sys | 25 | import sys |
223 | 26 | import os | 26 | import os |
224 | 27 | import gettext | 27 | import gettext |
225 | 28 | import gobject | ||
226 | 29 | import logging | 28 | import logging |
227 | 30 | from xdg import BaseDirectory | 29 | from xdg import BaseDirectory |
228 | 31 | from xdg.DesktopEntry import DesktopEntry | 30 | from xdg.DesktopEntry import DesktopEntry |
229 | @@ -34,34 +33,22 @@ | |||
230 | 34 | except ImportError: | 33 | except ImportError: |
231 | 35 | import sqlite3 | 34 | import sqlite3 |
232 | 36 | 35 | ||
235 | 37 | from _zeitgeist.engine.base import * | 36 | from _zeitgeist.engine.storm_base import * |
236 | 38 | from _zeitgeist.lrucache import LRUCache | 37 | from _zeitgeist.engine.engine_base import BaseEngine |
237 | 39 | from zeitgeist.dbusutils import EventDict | 38 | from zeitgeist.dbusutils import EventDict |
238 | 40 | 39 | ||
239 | 41 | logging.basicConfig(level=logging.DEBUG) | 40 | logging.basicConfig(level=logging.DEBUG) |
240 | 42 | log = logging.getLogger("zeitgeist.engine") | 41 | log = logging.getLogger("zeitgeist.engine") |
241 | 43 | 42 | ||
263 | 44 | class ZeitgeistEngine(gobject.GObject): | 43 | class ZeitgeistEngine(BaseEngine): |
264 | 45 | 44 | ||
265 | 46 | ALLOWED_FILTER_KEYS = set(["name", "uri", "tags", "mimetypes", | 45 | def __init__(self, store=None): |
266 | 47 | "source", "content", "application", "bookmarked"]) | 46 | super(ZeitgeistEngine, self).__init__() |
267 | 48 | 47 | if store is not None: | |
268 | 49 | def __init__(self, storm_store): | 48 | self.store = store |
269 | 50 | 49 | else: | |
270 | 51 | gobject.GObject.__init__(self) | 50 | self.store = get_default_store() |
271 | 52 | 51 | assert self.store is not None | |
251 | 53 | assert storm_store is not None | ||
252 | 54 | self.store = storm_store | ||
253 | 55 | self._apps = set() | ||
254 | 56 | self._last_time_from_app = {} | ||
255 | 57 | self._applications = LRUCache(100) | ||
256 | 58 | |||
257 | 59 | ''' | ||
258 | 60 | path = BaseDirectory.save_data_path("zeitgeist") | ||
259 | 61 | database = os.path.join(path, "zeitgeist.sqlite") | ||
260 | 62 | self.connection = self._get_database(database) | ||
261 | 63 | self.cursor = self.connection.cursor() | ||
262 | 64 | ''' | ||
272 | 65 | 52 | ||
273 | 66 | def _get_ids(self, uri, content, source): | 53 | def _get_ids(self, uri, content, source): |
274 | 67 | uri_id = URI.lookup_or_create(uri).id if uri else None | 54 | uri_id = URI.lookup_or_create(uri).id if uri else None |
275 | @@ -203,21 +190,9 @@ | |||
276 | 203 | successfully inserted. If an item fails, that's usually because it | 190 | successfully inserted. If an item fails, that's usually because it |
277 | 204 | already was in the database. | 191 | already was in the database. |
278 | 205 | """ | 192 | """ |
294 | 206 | 193 | result = super(ZeitgeistEngine, self).insert_events(items) | |
295 | 207 | inserted_items = [] | 194 | self.store.commit() |
296 | 208 | 195 | return result | |
282 | 209 | time1 = time.time() | ||
283 | 210 | for item in items: | ||
284 | 211 | # This is always 0 or 1, no need to consider 2 as we don't | ||
285 | 212 | # use the `force' option. | ||
286 | 213 | if self.insert_event(item, commit=False): | ||
287 | 214 | inserted_items.append(item) | ||
288 | 215 | self.store.commit() | ||
289 | 216 | time2 = time.time() | ||
290 | 217 | log.debug("Inserted %s items in %.5f s." % (len(inserted_items), | ||
291 | 218 | time2 - time1)) | ||
292 | 219 | |||
293 | 220 | return inserted_items | ||
297 | 221 | 196 | ||
298 | 222 | def get_item(self, uri): | 197 | def get_item(self, uri): |
299 | 223 | """ Returns basic information about the indicated URI. As we are | 198 | """ Returns basic information about the indicated URI. As we are |
300 | @@ -499,10 +474,3 @@ | |||
301 | 499 | ORDER BY start DESC LIMIT 1 | 474 | ORDER BY start DESC LIMIT 1 |
302 | 500 | """, (application,)).get_one() | 475 | """, (application,)).get_one() |
303 | 501 | return query[0] if query else 0 | 476 | return query[0] if query else 0 |
304 | 502 | |||
305 | 503 | _engine = None | ||
306 | 504 | def get_default_engine(): | ||
307 | 505 | global _engine | ||
308 | 506 | if not _engine: | ||
309 | 507 | _engine = ZeitgeistEngine(get_default_store()) | ||
310 | 508 | return _engine | ||
311 | 509 | 477 | ||
312 | === modified file 'test/benchmarks.py' | |||
313 | --- test/benchmarks.py 2009-07-13 07:43:29 +0000 | |||
314 | +++ test/benchmarks.py 2009-07-23 16:58:55 +0000 | |||
315 | @@ -5,10 +5,10 @@ | |||
316 | 5 | import os | 5 | import os |
317 | 6 | sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..")) | 6 | sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..")) |
318 | 7 | 7 | ||
321 | 8 | from _zeitgeist.engine.base import create_store, set_store | 8 | from _zeitgeist.engine.storm_base import create_store, set_store |
322 | 9 | from _zeitgeist.engine import base | 9 | from _zeitgeist.engine import storm_base as base |
323 | 10 | from zeitgeist.datamodel import * | 10 | from zeitgeist.datamodel import * |
325 | 11 | from _zeitgeist.engine.engine import ZeitgeistEngine | 11 | from _zeitgeist.engine.storm_engine import ZeitgeistEngine |
326 | 12 | 12 | ||
327 | 13 | from time import time | 13 | from time import time |
328 | 14 | import unittest | 14 | import unittest |
329 | 15 | 15 | ||
330 | === modified file 'test/engine-engine-test.py' | |||
331 | --- test/engine-engine-test.py 2009-07-15 21:36:41 +0000 | |||
332 | +++ test/engine-engine-test.py 2009-07-23 16:58:55 +0000 | |||
333 | @@ -5,10 +5,10 @@ | |||
334 | 5 | import os | 5 | import os |
335 | 6 | sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..")) | 6 | sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..")) |
336 | 7 | 7 | ||
339 | 8 | from _zeitgeist.engine.base import create_store, set_store | 8 | from _zeitgeist.engine.storm_base import create_store, set_store |
340 | 9 | from _zeitgeist.engine import base | 9 | from _zeitgeist.engine import storm_base as base |
341 | 10 | from zeitgeist.datamodel import * | 10 | from zeitgeist.datamodel import * |
343 | 11 | from _zeitgeist.engine.engine import ZeitgeistEngine | 11 | from _zeitgeist.engine.storm_engine import ZeitgeistEngine |
344 | 12 | 12 | ||
345 | 13 | import unittest | 13 | import unittest |
346 | 14 | import tempfile | 14 | import tempfile |
347 | 15 | 15 | ||
348 | === modified file 'zeitgeist-daemon' | |||
349 | --- zeitgeist-daemon 2009-07-20 17:10:41 +0000 | |||
350 | +++ zeitgeist-daemon 2009-07-23 17:37:06 +0000 | |||
351 | @@ -33,11 +33,8 @@ | |||
352 | 33 | gettext.install("zeitgeist", _config.localedir, unicode=1) | 33 | gettext.install("zeitgeist", _config.localedir, unicode=1) |
353 | 34 | logging.basicConfig(level=logging.DEBUG) | 34 | logging.basicConfig(level=logging.DEBUG) |
354 | 35 | 35 | ||
355 | 36 | from _zeitgeist.engine.engine import get_default_engine | ||
356 | 37 | from _zeitgeist.engine.remote import RemoteInterface | 36 | from _zeitgeist.engine.remote import RemoteInterface |
357 | 38 | 37 | ||
358 | 39 | engine = get_default_engine() | ||
359 | 40 | |||
360 | 41 | dbus.mainloop.glib.DBusGMainLoop(set_as_default=True) | 38 | dbus.mainloop.glib.DBusGMainLoop(set_as_default=True) |
361 | 42 | mainloop = gobject.MainLoop() | 39 | mainloop = gobject.MainLoop() |
362 | 43 | 40 |
This branch implements the flexible engine framework for zeitgeist, as discussed on IRC recently. engine( ) is now in _zeitgeist.engine instead of _zeitgeist. engine. engine engine( ) either returns the running engine instance, or creates a new one. To get the type of the new engine it looks in the ZEITGEIST_ENGINE environment variable, if not set it uses a hardcoded default, which is storm for now engine/ SOMENAME_ engine. py, where SOMENAME is the type of the engine (like sqlite3, sqlite3_ querymancer, couchdb etc.)
The most important changes are:
* get_default_
* get_default_
* new engine implementations can be added by creating _zeitgeist/
* all engine tests are running using the strom engine implementation