~affinity/zeitgeist-affinity/trunk

« back to all changes in this revision

Viewing changes to src/datamodel.py

  • Committer: Seif Lotfy
  • Date: 2010-11-04 14:43:30 UTC
  • Revision ID: seif@lotfy.com-20101104144330-ggnpl1ec3393tdx6
add missing file

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# -.- coding: utf-8 -.-
 
2
 
 
3
# Zeitgeist
 
4
#
 
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>
 
9
#
 
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.
 
14
#
 
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.
 
19
#
 
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/>.
 
22
 
 
23
import os.path
 
24
import gettext
 
25
import time
 
26
import sys
 
27
gettext.install("zeitgeist", unicode=1)
 
28
 
 
29
__all__ = [
 
30
        'Interpretation',
 
31
        'Manifestation',
 
32
        'ResultType',
 
33
        'StorageState',
 
34
        'TimeRange',
 
35
        'DataSource',
 
36
        'Event',
 
37
        'Subject',
 
38
        'NULL_EVENT',
 
39
        'NEGATION_OPERATOR',
 
40
]
 
41
 
 
42
NEGATION_OPERATOR = "!"
 
43
WILDCARD = "*"
 
44
 
 
45
def EQUAL(x, y):
 
46
        """checks if both given arguments are equal"""
 
47
        return x == y
 
48
        
 
49
def STARTSWITH(x, y):
 
50
        """checks if 'x' startswith 'y'"""
 
51
        return x.startswith(y)
 
52
 
 
53
# next() function is python >= 2.6
 
54
try:
 
55
        next = next
 
56
except NameError:
 
57
        # workaround this for older python versions
 
58
        _default_next = object()
 
59
        def next(iterator, default=_default_next):
 
60
                try:
 
61
                        return iterator.next()
 
62
                except StopIteration:
 
63
                        if default is not _default_next:
 
64
                                return default
 
65
                        raise
 
66
 
 
67
runpath = os.path.dirname(__file__)
 
68
 
 
69
NEEDS_CHILD_RESOLUTION = set()
 
70
 
 
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
 
75
        IS_LOCAL = False
 
76
else:
 
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
 
81
        IS_LOCAL = True
 
82
        
 
83
def get_timestamp_for_now():
 
84
        """
 
85
        Return the current time in milliseconds since the Unix Epoch.
 
86
        """
 
87
        return int(time.time() * 1000)
 
88
                
 
89
 
 
90
class enum_factory(object):
 
91
        """factory for enums"""
 
92
        counter = 0
 
93
        
 
94
        def __init__(self, doc):
 
95
                self.__doc__ = doc
 
96
                self._id = enum_factory.counter
 
97
                enum_factory.counter += 1
 
98
                
 
99
                
 
100
class EnumMeta(type):
 
101
        """Metaclass to register enums in correct order and assign interger
 
102
        values to them
 
103
        """
 
104
        def __new__(cls, name, bases, attributes):
 
105
                enums = filter(
 
106
                        lambda x: isinstance(x[1], enum_factory), attributes.iteritems()
 
107
                )
 
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)
 
112
 
 
113
 
 
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)
 
119
                return obj
 
120
 
 
121
 
 
122
class Enum(object):
 
123
 
 
124
        def __init__(self, docstring):
 
125
                self.__doc__ = str(docstring)
 
126
                self.__enums = {}
 
127
 
 
128
        def __getattr__(self, name):
 
129
                try:
 
130
                        return self.__enums[name]
 
131
                except KeyError:
 
132
                        raise AttributeError
 
133
 
 
134
        def register(self, value, name, docstring):
 
135
                ids = map(int, self.__enums.values())
 
136
                if value in ids or name in self.__enums:
 
137
                        raise ValueError
 
138
                self.__enums[name] = EnumValue(value, docstring)
 
139
                
 
140
                
 
141
def isCamelCase(text):
 
142
        return text and text[0].isupper() and " " not in text
 
143
        
 
144
def get_name_or_str(obj):
 
145
        try:
 
146
                return str(obj.name)
 
147
        except AttributeError:
 
148
                return str(obj)
 
149
 
 
150
_SYMBOLS_BY_URI = {}
 
151
 
 
152
class Symbol(str):
 
153
 
 
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)
 
158
                
 
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
 
