Merge lp:~jeremy-munsch/synapse-project/firefox-plugin into lp:synapse-project

Proposed by Jeremy Munsch on 2015-11-18
Status: Needs review
Proposed branch: lp:~jeremy-munsch/synapse-project/firefox-plugin
Merge into: lp:synapse-project
Diff against target: 794 lines (+693/-2)
9 files modified
configure.ac (+4/-2)
debian/control (+1/-0)
src/core/data-sink.vala (+6/-0)
src/core/utils.vala (+6/-0)
src/plugins/Makefile.am (+1/-0)
src/plugins/firefox-plugin.vala (+668/-0)
src/ui/controller.vala (+5/-0)
src/ui/synapse-main.vala (+1/-0)
src/ui/view-base.vala (+1/-0)
To merge this branch: bzr merge lp:~jeremy-munsch/synapse-project/firefox-plugin
Reviewer Review Type Date Requested Status
Rico Tzschichholz 2015-11-18 Needs Fixing on 2015-11-20
Review via email: mp+277776@code.launchpad.net

Description of the change

Changed file operation plugin: is now valid for file scheme only - this to remove delete on other schemes as it is not supported at all -
Added Firefox plugin,

Is capable of making search in bookmarks and history.
It handles all user profiles and firefox canals (normal, beta, aurora (dev edition), nightly)

Provides Action to refresh Bookmarks, and search through History (with performance in mind, well I tried)

To post a comment you must log in.
Jeremy Munsch (jeremy-munsch) wrote :

Working on addressing multiple issues.

Jeremy Munsch (jeremy-munsch) wrote :

Updated, there is now some serious performance improvements.

Still to be handle maybe is the results MatchScore. Currently they are all on the same level or results order seems weird, this could have some more investigation.

Jeremy Munsch (jeremy-munsch) wrote :

Note :

Some thing very weird is the results for "2e rima"

[INFO 11:35:20.715979] [firefox-plugin:391] 2e RIMa
[INFO 11:35:20.716032] [firefox-plugin:392] matcher 100000
[INFO 11:35:20.722415] [firefox-plugin:396] " The Kevin Bowser Group "
[INFO 11:35:20.722473] [firefox-plugin:397] matcher 100000
[INFO 11:35:20.829951] [firefox-plugin:396] " The Kevin Bowser Group "
[INFO 11:35:20.830008] [firefox-plugin:397] matcher 90000
[INFO 11:35:20.930300] [firefox-plugin:396] " The Kevin Bowser Group "
[INFO 11:35:20.930358] [firefox-plugin:397] matcher 85000
[INFO 11:35:21.029067] [firefox-plugin:396] " The Kevin Bowser Group "
[INFO 11:35:21.029124] [firefox-plugin:397] matcher 80000
[INFO 11:35:21.131601] [firefox-plugin:396] " The Kevin Bowser Group "
[INFO 11:35:21.131690] [firefox-plugin:397] matcher 78000
[INFO 11:35:21.234236] [firefox-plugin:396] " The Kevin Bowser Group "
[INFO 11:35:21.234310] [firefox-plugin:397] matcher 60000
[INFO 11:35:21.339622] [firefox-plugin:396] " The Kevin Bowser Group "
[INFO 11:35:21.339680] [firefox-plugin:397] matcher 50000

Something seems broken here.

Jeremy Munsch (jeremy-munsch) wrote :

Same log as above with associated regex
http://paste.ubuntu.com/13363445/

This is a complete non sense how could
[INFO 11:56:49.116434] [firefox-plugin:397] regex ^(2e rima)$
[INFO 11:56:49.116478] [firefox-plugin:398] 2e rima " The Kevin Bowser Group "
[INFO 11:56:49.116511] [firefox-plugin:399] matcher 100000

that be matching ?

(second line is query + searchedtitle)

Jeremy Munsch (jeremy-munsch) wrote :

OK this is now cristal clear, with my silly comments above (just likely to be ignored).
Using Fuzzy search was a complete non sense here. I just removed then and now results are relevant.

I also have to say even if the code may seems to not be the best, every thing was very well handled in terms of performances and stability.

I will update the branch with the last changes and it should be very ready.

Jeremy Munsch (jeremy-munsch) wrote :

Note:

Moz profiles contains search.json, which are the opensearch engines used by Firefox, this will benefit opensearch plugin as a data source in a another work.

Jeremy Munsch (jeremy-munsch) wrote :

This plugin is now ready at its best, to be reviewed.

Rico Tzschichholz (ricotz) wrote :

regarding r629 better propose this separately, but I guess I might just push this:
https://paste.debian.net/plain/334259

