1
# -.- coding: utf-8 -.-
5
# Copyright © 2009 Mikkel Kamstrup Erlandsen <mikkel.kamstrup@gmail.com>
6
# Copyright © 2009 Markus Korn <thekorn@gmx.de>
7
# Copyright © 2009-2010 Seif Lotfy <seif@lotfy.com>
8
# Copyright © 2009-2010 Siegfried-Angel Gevatter Pujals <rainct@ubuntu.com>
10
# This program is free software: you can redistribute it and/or modify
11
# it under the terms of the GNU Lesser General Public License as published by
12
# the Free Software Foundation, either version 3 of the License, or
13
# (at your option) any later version.
15
# This program is distributed in the hope that it will be useful,
16
# but WITHOUT ANY WARRANTY; without even the implied warranty of
17
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18
# GNU Lesser General Public License for more details.
20
# You should have received a copy of the GNU Lesser General Public License
21
# along with this program. If not, see <http://www.gnu.org/licenses/>.
27
gettext.install("zeitgeist", unicode=1)
42
NEGATION_OPERATOR = "!"
46
"""checks if both given arguments are equal"""
50
"""checks if 'x' startswith 'y'"""
51
return x.startswith(y)
53
# next() function is python >= 2.6
57
# workaround this for older python versions
58
_default_next = object()
59
def next(iterator, default=_default_next):
61
return iterator.next()
63
if default is not _default_next:
67
runpath = os.path.dirname(__file__)
69
NEEDS_CHILD_RESOLUTION = set()
71
if not os.path.isfile(os.path.join(runpath, '_config.py.in')):
72
# we are in a global installation
73
# this means we have already parsed zeo.trig into a python file
74
# all we need is to load this python file now
77
# we are using zeitgeist `from the branch` in development mode
78
# in this mode we would like to use the recent version of our
79
# ontology. This is why we parse the ontology to a temporary file
80
# and load it from there
83
def get_timestamp_for_now():
85
Return the current time in milliseconds since the Unix Epoch.
87
return int(time.time() * 1000)
90
class enum_factory(object):
91
"""factory for enums"""
94
def __init__(self, doc):
96
self._id = enum_factory.counter
97
enum_factory.counter += 1
100
class EnumMeta(type):
101
"""Metaclass to register enums in correct order and assign interger
104
def __new__(cls, name, bases, attributes):
106
lambda x: isinstance(x[1], enum_factory), attributes.iteritems()
108
enums = sorted(enums, key=lambda x: x[1]._id)
109
for n, (key, value) in enumerate(enums):
110
attributes[key] = EnumValue(n, value.__doc__)
111
return super(EnumMeta, cls).__new__(cls, name, bases, attributes)
114
class EnumValue(int):
115
"""class which behaves like an int, but has an additional docstring"""
116
def __new__(cls, value, doc=""):
117
obj = super(EnumValue, cls).__new__(EnumValue, value)
118
obj.__doc__ = "%s. ``(Integer value: %i)``" %(doc, obj)
124
def __init__(self, docstring):
125
self.__doc__ = str(docstring)
128
def __getattr__(self, name):
130
return self.__enums[name]
134
def register(self, value, name, docstring):
135
ids = map(int, self.__enums.values())
136
if value in ids or name in self.__enums:
138
self.__enums[name] = EnumValue(value, docstring)
141
def isCamelCase(text):
142
return text and text[0].isupper() and " " not in text
144
def get_name_or_str(obj):
147
except AttributeError:
154
def __new__(cls, name, parent=None, uri=None, display_name=None, doc=None, auto_resolve=True):
155
if not isCamelCase(name):
156
raise ValueError("Naming convention requires symbol name to be CamelCase, got '%s'" %name)
157
return super(Symbol, cls).__new__(Symbol, uri or name)
159
def __init__(self, name, parent=None, uri=None, display_name=None, doc=None, auto_resolve=True):
160
self._children = dict()
161
self._all_children = None
162
self._parents = parent or set() # will be bootstrapped to a dict at module load time
163
assert isinstance(self._parents, set), name
166
self._display_name = display_name
168
_SYMBOLS_BY_URI[uri] = self
171
return "<%s '%s'>" %(get_name_or_str(self), self.uri)
173
def __getattr__(self, name):
174
self._ensure_all_children()
176
return self._all_children[name]
178
for child in self.iter_all_children():
182
return getattr(child, name)
183
except AttributeError:
185
raise AttributeError("'%s' object has no attribute '%s'" %(self.__class__.__name__, name))
187
def __getitem__ (self, uri):
188
return _SYMBOLS_BY_URI[uri]
190
def _ensure_all_children (self):
191
if self._all_children is not None : return
192
self._all_children = dict()
193
for child in self._children.itervalues():
194
child._visit(self._all_children)
196
def _visit (self, dikt):
197
dikt[self.name] = self
198
for child in self._children.itervalues():
202
def find_child_uris_extended (uri):
204
Creates a list of all known child Symbols of `uri`, including
205
`uri` itself in the list. Hence the "extended". If `uri`
206
is unknown a list containing only `uri` is returned.
209
symbol = _SYMBOLS_BY_URI[uri]
210
children = list(symbol.get_all_children())
219
return self._uri or self.name
222
def display_name(self):
223
return self._display_name or ""
231
self._ensure_all_children()
232
return self._all_children.keys()
236
return self._doc or ""
240
return "%s\n\n %s. ``(Display name: '%s')``" %(self.uri, self.doc.rstrip("."), self.display_name)
242
def get_children(self):
244
Returns a list of immediate child symbols
246
return frozenset(self._children.itervalues())
248
def iter_all_children(self):
250
Returns a generator that recursively iterates over all children
253
self._ensure_all_children()
254
return self._all_children.itervalues()
256
def get_all_children(self):
258
Return a read-only set containing all children of this symbol
260
return frozenset(self.iter_all_children())
262
def get_parents(self):
264
Returns a list of immediate parent symbols
266
return frozenset(self._parents.itervalues())
268
def is_child_of (self, parent):
270
Returns True if this symbol is a child of `parent`.
272
if not isinstance (parent, Symbol):
274
parent = _SYMBOLS_BY_URI[parent]
276
# Parent is not a known URI
277
#print 11111111111, self.uri, parent #debug output
278
return self.uri == parent
280
# Invariant: parent is a Symbol
281
if self.uri == parent.uri : return True
283
parent._ensure_all_children()
285
# FIXME: We should really check that child.uri is in there,
286
# but that is not fast with the current code layout
287
return self.name in parent._all_children
290
def uri_is_child_of (child, parent):
292
Returns True if `child` is a child of `parent`. Both `child`
293
and `parent` arguments must be any combination of
294
:class:`Symbol` and/or string.
296
if isinstance (child, basestring):
298
child = _SYMBOLS_BY_URI[child]
300
# Child is not a know URI
301
if isinstance (parent, basestring):
302
return child == parent
303
elif isinstance (parent, Symbol):
304
return child == parent.uri
308
if not isinstance (child, Symbol):
309
raise ValueError("Child argument must be a Symbol or string. Got %s" % type(child))
311
return child.is_child_of(parent)
313
class TimeRange(list):
315
A class that represents a time range with a beginning and an end.
316
The timestamps used are integers representing milliseconds since the
319
By design this class will be automatically transformed to the DBus
322
# Maximal value of our timestamps
323
_max_stamp = 2**63 - 1
325
def __init__ (self, begin, end):
326
super(TimeRange, self).__init__((int(begin), int(end)))
328
def __eq__ (self, other):
329
return self.begin == other.begin and self.end == other.end
332
return "(%s, %s)" % (self.begin, self.end)
337
def set_begin(self, begin):
339
begin = property(get_begin, set_begin,
340
doc="The begining timestamp of this time range")
345
def set_end(self, end):
347
end = property(get_end, set_end,
348
doc="The end timestamp of this time range")
353
Return a :class:`TimeRange` from 0 to the instant of invocation
355
return cls(0, int(time.time() * 1000))
360
Return a :class:`TimeRange` from the instant of invocation to
363
return cls(int(time.time() * 1000), cls._max_stamp)
366
def from_seconds_ago(cls, sec):
368
Return a :class:`TimeRange` ranging from "sec" seconds before
369
the instant of invocation to the same.
371
now = int(time.time() * 1000)
372
return cls(now - (sec * 1000), now)
377
Return a :class:`TimeRange` from 0 (January 1, 1970) to the most
380
return cls(0, cls._max_stamp)
384
Returns True if this time range goes from timestamp 0 (January 1, 1970)
385
-or lower- to the most distant future.
387
return self.begin <= 0 and self.end >= TimeRange._max_stamp
389
def intersect(self, time_range):
391
Return a new :class:`TimeRange` that is the intersection of the
392
two time range intervals. If the intersection is empty this
393
method returns :const:`None`.
395
# Behold the boolean madness!
396
result = TimeRange(0,0)
397
if self.begin < time_range.begin:
398
if self.end < time_range.begin:
401
result.begin = time_range.begin
403
if self.begin > time_range.end:
406
result.begin = self.begin
408
if self.end < time_range.end:
409
if self.end < time_range.begin:
412
result.end = self.end
414
if self.begin > time_range.end:
417
result.end = time_range.end
422
class RelevantResultType(object):
424
An enumeration class used to define how query results should be returned
425
from the Zeitgeist engine.
427
__metaclass__ = EnumMeta
429
Recent = enum_factory("All uris with the most recent uri first")
430
Related = enum_factory("All uris with the most related one first")
435
Represents a subject of an :class:`Event`. This class is both used to
436
represent actual subjects, but also create subject templates to match
437
other subjects against.
439
Applications should normally use the method :meth:`new_for_values` to
450
SUPPORTS_NEGATION = (Uri, Interpretation, Manifestation, Origin, Mimetype)
451
SUPPORTS_WILDCARDS = (Uri, Origin, Mimetype)
453
def __init__(self, data=None):
454
super(Subject, self).__init__([""]*len(Subject.Fields))
456
if len(data) != len(Subject.Fields):
458
"Invalid subject data length %s, expected %s"
459
% (len(data), len(Subject.Fields)))
460
super(Subject, self).__init__(data)
462
super(Subject, self).__init__([""]*len(Subject.Fields))
466
self.__class__.__name__, super(Subject, self).__repr__()
470
def new_for_values (**values):
472
Create a new Subject instance and set its properties according
473
to the keyword arguments passed to this method.
475
:param uri: The URI of the subject. Eg. *file:///tmp/ratpie.txt*
476
:param interpretation: The interpretation type of the subject, given either as a string URI or as a :class:`Interpretation` instance
477
:param manifestation: The manifestation type of the subject, given either as a string URI or as a :class:`Manifestation` instance
478
:param origin: The URI of the location where subject resides or can be found
479
:param mimetype: The mimetype of the subject encoded as a string, if applicable. Eg. *text/plain*.
480
:param text: Free form textual annotation of the subject.
481
:param storage: String identifier for the storage medium of the subject. This should be the UUID of the volume or the string "net" for resources requiring a network interface, and the string "deleted" for subjects that are deleted.
484
for key, value in values.iteritems():
485
if not key in ("uri", "interpretation", "manifestation", "origin",
486
"mimetype", "text", "storage"):
487
raise ValueError("Subject parameter '%s' is not supported" %key)
488
setattr(self, key, value)
492
return self[Subject.Uri]
494
def set_uri(self, value):
495
self[Subject.Uri] = value
496
uri = property(get_uri, set_uri,
497
doc="Read/write property with the URI of the subject encoded as a string")
499
def get_interpretation(self):
500
return self[Subject.Interpretation]
502
def set_interpretation(self, value):
503
self[Subject.Interpretation] = value
504
interpretation = property(get_interpretation, set_interpretation,
505
doc="Read/write property defining the :class:`interpretation type <Interpretation>` of the subject")
507
def get_manifestation(self):
508
return self[Subject.Manifestation]
510
def set_manifestation(self, value):
511
self[Subject.Manifestation] = value
512
manifestation = property(get_manifestation, set_manifestation,
513
doc="Read/write property defining the :class:`manifestation type <Manifestation>` of the subject")
515
def get_origin(self):
516
return self[Subject.Origin]
518
def set_origin(self, value):
519
self[Subject.Origin] = value
520
origin = property(get_origin, set_origin,
521
doc="Read/write property with the URI of the location where the subject can be found. For files this is the parent directory, or for downloaded files it would be the URL of the page where you clicked the download link")
523
def get_mimetype(self):
524
return self[Subject.Mimetype]
526
def set_mimetype(self, value):
527
self[Subject.Mimetype] = value
528
mimetype = property(get_mimetype, set_mimetype,
529
doc="Read/write property containing the mimetype of the subject (encoded as a string) if applicable")
532
return self[Subject.Text]
534
def set_text(self, value):
535
self[Subject.Text] = value
536
text = property(get_text, set_text,
537
doc="Read/write property with a free form textual annotation of the subject")
539
def get_storage(self):
540
return self[Subject.Storage]
542
def set_storage(self, value):
543
self[Subject.Storage] = value
544
storage = property(get_storage, set_storage,
545
doc="Read/write property with a string id of the storage medium where the subject is stored. Fx. the UUID of the disk partition or just the string 'net' for items requiring network interface to be available")
547
def matches_template (self, subject_template):
549
Return True if this Subject matches *subject_template*. Empty
550
fields in the template are treated as wildcards.
551
Interpretations and manifestations are also matched if they are
552
children of the types specified in `subject_template`.
554
See also :meth:`Event.matches_template`
556
for m in Subject.Fields:
557
if not subject_template[m]:
558
# empty fields are handled as wildcards
560
if m == Subject.Storage:
561
# we do not support searching by storage field for now
563
raise ValueError("zeitgeist does not support searching by 'storage' field")
564
elif m in (Subject.Interpretation, Subject.Manifestation):
565
# symbols are treated differently
566
comp = Symbol.uri_is_child_of
569
if not self._check_field_match(m, subject_template[m], comp):
573
def _check_field_match(self, field_id, expression, comp):
574
""" Checks if an expression matches a field given by its `field_id`
575
using a `comp` comparison function """
576
if field_id in self.SUPPORTS_NEGATION \
577
and expression.startswith(NEGATION_OPERATOR):
578
return not self._check_field_match(field_id, expression[len(NEGATION_OPERATOR):], comp)
579
elif field_id in self.SUPPORTS_WILDCARDS \
580
and expression.endswith(WILDCARD):
581
assert comp == EQUAL, "wildcards only work for pure text fields"
582
return self._check_field_match(field_id, expression[:-len(WILDCARD)], STARTSWITH)
584
return comp(self[field_id], expression)
588
Core data structure in the Zeitgeist framework. It is an optimized and
589
convenient representation of an event.
591
This class is designed so that you can pass it directly over
592
DBus using the Python DBus bindings. It will automagically be
593
marshalled with the signature a(asaasay). See also the section
594
on the :ref:`event serialization format <event_serialization_format>`.
596
This class does integer based lookups everywhere and can wrap any
597
conformant data structure without the need for marshalling back and
598
forth between DBus wire format. These two properties makes it highly
599
efficient and is recommended for use everywhere.
607
SUPPORTS_NEGATION = (Interpretation, Manifestation, Actor)
608
SUPPORTS_WILDCARDS = (Actor,)
610
def __init__(self, struct = None):
612
If 'struct' is set it must be a list containing the event
613
metadata in the first position, and optionally the list of
614
subjects in the second position, and again optionally the event
615
payload in the third position.
617
Unless the event metadata contains a timestamp the event will
618
have its timestamp set to "now". Ie. the instant of invocation.
620
The event metadata (struct[0]) will be used as is, and must
621
contain the event data on the positions defined by the
622
Event.Fields enumeration.
624
Likewise each member of the subjects (struct[1]) must be an
625
array with subject metadata defined in the positions as laid
626
out by the Subject.Fields enumeration.
628
On the third position (struct[2]) the struct may contain the
629
event payload, which can be an arbitrary binary blob. The payload
630
will be transfered over DBus with the 'ay' signature (as an
633
super(Event, self).__init__()
636
self.append(struct[0])
639
elif len(struct) == 2:
640
self.append(struct[0])
641
self.append(map(Subject, struct[1]))
643
elif len(struct) == 3:
644
self.append(struct[0])
645
self.append(map(Subject, struct[1]))
646
self.append(struct[2])
648
raise ValueError("Invalid struct length %s" % len(struct))
650
self.extend(([""]* len(Event.Fields), [], ""))
652
# If we have no timestamp just set it to now
653
if not self[0][Event.Timestamp]:
654
self[0][Event.Timestamp] = str(get_timestamp_for_now())
657
def new_for_data(cls, event_data):
659
Create a new Event setting event_data as the backing array
660
behind the event metadata. The contents of the array must
661
contain the event metadata at the positions defined by the
662
Event.Fields enumeration.
665
if len(event_data) != len(cls.Fields):
666
raise ValueError("event_data must have %s members, found %s" % \
667
(len(cls.Fields), len(event_data)))
672
def new_for_struct(cls, struct):
673
"""Returns a new Event instance or None if `struct` is a `NULL_EVENT`"""
674
if struct == NULL_EVENT:
679
def new_for_values(cls, **values):
681
Create a new Event instance from a collection of keyword
685
:param timestamp: Event timestamp in milliseconds since the Unix Epoch
686
:param interpretaion: The Interpretation type of the event
687
:param manifestation: Manifestation type of the event
688
:param actor: The actor (application) that triggered the event
689
:param subjects: A list of :class:`Subject` instances
691
Instead of setting the *subjects* argument one may use a more
692
convenient approach for events that have exactly one Subject.
693
Namely by using the *subject_** keys - mapping directly to their
694
counterparts in :meth:`Subject.new_for_values`:
697
:param subject_interpretation:
698
:param subject_manifestation:
699
:param subject_origin:
700
:param subject_mimetype:
702
:param subject_storage:
708
if not key in ("timestamp", "interpretation", "manifestation",
709
"actor", "subjects", "subject_uri", "subject_interpretation",
710
"subject_manifestation", "subject_origin", "subject_mimetype",
711
"subject_text", "subject_storage"):
712
raise ValueError("Event parameter '%s' is not supported" % key)
714
self.timestamp = values.get("timestamp", self.timestamp)
715
self.interpretation = values.get("interpretation", "")
716
self.manifestation = values.get("manifestation", "")
717
self.actor = values.get("actor", "")
718
self.subjects = values.get("subjects", self.subjects)
720
if self._dict_contains_subject_keys(values):
721
if "subjects" in values:
722
raise ValueError("Subject keys, subject_*, specified together with full subject list")
724
subj.uri = values.get("subject_uri", "")
725
subj.interpretation = values.get("subject_interpretation", "")
726
subj.manifestation = values.get("subject_manifestation", "")
727
subj.origin = values.get("subject_origin", "")
728
subj.mimetype = values.get("subject_mimetype", "")
729
subj.text = values.get("subject_text", "")
730
subj.storage = values.get("subject_storage", "")
731
self.subjects = [subj]
736
def _dict_contains_subject_keys (dikt):
737
if "subject_uri" in dikt : return True
738
elif "subject_interpretation" in dikt : return True
739
elif "subject_manifestation" in dikt : return True
740
elif "subject_origin" in dikt : return True
741
elif "subject_mimetype" in dikt : return True
742
elif "subject_text" in dikt : return True
743
elif "subject_storage" in dikt : return True
748
self.__class__.__name__, super(Event, self).__repr__()
751
def append_subject(self, subject=None):
753
Append a new empty Subject and return a reference to it
757
self.subjects.append(subject)
760
def get_subjects(self):
763
def set_subjects(self, subjects):
765
subjects = property(get_subjects, set_subjects,
766
doc="Read/write property with a list of :class:`Subjects <Subject>`")
769
val = self[0][Event.Id]
770
return int(val) if val else 0
771
id = property(get_id,
772
doc="Read only property containing the the event id if the event has one")
774
def get_timestamp(self):
775
return self[0][Event.Timestamp]
777
def set_timestamp(self, value):
778
self[0][Event.Timestamp] = str(value)
779
timestamp = property(get_timestamp, set_timestamp,
780
doc="Read/write property with the event timestamp defined as milliseconds since the Epoch. By default it is set to the moment of instance creation")
782
def get_interpretation(self):
783
return self[0][Event.Interpretation]
785
def set_interpretation(self, value):
786
self[0][Event.Interpretation] = value
787
interpretation = property(get_interpretation, set_interpretation,
788
doc="Read/write property defining the interpretation type of the event")
790
def get_manifestation(self):
791
return self[0][Event.Manifestation]
793
def set_manifestation(self, value):
794
self[0][Event.Manifestation] = value
795
manifestation = property(get_manifestation, set_manifestation,
796
doc="Read/write property defining the manifestation type of the event")
799
return self[0][Event.Actor]
801
def set_actor(self, value):
802
self[0][Event.Actor] = value
803
actor = property(get_actor, set_actor,
804
doc="Read/write property defining the application or entity responsible for emitting the event. For applications the format of this field is base filename of the corresponding .desktop file with an `app://` URI scheme. For example `/usr/share/applications/firefox.desktop` is encoded as `app://firefox.desktop`")
806
def get_payload(self):
809
def set_payload(self, value):
811
payload = property(get_payload, set_payload,
812
doc="Free form attachment for the event. Transfered over DBus as an array of bytes")
814
def matches_template(self, event_template):
816
Return True if this event matches *event_template*. The
817
matching is done where unset fields in the template is
818
interpreted as wild cards. Interpretations and manifestations
819
are also matched if they are children of the types specified
820
in `event_template`. If the template has more than one
821
subject, this event matches if at least one of the subjects
822
on this event matches any single one of the subjects on the
825
Basically this method mimics the matching behaviour
826
found in the :meth:`FindEventIds` method on the Zeitgeist engine.
828
# We use direct member access to speed things up a bit
829
# First match the raw event data
831
tdata = event_template[0]
832
for m in Event.Fields:
833
if m == Event.Timestamp or not tdata[m]:
834
# matching be timestamp is not supported and
835
# empty template-fields are treated as wildcards
837
if m in (Event.Manifestation, Event.Interpretation):
838
# special check for symbols
839
comp = Symbol.uri_is_child_of
842
if not self._check_field_match(m, tdata[m], comp):
845
# If template has no subjects we have a match
846
if len(event_template[1]) == 0 : return True
848
# Now we check the subjects
849
for tsubj in event_template[1]:
851
if not subj.matches_template(tsubj) : continue
852
# We have a matching subject, all good!
855
# Template has subjects, but we never found a match
858
def _check_field_match(self, field_id, expression, comp):
859
""" Checks if an expression matches a field given by its `field_id`
860
using a `comp` comparison function """
861
if field_id in self.SUPPORTS_NEGATION \
862
and expression.startswith(NEGATION_OPERATOR):
863
return not self._check_field_match(field_id, expression[len(NEGATION_OPERATOR):], comp)
864
elif field_id in self.SUPPORTS_WILDCARDS \
865
and expression.endswith(WILDCARD):
866
assert comp == EQUAL, "wildcards only work for pure text fields"
867
return self._check_field_match(field_id, expression[:-len(WILDCARD)], STARTSWITH)
869
return comp(self[0][field_id], expression)
871
def matches_event (self, event):
873
Interpret *self* as the template an match *event* against it.
874
This method is the dual method of :meth:`matches_template`.
876
return event.matches_template(self)
878
def in_time_range (self, time_range):
880
Check if the event timestamp lies within a :class:`TimeRange`
882
t = int(self.timestamp) # The timestamp may be stored as a string
883
return (t >= time_range.begin) and (t <= time_range.end)
885
class DataSource(list):
886
""" Optimized and convenient data structure representing a datasource.
888
This class is designed so that you can pass it directly over
889
DBus using the Python DBus bindings. It will automagically be
890
marshalled with the signature a(asaasay). See also the section
891
on the :ref:`event serialization format <event_serialization_format>`.
893
This class does integer based lookups everywhere and can wrap any
894
conformant data structure without the need for marshalling back and
895
forth between DBus wire format. These two properties makes it highly
896
efficient and is recommended for use everywhere.
898
This is part of the :const:`org.gnome.zeitgeist.DataSourceRegistry`
906
LastSeen, # last time the data-source did something (connected,
907
# inserted events, disconnected).
910
def get_unique_id(self):
911
return self[self.UniqueId]
913
def set_unique_id(self, value):
914
self[self.UniqueId] = value
917
return self[self.Name]
919
def set_name(self, value):
920
self[self.Name] = value
922
def get_description(self):
923
return self[self.Description]
925
def set_description(self, value):
926
self[self.Description] = value
928
def get_running(self):
929
return self[self.Running]
931
def set_running(self,value):
932
self[self.Running] = value
934
def get_running(self):
935
return self[self.Running]
937
def running(self, value):
938
self[self.Running] = value
940
def get_last_seen(self):
941
return self[self.LastSeen]
943
def set_last_seen(self, value):
944
self[self.LastSeen] = value
946
def get_enabled(self):
947
return self[self.Enabled]
949
def set_enabled(self, value):
950
self[self.Enabled] = value
952
unique_id = property(get_unique_id, set_unique_id)
953
name = property(get_name, set_name)
954
description = property(get_description, set_description)
955
running = property(get_running, set_running)
956
last_seen = property(get_last_seen, set_last_seen)
957
enabled = property(get_enabled, set_enabled)
959
def __init__(self, unique_id, name, description, templates, running=True,
960
last_seen=None, enabled=True):
962
Create a new DataSource object using the given parameters.
964
If you want to instantiate this class from a dbus.Struct, you can
965
use: DataSource(*data_source), where data_source is the dbus.Struct.
967
super(DataSource, self).__init__()
968
self.append(unique_id)
970
self.append(description)
971
self.append(templates)
972
self.append(bool(running))
973
self.append(last_seen if last_seen else get_timestamp_for_now())
976
def __eq__(self, source):
977
return self[self.UniqueId] == source[self.UniqueId]
980
return "%s: %s (%s)" % (self.__class__.__name__, self[self.UniqueId],
985
NULL_EVENT = ([], [], [])
986
"""Minimal Event representation, a tuple containing three empty lists.
987
This `NULL_EVENT` is used by the API to indicate a queried but not
988
available (not found or blocked) Event.
992
class StorageState(object):
994
Enumeration class defining the possible values for the storage state
997
The StorageState enumeration can be used to control whether or not matched
998
events must have their subjects available to the user. Fx. not including
999
deleted files, files on unplugged USB drives, files available only when
1000
a network is available etc.
1002
__metaclass__ = EnumMeta
1004
NotAvailable = enum_factory(("The storage medium of the events "
1005
"subjects must not be available to the user"))
1006
Available = enum_factory(("The storage medium of all event subjects "
1007
"must be immediately available to the user"))
1008
Any = enum_factory("The event subjects may or may not be available")
1011
class ResultType(object):
1013
An enumeration class used to define how query results should be returned
1014
from the Zeitgeist engine.
1016
__metaclass__ = EnumMeta
1018
MostRecentEvents = enum_factory("All events with the most recent events first")
1019
LeastRecentEvents = enum_factory("All events with the oldest ones first")
1020
MostRecentSubjects = enum_factory(("One event for each subject only, "
1021
"ordered with the most recent events first"))
1022
LeastRecentSubjects = enum_factory(("One event for each subject only, "
1023
"ordered with oldest events first"))
1024
MostPopularSubjects = enum_factory(("One event for each subject only, "
1025
"ordered by the popularity of the subject"))
1026
LeastPopularSubjects = enum_factory(("One event for each subject only, "
1027
"ordered ascendingly by popularity of the subject"))
1028
MostPopularActor = enum_factory(("The last event of each different actor,"
1029
"ordered by the popularity of the actor"))
1030
LeastPopularActor = enum_factory(("The last event of each different actor,"
1031
"ordered ascendingly by the popularity of the actor"))
1032
MostRecentActor = enum_factory(("The Actor that has been used to most recently"))
1033
LeastRecentActor = enum_factory(("The Actor that has been used to least recently"))
1034
MostRecentOrigin = enum_factory(("The last event of each different origin"))
1035
LeastRecentOrigin = enum_factory(("The first event of each different origin"))
1036
MostPopularOrigin = enum_factory(("The last event of each different origin,"
1037
"ordered by the popularity of the origins"))
1038
LeastPopularOrigin = enum_factory(("The last event of each different origin,"
1039
"ordered ascendingly by the popularity of the origin"))
1040
OldestActor = enum_factory(("The first event of each different actor"))
1041
MostRecentSubjectInterpretation = enum_factory(("One event for each subject interpretation only, "
1042
"ordered with the most recent events first"))
1043
LeastRecentSubjectInterpretation = enum_factory(("One event for each subject interpretation only, "
1044
"ordered with the least recent events first"))
1045
MostPopularSubjectInterpretation = enum_factory(("One event for each subject interpretation only, "
1046
"ordered by the popularity of the subject interpretation"))
1047
LeastPopularSubjectInterpretation = enum_factory(("One event for each subject interpretation only, "
1048
"ordered ascendingly by popularity of the subject interpretation"))
1049
MostRecentMimeType = enum_factory(("One event for each mimetype only, "
1050
"ordered with the most recent events first"))
1051
LeastRecentMimeType = enum_factory(("One event for each mimetype only, "
1052
"ordered with the least recent events first"))
1053
MostPopularMimeType = enum_factory(("One event for each mimetype only, "
1054
"ordered by the popularity of the mimetype"))
1055
LeastPopularMimeType = enum_factory(("One event for each mimetype only, "
1056
"ordered ascendingly by popularity of the mimetype"))
1059
INTERPRETATION_DOC = \
1060
"""In general terms the *interpretation* of an event or subject is an abstract
1061
description of *"what happened"* or *"what is this"*.
1063
Each interpretation type is uniquely identified by a URI. This class provides
1064
a list of hard coded URI constants for programming convenience. In addition;
1065
each interpretation instance in this class has a *display_name* property, which
1066
is an internationalized string meant for end user display.
1068
The interpretation types listed here are all subclasses of *str* and may be
1069
used anywhere a string would be used.
1071
Interpretations form a hierarchical type tree. So that fx. Audio, Video, and
1072
Image all are sub types of Media. These types again have their own sub types,
1073
like fx. Image has children Icon, Photo, and VectorImage (among others).
1075
Templates match on all sub types, so that a query on subjects with
1076
interpretation Media also match subjects with interpretations
1077
Audio, Photo, and all other sub types of Media.
1080
MANIFESTATION_DOC = \
1081
"""The manifestation type of an event or subject is an abstract classification
1082
of *"how did this happen"* or *"how does this item exist"*.
1084
Each manifestation type is uniquely identified by a URI. This class provides
1085
a list of hard coded URI constants for programming convenience. In addition;
1086
each interpretation instance in this class has a *display_name* property, which
1087
is an internationalized string meant for end user display.
1089
The manifestation types listed here are all subclasses of *str* and may be
1090
used anywhere a string would be used.
1092
Manifestations form a hierarchical type tree. So that fx. ArchiveItem,
1093
Attachment, and RemoteDataObject all are sub types of FileDataObject.
1094
These types can again have their own sub types.
1096
Templates match on all sub types, so that a query on subjects with manifestation
1097
FileDataObject also match subjects of types Attachment or ArchiveItem and all
1098
other sub types of FileDataObject
1101
start_symbols = time.time()
1103
Interpretation = Symbol("Interpretation", doc=INTERPRETATION_DOC)
1104
Manifestation = Symbol("Manifestation", doc=MANIFESTATION_DOC)
1105
_SYMBOLS_BY_URI["Interpretation"] = Interpretation
1106
_SYMBOLS_BY_URI["Manifestation"] = Manifestation
1110
execfile(os.path.join(runpath, "../extra/ontology/zeitgeist.py"))
1112
raise ImportError("Unable to load zeitgeist ontology, "
1113
"please run `make` and try again.")
1115
from zeitgeist import _config
1116
execfile(os.path.join(_config.datadir, "zeitgeist/ontology/zeitgeist.py"))
1119
# Bootstrap the symbol relations. We use a 2-pass strategy:
1121
# 1) Make sure that all parents and children are registered on each symbol
1122
for symbol in _SYMBOLS_BY_URI.itervalues():
1123
for parent in symbol._parents:
1125
_SYMBOLS_BY_URI[parent]._children[symbol.uri] = None
1127
print "ERROR", e, parent, symbol.uri
1129
for child in symbol._children:
1131
_SYMBOLS_BY_URI[child]._parents.add(symbol.uri)
1133
print "ERROR", e, child, symbol.uri
1136
# 2) Resolve all child and parent URIs to their actual Symbol instances
1137
for symbol in _SYMBOLS_BY_URI.itervalues():
1138
for child_uri in symbol._children.iterkeys():
1139
symbol._children[child_uri] = _SYMBOLS_BY_URI[child_uri]
1142
for parent_uri in symbol._parents:
1143
parents[parent_uri] = _SYMBOLS_BY_URI[parent_uri]
1144
symbol._parents = parents
1147
if __name__ == "__main__":
1149
end_symbols = time.time()
1150
print >> sys.stderr, "Import time: %s" % (end_symbols - start_symbols)
1151
#~ x = len(Interpretation.get_all_children())
1152
#~ y = len(Manifestation.get_all_children())
1153
#~ print >> sys.stderr, \
1154
#~ ("Overall number of symbols: %i (man.: %i, int.: %i)" %(x+y, y, x))
1155
#~ print >> sys.stderr, ("Resolved %i symbols, needed %i iterations" %(initial_count, initial_count-c))
1156
#~ print >> sys.stderr, ("Loading symbols took %.4f seconds" %(end_symbols - start_symbols))
1159
#~ EventManifestation = Manifestation.EventManifestation
1160
#~ EventInterpretation = Interpretation.EventInterpretation
1162
#~ DataContainer = Interpretation.DataContainer
1165
#~ print dir(EventManifestation)
1166
#~ print dir(Manifestation)
1167
#~ print EventManifestation.UserActivity
1169
#~ print DataContainer
1170
#~ print DataContainer.Filesystem
1171
#~ print DataContainer.Filesystem.__doc__
1173
#~ print " OR ".join(DataContainer.get_all_children())
1174
#~ print " OR ".join(DataContainer.Filesystem.get_all_children())
1176
#~ print DataContainer.Boo
1178
#~ #Symbol("BOO", DataContainer) #must fail with ValueError
1179
#~ #Symbol("Boo", DataContainer) #must fail with ValueError
1180
#~ Symbol("Foo", set([DataContainer,]))
1181
#~ print DataContainer.Foo
1183
#~ #DataContainer._add_child("booo") #must fail with TypeError
1185
#~ print Interpretation
1186
#~ #print Interpretation.get_all_children()
1188
#~ pprint.pprint(Interpretation.Software.get_all_children())
1190
#~ print Interpretation["http://www.semanticdesktop.org/ontologies/2007/03/22/nfo#MindMap"]