Merge lp:~fabiozaramella/slingshot/code-style into lp:~elementary-pantheon/slingshot/trunk
- code-style
- Merge into trunk
Proposed by
Fabio Zaramella
Status: | Merged |
---|---|
Approved by: | Corentin Noël |
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 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Corentin Noël | Approve | ||
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.
Revision history for this message
Corentin Noël (tintou) wrote : | # |
- 726. By Fabio Zaramella
-
add elementary LLC to copyright and remove spaces in _()
Revision history for this message
Fabio Zaramella (fabiozaramella) wrote : | # |
Done :)
- 727. By Fabio Zaramella
-
Copyright indentation
Revision history for this message
Fabio Zaramella (fabiozaramella) wrote : | # |
Can you take a look at it again please?
Revision history for this message
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
Revision history for this message
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.
can you make so that the _() function has no space ?