164
                self._name = name
 
165
                self._uri = uri
 
166
                self._display_name = display_name
 
167
                self._doc = doc
 
168
                _SYMBOLS_BY_URI[uri] = self
 
169
 
 
170
        def __repr__(self):
 
171
                return "<%s '%s'>" %(get_name_or_str(self), self.uri)
 
172
                
 
173
        def __getattr__(self, name):
 
174
                self._ensure_all_children()
 
175
                try:
 
176
                        return self._all_children[name]
 
177
                except KeyError:
 
178
                        for child in self.iter_all_children():
 
179
                                if child == self:
 
180
                                        continue
 
181
                                try:
 
182
                                        return getattr(child, name)
 
183
                                except AttributeError:
 
184
                                        pass
 
185
                        raise AttributeError("'%s' object has no attribute '%s'" %(self.__class__.__name__, name))
 
186
        
 
187
        def __getitem__ (self, uri):
 
188
                return _SYMBOLS_BY_URI[uri]
 
189
 
 
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)
 
195
        
 
196
        def _visit (self, dikt):
 
197
                dikt[self.name] = self
 
198
                for child in self._children.itervalues():
 
199
                        child._visit(dikt) 
 
200
        
 
201
        @staticmethod
 
202
        def find_child_uris_extended (uri):
 
203
                """
 
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.
 
207
                """
 
208
                try:
 
209
                        symbol = _SYMBOLS_BY_URI[uri]
 
210
                        children = list(symbol.get_all_children())
 
211
                        children.append(uri)
 
212
                        return children
 
213
                except KeyError, e:
 
214
                        return [uri]
 
215
                
 
216
 
 
217
        @property
 
218
        def uri(self):
 
219
                return self._uri or self.name
 
220
 
 
221
        @property
 
222
        def display_name(self):
 
223
                return self._display_name or ""
 
224
 
 
225
        @property
 
226
        def name(self):
 
227
                return self._name
 
228
        __name__ = name
 
229
        
 
230
        def __dir__(self):
 
231
                self._ensure_all_children()
 
232
                return self._all_children.keys()
 
233
 
 
234
        @property
 
235
        def doc(self):
 
236
                return self._doc or ""
 
237
 
 
238
        @property
 
239
        def __doc__(self):
 
240
                return "%s\n\n  %s. ``(Display name: '%s')``" %(self.uri, self.doc.rstrip("."), self.display_name)
 
241
                
 
242
        def get_children(self):
 
243
                """
 
244
                Returns a list of immediate child symbols
 
245
                """
 
246
                return frozenset(self._children.itervalues())
 
247
                
 
248
        def iter_all_children(self):
 
249
                """
 
250
                Returns a generator that recursively iterates over all children
 
251
                of this symbol
 
252
                """
 
253
                self._ensure_all_children()
 
254
                return self._all_children.itervalues()
 
255
                
 
256
        def get_all_children(self):
 
257
                """
 
258
                Return a read-only set containing all children of this symbol
 
259
                """
 
260
                return frozenset(self.iter_all_children())
 
261
                
 
262
        def get_parents(self):
 
263
                """
 
264
                Returns a list of immediate parent symbols
 
265
                """
 
266
                return frozenset(self._parents.itervalues())
 
267
        
 
268
        def is_child_of (self, parent):
 
269
                """
 
270
                Returns True if this symbol is a child of `parent`.
 
271
                """
 
272
                if not isinstance (parent, Symbol):
 
273
                        try:
 
274
                                parent = _SYMBOLS_BY_URI[parent]
 
275
                        except KeyError, e:
 
276
                                # Parent is not a known URI
 
277
                                #print 11111111111, self.uri, parent #debug output
 
278
                                return self.uri == parent
 
279
                
 
280
                # Invariant: parent is a Symbol
 
281
                if self.uri == parent.uri : return True
 
282
                
 
283
                parent._ensure_all_children()
 
284
                
 
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
 
288
        
 
289
        @staticmethod
 
290
        def uri_is_child_of (child, parent):
 
291
                """
 
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.
 
295
                """
 
296
                if isinstance (child, basestring):
 
297
                        try:
 
298
                                child = _SYMBOLS_BY_URI[child]
 
299
                        except KeyError, e:
 
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
 
305
                                else:
 
