Merge lp:~jeremy-munsch/synapse-project/firefox-plugin into lp:synapse-project
- firefox-plugin
- Merge into trunk
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 | ||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Rico Tzschichholz | Needs Fixing | ||
Review via email: mp+277776@code.launchpad.net |
Commit message
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)
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-
[INFO 11:35:20.716032] [firefox-
[INFO 11:35:20.722415] [firefox-
[INFO 11:35:20.722473] [firefox-
[INFO 11:35:20.829951] [firefox-
[INFO 11:35:20.830008] [firefox-
[INFO 11:35:20.930300] [firefox-
[INFO 11:35:20.930358] [firefox-
[INFO 11:35:21.029067] [firefox-
[INFO 11:35:21.029124] [firefox-
[INFO 11:35:21.131601] [firefox-
[INFO 11:35:21.131690] [firefox-
[INFO 11:35:21.234236] [firefox-
[INFO 11:35:21.234310] [firefox-
[INFO 11:35:21.339622] [firefox-
[INFO 11:35:21.339680] [firefox-
Something seems broken here.
Jeremy Munsch (jeremy-munsch) wrote : | # |
Same log as above with associated regex
http://
This is a complete non sense how could
[INFO 11:56:49.116434] [firefox-
[INFO 11:56:49.116478] [firefox-
[INFO 11:56:49.116511] [firefox-
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:/
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!
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
-
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:/
Jeremy Munsch (jeremy-munsch) wrote : | # |
Damn it wrong link i meant this https:/
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
-
add firefox plugin for bookmarks and history
add sqlite3 dependency
Preview Diff
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 () |
Working on addressing multiple issues.