summaryrefslogtreecommitdiff
path: root/libzeitgeist/event.vala
blob: 0cd708cbde54cc114d1fc3204609bd2dbf77263b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
/* event.vala
 *
 * Copyright © 2011-2012 Collabora Ltd.
 *             By Seif Lotfy <seif@lotfy.com>
 *             By Siegfried-Angel Gevatter Pujals <siegfried@gevatter.com>
 * Copyright © 2011 Manish Sinha <manishsinha@ubuntu.com>
 * Copyright © 2011 Michal Hruby <michal.mhr@gmail.com>
 * Copyright © 2012 Canonical Ltd.
 *             By Siegfried-A. Gevatter <siegfried.gevatter@collabora.co.uk>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 2.1 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

using Zeitgeist;

namespace Zeitgeist
{

// Also used in subject.vala
private string? next_string_or_null (VariantIter iter)
{
    string text = iter.next_value ().get_string ();
    if (text != "")
        return text;
    return null;
}

// Also used in subject.vala
private bool check_field_match (string? property,
        string? template_property, bool is_symbol = false,
        bool can_wildcard = false)
{
    var matches = false;
    var is_negated = false;
    var parsed = template_property;

    if (parsed != null)
        is_negated = Utils.parse_negation (ref parsed);

    if (Utils.is_empty_string (parsed))
    {
        return true;
    }
    else if (parsed == property)
    {
        matches = true;
    }
    else if (is_symbol && property != null &&
        Symbol.get_all_parents (property).find_custom (parsed, strcmp) != null)
    {
        matches = true;
    }
    else if (can_wildcard && Utils.parse_wildcard (ref parsed))
    {
        if (property != null && property.has_prefix (parsed))
            matches = true;
    }

    return (is_negated) ? !matches : matches;
}

/**
 * Event objects abstract Zeitgeist events
 *
 * The Event class is one of the primary elements for communicating
 * with the Zeitgeist daemon. Events serve two purposes
 * Unsurprisingly, they represent events that have happened, but they
 * can also act as templates. See also {@link Subject}.
 *
 * An event in the Zeitgeist world is characterized by two main
 * properties. "What happened", also called the interpretation, and
 * "How did it happen", also called the manifestation. Besides these
 * properties, an event also has an actor which identifies the party
 * responsible for triggering the event which in most cases will be
 * an application. Lastly there is an event timestamp and an event ID.
 * The timestamp is calculated as the number of milliseconds since the
 * Unix epoch and the event ID is a number assigned to the event by
 * the Zeitgeist engine when it's logged. These five properties are
 * collectively known as the event metadata.
 *
 * An event must also describe what it happened to. For this we have
 * event subjects. Most events have one subject, but they may also
 * have more. The metadata of the subjects are recorded at the time
 * of logging, and are encapsulated by the #Subject class. It's
 * important to understand that it's just the subject metadata at the
 * time of logging, not necessarily the subject metadata as it exists
 * right now.
 *
 * In addition to the listed properties, events may also carry a free
 * form binary payload. The usage of this is application specific and
 * is generally useless unless you have some contextual information to
 * figure out what's in it.
 *
 * A large part of the Zeitgeist query and monitoring API revolves
 * around a concept of template matching. A query is simply a list of
 * event templates that you want to look for in the log. An unset
 * property on an event template indicates that anything is allowed in
 * that field. If the property is set it indicates that the property
 * must be an exact match, unless a special operator is used.
 */
public class Event : Object
{
    public const string SIGNATURE = "asaasay";

    private static StringChunk url_store;

    public uint32  id { get; set; }
    public int64   timestamp { get; set; }
    public string? origin { get; set; }

    public string? actor
    {
        get { return _actor; }
        set { _actor = (value != null) ? url_store.insert_const (value) : null; }
    }
    public string? interpretation
    {
        get { return _interpretation; }
        set { _interpretation = (value != null) ? url_store.insert_const (value) : null; }
    }
    public string? manifestation
    {
        get { return _manifestation; }
        set { _manifestation = (value != null) ? url_store.insert_const (value) : null; }
    }

    private unowned string? _actor;
    private unowned string? _interpretation;
    private unowned string? _manifestation;

    public GenericArray<Subject> subjects { get; set; }
    public ByteArray? payload { get; set; }

    static construct
    {
        url_store = new StringChunk (4096);
    }

    construct
    {
        subjects = new GenericArray<Subject> ();
    }

    public Subject get_subject(int index)
    {
        return subjects[index];
    }

    public int num_subjects ()
    {
        return subjects.length;
    }

    public void add_subject (Subject subject)
    {
        subjects.add (subject);
    }

    public void take_subject (owned Subject subject)
    {
        subjects.add ((owned) subject);
    }

