Logo Search packages:      
Sourcecode: zeitgeist-extensions version File versions  Download package

geolocation.py

# -.- coding: utf-8 -.-

# Zeitgeist - Geolocation Extension
#
# Copyright © 2010 Seif Lotfy <seif@lotfy.com>
# Copyright © 2010 Siegfried-Angel Gevatter Pujals <siegfried@gevatter.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

import os
import dbus
import dbus.service
import sqlite3
import logging
import dbus
import Geoclue

from zeitgeist.datamodel import TimeRange, ResultType
from _zeitgeist.engine.sql import get_default_cursor
from _zeitgeist.engine import constants
from _zeitgeist.engine.datamodel import Event
from _zeitgeist.engine.extension import Extension
from _zeitgeist.engine import constants

GEOLOCATION_DBUS_OBJECT_PATH = "/org/gnome/zeitgeist/geolocation"
DATABASE_TABLE_NAME = "_event_location" # "_" prefix for unofficial

log = logging.getLogger("zeitgeist.geolocation")

def create_db():
    """ Get a database connection and ensure all required tables exist. """

    cursor = get_default_cursor()
    
    cursor.execute("""
        CREATE TABLE IF NOT EXISTS %s
            (id INTEGER PRIMARY KEY, min_longitude INTEGER, max_longitude INTEGER,
            min_latitude INTEGER, max_latitude INTEGER)
        """ % DATABASE_TABLE_NAME)
    
    return cursor

00054 class Geolocation(Extension, dbus.service.Object):
    """
    For some workflows it can be practical to identify the location where
    certain activities were carried out. This Geolocation extension enables
    Zeitgeist to keep track of the physical location of the computer at the
    moment when events are inserted.
    
    The Geolocation extension for Zeitgeist has DBus object path
    :const:`/org/gnome/zeitgeist/geolocation` under the bus name
    :const:`org.gnome.zeitgeist.Geolocation`.
    """
    PUBLIC_METHODS = ["find_events_for_locations", "find_locations_for_events"]

    _position = None

    def __init__ (self, engine):
        Extension.__init__(self, engine)
        dbus.service.Object.__init__(self, dbus.SessionBus(),
            GEOLOCATION_DBUS_OBJECT_PATH)
        
        self._engine = engine
        self._cursor = create_db()

        self._location = Geoclue.DiscoverLocation()
        self._location.init()
        self._location.connect(self._position_changed_cb)
        
        # Choose a working provider
        # FIXME: Use the Master provider once it is released
        for provider in [provider["name"] for provider in self._location.get_available_providers()]:
            if self._location.set_position_provider(provider) and self._get_position():
                break
    
    def _get_position(self):
        position = self._location.get_location_info()
        if "longitude" in position and "latitude" in position and \
        position["longitude"] != 0 and position["latitude"] != 0:
            return (position["longitude"], position["latitude"])
        return None
    
    def _position_changed_cb(self):
        self._position = self._get_position()
    
    def post_insert_event(self, event, sender):
        # store location for inserted event
        if self._position:
            try:
                self._cursor.execute("""
                    INSERT INTO %s (id, min_longitude, max_longitude, min_latitude, max_latitude)
                    VALUES (?,?,?,?,?)""" % DATABASE_TABLE_NAME, (event.id, self._position[0],
                    self._position[0], self._position[1], self._position[1]))
                self._cursor.connection.commit()
            except sqlite3.IntegrityError:
                # Event already registered
                # FIXME: Don't check for this anymore once using post-insert hook
                pass
            except Exception, ex:
                log.debug(ex)
    
    # PUBLIC