306
                                        return False
 
307
                
 
308
                if not isinstance (child, Symbol):
 
309
                        raise ValueError("Child argument must be a Symbol or string. Got %s" % type(child))
 
310
                
 
311
                return child.is_child_of(parent)
 
312
                
 
313
class TimeRange(list):
 
314
        """
 
315
        A class that represents a time range with a beginning and an end.
 
316
        The timestamps used are integers representing milliseconds since the
 
317
        Epoch.
 
318
        
 
319
        By design this class will be automatically transformed to the DBus
 
320
        type (xx).
 
321
        """
 
322
        # Maximal value of our timestamps
 
323
        _max_stamp = 2**63 - 1
 
324
        
 
325
        def __init__ (self, begin, end):
 
326
                super(TimeRange, self).__init__((int(begin), int(end)))
 
327
        
 
328
        def __eq__ (self, other):
 
329
                return self.begin == other.begin and self.end == other.end
 
330
        
 
331
        def __str__ (self):
 
332
                return "(%s, %s)" % (self.begin, self.end)
 
333
        
 
334
        def get_begin(self):
 
335
                return self[0]
 
336
        
 
337
        def set_begin(self, begin):
 
338
                self[0] = begin
 
339
        begin = property(get_begin, set_begin,
 
340
        doc="The begining timestamp of this time range")
 
341
        
 
342
        def get_end(self):
 
343
                return self[1]
 
344
        
 
345
        def set_end(self, end):
 
346
                self[1] = end
 
347
        end = property(get_end, set_end,
 
348
        doc="The end timestamp of this time range")
 
349
        
 
350
        @classmethod
 
351
        def until_now(cls):
 
352
                """
 
353
                Return a :class:`TimeRange` from 0 to the instant of invocation
 
354
                """
 
355
                return cls(0, int(time.time() * 1000))
 
356
        
 
357
        @classmethod
 
358
        def from_now(cls):
 
359
                """
 
360
                Return a :class:`TimeRange` from the instant of invocation to
 
361
                the end of time
 
362
                """
 
363
                return cls(int(time.time() * 1000), cls._max_stamp)
 
364
        
 
365
        @classmethod
 
366
        def from_seconds_ago(cls, sec):
 
367
                """
 
368
                Return a :class:`TimeRange` ranging from "sec" seconds before
 
369
                the instant of invocation to the same.
 
370
                """
 
371
                now = int(time.time() * 1000)
 
372
                return cls(now - (sec * 1000), now)
 
373
        
 
374
        @classmethod
 
375
        def always(cls):
 
376
                """
 
377
                Return a :class:`TimeRange` from 0 (January 1, 1970) to the most
 
378
                distant future
 
379
                """
 
380
                return cls(0, cls._max_stamp)
 
381
        
 
382
        def is_always(self):
 
383
                """
 
384
                Returns True if this time range goes from timestamp 0 (January 1, 1970) 
 
385
                -or lower- to the most distant future.
 
386
                """
 
387
                return self.begin <= 0 and self.end >= TimeRange._max_stamp
 
388
                
 
389
        def intersect(self, time_range):
 
390
                """
 
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`.
 
394
                """
 
395
                # Behold the boolean madness!
 
396
                result = TimeRange(0,0)
 
397
                if self.begin < time_range.begin:
 
398
                        if self.end < time_range.begin:
 
399
                                return None
 
400
                        else:
 
401
                                result.begin = time_range.begin
 
402
                else:
 
403
                        if self.begin > time_range.end:
 
404
                                return None
 
405
                        else:
 
406
                                result.begin = self.begin
 
407
                
 
408
                if self.end < time_range.end:
 
409
                        if self.end < time_range.begin:
 
410
                                return None
 
411
                        else:
 
412
                                 result.end = self.end
 
413
                else:
 
414
                        if self.begin > time_range.end:
 
415
                                return None
 
416
                        else:
 
417
                                result.end = time_range.end
 
418
                
 
419
                return result
 
420
                
 
421
                
 
422
class RelevantResultType(object):
 
423
        """
 
424
        An enumeration class used to define how query results should be returned
 
425
        from the Zeitgeist engine.
 
426
        """
 
427
        __metaclass__ = EnumMeta
 
428
        
 
429
        Recent = enum_factory("All uris with the most recent uri first")
 
