20
20
# You should have received a copy of the GNU Lesser General Public License
21
21
# along with this program. If not, see <http://www.gnu.org/licenses/>.
24
This module provides the abstract datamodel used by the Zeitgeist framework.
25
In addition to providing useful constructs for dealing with the Zeitgeist data
26
it also defines symbolic values for the common item types. Using symbolic values
27
instead of URI strings will help detect programmer typos.
32
27
gettext.install("zeitgeist", unicode=1)
41
# next() function is python >= 2.6
45
# workaround this for older python versions
46
_default_next = object()
47
def next(iterator, default=_default_next):
49
return iterator.next()
51
if default is not _default_next:
55
runpath = os.path.dirname(__file__)
57
NEEDS_CHILD_RESOLUTION = set()
59
if not os.path.isfile(os.path.join(runpath, '_config.py.in')):
60
# we are in a global installation
61
# this means we have already parsed zeo.trig into a python file
62
# all we need is to load this python file now
65
# we are using zeitgeist `from the branch` in development mode
66
# in this mode we would like to use the recent version of our
67
# ontology. This is why we parse the ontology to a temporary file
68
# and load it from there
34
71
def get_timestamp_for_now():
36
73
Return the current time in milliseconds since the Unix Epoch.
38
75
return int(time.time() * 1000)
78
class enum_factory(object):
79
"""factory for enums"""
82
def __init__(self, doc):
84
self._id = enum_factory.counter
85
enum_factory.counter += 1
89
"""Metaclass to register enums in correct order and assign interger
92
def __new__(cls, name, bases, attributes):
94
lambda x: isinstance(x[1], enum_factory), attributes.iteritems()
96
enums = sorted(enums, key=lambda x: x[1]._id)
97
for n, (key, value) in enumerate(enums):
98
attributes[key] = EnumValue(n, value.__doc__)
99
return super(EnumMeta, cls).__new__(cls, name, bases, attributes)
102
class EnumValue(int):
103
"""class which behaves like an int, but has an additional docstring"""
104
def __new__(cls, value, doc=""):
105
obj = super(EnumValue, cls).__new__(EnumValue, value)
106
obj.__doc__ = "%s. ``(Integer value: %i)``" %(doc, obj)
112
def __init__(self, docstring):
113
self.__doc__ = str(docstring)
116
def __getattr__(self, name):
118
return self.__enums[name]
122
def register(self, value, name, docstring):
123
ids = map(int, self.__enums.values())
124
if value in ids or name in self.__enums:
126
self.__enums[name] = EnumValue(value, docstring)
129
def isCamelCase(text):
130
return text and text[0].isupper() and " " not in text
132
def get_name_or_str(obj):
135
except AttributeError:
40
140
class Symbol(str):
42
"""Immutable string-like object representing a Symbol
43
Zeitgeist uses Symbols when defining Manifestations and
47
def __new__(cls, symbol_type, name, uri=None, display_name=None, doc=None):
48
obj = super(Symbol, cls).__new__(Symbol, uri or name)
49
obj.__type = symbol_type
52
obj.__display_name = display_name
142
def __new__(cls, name, parent=None, uri=None, display_name=None, doc=None, auto_resolve=True):
143
if not isCamelCase(name):
144
raise ValueError("Naming convention requires symbol name to be CamelCase, got '%s'" %name)
145
return super(Symbol, cls).__new__(Symbol, uri or name)
147
def __init__(self, name, parent=None, uri=None, display_name=None, doc=None, auto_resolve=True):
148
self._children = dict()
149
self._all_children = None
150
self._parents = parent or set() # will be bootstrapped to a dict at module load time
151
assert isinstance(self._parents, set), name
154
self._display_name = display_name
156
_SYMBOLS_BY_URI[uri] = self
56
158
def __repr__(self):
57
return "<%s %r>" %(self.__type, self.uri)
159
return "<%s '%s'>" %(get_name_or_str(self), self.uri)
161
def __getattr__(self, name):
162
self._ensure_all_children()
164
return self._all_children[name]
166
for child in self.iter_all_children():
170
return getattr(child, name)
171
except AttributeError:
173
raise AttributeError("'%s' object has no attribute '%s'" %(self.__class__.__name__, name))
175
def _ensure_all_children (self):
176
if self._all_children is not None : return
177
self._all_children = dict()
178
for child in self._children.itervalues():
179
child._visit(self._all_children)
181
def _visit (self, dikt):
182
dikt[self.name] = self
183
for child in self._children.itervalues():
61
return self.__uri or self.name
188
return self._uri or self.name
64
191
def display_name(self):
65
return self.__display_name or ""
192
return self._display_name or ""
200
self._ensure_all_children()
201
return self._all_children.keys()
73
return self.__doc or ""
205
return self._doc or ""
76
208
def __doc__(self):
77
209
return "%s\n\n %s. ``(Display name: '%s')``" %(self.uri, self.doc.rstrip("."), self.display_name)
80
class SymbolCollection(object):
82
def __init__(self, name, doc=""):
84
self.__doc__ = str(doc)
87
def register(self, name, uri, display_name, doc):
88
if name in self.__symbols:
89
raise ValueError("Cannot register symbol %r, a definition for "
90
"this symbol already exists" % name)
91
if not name.isupper():
92
raise ValueError("Cannot register %r, name must be uppercase" %name)
93
self.__symbols[name] = Symbol(self.__name__, name, uri, display_name, doc)
96
return len(self.__symbols)
98
def __getattr__(self, name):
99
if not name in self.__symbols:
100
if not name.isupper():
101
# Symbols must be upper-case
102
raise AttributeError("%s has no attribute '%s'" % (
103
self.__name__, name))
104
print "Unrecognized %s: %s" % (self.__name__, name)
105
self.__symbols[name] = Symbol(self.__name__, name)
106
return self.__symbols[name]
108
def __getitem__(self, uri):
109
""" Get a symbol by its URI. """
110
symbol = [s for s in self.__symbols.values() if s.uri == uri]
113
raise KeyError("Could not find symbol for URI: %s" % uri)
116
return self.__symbols.itervalues()
119
return self.__symbols.keys()
122
INTERPRETATION_ID = "interpretation"
123
MANIFESTATION_ID = "manifestation"
125
INTERPRETATION_DOC = \
126
"""In general terms the *interpretation* of an event or subject is an abstract
127
description of *"what happened"* or *"what is this"*.
129
Each interpretation type is uniquely identified by a URI. This class provides
130
a list of hard coded URI constants for programming convenience. In addition;
131
each interpretation instance in this class has a *display_name* property, which
132
is an internationalized string meant for end user display.
134
The interpretation types listed here are all subclasses of *str* and may be
135
used anywhere a string would be used."""
137
MANIFESTATION_DOC = \
138
"""The manifestation type of an event or subject is an abstract classification
139
of *"how did this happen"* or *"how does this item exist"*.
141
Each manifestation type is uniquely identified by a URI. This class provides
142
a list of hard coded URI constants for programming convenience. In addition;
143
each interpretation instance in this class has a *display_name* property, which
144
is an internationalized string meant for end user display.
146
The manifestation types listed here are all subclasses of *str* and may be
147
used anywhere a string would be used."""
150
Interpretation = SymbolCollection(INTERPRETATION_ID, doc=INTERPRETATION_DOC)
151
Manifestation = SymbolCollection(MANIFESTATION_ID, doc=MANIFESTATION_DOC)
154
# Interpretation categories
156
Interpretation.register(
158
u"http://www.semanticdesktop.org/ontologies/2007/08/15/nao#Tag",
159
display_name=_("Tags"),
160
doc="User provided tags. The same tag may refer multiple items"
162
Interpretation.register(
164
u"http://www.semanticdesktop.org/ontologies/nfo/#Bookmark",
165
display_name=_("Bookmarks"),
166
doc="A user defined bookmark. The same bookmark may only refer exectly one item"
168
Interpretation.register(
170
u"http://www.semanticdesktop.org/ontologies/2007/01/19/nie/#comment",
171
display_name=_("Comments"),
172
doc="User provided comment"
174
Interpretation.register(
176
u"http://www.semanticdesktop.org/ontologies/2007/03/22/nfo/#Document",
177
display_name=_("Documents"),
178
doc="A document, presentation, spreadsheet, or other content centric item"
180
Interpretation.register(
182
u"http://www.semanticdesktop.org/ontologies/2007/03/22/nfo/#ManifestationCode",
183
display_name=_("Source Code"),
184
doc="Code in a compilable or interpreted programming language."
186
Interpretation.register(
188
u"http://www.semanticdesktop.org/ontologies/2007/03/22/nfo/#Image",
189
display_name=_("Images"),
190
doc="A photography, painting, or other digital image"
192
Interpretation.register(
194
u"http://www.semanticdesktop.org/ontologies/2007/03/22/nfo/#Video",
195
display_name=_("Videos"),
196
doc="Any form of digital video, streaming and non-streaming alike"
198
Interpretation.register(
200
u"http://www.semanticdesktop.org/ontologies/2007/03/22/nfo/#Audio",
201
display_name=_("Music"),
202
doc="Digital music or other creative audio work"
204
Interpretation.register(
206
u"http://www.semanticdesktop.org/ontologies/2007/03/22/nmo/#Email",
207
display_name=_("Email"),
208
doc="An email is an email is an email"
210
Interpretation.register(
212
u"http://www.semanticdesktop.org/ontologies/2007/03/22/nmo/#IMMessage",
213
display_name=_("Messages"),
214
doc="A message received from an instant messaging service"
216
Interpretation.register(
218
u"http://www.tracker-project.org/temp/mfo#FeedMessage",
219
display_name=_("Feeds"),
220
doc="Any syndicated item, RSS, Atom, or other"
222
Interpretation.register(
224
u"http://zeitgeist-project.com/schema/1.0/core#BroadcastMessage",
225
display_name=_("Broadcasts"), # FIXME: better display name
226
doc="Small broadcasted message, like Twitter/Identica micro blogging (TBD in tracker)"
228
Interpretation.register(
230
u"http://zeitgeist-project.com/schema/1.0/core#CreateEvent",
231
display_name=_("Created"),
232
doc="Event type triggered when an item is created"
234
Interpretation.register(
236
u"http://zeitgeist-project.com/schema/1.0/core#ModifyEvent",
237
display_name=_("Modified"),
238
doc="Event type triggered when an item is modified"
240
Interpretation.register(
242
u"http://zeitgeist-project.com/schema/1.0/core#VisitEvent",
243
display_name=_("Visited"),
244
doc="Event type triggered when an item is visited or opened"
246
Interpretation.register(
248
u"http://zeitgeist-project.com/schema/1.0/core#OpenEvent",
249
display_name=_("Opened"),
250
doc="Event type triggered when an item is visited or opened"
252
Interpretation.register(
254
u"http://zeitgeist-project.com/schema/1.0/core#SaveEvent",
255
display_name=_("Saved"),
256
doc="Event type triggered when an item is saved"
258
Interpretation.register(
260
u"http://zeitgeist-project.com/schema/1.0/core#CloseEvent",
261
display_name=_("Closed"),
262
doc="Event type triggered when an item is closed"
264
Interpretation.register(
266
u"http://zeitgeist-project.com/schema/1.0/core#SendEvent",
267
display_name=_("Send"),
268
doc="Event type triggered when the user sends/emails an item or message to a remote host"
270
Interpretation.register(
272
u"http://zeitgeist-project.com/schema/1.0/core#ReceiveEvent",
273
display_name=_("Received"),
274
doc="Event type triggered when the user has received an item from a remote host"
276
Interpretation.register(
278
u"http://zeitgeist-project.com/schema/1.0/core#FocusEvent",
279
display_name=_("Focused"),
280
doc="Event type triggered when the user has switched focus to a new item"
282
Interpretation.register(
284
u"http://zeitgeist-project.com/schema/1.0/core#WarnEvent",
285
display_name=_("Warnings"),
286
doc="Event type triggered when the user is warned about something"
288
Interpretation.register(
290
"http://zeitgeist-project.com/schema/1.0/core#ErrorEvent",
291
display_name=_("Errors"),
292
doc="Event type triggered when the user has encountered an error"
294
Interpretation.register(
296
u"http://www.semanticdesktop.org/ontologies/2007/03/22/nfo#Application",
297
display_name=_("Applications"),
298
doc="An item that is a launchable application. The item's URI must point to the relevant .desktop file"
300
Interpretation.register(
302
u"http://zeitgeist-project.com/schema/1.0/core#UnknownInterpretation",
303
display_name=_("Unknown"),
304
doc="An entity with an unknown interpretation"
308
# Manifestation categories
310
Manifestation.register(
312
u"http://www.semanticdesktop.org/ontologies/2007/03/22/nfo#WebHistory",
313
display_name=_("Web History"),
314
doc="An item that has been extracted from the user's browsing history"
316
Manifestation.register(
318
u"http://zeitgeist-project.com/schema/1.0/core#UserActivity",
319
display_name=_("Activities"),
320
doc="An item that has been created solely on the basis of user actions and is not otherwise stored in some physical location"
322
Manifestation.register(
323
"HEURISTIC_ACTIVITY",
324
u"http://zeitgeist-project.com/schema/1.0/core#HeuristicActivity",
325
display_name=_("Activities"),
326
doc="An application has calculated via heuristics that some relationship is very probable."
328
Manifestation.register(
329
"SCHEDULED_ACTIVITY",
330
u"http://zeitgeist-project.com/schema/1.0/core#ScheduledActivity",
331
display_name=_("Activities"), # FIXME: Is this a bad name?
332
doc="An event that has been triggered by some long running task activated by the user. Fx. playing a song from a playlist"
334
Manifestation.register(
336
u"http://zeitgeist-project.com/schema/1.0/core#UserNotification",
337
display_name=_("Notifications"),
338
doc="An item that has been send as a notification to the user"
340
Manifestation.register(
342
u"http://www.semanticdesktop.org/ontologies/nfo/#FileDataObject",
343
display_name=_("Files"),
344
doc="An item stored on the local filesystem"
346
Manifestation.register(
348
u"http://freedesktop.org/standards/xesam/1.0/core#SystemRessource",
349
display_name=_("System Resources"),
350
doc="An item available through the host operating system, such as an installed application or manual page (TBD in tracker)"
352
Manifestation.register(
354
u"http://zeitgeist-project.com/schema/1.0/core#UnknownManifestation",
355
display_name=_("Unknown"),
356
doc="An entity with an unknown manifestation"
211
def get_children(self):
213
Returns a list of immediate child symbols
215
return frozenset(self._children.itervalues())
217
def iter_all_children(self):
219
Returns a generator that recursively iterates over all children
223
for child in self._children.itervalues():
224
for sub_child in child.iter_all_children():
227
def get_all_children(self):
229
Return a read-only set containing all children of this symbol
231
return frozenset(self.iter_all_children())
233
def get_parents(self):
235
Returns a list of immediate parent symbols
237
return frozenset(self._parents.itervalues())
359
240
class TimeRange(list):
361
242
A class that represents a time range with a beginning and an end.
461
class enum_factory(object):
462
"""factory for enums"""
465
def __init__(self, doc):
467
self._id = enum_factory.counter
468
enum_factory.counter += 1
471
class EnumValue(int):
472
"""class which behaves like an int, but has an additional docstring"""
473
def __new__(cls, value, doc=""):
474
obj = super(EnumValue, cls).__new__(EnumValue, value)
475
obj.__doc__ = "%s. ``(Integer value: %i)``" %(doc, obj)
479
class EnumMeta(type):
480
"""Metaclass to register enums in correct order and assign interger
483
def __new__(cls, name, bases, attributes):
485
lambda x: isinstance(x[1], enum_factory), attributes.iteritems()
487
enums = sorted(enums, key=lambda x: x[1]._id)
488
for n, (key, value) in enumerate(enums):
489
attributes[key] = EnumValue(n, value.__doc__)
490
return super(EnumMeta, cls).__new__(cls, name, bases, attributes)
493
class StorageState(object):
495
Enumeration class defining the possible values for the storage state
498
The StorageState enumeration can be used to control whether or not matched
499
events must have their subjects available to the user. Fx. not including
500
deleted files, files on unplugged USB drives, files available only when
501
a network is available etc.
503
__metaclass__ = EnumMeta
505
NotAvailable = enum_factory(("The storage medium of the events "
506
"subjects must not be available to the user"))
507
Available = enum_factory(("The storage medium of all event subjects "
508
"must be immediately available to the user"))
509
Any = enum_factory("The event subjects may or may not be available")
512
class ResultType(object):
514
An enumeration class used to define how query results should be returned
515
from the Zeitgeist engine.
517
__metaclass__ = EnumMeta
519
MostRecentEvents = enum_factory("All events with the most recent events first")
520
LeastRecentEvents = enum_factory("All events with the oldest ones first")
521
MostRecentSubjects = enum_factory(("One event for each subject only, "
522
"ordered with the most recent events first"))
523
LeastRecentSubjects = enum_factory(("One event for each subject only, "
524
"ordered with oldest events first"))
525
MostPopularSubjects = enum_factory(("One event for each subject only, "
526
"ordered by the popularity of the subject"))
527
LeastPopularSubjects = enum_factory(("One event for each subject only, "
528
"ordered ascendently by popularity"))
529
MostPopularActor = enum_factory(("The last event of each different actor,"
530
"ordered by the popularity of the actor"))
531
LeastPopularActor = enum_factory(("The last event of each different actor,"
532
"ordered ascendently by the popularity of the actor"))
533
MostRecentActor = enum_factory(("The last event of each different actor"))
534
LeastRecentActor = enum_factory(("The first event of each different actor"))
536
342
class RelevantResultType(object):
538
344
An enumeration class used to define how query results should be returned
982
789
This `NULL_EVENT` is used by the API to indicate a queried but not
983
790
available (not found or blocked) Event.
794
class StorageState(object):
796
Enumeration class defining the possible values for the storage state
799
The StorageState enumeration can be used to control whether or not matched
800
events must have their subjects available to the user. Fx. not including
801
deleted files, files on unplugged USB drives, files available only when
802
a network is available etc.
804
__metaclass__ = EnumMeta
806
NotAvailable = enum_factory(("The storage medium of the events "
807
"subjects must not be available to the user"))
808
Available = enum_factory(("The storage medium of all event subjects "
809
"must be immediately available to the user"))
810
Any = enum_factory("The event subjects may or may not be available")
813
class ResultType(object):
815
An enumeration class used to define how query results should be returned
816
from the Zeitgeist engine.
818
__metaclass__ = EnumMeta
820
MostRecentEvents = enum_factory("All events with the most recent events first")
821
LeastRecentEvents = enum_factory("All events with the oldest ones first")
822
MostRecentSubjects = enum_factory(("One event for each subject only, "
823
"ordered with the most recent events first"))
824
LeastRecentSubjects = enum_factory(("One event for each subject only, "
825
"ordered with oldest events first"))
826
MostPopularSubjects = enum_factory(("One event for each subject only, "
827
"ordered by the popularity of the subject"))
828
LeastPopularSubjects = enum_factory(("One event for each subject only, "
829
"ordered ascendently by popularity"))
830
MostPopularActor = enum_factory(("The last event of each different actor,"
831
"ordered by the popularity of the actor"))
832
LeastPopularActor = enum_factory(("The last event of each different actor,"
833
"ordered ascendently by the popularity of the actor"))
834
MostRecentActor = enum_factory(("The last event of each different actor"))
835
LeastRecentActor = enum_factory(("The first event of each different actor"))
838
INTERPRETATION_DOC = \
839
"""In general terms the *interpretation* of an event or subject is an abstract
840
description of *"what happened"* or *"what is this"*.
842
Each interpretation type is uniquely identified by a URI. This class provides
843
a list of hard coded URI constants for programming convenience. In addition;
844
each interpretation instance in this class has a *display_name* property, which
845
is an internationalized string meant for end user display.
847
The interpretation types listed here are all subclasses of *str* and may be
848
used anywhere a string would be used.
850
Interpretations form a hierarchical type tree. So that fx. Audio, Video, and
851
Image all are sub types of Media. These types again have their own sub types,
852
like fx. Image has children Icon, Photo, and VectorImage (among others).
854
Templates match on all sub types, so that a query on subjects with
855
interpretation Media also match subjects with interpretations
856
Audio, Photo, and all other sub types of Media.
859
MANIFESTATION_DOC = \
860
"""The manifestation type of an event or subject is an abstract classification
861
of *"how did this happen"* or *"how does this item exist"*.
863
Each manifestation type is uniquely identified by a URI. This class provides
864
a list of hard coded URI constants for programming convenience. In addition;
865
each interpretation instance in this class has a *display_name* property, which
866
is an internationalized string meant for end user display.
868
The manifestation types listed here are all subclasses of *str* and may be
869
used anywhere a string would be used.
871
Manifestations form a hierarchical type tree. So that fx. ArchiveItem,
872
Attachment, and RemoteDataObject all are sub types of FileDataObject.
873
These types can again have their own sub types.
875
Templates match on all sub types, so that a query on subjects with manifestation
876
FileDataObject also match subjects of types Attachment or ArchiveItem and all
877
other sub types of FileDataObject
880
start_symbols = time.time()
882
Interpretation = Symbol("Interpretation", doc=INTERPRETATION_DOC)
883
Manifestation = Symbol("Manifestation", doc=MANIFESTATION_DOC)
884
_SYMBOLS_BY_URI["Interpretation"] = Interpretation
885
_SYMBOLS_BY_URI["Manifestation"] = Manifestation
889
execfile(os.path.join(runpath, "../extra/ontology/zeitgeist.py"))
891
raise ImportError("Unable to load zeitgeist ontology, "
892
"please run `make` and try again.")
894
from zeitgeist import _config
895
execfile(os.path.join(_config.datadir, "zeitgeist/ontology/zeitgeist.py"))
898
# Bootstrap the symbol relations. We use a 2-pass strategy:
900
# 1) Make sure that all parents and children are registered on each symbol
901
for symbol in _SYMBOLS_BY_URI.itervalues():
902
for parent in symbol._parents:
904
_SYMBOLS_BY_URI[parent]._children[symbol.uri] = None
906
print "ERROR", e, parent, symbol.uri
908
for child in symbol._children:
910
_SYMBOLS_BY_URI[child]._parents.add(symbol.uri)
912
print "ERROR", e, child, symbol.uri
915
# 2) Resolve all child and parent URIs to their actual Symbol instances
916
for symbol in _SYMBOLS_BY_URI.itervalues():
917
for child_uri in symbol._children.iterkeys():
918
symbol._children[child_uri] = _SYMBOLS_BY_URI[child_uri]
921
for parent_uri in symbol._parents:
922
parents[parent_uri] = _SYMBOLS_BY_URI[parent_uri]
923
symbol._parents = parents
926
if __name__ == "__main__":
928
end_symbols = time.time()
929
print >> sys.stderr, "Import time: %s" % (end_symbols - start_symbols)
930
#~ x = len(Interpretation.get_all_children())
931
#~ y = len(Manifestation.get_all_children())
932
#~ print >> sys.stderr, \
933
#~ ("Overall number of symbols: %i (man.: %i, int.: %i)" %(x+y, y, x))
934
#~ print >> sys.stderr, ("Resolved %i symbols, needed %i iterations" %(initial_count, initial_count-c))
935
#~ print >> sys.stderr, ("Loading symbols took %.4f seconds" %(end_symbols - start_symbols))
938
#~ EventManifestation = Manifestation.EventManifestation
939
#~ EventInterpretation = Interpretation.EventInterpretation
941
#~ DataContainer = Interpretation.DataContainer
944
#~ print dir(EventManifestation)
945
#~ print dir(Manifestation)
946
#~ print EventManifestation.UserActivity
948
#~ print DataContainer
949
#~ print DataContainer.Filesystem
950
#~ print DataContainer.Filesystem.__doc__
952
#~ print " OR ".join(DataContainer.get_all_children())
953
#~ print " OR ".join(DataContainer.Filesystem.get_all_children())
955
#~ print DataContainer.Boo
957
#~ #Symbol("BOO", DataContainer) #must fail with ValueError
958
#~ #Symbol("Boo", DataContainer) #must fail with ValueError
959
#~ Symbol("Foo", set([DataContainer,]))
960
#~ print DataContainer.Foo
962
#~ #DataContainer._add_child("booo") #must fail with TypeError
964
#~ print Interpretation
965
#~ #print Interpretation.get_all_children()
967
#~ pprint.pprint(Interpretation.Software.get_all_children())
969
#~ print Interpretation["http://www.semanticdesktop.org/ontologies/2007/03/22/nfo#MindMap"]