1
# -.- coding: utf-8 -.-
5
# Copyright © 2010 Siegfried-Angel Gevatter Pujals <siegfried@gevatter.com>
6
# Copyright © 2009 Mikkel Kamstrup Erlandsen <mikkel.kamstrup@gmail.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/>.
21
from __future__ import with_statement
23
import cPickle as pickle
28
from zeitgeist.datamodel import get_timestamp_for_now
29
from _zeitgeist.engine.datamodel import Event, DataSource as OrigDataSource
30
from _zeitgeist.engine.extension import Extension
31
from _zeitgeist.engine import constants
33
logging.basicConfig(level=logging.DEBUG)
34
log = logging.getLogger("zeitgeist.datasource_registry")
36
DATA_FILE = os.path.join(constants.DATA_PATH, "datasources.pickle")
37
REGISTRY_DBUS_OBJECT_PATH = "/org/gnome/zeitgeist/data_source_registry"
38
REGISTRY_DBUS_INTERFACE = "org.gnome.zeitgeist.DataSourceRegistry"
39
SIG_FULL_DATASOURCE = "(sssa("+constants.SIG_EVENT+")bxb)"
41
class DataSource(OrigDataSource):
43
def from_list(cls, l):
45
Parse a list into a DataSource, overriding the value of Running
48
s = cls(l[cls.UniqueId], l[cls.Name], l[cls.Description],
49
l[cls.EventTemplates], False, l[cls.LastSeen], l[cls.Enabled])
52
def update_from_data_source(self, source):
53
for prop in (self.Name, self.Description, self.EventTemplates,
54
self.LastSeen, self.Running):
55
self[prop] = source[prop]
57
class DataSourceRegistry(Extension, dbus.service.Object):
59
The Zeitgeist engine maintains a publicly available list of recognized
60
data-sources (components inserting information into Zeitgeist). An
61
option to disable such data-providers is also provided.
63
The data-source registry of the Zeitgeist engine has DBus object path
64
:const:`/org/gnome/zeitgeist/data_source_registry` under the bus name
65
:const:`org.gnome.zeitgeist.DataSourceRegistry`.
67
PUBLIC_METHODS = ["register_data_source", "get_data_sources",
68
"set_data_source_enabled"]
70
def __init__ (self, engine):
72
Extension.__init__(self, engine)
73
dbus.service.Object.__init__(self, dbus.SessionBus(),
74
REGISTRY_DBUS_OBJECT_PATH)
76
if os.path.exists(DATA_FILE):
78
self._registry = [DataSource.from_list(
79
datasource) for datasource in pickle.load(open(DATA_FILE))]
80
log.debug("Loaded data-source data from %s" % DATA_FILE)
82
log.warn("Failed to load data file %s: %s" % (DATA_FILE, e))
85
log.debug("No existing data-source data found.")
89
# Connect to client disconnection signals
90
dbus.SessionBus().add_signal_receiver(self._name_owner_changed,
91
signal_name="NameOwnerChanged",
92
dbus_interface=dbus.BUS_DAEMON_IFACE,
93
arg2="", # only match services with no new owner
96
def _write_to_disk(self):
97
data = [DataSource.get_plain(datasource) for datasource in self._registry]
98
with open(DATA_FILE, "w") as data_file:
99
pickle.dump(data, data_file)
100
#log.debug("Data-source registry update written to disk.")
102
def _get_data_source(self, unique_id):
103
for datasource in self._registry:
104
if datasource[DataSource.UniqueId] == unique_id:
107
def insert_event_hook(self, event, sender):
108
for (unique_id, bus_names) in self._running.iteritems():
109
if sender in bus_names:
110
datasource = self._get_data_source(unique_id)
111
# Update LastSeen time
112
datasource[DataSource.LastSeen] = get_timestamp_for_now()
113
self._write_to_disk()
114
# Check whether the data-source is allowed to insert events
115
if not [DataSource.Enabled]:
120
def register_data_source(self, unique_id, name, description, templates):
121
source = DataSource(str(unique_id), unicode(name), unicode(description),
122
map(Event.new_for_struct, templates))
123
for datasource in self._registry:
124
if datasource == source:
125
datasource.update_from_data_source(source)
126
self.DataSourceRegistered(datasource)
127
return datasource[DataSource.Enabled]
128
self._registry.append(source)
129
self._write_to_disk()
130
self.DataSourceRegistered(source)
134
def get_data_sources(self):
135
return self._registry
138
def set_data_source_enabled(self, unique_id, enabled):
139
datasource = self._get_data_source(unique_id)
142
if datasource[DataSource.Enabled] != enabled:
143
datasource[DataSource.Enabled] = enabled
144
self.DataSourceEnabled(datasource[DataSource.UniqueId], enabled)
147
@dbus.service.method(REGISTRY_DBUS_INTERFACE,
148
in_signature="sssa("+constants.SIG_EVENT+")",
150
sender_keyword="sender")
151
def RegisterDataSource(self, unique_id, name, description, event_templates,
154
Register a data-source as currently running. If the data-source was
155
already in the database, its metadata (name, description and
156
event_templates) are updated.
158
The optional event_templates is purely informational and serves to
159
let data-source management applications and other data-sources know
160
what sort of information you log.
162
:param unique_id: unique ASCII string identifying the data-source
163
:param name: data-source name (may be translated)
164
:param description: data-source description (may be translated)
165
:param event_templates: list of
166
:class:`Event <zeitgeist.datamodel.Event>` templates.
168
if not unique_id in self._running:
169
self._running[unique_id] = [sender]
170
elif sender not in self._running[unique_id]:
171
self._running[unique_id].append(sender)
172
return self.register_data_source(unique_id, name, description,
175
@dbus.service.method(REGISTRY_DBUS_INTERFACE,
177
out_signature="a"+SIG_FULL_DATASOURCE)
178
def GetDataSources(self):
180
Get the list of known data-sources.
183
:class:`DataSource <zeitgeist.datamodel.DataSource>` objects.
185
return self.get_data_sources()
187
@dbus.service.method(REGISTRY_DBUS_INTERFACE,
189
def SetDataSourceEnabled(self, unique_id, enabled):
191
Get a list of data-sources.
193
:param unique_id: unique string identifying a data-source (it's a good
194
idea to use a domain name / email address / etc. as part of the
195
name, to avoid name clashes).
196
:type unique_id: string
197
:param enabled: whether the data-source is to be enabled or disabled
199
:returns: True on success, False if there is no known data-source
200
matching the given ID.
203
return self.set_data_source_enabled(unique_id, enabled)
205
@dbus.service.signal(REGISTRY_DBUS_INTERFACE,
207
def DataSourceEnabled(self, value, enabled):
208
"""This signal is emitted whenever a data-source is enabled or
211
:returns: unique string identifier of a data-source and a bool which
212
is True if it was enabled False if it was disabled.
213
:rtype: struct containing a string and a bool
215
return (value, enabled)
217
@dbus.service.signal(REGISTRY_DBUS_INTERFACE,
218
signature=SIG_FULL_DATASOURCE)
219
def DataSourceRegistered(self, datasource):
220
"""This signal is emitted whenever a data-source registers itself.
222
:returns: the registered data-source
223
:rtype: :class:`DataSource <zeitgeist.datamodel.DataSource>`
227
@dbus.service.signal(REGISTRY_DBUS_INTERFACE,
228
signature=SIG_FULL_DATASOURCE)
229
def DataSourceDisconnected(self, datasource):
230
"""This signal is emitted whenever the last running instance of a
231
data-source disconnects.
233
:returns: the disconnected data-source
234
:rtype: :class:`DataSource <zeitgeist.datamodel.DataSource>`
238
def _name_owner_changed(self, owner, old, new):
240
Cleanup disconnected clients and mark data-sources as not running
241
when no client remains.
243
uid = [uid for uid, ids in self._running.iteritems() if owner in ids]
248
datasource = self._get_data_source(uid)
250
# Update LastSeen time
251
datasource[DataSource.LastSeen] = get_timestamp_for_now()
252
self._write_to_disk()
254
strid = "%s (%s)" % (uid, datasource[DataSource.Name])
255
log.debug("Client disconnected: %s" % strid)
256
if len(self._running[uid]) == 1:
257
log.debug("No remaining client running: %s" % strid)
258
del self._running[uid]
259
datasource[DataSource.Running] = False
260
self.DataSourceDisconnected(datasource)
262
self._running[uid].remove(owner)