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

Subscribers

People subscribed via source and target branches