Merge lp:~fabiozaramella/slingshot/code-style into lp:~elementary-pantheon/slingshot/trunk

Proposed by Fabio Zaramella on 2017-01-24
Status: Merged
Approved by: Corentin Noël on 2017-01-30
Approved revision: 727
Merged at revision: 727
Proposed branch: lp:~fabiozaramella/slingshot/code-style
Merge into: lp:~elementary-pantheon/slingshot/trunk
Diff against target: 10240 lines (+4663/-5362)
20 files modified
lib/synapse-core/common-actions.vala (+341/-417)
lib/synapse-core/config-service.vala (+168/-187)
lib/synapse-core/data-sink.vala (+510/-572)
lib/synapse-core/dbus-service.vala (+143/-164)
lib/synapse-core/desktop-file-service.vala (+558/-628)
lib/synapse-core/match.vala (+131/-142)
lib/synapse-core/plugin.vala (+51/-57)
lib/synapse-core/query.vala (+229/-294)
lib/synapse-core/relevancy-backend-zg.vala (+286/-311)
lib/synapse-core/relevancy-service.vala (+86/-93)
lib/synapse-core/result-set.vala (+94/-119)
lib/synapse-core/utils.vala (+373/-437)
lib/synapse-core/volume-service.vala (+168/-191)
lib/synapse-plugins/calculator-plugin.vala (+126/-147)
lib/synapse-plugins/command-plugin.vala (+174/-186)
lib/synapse-plugins/desktop-file-plugin.vala (+404/-473)
lib/synapse-plugins/link-plugin.vala (+117/-138)
lib/synapse-plugins/switchboard-plugin.vala (+146/-153)
lib/synapse-plugins/system-managment.vala (+428/-523)
po/slingshot.pot (+130/-130)
To merge this branch: bzr merge lp:~fabiozaramella/slingshot/code-style
Reviewer Review Type Date Requested Status
Corentin Noël 2017-01-24 Approve on 2017-01-29
Review via email: mp+315510@code.launchpad.net

Commit message

Synapse-core and synapse-plugins:
* Code style

Description of the change

Synapse-core and synapse-plugins:
* Code style

To post a comment you must log in.
Corentin Noël (tintou) wrote :

can you make so that the _() function has no space ?

726. By Fabio Zaramella on 2017-01-26

add elementary LLC to copyright and remove spaces in _()

Fabio Zaramella (fabiozaramella) wrote :

Done :)

727. By Fabio Zaramella on 2017-01-26

Copyright indentation

Fabio Zaramella (fabiozaramella) wrote :

Can you take a look at it again please?

Corentin Noël (tintou) wrote :

Well, this is not really reviewable so I'll trust you on this. If someone wants to review it, feel free otherwise I'll approve it at the end of the day (UTC)

review: Approve
Fabio Zaramella (fabiozaramella) wrote :

