Merge lp:~mhr3/libunity/invalidation-propagation into lp:libunity

Proposed by Michal Hruby
Status: Merged
Approved by: James Henstridge
Approved revision: 271
Merged at revision: 266
Proposed branch: lp:~mhr3/libunity/invalidation-propagation
Merge into: lp:libunity
Diff against target: 1928 lines (+790/-841)
8 files modified
protocol/protocol-icon.vala (+7/-0)
src/Makefile.am (+1/-0)
src/unity-aggregator-scope-private.vala (+48/-560)
src/unity-deprecated-scope-impl.vala (+0/-3)
src/unity-scope-dbus-impl.vala (+0/-22)
src/unity-scope-tracker.vala (+616/-0)
test/vala/test-scope-base.vala (+0/-200)
test/vala/test-scope.vala (+118/-56)
To merge this branch: bzr merge lp:~mhr3/libunity/invalidation-propagation
Reviewer Review Type Date Requested Status
PS Jenkins bot (community) continuous-integration Approve
James Henstridge Approve
Review via email: mp+176446@code.launchpad.net

Commit message

Correctly propagate the results_invalidated signal from scopes to master scopes and eventually to home scope.

Description of the change

Correctly propagate the results_invalidated signal from scopes to master scopes and eventually to home scope.

To post a comment you must log in.
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
James Henstridge (jamesh) wrote :

It looks like AggregatorScopeimpl.queue_search_for_type is unused, so can probably go. (from what I can see, its one previous use now emits the results_invalidated signal instead)

In the tests, the conversion of ChildScope to a Unity.SimpleScope looks like a good cleanup, but it seems that the dbus_path property you add is unused if I'm not mistaken (it duplicates the SimpleScope.unique_name one, which does seem to be used).

Other than that, the changes look good.

review: Needs Fixing
269. By Michal Hruby

Remove unused property

270. By Michal Hruby

Emit results_invalidated when necessary

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
271. By Michal Hruby

Don't automatically cancel current search when using results_invalidated

Revision history for this message
James Henstridge (jamesh) wrote :

Looks good.

