Merge lp:~stolowski/unity-scope-home/direct-search into lp:unity-scope-home
- direct-search
- Merge into trunk
Status: | Merged | ||||||||
---|---|---|---|---|---|---|---|---|---|
Approved by: | Michal Hruby | ||||||||
Approved revision: | 93 | ||||||||
Merged at revision: | 84 | ||||||||
Proposed branch: | lp:~stolowski/unity-scope-home/direct-search | ||||||||
Merge into: | lp:unity-scope-home | ||||||||
Diff against target: |
670 lines (+254/-105) 6 files modified
src/filter-state.vala (+26/-10) src/keyword-search.vala (+1/-1) src/remote-scope-registry.vala (+15/-0) src/scope.vala (+137/-69) src/search-util.vala (+37/-25) tests/unit/test-home-scope.vala (+38/-0) |
||||||||
To merge this branch: | bzr merge lp:~stolowski/unity-scope-home/direct-search | ||||||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Michal Hruby (community) | Approve | ||
PS Jenkins bot (community) | continuous-integration | Approve | |
Review via email:
|
Commit message
Implemented direct search (keyword search) for server scopes, this also makes client-side keyword search actually work.
Various fixes regarding filters (interrelations
Limit updates of user's default view in dconf.
Description of the change
Implemented direct search (keyword search) for server scopes, this also makes client-side keyword search actually work.
Various fixes regarding filters (interrelations
Limit updates of user's default view in dconf.
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
PS Jenkins bot (ps-jenkins) wrote : | # |
- 93. By Paweł Stołowski
-
Added a comment.
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:93
http://
Executed test runs:
FAILURE: http://
Click here to trigger a rebuild:
http://
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:93
http://
Executed test runs:
SUCCESS: http://
Click here to trigger a rebuild:
http://
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Michal Hruby (mhr3) wrote : | # |
Seems to work well, +1
Preview Diff
1 | === modified file 'src/filter-state.vala' |
2 | --- src/filter-state.vala 2013-03-21 21:40:00 +0000 |
3 | +++ src/filter-state.vala 2013-04-11 17:02:21 +0000 |
4 | @@ -29,13 +29,10 @@ |
5 | * highlights its subscopes, or deselecting last subscope deselects its master. |
6 | * This methods stores filter state in order to be able to detect what was selected/deselected. |
7 | */ |
8 | - public bool update_filter_relations (Unity.AggregatedScopeSearch scope_search) |
9 | + public bool update_filter_relations (string channel_id, Unity.OptionsFilter categories, Unity.OptionsFilter sources) |
10 | { |
11 | bool changed = false; |
12 | |
13 | - unowned Unity.OptionsFilter categories = scope_search.get_filter ("categories") as Unity.OptionsFilter; |
14 | - unowned Unity.OptionsFilter sources = scope_search.get_filter ("sources") as Unity.OptionsFilter; |
15 | - |
16 | if (categories == null || sources == null) |
17 | { |
18 | warning ("Categories or Sources filters missing"); |
19 | @@ -50,12 +47,24 @@ |
20 | var current_state = new Gee.TreeSet<string> (); |
21 | get_selected_scopes (categories, current_state); |
22 | get_selected_scopes (sources, current_state); |
23 | + |
24 | debug ("Scopes in current_state: %d", current_state.size); |
25 | + if (verbose_debug) |
26 | + { |
27 | + foreach (var scope_id in current_state) |
28 | + debug ("Current active: %s", scope_id); |
29 | + } |
30 | |
31 | - var previous_state = filter_state.lookup (scope_search.channel_id); |
32 | + var previous_state = filter_state.lookup (channel_id); |
33 | |
34 | if (previous_state != null) |
35 | { |
36 | + if (verbose_debug) |
37 | + { |
38 | + foreach (var scope_id in previous_state) |
39 | + debug ("Previous active: %s", scope_id); |
40 | + } |
41 | + |
42 | var selected = new Gee.TreeSet<string> (); |
43 | var unselected = new Gee.TreeSet<string> (); |
44 | |
45 | @@ -88,10 +97,20 @@ |
46 | } |
47 | else // first search - no previous state |
48 | { |
49 | + debug ("No previous filter state"); |
50 | changed = update_filters_for_selected (categories, sources, current_state); |
51 | } |
52 | |
53 | - filter_state[scope_search.channel_id] = current_state; |
54 | + var new_state = new Gee.TreeSet<string> (); |
55 | + get_selected_scopes (categories, new_state); |
56 | + get_selected_scopes (sources, new_state); |
57 | + if (verbose_debug) |
58 | + { |
59 | + foreach (var scope_id in new_state) |
60 | + debug ("New active: %s", scope_id); |
61 | + } |
62 | + |
63 | + filter_state[channel_id] = new_state; |
64 | |
65 | if (changed) |
66 | { |
67 | @@ -250,11 +269,8 @@ |
68 | return changed; |
69 | } |
70 | |
71 | - public static bool set_filters (Unity.AggregatedScopeSearch scope_search, string[] enabled_scopes) |
72 | + public static bool set_filters (Unity.OptionsFilter categories, Unity.OptionsFilter sources, string[] enabled_scopes) |
73 | { |
74 | - unowned Unity.OptionsFilter categories = scope_search.get_filter ("categories") as Unity.OptionsFilter; |
75 | - unowned Unity.OptionsFilter sources = scope_search.get_filter ("sources") as Unity.OptionsFilter; |
76 | - |
77 | if (categories == null || sources == null) |
78 | { |
79 | warning ("Can't set filters, Categories or Sources filters missing"); |
80 | |
81 | === modified file 'src/keyword-search.vala' |
82 | --- src/keyword-search.vala 2013-03-21 14:14:36 +0000 |
83 | +++ src/keyword-search.vala 2013-04-11 17:02:21 +0000 |
84 | @@ -46,7 +46,7 @@ |
85 | } |
86 | } |
87 | |
88 | - private void index_keywords (string scope_id, GLib.SList<string> keywords) |
89 | + public void index_keywords (string scope_id, GLib.SList<string> keywords) |
90 | { |
91 | foreach (var kw in keywords) |
92 | { |
93 | |
94 | === modified file 'src/remote-scope-registry.vala' |
95 | --- src/remote-scope-registry.vala 2013-03-22 09:09:21 +0000 |
96 | +++ src/remote-scope-registry.vala 2013-04-11 17:02:21 +0000 |
97 | @@ -29,12 +29,22 @@ |
98 | |
99 | private Dee.Model scopes_model; |
100 | private SmartScopes.RemoteScopeInfo[] remote_scopes; |
101 | + private Gee.TreeSet<string> remote_scopes_lut = new Gee.TreeSet<string> (); |
102 | |
103 | public RemoteScopeRegistry.for_scopes (SmartScopes.RemoteScopeInfo[] info) |
104 | { |
105 | Object (); |
106 | |
107 | remote_scopes = info; |
108 | + foreach (var scope in remote_scopes) |
109 | + { |
110 | + remote_scopes_lut.add (scope.scope_id); |
111 | + |
112 | + // also add a fake master scope id to the lookup |
113 | + var master_id = SearchUtil.get_master_id_from_scope_id (scope.scope_id); |
114 | + if (master_id != null) |
115 | + remote_scopes_lut.add (master_id); |
116 | + } |
117 | } |
118 | |
119 | public Dee.Model create_model () |
120 | @@ -63,5 +73,10 @@ |
121 | scopes_model = model; |
122 | return scopes_model; |
123 | } |
124 | + |
125 | + public bool has_scope (string scope_id) |
126 | + { |
127 | + return remote_scopes_lut.contains (scope_id); |
128 | + } |
129 | } |
130 | } |
131 | |
132 | === modified file 'src/scope.vala' |
133 | --- src/scope.vala 2013-03-28 12:26:57 +0000 |
134 | +++ src/scope.vala 2013-04-11 17:02:21 +0000 |
135 | @@ -169,10 +169,23 @@ |
136 | if (scopes != null) |
137 | { |
138 | debug ("Got %u remote scopes", scopes.length); |
139 | - smart_scopes_ready = true; |
140 | + foreach (var remote_scope in scopes) |
141 | + { |
142 | + if (remote_scope.keywords.length > 0) |
143 | + { |
144 | + var kw = new GLib.SList<string> (); |
145 | + foreach (var k in remote_scope.keywords) |
146 | + { |
147 | + kw.append (k); |
148 | + } |
149 | + keywords_search.index_keywords (remote_scope.scope_id, kw); |
150 | + } |
151 | + } |
152 | |
153 | remote_scope_registry = new RemoteScopeRegistry.for_scopes (scopes); |
154 | remote_scope_registry.create_model (); |
155 | + |
156 | + smart_scopes_ready = true; |
157 | } |
158 | } |
159 | catch (Error e) |
160 | @@ -442,8 +455,8 @@ |
161 | bool wait_for_push = false; |
162 | bool wait_for_search = false; |
163 | bool sss_query_done = false; |
164 | + bool sss_query_started = false; |
165 | uint num_scopes = 0; |
166 | - bool push_done = false; |
167 | |
168 | // ids of scopes recommended by Smart Scope Service |
169 | var recommended_search_scopes = new List<SmartScopes.RecommendedScope?> (); |
170 | @@ -523,12 +536,23 @@ |
171 | |
172 | bool empty_query = (scope_search.search_string.strip ().length == 0); |
173 | |
174 | + // set if this search is a filter update only (e.g. search string is the same) |
175 | + bool filter_change_only = false; |
176 | + if (search_query_changed == SearchQueryChange.NOT_CHANGED) |
177 | + { |
178 | + filter_change_only = true; |
179 | + debug ("Filter change only"); |
180 | + } |
181 | + |
182 | // apply user filters only if search string is unchanged, the query is empty or smart scopes are disabled completly. |
183 | // this mean user set filters *after* entering a query and we apply them; |
184 | // otherwise smart scopes recommendations will take precedence. |
185 | // if search query is empty, then apply default user's filters. |
186 | - if (search_query_changed == SearchQueryChange.NOT_CHANGED || empty_query || sss_client == null) |
187 | + if (filter_change_only || empty_query || sss_client == null) |
188 | { |
189 | + unowned Unity.OptionsFilter categories_filter = scope_search.get_filter ("categories") as Unity.OptionsFilter; |
190 | + unowned Unity.OptionsFilter sources_filter = scope_search.get_filter ("sources") as Unity.OptionsFilter; |
191 | + |
192 | // if query is empty but it's not just a filter change (i.e. state is REMOVES_FROM_PREVIOUS_QUERY or NEW_QUERY), |
193 | // then apply default user filters. |
194 | if (empty_query && search_query_changed != SearchQueryChange.NOT_CHANGED) |
195 | @@ -537,21 +561,27 @@ |
196 | if (default_filters.length > 0) |
197 | { |
198 | debug ("Empty query, applying default filter view"); |
199 | - needs_filter_update |= FilterState.set_filters (scope_search, default_filters); |
200 | + needs_filter_update |= FilterState.set_filters (categories_filter, sources_filter, default_filters); |
201 | } |
202 | default_view = true; |
203 | } |
204 | |
205 | debug ("Updating filter interrelationships"); |
206 | - needs_filter_update |= filter_state.update_filter_relations (scope_search); |
207 | - debug ("Filter interrelationships changed: %s", needs_filter_update.to_string ()); |
208 | + bool relations_changed = filter_state.update_filter_relations (scope_search.channel_id, categories_filter, sources_filter); |
209 | + |
210 | + // caution: push_filter_settings may get cancelled if we ever yield before; |
211 | + // in such case internal filter state must be updated later (at the end of search). |
212 | + if (relations_changed) |
213 | + push_filters_update (scope_search); |
214 | + debug ("Filter interrelationships changed: %s", relations_changed.to_string ()); |
215 | |
216 | SearchUtil.scopes_to_query_from_filters (scope_search.get_filter ("sources") as Unity.OptionsFilter, |
217 | scope_search.get_filter ("categories") as Unity.OptionsFilter, |
218 | search_scopes); |
219 | |
220 | - // if this search request is a filter change only, then update user's default view in gsettings |
221 | - if (search_query_changed == SearchQueryChange.NOT_CHANGED && empty_query) |
222 | + // if this search request is a filter change only and query is empty, |
223 | + // update user's default view in gsettings |
224 | + if (filter_change_only && empty_query && relations_changed) |
225 | { |
226 | default_view = true; |
227 | debug ("Updating user's default filter view"); |
228 | @@ -559,13 +589,29 @@ |
229 | } |
230 | } |
231 | |
232 | + // handle keywords (direct search) |
233 | + string? search_string = null; |
234 | + bool direct_search = false; |
235 | + unowned Gee.Set<string>? requested_scope_ids = keywords_search.process_query ( |
236 | + scope_search.search_string, out search_string); |
237 | + if (requested_scope_ids != null && search_string != null) |
238 | + { |
239 | + debug ("Direct search query, search_string = '%s'", search_string); |
240 | + direct_search = SearchUtil.scopes_to_query_from_requested_ids (requested_scope_ids, search_scopes); |
241 | + } |
242 | + |
243 | + if (search_string == null) |
244 | + search_string = scope_search.search_string; |
245 | + else |
246 | + empty_query = (search_string.strip ().length == 0); |
247 | + |
248 | uint push_data_idle_src = 0; |
249 | |
250 | // initiate Smart Scopes Search (if enabled and query not empty) |
251 | if (smart_scopes_ready) |
252 | { |
253 | if (empty_query) |
254 | - { |
255 | + { |
256 | if (channel_id_map.has_session_id_for_channel (scope_search.channel_id)) |
257 | { |
258 | debug ("Empty search, removing session mapping for channel %s", scope_search.channel_id); |
259 | @@ -575,7 +621,7 @@ |
260 | else // only sent the query to smart scopes if it's not empty |
261 | { |
262 | var session_id = get_session_id (search_query_changed, scope_search.channel_id); |
263 | - |
264 | + |
265 | AsyncReadyCallback sss_cb = (obj, res) => |
266 | { |
267 | try |
268 | @@ -592,43 +638,67 @@ |
269 | search.callback (); |
270 | }; |
271 | |
272 | - // apply filter to remote scopes query; since we don't really know remote scopes, |
273 | - // we can only pass master scopes ids. |
274 | - |
275 | + // apply filters to remote scopes query (either keyword-search filters or normal filters) |
276 | var reg = ScopeRegistry.instance (); |
277 | string[] remote_scopes_to_query = new string [0]; |
278 | - foreach (var scope_id in search_scopes.get_keys ()) |
279 | + var iter = HashTableIter<string, Gee.Set<string>> (search_scopes); |
280 | + string scope_id; |
281 | + Gee.Set<string> subscopes; |
282 | + while (iter.next (out scope_id, out subscopes)) |
283 | { |
284 | - if (reg.is_master (scope_id)) //make sure it's really a master, we don't want 'applications.scope' etc. |
285 | + // this is to support virtual more_suggestions-* scopes and |
286 | + // effectively filter out applications.scope and alike. |
287 | + if (subscopes == null && remote_scope_registry.has_scope (scope_id)) |
288 | remote_scopes_to_query += scope_id; |
289 | - } |
290 | - |
291 | - // timeout to give cancellable a chance for update; it depends on query length (the longer the string, the shorter timeout); |
292 | - // the formula is as follows: |
293 | - // min_delay + (max_delay - min_delay) / query_length |
294 | - // it gives the following values for lengths in 1..inf and min=50, max=100: |
295 | - // 100, 75, 66, 62, 60, 58, 57, 56, 55, 55, 54, 54, 53, 53, 53, 53, 52, 52, 52, ... 50 ... |
296 | - // |
297 | - // note: query is guaranteed to be non-empty at this point, so we're never dividing by 0! |
298 | - GLib.Timeout.add (SMART_SCOPES_QUERY_MIN_DELAY_MS + |
299 | - (SMART_SCOPES_QUERY_MAX_DELAY_MS - SMART_SCOPES_QUERY_MIN_DELAY_MS) / |
300 | - scope_search.search_string.length, |
301 | - ()=> |
302 | - { |
303 | - if (wait_for_search == false && wait_for_push == false && wait_for_sss_query == false) |
304 | - search.callback (); |
305 | - return false; |
306 | - }); |
307 | - yield; |
308 | - |
309 | - if (cancellable.is_cancelled ()) |
310 | - { |
311 | - debug ("The search for '%s' on channel %s was cancelled", scope_search.search_string, scope_search.channel_id); |
312 | - return; |
313 | - } |
314 | - |
315 | - sss_client.search.begin (scope_search.search_string, session_id, remote_scopes_to_query, scope_mgr.disabled_scopes, |
316 | - (scope_id, row) => |
317 | + |
318 | + if (subscopes != null) |
319 | + { |
320 | + foreach (var subscope_id in subscopes) |
321 | + { |
322 | + if (remote_scope_registry.has_scope (subscope_id)) |
323 | + remote_scopes_to_query += subscope_id; |
324 | + } |
325 | + } |
326 | + }; |
327 | + |
328 | + // if specific scopes were requested via filters or direct search, then |
329 | + // only send the query to smart scopes server if |
330 | + // it has (some) of the requested scopes (will be passed via &scopes=) |
331 | + if ((direct_search || filter_change_only) && (remote_scopes_to_query == null || remote_scopes_to_query.length == 0)) |
332 | + { |
333 | + debug ("No remote scopes to query based on keyword search or filters"); |
334 | + } |
335 | + else |
336 | + { |
337 | + // timeout to give cancellable a chance for update; it depends on query length (the longer the string, the shorter timeout); |
338 | + // the formula is as follows: |
339 | + // min_delay + (max_delay - min_delay) / query_length |
340 | + // it gives the following values for lengths in 1..inf and min=50, max=100: |
341 | + // 100, 75, 66, 62, 60, 58, 57, 56, 55, 55, 54, 54, 53, 53, 53, 53, 52, 52, 52, ... 50 ... |
342 | + // |
343 | + // note: query is guaranteed to be non-empty at this point, so we're never dividing by 0! |
344 | + GLib.Timeout.add (SMART_SCOPES_QUERY_MIN_DELAY_MS + |
345 | + (SMART_SCOPES_QUERY_MAX_DELAY_MS - SMART_SCOPES_QUERY_MIN_DELAY_MS) / |
346 | + search_string.length, |
347 | + ()=> |
348 | + { |
349 | + if (wait_for_search == false |
350 | + && wait_for_push == false |
351 | + && wait_for_sss_query == false) |
352 | + search.callback (); |
353 | + return false; |
354 | + }); |
355 | + yield; |
356 | + |
357 | + if (cancellable.is_cancelled ()) |
358 | + { |
359 | + debug ("The search for '%s' on channel %s was cancelled", scope_search.search_string, scope_search.channel_id); |
360 | + return; |
361 | + } |
362 | + |
363 | + sss_query_started = true; |
364 | + sss_client.search.begin (search_string, session_id, remote_scopes_to_query, scope_mgr.disabled_scopes, |
365 | + (scope_id, row) => |
366 | { |
367 | var pushed_model = push_data.lookup (scope_id); |
368 | if (pushed_model == null) |
369 | @@ -650,17 +720,19 @@ |
370 | (server_sid, recommendations) => |
371 | { |
372 | if (server_sid != null) |
373 | - { |
374 | + { |
375 | channel_id_map.map_server_sid (scope_search.channel_id, server_sid); |
376 | int rec_limit_count = 0; |
377 | foreach (var scope_rec in recommendations) |
378 | { |
379 | - recommended_search_scopes.append (scope_rec); |
380 | if (scope_rec.scope_type == SmartScopes.ScopeType.ClientScope) |
381 | { |
382 | - if (++rec_limit_count == SMART_SCOPES_RECOMMENDATIONS_CUTOFF) |
383 | - break; |
384 | + if (rec_limit_count == SMART_SCOPES_RECOMMENDATIONS_CUTOFF) |
385 | + continue; // past cut off, ignore this client scope |
386 | + ++rec_limit_count; |
387 | } |
388 | + recommended_search_scopes.append (scope_rec); |
389 | + debug ("Got recommended scope: %s, %s", scope_rec.scope_id, scope_rec.scope_type.to_string ()); |
390 | } |
391 | } |
392 | else |
393 | @@ -668,6 +740,7 @@ |
394 | warning ("server_sid is null"); |
395 | } |
396 | }, cancellable, sss_cb); |
397 | + } |
398 | } |
399 | } |
400 | else |
401 | @@ -683,22 +756,9 @@ |
402 | SearchUtil.scopes_to_query_from_requested_ids (scope_mgr.get_always_run_scopes (), search_scopes); |
403 | } |
404 | |
405 | - // handle keywords |
406 | - string search_string; |
407 | - bool direct_search = false; |
408 | - unowned Gee.Set<string>? requested_scope_ids = keywords_search.process_query (scope_search.search_string, out search_string); |
409 | - if (requested_scope_ids != null && search_string != null) |
410 | - { |
411 | - debug ("Potential keyword-based query, search_string = '%s'", search_string); |
412 | - direct_search = SearchUtil.scopes_to_query_from_requested_ids (requested_scope_ids, search_scopes); |
413 | - } |
414 | - |
415 | num_scopes = search_scopes.size (); |
416 | debug ("Dispatching search to %u scopes, home_channel=%s", num_scopes, scope_search.channel_id); |
417 | |
418 | - if (search_string == null) |
419 | - search_string = scope_search.search_string; |
420 | - |
421 | // iterate over master scopes, dispatch search query |
422 | foreach (var scope_id in search_scopes.get_keys ()) |
423 | { |
424 | @@ -716,7 +776,7 @@ |
425 | if (smart_scopes_ready && !empty_query) |
426 | { |
427 | // wait for smart scopes service query to finish |
428 | - if (!sss_query_done) |
429 | + if (sss_query_started && !sss_query_done) |
430 | { |
431 | debug ("Waiting for Smart Scopes query to finish"); |
432 | wait_for_sss_query = true; |
433 | @@ -740,15 +800,19 @@ |
434 | debug ("Waiting for results pushing to finish"); |
435 | wait_for_push = true; |
436 | yield; |
437 | + wait_for_push = false; |
438 | } |
439 | } |
440 | } |
441 | + catch (Error e) |
442 | + { |
443 | + debug ("The search for '%s' on channel %s was cancelled", scope_search.search_string, scope_search.channel_id); |
444 | + } |
445 | finally |
446 | { |
447 | if (push_data_idle_src > 0) |
448 | Source.remove (push_data_idle_src); |
449 | push_data_idle_src = 0; |
450 | - debug ("The search for '%s' on channel %s was cancelled", scope_search.search_string, scope_search.channel_id); |
451 | } |
452 | |
453 | debug ("Got %u recommended scopes from Smart Scope Service", recommended_search_scopes.length ()); |
454 | @@ -828,14 +892,10 @@ |
455 | |
456 | // update filter state again, but this time check result counts and send the update only if any of the highlighted masters has no results |
457 | if (!default_view) |
458 | - needs_filter_update |= SearchUtil.update_filters (search_scopes, use_recommended_scopes ? recommended_search_scopes : null, scope_search, true); |
459 | + needs_filter_update |= SearchUtil.update_filters (search_scopes, use_recommended_scopes ? recommended_search_scopes : null, scope_search, filter_change_only == false); |
460 | |
461 | if (needs_filter_update) |
462 | - { |
463 | - var filters = FilterState.create_filter_set (scope_search); |
464 | - debug ("Sending updated filters"); |
465 | - scope_search.push_filter_settings (filters); |
466 | - } |
467 | + push_filters_update (scope_search); |
468 | |
469 | if (use_recommended_scopes) |
470 | { |
471 | @@ -848,13 +908,21 @@ |
472 | } |
473 | |
474 | debug ("All search activities finished"); |
475 | + search_cancel.remove (scope_search.channel_id); |
476 | } |
477 | |
478 | public override int category_index_for_scope_id (string scope_id) |
479 | { |
480 | return CategoryManager.instance ().get_category_index (scope_id); |
481 | } |
482 | - |
483 | + |
484 | + private void push_filters_update (AggregatedScopeSearch scope_search) |
485 | + { |
486 | + var filters = FilterState.create_filter_set (scope_search); |
487 | + debug ("Sending updated filters"); |
488 | + scope_search.push_filter_settings (filters); |
489 | + } |
490 | + |
491 | private void signal_categories_order (AggregatedScopeSearch search, List<SmartScopes.RecommendedScope?> recommended_scopes) |
492 | { |
493 | var cats = CategoryManager.instance ().get_category_order (search.search_string, search.channel_id, search.results_model, recommended_scopes); |
494 | |
495 | === modified file 'src/search-util.vala' |
496 | --- src/search-util.vala 2013-03-21 21:51:40 +0000 |
497 | +++ src/search-util.vala 2013-04-11 17:02:21 +0000 |
498 | @@ -34,14 +34,14 @@ |
499 | { |
500 | foreach (var scope in recommendations) |
501 | { |
502 | - var parts = scope.scope_id.split ("-", 2); |
503 | - string master_scope_id = "%s.scope".printf (parts[0]); |
504 | + string master_scope_id = get_master_id_from_scope_id (scope.scope_id); |
505 | |
506 | rec_lookup.add (scope.scope_id); |
507 | rec_lookup.add (master_scope_id); |
508 | } |
509 | } |
510 | |
511 | + var registry = ScopeRegistry.instance (); |
512 | search_scopes.foreach ((master, subscopes) => |
513 | { |
514 | rec_lookup.add (master); |
515 | @@ -50,6 +50,17 @@ |
516 | foreach (var scope in subscopes) |
517 | rec_lookup.add (scope); |
518 | } |
519 | + else //no subscopes specified - assume all subscopes |
520 | + { |
521 | + var node = registry.get_master_scope_node (master); |
522 | + if (node != null) |
523 | + { |
524 | + foreach (var info in node.sub_scopes) |
525 | + { |
526 | + rec_lookup.add (info.id); |
527 | + } |
528 | + } |
529 | + } |
530 | }); |
531 | |
532 | var home_channel = scope_search.channel_id; |
533 | @@ -63,18 +74,15 @@ |
534 | { |
535 | int result_count = mgr.get_result_count (home_channel, opt.id); |
536 | debug ("Results for %s: %d", opt.id, result_count); |
537 | + bool value = false; |
538 | if (rec_lookup.contains (opt.id)) |
539 | - { |
540 | - bool value = check_result_counts == false || result_count > 0; |
541 | - if (opt.active != value) |
542 | - { |
543 | - opt.active = value; |
544 | - changed = true; |
545 | - } |
546 | - } |
547 | + value = check_result_counts == false || result_count > 0; |
548 | else |
549 | + value = false; |
550 | + if (opt.active != value) |
551 | { |
552 | - opt.active = false; |
553 | + opt.active = value; |
554 | + changed = true; |
555 | } |
556 | debug ("Setting category filter %s: %s", opt.id, opt.active.to_string ()); |
557 | } |
558 | @@ -88,23 +96,19 @@ |
559 | { |
560 | foreach (var opt in sources.options) |
561 | { |
562 | - var parts = opt.id.split ("-", 2); |
563 | - string master_scope_id = "%s.scope".printf (parts[0]); |
564 | + string master_scope_id = get_master_id_from_scope_id (opt.id); |
565 | |
566 | - // we don't have result counts for specific scopes, so light them up if master has any results |
567 | - int result_count = mgr.get_result_count (home_channel, master_scope_id); |
568 | + // light up subscope in sources filter if it has results |
569 | + int result_count = mgr.get_result_count (home_channel, opt.id); |
570 | + bool value = false; |
571 | if (rec_lookup.contains (opt.id)) |
572 | - { |
573 | - bool value = check_result_counts == false || result_count > 0; |
574 | - if (opt.active != value) |
575 | - { |
576 | - opt.active = value; |
577 | - changed = true; |
578 | - } |
579 | - } |
580 | + value = check_result_counts == false || result_count > 0; |
581 | else |
582 | + value = false; |
583 | + if (opt.active != value) |
584 | { |
585 | - opt.active = false; |
586 | + opt.active = value; |
587 | + changed = true; |
588 | } |
589 | debug ("Setting sources filter %s: %s", opt.id, opt.active.to_string ()); |
590 | } |
591 | @@ -197,6 +201,14 @@ |
592 | return scopes; |
593 | } |
594 | |
595 | + public string? get_master_id_from_scope_id (string scope_id) |
596 | + { |
597 | + var parts = scope_id.split ("-", 2); |
598 | + if (parts.length == 2) |
599 | + return "%s.scope".printf (parts[0]); |
600 | + return null; |
601 | + } |
602 | + |
603 | public bool scopes_to_query_from_requested_ids (Gee.Set<string> requested_scope_ids, HashTable<string, Gee.Set<string>?> search_scopes) |
604 | { |
605 | bool found = false; |
606 | @@ -215,7 +227,7 @@ |
607 | } |
608 | else // user may have requested a subscope (not a master scope), we need to find a master that needs to receive the query |
609 | { |
610 | - var master_id = registry.get_master_scope_id (req_scope_id); |
611 | + var master_id = get_master_id_from_scope_id (req_scope_id); |
612 | if (master_id != null) |
613 | { |
614 | if (!found) |
615 | |
616 | === modified file 'tests/unit/test-home-scope.vala' |
617 | --- tests/unit/test-home-scope.vala 2013-03-27 15:53:09 +0000 |
618 | +++ tests/unit/test-home-scope.vala 2013-04-11 17:02:21 +0000 |
619 | @@ -82,6 +82,9 @@ |
620 | Test.add_data_func ("/Unit/SearchUtil/BuildSearchScopesList", Fixture.create<SearchUtilTester> (SearchUtilTester.test_build_search_scopes_list)); |
621 | Test.add_data_func ("/Unit/SearchUtil/SetSubscopesFilterHint", Fixture.create<SearchUtilTester> (SearchUtilTester.test_set_subscopes_filter_hint)); |
622 | Test.add_data_func ("/Unit/SearchUtil/ScopesToQueryFromRequestedIds", Fixture.create<SearchUtilTester> (SearchUtilTester.test_scopes_to_query_from_requested_ids)); |
623 | + Test.add_data_func ("/Unit/SearchUtil/GetMasterScopeIdFromScopeId", Fixture.create<SearchUtilTester> (SearchUtilTester.test_get_master_id_from_scope_id)); |
624 | + |
625 | + Test.add_data_func ("/Unit/FilterState/SetFilters", Fixture.create<FilterStateTester> (FilterStateTester.test_set_filters)); |
626 | |
627 | Test.add_data_func ("/Unit/SmartScopes/Parse", Fixture.create<SmartScopesUtilTester> (SmartScopesUtilTester.test_smart_scopes_parse)); |
628 | Test.add_data_func ("/Unit/SmartScopes/ParseErrors", Fixture.create<SmartScopesUtilTester> (SmartScopesUtilTester.test_smart_scopes_parse_errors)); |
629 | @@ -585,6 +588,41 @@ |
630 | assert (subscopes.contains ("masterscope_b-subscope1.scope")); |
631 | assert (subscopes.contains ("masterscope_b-subscope2.scope")); |
632 | } |
633 | + |
634 | + internal void test_get_master_id_from_scope_id () |
635 | + { |
636 | + assert (SearchUtil.get_master_id_from_scope_id ("foo-bar.scope") == "foo.scope"); |
637 | + assert (SearchUtil.get_master_id_from_scope_id ("foo.scope") == null); |
638 | + } |
639 | + } |
640 | + |
641 | + class FilterStateTester: Object, Fixture |
642 | + { |
643 | + internal void test_set_filters () |
644 | + { |
645 | + var categories = new Unity.OptionsFilter (); |
646 | + categories.add_option ("a.scope", "A"); |
647 | + |
648 | + var sources = new Unity.OptionsFilter (); |
649 | + sources.add_option ("a-b.scope", "A"); |
650 | + |
651 | + var enabled_scopes = new string [0]; |
652 | + assert (FilterState.set_filters (categories, sources, enabled_scopes) == false); |
653 | + assert (categories.get_option ("a.scope").active == false); |
654 | + assert (sources.get_option ("a-b.scope").active == false); |
655 | + |
656 | + enabled_scopes += "a.scope"; |
657 | + assert (FilterState.set_filters (categories, sources, enabled_scopes) == true); |
658 | + assert (FilterState.set_filters (categories, sources, enabled_scopes) == false); |
659 | + assert (categories.get_option ("a.scope").active == true); |
660 | + assert (sources.get_option ("a-b.scope").active == false); |
661 | + |
662 | + enabled_scopes += "a-b.scope"; |
663 | + assert (FilterState.set_filters (categories, sources, enabled_scopes) == true); |
664 | + assert (FilterState.set_filters (categories, sources, enabled_scopes) == false); |
665 | + assert (categories.get_option ("a.scope").active == true); |
666 | + assert (sources.get_option ("a-b.scope").active == true); |
667 | + } |
668 | } |
669 | |
670 | class SmartScopesUtilTester: Object, Fixture |
FAILED: Continuous integration, rev:92 jenkins. qa.ubuntu. com/job/ unity-scope- home-ci/ 1/ jenkins. qa.ubuntu. com/job/ unity-scope- home-raring- amd64-ci/ 1/console
http://
Executed test runs:
FAILURE: http://
Click here to trigger a rebuild: s-jenkins: 8080/job/ unity-scope- home-ci/ 1/rebuild
http://