430
        Related = enum_factory("All uris with the most related one first")
 
431
        
 
432
        
 
433
class Subject(list):
 
434
        """
 
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.
 
438
        
 
439
        Applications should normally use the method :meth:`new_for_values` to
 
440
        create new subjects.
 
441
        """
 
442
        Fields = (Uri,
 
443
                Interpretation,
 
444
                Manifestation,
 
445
                Origin,
 
446
                Mimetype,
 
447
                Text,
 
448
                Storage) = range(7)
 
449
                
 
450
        SUPPORTS_NEGATION = (Uri, Interpretation, Manifestation, Origin, Mimetype)
 
451
        SUPPORTS_WILDCARDS = (Uri, Origin, Mimetype)
 
452
        
 
453
        def __init__(self, data=None):
 
454
                super(Subject, self).__init__([""]*len(Subject.Fields))
 
455
                if data:
 
456
                        if len(data) != len(Subject.Fields):
 
457
                                raise ValueError(
 
458
                                        "Invalid subject data length %s, expected %s"
 
459
                                        % (len(data), len(Subject.Fields)))
 
460
                        super(Subject, self).__init__(data)
 
461
                else:
 
462
                        super(Subject, self).__init__([""]*len(Subject.Fields))
 
463
                
 
464
        def __repr__(self):
 
465
                return "%s(%s)" %(
 
466
                        self.__class__.__name__, super(Subject, self).__repr__()
 
467
                )
 
468
        
 
469
        @staticmethod
 
470
        def new_for_values (**values):
 
471
                """
 
472
                Create a new Subject instance and set its properties according
 
473
                to the keyword arguments passed to this method.
 
474
                
 
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.
 
482
                """
 
483
                self = Subject()
 
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)
 
489
                return self
 
490
                
 
491
        def get_uri(self):
 
492
                return self[Subject.Uri]
 
493
                
 
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")
 
498
                
 
499
        def get_interpretation(self):
 
500
                return self[Subject.Interpretation]
 
501
                
 
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") 
 
506
                
 
507
        def get_manifestation(self):
 
508
                return self[Subject.Manifestation]
 
509
                
 
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")
 
514
                
 
515
        def get_origin(self):
 
516
                return self[Subject.Origin]
 
517
                
 
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")
 
522
                
 
523
        def get_mimetype(self):
 
524
                return self[Subject.Mimetype]
 
525
                
 
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")
 
530
        
 
531
        def get_text(self):
 
532
                return self[Subject.Text]
 
533
                
 
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")
 
538
                
 
539
        def get_storage(self):
 
540
                return self[Subject.Storage]
 
541
                
 
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")
 
546
        
 
547
        def matches_template (self, subject_template):
 
548
                """
 
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`. 
 
553
                
 
554
                See also :meth:`Event.matches_template`
 
555
                """
 
556
                for m in Subject.Fields:
 
557
                        if not subject_template[m]:
 
558
                                # empty fields are handled as wildcards
 
559
                                continue
 
560
                        if m == Subject.Storage:
 
561
                                # we do not support searching by storage field for now
 
562
                                # see LP: #580364
 
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
 
567
                        else:
 
568
                                comp = EQUAL
 
569
                        if not self._check_field_match(m, subject_template[m], comp):
 
570
                                return False
 
571
                return True
 
572
                
 
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)
 
583
                else:
 
584
                        return comp(self[field_id], expression)
 
585
 
 
586
class Event(list):
 
587
        """
 
588
        Core data structure in the Zeitgeist framework. It is an optimized and
 
589
        convenient representation of an event.
 
590
        
 
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>`.
 
595
        
 
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.
 
600
        """
 
601
        Fields = (Id,
 
602
                Timestamp,
 
603
                Interpretation,
 
604
                Manifestation,
 
605
                Actor) = range(5)
 
606
                
 
607
        SUPPORTS_NEGATION = (Interpretation, Manifestation, Actor)
 
608
        SUPPORTS_WILDCARDS = (Actor,)
 
609
        
 
610
        def __init__(self, struct = None):
 
611
                """
 
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.
 
616
                
 
617
                Unless the event metadata contains a timestamp the event will
 
618
                have its timestamp set to "now". Ie. the instant of invocation.
 
619
                
 
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.
 
623
                
 
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.
 
627
                
 
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
 
631
                array of bytes).
 
632
                """
 