Good, because I will propose another branch for merge soon.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/synapse-core/common-actions.vala'
2--- lib/synapse-core/common-actions.vala 2014-06-04 19:35:44 +0000
3+++ lib/synapse-core/common-actions.vala 2017-01-26 19:54:51 +0000
4@@ -1,419 +1,343 @@
5 /*
6- * Copyright (C) 2010 Michal Hruby <michal.mhr@gmail.com>
7- *
8- * This library is free software; you can redistribute it and/or
9- * modify it under the terms of the GNU Lesser General Public
10- * License as published by the Free Software Foundation; either
11- * version 2.1 of the License, or (at your option) any later version.
12- *
13- * This library is distributed in the hope that it will be useful,
14- * but WITHOUT ANY WARRANTY; without even the implied warranty of
15- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16- * Lesser General Public License for more details.
17- *
18- * You should have received a copy of the GNU Lesser General Public License
19- * along with this program. If not, see <http://www.gnu.org/licenses/>.
20- *
21- * Authored by Michal Hruby <michal.mhr@gmail.com>
22- *
23- */
24-
25-namespace Synapse
26-{
27- public abstract class BaseAction: Object, Match
28- {
29- // from Match interface
30- public string title { get; construct set; }
31- public string description { get; set; }
32- public string icon_name { get; construct set; }
33- public bool has_thumbnail { get; construct set; }
34- public string thumbnail_path { get; construct set; }
35- public MatchType match_type { get; construct set; }
36-
37- public int default_relevancy { get; set; }
38- public bool notify_match { get; set; default = true; }
39-
40- public abstract bool valid_for_match (Match match);
41- public virtual int get_relevancy_for_match (Match match)
42- {
43- return default_relevancy;
44- }
45-
46- public abstract void do_execute (Match? source, Match? target = null);
47- public void execute_with_target (Match? source, Match? target = null)
48- {
49- do_execute (source, target);
50- if (notify_match) source.executed ();
51- }
52-
53- public virtual bool needs_target () {
54- return false;
55- }
56-
57- public virtual QueryFlags target_flags ()
58- {
59- return QueryFlags.ALL;
60- }
61- }
62-
63- public class CommonActions: Object, Activatable, ActionProvider
64- {
65- public bool enabled { get; set; default = true; }
66-
67- public void activate ()
68- {
69-
70- }
71-
72- public void deactivate ()
73- {
74-
75- }
76-
77- private class Runner: BaseAction
78- {
79- public Runner ()
80- {
81- Object (title: _ ("Run"),
82- description: _ ("Run an application, action or script"),
83- icon_name: "system-run", has_thumbnail: false,
84- match_type: MatchType.ACTION,
85- default_relevancy: Match.Score.EXCELLENT);
86- }
87-
88- public override void do_execute (Match? match, Match? target = null)
89- {
90- if (match.match_type == MatchType.APPLICATION)
91- {
92- ApplicationMatch? app_match = match as ApplicationMatch;
93- return_if_fail (app_match != null);
94-
95- AppInfo app = app_match.app_info ??
96- new DesktopAppInfo.from_filename (app_match.filename);
97-
98- try
99- {
100- var display = Gdk.Display.get_default ();
101- app.launch (null, display.get_app_launch_context ());
102-
103- RelevancyService.get_default ().application_launched (app);
104- }
105- catch (Error err)
106- {
107- Utils.Logger.warning (this, "%s", err.message);
108- }
109- }
110- else // MatchType.ACTION
111- {
112- match.execute (null);
113- }
114- }
115-
116- public override bool valid_for_match (Match match)
117- {
118- switch (match.match_type)
119- {
120- case MatchType.SEARCH:
121- return true;
122- case MatchType.ACTION:
123- return true;
124- case MatchType.APPLICATION:
125- ApplicationMatch? am = match as ApplicationMatch;
126- return am == null || !am.needs_terminal;
127- default:
128- return false;
129- }
130- }
131- }
132-
133- private class TerminalRunner: BaseAction
134- {
135- public TerminalRunner ()
136- {
137- Object (title: _ ("Run in Terminal"),
138- description: _ ("Run application or command in terminal"),
139- icon_name: "terminal", has_thumbnail: false,
140- match_type: MatchType.ACTION,
141- default_relevancy: Match.Score.BELOW_AVERAGE);
142- }
143-
144- public override void do_execute (Match? match, Match? target = null)
145- {
146- if (match.match_type == MatchType.APPLICATION)
147- {
148- ApplicationMatch? app_match = match as ApplicationMatch;
149- return_if_fail (app_match != null);
150-
151- AppInfo original = app_match.app_info ??
152- new DesktopAppInfo.from_filename (app_match.filename);
153-
154- try
155- {
156- AppInfo app = AppInfo.create_from_commandline (
157- original.get_commandline (), original.get_name (),
158- AppInfoCreateFlags.NEEDS_TERMINAL);
159- var display = Gdk.Display.get_default ();
160- app.launch (null, display.get_app_launch_context ());
161- }
162- catch (Error err)
163- {
164- Utils.Logger.warning (this, "%s", err.message);
165- }
166- }
167- }
168-
169- public override bool valid_for_match (Match match)
170- {
171- switch (match.match_type)
172- {
173- case MatchType.APPLICATION:
174- ApplicationMatch? am = match as ApplicationMatch;
175- return am != null;
176- default:
177- return false;
178- }
179- }
180- }
181-
182- private class Opener: BaseAction
183- {
184- public Opener ()
185- {
186- Object (title: _ ("Open"),
187- description: _ ("Open using default application"),
188- icon_name: "fileopen", has_thumbnail: false,
189- match_type: MatchType.ACTION,
190- default_relevancy: Match.Score.GOOD);
191- }
192-
193- public override void do_execute (Match? match, Match? target = null)
194- {
195- UriMatch uri_match = match as UriMatch;
196-
197- if (uri_match != null)
198- {
199- CommonActions.open_uri (uri_match.uri);
200- }
201- else if (file_path.match (match.title))
202- {
203- File f;
204- if (match.title.has_prefix ("~"))
205- {
206- f = File.new_for_path (Path.build_filename (Environment.get_home_dir (),
207- match.title.substring (1),
208- null));
209- }
210- else
211- {
212- f = File.new_for_path (match.title);
213- }
214- CommonActions.open_uri (f.get_uri ());
215- }
216- else
217- {
218- CommonActions.open_uri (match.title);
219- }
220- }
221-
222- public override bool valid_for_match (Match match)
223- {
224- switch (match.match_type)
225- {
226- case MatchType.GENERIC_URI:
227- return true;
228- case MatchType.UNKNOWN:
229- return web_uri.match (match.title) || file_path.match (match.title);
230- default:
231- return false;
232- }
233- }
234-
235- private Regex web_uri;
236- private Regex file_path;
237-
238- construct
239- {
240- try
241- {
242- web_uri = new Regex ("^(ftp|http(s)?)://[^.]+\\.[^.]+", RegexCompileFlags.OPTIMIZE);
243- file_path = new Regex ("^(/|~/)[^/]+", RegexCompileFlags.OPTIMIZE);
244- }
245- catch (Error err)
246- {
247- Utils.Logger.warning (this, "%s", err.message);
248- }
249- }
250- }
251-
252- private class OpenFolder: BaseAction
253- {
254- public OpenFolder ()
255- {
256- Object (title: _ ("Open folder"),
257- description: _ ("Open folder containing this file"),
258- icon_name: "folder-open", has_thumbnail: false,
259- match_type: MatchType.ACTION,
260- default_relevancy: Match.Score.AVERAGE);
261- }
262-
263- public override void do_execute (Match? match, Match? target = null)
264- {
265- UriMatch uri_match = match as UriMatch;
266- return_if_fail (uri_match != null);
267- var f = File.new_for_uri (uri_match.uri);
268- f = f.get_parent ();
269- try
270- {
271- var app_info = f.query_default_handler (null);
272- List<File> files = new List<File> ();
273- files.prepend (f);
274- var display = Gdk.Display.get_default ();
275- app_info.launch (files, display.get_app_launch_context ());
276- }
277- catch (Error err)
278- {
279- Utils.Logger.warning (this, "%s", err.message);
280- }
281- }
282-
283- public override bool valid_for_match (Match match)
284- {
285- if (match.match_type != MatchType.GENERIC_URI) return false;
286- UriMatch uri_match = match as UriMatch;
287- var f = File.new_for_uri (uri_match.uri);
288- var parent = f.get_parent ();
289- return parent != null && f.is_native ();
290- }
291- }
292-
293- private class ClipboardCopy: BaseAction
294- {
295- public ClipboardCopy ()
296- {
297- Object (title: _ ("Copy to Clipboard"),
298- description: _ ("Copy selection to clipboard"),
299- icon_name: "gtk-copy", has_thumbnail: false,
300- match_type: MatchType.ACTION,
301- default_relevancy: Match.Score.AVERAGE);
302- }
303-
304- public override void do_execute (Match? match, Match? target = null)
305- {
306- var cb = Gtk.Clipboard.get (Gdk.Atom.NONE);
307- if (match.match_type == MatchType.GENERIC_URI)
308- {
309- UriMatch uri_match = match as UriMatch;
310- return_if_fail (uri_match != null);
311-
312- /*
313- // just wow, Gtk and also Vala are trying really hard to make this hard to do...
314- Gtk.TargetEntry[] no_entries = {};
315- Gtk.TargetList l = new Gtk.TargetList (no_entries);
316- l.add_uri_targets (0);
317- l.add_text_targets (0);
318- Gtk.TargetEntry te = Gtk.target_table_new_from_list (l, 2);
319- cb.set_with_data ();
320- */
321- cb.set_text (uri_match.uri, -1);
322- }
323- else if (match.match_type == MatchType.TEXT)
324- {
325- TextMatch? text_match = match as TextMatch;
326- string content = text_match != null ? text_match.get_text () : match.title;
327-
328- cb.set_text (content, -1);
329- }
330- }
331-
332- public override bool valid_for_match (Match match)
333- {
334- switch (match.match_type)
335- {
336- case MatchType.GENERIC_URI:
337- return true;
338- case MatchType.TEXT:
339- return true;
340- default:
341- return false;
342- }
343- }
344-
345- public override int get_relevancy_for_match (Match match)
346- {
347- TextMatch? text_match = match as TextMatch;
348- if (text_match != null && text_match.text_origin == TextOrigin.CLIPBOARD)
349- {
350- return 0;
351- }
352-
353- return default_relevancy;
354- }
355- }
356-
357- private Gee.List<BaseAction> actions;
358-
359- construct
360- {
361- actions = new Gee.ArrayList<BaseAction> ();
362-
363- actions.add (new Runner ());
364- actions.add (new TerminalRunner ());
365- actions.add (new Opener ());
366- actions.add (new OpenFolder ());
367- actions.add (new ClipboardCopy ());
368- }
369-
370- public ResultSet? find_for_match (ref Query query, Match match)
371- {
372- bool query_empty = query.query_string == "";
373- var results = new ResultSet ();
374-
375- if (query_empty)
376- {
377- foreach (var action in actions)
378- {
379- if (action.valid_for_match (match))
380- {
381- results.add (action, action.get_relevancy_for_match (match));
382- }
383- }
384- }
385- else
386- {
387- var matchers = Query.get_matchers_for_query (query.query_string, 0,
388- RegexCompileFlags.OPTIMIZE | RegexCompileFlags.CASELESS);
389- foreach (var action in actions)
390- {
391- if (!action.valid_for_match (match)) continue;
392- foreach (var matcher in matchers)
393- {
394- if (matcher.key.match (action.title))
395- {
396- results.add (action, matcher.value);
397- break;
398- }
399- }
400- }
401- }
402-
403- return results;
404- }
405-
406- public static void open_uri (string uri)
407- {
408- var f = File.new_for_uri (uri);
409- try
410- {
411- var app_info = f.query_default_handler (null);
412- List<File> files = new List<File> ();
413- files.prepend (f);
414- var display = Gdk.Display.get_default ();
415- app_info.launch (files, display.get_app_launch_context ());
416- }
417- catch (Error err)
418- {
419- Utils.Logger.warning (null, "%s", err.message);
420- }
421- }
422- }
423+* Copyright (c) 2010 Michal Hruby <michal.mhr@gmail.com>
424+* 2017 elementary LLC.
425+*
426+* This program is free software; you can redistribute it and/or
427+* modify it under the terms of the GNU General Public
428+* License as published by the Free Software Foundation; either
429+* version 2 of the License, or (at your option) any later version.
430+*
431+* This program is distributed in the hope that it will be useful,
432+* but WITHOUT ANY WARRANTY; without even the implied warranty of
433+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
434+* General Public License for more details.
435+*
436+* You should have received a copy of the GNU General Public
437+* License along with this program; if not, write to the
438+* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
439+* Boston, MA 02110-1301 USA
440+*
441+* Authored by: Michal Hruby <michal.mhr@gmail.com>
442+*/
443+
444+namespace Synapse {
445+ public abstract class BaseAction: Object, Match {
446+ // from Match interface
447+ public string title { get; construct set; }
448+ public string description { get; set; }
449+ public string icon_name { get; construct set; }
450+ public bool has_thumbnail { get; construct set; }
451+ public string thumbnail_path { get; construct set; }
452+ public MatchType match_type { get; construct set; }
453+
454+ public int default_relevancy { get; set; }
455+ public bool notify_match { get; set; default = true; }
456+
457+ public abstract bool valid_for_match (Match match);
458+ public virtual int get_relevancy_for_match (Match match) {
459+ return default_relevancy;
460+ }
461+
462+ public abstract void do_execute (Match? source, Match? target = null);
463+ public void execute_with_target (Match? source, Match? target = null) {
464+ do_execute (source, target);
465+ if (notify_match) source.executed ();
466+ }
467+
468+ public virtual bool needs_target () {
469+ return false;
470+ }
471+
472+ public virtual QueryFlags target_flags () {
473+ return QueryFlags.ALL;
474+ }
475+ }
476+
477+ public class CommonActions: Object, Activatable, ActionProvider {
478+ public bool enabled { get; set; default = true; }
479+
480+ public void activate () { }
481+
482+ public void deactivate () { }
483+
484+ private class Runner: BaseAction {
485+ public Runner () {
486+ Object (title: _("Run"),
487+ description: _("Run an application, action or script"),
488+ icon_name: "system-run", has_thumbnail: false,
489+ match_type: MatchType.ACTION,
490+ default_relevancy: Match.Score.EXCELLENT);
491+ }
492+
493+ public override void do_execute (Match? match, Match? target = null) {
494+ if (match.match_type == MatchType.APPLICATION) {
495+ ApplicationMatch? app_match = match as ApplicationMatch;
496+ return_if_fail (app_match != null);
497+
498+ AppInfo app = app_match.app_info ??
499+ new DesktopAppInfo.from_filename (app_match.filename);
500+
501+ try {
502+ var display = Gdk.Display.get_default ();
503+ app.launch (null, display.get_app_launch_context ());
504+
505+ RelevancyService.get_default ().application_launched (app);
506+ } catch (Error err) {
507+ Utils.Logger.warning (this, "%s", err.message);
508+ }
509+ } else { // MatchType.ACTION
510+ match.execute (null);
511+ }
512+ }
513+
514+ public override bool valid_for_match (Match match) {
515+ switch (match.match_type) {
516+ case MatchType.SEARCH:
517+ return true;
518+ case MatchType.ACTION:
519+ return true;
520+ case MatchType.APPLICATION:
521+ ApplicationMatch? am = match as ApplicationMatch;
522+ return am == null || !am.needs_terminal;
523+ default:
524+ return false;
525+ }
526+ }
527+ }
528+
529+ private class TerminalRunner: BaseAction {
530+ public TerminalRunner () {
531+ Object (title: _("Run in Terminal"),
532+ description: _("Run application or command in terminal"),
533+ icon_name: "terminal", has_thumbnail: false,
534+ match_type: MatchType.ACTION,
535+ default_relevancy: Match.Score.BELOW_AVERAGE);
536+ }
537+
538+ public override void do_execute (Match? match, Match? target = null) {
539+ if (match.match_type == MatchType.APPLICATION) {
540+ ApplicationMatch? app_match = match as ApplicationMatch;
541+ return_if_fail (app_match != null);
542+
543+ AppInfo original = app_match.app_info ??
544+ new DesktopAppInfo.from_filename (app_match.filename);
545+
546+ try {
547+ AppInfo app = AppInfo.create_from_commandline (
548+ original.get_commandline (), original.get_name (),
549+ AppInfoCreateFlags.NEEDS_TERMINAL);
550+ var display = Gdk.Display.get_default ();
551+ app.launch (null, display.get_app_launch_context ());
552+ } catch (Error err) {
553+ Utils.Logger.warning (this, "%s", err.message);
554+ }
555+ }
556+ }
557+
558+ public override bool valid_for_match (Match match) {
559+ switch (match.match_type) {
560+ case MatchType.APPLICATION:
561+ ApplicationMatch? am = match as ApplicationMatch;
562+ return am != null;
563+ default:
564+ return false;
565+ }
566+ }
567+ }
568+
569+ private class Opener: BaseAction {
570+ public Opener () {
571+ Object (title: _("Open"),
572+ description: _("Open using default application"),
573+ icon_name: "fileopen", has_thumbnail: false,
574+ match_type: MatchType.ACTION,
575+ default_relevancy: Match.Score.GOOD);
576+ }
577+
578+ public override void do_execute (Match? match, Match? target = null) {
579+ UriMatch uri_match = match as UriMatch;
580+
581+ if (uri_match != null) {
582+ CommonActions.open_uri (uri_match.uri);
583+ } else if (file_path.match (match.title)) {
584+ File f;
585+ if (match.title.has_prefix ("~")) {
586+ f = File.new_for_path (Path.build_filename (Environment.get_home_dir (), match.title.substring (1), null));
587+ } else {
588+ f = File.new_for_path (match.title);
589+ }
590+ CommonActions.open_uri (f.get_uri ());
591+ } else {
592+ CommonActions.open_uri (match.title);
593+ }
594+ }
595+
596+ public override bool valid_for_match (Match match) {
597+ switch (match.match_type) {
598+ case MatchType.GENERIC_URI:
599+ return true;
600+ case MatchType.UNKNOWN:
601+ return web_uri.match (match.title) || file_path.match (match.title);
602+ default:
603+ return false;
604+ }
605+ }
606+
607+ private Regex web_uri;
608+ private Regex file_path;
609+
610+ construct {
611+ try {
612+ web_uri = new Regex ("^(ftp|http(s)?)://[^.]+\\.[^.]+", RegexCompileFlags.OPTIMIZE);
613+ file_path = new Regex ("^(/|~/)[^/]+", RegexCompileFlags.OPTIMIZE);
614+ } catch (Error err) {
615+ Utils.Logger.warning (this, "%s", err.message);
616+ }
617+ }
618+ }
619+
620+ private class OpenFolder: BaseAction {
621+ public OpenFolder () {
622+ Object (title: _("Open folder"),
623+ description: _("Open folder containing this file"),
624+ icon_name: "folder-open", has_thumbnail: false,
625+ match_type: MatchType.ACTION,
626+ default_relevancy: Match.Score.AVERAGE);
627+ }
628+
629+ public override void do_execute (Match? match, Match? target = null) {
630+ UriMatch uri_match = match as UriMatch;
631+ return_if_fail (uri_match != null);
632+ var f = File.new_for_uri (uri_match.uri);
633+ f = f.get_parent ();
634+ try {
635+ var app_info = f.query_default_handler (null);
636+ List<File> files = new List<File> ();
637+ files.prepend (f);
638+ var display = Gdk.Display.get_default ();
639+ app_info.launch (files, display.get_app_launch_context ());
640+ } catch (Error err) {
641+ Utils.Logger.warning (this, "%s", err.message);
642+ }
643+ }
644+
645+ public override bool valid_for_match (Match match) {
646+ if (match.match_type != MatchType.GENERIC_URI) {
647+ return false;
648+ }
649+ UriMatch uri_match = match as UriMatch;
650+ var f = File.new_for_uri (uri_match.uri);
651+ var parent = f.get_parent ();
652+
653+ return parent != null && f.is_native ();
654+ }
655+ }
656+
657+ private class ClipboardCopy: BaseAction {
658+ public ClipboardCopy () {
659+ Object (title: _("Copy to Clipboard"),
660+ description: _("Copy selection to clipboard"),
661+ icon_name: "gtk-copy", has_thumbnail: false,
662+ match_type: MatchType.ACTION,
663+ default_relevancy: Match.Score.AVERAGE);
664+ }
665+
666+ public override void do_execute (Match? match, Match? target = null) {
667+ var cb = Gtk.Clipboard.get (Gdk.Atom.NONE);
668+ if (match.match_type == MatchType.GENERIC_URI) {
669+ UriMatch uri_match = match as UriMatch;
670+ return_if_fail (uri_match != null);
671+
672+ /* Just wow, Gtk and also Vala are trying really hard to make this hard to do...
673+ Gtk.TargetEntry[] no_entries = {};
674+ Gtk.TargetList l = new Gtk.TargetList (no_entries);
675+ l.add_uri_targets (0);
676+ l.add_text_targets (0);
677+ Gtk.TargetEntry te = Gtk.target_table_new_from_list (l, 2);
678+ cb.set_with_data ();
679+ */
680+ cb.set_text (uri_match.uri, -1);
681+ } else if (match.match_type == MatchType.TEXT) {
682+ TextMatch? text_match = match as TextMatch;
683+ string content = text_match != null ? text_match.get_text () : match.title;
684+
685+ cb.set_text (content, -1);
686+ }
687+ }
688+
689+ public override bool valid_for_match (Match match) {
690+ switch (match.match_type) {
691+ case MatchType.GENERIC_URI:
692+ return true;
693+ case MatchType.TEXT:
694+ return true;
695+ default:
696+ return false;
697+ }
698+ }
699+
700+ public override int get_relevancy_for_match (Match match) {
701+ TextMatch? text_match = match as TextMatch;
702+ if (text_match != null && text_match.text_origin == TextOrigin.CLIPBOARD) {
703+ return 0;
704+ }
705+
706+ return default_relevancy;
707+ }
708+ }
709+
710+ private Gee.List<BaseAction> actions;
711+
712+ construct {
713+ actions = new Gee.ArrayList<BaseAction> ();
714+
715+ actions.add (new Runner ());
716+ actions.add (new TerminalRunner ());
717+ actions.add (new Opener ());
718+ actions.add (new OpenFolder ());
719+ actions.add (new ClipboardCopy ());
720+ }
721+
722+ public ResultSet? find_for_match (ref Query query, Match match) {
723+ bool query_empty = query.query_string == "";
724+ var results = new ResultSet ();
725+
726+ if (query_empty) {
727+ foreach (var action in actions) {
728+ if (action.valid_for_match (match)) {
729+ results.add (action, action.get_relevancy_for_match (match));
730+ }
731+ }
732+ } else {
733+ var matchers = Query.get_matchers_for_query (query.query_string, 0, RegexCompileFlags.OPTIMIZE | RegexCompileFlags.CASELESS);
734+ foreach (var action in actions) {
735+ if (!action.valid_for_match (match)) {
736+ continue;
737+ }
738+
739+ foreach (var matcher in matchers) {
740+ if (matcher.key.match (action.title)) {
741+ results.add (action, matcher.value);
742+ break;
743+ }
744+ }
745+ }
746+ }
747+
748+ return results;
749+ }
750+
751+ public static void open_uri (string uri) {
752+ var f = File.new_for_uri (uri);
753+ try {
754+ var app_info = f.query_default_handler (null);
755+ List<File> files = new List<File> ();
756+ files.prepend (f);
757+ var display = Gdk.Display.get_default ();
758+ app_info.launch (files, display.get_app_launch_context ());
759+ } catch (Error err) {
760+ Utils.Logger.warning (null, "%s", err.message);
761+ }
762+ }
763+ }
764 }
765
766=== modified file 'lib/synapse-core/config-service.vala'
767--- lib/synapse-core/config-service.vala 2014-06-04 19:35:44 +0000
768+++ lib/synapse-core/config-service.vala 2017-01-26 19:54:51 +0000
769@@ -1,192 +1,173 @@
770 /*
771- * Copyright (C) 2010 Michal Hruby <michal.mhr@gmail.com>
772- *
773- * This library is free software; you can redistribute it and/or
774- * modify it under the terms of the GNU Lesser General Public
775- * License as published by the Free Software Foundation; either
776- * version 2 of the License, or (at your option) any later version.
777- *
778- * This library is distributed in the hope that it will be useful,
779- * but WITHOUT ANY WARRANTY; without even the implied warranty of
780- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
781- * Lesser General Public License for more details.
782- *
783- * You should have received a copy of the GNU Lesser General Public License
784- * along with this program. If not, see <http://www.gnu.org/licenses/>.
785- *
786- * Authored by Michal Hruby <michal.mhr@gmail.com>
787- *
788- */
789+* Copyright (c) 2010 Michal Hruby <michal.mhr@gmail.com>
790+* 2017 elementary LLC.
791+*
792+* This program is free software; you can redistribute it and/or
793+* modify it under the terms of the GNU General Public
794+* License as published by the Free Software Foundation; either
795+* version 2 of the License, or (at your option) any later version.
796+*
797+* This program is distributed in the hope that it will be useful,
798+* but WITHOUT ANY WARRANTY; without even the implied warranty of
799+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
800+* General Public License for more details.
801+*
802+* You should have received a copy of the GNU General Public
803+* License along with this program; if not, write to the
804+* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
805+* Boston, MA 02110-1301 USA
806+*
807+* Authored by: Michal Hruby <michal.mhr@gmail.com>
808+*/
809
810 using Json;
811
812-namespace Synapse
813-{
814- public abstract class ConfigObject : GLib.Object
815- {
816- }
817-
818- public class ConfigService : GLib.Object
819- {
820- // singleton that can be easily destroyed
821- private static unowned ConfigService? instance;
822- public static ConfigService get_default ()
823- {
824- return instance ?? new ConfigService ();
825- }
826-
827- private ConfigService ()
828- {
829- }
830-
831- ~ConfigService ()
832- {
833- // useless cause the timer takes a reference on self
834- if (save_timer_id != 0) save ();
835- instance = null;
836- }
837-
838- private Json.Node root_node;
839- private string config_file_name;
840- private uint save_timer_id = 0;
841-
842- construct
843- {
844- instance = this;
845-
846- var parser = new Parser ();
847- config_file_name =
848- GLib.Path.build_filename (Environment.get_user_config_dir (), "synapse",
849- "config.json");
850- try
851- {
852- parser.load_from_file (config_file_name);
853- root_node = parser.get_root ().copy ();
854- if (root_node.get_node_type () != NodeType.OBJECT)
855- {
856- root_node = new Json.Node (NodeType.OBJECT);
857- root_node.take_object (new Json.Object ());
858- }
859- }
860- catch (Error err)
861- {
862- root_node = new Json.Node (NodeType.OBJECT);
863- root_node.take_object (new Json.Object ());
864- }
865- }
866-
867- /**
868- * Creates an instance of an object derived from ConfigObject class, which
869- * will have its public properties set to values stored in config file, or
870- * to the default values if this object wasn't yet stored.
871- *
872- * @param group A group name.
873- * @param key A key name.
874- * @param config_type Type of the object (must be subclass of ConfigObject)
875- * @return An instance of config_type.
876- */
877- public ConfigObject get_config (string group, string key, Type config_type)
878- {
879- unowned Json.Object obj = root_node.get_object ();
880- unowned Json.Node group_node = obj.get_member (group);
881- if (group_node != null)
882- {
883- if (group_node.get_node_type () == NodeType.OBJECT)
884- {
885- unowned Json.Object group_obj = group_node.get_object ();
886- unowned Json.Node key_node = group_obj.get_member (key);
887- if (key_node != null && key_node.get_node_type () == NodeType.OBJECT)
888- {
889- var result = Json.gobject_deserialize (config_type, key_node);
890- return result as ConfigObject;
891- }
892- }
893- }
894-
895- return GLib.Object.new (config_type) as ConfigObject;
896- }
897-
898- /**
899- * Behaves in a similar way to get_config, but it also watches for changes
900- * in the returned config object and saves them back to the config file
901- * (without the need of calling set_config).
902- *
903- * @param group A group name.
904- * @param key A key name.
905- * @param config_type Type of the object (must be subclass of ConfigObject)
906- */
907- public ConfigObject bind_config (string group, string key, Type config_type)
908- {
909- ConfigObject config_object = get_config (group, key, config_type);
910- // make sure the lambda doesn't take a ref on the config_object
911- unowned ConfigObject co = config_object;
912- co.notify.connect (() => { this.set_config (group, key, co); });
913- return config_object;
914- }
915-
916- /**
917- * Stores all public properties of the object to the config file under
918- * specified group and key names.
919- *
920- * @param group A group name.
921- * @param key A key name.
922- * @param cfg_obj ConfigObject instance.
923- */
924- public void set_config (string group, string key, ConfigObject cfg_obj)
925- {
926- unowned Json.Object obj = root_node.get_object ();
927- if (!obj.has_member (group) ||
928- obj.get_member (group).get_node_type () != NodeType.OBJECT)
929- {
930- // why set_object_member works, but set_member doesn't ?!
931- obj.set_object_member (group, new Json.Object ());
932- }
933-
934- unowned Json.Object group_obj = obj.get_object_member (group);
935- // why the hell is this necessary?
936- if (group_obj.has_member (key)) group_obj.remove_member (key);
937-
938- Json.Node node = Json.gobject_serialize (cfg_obj);
939- group_obj.set_object_member (key, node.get_object ());
940-
941- if (save_timer_id != 0) Source.remove (save_timer_id);
942- // on crap, this takes a reference on self
943- save_timer_id = Timeout.add (30000, this.save_timeout);
944- }
945-
946- private bool save_timeout ()
947- {
948- save_timer_id = 0;
949- save ();
950-
951- return false;
952- }
953-
954- /**
955- * Forces immediate saving of the configuration file to the filesystem.
956- */
957- public void save ()
958- {
959- if (save_timer_id != 0)
960- {
961- Source.remove (save_timer_id);
962- save_timer_id = 0;
963- }
964-
965- var generator = new Generator ();
966- generator.pretty = true;
967- generator.set_root (root_node);
968-
969- DirUtils.create_with_parents (GLib.Path.get_dirname (config_file_name), 0755);
970- try
971- {
972- generator.to_file (config_file_name);
973- }
974- catch (Error err)
975- {
976- warning ("%s", err.message);
977- }
978- }
979- }
980+namespace Synapse {
981+ public abstract class ConfigObject : GLib.Object { }
982+
983+ public class ConfigService : GLib.Object {
984+ // singleton that can be easily destroyed
985+ private static unowned ConfigService? instance;
986+
987+ public static ConfigService get_default () {
988+ return instance ?? new ConfigService ();
989+ }
990+
991+ private ConfigService () { }
992+
993+ ~ConfigService () {
994+ // useless cause the timer takes a reference on self
995+ if (save_timer_id != 0) save ();
996+ instance = null;
997+ }
998+
999+ private Json.Node root_node;
1000+ private string config_file_name;
1001+ private uint save_timer_id = 0;
1002+
1003+ construct {
1004+ instance = this;
1005+
1006+ var parser = new Parser ();
1007+ config_file_name =
1008+ GLib.Path.build_filename (Environment.get_user_config_dir (), "synapse", "config.json");
1009+ try {
1010+ parser.load_from_file (config_file_name);
1011+ root_node = parser.get_root ().copy ();
1012+ if (root_node.get_node_type () != NodeType.OBJECT) {
1013+ root_node = new Json.Node (NodeType.OBJECT);
1014+ root_node.take_object (new Json.Object ());
1015+ }
1016+ } catch (Error err) {
1017+ root_node = new Json.Node (NodeType.OBJECT);
1018+ root_node.take_object (new Json.Object ());
1019+ }
1020+ }
1021+
1022+ /**
1023+ * Creates an instance of an object derived from ConfigObject class, which
1024+ * will have its public properties set to values stored in config file, or
1025+ * to the default values if this object wasn't yet stored.
1026+ *
1027+ * @param group A group name.
1028+ * @param key A key name.
1029+ * @param config_type Type of the object (must be subclass of ConfigObject)
1030+ * @return An instance of config_type.
1031+ */
1032+ public ConfigObject get_config (string group, string key, Type config_type) {
1033+ unowned Json.Object obj = root_node.get_object ();
1034+ unowned Json.Node group_node = obj.get_member (group);
1035+
1036+ if (group_node != null) {
1037+ if (group_node.get_node_type () == NodeType.OBJECT) {
1038+ unowned Json.Object group_obj = group_node.get_object ();
1039+ unowned Json.Node key_node = group_obj.get_member (key);
1040+
1041+ if (key_node != null && key_node.get_node_type () == NodeType.OBJECT) {
1042+ var result = Json.gobject_deserialize (config_type, key_node);
1043+ return result as ConfigObject;
1044+ }
1045+ }
1046+ }
1047+
1048+ return GLib.Object.new (config_type) as ConfigObject;
1049+ }
1050+
1051+ /**
1052+ * Behaves in a similar way to get_config, but it also watches for changes
1053+ * in the returned config object and saves them back to the config file
1054+ * (without the need of calling set_config).
1055+ *
1056+ * @param group A group name.
1057+ * @param key A key name.
1058+ * @param config_type Type of the object (must be subclass of ConfigObject)
1059+ */
1060+ public ConfigObject bind_config (string group, string key, Type config_type) {
1061+ ConfigObject config_object = get_config (group, key, config_type);
1062+ // make sure the lambda doesn't take a ref on the config_object
1063+ unowned ConfigObject co = config_object;
1064+ co.notify.connect (() => { this.set_config (group, key, co); });
1065+
1066+ return config_object;
1067+ }
1068+
1069+ /**
1070+ * Stores all public properties of the object to the config file under
1071+ * specified group and key names.
1072+ *
1073+ * @param group A group name.
1074+ * @param key A key name.
1075+ * @param cfg_obj ConfigObject instance.
1076+ */
1077+ public void set_config (string group, string key, ConfigObject cfg_obj) {
1078+ unowned Json.Object obj = root_node.get_object ();
1079+ if (!obj.has_member (group) || obj.get_member (group).get_node_type () != NodeType.OBJECT) {
1080+ // why set_object_member works, but set_member doesn't ?!
1081+ obj.set_object_member (group, new Json.Object ());
1082+ }
1083+
1084+ unowned Json.Object group_obj = obj.get_object_member (group);
1085+ // why the hell is this necessary?
1086+ if (group_obj.has_member (key)) {
1087+ group_obj.remove_member (key);
1088+ }
1089+
1090+ Json.Node node = Json.gobject_serialize (cfg_obj);
1091+ group_obj.set_object_member (key, node.get_object ());
1092+
1093+ if (save_timer_id != 0) {
1094+ Source.remove (save_timer_id);
1095+ }
1096+ // on crap, this takes a reference on self
1097+ save_timer_id = Timeout.add (30000, this.save_timeout);
1098+ }
1099+
1100+ private bool save_timeout () {
1101+ save_timer_id = 0;
1102+ save ();
1103+
1104+ return false;
1105+ }
1106+
1107+ /**
1108+ * Forces immediate saving of the configuration file to the filesystem.
1109+ */
1110+ public void save () {
1111+ if (save_timer_id != 0) {
1112+ Source.remove (save_timer_id);
1113+ save_timer_id = 0;
1114+ }
1115+
1116+ var generator = new Generator ();
1117+ generator.pretty = true;
1118+ generator.set_root (root_node);
1119+
1120+ DirUtils.create_with_parents (GLib.Path.get_dirname (config_file_name), 0755);
1121+ try {
1122+ generator.to_file (config_file_name);
1123+ } catch (Error err) {
1124+ warning ("%s", err.message);
1125+ }
1126+ }
1127+ }
1128 }
1129-
1130
1131=== modified file 'lib/synapse-core/data-sink.vala'
1132--- lib/synapse-core/data-sink.vala 2014-06-04 19:35:44 +0000
1133+++ lib/synapse-core/data-sink.vala 2017-01-26 19:54:51 +0000
1134@@ -1,574 +1,512 @@
1135 /*
1136- * Copyright (C) 2010 Michal Hruby <michal.mhr@gmail.com>
1137- *
1138- * This library is free software; you can redistribute it and/or
1139- * modify it under the terms of the GNU Lesser General Public
1140- * License as published by the Free Software Foundation; either
1141- * version 2.1 of the License, or (at your option) any later version.
1142- *
1143- * This library is distributed in the hope that it will be useful,
1144- * but WITHOUT ANY WARRANTY; without even the implied warranty of
1145- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
1146- * Lesser General Public License for more details.
1147- *
1148- * You should have received a copy of the GNU Lesser General Public License
1149- * along with this program. If not, see <http://www.gnu.org/licenses/>.
1150- *
1151- * Authored by Michal Hruby <michal.mhr@gmail.com>
1152- *
1153- */
1154-
1155-namespace Synapse
1156-{
1157- public errordomain SearchError
1158- {
1159- SEARCH_CANCELLED,
1160- UNKNOWN_ERROR
1161- }
1162-
1163- public interface SearchProvider : Object
1164- {
1165- public abstract async Gee.List<Match> search (string query,
1166- QueryFlags flags,
1167- ResultSet? dest_result_set,
1168- Cancellable? cancellable = null) throws SearchError;
1169- }
1170-
1171- // don't move into a class, gir doesn't like it
1172- [CCode (has_target = false)]
1173- public delegate void PluginRegisterFunc ();
1174-
1175- public class DataSink : Object, SearchProvider
1176- {
1177- public class PluginRegistry : Object
1178- {
1179- public class PluginInfo
1180- {
1181- public Type plugin_type;
1182- public string title;
1183- public string description;
1184- public string icon_name;
1185- public PluginRegisterFunc register_func;
1186- public bool runnable;
1187- public string runnable_error;
1188- public PluginInfo (Type type, string title, string desc,
1189- string icon_name, PluginRegisterFunc reg_func,
1190- bool runnable, string runnable_error)
1191- {
1192- this.plugin_type = type;
1193- this.title = title;
1194- this.description = desc;
1195- this.icon_name = icon_name;
1196- this.register_func = reg_func;
1197- this.runnable = runnable;
1198- this.runnable_error = runnable_error;
1199- }
1200- }
1201-
1202- public static unowned PluginRegistry instance = null;
1203-
1204- private Gee.List<PluginInfo> plugins;
1205-
1206- construct
1207- {
1208- instance = this;
1209- plugins = new Gee.ArrayList<PluginInfo> ();
1210- }
1211-
1212- ~PluginRegistry ()
1213- {
1214- instance = null;
1215- }
1216-
1217- public static PluginRegistry get_default ()
1218- {
1219- return instance ?? new PluginRegistry ();
1220- }
1221-
1222- public void register_plugin (Type plugin_type,
1223- string title,
1224- string description,
1225- string icon_name,
1226- PluginRegisterFunc reg_func,
1227- bool runnable = true,
1228- string runnable_error = "")
1229- {
1230- // FIXME: how about a frickin Type -> PluginInfo map?!
1231- int index = -1;
1232- for (int i=0; i < plugins.size; i++)
1233- {
1234- if (plugins[i].plugin_type == plugin_type)
1235- {
1236- index = i;
1237- break;
1238- }
1239- }
1240- if (index >= 0) plugins.remove_at (index);
1241-
1242- var p = new PluginInfo (plugin_type, title, description, icon_name,
1243- reg_func, runnable, runnable_error);
1244- plugins.add (p);
1245- }
1246-
1247- public Gee.List<PluginInfo> get_plugins ()
1248- {
1249- return plugins.read_only_view;
1250- }
1251-
1252- public PluginInfo? get_plugin_info_for_type (Type plugin_type)
1253- {
1254- foreach (PluginInfo pi in plugins)
1255- {
1256- if (pi.plugin_type == plugin_type) return pi;
1257- }
1258-
1259- return null;
1260- }
1261- }
1262-
1263- private class DataSinkConfiguration : ConfigObject
1264- {
1265- // vala keeps array lengths, and therefore doesn't support setting arrays
1266- // via automatic public properties
1267- private string[] _disabled_plugins = null;
1268- public string[] disabled_plugins
1269- {
1270- get
1271- {
1272- return _disabled_plugins;
1273- }
1274- set
1275- {
1276- _disabled_plugins = value;
1277- }
1278- }
1279-
1280- public void set_plugin_enabled (Type t, bool enabled)
1281- {
1282- if (enabled) enable_plugin (t.name ());
1283- else disable_plugin (t.name ());
1284- }
1285-
1286- public bool is_plugin_enabled (Type t)
1287- {
1288- if (_disabled_plugins == null) return true;
1289- unowned string plugin_name = t.name ();
1290- foreach (string s in _disabled_plugins)
1291- {
1292- if (s == plugin_name) return false;
1293- }
1294- return true;
1295- }
1296-
1297- private void enable_plugin (string name)
1298- {
1299- if (_disabled_plugins == null) return;
1300- if (!(name in _disabled_plugins)) return;
1301-
1302- string[] cpy = {};
1303- foreach (string s in _disabled_plugins)
1304- {
1305- if (s != name) cpy += s;
1306- }
1307- _disabled_plugins = (owned) cpy;
1308- }
1309-
1310- private void disable_plugin (string name)
1311- {
1312- if (_disabled_plugins == null || !(name in _disabled_plugins))
1313- {
1314- _disabled_plugins += name;
1315- }
1316- }
1317- }
1318-
1319- public DataSink ()
1320- {
1321- }
1322-
1323- ~DataSink ()
1324- {
1325- Utils.Logger.debug (this, "DataSink died...");
1326- }
1327-
1328- private DataSinkConfiguration config;
1329- private Gee.Set<ItemProvider> item_plugins;
1330- private Gee.Set<ActionProvider> action_plugins;
1331- private uint query_id;
1332- // data sink will keep reference to the name cache, so others will get this
1333- // instance on call to get_default()
1334- private DBusService dbus_name_cache;
1335- private DesktopFileService desktop_file_service;
1336- private PluginRegistry registry;
1337- private RelevancyService relevancy_service;
1338- private VolumeService volume_service;
1339- private Type[] plugin_types;
1340-
1341- construct
1342- {
1343- item_plugins = new Gee.HashSet<ItemProvider> ();
1344- action_plugins = new Gee.HashSet<ActionProvider> ();
1345- plugin_types = {};
1346- query_id = 0;
1347-
1348- var cfg = ConfigService.get_default ();
1349- config = (DataSinkConfiguration)
1350- cfg.get_config ("data-sink", "global", typeof (DataSinkConfiguration));
1351-
1352- // oh well, yea we need a few singletons
1353- registry = PluginRegistry.get_default ();
1354- relevancy_service = RelevancyService.get_default ();
1355- volume_service = VolumeService.get_default ();
1356-
1357- initialize_caches.begin ();
1358- register_static_plugin (typeof (CommonActions));
1359- }
1360-
1361- private async void initialize_caches ()
1362- {
1363- Idle.add_full (Priority.LOW, initialize_caches.callback);
1364- yield;
1365-
1366- int initialized_components = 0;
1367- int NUM_COMPONENTS = 2;
1368-
1369- dbus_name_cache = DBusService.get_default ();
1370- dbus_name_cache.initialize.begin (() =>
1371- {
1372- initialized_components++;
1373- if (initialized_components >= NUM_COMPONENTS)
1374- {
1375- initialize_caches.callback ();
1376- }
1377- });
1378-
1379- desktop_file_service = DesktopFileService.get_default ();
1380- desktop_file_service.reload_done.connect (this.check_plugins);
1381- desktop_file_service.initialize.begin (() =>
1382- {
1383- initialized_components++;
1384- if (initialized_components >= NUM_COMPONENTS)
1385- {
1386- initialize_caches.callback ();
1387- }
1388- });
1389-
1390- yield;
1391-
1392- Idle.add (() => { this.load_plugins (); return false; });
1393- }
1394-
1395- private void check_plugins ()
1396- {
1397- PluginRegisterFunc[] reg_funcs = {};
1398- foreach (var pi in registry.get_plugins ())
1399- {
1400- reg_funcs += pi.register_func;
1401- }
1402-
1403- foreach (PluginRegisterFunc func in reg_funcs)
1404- {
1405- func ();
1406- }
1407- }
1408-
1409- public bool has_empty_handlers { get; set; default = false; }
1410- public bool has_unknown_handlers { get; set; default = false; }
1411-
1412- private bool plugins_loaded = false;
1413-
1414- public signal void plugin_registered (Object plugin);
1415-
1416- protected void register_plugin (Object plugin)
1417- {
1418- if (plugin is ActionProvider)
1419- {
1420- ActionProvider action_plugin = plugin as ActionProvider;
1421- action_plugins.add (action_plugin);
1422- has_unknown_handlers |= action_plugin.handles_unknown ();
1423- }
1424- if (plugin is ItemProvider)
1425- {
1426- ItemProvider item_plugin = plugin as ItemProvider;
1427- item_plugins.add (item_plugin);
1428- has_empty_handlers |= item_plugin.handles_empty_query ();
1429- }
1430-
1431- plugin_registered (plugin);
1432- }
1433-
1434- private void update_has_unknown_handlers ()
1435- {
1436- bool tmp = false;
1437- foreach (var action in action_plugins)
1438- {
1439- if (action.enabled && action.handles_unknown ())
1440- {
1441- tmp = true;
1442- break;
1443- }
1444- }
1445- has_unknown_handlers = tmp;
1446- }
1447-
1448- private void update_has_empty_handlers ()
1449- {
1450- bool tmp = false;
1451- foreach (var item_plugin in item_plugins)
1452- {
1453- if (item_plugin.enabled && item_plugin.handles_empty_query ())
1454- {
1455- tmp = true;
1456- break;
1457- }
1458- }
1459- has_empty_handlers = tmp;
1460- }
1461-
1462- private Object? create_plugin (Type t)
1463- {
1464- var obj_class = (ObjectClass) t.class_ref ();
1465- if (obj_class != null && obj_class.find_property ("data-sink") != null)
1466- {
1467- return Object.new (t, "data-sink", this, null);
1468- }
1469- else
1470- {
1471- return Object.new (t, null);
1472- }
1473- }
1474-
1475- private void load_plugins ()
1476- {
1477- // FIXME: fetch and load modules
1478- foreach (Type t in plugin_types)
1479- {
1480- t.class_ref (); // makes the plugin register itself into PluginRegistry
1481- PluginRegistry.PluginInfo? info = registry.get_plugin_info_for_type (t);
1482- bool skip = info != null && info.runnable == false;
1483- if (config.is_plugin_enabled (t) && !skip)
1484- {
1485- var plugin = create_plugin (t);
1486- register_plugin (plugin);
1487- (plugin as Activatable).activate ();
1488- }
1489- }
1490-
1491- plugins_loaded = true;
1492- }
1493-
1494- /* This needs to be called right after instantiation,
1495- * if plugins_loaded == true, it won't have any effect. */
1496- public void register_static_plugin (Type plugin_type)
1497- {
1498- if (plugin_type in plugin_types) return;
1499- plugin_types += plugin_type;
1500- }
1501-
1502- public unowned Object? get_plugin (string name)
1503- {
1504- unowned Object? result = null;
1505-
1506- foreach (var plugin in item_plugins)
1507- {
1508- if (plugin.get_type ().name () == name)
1509- {
1510- result = plugin;
1511- break;
1512- }
1513- }
1514-
1515- return result;
1516- }
1517-
1518- public bool is_plugin_enabled (Type plugin_type)
1519- {
1520- foreach (var plugin in item_plugins)
1521- {
1522- if (plugin.get_type () == plugin_type) return plugin.enabled;
1523- }
1524-
1525- foreach (var action in action_plugins)
1526- {
1527- if (action.get_type () == plugin_type) return action.enabled;
1528- }
1529-
1530- return false;
1531- }
1532-
1533- public void set_plugin_enabled (Type plugin_type, bool enabled)
1534- {
1535- // save it into our config object
1536- config.set_plugin_enabled (plugin_type, enabled);
1537- ConfigService.get_default ().set_config ("data-sink", "global", config);
1538-
1539- foreach (var plugin in item_plugins)
1540- {
1541- if (plugin.get_type () == plugin_type)
1542- {
1543- plugin.enabled = enabled;
1544- if (enabled) plugin.activate ();
1545- else plugin.deactivate ();
1546- update_has_empty_handlers ();
1547- return;
1548- }
1549- }
1550-
1551- foreach (var action in action_plugins)
1552- {
1553- if (action.get_type () == plugin_type)
1554- {
1555- action.enabled = enabled;
1556- if (enabled) action.activate ();
1557- else action.deactivate ();
1558- update_has_unknown_handlers ();
1559- return;
1560- }
1561- }
1562-
1563- // plugin isn't instantiated yet
1564- if (enabled)
1565- {
1566- var new_instance = create_plugin (plugin_type);
1567- register_plugin (new_instance);
1568- (new_instance as Activatable).activate ();
1569- }
1570- }
1571-
1572- [Signal (detailed = true)]
1573- public signal void search_done (ResultSet rs, uint query_id);
1574-
1575- public async Gee.List<Match> search (string query,
1576- QueryFlags flags,
1577- ResultSet? dest_result_set,
1578- Cancellable? cancellable = null) throws SearchError
1579- {
1580- // wait for our initialization
1581- while (!plugins_loaded)
1582- {
1583- Timeout.add (100, search.callback);
1584- yield;
1585- if (cancellable != null && cancellable.is_cancelled ())
1586- {
1587- throw new SearchError.SEARCH_CANCELLED ("Cancelled");
1588- }
1589- }
1590- var q = Query (query_id++, query, flags);
1591- string query_stripped = query.strip ();
1592-
1593- var cancellables = new GLib.List<Cancellable> ();
1594-
1595- var current_result_set = dest_result_set ?? new ResultSet ();
1596- int search_size = item_plugins.size;
1597- // FIXME: this is probably useless, if async method finishes immediately,
1598- // it'll call complete_in_idle
1599- bool waiting = false;
1600-
1601- foreach (var data_plugin in item_plugins)
1602- {
1603- bool skip = !data_plugin.enabled ||
1604- (query == "" && !data_plugin.handles_empty_query ()) ||
1605- !data_plugin.handles_query (q);
1606- if (skip)
1607- {
1608- search_size--;
1609- continue;
1610- }
1611- // we need to pass separate cancellable to each plugin, because we're
1612- // running them in parallel
1613- var c = new Cancellable ();
1614- cancellables.prepend (c);
1615- q.cancellable = c;
1616- // magic comes here
1617- data_plugin.search.begin (q, (src_obj, res) =>
1618- {
1619- var plugin = src_obj as ItemProvider;
1620- try
1621- {
1622- var results = plugin.search.end (res);
1623- this.search_done[plugin.get_type ().name ()] (results, q.query_id);
1624- current_result_set.add_all (results);
1625- }
1626- catch (SearchError err)
1627- {
1628- if (!(err is SearchError.SEARCH_CANCELLED))
1629- {
1630- warning ("%s returned error: %s",
1631- plugin.get_type ().name (), err.message);
1632- }
1633- }
1634-
1635- if (--search_size == 0 && waiting) search.callback ();
1636- });
1637- }
1638- cancellables.reverse ();
1639-
1640- if (cancellable != null)
1641- {
1642- cancellable.connect (() =>
1643- {
1644- foreach (var c in cancellables) c.cancel ();
1645- });
1646- }
1647-
1648- waiting = true;
1649- if (search_size > 0) yield;
1650-
1651- if (cancellable != null && cancellable.is_cancelled ())
1652- {
1653- throw new SearchError.SEARCH_CANCELLED ("Cancelled");
1654- }
1655-
1656- if (has_unknown_handlers && query_stripped != "")
1657- {
1658- var unknown_match = new DefaultMatch (query);
1659- bool add_to_rs = false;
1660- if (QueryFlags.ACTIONS in flags || QueryFlags.TEXT in flags)
1661- {
1662- // FIXME: maybe we should also check here if there are any matches
1663- add_to_rs = true;
1664- }
1665- else
1666- {
1667- // check whether any of the actions support this category
1668- var unknown_match_actions = find_actions_for_unknown_match (unknown_match, flags);
1669- if (unknown_match_actions.size > 0) add_to_rs = true;
1670- }
1671-
1672- if (add_to_rs) current_result_set.add (unknown_match, 0);
1673- }
1674-
1675- return current_result_set.get_sorted_list ();
1676- }
1677-
1678- protected Gee.List<Match> find_actions_for_unknown_match (Match match,
1679- QueryFlags flags)
1680- {
1681- var rs = new ResultSet ();
1682- var q = Query (0, "", flags);
1683- foreach (var action_plugin in action_plugins)
1684- {
1685- if (!action_plugin.enabled) continue;
1686- if (!action_plugin.handles_unknown ()) continue;
1687- rs.add_all (action_plugin.find_for_match (ref q, match));
1688- }
1689-
1690- return rs.get_sorted_list ();
1691- }
1692-
1693- public Gee.List<Match> find_actions_for_match (Match match, string? query,
1694- QueryFlags flags)
1695- {
1696- var rs = new ResultSet ();
1697- var q = Query (0, query ?? "", flags);
1698- foreach (var action_plugin in action_plugins)
1699- {
1700- if (!action_plugin.enabled) continue;
1701- rs.add_all (action_plugin.find_for_match (ref q, match));
1702- }
1703-
1704- return rs.get_sorted_list ();
1705- }
1706- }
1707+* Copyright (c) 2010 Michal Hruby <michal.mhr@gmail.com>
1708+* 2017 elementary LLC.
1709+*
1710+* This program is free software; you can redistribute it and/or
1711+* modify it under the terms of the GNU General Public
1712+* License as published by the Free Software Foundation; either
1713+* version 2 of the License, or (at your option) any later version.
1714+*
1715+* This program is distributed in the hope that it will be useful,
1716+* but WITHOUT ANY WARRANTY; without even the implied warranty of
1717+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
1718+* General Public License for more details.
1719+*
1720+* You should have received a copy of the GNU General Public
1721+* License along with this program; if not, write to the
1722+* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
1723+* Boston, MA 02110-1301 USA
1724+*
1725+* Authored by: Michal Hruby <michal.mhr@gmail.com>
1726+*/
1727+
1728+namespace Synapse {
1729+ public errordomain SearchError {
1730+ SEARCH_CANCELLED,
1731+ UNKNOWN_ERROR
1732+ }
1733+
1734+ public interface SearchProvider : Object {
1735+ public abstract async Gee.List<Match> search (string query,
1736+ QueryFlags flags,
1737+ ResultSet? dest_result_set,
1738+ Cancellable? cancellable = null) throws SearchError;
1739+ }
1740+
1741+ // don't move into a class, gir doesn't like it
1742+ [CCode (has_target = false)]
1743+ public delegate void PluginRegisterFunc ();
1744+
1745+ public class DataSink : Object, SearchProvider {
1746+ public class PluginRegistry : Object {
1747+ public class PluginInfo {
1748+ public Type plugin_type;
1749+ public string title;
1750+ public string description;
1751+ public string icon_name;
1752+ public PluginRegisterFunc register_func;
1753+ public bool runnable;
1754+ public string runnable_error;
1755+
1756+ public PluginInfo (Type type, string title, string desc, string icon_name, PluginRegisterFunc reg_func, bool runnable, string runnable_error) {
1757+ this.plugin_type = type;
1758+ this.title = title;
1759+ this.description = desc;
1760+ this.icon_name = icon_name;
1761+ this.register_func = reg_func;
1762+ this.runnable = runnable;
1763+ this.runnable_error = runnable_error;
1764+ }
1765+ }
1766+
1767+ public static unowned PluginRegistry instance = null;
1768+
1769+ private Gee.List<PluginInfo> plugins;
1770+
1771+ construct {
1772+ instance = this;
1773+ plugins = new Gee.ArrayList<PluginInfo> ();
1774+ }
1775+
1776+ ~PluginRegistry () {
1777+ instance = null;
1778+ }
1779+
1780+ public static PluginRegistry get_default () {
1781+ return instance ?? new PluginRegistry ();
1782+ }
1783+
1784+ public void register_plugin (Type plugin_type, string title, string description, string icon_name, PluginRegisterFunc reg_func, bool runnable = true, string runnable_error = "") {
1785+ // FIXME: how about a frickin Type -> PluginInfo map?!
1786+ int index = -1;
1787+ for (int i = 0; i < plugins.size; i++) {
1788+ if (plugins[i].plugin_type == plugin_type) {
1789+ index = i;
1790+ break;
1791+ }
1792+ }
1793+ if (index >= 0) {
1794+ plugins.remove_at (index);
1795+ }
1796+
1797+ var p = new PluginInfo (plugin_type, title, description, icon_name, reg_func, runnable, runnable_error);
1798+ plugins.add (p);
1799+ }
1800+
1801+ public Gee.List<PluginInfo> get_plugins () {
1802+ return plugins.read_only_view;
1803+ }
1804+
1805+ public PluginInfo? get_plugin_info_for_type (Type plugin_type) {
1806+ foreach (PluginInfo pi in plugins) {
1807+ if (pi.plugin_type == plugin_type) {
1808+ return pi;
1809+ }
1810+ }
1811+
1812+ return null;
1813+ }
1814+ }
1815+
1816+ private class DataSinkConfiguration : ConfigObject {
1817+ // vala keeps array lengths, and therefore doesn't support setting arrays
1818+ // via automatic public properties
1819+ private string[] _disabled_plugins = null;
1820+ public string[] disabled_plugins { get { return _disabled_plugins; } set { _disabled_plugins = value; } }
1821+
1822+ public void set_plugin_enabled (Type t, bool enabled) {
1823+ if (enabled) {
1824+ enable_plugin (t.name ());
1825+ } else {
1826+ disable_plugin (t.name ());
1827+ }
1828+ }
1829+
1830+ public bool is_plugin_enabled (Type t) {
1831+ if (_disabled_plugins == null) {
1832+ return true;
1833+ }
1834+ unowned string plugin_name = t.name ();
1835+ foreach (string s in _disabled_plugins) {
1836+ if (s == plugin_name) {
1837+ return false;
1838+ }
1839+ }
1840+ return true;
1841+ }
1842+
1843+ private void enable_plugin (string name) {
1844+ if (_disabled_plugins == null) {
1845+ return;
1846+ }
1847+ if (!(name in _disabled_plugins)) {
1848+ return;
1849+ }
1850+
1851+ string[] cpy = {};
1852+ foreach (string s in _disabled_plugins) {
1853+ if (s != name) {
1854+ cpy += s;
1855+ }
1856+ }
1857+ _disabled_plugins = (owned) cpy;
1858+ }
1859+
1860+ private void disable_plugin (string name) {
1861+ if (_disabled_plugins == null || !(name in _disabled_plugins)) {
1862+ _disabled_plugins += name;
1863+ }
1864+ }
1865+ }
1866+
1867+ public DataSink () { }
1868+
1869+ ~DataSink () {
1870+ Utils.Logger.debug (this, "DataSink died...");
1871+ }
1872+
1873+ private DataSinkConfiguration config;
1874+ private Gee.Set<ItemProvider> item_plugins;
1875+ private Gee.Set<ActionProvider> action_plugins;
1876+ private uint query_id;
1877+ // data sink will keep reference to the name cache, so others will get this
1878+ // instance on call to get_default()
1879+ private DBusService dbus_name_cache;
1880+ private DesktopFileService desktop_file_service;
1881+ private PluginRegistry registry;
1882+ private RelevancyService relevancy_service;
1883+ private VolumeService volume_service;
1884+ private Type[] plugin_types;
1885+
1886+ construct {
1887+ item_plugins = new Gee.HashSet<ItemProvider> ();
1888+ action_plugins = new Gee.HashSet<ActionProvider> ();
1889+ plugin_types = {};
1890+ query_id = 0;
1891+
1892+ var cfg = ConfigService.get_default ();
1893+ config = (DataSinkConfiguration)
1894+ cfg.get_config ("data-sink", "global", typeof (DataSinkConfiguration));
1895+
1896+ // oh well, yea we need a few singletons
1897+ registry = PluginRegistry.get_default ();
1898+ relevancy_service = RelevancyService.get_default ();
1899+ volume_service = VolumeService.get_default ();
1900+
1901+ initialize_caches.begin ();
1902+ register_static_plugin (typeof (CommonActions));
1903+ }
1904+
1905+ private async void initialize_caches () {
1906+ Idle.add_full (Priority.LOW, initialize_caches.callback);
1907+ yield;
1908+
1909+ int initialized_components = 0;
1910+ int NUM_COMPONENTS = 2;
1911+
1912+ dbus_name_cache = DBusService.get_default ();
1913+ dbus_name_cache.initialize.begin (() => {
1914+ initialized_components++;
1915+ if (initialized_components >= NUM_COMPONENTS) {
1916+ initialize_caches.callback ();
1917+ }
1918+ });
1919+
1920+ desktop_file_service = DesktopFileService.get_default ();
1921+ desktop_file_service.reload_done.connect (this.check_plugins);
1922+ desktop_file_service.initialize.begin (() => {
1923+ initialized_components++;
1924+ if (initialized_components >= NUM_COMPONENTS) {
1925+ initialize_caches.callback ();
1926+ }
1927+ });
1928+
1929+ yield;
1930+
1931+ Idle.add (() => { this.load_plugins (); return false; });
1932+ }
1933+
1934+ private void check_plugins () {
1935+ PluginRegisterFunc[] reg_funcs = {};
1936+ foreach (var pi in registry.get_plugins ()) {
1937+ reg_funcs += pi.register_func;
1938+ }
1939+
1940+ foreach (PluginRegisterFunc func in reg_funcs) {
1941+ func ();
1942+ }
1943+ }
1944+
1945+ public bool has_empty_handlers { get; set; default = false; }
1946+ public bool has_unknown_handlers { get; set; default = false; }
1947+
1948+ private bool plugins_loaded = false;
1949+
1950+ public signal void plugin_registered (Object plugin);
1951+
1952+ protected void register_plugin (Object plugin) {
1953+ if (plugin is ActionProvider) {
1954+ ActionProvider action_plugin = plugin as ActionProvider;
1955+ action_plugins.add (action_plugin);
1956+ has_unknown_handlers |= action_plugin.handles_unknown ();
1957+ }
1958+ if (plugin is ItemProvider) {
1959+ ItemProvider item_plugin = plugin as ItemProvider;
1960+ item_plugins.add (item_plugin);
1961+ has_empty_handlers |= item_plugin.handles_empty_query ();
1962+ }
1963+
1964+ plugin_registered (plugin);
1965+ }
1966+
1967+ private void update_has_unknown_handlers () {
1968+ bool tmp = false;
1969+ foreach (var action in action_plugins) {
1970+ if (action.enabled && action.handles_unknown ()) {
1971+ tmp = true;
1972+ break;
1973+ }
1974+ }
1975+ has_unknown_handlers = tmp;
1976+ }
1977+
1978+ private void update_has_empty_handlers () {
1979+ bool tmp = false;
1980+ foreach (var item_plugin in item_plugins) {
1981+ if (item_plugin.enabled && item_plugin.handles_empty_query ()) {
1982+ tmp = true;
1983+ break;
1984+ }
1985+ }
1986+ has_empty_handlers = tmp;
1987+ }
1988+
1989+ private Object? create_plugin (Type t) {
1990+ var obj_class = (ObjectClass) t.class_ref ();
1991+ if (obj_class != null && obj_class.find_property ("data-sink") != null) {
1992+ return Object.new (t, "data-sink", this, null);
1993+ } else {
1994+ return Object.new (t, null);
1995+ }
1996+ }
1997+
1998+ private void load_plugins () {
1999+ // FIXME: fetch and load modules
2000+ foreach (Type t in plugin_types) {
2001+ t.class_ref (); // makes the plugin register itself into PluginRegistry
2002+ PluginRegistry.PluginInfo? info = registry.get_plugin_info_for_type (t);
2003+ bool skip = info != null && info.runnable == false;
2004+ if (config.is_plugin_enabled (t) && !skip) {
2005+ var plugin = create_plugin (t);
2006+ register_plugin (plugin);
2007+ (plugin as Activatable).activate ();
2008+ }
2009+ }
2010+
2011+ plugins_loaded = true;
2012+ }
2013+
2014+ /* This needs to be called right after instantiation,
2015+ * if plugins_loaded == true, it won't have any effect. */
2016+ public void register_static_plugin (Type plugin_type) {
2017+ if (plugin_type in plugin_types) {
2018+ return;
2019+ }
2020+ plugin_types += plugin_type;
2021+ }
2022+
2023+ public unowned Object? get_plugin (string name) {
2024+ unowned Object? result = null;
2025+
2026+ foreach (var plugin in item_plugins) {
2027+ if (plugin.get_type ().name () == name) {
2028+ result = plugin;
2029+ break;
2030+ }
2031+ }
2032+
2033+ return result;
2034+ }
2035+
2036+ public bool is_plugin_enabled (Type plugin_type) {
2037+ foreach (var plugin in item_plugins) {
2038+ if (plugin.get_type () == plugin_type) {
2039+ return plugin.enabled;
2040+ }
2041+ }
2042+
2043+ foreach (var action in action_plugins) {
2044+ if (action.get_type () == plugin_type) {
2045+ return action.enabled;
2046+ }
2047+ }
2048+
2049+ return false;
2050+ }
2051+
2052+ public void set_plugin_enabled (Type plugin_type, bool enabled) {
2053+ // save it into our config object
2054+ config.set_plugin_enabled (plugin_type, enabled);
2055+ ConfigService.get_default ().set_config ("data-sink", "global", config);
2056+
2057+ foreach (var plugin in item_plugins) {
2058+ if (plugin.get_type () == plugin_type) {
2059+ plugin.enabled = enabled;
2060+ if (enabled) {
2061+ plugin.activate ();
2062+ } else {
2063+ plugin.deactivate ();
2064+ }
2065+ update_has_empty_handlers ();
2066+
2067+ return;
2068+ }
2069+ }
2070+
2071+ foreach (var action in action_plugins) {
2072+ if (action.get_type () == plugin_type) {
2073+ action.enabled = enabled;
2074+ if (enabled) {
2075+ action.activate ();
2076+ } else {
2077+ action.deactivate ();
2078+ }
2079+ update_has_unknown_handlers ();
2080+
2081+ return;
2082+ }
2083+ }
2084+
2085+ // plugin isn't instantiated yet
2086+ if (enabled) {
2087+ var new_instance = create_plugin (plugin_type);
2088+ register_plugin (new_instance);
2089+ (new_instance as Activatable).activate ();
2090+ }
2091+ }
2092+
2093+ [Signal (detailed = true)]
2094+ public signal void search_done (ResultSet rs, uint query_id);
2095+
2096+ public async Gee.List<Match> search (string query, QueryFlags flags, ResultSet? dest_result_set, Cancellable? cancellable = null) throws SearchError {
2097+ // wait for our initialization
2098+ while (!plugins_loaded) {
2099+ Timeout.add (100, search.callback);
2100+ yield;
2101+ if (cancellable != null && cancellable.is_cancelled ()) {
2102+ throw new SearchError.SEARCH_CANCELLED ("Cancelled");
2103+ }
2104+ }
2105+
2106+ var q = Query (query_id++, query, flags);
2107+ string query_stripped = query.strip ();
2108+
2109+ var cancellables = new GLib.List<Cancellable> ();
2110+
2111+ var current_result_set = dest_result_set ?? new ResultSet ();
2112+ int search_size = item_plugins.size;
2113+ // FIXME: this is probably useless, if async method finishes immediately,
2114+ // it'll call complete_in_idle
2115+ bool waiting = false;
2116+
2117+ foreach (var data_plugin in item_plugins) {
2118+ bool skip = !data_plugin.enabled || (query == "" && !data_plugin.handles_empty_query ()) || !data_plugin.handles_query (q);
2119+ if (skip) {
2120+ search_size--;
2121+ continue;
2122+ }
2123+ // we need to pass separate cancellable to each plugin, because we're
2124+ // running them in parallel
2125+ var c = new Cancellable ();
2126+ cancellables.prepend (c);
2127+ q.cancellable = c;
2128+ // magic comes here
2129+ data_plugin.search.begin (q, (src_obj, res) => {
2130+ var plugin = src_obj as ItemProvider;
2131+ try {
2132+ var results = plugin.search.end (res);
2133+ this.search_done[plugin.get_type ().name ()] (results, q.query_id);
2134+ current_result_set.add_all (results);
2135+ } catch (SearchError err) {
2136+ if (!(err is SearchError.SEARCH_CANCELLED)) {
2137+ warning ("%s returned error: %s",
2138+ plugin.get_type ().name (), err.message);
2139+ }
2140+ }
2141+
2142+ if (--search_size == 0 && waiting) {
2143+ search.callback ();
2144+ }
2145+ });
2146+ }
2147+ cancellables.reverse ();
2148+
2149+ if (cancellable != null) {
2150+ cancellable.connect (() => {
2151+ foreach (var c in cancellables) {
2152+ c.cancel ();
2153+ }
2154+ });
2155+ }
2156+
2157+ waiting = true;
2158+ if (search_size > 0) {
2159+ yield;
2160+ }
2161+
2162+ if (cancellable != null && cancellable.is_cancelled ()) {
2163+ throw new SearchError.SEARCH_CANCELLED ("Cancelled");
2164+ }
2165+
2166+ if (has_unknown_handlers && query_stripped != "") {
2167+ var unknown_match = new DefaultMatch (query);
2168+ bool add_to_rs = false;
2169+
2170+ if (QueryFlags.ACTIONS in flags || QueryFlags.TEXT in flags) {
2171+ // FIXME: maybe we should also check here if there are any matches
2172+ add_to_rs = true;
2173+ } else {
2174+ // check whether any of the actions support this category
2175+ var unknown_match_actions = find_actions_for_unknown_match (unknown_match, flags);
2176+ if (unknown_match_actions.size > 0) add_to_rs = true;
2177+ }
2178+
2179+ if (add_to_rs) current_result_set.add (unknown_match, 0);
2180+ }
2181+
2182+ return current_result_set.get_sorted_list ();
2183+ }
2184+
2185+ protected Gee.List<Match> find_actions_for_unknown_match (Match match, QueryFlags flags) {
2186+ var rs = new ResultSet ();
2187+ var q = Query (0, "", flags);
2188+
2189+ foreach (var action_plugin in action_plugins) {
2190+ if (!action_plugin.enabled) {
2191+ continue;
2192+ }
2193+ if (!action_plugin.handles_unknown ()) {
2194+ continue;
2195+ }
2196+ rs.add_all (action_plugin.find_for_match (ref q, match));
2197+ }
2198+
2199+ return rs.get_sorted_list ();
2200+ }
2201+
2202+ public Gee.List<Match> find_actions_for_match (Match match, string? query, QueryFlags flags) {
2203+ var rs = new ResultSet ();
2204+ var q = Query (0, query ?? "", flags);
2205+
2206+ foreach (var action_plugin in action_plugins) {
2207+ if (!action_plugin.enabled) {
2208+ continue;
2209+ }
2210+
2211+ rs.add_all (action_plugin.find_for_match (ref q, match));
2212+ }
2213+
2214+ return rs.get_sorted_list ();
2215+ }
2216+ }
2217 }
2218-
2219
2220=== modified file 'lib/synapse-core/dbus-service.vala'
2221--- lib/synapse-core/dbus-service.vala 2014-06-04 19:35:44 +0000
2222+++ lib/synapse-core/dbus-service.vala 2017-01-26 19:54:51 +0000
2223@@ -1,166 +1,145 @@
2224 /*
2225- * Copyright (C) 2010 Michal Hruby <michal.mhr@gmail.com>
2226- *
2227- * This library is free software; you can redistribute it and/or
2228- * modify it under the terms of the GNU Lesser General Public
2229- * License as published by the Free Software Foundation; either
2230- * version 2.1 of the License, or (at your option) any later version.
2231- *
2232- * This library is distributed in the hope that it will be useful,
2233- * but WITHOUT ANY WARRANTY; without even the implied warranty of
2234- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
2235- * Lesser General Public License for more details.
2236- *
2237- * You should have received a copy of the GNU Lesser General Public License
2238- * along with this program. If not, see <http://www.gnu.org/licenses/>.
2239- *
2240- * Authored by Michal Hruby <michal.mhr@gmail.com>
2241- *
2242- */
2243-
2244-namespace Synapse
2245-{
2246- [DBus (name = "org.freedesktop.DBus")]
2247- public interface FreeDesktopDBus : GLib.Object
2248- {
2249- public const string UNIQUE_NAME = "org.freedesktop.DBus";
2250- public const string OBJECT_PATH = "/org/freedesktop/DBus";
2251-
2252- public abstract async string[] list_queued_owners (string name) throws IOError;
2253- public abstract async string[] list_names () throws IOError;
2254- public abstract async string[] list_activatable_names () throws IOError;
2255- public abstract async bool name_has_owner (string name) throws IOError;
2256- public signal void name_owner_changed (string name,
2257- string old_owner,
2258- string new_owner);
2259- public abstract async uint32 start_service_by_name (string name,
2260- uint32 flags) throws IOError;
2261- public abstract async string get_name_owner (string name) throws IOError;
2262- }
2263-
2264- public class DBusService : Object
2265- {
2266- private FreeDesktopDBus proxy;
2267- private Gee.Set<string> owned_names;
2268- private Gee.Set<string> activatable_names;
2269- private Gee.Set<string> system_activatable_names;
2270-
2271- private Utils.AsyncOnce<bool> init_once;
2272-
2273- // singleton that can be easily destroyed
2274- public static DBusService get_default ()
2275- {
2276- return instance ?? new DBusService ();
2277- }
2278-
2279- private DBusService ()
2280- {
2281- }
2282-
2283- private static unowned DBusService? instance;
2284- construct
2285- {
2286- instance = this;
2287- owned_names = new Gee.HashSet<string> ();
2288- activatable_names = new Gee.HashSet<string> ();
2289- system_activatable_names = new Gee.HashSet<string> ();
2290- init_once = new Utils.AsyncOnce<bool> ();
2291-
2292- initialize.begin ();
2293- }
2294-
2295- ~DBusService ()
2296- {
2297- instance = null;
2298- }
2299-
2300- private void name_owner_changed (FreeDesktopDBus sender,
2301- string name,
2302- string old_owner,
2303- string new_owner)
2304- {
2305- if (name.has_prefix (":")) return;
2306-
2307- if (old_owner == "")
2308- {
2309- owned_names.add (name);
2310- owner_changed (name, true);
2311- }
2312- else if (new_owner == "")
2313- {
2314- owned_names.remove (name);
2315- owner_changed (name, false);
2316- }
2317- }
2318-
2319- public signal void owner_changed (string name, bool is_owned);
2320-
2321- public bool name_has_owner (string name)
2322- {
2323- return name in owned_names;
2324- }
2325-
2326- public bool name_is_activatable (string name)
2327- {
2328- return name in activatable_names;
2329- }
2330-
2331- public bool service_is_available (string name)
2332- {
2333- return name in system_activatable_names;
2334- }
2335-
2336- public async void initialize ()
2337- {
2338- if (init_once.is_initialized ()) return;
2339- var is_locked = yield init_once.enter ();
2340- if (!is_locked) return;
2341-
2342- string[] names;
2343- try
2344- {
2345- proxy = Bus.get_proxy_sync (BusType.SESSION,
2346- FreeDesktopDBus.UNIQUE_NAME,
2347- FreeDesktopDBus.OBJECT_PATH);
2348-
2349- proxy.name_owner_changed.connect (this.name_owner_changed);
2350- names = yield proxy.list_names ();
2351- foreach (unowned string name in names)
2352- {
2353- if (name.has_prefix (":")) continue;
2354- owned_names.add (name);
2355- }
2356-
2357- names = yield proxy.list_activatable_names ();
2358- foreach (unowned string session_act in names)
2359- {
2360- activatable_names.add (session_act);
2361- }
2362- }
2363- catch (Error err)
2364- {
2365- warning ("%s", err.message);
2366- }
2367-
2368- try
2369- {
2370- FreeDesktopDBus sys_proxy = Bus.get_proxy_sync (
2371- BusType.SYSTEM,
2372- FreeDesktopDBus.UNIQUE_NAME,
2373- FreeDesktopDBus.OBJECT_PATH);
2374-
2375- names = yield sys_proxy.list_activatable_names ();
2376- foreach (unowned string system_act in names)
2377- {
2378- system_activatable_names.add (system_act);
2379- }
2380- }
2381- catch (Error sys_err)
2382- {
2383- warning ("%s", sys_err.message);
2384- }
2385- init_once.leave (true);
2386- }
2387- }
2388+* Copyright (c) 2010 Michal Hruby <michal.mhr@gmail.com>
2389+* 2017 elementary LLC.
2390+*
2391+* This program is free software; you can redistribute it and/or
2392+* modify it under the terms of the GNU General Public
2393+* License as published by the Free Software Foundation; either
2394+* version 2 of the License, or (at your option) any later version.
2395+*
2396+* This program is distributed in the hope that it will be useful,
2397+* but WITHOUT ANY WARRANTY; without even the implied warranty of
2398+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
2399+* General Public License for more details.
2400+*
2401+* You should have received a copy of the GNU General Public
2402+* License along with this program; if not, write to the
2403+* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
2404+* Boston, MA 02110-1301 USA
2405+*
2406+* Authored by: Michal Hruby <michal.mhr@gmail.com>
2407+*/
2408+
2409+namespace Synapse {
2410+ [DBus (name = "org.freedesktop.DBus")]
2411+ public interface FreeDesktopDBus : GLib.Object {
2412+ public const string UNIQUE_NAME = "org.freedesktop.DBus";
2413+ public const string OBJECT_PATH = "/org/freedesktop/DBus";
2414+
2415+ public abstract async string[] list_queued_owners (string name) throws IOError;
2416+ public abstract async string[] list_names () throws IOError;
2417+ public abstract async string[] list_activatable_names () throws IOError;
2418+ public abstract async bool name_has_owner (string name) throws IOError;
2419+ public signal void name_owner_changed (string name, string old_owner, string new_owner);
2420+ public abstract async uint32 start_service_by_name (string name, uint32 flags) throws IOError;
2421+ public abstract async string get_name_owner (string name) throws IOError;
2422+ }
2423+
2424+ public class DBusService : Object {
2425+ private FreeDesktopDBus proxy;
2426+ private Gee.Set<string> owned_names;
2427+ private Gee.Set<string> activatable_names;
2428+ private Gee.Set<string> system_activatable_names;
2429+
2430+ private Utils.AsyncOnce<bool> init_once;
2431+
2432+ // singleton that can be easily destroyed
2433+ public static DBusService get_default () {
2434+ return instance ?? new DBusService ();
2435+ }
2436+
2437+ private DBusService () { }
2438+
2439+ private static unowned DBusService? instance;
2440+
2441+ construct {
2442+ instance = this;
2443+ owned_names = new Gee.HashSet<string> ();
2444+ activatable_names = new Gee.HashSet<string> ();
2445+ system_activatable_names = new Gee.HashSet<string> ();
2446+ init_once = new Utils.AsyncOnce<bool> ();
2447+
2448+ initialize.begin ();
2449+ }
2450+
2451+ ~DBusService () {
2452+ instance = null;
2453+ }
2454+
2455+ private void name_owner_changed (FreeDesktopDBus sender, string name, string old_owner, string new_owner) {
2456+ if (name.has_prefix (":")) {
2457+ return;
2458+ }
2459+
2460+ if (old_owner == "") {
2461+ owned_names.add (name);
2462+ owner_changed (name, true);
2463+ } else if (new_owner == "") {
2464+ owned_names.remove (name);
2465+ owner_changed (name, false);
2466+ }
2467+ }
2468+
2469+ public signal void owner_changed (string name, bool is_owned);
2470+
2471+ public bool name_has_owner (string name) {
2472+ return name in owned_names;
2473+ }
2474+
2475+ public bool name_is_activatable (string name) {
2476+ return name in activatable_names;
2477+ }
2478+
2479+ public bool service_is_available (string name) {
2480+ return name in system_activatable_names;
2481+ }
2482+
2483+ public async void initialize () {
2484+ if (init_once.is_initialized ()) {
2485+ return;
2486+ }
2487+ var is_locked = yield init_once.enter ();
2488+ if (!is_locked) {
2489+ return;
2490+ }
2491+
2492+ string[] names;
2493+ try {
2494+ proxy = Bus.get_proxy_sync (BusType.SESSION,
2495+ FreeDesktopDBus.UNIQUE_NAME,
2496+ FreeDesktopDBus.OBJECT_PATH);
2497+
2498+ proxy.name_owner_changed.connect (this.name_owner_changed);
2499+ names = yield proxy.list_names ();
2500+ foreach (unowned string name in names) {
2501+ if (name.has_prefix (":")) {
2502+ continue;
2503+ }
2504+ owned_names.add (name);
2505+ }
2506+
2507+ names = yield proxy.list_activatable_names ();
2508+ foreach (unowned string session_act in names) {
2509+ activatable_names.add (session_act);
2510+ }
2511+ } catch (Error err) {
2512+ warning ("%s", err.message);
2513+ }
2514+
2515+ try {
2516+ FreeDesktopDBus sys_proxy = Bus.get_proxy_sync (
2517+ BusType.SYSTEM,
2518+ FreeDesktopDBus.UNIQUE_NAME,
2519+ FreeDesktopDBus.OBJECT_PATH);
2520+
2521+ names = yield sys_proxy.list_activatable_names ();
2522+ foreach (unowned string system_act in names) {
2523+ system_activatable_names.add (system_act);
2524+ }
2525+ } catch (Error sys_err) {
2526+ warning ("%s", sys_err.message);
2527+ }
2528+ init_once.leave (true);
2529+ }
2530+ }
2531 }
2532-
2533
2534=== modified file 'lib/synapse-core/desktop-file-service.vala'
2535--- lib/synapse-core/desktop-file-service.vala 2017-01-15 23:01:25 +0000
2536+++ lib/synapse-core/desktop-file-service.vala 2017-01-26 19:54:51 +0000
2537@@ -1,630 +1,560 @@
2538 /*
2539- * Copyright (C) 2010 Michal Hruby <michal.mhr@gmail.com>
2540- *
2541- * This library is free software; you can redistribute it and/or
2542- * modify it under the terms of the GNU Lesser General Public
2543- * License as published by the Free Software Foundation; either
2544- * version 2.1 of the License, or (at your option) any later version.
2545- *
2546- * This library is distributed in the hope that it will be useful,
2547- * but WITHOUT ANY WARRANTY; without even the implied warranty of
2548- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
2549- * Lesser General Public License for more details.
2550- *
2551- * You should have received a copy of the GNU Lesser General Public License
2552- * along with this program. If not, see <http://www.gnu.org/licenses/>.
2553- *
2554- * Authored by Michal Hruby <michal.mhr@gmail.com>
2555- * Alberto Aldegheri <albyrock87+dev@gmail.com>
2556- *
2557- */
2558-
2559-namespace Synapse
2560-{
2561- errordomain DesktopFileError
2562- {
2563- UNINTERESTING_ENTRY
2564- }
2565-
2566- public class DesktopFileInfo: Object
2567- {
2568- // registered environments from http://standards.freedesktop.org/menu-spec/latest
2569- // (and pantheon)
2570- [Flags]
2571- public enum EnvironmentType
2572- {
2573- GNOME = 1 << 0,
2574- KDE = 1 << 1,
2575- LXDE = 1 << 2,
2576- MATE = 1 << 3,
2577- RAZOR = 1 << 4,
2578- ROX = 1 << 5,
2579- TDE = 1 << 6,
2580- UNITY = 1 << 7,
2581- XFCE = 1 << 8,
2582- PANTHEON = 1 << 9,
2583- OLD = 1 << 10,
2584-
2585- ALL = 0x3FF
2586- }
2587-
2588- public string desktop_id { get; construct set; }
2589- public string name { get; construct set; }
2590- public string generic_name { get; construct set; }
2591- public string comment { get; set; default = ""; }
2592- public string icon_name { get; construct set; default = ""; }
2593- public string gettext_domain { get; construct set; }
2594-
2595- public bool needs_terminal { get; set; default = false; }
2596- public string filename { get; construct set; }
2597-
2598- public string exec { get; set; }
2599-
2600- public bool is_hidden { get; private set; default = false; }
2601- public bool is_valid { get; private set; default = true; }
2602-
2603- public string[] mime_types = null;
2604-
2605- private string? name_folded = null;
2606- public unowned string get_name_folded ()
2607- {
2608- if (name_folded == null) name_folded = name.casefold ();
2609- return name_folded;
2610- }
2611-
2612- public EnvironmentType show_in { get; set; default = EnvironmentType.ALL; }
2613-
2614- private const string[] SUPPORTED_GETTEXT_DOMAINS_KEYS = {"X-Ubuntu-Gettext-Domain", "X-GNOME-Gettext-Domain"};
2615- private const string GROUP = "Desktop Entry";
2616-
2617- public DesktopFileInfo.for_keyfile (string path, KeyFile keyfile,
2618- string desktop_id)
2619- {
2620- Object (filename: path, desktop_id: desktop_id);
2621-
2622- init_from_keyfile (keyfile);
2623- }
2624-
2625- private EnvironmentType parse_environments (string[] environments)
2626- {
2627- EnvironmentType result = 0;
2628- foreach (unowned string env in environments)
2629- {
2630- string env_up = env.up ();
2631- switch (env_up)
2632- {
2633- case "GNOME": result |= EnvironmentType.GNOME; break;
2634- case "PANTHEON": result |= EnvironmentType.PANTHEON; break;
2635- case "KDE": result |= EnvironmentType.KDE; break;
2636- case "LXDE": result |= EnvironmentType.LXDE; break;
2637- case "MATE": result |= EnvironmentType.MATE; break;
2638- case "RAZOR": result |= EnvironmentType.RAZOR; break;
2639- case "ROX": result |= EnvironmentType.ROX; break;
2640- case "TDE": result |= EnvironmentType.TDE; break;
2641- case "UNITY": result |= EnvironmentType.UNITY; break;
2642- case "XFCE": result |= EnvironmentType.XFCE; break;
2643- case "OLD": result |= EnvironmentType.OLD; break;
2644- default: warning ("%s is not understood", env); break;
2645- }
2646- }
2647- return result;
2648- }
2649-
2650- private void init_from_keyfile (KeyFile keyfile)
2651- {
2652- try
2653- {
2654- if (keyfile.get_string (GROUP, "Type") != "Application")
2655- {
2656- throw new DesktopFileError.UNINTERESTING_ENTRY ("Not Application-type desktop entry");
2657- }
2658-
2659- if (keyfile.has_key (GROUP, "Categories"))
2660- {
2661- string[] categories = keyfile.get_string_list (GROUP, "Categories");
2662- if ("Screensaver" in categories)
2663- {
2664- throw new DesktopFileError.UNINTERESTING_ENTRY ("Screensaver desktop entry");
2665- }
2666- }
2667- foreach (var domain_key in SUPPORTED_GETTEXT_DOMAINS_KEYS) {
2668- if (keyfile.has_key (GROUP, domain_key)) {
2669- gettext_domain = keyfile.get_string (GROUP, domain_key);
2670- break;
2671- }
2672- }
2673-
2674- DesktopAppInfo app_info;
2675- app_info = new DesktopAppInfo.from_keyfile (keyfile);
2676-
2677- if (app_info == null)
2678- {
2679- throw new DesktopFileError.UNINTERESTING_ENTRY ("Unable to create AppInfo");
2680- }
2681-
2682- name = app_info.get_name ();
2683- generic_name = app_info.get_generic_name () ?? "";
2684- exec = app_info.get_commandline ();
2685- if (exec == null)
2686- {
2687- throw new DesktopFileError.UNINTERESTING_ENTRY ("Unable to get exec for %s".printf (name));
2688- }
2689-
2690- // check for hidden desktop files
2691- if (keyfile.has_key (GROUP, "Hidden") &&
2692- keyfile.get_boolean (GROUP, "Hidden"))
2693- {
2694- is_hidden = true;
2695- }
2696- if (keyfile.has_key (GROUP, "NoDisplay") &&
2697- keyfile.get_boolean (GROUP, "NoDisplay"))
2698- {
2699- is_hidden = true;
2700- }
2701-
2702- comment = app_info.get_description () ?? "";
2703-
2704- var icon = app_info.get_icon () ??
2705- new ThemedIcon ("application-default-icon");
2706- icon_name = icon.to_string ();
2707-
2708- if (keyfile.has_key (GROUP, "MimeType"))
2709- {
2710- mime_types = keyfile.get_string_list (GROUP, "MimeType");
2711- }
2712- if (keyfile.has_key (GROUP, "Terminal"))
2713- {
2714- needs_terminal = keyfile.get_boolean (GROUP, "Terminal");
2715- }
2716- if (keyfile.has_key (GROUP, "OnlyShowIn"))
2717- {
2718- show_in = parse_environments (keyfile.get_string_list (GROUP,
2719- "OnlyShowIn"));
2720- }
2721- else if (keyfile.has_key (GROUP, "NotShowIn"))
2722- {
2723- var not_show = parse_environments (keyfile.get_string_list (GROUP,
2724- "NotShowIn"));
2725- show_in = EnvironmentType.ALL ^ not_show;
2726- }
2727-
2728- // special case these, people are using them quite often and wonder
2729- // why they don't appear
2730- if (filename.has_suffix ("gconf-editor.desktop") ||
2731- filename.has_suffix ("dconf-editor.desktop"))
2732- {
2733- is_hidden = false;
2734- }
2735- }
2736- catch (Error err)
2737- {
2738- Utils.Logger.warning (this, "%s", err.message);
2739- is_valid = false;
2740- }
2741- }
2742- }
2743-
2744- public class DesktopFileService : Object
2745- {
2746- private static unowned DesktopFileService? instance;
2747- private Utils.AsyncOnce<bool> init_once;
2748-
2749- // singleton that can be easily destroyed
2750- public static DesktopFileService get_default ()
2751- {
2752- return instance ?? new DesktopFileService ();
2753- }
2754-
2755- private DesktopFileService ()
2756- {
2757- }
2758-
2759- private Gee.List<FileMonitor> directory_monitors;
2760- private Gee.List<DesktopFileInfo> all_desktop_files;
2761- private Gee.List<DesktopFileInfo> non_hidden_desktop_files;
2762- private Gee.Map<unowned string, Gee.List<DesktopFileInfo> > mimetype_map;
2763- private Gee.Map<string, Gee.List<DesktopFileInfo> > exec_map;
2764- private Gee.Map<string, DesktopFileInfo> desktop_id_map;
2765- private Gee.MultiMap<string, string> mimetype_parent_map;
2766-
2767- construct
2768- {
2769- instance = this;
2770-
2771- directory_monitors = new Gee.ArrayList<FileMonitor> ();
2772- all_desktop_files = new Gee.ArrayList<DesktopFileInfo> ();
2773- non_hidden_desktop_files = new Gee.ArrayList<DesktopFileInfo> ();
2774- mimetype_parent_map = new Gee.HashMultiMap<string, string> ();
2775- init_once = new Utils.AsyncOnce<bool> ();
2776-
2777- initialize.begin ();
2778- }
2779-
2780- ~DesktopFileService ()
2781- {
2782- instance = null;
2783- }
2784-
2785- public async void initialize ()
2786- {
2787- if (init_once.is_initialized ()) return;
2788- var is_locked = yield init_once.enter ();
2789- if (!is_locked) return;
2790-
2791- get_environment_type ();
2792- DesktopAppInfo.set_desktop_env (session_type_str);
2793-
2794- Idle.add_full (Priority.LOW, initialize.callback);
2795- yield;
2796-
2797- yield load_all_desktop_files ();
2798-
2799- init_once.leave (true);
2800- }
2801-
2802- private DesktopFileInfo.EnvironmentType session_type =
2803- DesktopFileInfo.EnvironmentType.GNOME;
2804- private string session_type_str = "GNOME";
2805-
2806- public DesktopFileInfo.EnvironmentType get_environment ()
2807- {
2808- return this.session_type;
2809- }
2810-
2811- private void get_environment_type ()
2812- {
2813- unowned string? session_var;
2814- session_var = Environment.get_variable ("XDG_CURRENT_DESKTOP");
2815- if (session_var == null)
2816- {
2817- session_var = Environment.get_variable ("DESKTOP_SESSION");
2818- }
2819-
2820- if (session_var == null) return;
2821-
2822- string session = session_var.down ();
2823-
2824- if (session.has_prefix ("unity") || session.has_prefix ("ubuntu"))
2825- {
2826- session_type = DesktopFileInfo.EnvironmentType.UNITY;
2827- session_type_str = "Unity";
2828- }
2829- else if (session.has_prefix ("kde"))
2830- {
2831- session_type = DesktopFileInfo.EnvironmentType.KDE;
2832- session_type_str = "KDE";
2833- }
2834- else if (session.has_prefix ("gnome"))
2835- {
2836- session_type = DesktopFileInfo.EnvironmentType.GNOME;
2837- session_type_str = "GNOME";
2838- }
2839- else if (session.has_prefix ("lx"))
2840- {
2841- session_type = DesktopFileInfo.EnvironmentType.LXDE;
2842- session_type_str = "LXDE";
2843- }
2844- else if (session.has_prefix ("xfce"))
2845- {
2846- session_type = DesktopFileInfo.EnvironmentType.XFCE;
2847- session_type_str = "XFCE";
2848- }
2849- else if (session.has_prefix ("mate"))
2850- {
2851- session_type = DesktopFileInfo.EnvironmentType.MATE;
2852- session_type_str = "MATE";
2853- }
2854- else if (session.has_prefix ("razor"))
2855- {
2856- session_type = DesktopFileInfo.EnvironmentType.RAZOR;
2857- session_type_str = "Razor";
2858- }
2859- else if (session.has_prefix ("tde"))
2860- {
2861- session_type = DesktopFileInfo.EnvironmentType.TDE;
2862- session_type_str = "TDE";
2863- }
2864- else if (session.has_prefix ("rox"))
2865- {
2866- session_type = DesktopFileInfo.EnvironmentType.ROX;
2867- session_type_str = "ROX";
2868- }
2869- else if (session.has_prefix ("pantheon"))
2870- {
2871- session_type = DesktopFileInfo.EnvironmentType.PANTHEON;
2872- session_type_str = "Pantheon";
2873- }
2874- else
2875- {
2876- warning ("Desktop session type is not recognized, assuming GNOME.");
2877- }
2878- }
2879-
2880- private async void process_directory (File directory,
2881- string id_prefix,
2882- Gee.Set<File> monitored_dirs)
2883- {
2884- try
2885- {
2886- string path = directory.get_path ();
2887- // we need to skip menu-xdg directory, see lp:686624
2888- if (path != null && path.has_suffix ("menu-xdg")) return;
2889- // screensavers don't interest us, skip those
2890- if (path != null && path.has_suffix ("/screensavers")) return;
2891-
2892- Utils.Logger.debug (this, "Searching for desktop files in: %s", path);
2893- bool exists = yield Utils.query_exists_async (directory);
2894- if (!exists) return;
2895- /* Check if we already scanned this directory // lp:686624 */
2896- foreach (var scanned_dir in monitored_dirs)
2897- {
2898- if (path == scanned_dir.get_path ()) return;
2899- }
2900- monitored_dirs.add (directory);
2901- var enumerator = yield directory.enumerate_children_async (
2902- FileAttribute.STANDARD_NAME + "," + FileAttribute.STANDARD_TYPE,
2903- 0, 0);
2904- var files = yield enumerator.next_files_async (1024, 0);
2905- foreach (var f in files)
2906- {
2907- unowned string name = f.get_name ();
2908- if (f.get_file_type () == FileType.DIRECTORY)
2909- {
2910- // FIXME: this could cause too many open files error, or?
2911- var subdir = directory.get_child (name);
2912- var new_prefix = "%s%s-".printf (id_prefix, subdir.get_basename ());
2913- yield process_directory (subdir, new_prefix, monitored_dirs);
2914- }
2915- else
2916- {
2917- // ignore ourselves
2918- if (name.has_suffix ("synapse.desktop")) continue;
2919- if (name.has_suffix (".desktop"))
2920- {
2921- yield load_desktop_file (directory.get_child (name), id_prefix);
2922- }
2923- }
2924- }
2925- }
2926- catch (Error err)
2927- {
2928- warning ("%s", err.message);
2929- }
2930- }
2931-
2932- private async void load_all_desktop_files ()
2933- {
2934- string[] data_dirs = Environment.get_system_data_dirs ();
2935- data_dirs += Environment.get_user_data_dir ();
2936-
2937- Gee.Set<File> desktop_file_dirs = new Gee.HashSet<File> ();
2938-
2939- mimetype_parent_map.clear ();
2940-
2941- foreach (unowned string data_dir in data_dirs)
2942- {
2943- string dir_path = Path.build_filename (data_dir, "applications", null);
2944- var directory = File.new_for_path (dir_path);
2945- yield process_directory (directory, "", desktop_file_dirs);
2946- dir_path = Path.build_filename (data_dir, "mime", "subclasses");
2947- yield load_mime_parents_from_file (dir_path);
2948- }
2949-
2950- create_indices ();
2951-
2952- directory_monitors = new Gee.ArrayList<FileMonitor> ();
2953- foreach (File d in desktop_file_dirs)
2954- {
2955- try
2956- {
2957- FileMonitor monitor = d.monitor_directory (0, null);
2958- monitor.changed.connect (this.desktop_file_directory_changed);
2959- directory_monitors.add (monitor);
2960- }
2961- catch (Error err)
2962- {
2963- warning ("Unable to monitor directory: %s", err.message);
2964- }
2965- }
2966- }
2967-
2968- private uint timer_id = 0;
2969-
2970- public signal void reload_started ();
2971- public signal void reload_done ();
2972-
2973- private void desktop_file_directory_changed ()
2974- {
2975- reload_started ();
2976- if (timer_id != 0)
2977- {
2978- Source.remove (timer_id);
2979- }
2980-
2981- timer_id = Timeout.add (5000, () =>
2982- {
2983- timer_id = 0;
2984- reload_desktop_files.begin ();
2985- return false;
2986- });
2987- }
2988-
2989- private async void reload_desktop_files ()
2990- {
2991- debug ("Reloading desktop files...");
2992- all_desktop_files.clear ();
2993- non_hidden_desktop_files.clear ();
2994- yield load_all_desktop_files ();
2995-
2996- reload_done ();
2997- }
2998-
2999- private async void load_desktop_file (File file, string id_prefix)
3000- {
3001- try
3002- {
3003- uint8[] file_contents;
3004- bool success = yield file.load_contents_async (null, out file_contents,
3005- null);
3006- if (success)
3007- {
3008- var keyfile = new KeyFile ();
3009- keyfile.load_from_data ((string) file_contents,
3010- file_contents.length, 0);
3011-
3012- var desktop_id = "%s%s".printf (id_prefix, file.get_basename ());
3013- var dfi = new DesktopFileInfo.for_keyfile (file.get_path (),
3014- keyfile,
3015- desktop_id);
3016- if (dfi.is_valid)
3017- {
3018- all_desktop_files.add (dfi);
3019- if (!dfi.is_hidden && session_type in dfi.show_in)
3020- {
3021- non_hidden_desktop_files.add (dfi);
3022- }
3023- }
3024- }
3025- }
3026- catch (Error err)
3027- {
3028- warning ("%s", err.message);
3029- }
3030- }
3031-
3032- private void create_indices ()
3033- {
3034- // create mimetype maps
3035- mimetype_map =
3036- new Gee.HashMap<unowned string, Gee.List<DesktopFileInfo> > ();
3037- // and exec map
3038- exec_map =
3039- new Gee.HashMap<string, Gee.List<DesktopFileInfo> > ();
3040- // and desktop id map
3041- desktop_id_map =
3042- new Gee.HashMap<string, DesktopFileInfo> ();
3043-
3044- Regex exec_re;
3045- try
3046- {
3047- exec_re = new Regex ("%[fFuU]");
3048- }
3049- catch (Error err)
3050- {
3051- critical ("%s", err.message);
3052- return;
3053- }
3054-
3055- foreach (var dfi in all_desktop_files)
3056- {
3057- string exec = "";
3058- try
3059- {
3060- exec = exec_re.replace_literal (dfi.exec, -1, 0, "");
3061- }
3062- catch (RegexError err)
3063- {
3064- Utils.Logger.error (this, "%s", err.message);
3065- }
3066- exec = exec.strip ();
3067- // update exec map
3068- Gee.List<DesktopFileInfo>? exec_list = exec_map[exec];
3069- if (exec_list == null)
3070- {
3071- exec_list = new Gee.ArrayList<DesktopFileInfo> ();
3072- exec_map[exec] = exec_list;
3073- }
3074- exec_list.add (dfi);
3075-
3076- // update desktop id map
3077- var desktop_id = dfi.desktop_id ?? Path.get_basename (dfi.filename);
3078- desktop_id_map[desktop_id] = dfi;
3079-
3080- // update mimetype map
3081- if (dfi.is_hidden || dfi.mime_types == null) continue;
3082-
3083- foreach (unowned string mime_type in dfi.mime_types)
3084- {
3085- Gee.List<DesktopFileInfo>? list = mimetype_map[mime_type];
3086- if (list == null)
3087- {
3088- list = new Gee.ArrayList<DesktopFileInfo> ();
3089- mimetype_map[mime_type] = list;
3090- }
3091- list.add (dfi);
3092- }
3093- }
3094- }
3095-
3096- private async void load_mime_parents_from_file (string fi)
3097- {
3098- var file = File.new_for_path (fi);
3099- bool exists = yield Utils.query_exists_async (file);
3100- if (!exists) return;
3101- try
3102- {
3103- var fis = yield file.read_async (GLib.Priority.DEFAULT);
3104- var dis = new DataInputStream (fis);
3105- string line = null;
3106- string[] mimes = null;
3107- int len = 0;
3108- // Read lines until end of file (null) is reached
3109- do {
3110- line = yield dis.read_line_async (GLib.Priority.DEFAULT);
3111- if (line == null) break;
3112- if (line.has_prefix ("#")) continue; //comment line
3113- mimes = line.split (" ");
3114- len = (int)GLib.strv_length (mimes);
3115- if (len != 2) continue;
3116- // cannot be parent of myself!
3117- if (mimes[0] == mimes[1]) continue;
3118- //debug ("Map %s -> %s", mimes[0], mimes[1]);
3119- mimetype_parent_map.set (mimes[0], mimes[1]);
3120- } while (true);
3121- } catch (GLib.Error err) { /* can't read file */ }
3122- }
3123-
3124- private void add_dfi_for_mime (string mime, Gee.Set<DesktopFileInfo> ret)
3125- {
3126- var dfis = mimetype_map[mime];
3127- if (dfis != null) ret.add_all (dfis);
3128-
3129- var parents = mimetype_parent_map[mime];
3130- if (parents == null) return;
3131- foreach (string parent in parents)
3132- add_dfi_for_mime (parent, ret);
3133- }
3134-
3135- // retuns desktop files available on the system (without hidden ones)
3136- public Gee.List<DesktopFileInfo> get_desktop_files ()
3137- {
3138- return non_hidden_desktop_files.read_only_view;
3139- }
3140-
3141- // returns all desktop files available on the system (even the ones which
3142- // are hidden by default)
3143- public Gee.List<DesktopFileInfo> get_all_desktop_files ()
3144- {
3145- return all_desktop_files.read_only_view;
3146- }
3147-
3148- public Gee.List<DesktopFileInfo> get_desktop_files_for_type (string mime_type)
3149- {
3150- var dfi_set = new Gee.HashSet<DesktopFileInfo> ();
3151- add_dfi_for_mime (mime_type, dfi_set);
3152- var ret = new Gee.ArrayList<DesktopFileInfo> ();
3153- ret.add_all (dfi_set);
3154- return ret;
3155- }
3156-
3157- public Gee.List<DesktopFileInfo> get_desktop_files_for_exec (string exec)
3158- {
3159- return exec_map[exec] ?? new Gee.ArrayList<DesktopFileInfo> ();
3160- }
3161-
3162- public DesktopFileInfo? get_desktop_file_for_id (string desktop_id)
3163- {
3164- return desktop_id_map[desktop_id];
3165- }
3166- }
3167+* Copyright (c) 2010 Michal Hruby <michal.mhr@gmail.com>
3168+* 2017 elementary LLC.
3169+*
3170+* This program is free software; you can redistribute it and/or
3171+* modify it under the terms of the GNU General Public
3172+* License as published by the Free Software Foundation; either
3173+* version 2 of the License, or (at your option) any later version.
3174+*
3175+* This program is distributed in the hope that it will be useful,
3176+* but WITHOUT ANY WARRANTY; without even the implied warranty of
3177+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
3178+* General Public License for more details.
3179+*
3180+* You should have received a copy of the GNU General Public
3181+* License along with this program; if not, write to the
3182+* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
3183+* Boston, MA 02110-1301 USA
3184+*
3185+* Authored by: Michal Hruby <michal.mhr@gmail.com>
3186+*/
3187+
3188+namespace Synapse {
3189+ errordomain DesktopFileError {
3190+ UNINTERESTING_ENTRY
3191+ }
3192+
3193+ public class DesktopFileInfo: Object {
3194+ // registered environments from http://standards.freedesktop.org/menu-spec/latest
3195+ // (and pantheon)
3196+ [Flags]
3197+ public enum EnvironmentType {
3198+ GNOME = 1 << 0,
3199+ KDE = 1 << 1,
3200+ LXDE = 1 << 2,
3201+ MATE = 1 << 3,
3202+ RAZOR = 1 << 4,
3203+ ROX = 1 << 5,
3204+ TDE = 1 << 6,
3205+ UNITY = 1 << 7,
3206+ XFCE = 1 << 8,
3207+ PANTHEON = 1 << 9,
3208+ OLD = 1 << 10,
3209+
3210+ ALL = 0x3FF
3211+ }
3212+
3213+ public string desktop_id { get; construct set; }
3214+ public string name { get; construct set; }
3215+ public string generic_name { get; construct set; }
3216+ public string comment { get; set; default = ""; }
3217+ public string icon_name { get; construct set; default = ""; }
3218+ public string gettext_domain { get; construct set; }
3219+
3220+ public bool needs_terminal { get; set; default = false; }
3221+ public string filename { get; construct set; }
3222+
3223+ public string exec { get; set; }
3224+
3225+ public bool is_hidden { get; private set; default = false; }
3226+ public bool is_valid { get; private set; default = true; }
3227+
3228+ public string[] mime_types = null;
3229+
3230+ private string? name_folded = null;
3231+ public unowned string get_name_folded () {
3232+ if (name_folded == null) {
3233+ name_folded = name.casefold ();
3234+ }
3235+
3236+ return name_folded;
3237+ }
3238+
3239+ public EnvironmentType show_in { get; set; default = EnvironmentType.ALL; }
3240+
3241+ private const string[] SUPPORTED_GETTEXT_DOMAINS_KEYS = {"X-Ubuntu-Gettext-Domain", "X-GNOME-Gettext-Domain"};
3242+ private const string GROUP = "Desktop Entry";
3243+
3244+ public DesktopFileInfo.for_keyfile (string path, KeyFile keyfile, string desktop_id) {
3245+ Object (filename: path, desktop_id: desktop_id);
3246+
3247+ init_from_keyfile (keyfile);
3248+ }
3249+
3250+ private EnvironmentType parse_environments (string[] environments) {
3251+ EnvironmentType result = 0;
3252+ foreach (unowned string env in environments) {
3253+ string env_up = env.up ();
3254+ switch (env_up) {
3255+ case "GNOME":
3256+ result |= EnvironmentType.GNOME;
3257+ break;
3258+ case "PANTHEON":
3259+ result |= EnvironmentType.PANTHEON;
3260+ break;
3261+ case "KDE":
3262+ result |= EnvironmentType.KDE;
3263+ break;
3264+ case "LXDE":
3265+ result |= EnvironmentType.LXDE;
3266+ break;
3267+ case "MATE":
3268+ result |= EnvironmentType.MATE;
3269+ break;
3270+ case "RAZOR":
3271+ result |= EnvironmentType.RAZOR;
3272+ break;
3273+ case "ROX": result |= EnvironmentType.ROX; break;
3274+ case "TDE": result |= EnvironmentType.TDE; break;
3275+ case "UNITY": result |= EnvironmentType.UNITY; break;
3276+ case "XFCE": result |= EnvironmentType.XFCE; break;
3277+ case "OLD": result |= EnvironmentType.OLD; break;
3278+ default: warning ("%s is not understood", env); break;
3279+ }
3280+ }
3281+ return result;
3282+ }
3283+
3284+ private void init_from_keyfile (KeyFile keyfile) {
3285+ try {
3286+ if (keyfile.get_string (GROUP, "Type") != "Application") {
3287+ throw new DesktopFileError.UNINTERESTING_ENTRY ("Not Application-type desktop entry");
3288+ }
3289+
3290+ if (keyfile.has_key (GROUP, "Categories")) {
3291+ string[] categories = keyfile.get_string_list (GROUP, "Categories");
3292+ if ("Screensaver" in categories) {
3293+ throw new DesktopFileError.UNINTERESTING_ENTRY ("Screensaver desktop entry");
3294+ }
3295+ }
3296+ foreach (var domain_key in SUPPORTED_GETTEXT_DOMAINS_KEYS) {
3297+ if (keyfile.has_key (GROUP, domain_key)) {
3298+ gettext_domain = keyfile.get_string (GROUP, domain_key);
3299+ break;
3300+ }
3301+ }
3302+
3303+ DesktopAppInfo app_info;
3304+ app_info = new DesktopAppInfo.from_keyfile (keyfile);
3305+
3306+ if (app_info == null) {
3307+ throw new DesktopFileError.UNINTERESTING_ENTRY ("Unable to create AppInfo");
3308+ }
3309+
3310+ name = app_info.get_name ();
3311+ generic_name = app_info.get_generic_name () ?? "";
3312+ exec = app_info.get_commandline ();
3313+ if (exec == null) {
3314+ throw new DesktopFileError.UNINTERESTING_ENTRY ("Unable to get exec for %s".printf (name));
3315+ }
3316+
3317+ // check for hidden desktop files
3318+ if (keyfile.has_key (GROUP, "Hidden") && keyfile.get_boolean (GROUP, "Hidden")) {
3319+ is_hidden = true;
3320+ }
3321+ if (keyfile.has_key (GROUP, "NoDisplay") && keyfile.get_boolean (GROUP, "NoDisplay")) {
3322+ is_hidden = true;
3323+ }
3324+
3325+ comment = app_info.get_description () ?? "";
3326+
3327+ var icon = app_info.get_icon () ??
3328+ new ThemedIcon ("application-default-icon");
3329+ icon_name = icon.to_string ();
3330+
3331+ if (keyfile.has_key (GROUP, "MimeType")) {
3332+ mime_types = keyfile.get_string_list (GROUP, "MimeType");
3333+ }
3334+ if (keyfile.has_key (GROUP, "Terminal")) {
3335+ needs_terminal = keyfile.get_boolean (GROUP, "Terminal");
3336+ }
3337+ if (keyfile.has_key (GROUP, "OnlyShowIn")) {
3338+ show_in = parse_environments (keyfile.get_string_list (GROUP, "OnlyShowIn"));
3339+ } else if (keyfile.has_key (GROUP, "NotShowIn")) {
3340+ var not_show = parse_environments (keyfile.get_string_list (GROUP, "NotShowIn"));
3341+ show_in = EnvironmentType.ALL ^ not_show;
3342+ }
3343+
3344+ // special case these, people are using them quite often and wonder
3345+ // why they don't appear
3346+ if (filename.has_suffix ("gconf-editor.desktop") || filename.has_suffix ("dconf-editor.desktop")) {
3347+ is_hidden = false;
3348+ }
3349+ } catch (Error err) {
3350+ Utils.Logger.warning (this, "%s", err.message);
3351+ is_valid = false;
3352+ }
3353+ }
3354+ }
3355+
3356+ public class DesktopFileService : Object {
3357+ private static unowned DesktopFileService? instance;
3358+ private Utils.AsyncOnce<bool> init_once;
3359+
3360+ // singleton that can be easily destroyed
3361+ public static DesktopFileService get_default () {
3362+ return instance ?? new DesktopFileService ();
3363+ }
3364+
3365+ private DesktopFileService () { }
3366+
3367+ private Gee.List<FileMonitor> directory_monitors;
3368+ private Gee.List<DesktopFileInfo> all_desktop_files;
3369+ private Gee.List<DesktopFileInfo> non_hidden_desktop_files;
3370+ private Gee.Map<unowned string, Gee.List<DesktopFileInfo> > mimetype_map;
3371+ private Gee.Map<string, Gee.List<DesktopFileInfo> > exec_map;
3372+ private Gee.Map<string, DesktopFileInfo> desktop_id_map;
3373+ private Gee.MultiMap<string, string> mimetype_parent_map;
3374+
3375+ construct {
3376+ instance = this;
3377+
3378+ directory_monitors = new Gee.ArrayList<FileMonitor> ();
3379+ all_desktop_files = new Gee.ArrayList<DesktopFileInfo> ();
3380+ non_hidden_desktop_files = new Gee.ArrayList<DesktopFileInfo> ();
3381+ mimetype_parent_map = new Gee.HashMultiMap<string, string> ();
3382+ init_once = new Utils.AsyncOnce<bool> ();
3383+
3384+ initialize.begin ();
3385+ }
3386+
3387+ ~DesktopFileService () {
3388+ instance = null;
3389+ }
3390+
3391+ public async void initialize () {
3392+ if (init_once.is_initialized ()) {
3393+ return;
3394+ }
3395+ var is_locked = yield init_once.enter ();
3396+ if (!is_locked) {
3397+ return;
3398+ }
3399+
3400+ get_environment_type ();
3401+ DesktopAppInfo.set_desktop_env (session_type_str);
3402+
3403+ Idle.add_full (Priority.LOW, initialize.callback);
3404+ yield;
3405+ yield load_all_desktop_files ();
3406+
3407+ init_once.leave (true);
3408+ }
3409+
3410+ private DesktopFileInfo.EnvironmentType session_type = DesktopFileInfo.EnvironmentType.GNOME;
3411+ private string session_type_str = "GNOME";
3412+
3413+ public DesktopFileInfo.EnvironmentType get_environment () {
3414+ return this.session_type;
3415+ }
3416+
3417+ private void get_environment_type () {
3418+ unowned string? session_var;
3419+ session_var = Environment.get_variable ("XDG_CURRENT_DESKTOP");
3420+ if (session_var == null) {
3421+ session_var = Environment.get_variable ("DESKTOP_SESSION");
3422+ }
3423+
3424+ if (session_var == null) {
3425+ return;
3426+ }
3427+
3428+ string session = session_var.down ();
3429+
3430+ if (session.has_prefix ("unity") || session.has_prefix ("ubuntu")) {
3431+ session_type = DesktopFileInfo.EnvironmentType.UNITY;
3432+ session_type_str = "Unity";
3433+ } else if (session.has_prefix ("kde")) {
3434+ session_type = DesktopFileInfo.EnvironmentType.KDE;
3435+ session_type_str = "KDE";
3436+ } else if (session.has_prefix ("gnome")) {
3437+ session_type = DesktopFileInfo.EnvironmentType.GNOME;
3438+ session_type_str = "GNOME";
3439+ } else if (session.has_prefix ("lx")) {
3440+ session_type = DesktopFileInfo.EnvironmentType.LXDE;
3441+ session_type_str = "LXDE";
3442+ } else if (session.has_prefix ("xfce")) {
3443+ session_type = DesktopFileInfo.EnvironmentType.XFCE;
3444+ session_type_str = "XFCE";
3445+ } else if (session.has_prefix ("mate")) {
3446+ session_type = DesktopFileInfo.EnvironmentType.MATE;
3447+ session_type_str = "MATE";
3448+ } else if (session.has_prefix ("razor")) {
3449+ session_type = DesktopFileInfo.EnvironmentType.RAZOR;
3450+ session_type_str = "Razor";
3451+ } else if (session.has_prefix ("tde")) {
3452+ session_type = DesktopFileInfo.EnvironmentType.TDE;
3453+ session_type_str = "TDE";
3454+ } else if (session.has_prefix ("rox")) {
3455+ session_type = DesktopFileInfo.EnvironmentType.ROX;
3456+ session_type_str = "ROX";
3457+ } else if (session.has_prefix ("pantheon")) {
3458+ session_type = DesktopFileInfo.EnvironmentType.PANTHEON;
3459+ session_type_str = "Pantheon";
3460+ } else {
3461+ warning ("Desktop session type is not recognized, assuming GNOME.");
3462+ }
3463+ }
3464+
3465+ private async void process_directory (File directory, string id_prefix, Gee.Set<File> monitored_dirs) {
3466+ try {
3467+ string path = directory.get_path ();
3468+ // we need to skip menu-xdg directory, see lp:686624
3469+ if (path != null && path.has_suffix ("menu-xdg")) {
3470+ return;
3471+ }
3472+ // screensavers don't interest us, skip those
3473+ if (path != null && path.has_suffix ("/screensavers")) {
3474+ return;
3475+ }
3476+
3477+ Utils.Logger.debug (this, "Searching for desktop files in: %s", path);
3478+ bool exists = yield Utils.query_exists_async (directory);
3479+ if (!exists) {
3480+ return;
3481+ }
3482+ /* Check if we already scanned this directory // lp:686624 */
3483+ foreach (var scanned_dir in monitored_dirs) {
3484+ if (path == scanned_dir.get_path ()) {
3485+ return;
3486+ }
3487+ }
3488+ monitored_dirs.add (directory);
3489+ var enumerator = yield directory.enumerate_children_async (FileAttribute.STANDARD_NAME + "," + FileAttribute.STANDARD_TYPE, 0, 0);
3490+ var files = yield enumerator.next_files_async (1024, 0);
3491+ foreach (var f in files) {
3492+ unowned string name = f.get_name ();
3493+ if (f.get_file_type () == FileType.DIRECTORY) {
3494+ // FIXME: this could cause too many open files error, or?
3495+ var subdir = directory.get_child (name);
3496+ var new_prefix = "%s%s-".printf (id_prefix, subdir.get_basename ());
3497+ yield process_directory (subdir, new_prefix, monitored_dirs);
3498+ } else {
3499+ // ignore ourselves
3500+ if (name.has_suffix ("synapse.desktop")) {
3501+ continue;
3502+ }
3503+ if (name.has_suffix (".desktop")) {
3504+ yield load_desktop_file (directory.get_child (name), id_prefix);
3505+ }
3506+ }
3507+ }
3508+ } catch (Error err) {
3509+ warning ("%s", err.message);
3510+ }
3511+ }
3512+
3513+ private async void load_all_desktop_files () {
3514+ string[] data_dirs = Environment.get_system_data_dirs ();
3515+ data_dirs += Environment.get_user_data_dir ();
3516+
3517+ Gee.Set<File> desktop_file_dirs = new Gee.HashSet<File> ();
3518+ mimetype_parent_map.clear ();
3519+
3520+ foreach (unowned string data_dir in data_dirs) {
3521+ string dir_path = Path.build_filename (data_dir, "applications", null);
3522+ var directory = File.new_for_path (dir_path);
3523+ yield process_directory (directory, "", desktop_file_dirs);
3524+ dir_path = Path.build_filename (data_dir, "mime", "subclasses");
3525+ yield load_mime_parents_from_file (dir_path);
3526+ }
3527+
3528+ create_indices ();
3529+
3530+ directory_monitors = new Gee.ArrayList<FileMonitor> ();
3531+ foreach (File d in desktop_file_dirs) {
3532+ try {
3533+ FileMonitor monitor = d.monitor_directory (0, null);
3534+ monitor.changed.connect (this.desktop_file_directory_changed);
3535+ directory_monitors.add (monitor);
3536+ } catch (Error err) {
3537+ warning ("Unable to monitor directory: %s", err.message);
3538+ }
3539+ }
3540+ }
3541+
3542+ private uint timer_id = 0;
3543+
3544+ public signal void reload_started ();
3545+ public signal void reload_done ();
3546+
3547+ private void desktop_file_directory_changed () {
3548+ reload_started ();
3549+ if (timer_id != 0) {
3550+ Source.remove (timer_id);
3551+ }
3552+
3553+ timer_id = Timeout.add (5000, () => {
3554+ timer_id = 0;
3555+ reload_desktop_files.begin ();
3556+ return false;
3557+ });
3558+ }
3559+
3560+ private async void reload_desktop_files () {
3561+ debug ("Reloading desktop files...");
3562+ all_desktop_files.clear ();
3563+ non_hidden_desktop_files.clear ();
3564+ yield load_all_desktop_files ();
3565+
3566+ reload_done ();
3567+ }
3568+
3569+ private async void load_desktop_file (File file, string id_prefix) {
3570+ try {
3571+ uint8[] file_contents;
3572+ bool success = yield file.load_contents_async (null, out file_contents, null);
3573+ if (success) {
3574+ var keyfile = new KeyFile ();
3575+ keyfile.load_from_data ((string) file_contents,
3576+ file_contents.length, 0);
3577+
3578+ var desktop_id = "%s%s".printf (id_prefix, file.get_basename ());
3579+ var dfi = new DesktopFileInfo.for_keyfile (file.get_path (), keyfile, desktop_id);
3580+ if (dfi.is_valid) {
3581+ all_desktop_files.add (dfi);
3582+ if (!dfi.is_hidden && session_type in dfi.show_in) {
3583+ non_hidden_desktop_files.add (dfi);
3584+ }
3585+ }
3586+ }
3587+ } catch (Error err) {
3588+ warning ("%s", err.message);
3589+ }
3590+ }
3591+
3592+ private void create_indices () {
3593+ // create mimetype maps
3594+ mimetype_map = new Gee.HashMap<unowned string, Gee.List<DesktopFileInfo> > ();
3595+ // and exec map
3596+ exec_map = new Gee.HashMap<string, Gee.List<DesktopFileInfo> > ();
3597+ // and desktop id map
3598+ desktop_id_map = new Gee.HashMap<string, DesktopFileInfo> ();
3599+
3600+ Regex exec_re;
3601+ try {
3602+ exec_re = new Regex ("%[fFuU]");
3603+ } catch (Error err) {
3604+ critical ("%s", err.message);
3605+ return;
3606+ }
3607+
3608+ foreach (var dfi in all_desktop_files) {
3609+ string exec = "";
3610+ try {
3611+ exec = exec_re.replace_literal (dfi.exec, -1, 0, "");
3612+ } catch (RegexError err) {
3613+ Utils.Logger.error (this, "%s", err.message);
3614+ }
3615+ exec = exec.strip ();
3616+ // update exec map
3617+ Gee.List<DesktopFileInfo>? exec_list = exec_map[exec];
3618+ if (exec_list == null) {
3619+ exec_list = new Gee.ArrayList<DesktopFileInfo> ();
3620+ exec_map[exec] = exec_list;
3621+ }
3622+ exec_list.add (dfi);
3623+
3624+ // update desktop id map
3625+ var desktop_id = dfi.desktop_id ?? Path.get_basename (dfi.filename);
3626+ desktop_id_map[desktop_id] = dfi;
3627+
3628+ // update mimetype map
3629+ if (dfi.is_hidden || dfi.mime_types == null) {
3630+ continue;
3631+ }
3632+
3633+ foreach (unowned string mime_type in dfi.mime_types) {
3634+ Gee.List<DesktopFileInfo>? list = mimetype_map[mime_type];
3635+ if (list == null) {
3636+ list = new Gee.ArrayList<DesktopFileInfo> ();
3637+ mimetype_map[mime_type] = list;
3638+ }
3639+ list.add (dfi);
3640+ }
3641+ }
3642+ }
3643+
3644+ private async void load_mime_parents_from_file (string fi) {
3645+ var file = File.new_for_path (fi);
3646+ bool exists = yield Utils.query_exists_async (file);
3647+ if (!exists) {
3648+ return;
3649+ }
3650+
3651+ try {
3652+ var fis = yield file.read_async (GLib.Priority.DEFAULT);
3653+ var dis = new DataInputStream (fis);
3654+ string line = null;
3655+ string[] mimes = null;
3656+ int len = 0;
3657+ // Read lines until end of file (null) is reached
3658+ do {
3659+ line = yield dis.read_line_async (GLib.Priority.DEFAULT);
3660+ if (line == null) {
3661+ break;
3662+ }
3663+ if (line.has_prefix ("#")) {
3664+ continue; //comment line
3665+ }
3666+ mimes = line.split (" ");
3667+ len = (int)GLib.strv_length (mimes);
3668+ if (len != 2) {
3669+ continue;
3670+ }
3671+ // cannot be parent of myself!
3672+ if (mimes[0] == mimes[1]) {
3673+ continue;
3674+ }
3675+ //debug ("Map %s -> %s", mimes[0], mimes[1]);
3676+ mimetype_parent_map.set (mimes[0], mimes[1]);
3677+ } while (true);
3678+ } catch (GLib.Error err) {
3679+ warning ("Can't read file.");
3680+ }
3681+ }
3682+
3683+ private void add_dfi_for_mime (string mime, Gee.Set<DesktopFileInfo> ret) {
3684+ var dfis = mimetype_map[mime];
3685+ if (dfis != null) {
3686+ ret.add_all (dfis);
3687+ }
3688+
3689+ var parents = mimetype_parent_map[mime];
3690+ if (parents == null) {
3691+ return;
3692+ }
3693+ foreach (string parent in parents) {
3694+ add_dfi_for_mime (parent, ret);
3695+ }
3696+ }
3697+
3698+ // retuns desktop files available on the system (without hidden ones)
3699+ public Gee.List<DesktopFileInfo> get_desktop_files () {
3700+ return non_hidden_desktop_files.read_only_view;
3701+ }
3702+
3703+ // returns all desktop files available on the system (even the ones which
3704+ // are hidden by default)
3705+ public Gee.List<DesktopFileInfo> get_all_desktop_files () {
3706+ return all_desktop_files.read_only_view;
3707+ }
3708+
3709+ public Gee.List<DesktopFileInfo> get_desktop_files_for_type (string mime_type) {
3710+ var dfi_set = new Gee.HashSet<DesktopFileInfo> ();
3711+ add_dfi_for_mime (mime_type, dfi_set);
3712+ var ret = new Gee.ArrayList<DesktopFileInfo> ();
3713+ ret.add_all (dfi_set);
3714+ return ret;
3715+ }
3716+
3717+ public Gee.List<DesktopFileInfo> get_desktop_files_for_exec (string exec) {
3718+ return exec_map[exec] ?? new Gee.ArrayList<DesktopFileInfo> ();
3719+ }
3720+
3721+ public DesktopFileInfo? get_desktop_file_for_id (string desktop_id) {
3722+ return desktop_id_map[desktop_id];
3723+ }
3724+ }
3725 }
3726
3727=== modified file 'lib/synapse-core/match.vala'
3728--- lib/synapse-core/match.vala 2014-06-04 19:35:44 +0000
3729+++ lib/synapse-core/match.vala 2017-01-26 19:54:51 +0000
3730@@ -1,144 +1,133 @@
3731 /*
3732- * Copyright (C) 2010 Michal Hruby <michal.mhr@gmail.com>
3733- *
3734- * This library is free software; you can redistribute it and/or
3735- * modify it under the terms of the GNU Lesser General Public
3736- * License as published by the Free Software Foundation; either
3737- * version 2.1 of the License, or (at your option) any later version.
3738- *
3739- * This library is distributed in the hope that it will be useful,
3740- * but WITHOUT ANY WARRANTY; without even the implied warranty of
3741- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
3742- * Lesser General Public License for more details.
3743- *
3744- * You should have received a copy of the GNU Lesser General Public License
3745- * along with this program. If not, see <http://www.gnu.org/licenses/>.
3746- *
3747- * Authored by Michal Hruby <michal.mhr@gmail.com>
3748- * Alberto Aldegheri <albyrock87+dev@gmail.com>
3749- */
3750-
3751-namespace Synapse
3752-{
3753- public enum MatchType
3754- {
3755- UNKNOWN = 0,
3756- TEXT,
3757- APPLICATION,
3758- GENERIC_URI,
3759- ACTION,
3760- SEARCH,
3761- CONTACT
3762- }
3763-
3764- public interface Match: Object
3765- {
3766- public enum Score
3767- {
3768- INCREMENT_MINOR = 2000,
3769- INCREMENT_SMALL = 5000,
3770- INCREMENT_MEDIUM = 10000,
3771- INCREMENT_LARGE = 20000,
3772- URI_PENALTY = 15000,
3773-
3774- POOR = 50000,
3775- BELOW_AVERAGE = 60000,
3776- AVERAGE = 70000,
3777- ABOVE_AVERAGE = 75000,
3778- GOOD = 80000,
3779- VERY_GOOD = 85000,
3780- EXCELLENT = 90000,
3781-
3782- HIGHEST = 100000
3783- }
3784-
3785- // properties
3786- public abstract string title { get; construct set; }
3787- public abstract string description { get; set; }
3788- public abstract string icon_name { get; construct set; }
3789- public abstract bool has_thumbnail { get; construct set; }
3790- public abstract string thumbnail_path { get; construct set; }
3791- public abstract MatchType match_type { get; construct set; }
3792-
3793- public virtual void execute (Match? match)
3794- {
3795- Utils.Logger.error (this, "execute () is not implemented");
3796- }
3797-
3798- public virtual void execute_with_target (Match? source, Match? target = null)
3799- {
3800- if (target == null) execute (source);
3801- else Utils.Logger.error (this, "execute () is not implemented");
3802- }
3803-
3804- public virtual bool needs_target () {
3805- return false;
3806- }
3807-
3808- public virtual QueryFlags target_flags ()
3809- {
3810- return QueryFlags.ALL;
3811- }
3812-
3813- public signal void executed ();
3814- }
3815-
3816- public interface ApplicationMatch: Match
3817- {
3818- public abstract AppInfo? app_info { get; set; }
3819- public abstract bool needs_terminal { get; set; }
3820- public abstract string? filename { get; construct set; }
3821- }
3822-
3823- public interface UriMatch: Match
3824- {
3825- public abstract string uri { get; set; }
3826- public abstract QueryFlags file_type { get; set; }
3827- public abstract string mime_type { get; set; }
3828- }
3829-
3830- public interface ContactMatch: Match
3831- {
3832- public abstract void send_message (string message, bool present);
3833- public abstract void open_chat ();
3834- }
3835-
3836- public interface ExtendedInfo: Match
3837- {
3838- public abstract string? extended_info { get; set; }
3839- }
3840-
3841- public enum TextOrigin
3842- {
3843- UNKNOWN,
3844- CLIPBOARD
3845- }
3846-
3847- public interface TextMatch: Match
3848- {
3849- public abstract TextOrigin text_origin { get; set; }
3850- public abstract string get_text ();
3851- }
3852-
3853- public interface SearchMatch: Match, SearchProvider
3854- {
3855- public abstract Match search_source { get; set; }
3856- }
3857-
3858- public class DefaultMatch: Object, Match
3859- {
3860- public string title { get; construct set; }
3861- public string description { get; set; }
3862- public string icon_name { get; construct set; }
3863- public bool has_thumbnail { get; construct set; }
3864- public string thumbnail_path { get; construct set; }
3865- public MatchType match_type { get; construct set; }
3866-
3867- public DefaultMatch (string query_string)
3868- {
3869- Object (title: query_string, description: "", has_thumbnail: false,
3870- icon_name: "unknown", match_type: MatchType.UNKNOWN);
3871- }
3872- }
3873+* Copyright (c) 2010 Michal Hruby <michal.mhr@gmail.com>
3874+* 2017 elementary LLC.
3875+*
3876+* This program is free software; you can redistribute it and/or
3877+* modify it under the terms of the GNU General Public
3878+* License as published by the Free Software Foundation; either
3879+* version 2 of the License, or (at your option) any later version.
3880+*
3881+* This program is distributed in the hope that it will be useful,
3882+* but WITHOUT ANY WARRANTY; without even the implied warranty of
3883+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
3884+* General Public License for more details.
3885+*
3886+* You should have received a copy of the GNU General Public
3887+* License along with this program; if not, write to the
3888+* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
3889+* Boston, MA 02110-1301 USA
3890+*
3891+* Authored by: Michal Hruby <michal.mhr@gmail.com>
3892+* Alberto Aldegheri <albyrock87+dev@gmail.com>
3893+*/
3894+
3895+namespace Synapse {
3896+ public enum MatchType {
3897+ UNKNOWN = 0,
3898+ TEXT,
3899+ APPLICATION,
3900+ GENERIC_URI,
3901+ ACTION,
3902+ SEARCH,
3903+ CONTACT
3904+ }
3905+
3906+ public interface Match: Object {
3907+ public enum Score {
3908+ INCREMENT_MINOR = 2000,
3909+ INCREMENT_SMALL = 5000,
3910+ INCREMENT_MEDIUM = 10000,
3911+ INCREMENT_LARGE = 20000,
3912+ URI_PENALTY = 15000,
3913+
3914+ POOR = 50000,
3915+ BELOW_AVERAGE = 60000,
3916+ AVERAGE = 70000,
3917+ ABOVE_AVERAGE = 75000,
3918+ GOOD = 80000,
3919+ VERY_GOOD = 85000,
3920+ EXCELLENT = 90000,
3921+
3922+ HIGHEST = 100000
3923+ }
3924+
3925+ // properties
3926+ public abstract string title { get; construct set; }
3927+ public abstract string description { get; set; }
3928+ public abstract string icon_name { get; construct set; }
3929+ public abstract bool has_thumbnail { get; construct set; }
3930+ public abstract string thumbnail_path { get; construct set; }
3931+ public abstract MatchType match_type { get; construct set; }
3932+
3933+ public virtual void execute (Match? match) {
3934+ Utils.Logger.error (this, "execute () is not implemented");
3935+ }
3936+
3937+ public virtual void execute_with_target (Match? source, Match? target = null) {
3938+ if (target == null) {
3939+ execute (source);
3940+ } else {
3941+ Utils.Logger.error (this, "execute () is not implemented");
3942+ }
3943+ }
3944+
3945+ public virtual bool needs_target () {
3946+ return false;
3947+ }
3948+
3949+ public virtual QueryFlags target_flags () {
3950+ return QueryFlags.ALL;
3951+ }
3952+
3953+ public signal void executed ();
3954+ }
3955+
3956+ public interface ApplicationMatch: Match {
3957+ public abstract AppInfo? app_info { get; set; }
3958+ public abstract bool needs_terminal { get; set; }
3959+ public abstract string? filename { get; construct set; }
3960+ }
3961+
3962+ public interface UriMatch: Match {
3963+ public abstract string uri { get; set; }
3964+ public abstract QueryFlags file_type { get; set; }
3965+ public abstract string mime_type { get; set; }
3966+ }
3967+
3968+ public interface ContactMatch: Match {
3969+ public abstract void send_message (string message, bool present);
3970+ public abstract void open_chat ();
3971+ }
3972+
3973+ public interface ExtendedInfo: Match {
3974+ public abstract string? extended_info { get; set; }
3975+ }
3976+
3977+ public enum TextOrigin {
3978+ UNKNOWN,
3979+ CLIPBOARD
3980+ }
3981+
3982+ public interface TextMatch: Match {
3983+ public abstract TextOrigin text_origin { get; set; }
3984+ public abstract string get_text ();
3985+ }
3986+
3987+ public interface SearchMatch: Match, SearchProvider {
3988+ public abstract Match search_source { get; set; }
3989+ }
3990+
3991+ public class DefaultMatch: Object, Match {
3992+ public string title { get; construct set; }
3993+ public string description { get; set; }
3994+ public string icon_name { get; construct set; }
3995+ public bool has_thumbnail { get; construct set; }
3996+ public string thumbnail_path { get; construct set; }
3997+ public MatchType match_type { get; construct set; }
3998+
3999+ public DefaultMatch (string query_string) {
4000+ Object (title: query_string, description: "", has_thumbnail: false,
4001+ icon_name: "unknown", match_type: MatchType.UNKNOWN);
4002+ }
4003+ }
4004 }
4005-
4006
4007=== modified file 'lib/synapse-core/plugin.vala'
4008--- lib/synapse-core/plugin.vala 2014-06-04 19:35:44 +0000
4009+++ lib/synapse-core/plugin.vala 2017-01-26 19:54:51 +0000
4010@@ -1,59 +1,53 @@
4011 /*
4012- * Copyright (C) 2010 Michal Hruby <michal.mhr@gmail.com>
4013- *
4014- * This library is free software; you can redistribute it and/or
4015- * modify it under the terms of the GNU Lesser General Public
4016- * License as published by the Free Software Foundation; either
4017- * version 2.1 of the License, or (at your option) any later version.
4018- *
4019- * This library is distributed in the hope that it will be useful,
4020- * but WITHOUT ANY WARRANTY; without even the implied warranty of
4021- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
4022- * Lesser General Public License for more details.
4023- *
4024- * You should have received a copy of the GNU Lesser General Public License
4025- * along with this program. If not, see <http://www.gnu.org/licenses/>.
4026- *
4027- * Authored by Michal Hruby <michal.mhr@gmail.com>
4028- *
4029- */
4030-
4031-namespace Synapse
4032-{
4033- public interface Activatable : Object
4034- {
4035- // this property will eventually go away
4036- public abstract bool enabled { get; set; default = true; }
4037-
4038- public abstract void activate ();
4039- public abstract void deactivate ();
4040- }
4041-
4042- public interface Configurable : Object
4043- {
4044- public abstract Gtk.Widget create_config_widget ();
4045- }
4046-
4047- public interface ItemProvider : Activatable
4048- {
4049- public abstract async ResultSet? search (Query query) throws SearchError;
4050- public virtual bool handles_query (Query query)
4051- {
4052- return true;
4053- }
4054- public virtual bool handles_empty_query ()
4055- {
4056- return false;
4057- }
4058- }
4059-
4060- public interface ActionProvider : Activatable
4061- {
4062- public abstract ResultSet? find_for_match (ref Query query, Match match);
4063- public virtual bool handles_unknown ()
4064- {
4065- return false;
4066- }
4067- }
4068+* Copyright (c) 2010 Michal Hruby <michal.mhr@gmail.com>
4069+* 2017 elementary LLC.
4070+*
4071+* This program is free software; you can redistribute it and/or
4072+* modify it under the terms of the GNU General Public
4073+* License as published by the Free Software Foundation; either
4074+* version 2 of the License, or (at your option) any later version.
4075+*
4076+* This program is distributed in the hope that it will be useful,
4077+* but WITHOUT ANY WARRANTY; without even the implied warranty of
4078+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
4079+* General Public License for more details.
4080+*
4081+* You should have received a copy of the GNU General Public
4082+* License along with this program; if not, write to the
4083+* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
4084+* Boston, MA 02110-1301 USA
4085+*
4086+* Authored by: Michal Hruby <michal.mhr@gmail.com>
4087+*/
4088+
4089+namespace Synapse {
4090+ public interface Activatable : Object {
4091+ // this property will eventually go away
4092+ public abstract bool enabled { get; set; default = true; }
4093+
4094+ public abstract void activate ();
4095+ public abstract void deactivate ();
4096+ }
4097+
4098+ public interface Configurable : Object {
4099+ public abstract Gtk.Widget create_config_widget ();
4100+ }
4101+
4102+ public interface ItemProvider : Activatable {
4103+ public abstract async ResultSet? search (Query query) throws SearchError;
4104+ public virtual bool handles_query (Query query) {
4105+ return true;
4106+ }
4107+
4108+ public virtual bool handles_empty_query () {
4109+ return false;
4110+ }
4111+ }
4112+
4113+ public interface ActionProvider : Activatable {
4114+ public abstract ResultSet? find_for_match (ref Query query, Match match);
4115+ public virtual bool handles_unknown () {
4116+ return false;
4117+ }
4118+ }
4119 }
4120-
4121
4122=== modified file 'lib/synapse-core/query.vala'
4123--- lib/synapse-core/query.vala 2014-06-04 19:35:44 +0000
4124+++ lib/synapse-core/query.vala 2017-01-26 19:54:51 +0000
4125@@ -1,296 +1,231 @@
4126 /*
4127- * Copyright (C) 2010 Michal Hruby <michal.mhr@gmail.com>
4128- *
4129- * This library is free software; you can redistribute it and/or
4130- * modify it under the terms of the GNU Lesser General Public
4131- * License as published by the Free Software Foundation; either
4132- * version 2.1 of the License, or (at your option) any later version.
4133- *
4134- * This library is distributed in the hope that it will be useful,
4135- * but WITHOUT ANY WARRANTY; without even the implied warranty of
4136- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
4137- * Lesser General Public License for more details.
4138- *
4139- * You should have received a copy of the GNU Lesser General Public License
4140- * along with this program. If not, see <http://www.gnu.org/licenses/>.
4141- *
4142- * Authored by Michal Hruby <michal.mhr@gmail.com>
4143- *
4144- */
4145-
4146-namespace Synapse
4147-{
4148- [Flags]
4149- public enum QueryFlags
4150- {
4151- /* HowTo create categories (32bit).
4152- * Authored by Alberto Aldegheri <albyrock87+dev@gmail.com>
4153- * Categories are "stored" in 3 Levels:
4154- * Super-Category
4155- * -> Category
4156- * ----> Sub-Category
4157- * ------------------------------------
4158- * if (Super-Category does NOT have childs):
4159- * SUPER = 1 << FreeBitPosition
4160- * else:
4161- * if (Category does NOT have childs)
4162- * CATEGORY = 1 << FreeBitPosition
4163- * else
4164- * SUB = 1 << FreeBitPosition
4165- * CATEGORY = OR ([subcategories, ...]);
4166- *
4167- * SUPER = OR ([categories, ...]);
4168- *
4169- *
4170- * Remember:
4171- * if you add or remove a category,
4172- * change labels in UIInterface.CategoryConfig.init_labels
4173- *
4174- */
4175- INCLUDE_REMOTE = 1 << 0,
4176- UNCATEGORIZED = 1 << 1,
4177-
4178- APPLICATIONS = 1 << 2,
4179-
4180- ACTIONS = 1 << 3,
4181-
4182- AUDIO = 1 << 4,
4183- VIDEO = 1 << 5,
4184- DOCUMENTS = 1 << 6,
4185- IMAGES = 1 << 7,
4186- FILES = AUDIO | VIDEO | DOCUMENTS | IMAGES,
4187-
4188- PLACES = 1 << 8,
4189-
4190- // FIXME: shouldn't this be FILES | INCLUDE_REMOTE?
4191- INTERNET = 1 << 9,
4192-
4193- // FIXME: Text Query flag? kinda weird, why do we have this here?
4194- TEXT = 1 << 10,
4195-
4196- CONTACTS = 1 << 11,
4197-
4198- ALL = 0xFFFFFFFF,
4199- LOCAL_CONTENT = ALL ^ QueryFlags.INCLUDE_REMOTE
4200- }
4201-
4202- [Flags]
4203- public enum MatcherFlags
4204- {
4205- NO_REVERSED = 1 << 0,
4206- NO_SUBSTRING = 1 << 1,
4207- NO_PARTIAL = 1 << 2,
4208- NO_FUZZY = 1 << 3
4209- }
4210-
4211- public struct Query
4212- {
4213- string query_string;
4214- string query_string_folded;
4215- Cancellable cancellable;
4216- QueryFlags query_type;
4217- uint max_results;
4218- uint query_id;
4219-
4220- public Query (uint query_id,
4221- string query,
4222- QueryFlags flags = QueryFlags.LOCAL_CONTENT,
4223- uint num_results = 96)
4224- {
4225- this.query_id = query_id;
4226- this.query_string = query;
4227- this.query_string_folded = query.casefold ();
4228- this.query_type = flags;
4229- this.max_results = num_results;
4230- }
4231-
4232- public bool is_cancelled ()
4233- {
4234- return cancellable.is_cancelled ();
4235- }
4236-
4237- public void check_cancellable () throws SearchError
4238- {
4239- if (cancellable.is_cancelled ())
4240- {
4241- throw new SearchError.SEARCH_CANCELLED ("Cancelled");
4242- }
4243- }
4244-
4245- public static Gee.List<Gee.Map.Entry<Regex, int>>
4246- get_matchers_for_query (string query,
4247- MatcherFlags match_flags = 0,
4248- RegexCompileFlags flags = GLib.RegexCompileFlags.OPTIMIZE)
4249- {
4250- /* create a couple of regexes and try to help with matching
4251- * match with these regular expressions (with descending score):
4252- * 1) ^query$
4253- * 2) ^query
4254- * 3) \bquery
4255- * 4) split to words and seach \bword1.+\bword2 (if there are 2+ words)
4256- * 5) query
4257- * 6) split to characters and search \bq.+\bu.+\be.+\br.+\by
4258- * 7) split to characters and search \bq.*u.*e.*r.*y
4259- *
4260- * The set of returned regular expressions depends on MatcherFlags.
4261- */
4262-
4263- var results = new Gee.HashMap<Regex, int> ();
4264- Regex re;
4265-
4266- try
4267- {
4268- re = new Regex ("^(%s)$".printf (Regex.escape_string (query)), flags);
4269- results[re] = Match.Score.HIGHEST;
4270- }
4271- catch (RegexError err)
4272- {
4273- }
4274-
4275- try
4276- {
4277- re = new Regex ("^(%s)".printf (Regex.escape_string (query)), flags);
4278- results[re] = Match.Score.EXCELLENT;
4279- }
4280- catch (RegexError err)
4281- {
4282- }
4283-
4284- try
4285- {
4286- re = new Regex ("\\b(%s)".printf (Regex.escape_string (query)), flags);
4287- results[re] = Match.Score.VERY_GOOD;
4288- }
4289- catch (RegexError err)
4290- {
4291- }
4292-
4293- // split to individual chars
4294- string[] individual_words = Regex.split_simple ("\\s+", query.strip ());
4295- if (individual_words.length >= 2)
4296- {
4297- string[] escaped_words = {};
4298- foreach (unowned string word in individual_words)
4299- {
4300- escaped_words += Regex.escape_string (word);
4301- }
4302- string pattern = "\\b(%s)".printf (string.joinv (").+\\b(",
4303- escaped_words));
4304-
4305- try
4306- {
4307- re = new Regex (pattern, flags);
4308- results[re] = Match.Score.GOOD;
4309- }
4310- catch (RegexError err)
4311- {
4312- }
4313-
4314- // FIXME: do something generic here
4315- if (!(MatcherFlags.NO_REVERSED in match_flags))
4316- {
4317- if (escaped_words.length == 2)
4318- {
4319- var reversed = "\\b(%s)".printf (string.join (").+\\b(",
4320- escaped_words[1],
4321- escaped_words[0],
4322- null));
4323- try
4324- {
4325- re = new Regex (reversed, flags);
4326- results[re] = Match.Score.GOOD - Match.Score.INCREMENT_MINOR;
4327- }
4328- catch (RegexError err)
4329- {
4330- }
4331- }
4332- else
4333- {
4334- // not too nice, but is quite fast to compute
4335- var orred = "\\b((?:%s))".printf (string.joinv (")|(?:", escaped_words));
4336- var any_order = "";
4337- for (int i=0; i<escaped_words.length; i++)
4338- {
4339- bool is_last = i == escaped_words.length - 1;
4340- any_order += orred;
4341- if (!is_last) any_order += ".+";
4342- }
4343- try
4344- {
4345- re = new Regex (any_order, flags);
4346- results[re] = Match.Score.AVERAGE + Match.Score.INCREMENT_MINOR;
4347- }
4348- catch (RegexError err)
4349- {
4350- }
4351- }
4352- }
4353- }
4354-
4355- if (!(MatcherFlags.NO_SUBSTRING in match_flags))
4356- {
4357- try
4358- {
4359- re = new Regex ("(%s)".printf (Regex.escape_string (query)), flags);
4360- results[re] = Match.Score.BELOW_AVERAGE;
4361- }
4362- catch (RegexError err)
4363- {
4364- }
4365- }
4366-
4367- // split to individual characters
4368- string[] individual_chars = Regex.split_simple ("\\s*", query);
4369- string[] escaped_chars = {};
4370- foreach (unowned string word in individual_chars)
4371- {
4372- escaped_chars += Regex.escape_string (word);
4373- }
4374-
4375- // make "aj" match "Activity Journal"
4376- if (!(MatcherFlags.NO_PARTIAL in match_flags) &&
4377- individual_words.length == 1 && individual_chars.length <= 5)
4378- {
4379- string pattern = "\\b(%s)".printf (string.joinv (").+\\b(",
4380- escaped_chars));
4381- try
4382- {
4383- re = new Regex (pattern, flags);
4384- results[re] = Match.Score.ABOVE_AVERAGE;
4385- }
4386- catch (RegexError err)
4387- {
4388- }
4389- }
4390-
4391- if (!(MatcherFlags.NO_FUZZY in match_flags) && escaped_chars.length > 0)
4392- {
4393- string pattern = "\\b(%s)".printf (string.joinv (").*(",
4394- escaped_chars));
4395- try
4396- {
4397- re = new Regex (pattern, flags);
4398- results[re] = Match.Score.POOR;
4399- }
4400- catch (RegexError err)
4401- {
4402- }
4403- }
4404-
4405- var sorted_results = new Gee.ArrayList<Gee.Map.Entry<Regex, int>> ();
4406- var entries = results.entries;
4407- // FIXME: why it doesn't work without this?
4408- sorted_results.set_data ("entries-ref", entries);
4409- sorted_results.add_all (entries);
4410- sorted_results.sort ((a, b) =>
4411- {
4412- unowned Gee.Map.Entry<Regex, int> e1 = (Gee.Map.Entry<Regex, int>) a;
4413- unowned Gee.Map.Entry<Regex, int> e2 = (Gee.Map.Entry<Regex, int>) b;
4414- return e2.value - e1.value;
4415- });
4416-
4417- return sorted_results;
4418- }
4419- }
4420+* Copyright (c) 2010 Michal Hruby <michal.mhr@gmail.com>
4421+* 2017 elementary LLC.
4422+*
4423+* This program is free software; you can redistribute it and/or
4424+* modify it under the terms of the GNU General Public
4425+* License as published by the Free Software Foundation; either
4426+* version 2 of the License, or (at your option) any later version.
4427+*
4428+* This program is distributed in the hope that it will be useful,
4429+* but WITHOUT ANY WARRANTY; without even the implied warranty of
4430+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
4431+* General Public License for more details.
4432+*
4433+* You should have received a copy of the GNU General Public
4434+* License along with this program; if not, write to the
4435+* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
4436+* Boston, MA 02110-1301 USA
4437+*
4438+* Authored by: Michal Hruby <michal.mhr@gmail.com>
4439+*/
4440+
4441+namespace Synapse {
4442+ [Flags]
4443+ public enum QueryFlags {
4444+ /* HowTo create categories (32bit).
4445+ * Authored by Alberto Aldegheri <albyrock87+dev@gmail.com>
4446+ * Categories are "stored" in 3 Levels:
4447+ * Super-Category
4448+ * -> Category
4449+ * ----> Sub-Category
4450+ * ------------------------------------
4451+ * if (Super-Category does NOT have childs):
4452+ * SUPER = 1 << FreeBitPosition
4453+ * else:
4454+ * if (Category does NOT have childs)
4455+ * CATEGORY = 1 << FreeBitPosition
4456+ * else
4457+ * SUB = 1 << FreeBitPosition
4458+ * CATEGORY = OR ([subcategories, ...]);
4459+ *
4460+ * SUPER = OR ([categories, ...]);
4461+ *
4462+ *
4463+ * Remember:
4464+ * if you add or remove a category,
4465+ * change labels in UIInterface.CategoryConfig.init_labels
4466+ *
4467+ */
4468+ INCLUDE_REMOTE = 1 << 0,
4469+ UNCATEGORIZED = 1 << 1,
4470+
4471+ APPLICATIONS = 1 << 2,
4472+
4473+ ACTIONS = 1 << 3,
4474+
4475+ AUDIO = 1 << 4,
4476+ VIDEO = 1 << 5,
4477+ DOCUMENTS = 1 << 6,
4478+ IMAGES = 1 << 7,
4479+ FILES = AUDIO | VIDEO | DOCUMENTS | IMAGES,
4480+
4481+ PLACES = 1 << 8,
4482+
4483+ // FIXME: shouldn't this be FILES | INCLUDE_REMOTE?
4484+ INTERNET = 1 << 9,
4485+
4486+ // FIXME: Text Query flag? kinda weird, why do we have this here?
4487+ TEXT = 1 << 10,
4488+
4489+ CONTACTS = 1 << 11,
4490+
4491+ ALL = 0xFFFFFFFF,
4492+ LOCAL_CONTENT = ALL ^ QueryFlags.INCLUDE_REMOTE
4493+ }
4494+
4495+ [Flags]
4496+ public enum MatcherFlags {
4497+ NO_REVERSED = 1 << 0,
4498+ NO_SUBSTRING = 1 << 1,
4499+ NO_PARTIAL = 1 << 2,
4500+ NO_FUZZY = 1 << 3
4501+ }
4502+
4503+ public struct Query {
4504+ string query_string;
4505+ string query_string_folded;
4506+ Cancellable cancellable;
4507+ QueryFlags query_type;
4508+ uint max_results;
4509+ uint query_id;
4510+
4511+ public Query (uint query_id, string query, QueryFlags flags = QueryFlags.LOCAL_CONTENT, uint num_results = 96) {
4512+ this.query_id = query_id;
4513+ this.query_string = query;
4514+ this.query_string_folded = query.casefold ();
4515+ this.query_type = flags;
4516+ this.max_results = num_results;
4517+ }
4518+
4519+ public bool is_cancelled () {
4520+ return cancellable.is_cancelled ();
4521+ }
4522+
4523+ public void check_cancellable () throws SearchError {
4524+ if (cancellable.is_cancelled ()) {
4525+ throw new SearchError.SEARCH_CANCELLED ("Cancelled");
4526+ }
4527+ }
4528+
4529+ public static Gee.List<Gee.Map.Entry<Regex, int>> get_matchers_for_query (string query, MatcherFlags match_flags = 0, RegexCompileFlags flags = GLib.RegexCompileFlags.OPTIMIZE) {
4530+ /* create a couple of regexes and try to help with matching
4531+ * match with these regular expressions (with descending score):
4532+ * 1) ^query$
4533+ * 2) ^query
4534+ * 3) \bquery
4535+ * 4) split to words and seach \bword1.+\bword2 (if there are 2+ words)
4536+ * 5) query
4537+ * 6) split to characters and search \bq.+\bu.+\be.+\br.+\by
4538+ * 7) split to characters and search \bq.*u.*e.*r.*y
4539+ *
4540+ * The set of returned regular expressions depends on MatcherFlags.
4541+ */
4542+
4543+ var results = new Gee.HashMap<Regex, int> ();
4544+ Regex re;
4545+
4546+ try {
4547+ re = new Regex ("^(%s)$".printf (Regex.escape_string (query)), flags);
4548+ results[re] = Match.Score.HIGHEST;
4549+ } catch (RegexError err) { }
4550+
4551+ try {
4552+ re = new Regex ("^(%s)".printf (Regex.escape_string (query)), flags);
4553+ results[re] = Match.Score.EXCELLENT;
4554+ } catch (RegexError err) { }
4555+
4556+ try {
4557+ re = new Regex ("\\b(%s)".printf (Regex.escape_string (query)), flags);
4558+ results[re] = Match.Score.VERY_GOOD;
4559+ } catch (RegexError err) { }
4560+
4561+ // split to individual chars
4562+ string[] individual_words = Regex.split_simple ("\\s+", query.strip ());
4563+ if (individual_words.length >= 2) {
4564+ string[] escaped_words = {};
4565+ foreach (unowned string word in individual_words) {
4566+ escaped_words += Regex.escape_string (word);
4567+ }
4568+ string pattern = "\\b(%s)".printf (string.joinv (").+\\b(", escaped_words));
4569+
4570+ try {
4571+ re = new Regex (pattern, flags);
4572+ results[re] = Match.Score.GOOD;
4573+ } catch (RegexError err) { }
4574+
4575+ // FIXME: do something generic here
4576+ if (!(MatcherFlags.NO_REVERSED in match_flags)) {
4577+ if (escaped_words.length == 2) {
4578+ var reversed = "\\b(%s)".printf (string.join (").+\\b(", escaped_words[1], escaped_words[0], null));
4579+ try {
4580+ re = new Regex (reversed, flags);
4581+ results[re] = Match.Score.GOOD - Match.Score.INCREMENT_MINOR;
4582+ } catch (RegexError err) { }
4583+ } else {
4584+ // not too nice, but is quite fast to compute
4585+ var orred = "\\b((?:%s))".printf (string.joinv (")|(?:", escaped_words));
4586+ var any_order = "";
4587+ for (int i=0; i<escaped_words.length; i++) {
4588+ bool is_last = i == escaped_words.length - 1;
4589+ any_order += orred;
4590+ if (!is_last) {
4591+ any_order += ".+";
4592+ }
4593+ }
4594+ try {
4595+ re = new Regex (any_order, flags);
4596+ results[re] = Match.Score.AVERAGE + Match.Score.INCREMENT_MINOR;
4597+ } catch (RegexError err) { }
4598+ }
4599+ }
4600+ }
4601+
4602+ if (!(MatcherFlags.NO_SUBSTRING in match_flags)) {
4603+ try {
4604+ re = new Regex ("(%s)".printf (Regex.escape_string (query)), flags);
4605+ results[re] = Match.Score.BELOW_AVERAGE;
4606+ } catch (RegexError err) { }
4607+ }
4608+
4609+ // split to individual characters
4610+ string[] individual_chars = Regex.split_simple ("\\s*", query);
4611+ string[] escaped_chars = {};
4612+ foreach (unowned string word in individual_chars) {
4613+ escaped_chars += Regex.escape_string (word);
4614+ }
4615+
4616+ // make "aj" match "Activity Journal"
4617+ if (!(MatcherFlags.NO_PARTIAL in match_flags) && individual_words.length == 1 && individual_chars.length <= 5) {
4618+ string pattern = "\\b(%s)".printf (string.joinv (").+\\b(", escaped_chars));
4619+
4620+ try {
4621+ re = new Regex (pattern, flags);
4622+ results[re] = Match.Score.ABOVE_AVERAGE;
4623+ } catch (RegexError err) { }
4624+ }
4625+
4626+ if (!(MatcherFlags.NO_FUZZY in match_flags) && escaped_chars.length > 0) {
4627+ string pattern = "\\b(%s)".printf (string.joinv (").*(", escaped_chars));
4628+
4629+ try {
4630+ re = new Regex (pattern, flags);
4631+ results[re] = Match.Score.POOR;
4632+ } catch (RegexError err) { }
4633+ }
4634+
4635+ var sorted_results = new Gee.ArrayList<Gee.Map.Entry<Regex, int>> ();
4636+ var entries = results.entries;
4637+ // FIXME: why it doesn't work without this?
4638+ sorted_results.set_data ("entries-ref", entries);
4639+ sorted_results.add_all (entries);
4640+ sorted_results.sort ((a, b) => {
4641+ unowned Gee.Map.Entry<Regex, int> e1 = (Gee.Map.Entry<Regex, int>) a;
4642+ unowned Gee.Map.Entry<Regex, int> e2 = (Gee.Map.Entry<Regex, int>) b;
4643+ return e2.value - e1.value;
4644+ });
4645+
4646+ return sorted_results;
4647+ }
4648+ }
4649 }
4650-
4651
4652=== modified file 'lib/synapse-core/relevancy-backend-zg.vala'
4653--- lib/synapse-core/relevancy-backend-zg.vala 2014-07-03 06:38:42 +0000
4654+++ lib/synapse-core/relevancy-backend-zg.vala 2017-01-26 19:54:51 +0000
4655@@ -1,313 +1,288 @@
4656 /*
4657- * Copyright (C) 2010 Michal Hruby <michal.mhr@gmail.com>
4658- *
4659- * This library is free software; you can redistribute it and/or
4660- * modify it under the terms of the GNU Lesser General Public
4661- * License as published by the Free Software Foundation; either
4662- * version 2.1 of the License, or (at your option) any later version.
4663- *
4664- * This library is distributed in the hope that it will be useful,
4665- * but WITHOUT ANY WARRANTY; without even the implied warranty of
4666- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
4667- * Lesser General Public License for more details.
4668- *
4669- * You should have received a copy of the GNU Lesser General Public License
4670- * along with this program. If not, see <http://www.gnu.org/licenses/>.
4671- *
4672- * Authored by Michal Hruby <michal.mhr@gmail.com>
4673- *
4674- */
4675-
4676-namespace Synapse
4677-{
4678- private class ZeitgeistRelevancyBackend: Object, RelevancyBackend
4679- {
4680- private Zeitgeist.Log zg_log;
4681- private Zeitgeist.DataSourceRegistry zg_dsr;
4682- private Gee.Map<string, int> application_popularity;
4683- private Gee.Map<string, int> uri_popularity;
4684- private bool has_datahub_gio_module = false;
4685-
4686- private const float MULTIPLIER = 65535.0f;
4687-
4688- construct
4689- {
4690- zg_log = new Zeitgeist.Log ();
4691- application_popularity = new Gee.HashMap<string, int> ();
4692- uri_popularity = new Gee.HashMap<string, int> ();
4693-
4694- refresh_popularity ();
4695- check_data_sources.begin ();
4696-
4697- Timeout.add_seconds (60*30, refresh_popularity);
4698- }
4699-
4700- private async void check_data_sources ()
4701- {
4702- zg_dsr = new Zeitgeist.DataSourceRegistry ();
4703- try
4704- {
4705- var array = yield zg_dsr.get_data_sources (null);
4706-
4707- array.foreach ((ds) => {
4708- if (ds.unique_id == "com.zeitgeist-project,datahub,gio-launch-listener"
4709- && ds.enabled)
4710- {
4711- has_datahub_gio_module = true;
4712- return;
4713- }
4714- });
4715- }
4716- catch (Error err)
4717- {
4718- warning ("Unable to check Zeitgeist data sources: %s", err.message);
4719- }
4720- }
4721-
4722- private bool refresh_popularity ()
4723- {
4724- load_application_relevancies.begin ();
4725- load_uri_relevancies.begin ();
4726- return true;
4727- }
4728-
4729- private async void load_application_relevancies ()
4730- {
4731- Idle.add (load_application_relevancies.callback, Priority.LOW);
4732- yield;
4733-
4734- int64 end = new DateTime.now_local ().to_unix () * 1000;
4735- int64 start = end - Zeitgeist.Timestamp.WEEK * 4;
4736- Zeitgeist.TimeRange tr = new Zeitgeist.TimeRange (start, end);
4737-
4738- var event = new Zeitgeist.Event ();
4739- event.interpretation = "!" + Zeitgeist.ZG.LEAVE_EVENT;
4740- var subject = new Zeitgeist.Subject ();
4741- subject.interpretation = Zeitgeist.NFO.SOFTWARE;
4742- subject.uri = "application://*";
4743- event.add_subject (subject);
4744-
4745- var array = new GenericArray<Zeitgeist.Event> ();
4746- array.add (event);
4747-
4748- Zeitgeist.ResultSet rs;
4749-
4750- try
4751- {
4752- rs = yield zg_log.find_events (tr, array,
4753- Zeitgeist.StorageState.ANY,
4754- 256,
4755- Zeitgeist.ResultType.MOST_POPULAR_SUBJECTS,
4756- null);
4757-
4758- application_popularity.clear ();
4759- uint size = rs.size ();
4760- uint index = 0;
4761-
4762- // Zeitgeist (0.6) doesn't have any stats API, so let's approximate
4763-
4764- foreach (Zeitgeist.Event e in rs)
4765- {
4766- if (e.num_subjects () <= 0) continue;
4767- Zeitgeist.Subject s = e.subjects[0];
4768-
4769- float power = index / (size * 2) + 0.5f; // linearly <0.5, 1.0>
4770- float relevancy = 1.0f / Math.powf (index + 1, power);
4771- application_popularity[s.uri] = (int)(relevancy * MULTIPLIER);
4772-
4773- index++;
4774- }
4775- }
4776- catch (Error err)
4777- {
4778- warning ("%s", err.message);
4779- return;
4780- }
4781- }
4782-
4783- private async void load_uri_relevancies ()
4784- {
4785- Idle.add (load_uri_relevancies.callback, Priority.LOW);
4786- yield;
4787-
4788- int64 end = new DateTime.now_local ().to_unix () * 1000;
4789- int64 start = end - Zeitgeist.Timestamp.WEEK * 4;
4790- Zeitgeist.TimeRange tr = new Zeitgeist.TimeRange (start, end);
4791-
4792- var event = new Zeitgeist.Event ();
4793- event.interpretation = "!" + Zeitgeist.ZG.LEAVE_EVENT;
4794- var subject = new Zeitgeist.Subject ();
4795- subject.interpretation = "!" + Zeitgeist.NFO.SOFTWARE;
4796- subject.uri = "file://*";
4797- event.add_subject (subject);
4798-
4799- var array = new GenericArray<Zeitgeist.Event> ();
4800- array.add (event);
4801-
4802- Zeitgeist.ResultSet rs;
4803- Gee.Map<string, int> popularity_map = new Gee.HashMap<string, int> ();
4804-
4805- try
4806- {
4807- uint size, index;
4808- float power, relevancy;
4809- /* Get popularity for file uris */
4810- rs = yield zg_log.find_events (tr, array,
4811- Zeitgeist.StorageState.ANY,
4812- 256,
4813- Zeitgeist.ResultType.MOST_POPULAR_SUBJECTS,
4814- null);
4815-
4816- size = rs.size ();
4817- index = 0;
4818-
4819- // Zeitgeist (0.6) doesn't have any stats API, so let's approximate
4820-
4821- foreach (Zeitgeist.Event e1 in rs)
4822- {
4823- if (e1.num_subjects () <= 0) continue;
4824- Zeitgeist.Subject s1 = e1.subjects[0];
4825-
4826- power = index / (size * 2) + 0.5f; // linearly <0.5, 1.0>
4827- relevancy = 1.0f / Math.powf (index + 1, power);
4828- popularity_map[s1.uri] = (int)(relevancy * MULTIPLIER);
4829-
4830- index++;
4831- }
4832-
4833- /* Get popularity for web uris */
4834- subject.interpretation = Zeitgeist.NFO.WEBSITE;
4835- subject.uri = "";
4836- array = new GenericArray<Zeitgeist.Event> ();
4837- array.add (event);
4838-
4839- rs = yield zg_log.find_events (tr, array,
4840- Zeitgeist.StorageState.ANY,
4841- 128,
4842- Zeitgeist.ResultType.MOST_POPULAR_SUBJECTS,
4843- null);
4844-
4845- size = rs.size ();
4846- index = 0;
4847-
4848- // Zeitgeist (0.6) doesn't have any stats API, so let's approximate
4849-
4850- foreach (Zeitgeist.Event e2 in rs)
4851- {
4852- if (e2.num_subjects () <= 0) continue;
4853- Zeitgeist.Subject s2 = e2.subjects[0];
4854-
4855- power = index / (size * 2) + 0.5f; // linearly <0.5, 1.0>
4856- relevancy = 1.0f / Math.powf (index + 1, power);
4857- popularity_map[s2.uri] = (int)(relevancy * MULTIPLIER);
4858-
4859- index++;
4860- }
4861- }
4862- catch (Error err)
4863- {
4864- warning ("%s", err.message);
4865- }
4866-
4867- uri_popularity = popularity_map;
4868- }
4869-
4870- public float get_application_popularity (string desktop_id)
4871- {
4872- if (application_popularity.has_key (desktop_id))
4873- {
4874- return application_popularity[desktop_id] / MULTIPLIER;
4875- }
4876-
4877- return 0.0f;
4878- }
4879-
4880- public float get_uri_popularity (string uri)
4881- {
4882- if (uri_popularity.has_key (uri))
4883- {
4884- return uri_popularity[uri] / MULTIPLIER;
4885- }
4886-
4887- return 0.0f;
4888- }
4889-
4890- private void reload_relevancies ()
4891- {
4892- Idle.add_full (Priority.LOW, () =>
4893- {
4894- load_application_relevancies.begin ();
4895- return false;
4896- });
4897- }
4898-
4899- public void application_launched (AppInfo app_info)
4900- {
4901- // FIXME: get rid of this maverick-specific workaround
4902- // detect if the Zeitgeist GIO module is installed
4903- Type zg_gio_module = Type.from_name ("GAppLaunchHandlerZeitgeist");
4904- // FIXME: perhaps we should check app_info.should_show?
4905- // but user specifically asked to open this, so probably not
4906- // otoh the gio module won't pick it up if it's not should_show
4907- if (zg_gio_module != 0)
4908- {
4909- Utils.Logger.debug (this, "libzg-gio-module detected, not pushing");
4910- reload_relevancies ();
4911- return;
4912- }
4913-
4914- if (has_datahub_gio_module)
4915- {
4916- reload_relevancies ();
4917- return;
4918- }
4919-
4920- string app_uri = null;
4921- if (app_info.get_id () != null)
4922- {
4923- app_uri = "application://" + app_info.get_id ();
4924- }
4925- else if (app_info is DesktopAppInfo)
4926- {
4927- string? filename = (app_info as DesktopAppInfo).get_filename ();
4928- if (filename == null) return;
4929- app_uri = "application://" + Path.get_basename (filename);
4930- }
4931-
4932- Utils.Logger.debug (this, "launched \"%s\", pushing to ZG", app_uri);
4933- push_app_launch (app_uri, app_info.get_display_name ());
4934-
4935- // and refresh
4936- reload_relevancies ();
4937- }
4938-
4939- private void push_app_launch (string app_uri, string? display_name)
4940- {
4941- //debug ("pushing launch event: %s [%s]", app_uri, display_name);
4942- var event = new Zeitgeist.Event ();
4943- var subject = new Zeitgeist.Subject ();
4944-
4945- event.actor = "application://synapse.desktop";
4946- event.interpretation = Zeitgeist.ZG.ACCESS_EVENT;
4947- event.manifestation = Zeitgeist.ZG.USER_ACTIVITY;
4948- event.add_subject (subject);
4949-
4950- subject.uri = app_uri;
4951- subject.interpretation = Zeitgeist.NFO.SOFTWARE;
4952- subject.manifestation = Zeitgeist.NFO.SOFTWARE_ITEM;
4953- subject.mimetype ="application/x-desktop";
4954- subject.text = display_name;
4955-
4956- try
4957- {
4958- zg_log.insert_event_no_reply (event);
4959- }
4960- catch (Error err)
4961- {
4962- warning ("%s", err.message);
4963- return;
4964- }
4965- }
4966- }
4967+* Copyright (c) 2010 Michal Hruby <michal.mhr@gmail.com>
4968+* 2017 elementary LLC.
4969+*
4970+* This program is free software; you can redistribute it and/or
4971+* modify it under the terms of the GNU General Public
4972+* License as published by the Free Software Foundation; either
4973+* version 2 of the License, or (at your option) any later version.
4974+*
4975+* This program is distributed in the hope that it will be useful,
4976+* but WITHOUT ANY WARRANTY; without even the implied warranty of
4977+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
4978+* General Public License for more details.
4979+*
4980+* You should have received a copy of the GNU General Public
4981+* License along with this program; if not, write to the
4982+* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
4983+* Boston, MA 02110-1301 USA
4984+*
4985+* Authored by: Michal Hruby <michal.mhr@gmail.com>
4986+*/
4987+
4988+namespace Synapse {
4989+ private class ZeitgeistRelevancyBackend: Object, RelevancyBackend {
4990+ private Zeitgeist.Log zg_log;
4991+ private Zeitgeist.DataSourceRegistry zg_dsr;
4992+ private Gee.Map<string, int> application_popularity;
4993+ private Gee.Map<string, int> uri_popularity;
4994+ private bool has_datahub_gio_module = false;
4995+
4996+ private const float MULTIPLIER = 65535.0f;
4997+
4998+ construct {
4999+ zg_log = new Zeitgeist.Log ();
5000+ application_popularity = new Gee.HashMap<string, int> ();
The diff has been truncated for viewing.

Subscribers

People subscribed via source and target branches