    public Event.full (string? interpretation=null,
        string? manifestation=null, string? actor=null,
        string? origin=null, ...)
    {
        this.interpretation = interpretation;
        this.manifestation = manifestation;
        this.actor = actor;
        this.origin = origin;

        // Requires Vala bug #620675 - fixed as of 2012-07-30
        var subjects = va_list ();
        unowned Subject subject;
        while ((subject = subjects.arg ()) != null)
            add_subject (subject);
    }

    public Event.from_variant (Variant event_variant) throws DataModelError {
        Utils.assert_sig (event_variant.get_type_string () == "(" +
            Utils.SIG_EVENT + ")", "Invalid D-Bus signature.");

        VariantIter iter = event_variant.iterator ();

        Utils.assert_sig (iter.n_children () >= 3, "Incomplete event struct.");
        VariantIter event_array = iter.next_value ().iterator ();
        VariantIter subjects_array = iter.next_value ().iterator ();
        Variant payload_variant = iter.next_value ();

        var event_props = event_array.n_children ();

        if (event_props == 0)
        {
            throw new DataModelError.NULL_EVENT ("This is an empty event.");
        }

        Utils.assert_sig (event_props >= 5, "Missing event information.");
        id = (uint32) uint64.parse (event_array.next_value().get_string ());
        var str_timestamp = event_array.next_value().get_string ();
        if (str_timestamp != "")
            timestamp = int64.parse (str_timestamp);
        else
            timestamp = Timestamp.from_now ();
        interpretation = next_string_or_null (event_array);
        manifestation = next_string_or_null (event_array);
        actor = next_string_or_null (event_array);
        // let's keep this compatible with older clients
        if (event_props >= 6)
            origin = next_string_or_null (event_array);

        for (int i = 0; i < subjects_array.n_children (); ++i) {
            Variant subject_variant = subjects_array.next_value ();
            subjects.add (new Subject.from_variant (subject_variant));
        }

        // Parse payload...
        uint payload_length = (uint) payload_variant.n_children ();
        if (payload_length > 0)
        {
            payload = new ByteArray.sized (payload_length);
            unowned uint8[] data = (uint8[]?) payload_variant.get_data ();
            data.length = (int) payload_length;
            payload.append (data);
        }
    }

    public void set_actor_from_app_info (AppInfo info)
    {
        if (info.get_id () != null)
        {
            actor = "application://" + info.get_id ();
        }
        else
        {
            string? path = null;
            if (info is DesktopAppInfo)
                path = (info as DesktopAppInfo).filename;

            if (path != null)
            {
                actor = "application://" + Path.get_basename (path);
            }
            else if (info.get_name () != null)
            {
                actor = "application://" + info.get_name () + ".desktop";
            }
        }
    }

    public Variant to_variant ()
    {
        var vb = new VariantBuilder (new VariantType ("("+Utils.SIG_EVENT+")"));

        vb.open (new VariantType ("as"));
        vb.add ("s", id == 0 ? "" : id.to_string ());
        vb.add ("s", timestamp != 0 ? timestamp.to_string () : "");
        vb.add ("s", interpretation != null ? interpretation : "");
        vb.add ("s", manifestation != null ? manifestation : "");
        vb.add ("s", actor != null ? actor : "");
        vb.add ("s", origin != null ? origin : "");
        vb.close ();

        vb.open (new VariantType ("aas"));
        for (int i = 0; i < subjects.length; ++i) {
            vb.add_value (subjects[i].to_variant ());
        }
        vb.close ();

        if (payload != null)
        {
            Variant payload_variant = Variant.new_from_data<ByteArray> (
                new VariantType ("ay"), payload.data, false, payload);
            vb.add_value (payload_variant);
        }
        else
        {
            vb.open (new VariantType ("ay"));
            vb.close ();
        }

        Variant event_variant = vb.end ().get_normal_form ();
        //Variant ret = optimize_variant_allocation (event_variant);
        return event_variant;
    }

    private Variant optimize_variant_allocation (Variant event_variant) {
        // FIXME: this uses g_new0, we dont need the mem to be zero-filled
        uchar[] data = new uchar[event_variant.get_size ()];
        event_variant.store (data);
        unowned uchar[] data_copy = data;

        Variant ret = Variant.new_from_data (
            new VariantType ("("+Utils.SIG_EVENT+")"),
            data_copy, true, (owned) data);
        return ret;
    }