633
                super(Event, self).__init__()
 
634
                if struct:
 
635
                        if len(struct) == 1:
 
636
                                self.append(struct[0])
 
637
                                self.append([])
 
638
                                self.append("")
 
639
                        elif len(struct) == 2:
 
640
                                self.append(struct[0])
 
641
                                self.append(map(Subject, struct[1]))
 
642
                                self.append("")
 
643
                        elif len(struct) == 3:
 
644
                                self.append(struct[0])
 
645
                                self.append(map(Subject, struct[1]))
 
646
                                self.append(struct[2])
 
647
                        else:
 
648
                                raise ValueError("Invalid struct length %s" % len(struct))
 
649
                else:
 
650
                        self.extend(([""]* len(Event.Fields), [], ""))
 
651
                
 
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())
 
655
                
 
656
        @classmethod
 
657
        def new_for_data(cls, event_data):
 
658
                """
 
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.
 
663
                """
 
664
                self = cls()
 
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)))
 
668
                self[0] = event_data
 
669
                return self
 
670
                
 
671
        @classmethod
 
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:
 
675
                        return None
 
676
                return cls(struct)
 
677
        
 
678
        @classmethod
 
679
        def new_for_values(cls, **values):
 
680
                """
 
681
                Create a new Event instance from a collection of keyword
 
682
                arguments.
 
683
                
 
684
                 
 
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
 
690
                
 
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`:
 
695
                
 
696
                :param subject_uri:
 
697
                :param subject_interpretation:
 
698
                :param subject_manifestation:
 
699
                :param subject_origin:
 
700
                :param subject_mimetype:
 
701
                :param subject_text:
 
702
                :param subject_storage:
 
703
                 
 
704
                
 
705
                """
 
706
                self = cls()
 
707
                for key in values:
 
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)
 
713
                        
 
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)
 
719
                
 
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")
 
723
                        subj = Subject()
 
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]
 
732
                
 
733
                return self
 
734
        
 
735
        @staticmethod
 
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
 
744
                return False
 
745
        
 
746
        def __repr__(self):
 
747
                return "%s(%s)" %(
 
748
                        self.__class__.__name__, super(Event, self).__repr__()
 
749
                )
 
750
        
 
751
        def append_subject(self, subject=None):
 
752
                """
 
753
                Append a new empty Subject and return a reference to it
 
754
                """
 
755
                if not subject:
 
756
                        subject = Subject()
 
757
                self.subjects.append(subject)
 
758
                return subject
 
759
        
 
760
        def get_subjects(self):
 
761
                return self[1]  
 
762
        
 
763
        def set_subjects(self, subjects):
 
764
                self[1] = subjects
 
765
        subjects = property(get_subjects, set_subjects,
 
766
        doc="Read/write property with a list of :class:`Subjects <Subject>`")
 
767
                
 
768
        def get_id(self):
 
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")
 
773
        
 
774
        def get_timestamp(self):
 
775
                return self[0][Event.Timestamp]
 
776
        
 
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")
 
781
        
 
782
        def get_interpretation(self):
 
783
                return self[0][Event.Interpretation]
 
784
        
 
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") 
 
789
        
 
790
        def get_manifestation(self):
 
791
                return self[0][Event.Manifestation]
 
792
        
 
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")
 
797
        
 
798
        def get_actor(self):
 
799
                return self[0][Event.Actor]
 