On the actual topic here, this will take a while since I don't know where I would begin to clean things up :\

* get_fqdn() looks horrible while I could imaging there are glib functions for this
* Look into using GMutex
* Don't misuse static fields in some internal enum/struct declarations
* Avoid using all those static fields
* try to sort your code a bit (from top to bottom: consts, statics, normal methods)
* Are there multiple errordomains needed

It would be better if you look at it yourself again and give it another iterations!

review: Needs Fixing
Jeremy Munsch (jeremy-munsch) wrote :

OK thank you, I'm working on this.

* get_fqdn() I could not find parsing functions on all available libs, libsoup is the one to be used but including one lib for one secondary function/use would be overkilled i guess. I could also look for an existing pseudo code solution, but I could not find such a thing at the time.
I could also try to use Regex.replace, but i have to say to is also overkilled since perfect url regex do not exists at all, I know that well. I'll try to find something although.

Jeremy Munsch (jeremy-munsch) wrote :

Correct me if I'm wrong but mutex are not compatible with async functions as it is runned in this case by the Display/main thread, this would not work. I should have renamed the var to semaphore.

The goal here is to make all search requests (character typed) wait for the current search thread to finish. When it does, a signal is sent to all search requests and all are dismissed except the most recent one which is allow to start the search thread.

This avoids creating a thread per keyboard hit. despite I named my variable mutex, there are no critical resources to be protected in the Thread code. I also have and i7 so it won't bother me to have one thread per character but the search could last 1-2 seconds to find in all your history. So having a behaviour similar to "non retriggerable monostable" is likely to be to most performante way to proceed here.

Still making some cleanup as possible although

Jeremy Munsch (jeremy-munsch) wrote :

Ok I just made some clean-up according your previous comment.
I was also able to reduce amounts of code here.

I added some comment on top explaining choices i've made and how the plugin works.

Jeremy Munsch (jeremy-munsch) wrote :

Updated commit message

633. By Jeremy Munsch on 2015-12-02

add firefox plugin for bookmarks and history
add sqlite3 dependency

Jeremy Munsch (jeremy-munsch) wrote :

removed "using Sqlite;"

Jeremy Munsch (jeremy-munsch) wrote :

As a note here, and as mentionned by Rico, usage of threads would need this https://git.launchpad.net/plank/tree/lib/Items/ApplicationDockItem.vala#n397

Rico Tzschichholz (ricotz) wrote :

I didnt say this is *needed*. It is a more resource friendly method using a ThreadPool to have an amount of ready-to-use threads available and to avoid creating an arbitrary number of threads in unlucky cases.

Unmerged revisions

633. By Jeremy Munsch on 2015-12-02

