2
* Copyright (C) 2010 Canonical Ltd
4
* This program is free software: you can redistribute it and/or modify
5
* it under the terms of the GNU General Public License version 3 as
6
* published by the Free Software Foundation.
8
* This program is distributed in the hope that it will be useful,
9
* but WITHOUT ANY WARRANTY; without even the implied warranty of
10
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
* GNU General Public License for more details.
13
* You should have received a copy of the GNU General Public License
14
* along with this program. If not, see <http://www.gnu.org/licenses/>.
16
* Authored by Mikkel Kamstrup Erlandsen <mikkel.kamstrup@canonical.com>
22
using Zeitgeist.Timestamp;
27
namespace Unity.FilesPlace {
29
public class FilesEntryInfo : Unity.Place.EntryInfo
31
private Zeitgeist.Index index;
32
private Gee.List<PtrArray> section_templates;
34
public FilesEntryInfo (string dbus_path,
35
Gee.List<PtrArray> section_templates)
38
index = new Zeitgeist.Index ();
39
this.section_templates = section_templates;
42
public override async uint set_global_search (Search search)
44
var results_model = global_renderer_info.results_model;
45
var groups_model = global_renderer_info.groups_model;
46
uint hit_count = yield do_search (search, Section.ALL_FILES,
47
results_model, groups_model);
51
public override async uint set_search (Search search)
53
var results_model = entry_renderer_info.results_model;
54
var groups_model = entry_renderer_info.groups_model;
55
uint hit_count = yield do_search (search, (Section)active_section,
56
results_model, groups_model);
60
private async uint do_search (Search search,
62
Dee.Model results_model,
63
Dee.Model groups_model)
65
var templates = section_templates.get((int)section);
68
/* Get relevancy ranked results for the "Top Results" group */
69
var results = yield index.search (search.get_search_string (),
70
new Zeitgeist.TimeRange.anytime(),
72
Zeitgeist.StorageState.ANY,
74
Zeitgeist.ResultType.RELEVANCY,
77
results_model.clear ();
78
Unity.FilesPlace.append_top_results (results,
82
debug ("Found %u/%u matches for search '%s'",
83
results.size (), results.estimated_matches (),
84
search.get_search_string ());
86
results = yield index.search (search.get_search_string (),
87
new Zeitgeist.TimeRange.anytime(),
89
Zeitgeist.StorageState.ANY,
91
Zeitgeist.ResultType.MOST_RECENT_SUBJECTS,
93
Unity.FilesPlace.append_events_sorted (results,
94
results_model, groups_model);
96
return results.estimated_matches ();
97
} catch (GLib.Error e) {
98
warning ("Error performing search '%s': %s",
99
search.get_search_string (), e.message);
105
public class Daemon : GLib.Object
107
private Zeitgeist.Log log;
108
private Zeitgeist.Index index;
109
private Zeitgeist.Monitor monitor;
111
private Unity.Place.Controller control;
112
private Unity.Place.EntryInfo files;
114
/* For each section we have a set of Zeitgeist.Event templates that
115
* we use to query Zeitgeist */
116
private Gee.List<PtrArray> section_templates;
118
/* Store a maping of DateMonth to Dee.ModelIter. We map to the iter and
119
* not simply the offset, because in theory anyone on the bus could
120
* update the Dee.SharedModel we use for the groups changing the row
122
private Gee.List<unowned Dee.ModelIter?> months;
126
var sections_model = new Dee.SharedModel(
127
"com.canonical.Unity.FilesPlace.SectionsModel",
128
2, typeof (string), typeof (string));
130
var groups_model = new Dee.SharedModel(
131
"com.canonical.Unity.FilesPlace.GroupsModel",
132
3, typeof (string), typeof (string),
135
var global_groups_model = new Dee.SharedModel(
136
"com.canonical.Unity.FilesPlace.GlobalGroupsModel",
137
3, typeof (string), typeof (string),
140
var results_model = new Dee.SharedModel(
141
"com.canonical.Unity.FilesPlace.ResultsModel",
142
6, typeof (string), typeof (string),
143
typeof (uint), typeof (string),
144
typeof (string), typeof (string));
146
var global_results_model = new Dee.SharedModel(
147
"com.canonical.Unity.FilesPlace.GlobalResultsModel",
148
6, typeof (string), typeof (string),
149
typeof (uint), typeof (string),
150
typeof (string), typeof (string));
152
section_templates = new Gee.ArrayList<PtrArray> ();
153
prepare_section_templates();
155
files = new FilesEntryInfo ("/com/canonical/unity/filesplace/files",
157
files.sections_model = sections_model;
158
files.entry_renderer_info.groups_model = groups_model;
159
files.entry_renderer_info.results_model = results_model;
160
files.global_renderer_info.groups_model = global_groups_model;
161
files.global_renderer_info.results_model = global_results_model;
163
populate_sections ();
166
files.icon = @"$(Config.PREFIX)/share/unity/files.png";
169
// FIXME: We monitor on all events, restrict templates to file events
170
var templates = new PtrArray();
171
var event = new Zeitgeist.Event ();
172
templates.add (event);
173
monitor = new Zeitgeist.Monitor (new Zeitgeist.TimeRange.from_now (),
176
log = new Zeitgeist.Log();
177
index = new Zeitgeist.Index();
179
/* Listen for section changes */
180
files.notify["active-section"].connect (on_active_section_changed);
182
/* We should not do anything with the results modelresults = yield index.search (search.get_search_string (),
183
new Zeitgeist.TimeRange.anytime(),
185
Zeitgeist.StorageState.ANY,
187
Zeitgeist.ResultType.RELEVANCY,
189
* until we receieve the 'ready' signal */
190
results_model.ready.connect (this.on_results_model_ready);
192
sections_model.connect ();
193
groups_model.connect ();
194
global_groups_model.connect ();
195
results_model.connect ();
196
global_results_model.connect ();
198
/* The last thing we do is export the controller. Once that is up,
199
* clients will expect the SharedModels to work */
200
control = new Unity.Place.Controller ("/com/canonical/unity/filesplace");
201
control.add_entry (files);
204
} catch (DBus.Error error) {
205
critical ("Failed to export DBus service for '%s': %s",
206
control.dbus_path, error.message);
210
private void populate_sections ()
212
var sections = files.sections_model;
214
if (sections.get_n_rows() != 0)
216
critical ("The sections model should be empty before initial population");
220
sections.append (SectionsColumn.DISPLAY_NAME, "All Files",
221
SectionsColumn.ICON_HINT, "", -1);
222
sections.append (SectionsColumn.DISPLAY_NAME, "Documents",
223
SectionsColumn.ICON_HINT, "", -1);
224
sections.append (SectionsColumn.DISPLAY_NAME, "Folders",
225
SectionsColumn.ICON_HINT, "", -1);
226
sections.append (SectionsColumn.DISPLAY_NAME, "Images",
227
SectionsColumn.ICON_HINT, "", -1);
228
sections.append (SectionsColumn.DISPLAY_NAME, "Videos",
229
SectionsColumn.ICON_HINT, "", -1);
230
sections.append (SectionsColumn.DISPLAY_NAME, "Presentations",
231
SectionsColumn.ICON_HINT, "", -1);
232
sections.append (SectionsColumn.DISPLAY_NAME, "Other",
233
SectionsColumn.ICON_HINT, "", -1);
236
private void populate_groups ()
238
var groups = files.entry_renderer_info.groups_model;
240
if (groups.get_n_rows() != 0)
242
critical ("The groups model should be empty before initial population");
246
groups.append (GroupsColumn.RENDERER, "UnityDefaultRenderer",
247
GroupsColumn.DISPLAY_NAME, "Top Results",
248
GroupsColumn.ICON_HINT, "", -1);
249
groups.append (GroupsColumn.RENDERER, "UnityDefaultRenderer",
250
GroupsColumn.DISPLAY_NAME, "Today",
251
GroupsColumn.ICON_HINT, "", -1);
252
groups.append (GroupsColumn.RENDERER, "UnityDefaultRenderer",
253
GroupsColumn.DISPLAY_NAME, "Yesterday",
254
GroupsColumn.ICON_HINT, "", -1);
255
groups.append (GroupsColumn.RENDERER, "UnityDefaultRenderer",
256
GroupsColumn.DISPLAY_NAME, "This week",
257
GroupsColumn.ICON_HINT, "", -1);
258
groups.append (GroupsColumn.RENDERER, "UnityDefaultRenderer",
259
GroupsColumn.DISPLAY_NAME, "Last Week",
260
GroupsColumn.ICON_HINT, "", -1);
261
groups.append (GroupsColumn.RENDERER, "UnityDefaultRenderer",
262
GroupsColumn.DISPLAY_NAME, "This Month",
263
GroupsColumn.ICON_HINT, "", -1);
264
groups.append (GroupsColumn.RENDERER, "UnityDefaultRenderer",
265
GroupsColumn.DISPLAY_NAME, "Past Six Months",
266
GroupsColumn.ICON_HINT, "", -1);
267
groups.append (GroupsColumn.RENDERER, "UnityDefaultRenderer",
268
GroupsColumn.DISPLAY_NAME, "This Year",
269
GroupsColumn.ICON_HINT, "", -1);
270
groups.append (GroupsColumn.RENDERER, "UnityDefaultRenderer",
271
GroupsColumn.DISPLAY_NAME, "Last Year",
272
GroupsColumn.ICON_HINT, "", -1);
273
groups.append (GroupsColumn.RENDERER, "Specific Year", // FIXME: Use actual year, eg "2009"
274
GroupsColumn.DISPLAY_NAME, "Today",
275
GroupsColumn.ICON_HINT, "", -1);
276
groups.append (GroupsColumn.RENDERER, "UnityDefaultRenderer",
277
GroupsColumn.DISPLAY_NAME, "Today",
278
GroupsColumn.ICON_HINT, "", -1);
280
months = new Gee.ArrayList<unowned Dee.ModelIter?>();
282
for (uint i = 1; i <= DateMonth.DECEMBER; i++)
284
unowned Dee.ModelIter iter = groups.append (
285
GroupsColumn.RENDERER, "UnityDefaultRenderer",
286
GroupsColumn.DISPLAY_NAME, Utils.get_month_name ((DateMonth)i),
287
GroupsColumn.ICON_HINT, "", -1);
293
private void prepare_section_templates ()
298
/* HACK ALERT: All the (event as GLib.Object).ref() are needed because
299
* GPtrArray doesn't grab a ref to the event objects */
301
/* Section.ALL_FILES */
302
templates = new PtrArray.sized(1);
303
event = new Event.full("", ZG_USER_ACTIVITY, "",
304
new Subject.full ("file:*",
306
NFO_FILE_DATA_OBJECT,
308
templates.add ((event as GLib.Object).ref());
309
section_templates.add (templates);
312
* FIXME: Filter out presentations: https://bugs.launchpad.net/zeitgeist/+bug/592599 */
313
templates = new PtrArray.sized(1);
314
event = new Event.full("", ZG_USER_ACTIVITY, "",
315
new Subject.full ("file:*",
317
NFO_FILE_DATA_OBJECT,
319
templates.add ((event as GLib.Object).ref());
320
section_templates.add (templates);
323
* FIXME: We probably need to be clever here and use something
324
* like subject.origin in stead of NFO_FOLDER */
325
templates = new PtrArray.sized(1);
326
event = new Event.full("", ZG_USER_ACTIVITY, "",
327
new Subject.full ("file:*",
329
NFO_FILE_DATA_OBJECT,
331
templates.add ((event as GLib.Object).ref());
332
section_templates.add (templates);
335
templates = new PtrArray.sized(1);
336
event = new Event.full("", ZG_USER_ACTIVITY, "",
337
new Subject.full ("file:*",
339
NFO_FILE_DATA_OBJECT,
341
templates.add ((event as GLib.Object).ref());
342
section_templates.add (templates);
345
templates = new PtrArray.sized(1);
346
event = new Event.full("", ZG_USER_ACTIVITY, "",
347
new Subject.full ("file:*",
349
NFO_FILE_DATA_OBJECT,
351
templates.add ((event as GLib.Object).ref());
352
section_templates.add (templates);
354
/* Section.PRESENTATIONS
355
* FIXME: Zeitgeist logger needs to user finer granularity
356
* on classification as I am not sure it uses
357
* NFO_PRESENTATION yet */
358
templates = new PtrArray.sized(1);
359
event = new Event.full("", ZG_USER_ACTIVITY, "",
360
new Subject.full ("file:*",
362
NFO_FILE_DATA_OBJECT,
364
templates.add ((event as GLib.Object).ref());
365
section_templates.add (templates);
368
* FIXME: Zeitgeist doesn't support collated AND NOT queries yet */
369
templates = new PtrArray.sized(1);
370
event = new Event.full("", ZG_USER_ACTIVITY, "",
371
new Subject.full ("file:*",
373
NFO_FILE_DATA_OBJECT,
375
templates.add ((event as GLib.Object).ref());
376
section_templates.add (templates);
379
public void on_results_model_ready (Dee.SharedModel model)
381
update_entry_results_model.begin();
382
monitor.events_inserted.connect (on_events_inserted);
384
// FIXME: Use the section queries as monitor templates as well
385
// and make sure we de-dupe the existing results as updates
387
// See https://bugs.launchpad.net/anjali/+bug/598078
388
//log.install_monitor.begin (monitor, null);
391
public void on_active_section_changed ()
393
// FIXME: Take active search into account
394
// See https://bugs.launchpad.net/anjali/+bug/598082
395
files.entry_renderer_info.results_model.clear();
396
update_entry_results_model.begin();
399
private async void update_entry_results_model ()
401
var section = files.active_section;
402
var sections_model = files.sections_model;
403
var results_model = files.entry_renderer_info.results_model;
404
var groups_model = files.entry_renderer_info.groups_model;
406
if (Section.LAST_SECTION != sections_model.get_n_rows())
408
critical ("Section model malformed");
412
if (section > Section.LAST_SECTION)
414
critical ("Active section out of bounds: %u", section);
418
var templates = section_templates.get((int)section);
421
var events = yield log.find_events (
422
new Zeitgeist.TimeRange.anytime(),
424
Zeitgeist.StorageState.ANY,
426
Zeitgeist.ResultType.MOST_RECENT_SUBJECTS,
428
debug ("Got %u events for section %u", events.size(), section);
430
Unity.FilesPlace.append_events_sorted (events,
431
results_model, groups_model);
432
} catch (GLib.Error e) {
433
warning ("Error fetching recetnly used files: %s", e.message);
437
private void on_events_inserted (Zeitgeist.Monitor mon,
438
Zeitgeist.TimeRange time_range,
441
// FIXME: Since we don't really have the timestamps for the evens in
442
// our model, we can't insert the events in the correct place
443
// although it's likely fine to just prepend them
445
var results_model = files.entry_renderer_info.results_model;
446
var groups_model = files.entry_renderer_info.groups_model;
448
foreach (var ev in events)
450
if (ev.num_subjects() > 0)
452
// FIXME: We only use the first subject...
453
Zeitgeist.Subject su = ev.get_subject(0);
456
uint group_id = Utils.get_time_group (ev, groups_model);
458
debug ("Notify %s, %s, %u", su.get_uri(), su.get_mimetype(), group_id);
460
results_model.prepend (ResultsColumn.URI, su.get_uri(),
461
ResultsColumn.ICON_HINT, icon,
462
ResultsColumn.GROUP_ID, group_id,
463
ResultsColumn.MIMETYPE, su.get_mimetype(),
464
ResultsColumn.DISPLAY_NAME, su.get_text(),
465
ResultsColumn.COMMENT, su.get_uri(),
472
/* Appends a set of Zeitgeist.Events to our Dee.Model assuming that
473
* these events are already sorted with descending timestamps */
474
public void append_events_sorted (ResultSet events,
478
foreach (var ev in events)
480
if (ev.num_subjects() > 0)
482
// FIXME: We only use the first subject...
483
Zeitgeist.Subject su = ev.get_subject(0);
485
string icon = "";//yield get_icon_for_subject (su);
486
uint group_id = Utils.get_time_group (ev, groups);
488
debug ("Got %s, %s, %u", su.get_uri(), su.get_mimetype(), group_id);
490
results.append (ResultsColumn.URI, su.get_uri(),
491
ResultsColumn.ICON_HINT, icon,
492
ResultsColumn.GROUP_ID, group_id,
493
ResultsColumn.MIMETYPE, su.get_mimetype(),
494
ResultsColumn.DISPLAY_NAME, su.get_text(),
495
ResultsColumn.COMMENT, su.get_uri(),
501
/* Appends a set of Zeitgeist.Events to our Dee.Model assuming that
502
* these events are already sorted according to query relevancy.
503
* The results will be added to the Top Results group */
504
public void append_top_results (ResultSet events,
508
Gee.Set<string> seen_uris = new Gee.HashSet<string>(str_hash);
510
foreach (var ev in events)
512
if (ev.num_subjects() > 0)
514
// FIXME: We only use the first subject...
515
Zeitgeist.Subject su = ev.get_subject(0);
517
/* De-dup the results keyed on the subject URIs */
518
var uri = su.get_uri();
519
if (uri in seen_uris)
525
debug ("Got %s, %s, %u", uri, su.get_mimetype(), Group.TOP_RESULTS);
527
results.append (ResultsColumn.URI, uri,
528
ResultsColumn.ICON_HINT, icon,
529
ResultsColumn.GROUP_ID, Group.TOP_RESULTS,
530
ResultsColumn.MIMETYPE, su.get_mimetype(),
531
ResultsColumn.DISPLAY_NAME, su.get_text(),
532
ResultsColumn.COMMENT, uri,