review: Approve
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'protocol/protocol-icon.vala'
2--- protocol/protocol-icon.vala 2012-09-24 13:53:47 +0000
3+++ protocol/protocol-icon.vala 2013-07-24 13:18:24 +0000
4@@ -157,6 +157,13 @@
5 tokens.add (dict.print (true));
6 return true;
7 }
8+
9+ /* Added to GIcon interface in 2.37 */
10+ private Variant serialize ()
11+ {
12+ Variant? ret = null;
13+ return ret;
14+ }
15 }
16
17
18
19=== modified file 'src/Makefile.am'
20--- src/Makefile.am 2013-07-03 23:14:43 +0000
21+++ src/Makefile.am 2013-07-24 13:18:24 +0000
22@@ -119,6 +119,7 @@
23 unity-master-scope.vala \
24 unity-simple-scope.vala \
25 unity-scope-loader.vala \
26+ unity-scope-tracker.vala \
27 unity-sound-menu.vala \
28 unity-sound-menu-mpris.vala \
29 unity-utils.vala \
30
31=== modified file 'src/unity-aggregator-scope-private.vala'
32--- src/unity-aggregator-scope-private.vala 2013-07-22 12:11:04 +0000
33+++ src/unity-aggregator-scope-private.vala 2013-07-24 13:18:24 +0000
34@@ -30,548 +30,6 @@
35 DeprecatedScopeDBusImpl, MergeStrategy
36 {
37 const int DEFAULT_TIMEOUT_INTERVAL_MS = 16;
38- const int MODEL_UPDATE_TIMEOUT_SECS = 30;
39- private static Quark dedup_model_quark = Quark.from_string ("unity-dedup-model");
40-
41- private class ScopeTracker : Object
42- {
43- private GenericArray<ScopeProxy> scope_proxy_arr;
44- // scope_id -> ScopeProxy
45- private HashTable<string, Utils.AsyncOnce<ScopeProxy>> scope_proxies;
46- // channel_key (proxy + master_channel_id) -> scope channel_id
47- private HashTable<string, Utils.AsyncOnce<string>> scope_channel_ids;
48- // channel_key (proxy + master_channel_id) -> Dee.Model
49- private HashTable<string, Dee.SerializableModel> scope_models;
50- // master_channel_id -> ResultsSynchronizer
51- private HashTable<string, ResultsSynchronizer> synchronizers;
52- // scope_id -> ScopeMetadata
53- private HashTable<string, ScopeRegistry.ScopeMetadata> scope_metadata;
54- // binary name -> present on the system
55- private HashTable<string, bool> binary_present;
56-
57- public ScopeTracker ()
58- {
59- scope_proxy_arr = new GenericArray<ScopeProxy> ();
60- scope_proxies = new HashTable<string, Utils.AsyncOnce<ScopeProxy>> (str_hash, str_equal);
61- scope_channel_ids = new HashTable<string, Utils.AsyncOnce<string>> (str_hash, str_equal);
62- scope_models = new HashTable<string, Dee.SerializableModel> (str_hash, str_equal);
63- synchronizers = new HashTable<string, ResultsSynchronizer> (str_hash, str_equal);
64- scope_metadata = new HashTable<string, ScopeRegistry.ScopeMetadata> (str_hash, str_equal);
65- binary_present = new HashTable<string, bool> (str_hash, str_equal);
66- }
67-
68- public List<weak string> scope_ids_for_proxies ()
69- {
70- return scope_proxies.get_keys ();
71- }
72-
73- public unowned ScopeProxy? get_proxy_for_scope_id (string scope_id)
74- {
75- var proxy_once = get_proxy_once (scope_id);
76- if (!proxy_once.is_initialized ())
77- {
78- return null;
79- }
80-
81- return proxy_once.get_data ();
82- }
83-
84- public unowned ResultsSynchronizer? get_synchronizer (string channel_id)
85- {
86- return synchronizers.lookup (channel_id);
87- }
88-
89- private bool content_enabled (ScopeRegistry.ScopeMetadata metadata)
90- {
91- var pref_man = PreferencesManager.get_default ();
92- if (metadata.remote_content && pref_man.remote_content_search == PreferencesManager.RemoteContent.NONE)
93- {
94- return false;
95- }
96- return true;
97- }
98-
99- private void perform_checks (ScopeRegistry.ScopeMetadata metadata,
100- ChannelType requested_channel_type)
101- throws ScopeError
102- {
103- // check remote content vs user preference
104- if (!content_enabled (metadata))
105- {
106- throw new ScopeError.DISABLED_CONTENT ("Requested content disabled");
107- }
108- // check global search flag
109- if (!metadata.global_searches && requested_channel_type == ChannelType.GLOBAL)
110- {
111- throw new ScopeError.DISABLED_CONTENT ("Global search is disabled");
112- }
113- // check if the required binary is installed
114- if (metadata.query_binary != null && metadata.query_binary != "")
115- {
116- if (!(metadata.query_binary in binary_present))
117- {
118- binary_present[metadata.query_binary] = Environment.find_program_in_path (metadata.query_binary) != null;
119- }
120- if (!binary_present[metadata.query_binary])
121- {
122- throw new ScopeError.DISABLED_CONTENT ("Required application isn't installed");
123- }
124- }
125- }
126-
127- public async ScopeProxy? create_proxy (ScopeRegistry.ScopeMetadata metadata)
128- throws Error
129- {
130- var proxy = yield ScopeProxy.new_from_metadata (metadata);
131- dynamic ScopeProxy remote_proxy = proxy;
132- remote_proxy.auto_reconnect = false;
133- // check that the proxy props match the metadata,
134- // will throw an error if it doesn't
135- check_proxy_vs_metadata (proxy, metadata);
136-
137- scope_proxy_arr.add (proxy);
138-
139- proxy.channels_invalidated.connect (channels_invalidated);
140- proxy.results_invalidated.connect (on_results_invalidated);
141- return proxy;
142- }
143-
144- private void channels_invalidated (ScopeProxy proxy)
145- {
146- // we're not removing the results that were associated with this proxy
147- // and are still living in the associated ResultsSynchronizer,
148- // cause scope quitting after a while is an excepted part of scope
149- // lifecycle, plus activating a result will try to respawn the scope
150-
151- // invalidate all associated containers
152- string[] invalid_keys = {};
153- string prefix = "%p::".printf (proxy);
154-
155- debug ("Invalidating channels for %s", prefix);
156-
157- foreach (unowned string channel_key in scope_channel_ids.get_keys ())
158- {
159- if (channel_key.has_prefix (prefix)) invalid_keys += channel_key;
160- }
161-
162- foreach (unowned string channel_key in invalid_keys)
163- {
164- scope_channel_ids.remove (channel_key);
165- scope_models.remove (channel_key);
166- }
167- }
168-
169- public signal void results_invalidated (ChannelType channel_type);
170-
171- private void on_results_invalidated (ScopeProxy proxy,
172- ChannelType channel_type)
173- {
174- this.results_invalidated (channel_type);
175- }
176-
177- private void check_proxy_vs_metadata (
178- ScopeProxy proxy,
179- ScopeRegistry.ScopeMetadata metadata) throws Error
180- {
181- if (proxy.is_master != metadata.is_master)
182- throw new ScopeError.DATA_MISMATCH ("Scope file info for '%s' doesn't match on IsMaster key".printf (metadata.id));
183-
184- if (metadata.required_metadata != null)
185- {
186- var dict = metadata.required_metadata.as_hash_table ();
187- unowned string field_name;
188- unowned string schema;
189- var iter = HashTableIter<string, string> (dict);
190- while (iter.next (out field_name, out schema))
191- {
192- if (proxy.metadata[field_name] != schema)
193- throw new ScopeError.DATA_MISMATCH ("Scope file info for '%s' doesn't match on RequiredMetadata key".printf (metadata.id));
194- }
195- }
196- }
197-
198- private string get_channel_key (string master_channel_id, ScopeProxy proxy)
199- {
200- return "%p::%s".printf (proxy, master_channel_id);
201- }
202-
203- public void register_channel (string master_channel_id,
204- Dee.SerializableModel model,
205- MergeStrategy merge_strategy)
206- {
207- // create new synchronizer for this channel
208- var synchronizer = new ResultsSynchronizer (model);
209- synchronizer.merge_strategy = merge_strategy;
210- synchronizers[master_channel_id] = synchronizer;
211- }
212-
213- public void unregister_channel (string master_channel_id)
214- {
215- var synchronizer = synchronizers[master_channel_id];
216- if (synchronizer != null)
217- {
218- // break the circular reference
219- synchronizer.receiver.set_qdata<Dee.Model?> (dedup_model_quark, null);
220- synchronizers.remove (master_channel_id);
221- }
222- // FIXME: close child scopes' channels
223- }
224-
225- private async void wait_for_seqnum (Dee.SharedModel model, uint64 seqnum)
226- throws Error
227- {
228- if (model.get_seqnum () >= seqnum) return;
229-
230- var update_sig_id = model.end_transaction.connect ((m, begin_seqnum, end_seqnum) =>
231- {
232- if (end_seqnum < seqnum) return;
233-
234- wait_for_seqnum.callback ();
235- });
236-
237- // make sure we don't wait indefinitely
238- uint src_id = 0;
239- src_id = Timeout.add_seconds (MODEL_UPDATE_TIMEOUT_SECS, () =>
240- {
241- src_id = 0;
242- wait_for_seqnum.callback ();
243- return false;
244- });
245-
246- yield;
247-
248- SignalHandler.disconnect (model, update_sig_id);
249- if (src_id != 0)
250- {
251- Source.remove (src_id);
252- }
253- else
254- {
255- // timeout was reached
256- throw new DBusError.TIMEOUT ("Timed out waiting for model update");
257- }
258- }
259-
260- private Utils.AsyncOnce<ScopeProxy> get_proxy_once (string scope_id)
261- {
262- var proxy_once = scope_proxies[scope_id];
263- if (proxy_once == null)
264- {
265- proxy_once = new Utils.AsyncOnce<ScopeProxy> ();
266- scope_proxies[scope_id] = proxy_once;
267- }
268-
269- return proxy_once;
270- }
271-
272- private Utils.AsyncOnce<string> get_channel_id_once (string channel_key)
273- {
274- var channel_id_once = scope_channel_ids[channel_key];
275- if (channel_id_once == null)
276- {
277- channel_id_once = new Utils.AsyncOnce<string> ();
278- scope_channel_ids[channel_key] = channel_id_once;
279- }
280-
281- return channel_id_once;
282- }
283-
284- private unowned string? get_channel_id (string master_channel_id,
285- string scope_id,
286- out ScopeProxy? proxy) throws Error
287- {
288- var proxy_once = get_proxy_once (scope_id);
289- if (!proxy_once.is_initialized ())
290- {
291- proxy = null;
292- return null;
293- }
294-
295- if (proxy_once.get_data () == null)
296- throw new ScopeError.REQUEST_FAILED ("Unable to create proxy");
297-
298- proxy = proxy_once.get_data ();
299-
300- var channel_key = get_channel_key (master_channel_id, proxy);
301- var channel_id_once = get_channel_id_once (channel_key);
302- if (!channel_id_once.is_initialized ()) return null;
303- return channel_id_once.get_data ();
304- }
305-
306- private async unowned string init_channel (string master_channel_id,
307- string scope_id,
308- ChannelType channel_type,
309- out ScopeProxy proxy)
310- throws Error
311- {
312- // init ScopeProxy
313- var proxy_once = get_proxy_once (scope_id);
314- Error? failure = null;
315-
316- // short-circuit evaluation
317- if (scope_id in scope_metadata)
318- {
319- // will throw if checks fail
320- perform_checks (scope_metadata[scope_id], channel_type);
321- }
322-
323- if (!proxy_once.is_initialized ())
324- {
325- if (yield proxy_once.enter ())
326- {
327- ScopeProxy? actual_proxy = null;
328- try
329- {
330- if (!(scope_id in scope_metadata))
331- {
332- scope_metadata[scope_id] = ScopeRegistry.ScopeMetadata.for_id (scope_id);
333- }
334- var metadata = scope_metadata[scope_id];
335- // don't even create the proxy if one of the checks fail
336- perform_checks (metadata, channel_type);
337- actual_proxy = yield create_proxy (metadata);
338- }
339- catch (Error e)
340- {
341- failure = e;
342- }
343- finally
344- {
345- proxy_once.leave (actual_proxy);
346- }
347- }
348- }
349-
350- proxy = proxy_once.get_data ();
351- if (proxy == null)
352- {
353- if (failure != null && failure is ScopeError.DISABLED_CONTENT)
354- {
355- // retry next time
356- proxy_once.reset ();
357- throw failure;
358- }
359- var msg = "Unable to create scope proxy for \"%s\": %s".printf (
360- scope_id, failure != null ? failure.message : "(unknown)");
361- throw new ScopeError.REQUEST_FAILED (msg);
362- }
363-
364- // open a channel
365- var channel_key = get_channel_key (master_channel_id, proxy);
366- var channel_id_once = get_channel_id_once (channel_key);
367-
368- if (!channel_id_once.is_initialized ())
369- {
370- if (yield channel_id_once.enter ())
371- {
372- Dee.SerializableModel model;
373- string? chan_id = null;
374- try
375- {
376- chan_id = yield proxy.open_channel (channel_type,
377- ChannelFlags.PRIVATE,
378- null,
379- out model);
380- scope_models[channel_key] = model;
381- // register as receiver
382- var synchronizer = synchronizers[master_channel_id];
383- if (synchronizer != null)
384- {
385- synchronizer.add_provider (model, scope_id);
386- }
387- else
388- {
389- warning ("Unable to find ResultsSynchronizer for channel %s",
390- master_channel_id);
391- }
392- }
393- finally
394- {
395- channel_id_once.leave (chan_id);
396- }
397- // emit a signal
398- scope_channel_opened (master_channel_id, proxy, model);
399- }
400- }
401-
402- unowned string scope_channel_id = channel_id_once.get_data ();
403- if (scope_channel_id == null)
404- {
405- // uh oh, couldn't open a channel, try again next time
406- channel_id_once.reset ();
407- }
408- return scope_channel_id;
409- }
410-
411- public async ActivationReplyRaw activate_wrapper (
412- string master_channel_id,
413- string scope_id,
414- ChannelType channel_type,
415- owned Variant[] result_arr,
416- uint action_type,
417- HashTable<string, Variant> hints,
418- GLib.Cancellable? cancellable) throws Error
419- {
420- ScopeProxy proxy;
421- unowned string scope_channel_id;
422- scope_channel_id = get_channel_id (master_channel_id, scope_id, out proxy);
423- if (scope_channel_id == null)
424- scope_channel_id = yield init_channel (master_channel_id, scope_id,
425- channel_type, out proxy);
426-
427- cancellable.set_error_if_cancelled ();
428-
429- var action = (Unity.Protocol.ActionType) action_type;
430- return yield proxy.activate (scope_channel_id, result_arr,
431- action, hints, cancellable);
432- }
433-
434- public async HashTable<string, Variant> search_wrapper (
435- string master_channel_id,
436- ChannelType channel_type,
437- string search_string,
438- HashTable<string, Variant> hints,
439- string scope_id,
440- CategoryMerger category_merger,
441- GLib.Cancellable? cancellable) throws Error
442- {
443- ScopeProxy proxy;
444- unowned string scope_channel_id;
445- scope_channel_id = get_channel_id (master_channel_id, scope_id, out proxy);
446- if (scope_channel_id == null)
447- scope_channel_id = yield init_channel (master_channel_id, scope_id,
448- channel_type, out proxy);
449-
450- cancellable.set_error_if_cancelled ();
451-
452- var reply_hints = new HashTable<string, Variant> (str_hash, str_equal);
453-
454- if (!content_enabled (scope_metadata[scope_id])
455- || scope_channel_id == null)
456- {
457- return reply_hints;
458- }
459-
460- if (category_merger is CategoryMergerByField)
461- {
462- (category_merger as CategoryMergerByField).map_subscope_categories (scope_id, proxy.categories_model);
463- }
464-
465- var channel_key = get_channel_key (master_channel_id, proxy);
466- var last_seq_num = scope_models[channel_key].get_seqnum ();
467-
468- var sync = synchronizers[master_channel_id];
469- sync.enable_provider (scope_id);
470-
471- var reply_dict = yield proxy.search (scope_channel_id, search_string,
472- hints, cancellable);
473-
474- var iter = HashTableIter<string, Variant> (reply_dict);
475- unowned string key;
476- unowned Variant variant;
477-
478- while (iter.next (out key, out variant))
479- {
480- if (key == SEARCH_SEQNUM_HINT)
481- {
482- uint64 seqnum = variant.get_uint64 ();
483- var model = scope_models[channel_key];
484- if (model.get_seqnum () < seqnum)
485- yield wait_for_seqnum (model as Dee.SharedModel, seqnum);
486-
487- // if the proxy was disconnected and its channels invalidated, this
488- // model is no longer merged, check if that's the case
489- if (scope_models[channel_key] != model)
490- return reply_hints;
491-
492- if (seqnum == last_seq_num)
493- {
494- debug ("Model seqnum for channel key %s not changed, copying", channel_key);
495- var synchronizer = get_synchronizer (master_channel_id);
496- if (synchronizer != null)
497- synchronizer.copy_model (model);
498- else
499- warning ("No synchronizer for master channel %s", master_channel_id);
500- }
501- }
502- else if (key == SEARCH_TIME_HINT)
503- {
504- reply_hints["%s:%s".printf (SEARCH_TIME_HINT, scope_id)] = variant;
505- }
506- else
507- {
508- reply_hints[key] = variant; // pass up
509- }
510- }
511-
512- cancellable.set_error_if_cancelled ();
513- // don't disable the provider if this search got cancelled, new search
514- // might expect it to be enabled
515- sync.disable_provider (scope_id);
516-
517- return reply_hints;
518- }
519-
520- public async void push_wrapper (
521- string parent_channel_id,
522- string search_string,
523- ChannelType channel_type,
524- string master_scope_id,
525- string scope_id,
526- Dee.SerializableModel results_model,
527- owned string[] categories,
528- GLib.Cancellable? cancellable) throws Error
529- {
530- ScopeProxy proxy;
531- unowned string scope_channel_id;
532- scope_channel_id = get_channel_id (parent_channel_id, master_scope_id, out proxy);
533- if (scope_channel_id == null)
534- scope_channel_id = yield init_channel (parent_channel_id,
535- master_scope_id,
536- channel_type, out proxy);
537-
538- if (scope_channel_id == null)
539- {
540- return; // shouldn't be reached really
541- }
542-
543- cancellable.set_error_if_cancelled ();
544-
545- var channel_key = get_channel_key (parent_channel_id, proxy);
546- var sync = synchronizers[parent_channel_id];
547- sync.enable_provider (master_scope_id);
548-
549- var reply_dict = yield proxy.push_results (scope_channel_id,
550- search_string,
551- scope_id,
552- results_model,
553- categories, cancellable);
554-
555- var iter = HashTableIter<string, Variant> (reply_dict);
556- unowned string key;
557- unowned Variant variant;
558-
559- while (iter.next (out key, out variant))
560- {
561- if (key == SEARCH_SEQNUM_HINT)
562- {
563- uint64 seqnum = variant.get_uint64 ();
564- var model = scope_models[channel_key];
565- if (model.get_seqnum () < seqnum)
566- yield wait_for_seqnum (model as Dee.SharedModel, seqnum);
567- }
568- }
569-
570- cancellable.set_error_if_cancelled ();
571- // don't disable the provider if this search got cancelled, new search
572- // might expect it to be enabled
573- sync.disable_provider (master_scope_id);
574- }
575-
576- public signal void scope_channel_opened (string master_channel_id,
577- ScopeProxy scope_proxy,
578- Dee.SerializableModel model);
579- }
580
581 private abstract class CategoryMerger : Object
582 {
583@@ -806,7 +264,8 @@
584 _rand = new Rand ();
585 _channels = new HashTable<string, ScopeChannel> (str_hash, str_equal);
586 _scopes = new ScopeTracker ();
587- _scopes.results_invalidated.connect (on_proxy_results_invalidated);
588+ _scopes.results_invalidated.connect (on_results_invalidated);
589+ _scopes.proxy_category_model_changed.connect (on_proxy_categories_changed);
590 merge_strategy = this;
591 create_models ();
592 }
593@@ -978,22 +437,50 @@
594 }
595 }
596
597- private void on_proxy_results_invalidated (ChannelType channel_type)
598- {
599- queue_search_for_type ((SearchType) channel_type);
600- }
601-
602- public void queue_search_for_type (SearchType search_type)
603- {
604- var channel_type = search_type == SearchType.GLOBAL ?
605- ChannelType.GLOBAL : ChannelType.DEFAULT;
606+ private void on_results_invalidated (ChannelUpdateFlags flags)
607+ {
608+ // results of a subscope got invalidated, propagate the change if necesary
609+ if (ChannelUpdateFlags.DEFAULT in flags)
610+ {
611+ var channel_type = ChannelType.DEFAULT;
612+ if (invalidate_last_search_for_channel (channel_type))
613+ this.results_invalidated (channel_type);
614+ }
615+ if (ChannelUpdateFlags.GLOBAL in flags)
616+ {
617+ var channel_type = ChannelType.GLOBAL;
618+ if (invalidate_last_search_for_channel (channel_type))
619+ this.results_invalidated (channel_type);
620+ }
621+ }
622+
623+ private void on_proxy_categories_changed (string scope_id, ScopeProxy proxy)
624+ {
625+ if (category_merger is CategoryMergerByField)
626+ {
627+ (category_merger as CategoryMergerByField).map_subscope_categories (scope_id, proxy.categories_model);
628+ }
629+ }
630+
631+ private bool invalidate_last_search_for_channel (ChannelType channel_type)
632+ {
633+ bool any_invalidated = false;
634 foreach (var channel in _channels.get_values ())
635 {
636 if (channel.channel_type == channel_type)
637 {
638 channel.last_search = null;
639+ any_invalidated = true;
640 }
641 }
642+ return any_invalidated;
643+ }
644+
645+ public void queue_search_for_type (SearchType search_type)
646+ {
647+ var flags = search_type == SearchType.GLOBAL ?
648+ ChannelUpdateFlags.GLOBAL : ChannelUpdateFlags.DEFAULT;
649+ on_results_invalidated (flags);
650 }
651
652 public void invalidate_search (SearchType search_type)
653@@ -1219,7 +706,7 @@
654 {
655 /* the lookups need to be fast, so keeping a FilterModel on top
656 * of the real model that's sorted according to the de-dup fields */
657- var dedup_model = target.get_qdata<Dee.Model> (dedup_model_quark);
658+ var dedup_model = target.get_qdata<Dee.Model> (ScopeTracker.DEDUP_MODEL_QUARK);
659 if (dedup_model == null)
660 {
661 warn_if_fail (target.get_n_rows () == 0);
662@@ -1238,7 +725,7 @@
663 });
664 dedup_model = new Dee.FilterModel (target, filter);
665 // eek, circular reference
666- target.set_qdata (dedup_model_quark, dedup_model);
667+ target.set_qdata (ScopeTracker.DEDUP_MODEL_QUARK, dedup_model);
668 }
669 bool found;
670 bool fell_through = false;
671@@ -1359,8 +846,7 @@
672 /* proxy the request by default */
673 try
674 {
675- return yield _scopes.activate_wrapper (channel_id, scope_id,
676- channel.channel_type,
677+ return yield _scopes.activate_wrapper (channel, scope_id,
678 (owned) result_arr_cpy,
679 action_type, hints, cancellable);
680 }
681@@ -1666,11 +1152,12 @@
682
683 var channel_type = search_type == SearchType.DEFAULT ?
684 ChannelType.DEFAULT : ChannelType.GLOBAL;
685- var res = yield _scopes.search_wrapper (search.channel_id,
686+ var channel = get_channel_by_id (search.channel_id);
687+ var res = yield _scopes.search_wrapper (channel,
688 channel_type,
689 search_string,
690- combined_hints != null ? combined_hints : search.hints, scope_id,
691- category_merger,
692+ combined_hints != null ? combined_hints : search.hints,
693+ scope_id,
694 cancellable);
695 res.foreach ((key, variant) =>
696 {
697@@ -1707,7 +1194,8 @@
698 if (category_merger is CategoryMergerByScope)
699 (category_merger as CategoryMergerByScope).add_scope_mapping (owner, master_scope_id);
700
701- yield _scopes.push_wrapper (channel_id, search_string, ChannelType.GLOBAL,
702+ var channel = get_channel_by_id (channel_id);
703+ yield _scopes.push_wrapper (channel, search_string, ChannelType.GLOBAL,
704 master_scope_id, scope_id,
705 results_model, category_ids, cancellable);
706 }
707
708=== modified file 'src/unity-deprecated-scope-impl.vala'
709--- src/unity-deprecated-scope-impl.vala 2013-07-17 15:18:49 +0000
710+++ src/unity-deprecated-scope-impl.vala 2013-07-24 13:18:24 +0000
711@@ -384,10 +384,7 @@
712 {
713 if (channel.channel_type == channel_type)
714 {
715- ScopeSearchBase? search = channel.last_search;
716- if (search != null) search.search_context.cancellable.cancel ();
717 channel.last_search = null;
718- // FIXME: queue new search
719 }
720 }
721
722
723=== modified file 'src/unity-scope-dbus-impl.vala'
724--- src/unity-scope-dbus-impl.vala 2013-07-22 10:35:06 +0000
725+++ src/unity-scope-dbus-impl.vala 2013-07-24 13:18:24 +0000
726@@ -28,7 +28,6 @@
727 {
728 public abstract void export () throws Error;
729 public abstract void unexport ();
730- public abstract void queue_search_for_type (SearchType search_type);
731
732 public abstract void queue_property_notification (string prop_name,
733 Variant prop_value);
734@@ -302,10 +301,7 @@
735 {
736 if (channel.channel_type == channel_type)
737 {
738- var search = channel.last_search;
739- if (search != null) search.search_context.cancellable.cancel ();
740 channel.last_search = null;
741- // FIXME: queue new search
742 }
743 }
744 this.results_invalidated (channel_type);
745@@ -433,24 +429,6 @@
746 return reply;
747 }
748
749- public void queue_search_for_type (SearchType search_type)
750- requires (search_type < SearchType.N_TYPES)
751- {
752- ChannelType channel_type = search_type == SearchType.DEFAULT ?
753- ChannelType.DEFAULT : ChannelType.GLOBAL;
754-
755- foreach (var channel in _channels.get_values ())
756- {
757- if (channel.channel_type == channel_type)
758- {
759- ScopeSearchBase? search = channel.last_search;
760- if (search != null) search.search_context.cancellable.cancel ();
761- channel.last_search = null;
762- // FIXME: queue new search
763- }
764- }
765- }
766-
767 private async HashTable<string, Variant> search_internal (
768 string search_string, HashTable<string, Variant> hints,
769 ScopeChannel channel) throws ScopeError
770
771=== added file 'src/unity-scope-tracker.vala'
772--- src/unity-scope-tracker.vala 1970-01-01 00:00:00 +0000
773+++ src/unity-scope-tracker.vala 2013-07-24 13:18:24 +0000
774@@ -0,0 +1,616 @@
775+/*
776+ * Copyright (C) 2011-2012 Canonical, Ltd.
777+ *
778+ * This library is free software; you can redistribute it and/or modify
779+ * it under the terms of the GNU Lesser General Public License
780+ * version 3.0 as published by the Free Software Foundation.
781+ *
782+ * This library is distributed in the hope that it will be useful,
783+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
784+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
785+ * GNU Lesser General Public License version 3.0 for more details.
786+ *
787+ * You should have received a copy of the GNU Lesser General Public
788+ * License along with this library. If not, see
789+ * <http://www.gnu.org/licenses/>.
790+ *
791+ * Authored by Neil Jagdish Patel <neil.patel@canonical.com>
792+ * Michal Hruby <michal.hruby@canonical.com>
793+ *
794+ */
795+
796+using GLib;
797+using Dee;
798+using Unity;
799+using Unity.Protocol;
800+
801+namespace Unity.Internal {
802+
803+[Flags]
804+private enum ChannelUpdateFlags
805+{
806+ NONE = 0,
807+ DEFAULT,
808+ GLOBAL
809+}
810+
811+private class ScopeTracker : Object
812+{
813+ public static Quark DEDUP_MODEL_QUARK = Quark.from_string ("unity-dedup-model");
814+ const int MODEL_UPDATE_TIMEOUT_SECS = 30;
815+
816+ private GenericArray<ScopeProxy> scope_proxy_arr;
817+ // scope_id -> ScopeProxy
818+ private HashTable<string, Utils.AsyncOnce<ScopeProxy>> scope_proxies;
819+ // channel_key (proxy + master_channel_id) -> scope channel_id
820+ private HashTable<string, Utils.AsyncOnce<string>> scope_channel_ids;
821+ // channel_key (proxy + master_channel_id) -> Dee.Model
822+ private HashTable<string, Dee.SerializableModel> scope_models;
823+ // master_channel_id -> ResultsSynchronizer
824+ private HashTable<string, ResultsSynchronizer> synchronizers;
825+ // update_key (proxy + channel_type) -> ChannelUpdateFlags
826+ private HashTable<string, ChannelUpdateFlags> master_update_flags;
827+ // scope_id -> ScopeMetadata
828+ private HashTable<string, ScopeRegistry.ScopeMetadata> scope_metadata;
829+ // binary name -> present on the system
830+ private HashTable<string, bool> binary_present;
831+
832+ public ScopeTracker ()
833+ {
834+ scope_proxy_arr = new GenericArray<ScopeProxy> ();
835+ scope_proxies = new HashTable<string, Utils.AsyncOnce<ScopeProxy>> (str_hash, str_equal);
836+ scope_channel_ids = new HashTable<string, Utils.AsyncOnce<string>> (str_hash, str_equal);
837+ scope_models = new HashTable<string, Dee.SerializableModel> (str_hash, str_equal);
838+ synchronizers = new HashTable<string, ResultsSynchronizer> (str_hash, str_equal);
839+ master_update_flags = new HashTable<string, ChannelUpdateFlags> (str_hash, str_equal);
840+ scope_metadata = new HashTable<string, ScopeRegistry.ScopeMetadata> (str_hash, str_equal);
841+ binary_present = new HashTable<string, bool> (str_hash, str_equal);
842+ }
843+
844+ public List<weak string> scope_ids_for_proxies ()
845+ {
846+ return scope_proxies.get_keys ();
847+ }
848+
849+ public unowned ScopeProxy? get_proxy_for_scope_id (string scope_id)
850+ {
851+ var proxy_once = get_proxy_once (scope_id);
852+ if (!proxy_once.is_initialized ())
853+ {
854+ return null;
855+ }
856+
857+ return proxy_once.get_data ();
858+ }
859+
860+ /* Careful, this could be expensive... O(n) */
861+ public string? get_scope_id_for_proxy (ScopeProxy proxy)
862+ {
863+ var iter = HashTableIter<string, Utils.AsyncOnce<ScopeProxy>> (scope_proxies);
864+ unowned string scope_id;
865+ unowned Utils.AsyncOnce<ScopeProxy> val;
866+ while (iter.next (out scope_id, out val))
867+ {
868+ if (val.get_data () == proxy) return scope_id;
869+ }
870+
871+ return null;
872+ }
873+
874+ public unowned ResultsSynchronizer? get_synchronizer (string channel_id)
875+ {
876+ return synchronizers.lookup (channel_id);
877+ }
878+
879+ private bool content_enabled (ScopeRegistry.ScopeMetadata metadata)
880+ {
881+ var pref_man = PreferencesManager.get_default ();
882+ if (metadata.remote_content && pref_man.remote_content_search == PreferencesManager.RemoteContent.NONE)
883+ {
884+ return false;
885+ }
886+ return true;
887+ }
888+
889+ private void perform_checks (ScopeRegistry.ScopeMetadata metadata,
890+ ChannelType requested_channel_type)
891+ throws ScopeError
892+ {
893+ // check remote content vs user preference
894+ if (!content_enabled (metadata))
895+ {
896+ throw new ScopeError.DISABLED_CONTENT ("Requested content disabled");
897+ }
898+ // check global search flag
899+ if (!metadata.global_searches && requested_channel_type == ChannelType.GLOBAL)
900+ {
901+ throw new ScopeError.DISABLED_CONTENT ("Global search is disabled");
902+ }
903+ // check if the required binary is installed
904+ if (metadata.query_binary != null && metadata.query_binary != "")
905+ {
906+ if (!(metadata.query_binary in binary_present))
907+ {
908+ binary_present[metadata.query_binary] = Environment.find_program_in_path (metadata.query_binary) != null;
909+ }
910+ if (!binary_present[metadata.query_binary])
911+ {
912+ throw new ScopeError.DISABLED_CONTENT ("Required application isn't installed");
913+ }
914+ }
915+ }
916+
917+ public async ScopeProxy? create_proxy (ScopeRegistry.ScopeMetadata metadata)
918+ throws Error
919+ {
920+ var proxy = yield ScopeProxy.new_from_metadata (metadata);
921+ dynamic ScopeProxy remote_proxy = proxy;
922+ remote_proxy.auto_reconnect = false;
923+ // check that the proxy props match the metadata,
924+ // will throw an error if it doesn't
925+ check_proxy_vs_metadata (proxy, metadata);
926+
927+ scope_proxy_arr.add (proxy);
928+
929+ proxy.channels_invalidated.connect (channels_invalidated);
930+ proxy.results_invalidated.connect (on_results_invalidated);
931+
932+ return proxy;
933+ }
934+
935+ private void channels_invalidated (ScopeProxy proxy)
936+ {
937+ // we're not removing the results that were associated with this proxy
938+ // and are still living in the associated ResultsSynchronizer,
939+ // cause scope quitting after a while is an excepted part of scope
940+ // lifecycle, plus activating a result will try to respawn the scope
941+
942+ // invalidate all associated containers
943+ string[] invalid_keys = {};
944+ string prefix = "%p::".printf (proxy);
945+
946+ debug ("Invalidating channels for %s", prefix);
947+
948+ foreach (unowned string channel_key in scope_channel_ids.get_keys ())
949+ {
950+ if (channel_key.has_prefix (prefix)) invalid_keys += channel_key;
951+ }
952+
953+ foreach (unowned string channel_key in invalid_keys)
954+ {
955+ scope_channel_ids.remove (channel_key);
956+ scope_models.remove (channel_key);
957+ }
958+ }
959+
960+ public signal void results_invalidated (ChannelUpdateFlags update_flags);
961+
962+ private void on_results_invalidated (ScopeProxy proxy,
963+ ChannelType channel_type)
964+ {
965+ var flags = master_update_flags[get_update_key (proxy, channel_type)];
966+ if (flags == ChannelUpdateFlags.NONE) return;
967+ this.results_invalidated (flags);
968+ }
969+
970+ private void check_proxy_vs_metadata (
971+ ScopeProxy proxy,
972+ ScopeRegistry.ScopeMetadata metadata) throws Error
973+ {
974+ if (proxy.is_master != metadata.is_master)
975+ throw new ScopeError.DATA_MISMATCH ("Scope file info for '%s' doesn't match on IsMaster key".printf (metadata.id));
976+
977+ if (metadata.required_metadata != null)
978+ {
979+ var dict = metadata.required_metadata.as_hash_table ();
980+ unowned string field_name;
981+ unowned string schema;
982+ var iter = HashTableIter<string, string> (dict);
983+ while (iter.next (out field_name, out schema))
984+ {
985+ if (proxy.metadata[field_name] != schema)
986+ throw new ScopeError.DATA_MISMATCH ("Scope file info for '%s' doesn't match on RequiredMetadata key".printf (metadata.id));
987+ }
988+ }
989+ }
990+
991+ private string get_channel_key (string master_channel_id, ScopeProxy proxy)
992+ {
993+ return "%p::%s".printf (proxy, master_channel_id);
994+ }
995+
996+ private string get_update_key (ScopeProxy proxy, ChannelType channel_type)
997+ {
998+ return "%p::%d".printf (proxy, (int) channel_type);
999+ }
1000+
1001+ public void register_channel (string master_channel_id,
1002+ Dee.SerializableModel model,
1003+ MergeStrategy merge_strategy)
1004+ {
1005+ // create new synchronizer for this channel
1006+ var synchronizer = new ResultsSynchronizer (model);
1007+ synchronizer.merge_strategy = merge_strategy;
1008+ synchronizers[master_channel_id] = synchronizer;
1009+ }
1010+
1011+ public void unregister_channel (string master_channel_id)
1012+ {
1013+ var synchronizer = synchronizers[master_channel_id];
1014+ if (synchronizer != null)
1015+ {
1016+ // break the circular reference
1017+ synchronizer.receiver.set_qdata<Dee.Model?> (DEDUP_MODEL_QUARK, null);
1018+ synchronizers.remove (master_channel_id);
1019+ }
1020+ // FIXME: close child scopes' channels
1021+ }
1022+
1023+ private async void wait_for_seqnum (Dee.SharedModel model, uint64 seqnum)
1024+ throws Error
1025+ {
1026+ if (model.get_seqnum () >= seqnum) return;
1027+
1028+ var update_sig_id = model.end_transaction.connect ((m, begin_seqnum, end_seqnum) =>
1029+ {
1030+ if (end_seqnum < seqnum) return;
1031+
1032+ wait_for_seqnum.callback ();
1033+ });
1034+
1035+ // make sure we don't wait indefinitely
1036+ uint src_id = 0;
1037+ src_id = Timeout.add_seconds (MODEL_UPDATE_TIMEOUT_SECS, () =>
1038+ {
1039+ src_id = 0;
1040+ wait_for_seqnum.callback ();
1041+ return false;
1042+ });
1043+
1044+ yield;
1045+
1046+ SignalHandler.disconnect (model, update_sig_id);
1047+ if (src_id != 0)
1048+ {
1049+ Source.remove (src_id);
1050+ }
1051+ else
1052+ {
1053+ // timeout was reached
1054+ throw new DBusError.TIMEOUT ("Timed out waiting for model update");
1055+ }
1056+ }
1057+
1058+ private Utils.AsyncOnce<ScopeProxy> get_proxy_once (string scope_id)
1059+ {
1060+ var proxy_once = scope_proxies[scope_id];
1061+ if (proxy_once == null)
1062+ {
1063+ proxy_once = new Utils.AsyncOnce<ScopeProxy> ();
1064+ scope_proxies[scope_id] = proxy_once;
1065+ }
1066+
1067+ return proxy_once;
1068+ }
1069+
1070+ private Utils.AsyncOnce<string> get_channel_id_once (string channel_key)
1071+ {
1072+ var channel_id_once = scope_channel_ids[channel_key];
1073+ if (channel_id_once == null)
1074+ {
1075+ channel_id_once = new Utils.AsyncOnce<string> ();
1076+ scope_channel_ids[channel_key] = channel_id_once;
1077+ }
1078+
1079+ return channel_id_once;
1080+ }
1081+
1082+ private unowned string? get_channel_id (string master_channel_id,
1083+ string scope_id,
1084+ out ScopeProxy? proxy) throws Error
1085+ {
1086+ var proxy_once = get_proxy_once (scope_id);
1087+ if (!proxy_once.is_initialized ())
1088+ {
1089+ proxy = null;
1090+ return null;
1091+ }
1092+
1093+ if (proxy_once.get_data () == null)
1094+ throw new ScopeError.REQUEST_FAILED ("Unable to create proxy");
1095+
1096+ proxy = proxy_once.get_data ();
1097+
1098+ var channel_key = get_channel_key (master_channel_id, proxy);
1099+ var channel_id_once = get_channel_id_once (channel_key);
1100+ if (!channel_id_once.is_initialized ()) return null;
1101+ return channel_id_once.get_data ();
1102+ }
1103+
1104+ private async unowned string init_channel (ScopeChannel master_channel,
1105+ string scope_id,
1106+ ChannelType channel_type,
1107+ out ScopeProxy proxy)
1108+ throws Error
1109+ {
1110+ // init ScopeProxy
1111+ var proxy_once = get_proxy_once (scope_id);
1112+ Error? failure = null;
1113+
1114+ // short-circuit evaluation
1115+ if (scope_id in scope_metadata)
1116+ {
1117+ // will throw if checks fail
1118+ perform_checks (scope_metadata[scope_id], channel_type);
1119+ }
1120+
1121+ if (!proxy_once.is_initialized ())
1122+ {
1123+ if (yield proxy_once.enter ())
1124+ {
1125+ ScopeProxy? actual_proxy = null;
1126+ try
1127+ {
1128+ if (!(scope_id in scope_metadata))
1129+ {
1130+ scope_metadata[scope_id] = ScopeRegistry.ScopeMetadata.for_id (scope_id);
1131+ }
1132+ var metadata = scope_metadata[scope_id];
1133+ // don't even create the proxy if one of the checks fail
1134+ perform_checks (metadata, channel_type);
1135+ actual_proxy = yield create_proxy (metadata);
1136+
1137+ if (actual_proxy.categories_model != null)
1138+ {
1139+ proxy_category_model_changed (scope_id, actual_proxy);
1140+ }
1141+ actual_proxy.notify["categories-model"].connect ((obj, pspec) =>
1142+ {
1143+ ScopeProxy the_proxy = obj as ScopeProxy;
1144+ string? id = get_scope_id_for_proxy (the_proxy);
1145+ if (id != null)
1146+ {
1147+ proxy_category_model_changed (id, the_proxy);
1148+ }
1149+ });
1150+ }
1151+ catch (Error e)
1152+ {
1153+ failure = e;
1154+ }
1155+ finally
1156+ {
1157+ proxy_once.leave (actual_proxy);
1158+ }
1159+ }
1160+ }
1161+
1162+ proxy = proxy_once.get_data ();
1163+ if (proxy == null)
1164+ {
1165+ if (failure != null && failure is ScopeError.DISABLED_CONTENT)
1166+ {
1167+ // retry next time
1168+ proxy_once.reset ();
1169+ throw failure;
1170+ }
1171+ var msg = "Unable to create scope proxy for \"%s\": %s".printf (
1172+ scope_id, failure != null ? failure.message : "(unknown)");
1173+ throw new ScopeError.REQUEST_FAILED (msg);
1174+ }
1175+
1176+ // open a channel
1177+ var channel_key = get_channel_key (master_channel.id, proxy);
1178+ var channel_id_once = get_channel_id_once (channel_key);
1179+
1180+ if (!channel_id_once.is_initialized ())
1181+ {
1182+ if (yield channel_id_once.enter ())
1183+ {
1184+ Dee.SerializableModel model;
1185+ string? chan_id = null;
1186+ try
1187+ {
1188+ chan_id = yield proxy.open_channel (channel_type,
1189+ ChannelFlags.PRIVATE,
1190+ null,
1191+ out model);
1192+ scope_models[channel_key] = model;
1193+ // register as receiver
1194+ var synchronizer = synchronizers[master_channel.id];
1195+ if (synchronizer != null)
1196+ {
1197+ synchronizer.add_provider (model, scope_id);
1198+ }
1199+ else
1200+ {
1201+ warning ("Unable to find ResultsSynchronizer for channel %s",
1202+ master_channel.id);
1203+ }
1204+ // a mapping for on_results_invalidated
1205+ var flag = master_channel.channel_type == ChannelType.DEFAULT ?
1206+ ChannelUpdateFlags.DEFAULT : ChannelUpdateFlags.GLOBAL;
1207+ // note that the hash table key contains the channel_type of the just
1208+ // opened channel, while the flag depends on master channel type
1209+ master_update_flags[get_update_key (proxy, channel_type)] |= flag;
1210+ }
1211+ finally
1212+ {
1213+ channel_id_once.leave (chan_id);
1214+ }
1215+ }
1216+ }
1217+
1218+ unowned string scope_channel_id = channel_id_once.get_data ();
1219+ if (scope_channel_id == null)
1220+ {
1221+ // uh oh, couldn't open a channel, try again next time
1222+ channel_id_once.reset ();
1223+ }
1224+ return scope_channel_id;
1225+ }
1226+
1227+ public async ActivationReplyRaw activate_wrapper (
1228+ ScopeChannel master_channel,
1229+ string scope_id,
1230+ owned Variant[] result_arr,
1231+ uint action_type,
1232+ HashTable<string, Variant> hints,
1233+ GLib.Cancellable? cancellable) throws Error
1234+ {
1235+ ScopeProxy proxy;
1236+ unowned string scope_channel_id;
1237+ scope_channel_id = get_channel_id (master_channel.id, scope_id, out proxy);
1238+ if (scope_channel_id == null)
1239+ scope_channel_id = yield init_channel (master_channel, scope_id,
1240+ master_channel.channel_type,
1241+ out proxy);
1242+
1243+ cancellable.set_error_if_cancelled ();
1244+
1245+ var action = (Unity.Protocol.ActionType) action_type;
1246+ return yield proxy.activate (scope_channel_id, result_arr,
1247+ action, hints, cancellable);
1248+ }
1249+
1250+ public async HashTable<string, Variant> search_wrapper (
1251+ ScopeChannel master_channel,
1252+ ChannelType channel_type,
1253+ string search_string,
1254+ HashTable<string, Variant> hints,
1255+ string scope_id,
1256+ GLib.Cancellable? cancellable) throws Error
1257+ {
1258+ ScopeProxy proxy;
1259+ unowned string scope_channel_id;
1260+ scope_channel_id = get_channel_id (master_channel.id, scope_id, out proxy);
1261+ if (scope_channel_id == null)
1262+ scope_channel_id = yield init_channel (master_channel, scope_id,
1263+ channel_type, out proxy);
1264+
1265+ cancellable.set_error_if_cancelled ();
1266+
1267+ var reply_hints = new HashTable<string, Variant> (str_hash, str_equal);
1268+
1269+ if (!content_enabled (scope_metadata[scope_id])
1270+ || scope_channel_id == null)
1271+ {
1272+ return reply_hints;
1273+ }
1274+
1275+ var channel_key = get_channel_key (master_channel.id, proxy);
1276+ var last_seq_num = scope_models[channel_key].get_seqnum ();
1277+
1278+ var sync = synchronizers[master_channel.id];
1279+ sync.enable_provider (scope_id);
1280+
1281+ var reply_dict = yield proxy.search (scope_channel_id, search_string,
1282+ hints, cancellable);
1283+
1284+ var iter = HashTableIter<string, Variant> (reply_dict);
1285+ unowned string key;
1286+ unowned Variant variant;
1287+
1288+ while (iter.next (out key, out variant))
1289+ {
1290+ if (key == SEARCH_SEQNUM_HINT)
1291+ {
1292+ uint64 seqnum = variant.get_uint64 ();
1293+ var model = scope_models[channel_key];
1294+ if (model.get_seqnum () < seqnum)
1295+ yield wait_for_seqnum (model as Dee.SharedModel, seqnum);
1296+
1297+ // if the proxy was disconnected and its channels invalidated, this
1298+ // model is no longer merged, check if that's the case
1299+ if (scope_models[channel_key] != model)
1300+ return reply_hints;
1301+
1302+ if (seqnum == last_seq_num)
1303+ {
1304+ debug ("Model seqnum for channel key %s not changed, copying", channel_key);
1305+ var synchronizer = get_synchronizer (master_channel.id);
1306+ if (synchronizer != null)
1307+ synchronizer.copy_model (model);
1308+ else
1309+ warning ("No synchronizer for master channel %s", master_channel.id);
1310+ }
1311+ }
1312+ else if (key == SEARCH_TIME_HINT)
1313+ {
1314+ reply_hints["%s:%s".printf (SEARCH_TIME_HINT, scope_id)] = variant;
1315+ }
1316+ else
1317+ {
1318+ reply_hints[key] = variant; // pass up
1319+ }
1320+ }
1321+
1322+ cancellable.set_error_if_cancelled ();
1323+ // don't disable the provider if this search got cancelled, new search
1324+ // might expect it to be enabled
1325+ sync.disable_provider (scope_id);
1326+
1327+ return reply_hints;
1328+ }
1329+
1330+ public async void push_wrapper (
1331+ ScopeChannel parent_channel,
1332+ string search_string,
1333+ ChannelType channel_type,
1334+ string master_scope_id,
1335+ string scope_id,
1336+ Dee.SerializableModel results_model,
1337+ owned string[] categories,
1338+ GLib.Cancellable? cancellable) throws Error
1339+ {
1340+ ScopeProxy proxy;
1341+ unowned string scope_channel_id;
1342+ scope_channel_id = get_channel_id (parent_channel.id, master_scope_id, out proxy);
1343+ if (scope_channel_id == null)
1344+ scope_channel_id = yield init_channel (parent_channel,
1345+ master_scope_id,
1346+ channel_type, out proxy);
1347+
1348+ if (scope_channel_id == null)
1349+ {
1350+ return; // shouldn't be reached really
1351+ }
1352+
1353+ cancellable.set_error_if_cancelled ();
1354+
1355+ var channel_key = get_channel_key (parent_channel.id, proxy);
1356+ var sync = synchronizers[parent_channel.id];
1357+ sync.enable_provider (master_scope_id);
1358+
1359+ var reply_dict = yield proxy.push_results (scope_channel_id,
1360+ search_string,
1361+ scope_id,
1362+ results_model,
1363+ categories, cancellable);
1364+
1365+ var iter = HashTableIter<string, Variant> (reply_dict);
1366+ unowned string key;
1367+ unowned Variant variant;
1368+
1369+ while (iter.next (out key, out variant))
1370+ {
1371+ if (key == SEARCH_SEQNUM_HINT)
1372+ {
1373+ uint64 seqnum = variant.get_uint64 ();
1374+ var model = scope_models[channel_key];
1375+ if (model.get_seqnum () < seqnum)
1376+ yield wait_for_seqnum (model as Dee.SharedModel, seqnum);
1377+ }
1378+ }
1379+
1380+ cancellable.set_error_if_cancelled ();
1381+ // don't disable the provider if this search got cancelled, new search
1382+ // might expect it to be enabled
1383+ sync.disable_provider (master_scope_id);
1384+ }
1385+
1386+ public signal void proxy_category_model_changed (string scope_id,
1387+ ScopeProxy scope_proxy);
1388+}
1389+
1390+} /* namespace Unity.Internal */
1391
1392=== modified file 'test/vala/test-scope-base.vala'
1393--- test/vala/test-scope-base.vala 2013-07-22 14:02:29 +0000
1394+++ test/vala/test-scope-base.vala 2013-07-24 13:18:24 +0000
1395@@ -32,20 +32,6 @@
1396 Fixture.create<AbstractScopeTester> (AbstractScopeTester.test_add_variant_result));
1397 GLib.Test.add_data_func ("/Unit/Cancellable/GetGCancellable",
1398 Fixture.create<CancellableTester> (CancellableTester.test_get_gcancellable));
1399- /* disabled for now
1400- GLib.Test.add_data_func ("/Unit/Scope/Signal/NoSearchChanged",
1401- test_no_search_changed);
1402- GLib.Test.add_data_func ("/Unit/Scope/Signal/SearchChanged",
1403- test_search_changed);
1404- GLib.Test.add_data_func ("/Unit/Scope/Signal/SearchKey",
1405- test_search_key);
1406- GLib.Test.add_data_func ("/Unit/Scope/Signal/SearchKeyDetails",
1407- test_search_key_details);
1408- GLib.Test.add_data_func ("/Unit/Scope/Signals/MultipleQueueCalls",
1409- test_multiple_queue_calls);
1410- GLib.Test.add_data_func ("/Unit/Scope/Signals/Cancellable",
1411- test_cancellable);
1412- */
1413 }
1414
1415 class AbstractScopeTester: Object, Fixture
1416@@ -219,191 +205,5 @@
1417 assert (cancellable.get_gcancellable () is GLib.Cancellable);
1418 }
1419 }
1420-
1421-/* disabled for now
1422- static void test_no_search_changed ()
1423- {
1424- MainLoop ml = new MainLoop ();
1425- var scope = new Unity.Scope ("com/canonical/Scope/Test", "test_scope");
1426- assert (scope != null);
1427-
1428- bool got_signal = false;
1429- scope.search_changed.connect (() =>
1430- {
1431- got_signal = true;
1432- ml.quit ();
1433- });
1434-
1435- // queue_search_changed shouldn't emit the search_changed signal if
1436- // appropriate view isn't active
1437- scope.queue_search_changed (SearchType.DEFAULT);
1438-
1439- uint tid = Timeout.add (500, () => { ml.quit (); return false; });
1440-
1441- ml.run ();
1442- assert (got_signal == false);
1443- Source.remove (tid);
1444- }
1445-
1446- static void test_search_changed ()
1447- {
1448- MainLoop ml = new MainLoop ();
1449- var scope = new Unity.Scope ("com/canonical/Scope/Test", "test_scope");
1450- assert (scope != null);
1451- scope.set_view_type_internal (ViewType.LENS_VIEW);
1452-
1453- bool got_signal = false;
1454- scope.search_changed.connect (() =>
1455- {
1456- got_signal = true;
1457- ml.quit ();
1458- });
1459-
1460- scope.queue_search_changed (SearchType.DEFAULT);
1461-
1462- // we don't want to wait indefinitely
1463- uint tid = Timeout.add (500, () => { assert_not_reached (); });
1464-
1465- ml.run ();
1466- assert (got_signal == true);
1467- Source.remove (tid);
1468- }
1469-
1470- static void test_search_key ()
1471- {
1472- MainLoop ml = new MainLoop ();
1473- var scope = new Unity.Scope ("com/canonical/Scope/Test", "test_scope");
1474- assert (scope != null);
1475- scope.set_view_type_internal (ViewType.LENS_VIEW);
1476-
1477- bool got_changed_signal = false;
1478- scope.search_changed.connect ((lens_search, search_type, cancellable) =>
1479- {
1480- got_changed_signal = true;
1481- ml.quit ();
1482- });
1483-
1484- bool got_gen_signal = false;
1485- scope.generate_search_key.connect ((lens_search) =>
1486- {
1487- got_gen_signal = true;
1488- return "foo";
1489- });
1490-
1491- scope.queue_search_changed (SearchType.DEFAULT);
1492-
1493- // we don't want to wait indefinitely
1494- uint tid = Timeout.add (500, () => { assert_not_reached (); });
1495-
1496- ml.run ();
1497- assert (got_changed_signal == true);
1498- assert (got_gen_signal == true);
1499-
1500- Source.remove (tid);
1501- }
1502-
1503- static void test_search_key_details ()
1504- {
1505- MainLoop ml = new MainLoop ();
1506- var scope = new Unity.Scope ("com/canonical/Scope/Test", "test_scope");
1507- assert (scope != null);
1508- scope.set_view_type_internal (ViewType.LENS_VIEW);
1509-
1510- int num_changed_signals = 0;
1511- int num_gen_signals = 0;
1512- scope.search_changed.connect ((lens_search, search_type, cancellable) =>
1513- {
1514- num_changed_signals++;
1515-
1516- if (num_changed_signals == 1)
1517- scope.set_view_type_internal (ViewType.HOME_VIEW);
1518- else if (num_changed_signals >= 2) ml.quit ();
1519- });
1520-
1521- scope.generate_search_key["default"].connect ((lens_search) =>
1522- {
1523- num_gen_signals++;
1524- return "foo";
1525- });
1526-
1527- scope.queue_search_changed (SearchType.DEFAULT);
1528- scope.queue_search_changed (SearchType.GLOBAL);
1529-
1530- // we don't want to wait indefinitely
1531- uint tid = Timeout.add (500, () => { assert_not_reached (); });
1532-
1533- ml.run ();
1534- assert (num_changed_signals == 2);
1535- assert (num_gen_signals == 1);
1536-
1537- Source.remove (tid);
1538- }
1539-
1540- static void test_multiple_queue_calls ()
1541- {
1542- MainLoop ml = new MainLoop ();
1543- var scope = new Unity.Scope ("com/canonical/Scope/Test", "test_scope");
1544- assert (scope != null);
1545- scope.set_view_type_internal (ViewType.LENS_VIEW);
1546-
1547- int num_changed = 0;
1548- scope.search_changed.connect (() =>
1549- {
1550- num_changed++;
1551- });
1552-
1553- scope.queue_search_changed (SearchType.DEFAULT);
1554- scope.queue_search_changed (SearchType.GLOBAL);
1555- scope.queue_search_changed (SearchType.DEFAULT);
1556- scope.queue_search_changed (SearchType.GLOBAL);
1557- scope.queue_search_changed (SearchType.DEFAULT);
1558- scope.queue_search_changed (SearchType.GLOBAL);
1559- scope.queue_search_changed (SearchType.DEFAULT);
1560- scope.queue_search_changed (SearchType.DEFAULT);
1561- scope.queue_search_changed (SearchType.GLOBAL);
1562- scope.queue_search_changed (SearchType.GLOBAL);
1563-
1564- // we don't want to wait indefinitely
1565- Timeout.add (500, () => { ml.quit (); return false; });
1566-
1567- ml.run ();
1568- // we expect just one signal (for SearchType.DEFAULT, LENS_VIEW is active)
1569- assert (num_changed == 1);
1570- }
1571-
1572- static void test_cancellable ()
1573- {
1574- MainLoop ml = new MainLoop ();
1575- var scope = new Unity.Scope ("com/canonical/Scope/Test", "test_scope");
1576- assert (scope != null);
1577- scope.set_view_type_internal (ViewType.LENS_VIEW);
1578-
1579- Cancellable? canc = null;
1580- scope.search_changed.connect ((lens_search, search_type, cancellable) =>
1581- {
1582- if (canc == null)
1583- {
1584- canc = cancellable;
1585- scope.queue_search_changed (SearchType.DEFAULT);
1586- }
1587- else
1588- {
1589- assert (cancellable.is_cancelled () == false);
1590- ml.quit ();
1591- }
1592- });
1593-
1594- scope.queue_search_changed (SearchType.DEFAULT);
1595-
1596- // we don't want to wait indefinitely
1597- uint tid = Timeout.add (500, () => { assert_not_reached (); });
1598-
1599- ml.run ();
1600- assert (canc != null);
1601- assert (canc.is_cancelled () == true);
1602-
1603- Source.remove (tid);
1604- }
1605-*/
1606 }
1607 }
1608
1609=== modified file 'test/vala/test-scope.vala'
1610--- test/vala/test-scope.vala 2013-07-22 14:02:29 +0000
1611+++ test/vala/test-scope.vala 2013-07-24 13:18:24 +0000
1612@@ -108,6 +108,8 @@
1613 Fixture.create<MasterScopeTester> (MasterScopeTester.test_master_multiple_channels));
1614 Test.add_data_func ("/Unit/MasterScope/NoContentHint",
1615 Fixture.create<MasterScopeTester> (MasterScopeTester.test_master_no_content));
1616+ Test.add_data_func ("/Unit/MasterScope/ResultsInvalidated",
1617+ Fixture.create<MasterScopeTester> (MasterScopeTester.test_master_results_invalidated));
1618 Test.add_data_func ("/Unit/MasterScope/Activation",
1619 Fixture.create<MasterScopeTester> (MasterScopeTester.test_master_activation));
1620 Test.add_data_func ("/Unit/MasterScope/Sorting/Ascending",
1621@@ -1292,11 +1294,13 @@
1622
1623 class MasterScopeTester: Object, Fixture
1624 {
1625- class ChildScope: Unity.DeprecatedScope
1626+ class ChildScope: Unity.SimpleScope
1627 {
1628+ public string scope_id { get; construct set; }
1629+
1630 public ChildScope (string dbus_path, string id)
1631 {
1632- Object (dbus_path: dbus_path, id: id);
1633+ Object (unique_name: dbus_path, scope_id: id, group_name: DBUS_NAME);
1634 }
1635
1636 protected override void constructed ()
1637@@ -1312,14 +1316,24 @@
1638 var cats = new Unity.CategorySet ();
1639 cats.add (new Unity.Category ("1211", "A Category", new GLib.ThemedIcon ("text")));
1640 cats.add (new Unity.Category ("1991", "Unused category", new GLib.ThemedIcon ("text")));
1641- categories = cats;
1642+ this.category_set = cats;
1643+
1644+ this.set_search_async_func ((search, cb) =>
1645+ {
1646+ perform_search (search.search_context);
1647+ // simulate a bit of asynchronicity
1648+ Idle.add (() => { cb (search); return false; });
1649+ });
1650 }
1651+
1652+ public signal void perform_search (Unity.SearchContext? search);
1653 }
1654
1655 private uint owning_id;
1656 private Unity.MasterScope master_scope;
1657 private ScopeProxy proxy;
1658- private Unity.DeprecatedScope[] child_scopes = {};
1659+ private ChildScope[] child_scopes = {};
1660+ private Unity.ScopeDBusConnector[] connectors = {};
1661 private Rand random = new Rand ();
1662 private uint results_per_scope = 1;
1663 private int random_range_end = 1000000000;
1664@@ -1335,14 +1349,12 @@
1665 }
1666 private string[] active_filters = {};
1667
1668- private void child_search_handler (Unity.DeprecatedScope scope,
1669- Unity.DeprecatedScopeSearch search,
1670- Unity.SearchType search_type,
1671- Cancellable canc)
1672+ private void child_search_handler (ChildScope scope,
1673+ Unity.SearchContext? search)
1674 {
1675- child_searches += scope.id;
1676+ child_searches += scope.scope_id;
1677
1678- var filter = search.get_filter ("check-options");
1679+ var filter = search.filter_state.get_filter_by_id ("check-options");
1680 if (filter != null)
1681 {
1682 foreach (var opt in (filter as Unity.CheckOptionFilter).options)
1683@@ -1352,48 +1364,44 @@
1684 }
1685 }
1686
1687- // simulate async search
1688- Idle.add (() => { search.finished (); return false; });
1689-
1690- var model = search.results_model;
1691- var uri = "file:///" + scope.id;
1692+ var uri = "file:///" + scope.scope_id;
1693 for (uint i = 0; i < results_per_scope; i++)
1694 {
1695- Variant[] row_data;
1696 uint category_index = 0;
1697 if (randomize_categories) category_index = random.boolean () ? 1 : 0;
1698 var rand_int = random.int_range (0, random_range_end);
1699+ Unity.ScopeResult result = Unity.ScopeResult ();
1700 if (random.boolean ())
1701 {
1702- row_data = model.build_named_row (null,
1703- "uri", uri,
1704- "icon_hint", "",
1705- "category", category_index,
1706- "result_type", 0,
1707- "mimetype", "inode/folder",
1708- "title", "Title",
1709- "comment", scope.id,
1710- "dnd_uri", "file:///",
1711- "required_int", rand_int,
1712- "required_string", "qoo");
1713+ result.uri = uri;
1714+ result.icon_hint = "";
1715+ result.category = category_index;
1716+ result.result_type = Unity.ResultType.DEFAULT;
1717+ result.mimetype = "inode/folder";
1718+ result.title = "Title";
1719+ result.comment = scope.scope_id;
1720+ result.dnd_uri = "file:///";
1721+ result.metadata = new HashTable<string, Variant> (str_hash, str_equal);
1722+ result.metadata["required_int"] = rand_int;
1723+ result.metadata["required_string"] = "qoo";
1724 }
1725 else
1726 {
1727 var opt_field = random.next_int ().to_string ();
1728- row_data = model.build_named_row (null,
1729- "uri", uri,
1730- "icon_hint", "",
1731- "category", category_index,
1732- "result_type", 0,
1733- "mimetype", "inode/folder",
1734- "title", "Title",
1735- "comment", scope.id,
1736- "dnd_uri", "file:///",
1737- "required_int", rand_int,
1738- "required_string", "qoo",
1739- "optional_string", opt_field);
1740+ result.uri = uri;
1741+ result.icon_hint = "";
1742+ result.category = category_index;
1743+ result.result_type = Unity.ResultType.DEFAULT;
1744+ result.mimetype = "inode/folder";
1745+ result.title = "Title";
1746+ result.comment = scope.scope_id;
1747+ result.dnd_uri = "file:///";
1748+ result.metadata = new HashTable<string, Variant> (str_hash, str_equal);
1749+ result.metadata["required_int"] = rand_int;
1750+ result.metadata["required_string"] = "qoo";
1751+ result.metadata["optional_string"] = opt_field;
1752 }
1753- model.append_row (row_data);
1754+ search.result_set.add_result (result);
1755 }
1756 }
1757
1758@@ -1439,22 +1447,25 @@
1759 ChildScope child_scope;
1760 child_scope = new ChildScope ("/com/canonical/unity/scope/childscope_1",
1761 "test_masterscope-childscope_1.scope");
1762- child_scope.filters = filters; // set filters for one child scope
1763- child_scope.search_changed.connect (child_search_handler);
1764- child_scope.export ();
1765+ child_scope.filter_set = filters; // set filters for one child scope
1766+ child_scope.perform_search.connect (child_search_handler);
1767+ connectors += new Unity.ScopeDBusConnector (child_scope);
1768+ connectors[connectors.length-1].export ();
1769 child_scopes += child_scope;
1770
1771 child_scope = new ChildScope ("/com/canonical/unity/scope/childscope_2",
1772 "test_masterscope-childscope_2.scope");
1773- child_scope.search_changed.connect (child_search_handler);
1774- child_scope.export ();
1775+ child_scope.perform_search.connect (child_search_handler);
1776+ connectors += new Unity.ScopeDBusConnector (child_scope);
1777+ connectors[connectors.length-1].export ();
1778 child_scopes += child_scope;
1779
1780 // this one has GlobalSearches=false
1781 child_scope = new ChildScope ("/com/canonical/unity/scope/childscope_3",
1782 "test_masterscope-childscope_3.scope");
1783- child_scope.search_changed.connect (child_search_handler);
1784- child_scope.export ();
1785+ child_scope.perform_search.connect (child_search_handler);
1786+ connectors += new Unity.ScopeDBusConnector (child_scope);
1787+ connectors[connectors.length-1].export ();
1788 child_scopes += child_scope;
1789 }
1790 catch (Error err) { assert_not_reached (); }
1791@@ -1468,7 +1479,8 @@
1792 ensure_destruction ((owned) proxy);
1793 master_scope.unexport ();
1794 ensure_destruction ((owned) master_scope);
1795- foreach (var scope in child_scopes) scope.unexport ();
1796+ foreach (var connector in connectors) connector.unexport ();
1797+ connectors = {};
1798 child_scopes = {};
1799 if (owning_id != 0) Bus.unown_name (owning_id);
1800 }
1801@@ -1521,7 +1533,7 @@
1802
1803 assert (search_handler_invocations == child_scopes.length);
1804
1805- child_scopes[0].queue_search_changed (Unity.SearchType.DEFAULT);
1806+ child_scopes[0].results_invalidated (Unity.SearchType.DEFAULT);
1807
1808 // and one more time but this time one scope should be queried
1809 reply_dict = ScopeTester.perform_search (proxy, channel_id, "foo",
1810@@ -1567,6 +1579,41 @@
1811 assert (reply_dict[HINT].get_string () == MSG);
1812 }
1813
1814+ public void test_master_results_invalidated ()
1815+ {
1816+ // we need channel first
1817+ Dee.SerializableModel model;
1818+ var channel_id = ScopeTester.open_channel (proxy, ChannelType.DEFAULT,
1819+ out model);
1820+ assert (channel_id != null);
1821+
1822+ var ml = new MainLoop ();
1823+ bool master_results_invalidated = false;
1824+ ChannelType channel_type = ChannelType.GLOBAL;
1825+ proxy.results_invalidated.connect ((type) =>
1826+ {
1827+ master_results_invalidated = true;
1828+ channel_type = type;
1829+ ml.quit ();
1830+ });
1831+
1832+ var reply_dict = ScopeTester.perform_search (proxy, channel_id, "", null, model);
1833+ assert (search_handler_invocations == child_scopes.length);
1834+ assert (reply_dict != null);
1835+
1836+ child_scopes[1].results_invalidated (Unity.SearchType.DEFAULT);
1837+
1838+ assert (run_with_timeout (ml));
1839+ assert (master_results_invalidated == true);
1840+ assert (channel_type == ChannelType.DEFAULT);
1841+
1842+ // repeat the search again and see if the scope search is performed
1843+ // on the invalidated scope
1844+ reply_dict = ScopeTester.perform_search (proxy, channel_id, "", null, model);
1845+ assert (search_handler_invocations == child_scopes.length + 1);
1846+ assert (reply_dict != null);
1847+ }
1848+
1849 public void test_master_multiple_channels ()
1850 {
1851 // we need channel first
1852@@ -1619,22 +1666,25 @@
1853
1854 string[] activated_scopes = {};
1855
1856- child_scopes[0].activate_uri.connect ((scope, uri) =>
1857+ child_scopes[0].set_activate_func ((result, metadata, action_id) =>
1858 {
1859+ var uri = result.uri;
1860 assert (uri.has_prefix ("file:///") && "childscope_1" in uri);
1861 activated_scopes += "childscope_1";
1862 return new Unity.ActivationResponse (Unity.HandledType.NOT_HANDLED);
1863 });
1864
1865- child_scopes[1].activate_uri.connect ((scope, uri) =>
1866+ child_scopes[1].set_activate_func ((result, metadata, action_id) =>
1867 {
1868+ var uri = result.uri;
1869 assert (uri.has_prefix ("file:///") && "childscope_2" in uri);
1870 activated_scopes += "childscope_2";
1871 return new Unity.ActivationResponse (Unity.HandledType.NOT_HANDLED);
1872 });
1873
1874- child_scopes[2].activate_uri.connect ((scope, uri) =>
1875+ child_scopes[2].set_activate_func ((result, metadata, action_id) =>
1876 {
1877+ var uri = result.uri;
1878 assert (uri.has_prefix ("file:///") && "childscope_3" in uri);
1879 activated_scopes += "childscope_3";
1880 return new Unity.ActivationResponse (Unity.HandledType.NOT_HANDLED);
1881@@ -2102,7 +2152,7 @@
1882 public override List<Unity.AbstractScope> get_scopes (string module, string? module_type) throws Error
1883 {
1884 requested_modules.append(module);
1885- return null;
1886+ return new List<Unity.AbstractScope> ();
1887 }
1888 }
1889
1890@@ -2111,7 +2161,11 @@
1891 public void test_load_scope ()
1892 {
1893 var loader = new TestScopeLoader ();
1894- loader.load_scope ("test_masterscope/childscope_1.scope");
1895+ try
1896+ {
1897+ loader.load_scope ("test_masterscope/childscope_1.scope");
1898+ }
1899+ catch (Error err) { assert_not_reached (); }
1900 assert (loader.requested_modules != null);
1901 assert (loader.requested_modules.data == "childscope_1.so");
1902 }
1903@@ -2120,7 +2174,11 @@
1904 {
1905 var loader = new TestScopeLoader ();
1906 var file_name = Config.TESTDIR + "/data/test-group.group";
1907- loader.load_group (file_name);
1908+ try
1909+ {
1910+ loader.load_group (file_name);
1911+ }
1912+ catch (Error err) { assert_not_reached (); }
1913
1914 assert (loader.requested_modules.data == "childscope_1.so");
1915 assert (loader.requested_modules.next.data == "childscope_2.so");
1916@@ -2130,7 +2188,11 @@
1917 public void test_load_module ()
1918 {
1919 var loader = new TestScopeLoader ();
1920- loader.load_module ("foo.so", "type");
1921+ try
1922+ {
1923+ loader.load_module ("foo.so", "type");
1924+ }
1925+ catch (Error err) { assert_not_reached (); }
1926 assert (loader.requested_modules != null);
1927 assert (loader.requested_modules.data == "foo.so");
1928 }

Subscribers

People subscribed via source and target branches