00114     def find_events_for_locations(self, longitude, latitude, radius,
        time_range, event_templates, storage_state, max_events, order):
        """
        Accepts 'event_templates' as either a real list of Events or as
        a list of tuples (event_data, subject_data) as we do in the
        DBus API.
        
        Return modes:
         - 0: IDs.
         - 1: Events.
        """
        
        where = self._engine._build_sql_event_filter(time_range, event_templates,
            storage_state)
        
        if not where.may_have_results():
            return []
        
        sql = "SELECT * FROM event_view"
        
        if order == ResultType.LeastRecentActor:
            sql += """
                NATURAL JOIN (
                    SELECT actor, min(timestamp) AS timestamp
                    FROM event_view
                    GROUP BY actor)
                """
        sql += " INNER JOIN %s locations ON (locations.id = event_view.id)" % DATABASE_TABLE_NAME
        
        where.add("min_longitude >= ?", longitude - radius)
        where.add("max_longitude <= ?", longitude + radius)
        where.add("min_latitude >= ?", latitude - radius)
        where.add("max_latitude <= ?", latitude + radius)
        sql += " WHERE " + where.sql
        
        sql += (" ORDER BY timestamp DESC",
            " ORDER BY timestamp ASC",
            " GROUP BY subj_uri ORDER BY timestamp DESC",
            " GROUP BY subj_uri ORDER BY timestamp ASC",
            " GROUP BY subj_uri ORDER BY COUNT(event_view.id) DESC, timestamp DESC",
            " GROUP BY subj_uri ORDER BY COUNT(event_view.id) ASC, timestamp ASC",
            " GROUP BY actor ORDER BY COUNT(event_view.id) DESC, timestamp DESC",
            " GROUP BY actor ORDER BY COUNT(event_view.id) ASC, timestamp ASC",
            " GROUP BY actor", # implicit: ORDER BY max(timestamp) DESC
            " ORDER BY timestamp ASC")[order]
        
        if max_events > 0:
            sql += " LIMIT %d" % max_events
        
        result = self._cursor.execute(sql, where.arguments).fetchall()
        
        return self._get_events(result)
    
    # PUBLIC
00168     def find_locations_for_events(self, event_ids):
        """ 
        Takes a list of event IDs and returns a ordered list with the positions
        associated to each of them, with the form (min_longitude, max_longitude,
        min_latitude, max_latitude).
        
        If an event ID has no geolocation information, the result for it will
        be (0, 0, 0, 0).
        """
        sql =  "SELECT * FROM %s WHERE id IN (%s)" % (DATABASE_TABLE_NAME,
            ",".join(str(int(_id)) for _id in event_ids))
        locations = [(0, 0, 0, 0)] * len(event_ids)
        _func = self._find_position
        for row in self._cursor.execute(sql).fetchall():
            locations[_func(event_ids, row["id"])] = (
                row["min_longitude"], row["max_longitude"], row["min_latitude"],
                row["max_latitude"])
        return locations
    
    @staticmethod
    def _find_position(ids, _id):
        for i, x in enumerate(ids):
            if x == _id:
                return i
        raise AssertionError, "Whoops! Oh my dear!"
    
    def _get_events(self, rows):
        events = {}
        for row in rows:
            # Assumption: all rows of a same event for its different
            # subjects are in consecutive order.
            event = self._engine._get_event_from_row(row)
            if event.id not in events:
                event.min_longitude = row["min_longitude"]
                event.max_longitude = row["max_longitude"]
                event.min_latitude = row["min_latitude"]
                event.max_latitude = row["max_latitude"]
                events[event.id] = event
            events[event.id].append_subject(self._engine._get_subject_from_row(row))
        return list(events.values())
    
    def _make_events_sendable(self, events):
        for event in events:
            event._make_dbus_sendable()
            event.append((event.min_longitude, event.max_longitude,
                event.min_latitude, event.max_latitude))
        return events
    
    @dbus.service.method(constants.DBUS_INTERFACE,
                        in_signature="au",
                        out_signature="a(dddd)")
00219     def FindLocationsForEvents(self, event_ids):
        """
        """
        return self.find_locations_for_events(event_ids)
    
    @dbus.service.method(constants.DBUS_INTERFACE,
                        in_signature="(ddd)(xx)a("+constants.SIG_EVENT+")uuu",
                        out_signature="a("+constants.SIG_EVENT+"(dddd))")
00227     def FindEventsForLocations(self, position, time_range, event_templates,
        storage_state, num_events, result_type):
        """
        """
        time_range = TimeRange(time_range[0], time_range[1])
        event_templates = map(Event, event_templates)
        return self._make_events_sendable(self.find_events_for_locations(
            position[0], position[1], position[2], time_range, event_templates,
            storage_state, num_events, result_type))

Generated by  Doxygen 1.6.0   Back to index