    public void debug_print ()
    {
        stdout.printf ("id: %d\t" +
                       "timestamp: %" + int64.FORMAT + "\n" +
                       "actor: %s\n" +
                       "interpretation: %s\n" +
                       "manifestation: %s\n" +
                       "origin: %s\n" +
                       "num subjects: %d\n",
                       id, timestamp, actor, interpretation,
                       manifestation, origin, subjects.length);
        for (int i = 0; i < subjects.length; i++)
        {
            var s = subjects[i];
            stdout.printf ("  Subject #%d:\n" +
                           "    uri: %s\n" +
                           "    interpretation: %s\n" +
                           "    manifestation: %s\n" +
                           "    mimetype: %s\n" +
                           "    origin: %s\n" +
                           "    text: %s\n" +
                           "    current_uri: %s\n" +
                           "    current_origin: %s\n" +
                           "    storage: %s\n",
                           i, s.uri, s.interpretation, s.manifestation,
                           s.mimetype, s.origin, s.text, s.current_uri,
                           s.current_origin, s.storage);
        }
        if (payload != null)
            stdout.printf ("payload: %u bytes", payload.len);
        else
            stdout.printf ("payload: (null)\n");
    }


    public bool matches_template (Event template_event)
    {
        /**
        Return True if this event matches *event_template*. The
        matching is done where unset fields in the template is
        interpreted as wild cards. Interpretations and manifestations
        are also matched if they are children of the types specified
        in `event_template`. If the template has more than one
        subject, this event matches if at least one of the subjects
        on this event matches any single one of the subjects on the
        template.
        */

        //Check if interpretation is child of template_event or same
        if (!check_field_match (this.interpretation, template_event.interpretation, true))
            return false;
        //Check if manifestation is child of template_event or same
        if (!check_field_match (this.manifestation, template_event.manifestation, true))
            return false;
        //Check if actor is equal to template_event actor
        if (!check_field_match (this.actor, template_event.actor, false, true))
            return false;
        //Check if origin is equal to template_event origin
        if (!check_field_match (this.origin, template_event.origin, false, true))
            return false;

        if (template_event.subjects.length == 0)
            return true;

        for (int i = 0; i < this.subjects.length; i++)
            for (int j = 0; j < template_event.subjects.length; j++)
                if (this.subjects[i].matches_template (template_event.subjects[j]))
                    return true;

        return false;
    }

}

namespace Events
{

    public static GenericArray<Event?> from_variant (Variant vevents)
        throws DataModelError
    {
        GenericArray<Event?> events = new GenericArray<Event> ();

        assert (vevents.get_type_string () == "a("+Utils.SIG_EVENT+")");
        foreach (Variant vevent in vevents)
        {
            Event? event = null;
            try
            {
                event = new Event.from_variant (vevent);
            }
            catch (DataModelError err)
            {
                if (!(err is DataModelError.NULL_EVENT))
                    throw err;
            }
            events.add (event);
        }

        return events;
    }

    public static Variant to_variant (GenericArray<Event?> events)
    {
        var vb = new VariantBuilder(new VariantType("a("+Utils.SIG_EVENT+")"));

        for (int i = 0; i < events.length; ++i)
        {
            if (events[i] != null)
            {
                vb.add_value (events[i].to_variant ());
            }
            else
            {
                vb.add_value (get_null_event_variant ());
            }
        }

        return vb.end ();
    }

    /* Same as to_variant but raises an exception if the variant size
     * exceeds `limit' bytes.
     * */
    public static Variant to_variant_with_limit (GenericArray<Event?> events,
        size_t limit=Utils.MAX_DBUS_RESULT_SIZE) throws DataModelError
    {
        var vb = new VariantBuilder(new VariantType("a("+Utils.SIG_EVENT+")"));

        size_t variant_size = 0;

        for (int i = 0; i < events.length; ++i)
        {
            Variant event_variant;

            if (events[i] != null)
            {
                event_variant = events[i].to_variant ();
            }
            else
            {
                event_variant = get_null_event_variant ();
            }

            variant_size += event_variant.get_size();
            if (variant_size > limit)
            {
                size_t avg_event_size = variant_size / (i+1);
                string error_message = ("Query exceeded size limit of % " +
                    size_t.FORMAT + "MiB (roughly ~%d events).").printf (
                        limit / 1024 / 1024, limit / avg_event_size);
                warning (error_message);
                throw new DataModelError.TOO_MANY_RESULTS (error_message);
            }

            vb.add_value (event_variant);
        }

        return vb.end ();
    }

    private static Variant get_null_event_variant ()
    {
        var vb = new VariantBuilder (new VariantType ("("+Utils.SIG_EVENT+")"));
        vb.open (new VariantType ("as"));
        vb.close ();
        vb.open (new VariantType ("aas"));
        vb.close ();
        vb.open (new VariantType ("ay"));
        vb.close ();
        return vb.end ();
    }

}

}

// vim:expandtab:ts=4:sw=4