~ubuntu-branches/ubuntu/trusty/zeitgeist-extensions/trusty

« back to all changes in this revision

Viewing changes to geolocation/geolocation.py

  • Committer: Bazaar Package Importer
  • Author(s): Didier Roche
  • Date: 2010-08-05 21:27:04 UTC
  • Revision ID: james.westby@ubuntu.com-20100805212704-o4scyil4j8ewkbn2
Tags: upstream-0.0.3
Import upstream version 0.0.3

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# -.- coding: utf-8 -.-
 
2
 
 
3
# Zeitgeist - Geolocation Extension
 
4
#
 
5
# Copyright © 2010 Seif Lotfy <seif@lotfy.com>
 
6
# Copyright © 2010 Siegfried-Angel Gevatter Pujals <siegfried@gevatter.com>
 
7
#
 
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.
 
12
#
 
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.
 
17
#
 
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/>.
 
20
 
 
21
import os
 
22
import dbus
 
23
import dbus.service
 
24
import sqlite3
 
25
import logging
 
26
import dbus
 
27
import Geoclue
 
28
 
 
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
 
35
 
 
36
GEOLOCATION_DBUS_OBJECT_PATH = "/org/gnome/zeitgeist/geolocation"
 
37
DATABASE_TABLE_NAME = "_event_location" # "_" prefix for unofficial
 
38
 
 
39
log = logging.getLogger("zeitgeist.geolocation")
 
40
 
 
41
def create_db():
 
42
    """ Get a database connection and ensure all required tables exist. """
 
43
 
 
44
    cursor = get_default_cursor()
 
45
    
 
46
    cursor.execute("""
 
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)
 
51
    
 
52
    return cursor
 
53
 
 
54
class Geolocation(Extension, dbus.service.Object):
 
55
    """
 
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.
 
60
    
 
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`.
 
64
    """
 
65
    PUBLIC_METHODS = ["find_events_for_locations", "find_locations_for_events"]
 
66
 
 
67
    _position = None
 
68
 
 
69
    def __init__ (self, engine):
 
70
        Extension.__init__(self, engine)
 
71
        dbus.service.Object.__init__(self, dbus.SessionBus(),
 
72
            GEOLOCATION_DBUS_OBJECT_PATH)
 
73
        
 
74
        self._engine = engine
 
75
        self._cursor = create_db()
 
76
 
 
77
        self._location = Geoclue.DiscoverLocation()
 
78
        self._location.init()
 
79
        self._location.connect(self._position_changed_cb)
 
80
        
 
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():
 
85
                break
 
86
    
 
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"])
 
92
        return None
 
93
    
 
94
    def _position_changed_cb(self):
 
95
        self._position = self._get_position()
 
96
    
 
97
    def post_insert_event(self, event, sender):
 
98
        # store location for inserted event
 
99
        if self._position:
 
100
            try:
 
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
 
109
                pass
 
110
            except Exception, ex:
 
111
                log.debug(ex)
 
112
    
 
113
    # PUBLIC
 
114
    def find_events_for_locations(self, longitude, latitude, radius,
 
115
        time_range, event_templates, storage_state, max_events, order):
 
116
        """
 
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
 
119
        DBus API.
 
120
        
 
121
        Return modes:
 
122
         - 0: IDs.
 
123
         - 1: Events.
 
124
        """
 
125
        
 
126
        where = self._engine._build_sql_event_filter(time_range, event_templates,
 
127
            storage_state)
 
128
        
 
129
        if not where.may_have_results():
 
130
            return []
 
131
        
 
132
        sql = "SELECT * FROM event_view"
 
133
        
 
134
        if order == ResultType.LeastRecentActor:
 
135
            sql += """
 
136
                NATURAL JOIN (
 
137
                    SELECT actor, min(timestamp) AS timestamp
 
138
                    FROM event_view
 
139
                    GROUP BY actor)
 
140
                """
 
141
        sql += " INNER JOIN %s locations ON (locations.id = event_view.id)" % DATABASE_TABLE_NAME
 
142
        
 
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
 
148
        
 
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]
 
159
        
 
160
        if max_events > 0:
 
161
            sql += " LIMIT %d" % max_events
 
162
        
 
163
        result = self._cursor.execute(sql, where.arguments).fetchall()
 
164
        
 
165
        return self._get_events(result)
 
166
    
 
167
    # PUBLIC
 
168
    def find_locations_for_events(self, event_ids):
 
169
        """ 
 
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).
 
173
        
 
174
        If an event ID has no geolocation information, the result for it will
 
175
        be (0, 0, 0, 0).
 
176
        """
 
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"],
 
184
                row["max_latitude"])
 
185
        return locations
 
186
    
 
187
    @staticmethod
 
188
    def _find_position(ids, _id):
 
189
        for i, x in enumerate(ids):
 
190
            if x == _id:
 
191
                return i
 
192
        raise AssertionError, "Whoops! Oh my dear!"
 
193
    
 
194
    def _get_events(self, rows):
 
195
        events = {}
 
196
        for row in 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())
 
208
    
 
209
    def _make_events_sendable(self, events):
 
210
        for event in events:
 
211
            event._make_dbus_sendable()
 
212
            event.append((event.min_longitude, event.max_longitude,
 
213
                event.min_latitude, event.max_latitude))
 
214
        return events
 
215
    
 
216
    @dbus.service.method(constants.DBUS_INTERFACE,
 
217
                        in_signature="au",
 
218
                        out_signature="a(dddd)")
 
219
    def FindLocationsForEvents(self, event_ids):
 
220
        """
 
221
        """
 
222
        return self.find_locations_for_events(event_ids)
 
223
    
 
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):
 
229
        """
 
230
        """
 
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))