add firefox plugin for bookmarks and history
add sqlite3 dependency

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'configure.ac'
2--- configure.ac 2015-11-14 20:50:37 +0000
3+++ configure.ac 2015-12-02 20:28:49 +0000
4@@ -62,7 +62,8 @@
5 gee-0.8 >= $MIN_GEE_VERSION \
6 json-glib-1.0 >= $MIN_JSON_VERSION \
7 keybinder-3.0 \
8- libnotify
9+ libnotify \
10+ sqlite3
11 )
12 SYNAPSE_MODULES_VALAFLAGS=" --pkg gdk-x11-3.0 \
13 --pkg gtk+-3.0 \
14@@ -71,7 +72,8 @@
15 --pkg gee-0.8 \
16 --pkg json-glib-1.0 \
17 --pkg keybinder-3.0 \
18- --pkg libnotify"
19+ --pkg libnotify \
20+ --pkg sqlite3"
21 AC_SUBST(SYNAPSE_MODULES_VALAFLAGS)
22
23 SYNAPSE_COMMON_VALAFLAGS=" --target-glib=2.40 --thread -g"
24
25=== modified file 'debian/control'
26--- debian/control 2015-11-30 14:47:10 +0000
27+++ debian/control 2015-12-02 20:28:49 +0000
28@@ -15,6 +15,7 @@
29 libkeybinder-3.0-dev,
30 libnotify-dev,
31 librest-dev,
32+ libsqlite3,
33 libappindicator3-dev (>= 0.0.7)
34 Vcs-Bzr: http://bazaar.launchpad.net/~synapse-core/synapse-project/trunk/
35 Vcs-Browser: http://bazaar.launchpad.net/~synapse-core/synapse-project/trunk/files
36
37=== modified file 'src/core/data-sink.vala'
38--- src/core/data-sink.vala 2014-07-10 11:39:28 +0000
39+++ src/core/data-sink.vala 2015-12-02 20:28:49 +0000
40@@ -480,6 +480,12 @@
41
42 return rs.get_sorted_list ();
43 }
44+
45+ public signal void plugin_reseted ();
46+ public void reset_plugins ()
47+ {
48+ plugin_reseted ();
49+ }
50 }
51 }
52
53
54=== modified file 'src/core/utils.vala'
55--- src/core/utils.vala 2015-11-19 09:11:34 +0000
56+++ src/core/utils.vala 2015-12-02 20:28:49 +0000
57@@ -46,6 +46,12 @@
58 return result;
59 }
60
61+ public static string replace_first_occurence (string subject, string old, string replacement = "")
62+ {
63+ int pos = subject.index_of (old);
64+ return "%s%s%s".printf (subject.substring (0, pos), replacement, subject.substring (pos + old.length));
65+ }
66+
67 public static string? remove_last_unichar (string input)
68 {
69 long char_count = input.char_count ();
70
71=== modified file 'src/plugins/Makefile.am'
72--- src/plugins/Makefile.am 2015-07-21 08:13:23 +0000
73+++ src/plugins/Makefile.am 2015-12-02 20:28:49 +0000
74@@ -37,6 +37,7 @@
75 ssh-plugin.vala \
76 dictionary.vala \
77 directory-plugin.vala \
78+ firefox-plugin.vala \
79 gnome-bookmarks-plugin.vala \
80 gnome-session-plugin.vala \
81 gnome-screensaver-plugin.vala \
82
83=== added file 'src/plugins/firefox-plugin.vala'
84--- src/plugins/firefox-plugin.vala 1970-01-01 00:00:00 +0000
85+++ src/plugins/firefox-plugin.vala 2015-12-02 20:28:49 +0000
86@@ -0,0 +1,668 @@
87+/*
88+ * Copyright (C) 2015 Jérémy Munsch <jeremy.munsch@gmail.com>
89+ *
90+ * This program is free software: you can redistribute it and/or modify it
91+ * under the terms of the GNU General Public License as published by the
92+ * Free Software Foundation, either version 3 of the License, or
93+ * (at your option) any later version.
94+ *
95+ * This program is distributed in the hope that it will be useful, but
96+ * WITHOUT ANY WARRANTY; without even the implied warranty of
97+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
98+ * See the GNU General Public License for more details.
99+ *
100+ * You should have received a copy of the GNU General Public License along
101+ * with this program. If not, see <http://www.gnu.org/licenses/>.
102+ *
103+ *
104+ * Performance notes:
105+ *
106+ * Sqlite databases can be hudge, blocking synapse,
107+ * so using Threads is required in this plugin
108+ *
109+ * Only one search is conducted at a time,
110+ * and then when it finishes a signal is sent
111+ * to other search requests waiting and only
112+ * the most recent one is allowed to be run.
113+ *
114+ * Note : path variables would be used by other plugins
115+ * eg : opensearch plugin
116+ * therefore they are meant to be static
117+ *
118+ *
119+ * --- How it works ---
120+ *
121+ * This plugin is looking into ~/.mozilla/{firefox,firefox-trunk}
122+ * for user profiles by reading ini files, then parsing places.sql
123+ *
124+ * It searches in moz_bookmarks, moz_historyvisits, moz_places
125+ * History and bookmarks hashmaps are stored in Profiles.
126+ * Each profil has a canal associated (Firefox, FirefoxTrunk)
127+ *
128+ * Bookmarks is searched in all canals, but History is search
129+ * in canals separatly (two History search Actions)
130+ *
131+ * Selecting an action stays memorized until another action is
132+ * choosen or Synapse Hidden (close event added here)
133+ * This means by selecting Search in FirefoxTrunk history
134+ * all search are conducted in it until antoher action is
135+ * choosen or Synapse Hidden.
136+ *
137+ */
138+
139+namespace Synapse
140+{
141+ private errordomain ProfileParseError { ERROR }
142+
143+ public class FirefoxPlugin : Object, Activatable, ItemProvider, ActionProvider
144+ {
145+
146+
147+ public unowned DataSink data_sink { get; construct; }
148+
149+ public bool enabled { get; set; default = true; }
150+ private bool search_busy = false;
151+ private static uint last_registered_signal_id = 0;
152+
153+ private signal void search_completed ();
154+
155+ private static unowned DesktopFileInfo firefox_app_info { set; get; default = null; }
156+ private static unowned DesktopFileInfo firefoxtrunk_app_info { set; get; default = null; }
157+ private static FirefoxCanal firefox_canal { get; set; default = FirefoxCanal.NOFIREFOX; }
158+ public static Gee.List<string> firefox_profiles_paths;
159+ public static Gee.List<FirefoxProfile?> firefox_profiles;
160+ public static string firefox_root_dir { get; private set; default = ".mozilla"; }
161+
162+ private FirefoxCanal search_using_canal { get; set; default = FirefoxCanal.FIREFOXALL; }
163+ private bool use_history;
164+ private RefreshBookmarks refresh_action;
165+ private SearchHistory history_action_firefox;
166+ private SearchHistory history_action_firefoxtrunk;
167+
168+
169+ /**
170+ * Plugin Objects
171+ */
172+
173+ public enum FirefoxCanal
174+ {
175+ NOFIREFOX,
176+ FIREFOX = 1 << 0,
177+ FIREFOXTRUNK = 1 << 1,
178+ FIREFOXALL = ~0;
179+
180+ public string to_string ()
181+ {
182+ switch (this)
183+ {
184+ case FirefoxCanal.FIREFOX:
185+ return "firefox";
186+ case FirefoxCanal.FIREFOXTRUNK:
187+ return "firefox-trunk";
188+ default:
189+ return "";
190+ }
191+ }
192+
193+ public string display ()
194+ {
195+ switch (this)
196+ {
197+ case FirefoxCanal.FIREFOX:
198+ return null != firefox_app_info ? firefox_app_info.name : "Firefox";
199+ case FirefoxCanal.FIREFOXTRUNK:
200+ return null != firefoxtrunk_app_info ? firefoxtrunk_app_info.name : "Nightly";
201+ default:
202+ return null != firefox_app_info ? firefox_app_info.name : "Firefox";
203+ }
204+ }
205+
206+ public FirefoxCanal from_string (string canal)
207+ {
208+ switch (canal)
209+ {
210+ case "firefox":
211+ return FirefoxCanal.FIREFOX;
212+ case "firefox-trunk":
213+ return FirefoxCanal.FIREFOXTRUNK;
214+ default:
215+ return FirefoxCanal.NOFIREFOX;
216+ }
217+ }
218+ }
219+ public struct FirefoxProfile
220+ {
221+ public bool is_default;
222+ public string name;
223+ public string path;
224+ public FirefoxCanal type;
225+ public Gee.HashMap<string, BookmarkMatch> bookmarks;
226+ public Gee.HashMap<string, HistoryMatch> history;
227+ }
228+
229+ public class BookmarkMatch : UriMatch
230+ {
231+ public BookmarkMatch (string name, string url)
232+ {
233+ Object (title: name,
234+ description: url,
235+ uri: url,
236+ mime_type: "text/html",
237+ has_thumbnail: false, icon_name: "bookmarks");
238+ }
239+ }
240+
241+ public class HistoryMatch : UriMatch
242+ {
243+ public int default_relevancy { get; set; default = MatchScore.INCREMENT_SMALL; }
244+ public HistoryMatch (string name, string url)
245+ {
246+ Object (title: name,
247+ description: url,
248+ uri: url,
249+ mime_type: "text/html",
250+ has_thumbnail: false, icon_name: "bookmarks");
251+ }
252+ }
253+
254+ private class RefreshBookmarks : SearchMatch
255+ {
256+ public int default_relevancy { get; set; default = MatchScore.INCREMENT_SMALL; }
257+ private unowned FirefoxPlugin plugin;
258+ public FirefoxCanal canal { get; set; default = FirefoxCanal.FIREFOXALL; }
259+
260+ public RefreshBookmarks (FirefoxPlugin plugin, FirefoxCanal search_canal)
261+ {
262+ Object (has_thumbnail: false,
263+ icon_name: "firefox",
264+ title: _("Refresh %s bookmarks".printf (search_canal.display ())),
265+ description: _("Try to search by refreshing %s bookmarks".printf (search_canal.display ())));
266+ this.plugin = plugin;
267+ canal = search_canal;
268+ }
269+
270+ // for SearchMatch interface
271+ public override async Gee.List<Match> search (string query,
272+ QueryFlags flags,
273+ ResultSet? dest_result_set,
274+ Cancellable? cancellable = null) throws SearchError
275+ {
276+ var q = Query (0, query, flags);
277+ q.cancellable = cancellable;
278+
279+ try
280+ {
281+ Thread<void*> init = new Thread<void*>.try ("FirefoxPluginRefreshBookmarks", () => {
282+ plugin.parse_bookmarks ();
283+ Idle.add (search.callback);
284+ return null;
285+ });
286+ yield;
287+ if (null == init)
288+ {
289+ plugin.data_sink.set_plugin_enabled (typeof (FirefoxPlugin), false);
290+ throw new ThreadError.AGAIN ("Firefox Plugin init Refresh Bookmarks thread failed, deactivating plugin");
291+ }
292+ }
293+ catch (Error e)
294+ {
295+ warning ("%s".printf (e.message));
296+ plugin.data_sink.set_plugin_enabled (typeof (FirefoxPlugin), false);
297+ return dest_result_set.get_sorted_list ();
298+ }
299+
300+ plugin.use_history = false;
301+ plugin.search_using_canal = canal;
302+ ResultSet? results = yield plugin.search (q);
303+ dest_result_set.add_all (results);
304+
305+ return dest_result_set.get_sorted_list ();
306+ }
307+ }
308+
309+ private class SearchHistory : SearchMatch
310+ {
311+ public int default_relevancy { get; set; default = MatchScore.INCREMENT_SMALL; }
312+ private unowned FirefoxPlugin plugin;
313+ public FirefoxCanal canal { get; set; default = FirefoxCanal.FIREFOXALL; }
314+
315+ public SearchHistory (FirefoxPlugin plugin, FirefoxCanal search_canal)
316+ {
317+ Object (has_thumbnail: false,
318+ icon_name: "firefox",
319+ title: _("Search in %s history".printf (search_canal.display ())),
320+ description: _("Try to search in %s history".printf (search_canal.display ())));
321+ this.plugin = plugin;
322+ canal = search_canal;
323+ }
324+
325+ // for SearchMatch interface
326+ public override async Gee.List<Match> search (string query,
327+ QueryFlags flags,
328+ ResultSet? dest_result_set,
329+ Cancellable? cancellable = null) throws SearchError
330+ {
331+ var q = Query (0, query, flags);
332+ q.cancellable = cancellable;
333+
334+ try
335+ {
336+ Thread<void*> init = new Thread<void*>.try ("FirefoxPluginSearchHistory", () => {
337+ plugin.parse_histories ();
338+ Idle.add (search.callback);
339+ return null;
340+ });
341+ yield;
342+ if (null == init)
343+ {
344+ plugin.data_sink.set_plugin_enabled (typeof (FirefoxPlugin), false);
345+ throw new ThreadError.AGAIN ("Firefox Plugin init Search History thread failed, deactivating plugin");
346+ }
347+ }
348+ catch (Error e)
349+ {
350+ warning ("%s".printf (e.message));
351+ plugin.data_sink.set_plugin_enabled (typeof (FirefoxPlugin), false);
352+ return dest_result_set.get_sorted_list ();
353+ }
354+
355+ plugin.use_history = true;
356+ plugin.search_using_canal = canal;
357+ ResultSet? results = yield plugin.search (q);
358+ dest_result_set.add_all (results);
359+
360+ return dest_result_set.get_sorted_list ();
361+ }
362+ }
363+
364+
365+ /**
366+ * Initialisation methods
367+ */
368+
369+ static construct
370+ {
371+ firefox_root_dir = Path.build_filename (Environment.get_home_dir (), firefox_root_dir);
372+ var dfs = DesktopFileService.get_default ();
373+ firefox_app_info = dfs.get_desktop_files_for_exec (FirefoxCanal.FIREFOX.to_string ()).first ();
374+ firefoxtrunk_app_info = dfs.get_desktop_files_for_exec (FirefoxCanal.FIREFOXTRUNK.to_string ()).first ();
375+ register_plugin ();
376+ }
377+
378+ construct
379+ {
380+ use_history = false;
381+ refresh_action = new RefreshBookmarks (this, FirefoxCanal.FIREFOXALL);
382+ history_action_firefox = new SearchHistory (this, FirefoxCanal.FIREFOX);
383+ history_action_firefoxtrunk = new SearchHistory (this, FirefoxCanal.FIREFOXTRUNK);
384+ firefox_profiles_paths = new Gee.ArrayList<string> ();
385+
386+ string firefox_path = Path.build_filename (firefox_root_dir, FirefoxCanal.FIREFOX.to_string ());
387+ string firefox_trunk_path = Path.build_filename (firefox_root_dir, FirefoxCanal.FIREFOXTRUNK.to_string ());
388+
389+ if ((bool)(firefox_canal & FirefoxCanal.FIREFOX) && FileUtils.test (firefox_path, FileTest.IS_DIR))
390+ firefox_profiles_paths.add (firefox_path);
391+
392+ if ((bool)(firefox_canal & FirefoxCanal.FIREFOXTRUNK) && FileUtils.test (firefox_trunk_path, FileTest.IS_DIR))
393+ firefox_profiles_paths.add (firefox_trunk_path);
394+ }
395+
396+ protected override void constructed ()
397+ {
398+ data_sink.plugin_reseted.connect (this.reset);
399+ }
400+
401+ public static void register_plugin ()
402+ {
403+ if (Environment.find_program_in_path (FirefoxCanal.FIREFOX.to_string ()) != null)
404+ firefox_canal |= FirefoxCanal.FIREFOX;
405+ if (Environment.find_program_in_path (FirefoxCanal.FIREFOXTRUNK.to_string ()) != null)
406+ firefox_canal |= FirefoxCanal.FIREFOXTRUNK;
407+
408+ PluginRegistry.get_default ().register_plugin (
409+ typeof (FirefoxPlugin),
410+ _("Firefox"),
411+ _("Browse and open Firefox bookmarks and history"),
412+ FirefoxCanal.FIREFOX.to_string (),
413+ register_plugin,
414+ (bool)firefox_canal,
415+ _("Firefox is not installed")
416+ );
417+ }
418+
419+ public void activate ()
420+ {
421+ firefox_profiles = new Gee.ArrayList<FirefoxProfile?> ();
422+
423+ try
424+ {
425+ // This would freeze ui in direct run
426+ Thread<void*> init = new Thread<void*>.try ("FirefoxPluginActivation", () => {
427+ search_busy = true;
428+ parse_profiles ();
429+ parse_bookmarks ();
430+ parse_histories ();
431+ search_busy = false;
432+ return null;
433+ });
434+
435+ if (null == init)
436+ {
437+ warning ("Firefox Plugin failed to init, deactivating plugin");
438+ data_sink.set_plugin_enabled (typeof (FirefoxPlugin), false);
439+ }
440+ }
441+ catch (Error e)
442+ {
443+ warning ("%s".printf (e.message));
444+ }
445+ }
446+
447+ public void deactivate ()
448+ {
449+ firefox_profiles = null;
450+ }
451+
452+
453+ /**
454+ * Behaviors methods
455+ */
456+
457+ public void reset ()
458+ {
459+ use_history = false;
460+ search_using_canal = FirefoxCanal.FIREFOXALL;
461+ }
462+
463+ public bool handles_unknown ()
464+ {
465+ return true;
466+ }
467+
468+ public bool handles_query (Query query)
469+ {
470+ return (QueryFlags.INTERNET in query.query_type);
471+ }
472+
473+
474+ /**
475+ * Search related methods
476+ */
477+
478+ public async ResultSet? search (Query q) throws SearchError
479+ {
480+ bool search_cancelled = false;
481+
482+ var results = new ResultSet ();
483+
484+ try
485+ {
486+ uint state_signal_id = last_registered_signal_id = Idle.add_full (Priority.DEFAULT_IDLE, search.callback);
487+ yield;
488+
489+ // Cancel if not the most recent search
490+ if (state_signal_id != last_registered_signal_id)
491+ throw new SearchError.SEARCH_CANCELLED ("Cancelled");
492+
493+ search_busy = true;
494+
495+ var matchers = Query.get_matchers_for_query (q.query_string, MatcherFlags.NO_FUZZY, RegexCompileFlags.OPTIMIZE | RegexCompileFlags.CASELESS);
496+
497+ // This would freeze ui in direct run
498+ Thread<void*> init = new Thread<void*>.try ("FirefoxPluginSearchThread", () => {
499+ try
500+ {
501+ foreach (var matcher in matchers)
502+ {
503+ foreach (var profile in firefox_profiles)
504+ {
505+ // Use only Default profiles and Search match canal
506+ if (profile.is_default && (bool)(search_using_canal & profile.type))
507+ {
508+ if (use_history)
509+ {
510+ foreach (var history_entry in profile.history.entries)
511+ {
512+ q.check_cancellable ();
513+
514+ // drop search for newerone
515+ if (state_signal_id != last_registered_signal_id)
516+ throw new SearchError.SEARCH_CANCELLED ("Cancelled");
517+
518+ // search titles only for performance sakes
519+ if (matcher.key.match (history_entry.value.title))
520+ results.add (history_entry.value, matcher.value);
521+ }
522+ }
523+ else
524+ {
525+ foreach (var bookmark in profile.bookmarks.entries)
526+ {
527+ q.check_cancellable ();
528+
529+ // drop search for newerone
530+ if (state_signal_id != last_registered_signal_id)
531+ throw new SearchError.SEARCH_CANCELLED ("Cancelled");
532+
533+ // title search
534+ if (matcher.key.match (bookmark.value.title))
535+ {
536+ results.add (bookmark.value, matcher.value);
537+ continue;
538+ }
539+
540+ // uri search
541+ if (matcher.key.match (get_fqdn (bookmark.value.uri)))
542+ results.add (bookmark.value, matcher.value-MatchScore.INCREMENT_MINOR);
543+ }
544+ }
545+ }
546+ }
547+ }
548+ }
549+ catch (SearchError e)
550+ {
551+ search_cancelled = true;
552+ }
553+ Idle.add (search.callback);
554+ return null;
555+ });
556+
557+ if (null == init)
558+ {
559+ warning ("Init search thread failed, deactivating plugin");
560+ data_sink.set_plugin_enabled (typeof (FirefoxPlugin), false);
561+ }
562+ else
563+ yield; // wait for thread to finish
564+ }
565+ catch (SearchError e)
566+ {
567+ }
568+ catch (Error e)
569+ {
570+ warning ("%s".printf (e.message));
571+ }
572+
573+ search_busy = false;
574+
575+ // honor cancelled search from Thread
576+ if (search_cancelled)
577+ throw new SearchError.SEARCH_CANCELLED ("Cancelled");
578+
579+ return results;
580+ }
581+
582+ public ResultSet? find_for_match (ref Query q, Match match)
583+ {
584+ if (0 == (q.query_type & QueryFlags.INTERNET) || !(match is UnknownMatch))
585+ return null;
586+ var results = new ResultSet ();
587+ results.add (refresh_action, refresh_action.default_relevancy);
588+ if ((bool)firefox_canal & history_action_firefox.canal)
589+ results.add (history_action_firefox, history_action_firefox.default_relevancy);
590+ if ((bool)firefox_canal & history_action_firefox.canal)
591+ results.add (history_action_firefoxtrunk, history_action_firefoxtrunk.default_relevancy);
592+ return results;
593+ }
594+
595+
596+ /**
597+ * Private methods
598+ */
599+
600+ private void parse_profiles ()
601+ {
602+ foreach (string profile_path in firefox_profiles_paths)
603+ {
604+ if (FileUtils.test (profile_path, FileTest.EXISTS))
605+ {
606+ try
607+ {
608+ KeyFile file = new KeyFile ();
609+ file.set_list_separator ('\n');
610+ file.load_from_file (Path.build_filename (profile_path, "profiles.ini"), KeyFileFlags.NONE);
611+
612+ foreach (string group in file.get_groups ())
613+ {
614+ if ("general" == group.down ())
615+ continue;
616+
617+ string name = file.get_string (group, "Name");
618+ string _path = file.get_string (group, "Path");
619+ bool _is_default = false;
620+
621+ if ("1" == file.get_string (group, "IsRelative"))
622+ _path = Path.build_filename (profile_path, _path);
623+
624+ if (!FileUtils.test (_path, FileTest.EXISTS))
625+ {
626+ warning ("Profile '%s' was not found at %s".printf (name, _path));
627+ continue;
628+ }
629+
630+ if (file.has_key (group, "Default"))
631+ _is_default = "1" == file.get_string (group, "Default") ? true : false;
632+
633+ firefox_profiles.add (FirefoxProfile ()
634+ {
635+ name = file.get_string (group, "Name"),
636+ path = _path,
637+ is_default = _is_default,
638+ type = firefox_canal.from_string (Path.get_basename (profile_path)),
639+ bookmarks = new Gee.HashMap<string, BookmarkMatch> (),
640+ history = new Gee.HashMap<string, HistoryMatch> ()
641+ });
642+ }
643+ }
644+ catch (Error e)
645+ {
646+ warning ("%s".printf (e.message));
647+ }
648+ }
649+ }
650+ }
651+
652+ /**
653+ * Send queries to DB
654+ */
655+ private Sqlite.Statement query_places (string query, string profile_path) throws ProfileParseError
656+ {
657+ string places_sqlite = Path.build_filename (profile_path, "places.sqlite");
658+
659+ if (!FileUtils.test (profile_path, FileTest.EXISTS))
660+ throw new ProfileParseError.ERROR ("Profile folder not found at %s".printf (profile_path));
661+ if (!FileUtils.test (places_sqlite, FileTest.EXISTS))
662+ throw new ProfileParseError.ERROR ("Profile places database not found at %s".printf (places_sqlite));
663+
664+ Sqlite.Database db;
665+ Sqlite.Statement stmt;
666+
667+ int ec = Sqlite.Database.open_v2 (places_sqlite, out db, Sqlite.OPEN_READONLY);
668+ if (ec != Sqlite.OK)
669+ throw new ProfileParseError.ERROR ("Can't open database: %d: %s\n", db.errcode (), db.errmsg ());
670+
671+ ec = db.prepare_v2 (query, query.length, out stmt);
672+ if (ec != Sqlite.OK)
673+ throw new ProfileParseError.ERROR ("Error: %d: %s\n", db.errcode (), db.errmsg ());
674+
675+ return stmt;
676+ }
677+
678+ private void get_data_from_profiles_db (Type match_type, string query)
679+ {
680+ foreach (var profile in firefox_profiles)
681+ {
682+ try
683+ {
684+ var stmt = query_places (query, profile.path);
685+ while (stmt.step () == Sqlite.ROW)
686+ {
687+ var title = stmt.column_text (0) ?? null;
688+ var url = stmt.column_text (1) ?? null;
689+
690+ // if we can't display no use, then dismiss
691+ if (null == title || null == url)
692+ continue;
693+
694+ if (match_type == typeof (BookmarkMatch))
695+ {
696+ if (!profile.bookmarks.has_key (url))
697+ profile.bookmarks.set (url, new BookmarkMatch (title, url));
698+ }
699+ else if (!profile.history.has_key (url))
700+ profile.history.set (url, new HistoryMatch (title, url));
701+ }
702+ }
703+ catch (ProfileParseError e)
704+ {
705+ message ("%s : %s".printf (profile.name, e.message));
706+ }
707+ }
708+ }
709+
710+ private void parse_bookmarks ()
711+ {
712+ string query = """
713+ SELECT b.title, h.url
714+ FROM moz_places h
715+ JOIN moz_bookmarks b ON h.id = b.fk
716+ WHERE b.title != ""
717+ AND h.url != ""
718+ AND h.url NOT LIKE "place:%"
719+ AND h.url NOT LIKE "source:%"
720+ AND h.url NOT LIKE "css:%"
721+ AND h.url NOT LIKE "data:%"
722+ AND h.url NOT LIKE "file:%"
723+ AND h.url NOT LIKE "javascript:%";""";
724+
725+ get_data_from_profiles_db (typeof (BookmarkMatch), query);
726+ }
727+
728+ private void parse_histories ()
729+ {
730+ string query = """
731+ SELECT h.title, h.url
732+ FROM moz_places h
733+ JOIN moz_historyvisits b ON h.id = b.place_id
734+ WHERE h.title != ""
735+ AND h.url != ""
736+ AND h.url NOT LIKE "place:%"
737+ AND h.url NOT LIKE "source:%"
738+ AND h.url NOT LIKE "css:%"
739+ AND h.url NOT LIKE "data:%"
740+ AND h.url NOT LIKE "file:%"
741+ AND h.url NOT LIKE "javascript:%";""";
742+
743+ get_data_from_profiles_db (typeof (HistoryMatch), query);
744+ }
745+
746+ private string get_fqdn (string uri)
747+ {
748+ string _uri = Utils.replace_first_occurence (uri, Uri.parse_scheme (uri) + "://");
749+ _uri = Utils.replace_first_occurence (_uri, "www.");
750+ int slash_index = _uri.index_of_char ('/');
751+ return -1 != slash_index ? _uri.slice (0, slash_index) : _uri;
752+ }
753+ }
754+}
755
756=== modified file 'src/ui/controller.vala'
757--- src/ui/controller.vala 2015-11-30 14:44:16 +0000
758+++ src/ui/controller.vala 2015-12-02 20:28:49 +0000
759@@ -177,6 +177,11 @@
760 this.view.summon_or_vanish ();
761 }
762
763+ public void view_is_hidden ()
764+ {
765+ data_sink.reset_plugins ();
766+ }
767+
768 /* End interface implementation */
769 protected Gtk.IMMulticontext im_context;
770
771
772=== modified file 'src/ui/synapse-main.vala'
773--- src/ui/synapse-main.vala 2015-10-05 07:03:23 +0000
774+++ src/ui/synapse-main.vala 2015-12-02 20:28:49 +0000
775@@ -176,6 +176,7 @@
776 typeof (SshPlugin),
777 typeof (XnoiseActions),
778 typeof (ChromiumPlugin),
779+ typeof (FirefoxPlugin),
780 typeof (FileOpPlugin),
781 typeof (PidginPlugin),
782 typeof (ChatActions),
783
784=== modified file 'src/ui/view-base.vala'
785--- src/ui/view-base.vala 2015-11-16 10:25:05 +0000
786+++ src/ui/view-base.vala 2015-12-02 20:28:49 +0000
787@@ -448,6 +448,7 @@
788 this.hide ();
789 this.vanished ();
790 IconCacheService.get_default ().reduce_cache ();
791+ ((Gui.Controller)controller).view_is_hidden ();
792 }
793
794 public virtual void summon_or_vanish ()