Merge ~letterus/synapse-project:recoll_integration into synapse-project:master

Proposed by Johannes Brakensiek
Status: Needs review
Proposed branch: ~letterus/synapse-project:recoll_integration
Merge into: synapse-project:master
Diff against target: 254 lines (+226/-0)
3 files modified
src/plugins/Makefile.am (+1/-0)
src/plugins/recoll-plugin.vala (+224/-0)
src/ui/synapse-main.vala (+1/-0)
Reviewer Review Type Date Requested Status
Michal Hruby Pending
Review via email: mp+435025@code.launchpad.net

Commit message

Add plugin that integrations searching via Recoll, originally written by Patrick Marchwiak.

Description of the change

This branch features the Recoll plugin originally written by Patrick Marchwiak at https://code.launchpad.net/~pmarchwiak/synapse-project/recoll-plugin/+merge/133784.

It was updated to the current API. In addition to the former behaviour a timeout for keystrokes was added. Thus a search query via Recoll only is performed if the user stopped typing.

This resolves the problem mentioned here: https://code.launchpad.net/~pmarchwiak/synapse-project/recoll-plugin/+merge/133784/comments/289583

Edit: In addition this plugin now only is going to start working after the third character entered. The assumption is you won't be able to enter any meaningful word using less than three chars.

To post a comment you must log in.
Revision history for this message
Johannes Brakensiek (letterus) wrote :

Pinging this issue. Would be great if it could be merged.

Unmerged commits

df08c14... by Letterus <email address hidden>

More verbose code

4bfab77... by Letterus <email address hidden>

Disable debugging log

358f88f... by Letterus <email address hidden>

Add some async timer and timeouts to check if the user has stopped typing. Only start the Recoll search task then.

078ec2e... by Letterus <email address hidden>

