1
# -.- coding: utf-8 -.-
3
# Zeitgeist - Geolocation Extension
5
# Copyright © 2010 Seif Lotfy <seif@lotfy.com>
6
# Copyright © 2010 Siegfried-Angel Gevatter Pujals <siegfried@gevatter.com>
8
# This program is free software: you can redistribute it and/or modify
9
# it under the terms of the GNU Lesser General Public License as published by
10
# the Free Software Foundation, either version 3 of the License, or
11
# (at your option) any later version.
13
# This program is distributed in the hope that it will be useful,
14
# but WITHOUT ANY WARRANTY; without even the implied warranty of
15
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16
# GNU Lesser General Public License for more details.
18
# You should have received a copy of the GNU Lesser General Public License
19
# along with this program. If not, see <http://www.gnu.org/licenses/>.
29
from zeitgeist.datamodel import TimeRange, ResultType
30
from _zeitgeist.engine.sql import get_default_cursor
31
from _zeitgeist.engine import constants
32
from _zeitgeist.engine.datamodel import Event
33
from _zeitgeist.engine.extension import Extension
34
from _zeitgeist.engine import constants
36
GEOLOCATION_DBUS_OBJECT_PATH = "/org/gnome/zeitgeist/geolocation"
37
DATABASE_TABLE_NAME = "_event_location" # "_" prefix for unofficial
39
log = logging.getLogger("zeitgeist.geolocation")
42
""" Get a database connection and ensure all required tables exist. """
44
cursor = get_default_cursor()
47
CREATE TABLE IF NOT EXISTS %s
48
(id INTEGER PRIMARY KEY, min_longitude INTEGER, max_longitude INTEGER,
49
min_latitude INTEGER, max_latitude INTEGER)
50
""" % DATABASE_TABLE_NAME)
54
class Geolocation(Extension, dbus.service.Object):
56
For some workflows it can be practical to identify the location where
57
certain activities were carried out. This Geolocation extension enables
58
Zeitgeist to keep track of the physical location of the computer at the
59
moment when events are inserted.
61
The Geolocation extension for Zeitgeist has DBus object path
62
:const:`/org/gnome/zeitgeist/geolocation` under the bus name
63
:const:`org.gnome.zeitgeist.Geolocation`.
65
PUBLIC_METHODS = ["find_events_for_locations", "find_locations_for_events"]
69
def __init__ (self, engine):
70
Extension.__init__(self, engine)
71
dbus.service.Object.__init__(self, dbus.SessionBus(),
72
GEOLOCATION_DBUS_OBJECT_PATH)
75
self._cursor = create_db()
77
self._location = Geoclue.DiscoverLocation()
79
self._location.connect(self._position_changed_cb)
81
# Choose a working provider
82
# FIXME: Use the Master provider once it is released
83
for provider in [provider["name"] for provider in self._location.get_available_providers()]:
84
if self._location.set_position_provider(provider) and self._get_position():
87
def _get_position(self):
88
position = self._location.get_location_info()
89
if "longitude" in position and "latitude" in position and \
90
position["longitude"] != 0 and position["latitude"] != 0:
91
return (position["longitude"], position["latitude"])
94
def _position_changed_cb(self):
95
self._position = self._get_position()
97
def post_insert_event(self, event, sender):
98
# store location for inserted event
101
self._cursor.execute("""
102
INSERT INTO %s (id, min_longitude, max_longitude, min_latitude, max_latitude)
103
VALUES (?,?,?,?,?)""" % DATABASE_TABLE_NAME, (event.id, self._position[0],
104
self._position[0], self._position[1], self._position[1]))
105
self._cursor.connection.commit()
106
except sqlite3.IntegrityError:
107
# Event already registered
108
# FIXME: Don't check for this anymore once using post-insert hook
110
except Exception, ex:
114
def find_events_for_locations(self, longitude, latitude, radius,
115
time_range, event_templates, storage_state, max_events, order):
117
Accepts 'event_templates' as either a real list of Events or as
118
a list of tuples (event_data, subject_data) as we do in the
126
where = self._engine._build_sql_event_filter(time_range, event_templates,
129
if not where.may_have_results():
132
sql = "SELECT * FROM event_view"
134
if order == ResultType.LeastRecentActor:
137
SELECT actor, min(timestamp) AS timestamp
141
sql += " INNER JOIN %s locations ON (locations.id = event_view.id)" % DATABASE_TABLE_NAME
143
where.add("min_longitude >= ?", longitude - radius)
144
where.add("max_longitude <= ?", longitude + radius)
145
where.add("min_latitude >= ?", latitude - radius)
146
where.add("max_latitude <= ?", latitude + radius)
147
sql += " WHERE " + where.sql
149
sql += (" ORDER BY timestamp DESC",
150
" ORDER BY timestamp ASC",
151
" GROUP BY subj_uri ORDER BY timestamp DESC",
152
" GROUP BY subj_uri ORDER BY timestamp ASC",
153
" GROUP BY subj_uri ORDER BY COUNT(event_view.id) DESC, timestamp DESC",
154
" GROUP BY subj_uri ORDER BY COUNT(event_view.id) ASC, timestamp ASC",
155
" GROUP BY actor ORDER BY COUNT(event_view.id) DESC, timestamp DESC",
156
" GROUP BY actor ORDER BY COUNT(event_view.id) ASC, timestamp ASC",
157
" GROUP BY actor", # implicit: ORDER BY max(timestamp) DESC
158
" ORDER BY timestamp ASC")[order]
161
sql += " LIMIT %d" % max_events
163
result = self._cursor.execute(sql, where.arguments).fetchall()
165
return self._get_events(result)
168
def find_locations_for_events(self, event_ids):
170
Takes a list of event IDs and returns a ordered list with the positions
171
associated to each of them, with the form (min_longitude, max_longitude,
172
min_latitude, max_latitude).
174
If an event ID has no geolocation information, the result for it will
177
sql = "SELECT * FROM %s WHERE id IN (%s)" % (DATABASE_TABLE_NAME,
178
",".join(str(int(_id)) for _id in event_ids))
179
locations = [(0, 0, 0, 0)] * len(event_ids)
180
_func = self._find_position
181
for row in self._cursor.execute(sql).fetchall():
182
locations[_func(event_ids, row["id"])] = (
183
row["min_longitude"], row["max_longitude"], row["min_latitude"],
188
def _find_position(ids, _id):
189
for i, x in enumerate(ids):
192
raise AssertionError, "Whoops! Oh my dear!"
194
def _get_events(self, rows):
197
# Assumption: all rows of a same event for its different
198
# subjects are in consecutive order.
199
event = self._engine._get_event_from_row(row)
200
if event.id not in events:
201
event.min_longitude = row["min_longitude"]
202
event.max_longitude = row["max_longitude"]
203
event.min_latitude = row["min_latitude"]
204
event.max_latitude = row["max_latitude"]
205
events[event.id] = event
206
events[event.id].append_subject(self._engine._get_subject_from_row(row))
207
return list(events.values())
209
def _make_events_sendable(self, events):
211
event._make_dbus_sendable()
212
event.append((event.min_longitude, event.max_longitude,
213
event.min_latitude, event.max_latitude))
216
@dbus.service.method(constants.DBUS_INTERFACE,
218
out_signature="a(dddd)")
219
def FindLocationsForEvents(self, event_ids):
222
return self.find_locations_for_events(event_ids)
224
@dbus.service.method(constants.DBUS_INTERFACE,
225
in_signature="(ddd)(xx)a("+constants.SIG_EVENT+")uuu",
226
out_signature="a("+constants.SIG_EVENT+"(dddd))")
227
def FindEventsForLocations(self, position, time_range, event_templates,
228
storage_state, num_events, result_type):
231
time_range = TimeRange(time_range[0], time_range[1])
232
event_templates = map(Event, event_templates)
233
return self._make_events_sendable(self.find_events_for_locations(
234
position[0], position[1], position[2], time_range, event_templates,
235
storage_state, num_events, result_type))