800
        
 
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`")
 
805
        
 
806
        def get_payload(self):
 
807
                return self[2]
 
808
        
 
809
        def set_payload(self, value):
 
810
                self[2] = value
 
811
        payload = property(get_payload, set_payload,
 
812
        doc="Free form attachment for the event. Transfered over DBus as an array of bytes")
 
813
        
 
814
        def matches_template(self, event_template):
 
815
                """
 
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
 
823
                template.
 
824
                
 
825
                Basically this method mimics the matching behaviour
 
826
                found in the :meth:`FindEventIds` method on the Zeitgeist engine.
 
827
                """
 
828
                # We use direct member access to speed things up a bit
 
829
                # First match the raw event data
 
830
                data = self[0]
 
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
 
836
                                continue
 
837
                        if m in (Event.Manifestation, Event.Interpretation):
 
838
                                # special check for symbols
 
839
                                comp = Symbol.uri_is_child_of
 
840
                        else:
 
841
                                comp = EQUAL
 
842
                        if not self._check_field_match(m, tdata[m], comp):
 
843
                                return False
 
844
                
 
845
                # If template has no subjects we have a match
 
846
                if len(event_template[1]) == 0 : return True
 
847
                
 
848
                # Now we check the subjects
 
849
                for tsubj in event_template[1]:
 
850
                        for subj in self[1]:            
 
851
                                if not subj.matches_template(tsubj) : continue                          
 
852
                                # We have a matching subject, all good!
 
853
                                return True
 
854
                
 
855
                # Template has subjects, but we never found a match
 
856
                return False
 
857
                
 
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)
 
868
                else:
 
869
                        return comp(self[0][field_id], expression)
 
870
        
 
871
        def matches_event (self, event):
 
872
                """
 
873
                Interpret *self* as the template an match *event* against it.
 
874
                This method is the dual method of :meth:`matches_template`.
 
875
                """
 
876
                return event.matches_template(self)
 
877
        
 
878
        def in_time_range (self, time_range):
 
879
                """
 
880
                Check if the event timestamp lies within a :class:`TimeRange`
 
881
                """
 
882
                t = int(self.timestamp) # The timestamp may be stored as a string
 
883
                return (t >= time_range.begin) and (t <= time_range.end)
 
884
 
 
885
class DataSource(list):
 
886
        """ Optimized and convenient data structure representing a datasource.
 
887
        
 
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>`.
 
892
        
 
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.
 
897
 
 
898
        This is part of the :const:`org.gnome.zeitgeist.DataSourceRegistry`
 
899
        extension.
 
900
        """
 
901
        Fields = (UniqueId,
 
902
                Name,
 
903
                Description,
 
904
                EventTemplates,
 
905
                Running,
 
906
                LastSeen,       # last time the data-source did something (connected,
 
907
                                        # inserted events, disconnected).
 
908
                Enabled) = range(7)
 
909
                
 
910
        def get_unique_id(self):
 
911
                return self[self.UniqueId]
 
912
        
 
913
        def set_unique_id(self, value):
 
914
                self[self.UniqueId] = value
 
915
        
 
916
        def get_name(self):
 
917
                return self[self.Name]
 
918
        
 
919
        def set_name(self, value):
 
920
                self[self.Name] = value
 
921
        
 
922
        def get_description(self):
 
923
                return self[self.Description]
 
924
        
 
925
        def set_description(self, value):
 
926
                self[self.Description] = value
 
927
        
 
928
        def get_running(self):
 
929
                return self[self.Running]
 
930
        
 
931
        def set_running(self,value):
 
932
                self[self.Running] = value
 
933
        
 
934
        def get_running(self):
 
935
                return self[self.Running]
 
936
        
 
937
        def running(self, value):
 
938
                self[self.Running] = value
 
939
        
 
940
        def get_last_seen(self):
 
941
                return self[self.LastSeen]
 
942
        
 
943
        def set_last_seen(self, value):
 
944
                self[self.LastSeen] = value
 
945
        
 
946
        def get_enabled(self):
 
947
                return self[self.Enabled]
 
948
        
 
949
        def set_enabled(self, value):
 
950
                self[self.Enabled] = value
 
951
                
 
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)
 
958
        
 
959
        def __init__(self, unique_id, name, description, templates, running=True,
 
960
                last_seen=None, enabled=True):
 
961
                """
 
962
                Create a new DataSource object using the given parameters.
 
963
                
 
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.
 
966
                """
 
967
                super(DataSource, self).__init__()
 
968
                self.append(unique_id)
 
969
                self.append(name)
 
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())
 
974
                self.append(enabled)
 
975
        
 
976
        def __eq__(self, source):
 
977
                return self[self.UniqueId] == source[self.UniqueId]
 
978
        
 
979
        def __repr__(self):
 
980
                return "%s: %s (%s)" % (self.__class__.__name__, self[self.UniqueId],
 
981
                        self[self.Name])
 
982
        
 
983
        
 
984
 
 
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.
 
989
"""
 
990
                
 
991
                
 
992
class StorageState(object):
 
