10
11
from xdg import BaseDirectory
14
from random import randint
12
16
from zeitgeist import config
13
17
from zeitgeist.shared.zeitgeist_shared import *
18
from zeitgeist.engine.base import *
15
20
class ZeitgeistEngine(gobject.GObject):
17
23
def __init__(self):
19
25
gobject.GObject.__init__(self)
20
26
self.reload_callback = None
22
28
path = BaseDirectory.save_config_path("zeitgeist")
23
29
database = os.path.join(path, "zeitgeist.sqlite")
24
30
self.connection = self._get_database(database)
25
31
self.cursor = self.connection.cursor()
28
def _result2data(self, result, timestamp=0, start=0, end=sys.maxint, app="", usage=""):
30
res = self.cursor.execute(
31
"""SELECT tagid FROM tags WHERE uri = ?""",(result[0],)
34
tags = ",".join([unicode(tag[0]) for tag in res]) or ""
35
def _result2data(self, result, timestamp=0, app="", usage=""):
40
result[6] or "N/A", # type
41
result[5] or "N/A", # mimetype
48
store.find(URI.value, URI.id == result.id), # uri
50
store.find(URI.value, URI.id == Item.id) or "N/A", # type
51
result.mimetype, # mimetype
43
result[2] or "", # comment
50
def get_count_for_item(self, uri, start=0, end=sys.maxint):
51
# Emulate optional arguments for D-Bus
55
query = "SELECT COUNT(uri) FROM timetable where start >=? and end<=? and uri=?"
56
result = self.cursor.execute(query,(start, end, uri,)).fetchone()
59
60
def _ensure_item(self, item, uri_only=False):
61
62
Takes either a Data object or an URI for an item in the
111
116
Returns 0 if there are no items in the database.
115
filter = "WHERE uri=\"%s\"" % uri
119
query = "SELECT * FROM timetable %s LIMIT 1" % filter
121
result = self.cursor.execute(query).fetchone()
127
def insert_item(self, item):
120
def insert_item(self, ritem, commit=True):
129
122
Inserts an item into the database. Returns True on success,
130
123
False otherwise (for example, if the item already is in the
127
if not ritem.has_key("uri") or not ritem["uri"]:
128
print >> sys.stderr, "Discarding item without a URI: %s" % ritem
130
if not ritem.has_key("content") or not ritem["content"]:
131
print >> sys.stderr, "Discarding item without a Content type: %s" % ritem
133
if not ritem.has_key("source") or not ritem["source"]:
134
print >> sys.stderr, "Discarding item without a Source type: %s" % ritem
135
# Insert into timetable
136
self.cursor.execute('INSERT INTO timetable VALUES (?,?,?,?,?,?)',
141
"%d-%s" % (item["timestamp"], item["uri"]),
145
# Insert into data, if it isn't there yet
147
self.cursor.execute('INSERT INTO data VALUES (?,?,?,?,?,?,?)',
155
except sqlite3.IntegrityError:
159
# Add tags into the database
160
for tag in (tag.strip() for tag in item["tags"].split(",") if tag.strip()):
161
self.cursor.execute('INSERT INTO tags VALUES (?,?)',
162
(tag.capitalize(), item["uri"]))
163
except Exception, ex:
164
print "Error inserting tags: %s" % ex
166
if not item["app"] in self._apps:
167
self._apps.add(item["app"])
169
# Add app into the database
170
self.cursor.execute('INSERT INTO app VALUES (?,?)',
172
except Exception, ex:
173
print "Error inserting application: %s" % ex
175
except sqlite3.IntegrityError, ex:
138
# The item may already exist in the db,
139
# so only create it if necessary
140
item = Item.lookup_or_create(ritem["uri"])
141
item.content = Content.lookup_or_create(ritem["content"])
142
item.source = Source.lookup_or_create(ritem["source"])
143
item.text = unicode(ritem["text"])
144
item.mimetype = unicode(ritem["mimetype"])
145
item.icon = unicode(ritem["icon"])
146
item.origin = ritem["origin"]
147
except sqlite3.IntegrityError, ex:
148
traceback.print_exc()
149
print >> sys.stderr, "Failed to insert item:\n%s" % ritem
150
print >> sys.stderr, "Error was: %s" % ex
153
# Store a new event for this
155
e_uri = "zeitgeist://event/"+ritem["use"]+"/"+str(item.id)+"/"+str(ritem["timestamp"]) + "#" + str(self._next_salt())
156
e = Event.lookup_or_create(e_uri)
158
e.start = ritem["timestamp"]
159
e.item.text = u"Activity"
160
e.item.source_id = Source.USER_ACTIVITY.id
161
e.item.content = Content.lookup_or_create(ritem["use"])
163
#FIXME: Lots of info from the applications, try to sort them out here properly
165
app_info = resolve_dot_desktop(ritem["app"])
167
e.app = App.lookup_or_create(ritem["app"])
169
e.app.item.text = unicode(app_info["name"])
170
e.app.item.content = Content.lookup_or_create(app_info["type"])
171
e.app.item.source = Source.lookup_or_create(app_info["exec"])
172
e.app.item.icon = unicode(app_info["icon"])
173
e.app.info = unicode(ritem["app"]) # FIXME: App constructor could parse out appliction name from .desktop file
174
if app_info.has_key("categories") and app_info["categories"].strip() != "":
175
# Iterate over non-empty strings only
176
for tag in filter(lambda t : bool(t), app_info["categories"].split(";")):
178
a_uri = "zeitgeist://tag/%s" % tag
179
a = Annotation.lookup_or_create(a_uri)
180
a.subject = e.app.item
182
a.item.source_id = Source.APPLICATION.id
183
a.item.content_id = Content.TAG.id
186
except sqlite3.IntegrityError, ex:
187
traceback.print_exc()
188
print >> sys.stderr, "Failed to insert event, '%s':\n%s" % (e_uri, ritem)
189
print >> sys.stderr, "Error was: %s" % ex
193
if ritem.has_key("tags") and ritem["tags"].strip() != "":
194
# Iterate over non-empty strings only
195
for tag in filter(lambda t : bool(t), ritem["tags"].split(";")):
197
a_uri = "zeitgeist://tag/%s" % tag
198
a = Annotation.lookup_or_create(a_uri)
201
a.item.source_id = Source.USER_ACTIVITY.id
202
a.item.content_id = Content.TAG.id
205
if ritem.has_key("bookmark") and ritem["bookmark"]:
206
print "BOOKMARK:", ritem["uri"]
207
a_uri = "zeitgeist://bookmark/%s" % ritem["uri"]
208
a = Annotation.lookup_or_create(a_uri)
210
a.item.text = u"Bookmark"
211
a.item.source_id = Source.USER_ACTIVITY.id
212
a.item.content_id = Content.BOOKMARK.id
181
219
def insert_items(self, items):
207
251
may be used to filter on tags.
209
253
func = self._result2data
211
255
# Emulate optional arguments for the D-Bus interface
212
256
if not max: max = sys.maxint
214
# Get a list of all tags
217
for tag in tags.split(","):
218
tagsql.append("""(data.uri LIKE '%%%s%%'
219
OR data.name LIKE '%%%s%%' OR '%s' in
220
(SELECT tags.tagid FROM tags
221
WHERE tags.uri=data.uri))""" % (tag, tag, tag))
222
condition = "(" + " AND ".join(tagsql) + ")"
227
condition += " AND data.mimetype IN (%s)" % \
228
",".join(("\"%s\"" % mime for mime in mimetypes.split(",")))
230
# Loop over all items in the timetable table which are between min and max
232
SELECT start, data.uri, app, usage FROM timetable
233
JOIN data ON (data.uri=timetable.uri)
234
WHERE usage != 'linked' AND start >= ? AND start <= ?
239
res = self.cursor.execute(query, (str(min), str(max))).fetchall()
241
for start, uri, app, usage in res:
242
# Retrieve the item from the data table
243
# TODO: Integrate this into the previous SQL query.
244
item = self.cursor.execute(
245
"""SELECT * FROM data WHERE data.uri=?""", (uri,)).fetchone()
258
for event in store.find(Event, Event.start >= min, Event.start <= max):
261
usage_id = store.find(URI, URI.id == event.item_id).one()
262
usage = store.find(Item.content_id, Item.id == usage_id.id).one()
263
usage = store.find(Content.value, Content.id == usage).one()
265
item = store.find(Item, Item.id == event.subject_id).one()
248
item = func(item, timestamp = start, usage=usage, app=app)
270
pack.append(func(item, timestamp = start, usage=usage, app=app))
253
273
def update_item(self, item):
283
303
self.connection.commit()
285
305
def delete_item(self, item):
286
item_uri = self._ensure_item(item, uri_only=True)
287
self.cursor.execute('DELETE FROM data where uri=?', (item_uri,))
288
self.cursor.execute('DELETE FROM tags where uri=?', (item_uri,))
289
self.connection.commit()
291
308
def _get_tags(self, order_by, count, min, max):
293
310
Private class used to retrive a list of tags according to a
294
311
desired condition (eg., most used tags, recently used tags...).
297
# We simulate optional values in D-Bus; reset the defaults
298
if not count: count = 20
299
if not max: max = sys.maxint
301
# TODO: This is awful.
306
# Get uri's in in time interval sorted desc by time
307
query = """SELECT uri
309
WHERE usage != 'linked'
312
ORDER BY %s DESC""" % order_by
314
for uri in self.cursor.execute(query, (str(int(min)), str(int(max)))).fetchall():
316
# Retrieve the item from the data table:
319
if uris.count(uri) <= 0 and len(tags) < count:
321
uri = self.cursor.execute(
322
"SELECT * FROM data WHERE uri=?", (uri,)).fetchone()
325
res = self.cursor.execute(
326
"""SELECT tagid FROM tags WHERE uri = ?""",
327
(uri[0],)).fetchall()
330
tag = unicode(tag[0])
331
if tags.count(tag) <= 0:
332
if len(tags) < count:
335
if len(tags) == count:
340
315
def get_all_tags(self):
342
317
Returns a list containing the name of all tags.
345
for tag in self.cursor.execute(
346
"SELECT DISTINCT(tagid) FROM tags").fetchall():
347
yield unicode(tag[0])
349
321
def get_types(self):
372
344
return self._get_tags("uri", count, min, max)
374
346
def get_min_timestamp_for_tag(self, tag):
375
res = self.cursor.execute("""
377
(SELECT min(start) FROM timetable WHERE uri=tags.uri)
379
FROM tags WHERE tagid=?
380
ORDER BY value ASC LIMIT 1
381
""", (tag,)).fetchall()
387
349
def get_max_timestamp_for_tag(self, tag):
388
res = self.cursor.execute("""
390
(SELECT max(start) FROM timetable WHERE uri=tags.uri)
392
FROM tags WHERE tagid=?
393
ORDER BY value DESC LIMIT 1
394
""", (tag,)).fetchall()
400
352
def get_timestamps_for_tag(self, tag):
402
begin = self.get_min_timestamp_for_tag(tag)
403
end = self.get_max_timestamp_for_tag(tag)
406
if end - begin > 86400:
407
# TODO: Why do we do this?
414
355
def get_items_related_by_tags(self, item):
416
356
# TODO: Is one matching tag enough or should more/all of them
418
for tag in self._ensure_item(item)[4]:
419
res = self.cursor.execute('SELECT uri FROM tags WHERE tagid=? GROUP BY uri ORDER BY COUNT(uri) DESC', (tag,)).fetchall()
421
item = self.cursor.execute("SELECT * FROM data WHERE uri=?", (raw[0],)).fetchone()
423
yield self._result2data(item)
425
360
def get_related_items(self, uri):
431
radius = 120 #radius is half a day
434
Create pins to create neighbourhoods around
436
results = self.cursor.execute( "SELECT start FROM timetable WHERE uri=? ORDER BY start",
437
( uri, ) ).fetchall()
438
pins = [r[0] for r in results]
441
Determine neighbourhoods
449
info = {"start":start, "pin":p, "end":end, "uri":uri}
452
start = ( p + pins[pins.index( p ) - 1] ) / 2
453
print + "+ " + start + " +"
454
if p - start < radius:
455
info["start"] = start
456
except Exception, ex:
460
end = ( p + pins[pins.index( p ) + 1] ) / 2
463
except Exception, ex:
467
for i in self.get_items(info["start"],info["end"]):
471
self.compare_nbhs(nbhs)
474
get items for each nbh and store in a list in a hash'
476
for item in self.common_items.values():
482
363
def compare_nbhs(self,nbhs):
483
self.common_items = {}
484
for nbh in nbhs.values():
487
for tempnbh in nbhs.values():
488
for tempitem in tempnbh:
489
if tempitem[2] == item[2]:
490
if self.common_items.has_key(item[2]):
491
self.common_items[item[2]][1] +=1
493
self.common_items[item[2]] = [item,1]
495
except Exception, ex:
501
366
def get_items_with_mimetype(self, mimetype, min=0, max=sys.maxint, tags=""):
502
return self.get_items(min, max, tags, mimetype)
504
369
def get_uris_for_timestamp(self, timestamp):
505
return [x[0] for x in
506
self.cursor.execute("SELECT uri FROM timetable WHERE start=?",
507
(timestamp,)).fetchall()]
509
372
def get_bookmarks(self):
510
for item in self.cursor.execute("SELECT * FROM data WHERE boomark=1").fetchall():
511
yield self._result2data(item, timestamp = -1)
513
375
engine = ZeitgeistEngine()