First working version of Patrick Marchwiak's Recoll plugin reintegrated

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/src/plugins/Makefile.am b/src/plugins/Makefile.am
2index 28a60b7..2827053 100644
3--- a/src/plugins/Makefile.am
4+++ b/src/plugins/Makefile.am
5@@ -48,6 +48,7 @@ libplugins_la_VALASOURCES = \
6 pass-plugin.vala \
7 pastebin-plugin.vala \
8 pidgin-plugin.vala \
9+ recoll-plugin.vala \
10 rhythmbox-plugin.vala \
11 selection-plugin.vala \
12 test-slow-plugin.vala \
13diff --git a/src/plugins/recoll-plugin.vala b/src/plugins/recoll-plugin.vala
14new file mode 100644
15index 0000000..9bf64d7
16--- /dev/null
17+++ b/src/plugins/recoll-plugin.vala
18@@ -0,0 +1,224 @@
19+/*
20+ *
21+ * Authored by Patrick Marchwiak <pd@marchwiak.com>
22+ * Updated by Johannes Brakensiek <letterus@codingpastor.de>
23+ *
24+ */
25+namespace Synapse
26+{
27+
28+ // Sends query to Recoll command line tool.
29+ public class RecollPlugin : Object, Activatable, ItemProvider
30+ {
31+ // a mandatory property
32+ public bool enabled { get; set; default = true; }
33+
34+ // properties for keeping track typing activity of the user
35+ public signal void typing_finished ();
36+ private bool user_typing = false;
37+ private Timer timer;
38+
39+ const uint maxKeystrokeInterval = 300;
40+
41+ static construct
42+ {
43+ // register the plugin when the class is constructed
44+ register_plugin ();
45+ }
46+
47+ construct
48+ {
49+ timer = new Timer();
50+ }
51+
52+ // this method is called when a plugin is enabled
53+ // use it to initialize your plugin
54+ public void activate ()
55+ {
56+ }
57+
58+ // this method is called when a plugin is disabled
59+ // use it to free the resources you're using
60+ public void deactivate ()
61+ {
62+ }
63+
64+ // register your plugin in the UI
65+ static void register_plugin ()
66+ {
67+ PluginRegistry.get_default ().register_plugin (
68+ typeof (RecollPlugin),
69+ _ ("Recoll"), // plugin title
70+ _ ("Returns results of full text search against an existing Recoll index."), // description
71+ "recoll", // icon name
72+ register_plugin, // reference to this function
73+ Environment.find_program_in_path ("recoll") != null, // true if user's system has all required components which the plugin needs
74+ _ ("recoll is not installed") // error message
75+ );
76+ }
77+
78+ // an optional method to improve the speed of searches,
79+ // if you return false here, the search method won't be called
80+ // for this query
81+ public bool handles_query (Query query)
82+ {
83+ return (QueryFlags.FILES in query.query_type);
84+ }
85+
86+ enum LineType {
87+ FIELDS,
88+ ABSTRACT_START,
89+ ABSTRACT,
90+ ABSTRACT_END;
91+ }
92+
93+ private async void check_if_user_is_typing()
94+ {
95+ timer = new Timer();
96+
97+ if(!user_typing) {
98+ Timeout.add(50, () => {
99+ ulong microseconds;
100+ user_typing = true;
101+ timer.elapsed (out microseconds);
102+ //print ("Milliseconds elapsed %lu\n", microseconds / 1000);
103+ if((microseconds / 1000) > maxKeystrokeInterval) {
104+ user_typing = false;
105+ check_if_user_is_typing.callback ();
106+ return false;
107+ }
108+ return true;
109+ });
110+ }
111+
112+ yield;
113+
114+ typing_finished ();
115+ }
116+
117+ private async void recollSearch (Query query, ResultSet results) throws SearchError
118+ {
119+ Pid pid;
120+ int read_fd, write_fd;
121+ string[] argv = {"recoll",
122+ "-t", // command line mode
123+ "-n", // indices of results
124+ "0-20", // return first 20 results
125+ "-a", // ALL TERMS mode
126+ "-A", // output abstracts
127+ query.query_string};
128+
129+ try
130+ {
131+ Process.spawn_async_with_pipes (null, argv, null,
132+ SpawnFlags.SEARCH_PATH,
133+ null, out pid, out write_fd, out read_fd);
134+ UnixInputStream read_stream = new UnixInputStream (read_fd, true);
135+ DataInputStream recoll_output = new DataInputStream (read_stream);
136+
137+ // Sample output from `recoll -t` :
138+ // ===============================
139+ // Recoll query: ((kernel:(wqf=11) OR kernels OR kernelize OR kernelized))
140+ // 4725 results (printing 1 max):
141+ // text/plain [file:///home/patrick/code/sample-results.txt] [sample-results.txt] 8806 bytes
142+ // ABSTRACT
143+ // some text summarizing the document usually has the keyword (kernel)
144+ // /ABSTRACT
145+
146+ string line = null;
147+ var next_line_type = LineType.FIELDS;
148+
149+ Utils.FileInfo result = null;
150+ int line_idx = 0;
151+ string description = null;
152+ while ((line = yield recoll_output.read_line_async (Priority.DEFAULT)) != null)
153+ {
154+ if (line_idx >= 2) // skip first two lines
155+ {
156+ if (next_line_type == LineType.FIELDS)
157+ {
158+ string[] fields = line.split("\t");
159+
160+ //string mimetype = fields[0];
161+
162+ string uri = fields[1];
163+ uri = uri.substring(1, uri.length - 2);
164+
165+ description = uri.split("://")[1];
166+
167+ // FIXME: recoll already gives us the mimetype so FileInfo
168+ // is doing extra work obtaining it from the file
169+ result = new Utils.FileInfo (uri, typeof (UriMatch));
170+
171+ yield result.initialize ();
172+
173+ next_line_type = LineType.ABSTRACT_START;
174+ }
175+ else if (next_line_type == LineType.ABSTRACT_START)
176+ {
177+ // TODO check for the start of the abstract
178+ next_line_type = LineType.ABSTRACT;
179+ }
180+ else if (next_line_type == LineType.ABSTRACT)
181+ {
182+ line = line.chug().chomp();
183+ if (line != null && line != "")
184+ {
185+ description = description + ": " + line;
186+ }
187+ next_line_type = LineType.ABSTRACT_END;
188+ }
189+ else if (next_line_type == LineType.ABSTRACT_END)
190+ {
191+ // TODO check for the end of the abstract
192+
193+ // TODO use relevancy rating to set match score
194+
195+ // score defaults to just under that of results from the locate plugin
196+ result.match_obj.description = description;
197+ results.add(result.match_obj, MatchScore.INCREMENT_MINOR * 2);
198+ next_line_type = LineType.FIELDS;
199+ }
200+ else
201+ {
202+ // TODO handle unexpected output ?
203+ }
204+ }
205+ line_idx++;
206+ }
207+ }
208+ catch (Error err)
209+ {
210+ if (!query.is_cancelled ()) warning ("%s", err.message);
211+ }
212+ }
213+
214+ public async ResultSet? search (Query query) throws SearchError
215+ {
216+ check_if_user_is_typing.begin((obj,res) => {
217+ check_if_user_is_typing.end(res);
218+ });
219+
220+ if (user_typing)
221+ {
222+ // wait
223+ ulong signal_id = this.typing_finished.connect (() => {
224+ search.callback ();
225+ });
226+ yield;
227+ SignalHandler.disconnect (this, signal_id);
228+ }
229+
230+ // make sure this method is called before returning any results
231+ query.check_cancellable ();
232+ if(query.query_string.char_count () < 3)
233+ return null;
234+
235+ ResultSet results = new ResultSet ();
236+ yield recollSearch(query, results);
237+
238+ query.check_cancellable ();
239+ return results;
240+ }
241+ }
242+}
243diff --git a/src/ui/synapse-main.vala b/src/ui/synapse-main.vala
244index 2d9c1d3..059e5c5 100644
245--- a/src/ui/synapse-main.vala
246+++ b/src/ui/synapse-main.vala
247@@ -184,6 +184,7 @@ namespace Synapse
248 #if HAVE_LIBREST
249 typeof (ImgUrPlugin),
250 #endif
251+ typeof (RecollPlugin),
252 // action-only plugins
253 typeof (DevhelpPlugin),
254 typeof (OpenSearchPlugin),

Subscribers

People subscribed via source and target branches