993
        """
 
994
        Enumeration class defining the possible values for the storage state
 
995
        of an event subject.
 
996
        
 
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.
 
1001
        """
 
1002
        __metaclass__ = EnumMeta
 
1003
        
 
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")
 
1009
 
 
1010
 
 
1011
class ResultType(object):
 
1012
        """
 
1013
        An enumeration class used to define how query results should be returned
 
1014
        from the Zeitgeist engine.
 
1015
        """
 
1016
        __metaclass__ = EnumMeta
 
1017
        
 
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"))
 
1057
 
 
1058
 
 
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"*.
 
1062
 
 
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.
 
1067
 
 
1068
The interpretation types listed here are all subclasses of *str* and may be
 
1069
used anywhere a string would be used.
 
1070
 
 
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).
 
1074
 
 
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.
 
1078
"""
 
1079
 
 
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"*.
 
1083
 
 
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.
 
1088
 
 
1089
The manifestation types listed here are all subclasses of *str* and may be
 
1090
used anywhere a string would be used.
 
1091
 
 
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.
 
1095
 
 
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
 
1099
"""
 
1100
 
 
1101
start_symbols = time.time()
 
1102
 
 
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
 
1107
 
 
1108
if IS_LOCAL:
 
1109
        try:
 
1110
                execfile(os.path.join(runpath, "../extra/ontology/zeitgeist.py"))
 
1111
        except IOError:
 
1112
                raise ImportError("Unable to load zeitgeist ontology, "
 
1113
                                  "please run `make` and try again.")
 
1114
else:
 
1115
        from zeitgeist import _config
 
1116
        execfile(os.path.join(_config.datadir, "zeitgeist/ontology/zeitgeist.py"))
 
1117
 
 
1118
#
 
1119
# Bootstrap the symbol relations. We use a 2-pass strategy:
 
1120
#
 
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:
 
1124
                try:
 
1125
                        _SYMBOLS_BY_URI[parent]._children[symbol.uri] = None
 
1126
                except KeyError, e:
 
1127
                        print "ERROR", e, parent, symbol.uri
 
1128
                        pass
 
1129
        for child in symbol._children:
 
1130
                try:
 
1131
                        _SYMBOLS_BY_URI[child]._parents.add(symbol.uri)
 
1132
                except KeyError:
 
1133
                        print "ERROR", e, child, symbol.uri
 
1134
                        pass
 
1135
 
 
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]
 
1140
        
 
1141
        parents = {}
 
1142
        for parent_uri in symbol._parents:
 
1143
                parents[parent_uri] = _SYMBOLS_BY_URI[parent_uri]
 
1144
        symbol._parents = parents
 
1145
 
 
1146
 
 
1147
if __name__ == "__main__":
 
1148
        print "Success"
 
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))
 
1157
        #~ #
 
1158
        #~ # shortcuts
 
1159
        #~ EventManifestation = Manifestation.EventManifestation
 
1160
        #~ EventInterpretation = Interpretation.EventInterpretation
 
1161
        #~ 
 
1162
        #~ DataContainer = Interpretation.DataContainer
 
1163
        #~ 
 
1164
        #~ # testing
 
1165
        #~ print dir(EventManifestation)
 
1166
        #~ print dir(Manifestation)
 
1167
        #~ print EventManifestation.UserActivity
 
1168
        #~ 
 
1169
        #~ print DataContainer
 
1170
        #~ print DataContainer.Filesystem
 
1171
        #~ print DataContainer.Filesystem.__doc__
 
1172
        #~ 
 
1173
        #~ print " OR ".join(DataContainer.get_all_children())
 
1174
        #~ print " OR ".join(DataContainer.Filesystem.get_all_children())
 
1175
        #~ 
 
1176
        #~ print DataContainer.Boo
 
1177
        #~ 
 
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
 
1182
        #~ 
 
1183
        #~ #DataContainer._add_child("booo") #must fail with TypeError
 
1184
        #~ 
 
1185
        #~ print Interpretation
 
1186
        #~ #print Interpretation.get_all_children()
 
1187
        #~ import pprint
 
1188
        #~ pprint.pprint(Interpretation.Software.get_all_children())
 
1189
        #~ 
 
1190
        #~ print Interpretation["http://www.semanticdesktop.org/ontologies/2007/03/22/nfo#MindMap"]