Merge lp:~xavi-garcia-mena/go-unityscopes/go-subsearch into lp:go-unityscopes/v2

Proposed by Xavi Garcia
Status: Needs review
Proposed branch: lp:~xavi-garcia-mena/go-unityscopes/go-subsearch
Merge into: lp:go-unityscopes/v2
Prerequisite: lp:~xavi-garcia-mena/go-unityscopes/find-list-child-scopes
Diff against target: 1200 lines (+922/-32)
16 files modified
metadata_test.go (+2/-2)
reply.cpp (+46/-0)
reply.go (+51/-4)
result.cpp (+9/-0)
result.go (+12/-0)
scope.cpp (+6/-1)
shim.h (+3/-0)
tests/aggregated/aggregated.go (+141/-0)
tests/aggregated/aggregated.ini.in (+16/-0)
tests/aggregated/aggregated_test.py (+269/-0)
tests/aggregated/simple-scope-2.ini.in (+16/-0)
tests/aggregated/simple-scope.ini.in (+16/-0)
tests/goscope/goscope.go (+2/-22)
tests/simple-scope-2/simple-scope-2.go (+157/-0)
tests/simple-scope/simple-scope.go (+157/-0)
unityscope.go (+19/-3)
To merge this branch: bzr merge lp:~xavi-garcia-mena/go-unityscopes/go-subsearch
Reviewer Review Type Date Requested Status
Kyle Fazzari (community) Approve
James Henstridge Needs Fixing
Review via email: mp+260066@code.launchpad.net

Commit message

This branch adds full functionality to implement aggregated scopes in go.
It implements the subsearch method from SearchQueryBase.

The branch also includes scope-harness to verify aggregated scopes.

Description of the change

This branch adds full functionality to implement aggregated scopes in go.
It implements the subsearch method from SearchQueryBase.

The branch also includes scope-harness to verify aggregated scopes.

To post a comment you must log in.
76. By Xavi Garcia

Fixed typo and removed commented include

77. By Xavi Garcia

Removed some comments

78. By Xavi Garcia

Hide wait mechanism for child scopes to the developer. Now Aggregated scopes wait by default transparently

79. By Xavi Garcia

Removed binary

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

I've left some inline comments. The main conceptual problem I see is tying the Subsearch API to the ScopeBase class, with other issues stemming from that (e.g. the WaitGroup on ScopeBase).

review: Needs Fixing
80. By Xavi Garcia

Moved Subsearch to SearchReply. Passed go fmt to the test files. NOTE: This branch is still work in progress, it's not fully functional

81. By Xavi Garcia

Moved subsearch method to SeachReply. Working version

82. By Xavi Garcia

Removed SetSearchReply and GetSearchReply from the subsearch listener, as they are no longer needed

83. By Xavi Garcia

Added Finished method to SearchListener. Some functions moved from goscope.go to reply.go.

Revision history for this message
Kyle Fazzari (kyrofa) wrote :

A few little things, comments inline.

review: Needs Fixing
84. By Xavi Garcia

added suggested changes

Revision history for this message
Xavi Garcia (xavi-garcia-mena) wrote :

Thanks for the review Kyle!

There is no reason to not use const & in the parameters, so I've add it :).

Regarding making the members private I didn't add it because that's a class in a cpp file that cannot be accessed from anywhere but that file.

I've added the private as it doesn't harm ;).

Revision history for this message
Kyle Fazzari (kyrofa) wrote :

Looks good to me!

Hmm, that class being inaccessible to others must be an artifact of cgo, then? Since it's not wrapped in an anonymous namespace? Regardless, encapsulation is still a best-practice, even if you're the only one using it :) . Worst case, it doesn't hurt; best case, it catches a mistake later.

review: Approve

Unmerged revisions

84. By Xavi Garcia

added suggested changes

83. By Xavi Garcia

Added Finished method to SearchListener. Some functions moved from goscope.go to reply.go.

82. By Xavi Garcia

Removed SetSearchReply and GetSearchReply from the subsearch listener, as they are no longer needed

81. By Xavi Garcia

Moved subsearch method to SeachReply. Working version

80. By Xavi Garcia

Moved Subsearch to SearchReply. Passed go fmt to the test files. NOTE: This branch is still work in progress, it's not fully functional

79. By Xavi Garcia

Removed binary

78. By Xavi Garcia

Hide wait mechanism for child scopes to the developer. Now Aggregated scopes wait by default transparently

77. By Xavi Garcia

Removed some comments

76. By Xavi Garcia

Fixed typo and removed commented include

75. By Xavi Garcia

Added Subsearch method to retrieve results from child scopes. Added scope-harness tests for aggregated scopes

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'metadata_test.go'
2--- metadata_test.go 2015-05-21 14:58:07 +0000
3+++ metadata_test.go 2015-06-17 07:29:19 +0000
4@@ -170,6 +170,6 @@
5 c.Check(pageHeader["background"], Equals, "color:///#ffffff")
6 c.Check(pageHeader["divider-color"], Equals, "#b31217")
7 c.Check(pageHeader["logo"], Equals, "unity-scope-youtube/build/src/logo.png")
8-
9- c.Check(scopeMetadata.Keywords, DeepEquals, []string{"music","video"})
10+
11+ c.Check(scopeMetadata.Keywords, DeepEquals, []string{"music", "video"})
12 }
13
14=== modified file 'reply.cpp'
15--- reply.cpp 2015-03-25 07:46:21 +0000
16+++ reply.cpp 2015-06-17 07:29:19 +0000
17@@ -2,19 +2,50 @@
18 #include <cstring>
19 #include <iostream>
20
21+#include <unity/scopes/ChildScope.h>
22 #include <unity/scopes/PreviewReply.h>
23+#include <unity/scopes/SearchListenerBase.h>
24+#include <unity/scopes/SearchReplyProxyFwd.h>
25 #include <unity/scopes/SearchReply.h>
26 #include <unity/scopes/Version.h>
27
28 extern "C" {
29 #include "_cgo_export.h"
30 }
31+#include "scope.h"
32 #include "helpers.h"
33 #include "smartptr_helper.h"
34
35 using namespace unity::scopes;
36 using namespace gounityscopes::internal;
37
38+class ResultForwarder : public SearchListenerBase
39+{
40+public:
41+ ResultForwarder(GoInterface const& listener, GoInterface const& goreply, SearchReplyProxy const& upstream)
42+ : SearchListenerBase()
43+ , listener_(listener)
44+ , reply_(goreply)
45+ , upstream_(upstream){
46+ }
47+
48+ void push(unity::scopes::CategorisedResult result) override {
49+ if (callFilterResult(listener_, reinterpret_cast<_CategorisedResult*>(static_cast<Result*>(&result)))) {
50+ upstream_->push(result);
51+ }
52+ }
53+
54+ virtual void finished(CompletionDetails const& details) override {
55+ callSearchListenerFinished(reply_);
56+ callListenerFinished(listener_);
57+ }
58+
59+private:
60+ GoInterface listener_;
61+ GoInterface reply_;
62+ SearchReplyProxy upstream_;
63+};
64+
65 void init_search_reply_ptr(SharedPtrData dest, SharedPtrData src) {
66 std::shared_ptr<SearchReply> reply = get_ptr<SearchReply>(src);
67 init_ptr<SearchReply>(dest, reply);
68@@ -76,6 +107,21 @@
69 #endif
70 }
71
72+void search_reply_subsearch(_ChildScope *child_scope, void *query, void *department_id, _SearchMetadata *metadata,
73+ SharedPtrData reply, _SearchQueryBase *search_query_base, void *pointer_to_iface, void *pointer_to_go_reply) {
74+ ChildScope *c_child_scope = reinterpret_cast<ChildScope*>(child_scope);
75+ std::string c_query = from_gostring(query);
76+ std::string c_department_id = from_gostring(department_id);
77+ SearchMetadata *c_metadata = reinterpret_cast<SearchMetadata*>(metadata);
78+ auto c_reply = get_ptr<SearchReply>(reply);
79+ SearchQueryBase *c_search_query_base = reinterpret_cast<SearchQueryBase*>(search_query_base);
80+ GoInterface *listener = reinterpret_cast<GoInterface*>(pointer_to_iface);
81+ GoInterface *go_reply = reinterpret_cast<GoInterface*>(pointer_to_go_reply);
82+
83+ auto forwarder = std::make_shared<ResultForwarder>(*listener, *go_reply, c_reply);
84+ c_search_query_base->subsearch(*c_child_scope, c_query, c_department_id, FilterState(), *c_metadata, forwarder);
85+}
86+
87 void init_preview_reply_ptr(SharedPtrData dest, SharedPtrData src) {
88 std::shared_ptr<PreviewReply> reply = get_ptr<PreviewReply>(src);
89 init_ptr<PreviewReply>(dest, reply);
90
91=== modified file 'reply.go'
92--- reply.go 2015-03-25 08:14:19 +0000
93+++ reply.go 2015-06-17 07:29:19 +0000
94@@ -6,18 +6,35 @@
95 import (
96 "encoding/json"
97 "runtime"
98+ "sync"
99 "unsafe"
100 )
101
102+type childScopeSubsearchSync interface {
103+ finishSubsearch()
104+ waitForChildScopesFinished()
105+}
106+
107 // SearchReply is used to send results of search queries to the client.
108 type SearchReply struct {
109- r C.SharedPtrData
110-}
111-
112-func makeSearchReply(replyData *C.uintptr_t) *SearchReply {
113+ r C.SharedPtrData
114+ s *C._SearchQueryBase
115+ waitGroup sync.WaitGroup
116+}
117+
118+func (reply *SearchReply) finishSubsearch() {
119+ reply.waitGroup.Done()
120+}
121+
122+func (reply *SearchReply) waitForChildScopesFinished() {
123+ reply.waitGroup.Wait()
124+}
125+
126+func makeSearchReply(replyData *C.uintptr_t, searchQueryBase *C._SearchQueryBase) *SearchReply {
127 reply := new(SearchReply)
128 runtime.SetFinalizer(reply, finalizeSearchReply)
129 C.init_search_reply_ptr(&reply.r[0], replyData)
130+ reply.s = searchQueryBase
131 return reply
132 }
133
134@@ -99,6 +116,36 @@
135 return checkError(errorString)
136 }
137
138+// Subsearch method is used by aggregating scopes.
139+// When an aggregator passes a query to its child scopes, it should
140+// use Subsearch() instead of the normal Search()
141+// that would be called by a client. Subsearch() takes care
142+// of automatically forwarding query cancellation to child scopes.
143+// This means that there is no need for an aggregating scope to
144+// explicitly forward cancellation to child scopes.
145+func (reply *SearchReply) Subsearch(childScope *ChildScope, query string, departmentId string, metadata *SearchMetadata, searchListener SearchListener) {
146+ reply.waitGroup.Add(1)
147+ var syncInterface childScopeSubsearchSync
148+ syncInterface = reply
149+ C.search_reply_subsearch(childScope.c, unsafe.Pointer(&query), unsafe.Pointer(&departmentId), (*C._SearchMetadata)(metadata.m), &reply.r[0], reply.s, unsafe.Pointer(&searchListener), unsafe.Pointer(&syncInterface))
150+}
151+
152+//export callSearchListenerFinished
153+func callSearchListenerFinished(reply childScopeSubsearchSync) {
154+ reply.finishSubsearch()
155+}
156+
157+//export callFilterResult
158+func callFilterResult(listener SearchListener, result *C._CategorisedResult) bool {
159+ goresult := makeCategorisedResult(result)
160+ return listener.FilterResult(goresult)
161+}
162+
163+//export callListenerFinished
164+func callListenerFinished(listener SearchListener) {
165+ listener.Finished()
166+}
167+
168 // PreviewReply is used to send result previews to the client.
169 type PreviewReply struct {
170 r C.SharedPtrData
171
172=== modified file 'result.cpp'
173--- result.cpp 2015-03-02 05:43:42 +0000
174+++ result.cpp 2015-06-17 07:29:19 +0000
175@@ -47,3 +47,12 @@
176 void result_set_intercept_activation(_Result *res) {
177 reinterpret_cast<Result*>(res)->set_intercept_activation();
178 }
179+
180+void result_set_category(_Result *res, SharedPtrData category) {
181+ Result *result = reinterpret_cast<Result*>(res);
182+ CategorisedResult *cat_result = dynamic_cast<CategorisedResult*>(result);
183+ if (cat_result) {
184+ auto cat = get_ptr<Category>(category);
185+ cat_result->set_category(cat);
186+ }
187+}
188
189=== modified file 'result.go'
190--- result.go 2015-03-23 11:16:52 +0000
191+++ result.go 2015-06-17 07:29:19 +0000
192@@ -142,3 +142,15 @@
193 func finalizeCategorisedResult(res *CategorisedResult) {
194 finalizeResult(&res.Result)
195 }
196+
197+func makeCategorisedResult(res *C._Result) *CategorisedResult {
198+ result := new(CategorisedResult)
199+ runtime.SetFinalizer(result, finalizeCategorisedResult)
200+ result.result = res
201+ return result
202+}
203+
204+// SetCategory Updates the category of this result.
205+func (res *CategorisedResult) SetCategory(category *Category) {
206+ C.result_set_category(res.result, &category.c[0])
207+}
208
209=== modified file 'scope.cpp'
210--- scope.cpp 2015-06-17 07:29:19 +0000
211+++ scope.cpp 2015-06-17 07:29:19 +0000
212@@ -1,11 +1,15 @@
213 #include <unity/scopes/Category.h>
214+#include <unity/scopes/utility/BufferedResultForwarder.h>
215 extern "C" {
216 #include "_cgo_export.h"
217 }
218 #include "scope.h"
219+#include "helpers.h"
220 #include "smartptr_helper.h"
221
222 using namespace unity::scopes;
223+using namespace gounityscopes::internal;
224+using namespace unity::scopes::utility;
225
226 ScopeAdapter::ScopeAdapter(GoInterface goscope) : goscope(goscope) {
227 }
228@@ -40,9 +44,9 @@
229 }
230
231 ChildScopeList ScopeAdapter::find_child_scopes() const {
232- ChildScopeList child_scopes;
233 GoSlice go_slice = callFindChildScopes(goscope);
234 ChildScope **data = reinterpret_cast<ChildScope **>(go_slice.data);
235+ ChildScopeList child_scopes;
236 for(uint i = 0;i < go_slice.len; ++i)
237 {
238 ChildScope child_scope(data[i]->id, data[i]->metadata, data[i]->enabled, data[i]->keywords);
239@@ -68,6 +72,7 @@
240 reinterpret_cast<_CannedQuery*>(new CannedQuery(query())),
241 reinterpret_cast<_SearchMetadata*>(new SearchMetadata(search_metadata())),
242 const_cast<uintptr_t*>(reinterpret_cast<const uintptr_t*>(&reply)),
243+ reinterpret_cast<_SearchQueryBase*>(this),
244 cancel_channel.get());
245 }
246
247
248=== modified file 'shim.h'
249--- shim.h 2015-06-17 07:29:19 +0000
250+++ shim.h 2015-06-17 07:29:19 +0000
251@@ -20,6 +20,7 @@
252 typedef struct _QueryMetadata _QueryMetadata;
253 typedef struct _ColumnLayout _ColumnLayout;
254 typedef struct _ChildScope _ChildScope;
255+typedef struct _SearchQueryBase _SearchQueryBase;
256 typedef void _ScopeBase;
257 typedef struct _GoString _GoString;
258
259@@ -52,6 +53,7 @@
260 void search_reply_register_departments(SharedPtrData reply, SharedPtrData dept);
261 void search_reply_push(SharedPtrData reply, _CategorisedResult *result, char **error);
262 void search_reply_push_filters(SharedPtrData reply, void *filters_json, void *filter_state_json, char **error);
263+void search_reply_subsearch(_ChildScope *child_scope, void *query, void *department_id, _SearchMetadata *metadata, SharedPtrData reply, _SearchQueryBase *query_base, void *pointer_to_iface, void *pointer_to_go_reply);
264
265 /* PreviewReply objects */
266 void init_preview_reply_ptr(SharedPtrData dest, SharedPtrData src);
267@@ -80,6 +82,7 @@
268 /* CategorisedResult objects */
269 _Result *new_categorised_result(SharedPtrData category);
270 void destroy_result(_Result *res);
271+void result_set_category(_Result *res, SharedPtrData category);
272
273 /* Result objects */
274 void *result_get_attr(_Result *res, void *attr, int *length, char **error);
275
276=== added directory 'tests/aggregated'
277=== added file 'tests/aggregated/aggregated.go'
278--- tests/aggregated/aggregated.go 1970-01-01 00:00:00 +0000
279+++ tests/aggregated/aggregated.go 2015-06-17 07:29:19 +0000
280@@ -0,0 +1,141 @@
281+package main
282+
283+import (
284+ "fmt"
285+ "launchpad.net/go-unityscopes/v2"
286+ "log"
287+)
288+
289+const searchCategoryTemplate = `{
290+ "schema-version": 1,
291+ "template": {
292+ "category-layout": "grid",
293+ "card-size": "small"
294+ },
295+ "components": {
296+ "title": "title",
297+ "art": "art",
298+ "subtitle": "username"
299+ }
300+}`
301+
302+const searchCategoryTemplate2 = `{
303+ "schema-version": 1,
304+ "template": {
305+ "category-layout": "grid",
306+ "card-size": "small"
307+ },
308+ "components": {
309+ "title": "title",
310+ "art": "art",
311+ "subtitle": "username"
312+ }
313+}`
314+
315+// SCOPE ***********************************************************************
316+
317+var scope_interface scopes.AggregatedScope
318+
319+type MyScope struct {
320+ base *scopes.ScopeBase
321+}
322+
323+type TestScopeListener struct {
324+ category *scopes.Category
325+ id string
326+
327+}
328+
329+func NewTestScopeListener(id string, category *scopes.Category) *TestScopeListener {
330+ listener := new(TestScopeListener)
331+ listener.SetCategory(category)
332+ listener.id = id
333+ return listener
334+}
335+
336+func (l *TestScopeListener) SetCategory(category *scopes.Category) {
337+ l.category = category
338+}
339+
340+func (l *TestScopeListener) Finished() {
341+ fmt.Printf("SUBSEARCH [%s] FINISHED\n", l.id)
342+}
343+
344+func (l *TestScopeListener) FilterResult(result *scopes.CategorisedResult) bool {
345+ fmt.Print("PUSHING RESULT\n")
346+ result.SetCategory(l.category)
347+ fmt.Print(result.URI())
348+ fmt.Print("\n")
349+ // we just filter one of the results to verify
350+ // this method is taken into account when pushing results
351+ return result.Title() != "TEST"
352+}
353+
354+func (s *MyScope) Preview(result *scopes.Result, metadata *scopes.ActionMetadata, reply *scopes.PreviewReply, cancelled <-chan bool) error {
355+ return nil
356+}
357+
358+func (s *MyScope) Search(query *scopes.CannedQuery, metadata *scopes.SearchMetadata, reply *scopes.SearchReply, cancelled <-chan bool) error {
359+ // THIS IS JUST A TEST ON THE STDOUT
360+ childScopes := s.base.ChildScopes()
361+ fmt.Print("CHILD SCOPES ****************************************************************\n")
362+ for k := range childScopes {
363+ fmt.Print(childScopes[k].Id())
364+ fmt.Print("\n")
365+ // create the cateogry for this child scope
366+ cat := reply.RegisterCategory("cat_"+childScopes[k].Id(), fmt.Sprintf("Category %s", childScopes[k].Id()), "", searchCategoryTemplate)
367+ // create a search listener
368+ listener := NewTestScopeListener(childScopes[k].Id(), cat)
369+ reply.Subsearch(childScopes[k], query.QueryString(), "", metadata, listener)
370+ }
371+ fmt.Print("CHILD SCOPES ****************************************************************\n")
372+ fmt.Print("EXIT SEARCH -------------------------------------\n")
373+ return nil
374+}
375+
376+func (s *MyScope) FindChildScopes() []*scopes.ChildScope {
377+ child_scopes := make([]*scopes.ChildScope, 0)
378+
379+ listScopes := s.base.ListRegistryScopes()
380+ for _, item := range listScopes {
381+ fmt.Print("Adding.... ")
382+ fmt.Print(item.ScopeId)
383+ fmt.Print("\n")
384+ if item.ScopeId != "aggregated" {
385+ fmt.Print("Done. \n")
386+ child_scope := scopes.NewChildScope(item.ScopeId, item, true, []string{"music"})
387+ child_scopes = append(child_scopes, child_scope)
388+ }
389+ }
390+ return child_scopes
391+}
392+
393+func (s *MyScope) SetScopeBase(base *scopes.ScopeBase) {
394+ s.base = base
395+
396+ // THIS IS JUST A TEST ON THE STDOUT
397+ listScopes := s.base.ListRegistryScopes()
398+ fmt.Print("LIST SCOPES ****************************************************************\n")
399+ for k := range listScopes {
400+ fmt.Print(k)
401+ fmt.Print("\n")
402+ fmt.Print(*listScopes[k])
403+ fmt.Print("\n")
404+ }
405+ fmt.Print("LIST SCOPES ****************************************************************\n")
406+}
407+
408+func (s *MyScope) GetScopeBase() *scopes.ScopeBase {
409+ return s.base
410+}
411+
412+// MAIN ************************************************************************
413+
414+func main() {
415+ var sc MyScope
416+ scope_interface = &sc
417+
418+ if err := scopes.Run(&MyScope{}); err != nil {
419+ log.Fatalln(err)
420+ }
421+}
422
423=== added file 'tests/aggregated/aggregated.ini.in'
424--- tests/aggregated/aggregated.ini.in 1970-01-01 00:00:00 +0000
425+++ tests/aggregated/aggregated.ini.in 2015-06-17 07:29:19 +0000
426@@ -0,0 +1,16 @@
427+[ScopeConfig]
428+ScopeRunner=$GOPATH$/bin/aggregated --runtime %R --scope %S
429+DisplayName = mock.DisplayName
430+Description = mock.Description
431+Author = test
432+Art = mock.Art
433+Icon = /mock.Icon
434+SearchHint = mock.SearchHint
435+HotKey = mock.HotKey
436+IsAggregator=false
437+
438+[Appearance]
439+PageHeader.Logo = http://assets.ubuntu.com/sites/ubuntu/1110/u/img/logos/logo-ubuntu-orange.svg
440+PageHeader.ForegroundColor = white
441+PageHeader.Background = color://black
442+ShapeImages = false
443\ No newline at end of file
444
445=== added file 'tests/aggregated/aggregated_test.py'
446--- tests/aggregated/aggregated_test.py 1970-01-01 00:00:00 +0000
447+++ tests/aggregated/aggregated_test.py 2015-06-17 07:29:19 +0000
448@@ -0,0 +1,269 @@
449+#!/usr/bin/env python3
450+#
451+# Copyright (C) 2015 Canonical Ltd.
452+# Author: Xavi Garcia <xavi.garcia.mena@canonical.com>
453+#
454+# This program is free software; you can redistribute it and/or modify
455+# it under the terms of the GNU General Public License as published by
456+# the Free Software Foundation; version 3.
457+#
458+# This program is distributed in the hope that it will be useful,
459+# but WITHOUT ANY WARRANTY; without even the implied warranty of
460+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
461+# GNU General Public License for more details.
462+#
463+# You should have received a copy of the GNU General Public License
464+# along with this program. If not, see <http://www.gnu.org/licenses/>.
465+#
466+
467+"""
468+This is a test of an aggregated go-bindings scope.
469+"""
470+
471+from scope_harness import ( ScopeHarness, CategoryMatcher, CategoryMatcherMode, CategoryListMatcher,
472+ CategoryListMatcherMode, ResultMatcher, PreviewMatcher, PreviewWidgetMatcher,
473+ PreviewColumnMatcher, PreviewView,
474+ Parameters, DepartmentMatcher, ChildDepartmentMatcher )
475+from scope_harness.testing import ScopeHarnessTestCase
476+import unittest
477+import sys
478+import os
479+from shutil import copyfile
480+import inspect
481+
482+TEST_DATA_DIR = os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe())))
483+SCOPE_NAME = "aggregated"
484+CONFIG_FILE = "aggregated.ini"
485+
486+CHILD_SCOPE_NAME_1 = "simple-scope"
487+CHILD_SCOPE_NAME_2 = "simple-scope-2"
488+CHILD_SCOPE_CONFIG_FILE = "simple-scope.ini"
489+CHILD_SCOPE_CONFIG_FILE_2 = "simple-scope-2.ini"
490+
491+def remove_ini_file(config_file):
492+ os.remove(TEST_DATA_DIR + "/" + config_file)
493+
494+def copy_binary(scope_name):
495+ binary = os.environ.get('GOPATH') + "/bin/" + scope_name
496+ if not os.path.exists(binary):
497+ raise Exception("The binary %s does not exist." % binary)
498+ copyfile(binary, TEST_DATA_DIR + "/" + scope_name)
499+
500+def remove_binary(scope_name):
501+ os.remove(TEST_DATA_DIR + "/" + scope_name)
502+
503+def prepare_ini_file(config_file):
504+ copy_and_set_go_path(TEST_DATA_DIR + "/" + config_file + ".in", TEST_DATA_DIR + "/" + config_file)
505+
506+def copy_and_set_go_path(infile, outfile):
507+ with open(outfile, "wt") as fout:
508+ with open(infile, "rt") as fin:
509+ for line in fin:
510+ fout.write(line.replace("$GOPATH$", os.environ.get('GOPATH')))
511+
512+class ResultsTest(ScopeHarnessTestCase):
513+ @classmethod
514+ def setUpClass(cls):
515+ copy_binary(SCOPE_NAME)
516+ copy_binary(CHILD_SCOPE_NAME_1)
517+ copy_binary(CHILD_SCOPE_NAME_2)
518+
519+ prepare_ini_file(CONFIG_FILE)
520+ prepare_ini_file(CHILD_SCOPE_CONFIG_FILE)
521+ prepare_ini_file(CHILD_SCOPE_CONFIG_FILE_2)
522+ cls.harness = ScopeHarness.new_from_scope_list(Parameters([
523+ TEST_DATA_DIR + "/" + CONFIG_FILE,
524+ TEST_DATA_DIR + "/" + CHILD_SCOPE_CONFIG_FILE,
525+ TEST_DATA_DIR + "/" + CHILD_SCOPE_CONFIG_FILE_2
526+ ]))
527+ cls.view = cls.harness.results_view
528+ cls.view.active_scope = SCOPE_NAME
529+ cls.view.search_query = ""
530+
531+ @classmethod
532+ def tearDownClass(cls):
533+ remove_ini_file(CONFIG_FILE)
534+ remove_ini_file(CHILD_SCOPE_CONFIG_FILE)
535+ remove_ini_file(CHILD_SCOPE_CONFIG_FILE_2)
536+
537+ remove_binary(SCOPE_NAME)
538+ remove_binary(CHILD_SCOPE_NAME_1)
539+ remove_binary(CHILD_SCOPE_NAME_2)
540+
541+ def test_basic_result(self):
542+ self.assertMatchResult(CategoryListMatcher()
543+ .has_at_least(1)
544+ .mode(CategoryListMatcherMode.BY_ID)
545+ .category(CategoryMatcher("cat_simple-scope")
546+ .has_at_least(1)
547+ .mode(CategoryMatcherMode.BY_URI)
548+ .title("Category simple-scope")
549+ .icon("")
550+ )
551+ .match(self.view.categories)
552+ )
553+
554+ self.assertMatchResult(CategoryListMatcher()
555+ .has_at_least(1)
556+ .mode(CategoryListMatcherMode.BY_ID)
557+ .category(CategoryMatcher("cat_simple-scope-2")
558+ .has_at_least(1)
559+ .mode(CategoryMatcherMode.BY_URI)
560+ .title("Category simple-scope-2")
561+ .icon("")
562+ )
563+ .match(self.view.categories)
564+ )
565+
566+ def test_scope_properties(self):
567+ self.assertEqual(self.view.scope_id, SCOPE_NAME)
568+ self.assertEqual(self.view.display_name, 'mock.DisplayName')
569+ self.assertEqual(self.view.icon_hint, '/mock.Icon')
570+ self.assertEqual(self.view.description, 'mock.Description')
571+ self.assertEqual(self.view.search_hint, 'mock.SearchHint')
572+ self.assertEqual(self.view.shortcut, 'mock.HotKey')
573+
574+ customizations = self.view.customizations
575+ self.assertTrue(len(customizations)!=0)
576+
577+ header_customizations = customizations["page-header"]
578+ self.assertEqual(header_customizations["logo"], "http://assets.ubuntu.com/sites/ubuntu/1110/u/img/logos/logo-ubuntu-orange.svg")
579+ self.assertEqual(header_customizations["background"], "color://black")
580+ self.assertEqual(header_customizations["foreground-color"], "white")
581+ self.assertEqual(customizations["shape-images"], False)
582+
583+ def test_result_data(self):
584+ self.assertMatchResult(CategoryListMatcher()
585+ .has_at_least(1)
586+ .mode(CategoryListMatcherMode.BY_ID)
587+ .category(CategoryMatcher("cat_simple-scope")
588+ .has_at_least(1)
589+ .mode(CategoryMatcherMode.BY_URI)
590+ .result(ResultMatcher("http://localhost2/")
591+ .properties({'test_value_bool': False})
592+ .properties({'test_value_string': "test_value2"})
593+ .properties({'test_value_int': 2000})
594+ .properties({'test_value_float': 2.1})
595+ .dnd_uri("http://localhost_dnduri2")
596+ .properties({'test_value_map': {'value1':1,'value2':'string_value'}})
597+ .properties({'test_value_array': [1999,"string_value"]})
598+ .art("https://pbs.twimg.com/profile_images/1117820653/5ttls5.jpg.png")
599+ ))
600+ .match(self.view.categories)
601+ )
602+
603+ self.assertMatchResult(CategoryListMatcher()
604+ .has_at_least(1)
605+ .mode(CategoryListMatcherMode.BY_ID)
606+ .category(CategoryMatcher("cat_simple-scope-2")
607+ .has_at_least(1)
608+ .mode(CategoryMatcherMode.BY_URI)
609+ .result(ResultMatcher("http://TEST-FROM-SCOPE-2_1/")
610+ .properties({'test_value_bool': True})
611+ .properties({'test_value_string': "test_value"})
612+ .properties({'test_value_int': 1999})
613+ .properties({'test_value_float': 1.999})
614+ .dnd_uri("http://localhost_dnduri")
615+ .art("https://help.ubuntu.com/stable/ubuntu-help/figures/ubuntu-logo.png")
616+ ))
617+ .match(self.view.categories)
618+ )
619+
620+ self.assertMatchResult(CategoryListMatcher()
621+ .has_at_least(1)
622+ .mode(CategoryListMatcherMode.BY_ID)
623+ .category(CategoryMatcher("cat_simple-scope-2")
624+ .has_at_least(1)
625+ .mode(CategoryMatcherMode.BY_URI)
626+ .result(ResultMatcher("http://TEST-FROM-SCOPE-2_2/")
627+ .properties({'test_value_bool': False})
628+ .properties({'test_value_string': "test_value2"})
629+ .properties({'test_value_int': 2000})
630+ .properties({'test_value_float': 2.1})
631+ .dnd_uri("http://localhost_dnduri2")
632+ .properties({'test_value_map': {'value1':1,'value2':'string_value'}})
633+ .properties({'test_value_array': [1999,"string_value"]})
634+ .art("https://help.ubuntu.com/stable/ubuntu-help/figures/ubuntu-logo.png")
635+ ))
636+ .match(self.view.categories)
637+ )
638+
639+ def test_result_data_query(self):
640+ self.view.active_scope = SCOPE_NAME
641+ test_query = "test_query"
642+ self.view.search_query = test_query
643+
644+ self.assertMatchResult(CategoryListMatcher()
645+ .has_at_least(1)
646+ .mode(CategoryListMatcherMode.BY_ID)
647+ .category(CategoryMatcher("cat_simple-scope")
648+ .has_at_least(1)
649+ .mode(CategoryMatcherMode.BY_URI)
650+ .result(ResultMatcher("http://localhost/" + test_query)
651+ .properties({'test_value_bool': True})
652+ .properties({'test_value_string': "test_value" + test_query})
653+ .properties({'test_value_int': 1999})
654+ .properties({'test_value_float': 1.999})
655+ .dnd_uri("http://localhost_dnduri" + test_query)
656+ .art("https://pbs.twimg.com/profile_images/1117820653/5ttls5.jpg.png")
657+ ))
658+ .match(self.view.categories)
659+ )
660+
661+ self.assertMatchResult(CategoryListMatcher()
662+ .has_at_least(1)
663+ .mode(CategoryListMatcherMode.BY_ID)
664+ .category(CategoryMatcher("cat_simple-scope")
665+ .has_at_least(1)
666+ .mode(CategoryMatcherMode.BY_URI)
667+ .result(ResultMatcher("http://localhost2/" + test_query)
668+ .properties({'test_value_bool': False})
669+ .properties({'test_value_string': "test_value2" + test_query})
670+ .properties({'test_value_int': 2000})
671+ .properties({'test_value_float': 2.1})
672+ .dnd_uri("http://localhost_dnduri2" + test_query)
673+ .properties({'test_value_map': {'value1':1,'value2':'string_value'}})
674+ .properties({'test_value_array': [1999,"string_value"]})
675+ .art("https://pbs.twimg.com/profile_images/1117820653/5ttls5.jpg.png")
676+ ))
677+ .match(self.view.categories)
678+ )
679+
680+ self.assertMatchResult(CategoryListMatcher()
681+ .has_at_least(1)
682+ .mode(CategoryListMatcherMode.BY_ID)
683+ .category(CategoryMatcher("cat_simple-scope-2")
684+ .has_at_least(1)
685+ .mode(CategoryMatcherMode.BY_URI)
686+ .result(ResultMatcher("http://TEST-FROM-SCOPE-2_1/" + test_query)
687+ .properties({'test_value_bool': True})
688+ .properties({'test_value_string': "test_value" + test_query})
689+ .properties({'test_value_int': 1999})
690+ .properties({'test_value_float': 1.999})
691+ .dnd_uri("http://localhost_dnduri" + test_query)
692+ .art("https://help.ubuntu.com/stable/ubuntu-help/figures/ubuntu-logo.png")
693+ ))
694+ .match(self.view.categories)
695+ )
696+
697+ self.assertMatchResult(CategoryListMatcher()
698+ .has_at_least(1)
699+ .mode(CategoryListMatcherMode.BY_ID)
700+ .category(CategoryMatcher("cat_simple-scope-2")
701+ .has_at_least(1)
702+ .mode(CategoryMatcherMode.BY_URI)
703+ .result(ResultMatcher("http://TEST-FROM-SCOPE-2_2/" + test_query)
704+ .properties({'test_value_bool': False})
705+ .properties({'test_value_string': "test_value2" + test_query})
706+ .properties({'test_value_int': 2000})
707+ .properties({'test_value_float': 2.1})
708+ .dnd_uri("http://localhost_dnduri2" + test_query)
709+ .properties({'test_value_map': {'value1':1,'value2':'string_value'}})
710+ .properties({'test_value_array': [1999,"string_value"]})
711+ .art("https://help.ubuntu.com/stable/ubuntu-help/figures/ubuntu-logo.png")
712+ ))
713+ .match(self.view.categories)
714+ )
715+
716+if __name__ == '__main__':
717+ unittest.main(argv = sys.argv[:1])
718
719=== added file 'tests/aggregated/simple-scope-2.ini.in'
720--- tests/aggregated/simple-scope-2.ini.in 1970-01-01 00:00:00 +0000
721+++ tests/aggregated/simple-scope-2.ini.in 2015-06-17 07:29:19 +0000
722@@ -0,0 +1,16 @@
723+[ScopeConfig]
724+ScopeRunner=$GOPATH$/bin/simple-scope-2 --runtime %R --scope %S
725+DisplayName = mock.DisplayName
726+Description = mock.Description
727+Author = test
728+Art = mock.Art
729+Icon = /mock.Icon
730+SearchHint = mock.SearchHint
731+HotKey = mock.HotKey
732+IsAggregator=false
733+
734+[Appearance]
735+PageHeader.Logo = http://assets.ubuntu.com/sites/ubuntu/1110/u/img/logos/logo-ubuntu-orange.svg
736+PageHeader.ForegroundColor = white
737+PageHeader.Background = color://black
738+ShapeImages = false
739
740=== added file 'tests/aggregated/simple-scope.ini.in'
741--- tests/aggregated/simple-scope.ini.in 1970-01-01 00:00:00 +0000
742+++ tests/aggregated/simple-scope.ini.in 2015-06-17 07:29:19 +0000
743@@ -0,0 +1,16 @@
744+[ScopeConfig]
745+ScopeRunner=$GOPATH$/bin/simple-scope --runtime %R --scope %S
746+DisplayName = mock.DisplayName
747+Description = mock.Description
748+Author = test
749+Art = mock.Art
750+Icon = /mock.Icon
751+SearchHint = mock.SearchHint
752+HotKey = mock.HotKey
753+IsAggregator=false
754+
755+[Appearance]
756+PageHeader.Logo = http://assets.ubuntu.com/sites/ubuntu/1110/u/img/logos/logo-ubuntu-orange.svg
757+PageHeader.ForegroundColor = white
758+PageHeader.Background = color://black
759+ShapeImages = false
760
761=== modified file 'tests/goscope/goscope.go'
762--- tests/goscope/goscope.go 2015-06-17 07:29:19 +0000
763+++ tests/goscope/goscope.go 2015-06-17 07:29:19 +0000
764@@ -3,7 +3,6 @@
765 import (
766 "launchpad.net/go-unityscopes/v2"
767 "log"
768- "fmt"
769 )
770
771 const searchCategoryTemplate = `{
772@@ -21,7 +20,7 @@
773
774 // SCOPE ***********************************************************************
775
776-var scope_interface scopes.AggregatedScope
777+var scope_interface scopes.Scope
778
779 type MyScope struct {
780 base *scopes.ScopeBase
781@@ -102,15 +101,7 @@
782 var filterState scopes.FilterState
783 // for RTM version of libunity-scopes we should see a log message
784 reply.PushFilters([]scopes.Filter{filter1}, filterState)
785-
786- // THIS IS JUST A TEST ON THE STDOUT
787- childScopes := s.base.ChildScopes()
788- fmt.Print("CHILD SCOPES ****************************************************************\n")
789- for k := range childScopes {
790- fmt.Print(childScopes[k].Id())
791- fmt.Print("\n")
792- }
793- fmt.Print("CHILD SCOPES ****************************************************************\n")
794+
795 return s.AddQueryResults(reply, query.QueryString())
796 }
797
798@@ -127,17 +118,6 @@
799
800 func (s *MyScope) SetScopeBase(base *scopes.ScopeBase) {
801 s.base = base
802-
803- // THIS IS JUST A TEST ON THE STDOUT
804- listScopes := s.base.ListRegistryScopes()
805- fmt.Print("LIST SCOPES ****************************************************************\n")
806- for k := range listScopes {
807- fmt.Print(k)
808- fmt.Print("\n")
809- fmt.Print(*listScopes[k])
810- fmt.Print("\n")
811- }
812- fmt.Print("LIST SCOPES ****************************************************************\n")
813 }
814
815 // RESULTS *********************************************************************
816
817=== added directory 'tests/simple-scope'
818=== added directory 'tests/simple-scope-2'
819=== added file 'tests/simple-scope-2/simple-scope-2.go'
820--- tests/simple-scope-2/simple-scope-2.go 1970-01-01 00:00:00 +0000
821+++ tests/simple-scope-2/simple-scope-2.go 2015-06-17 07:29:19 +0000
822@@ -0,0 +1,157 @@
823+package main
824+
825+import (
826+ "launchpad.net/go-unityscopes/v2"
827+ "log"
828+)
829+
830+const searchCategoryTemplate = `{
831+ "schema-version": 1,
832+ "template": {
833+ "category-layout": "grid",
834+ "card-size": "small"
835+ },
836+ "components": {
837+ "title": "title",
838+ "art": "art",
839+ "subtitle": "username"
840+ }
841+}`
842+
843+// SCOPE ***********************************************************************
844+
845+var scope_interface scopes.Scope
846+
847+type MyScope struct {
848+ base *scopes.ScopeBase
849+}
850+
851+func (s *MyScope) Preview(result *scopes.Result, metadata *scopes.ActionMetadata, reply *scopes.PreviewReply, cancelled <-chan bool) error {
852+ layout1col := scopes.NewColumnLayout(1)
853+ layout2col := scopes.NewColumnLayout(2)
854+ layout3col := scopes.NewColumnLayout(3)
855+
856+ // Single column layout
857+ layout1col.AddColumn("image", "header", "summary", "actions")
858+
859+ // Two column layout
860+ layout2col.AddColumn("image")
861+ layout2col.AddColumn("header", "summary", "actions")
862+
863+ // Three cokumn layout
864+ layout3col.AddColumn("image")
865+ layout3col.AddColumn("header", "summary", "actions")
866+ layout3col.AddColumn()
867+
868+ // Register the layouts we just created
869+ reply.RegisterLayout(layout1col, layout2col, layout3col)
870+
871+ header := scopes.NewPreviewWidget("header", "header")
872+
873+ // It has title and a subtitle properties
874+ header.AddAttributeMapping("title", "title")
875+ header.AddAttributeMapping("subtitle", "subtitle")
876+
877+ // Define the image section
878+ image := scopes.NewPreviewWidget("image", "image")
879+ // It has a single source property, mapped to the result's art property
880+ image.AddAttributeMapping("source", "art")
881+
882+ // Define the summary section
883+ description := scopes.NewPreviewWidget("summary", "text")
884+ // It has a text property, mapped to the result's description property
885+ description.AddAttributeMapping("text", "description")
886+
887+ // build variant map.
888+ tuple1 := make(map[string]interface{})
889+ tuple1["id"] = "open"
890+ tuple1["label"] = "Open"
891+ tuple1["uri"] = "application:///tmp/non-existent.desktop"
892+
893+ tuple2 := make(map[string]interface{})
894+ tuple1["id"] = "download"
895+ tuple1["label"] = "Download"
896+
897+ tuple3 := make(map[string]interface{})
898+ tuple1["id"] = "hide"
899+ tuple1["label"] = "Hide"
900+
901+ actions := scopes.NewPreviewWidget("actions", "actions")
902+ actions.AddAttributeValue("actions", []interface{}{tuple1, tuple2, tuple3})
903+
904+ var scope_data string
905+ metadata.ScopeData(scope_data)
906+ if len(scope_data) > 0 {
907+ extra := scopes.NewPreviewWidget("extra", "text")
908+ extra.AddAttributeValue("text", "test Text")
909+ reply.PushWidgets(header, image, description, actions, extra)
910+ } else {
911+ reply.PushWidgets(header, image, description, actions)
912+ }
913+
914+ return nil
915+}
916+
917+func (s *MyScope) Search(query *scopes.CannedQuery, metadata *scopes.SearchMetadata, reply *scopes.SearchReply, cancelled <-chan bool) error {
918+ return s.AddQueryResults(reply, query.QueryString())
919+}
920+
921+func (s *MyScope) SetScopeBase(base *scopes.ScopeBase) {
922+ s.base = base
923+}
924+
925+// RESULTS *********************************************************************
926+
927+func (s *MyScope) AddQueryResults(reply *scopes.SearchReply, query string) error {
928+ cat := reply.RegisterCategory("category", "Category", "", searchCategoryTemplate)
929+
930+ result := scopes.NewCategorisedResult(cat)
931+ result.SetURI("http://TEST-FROM-SCOPE-2_1/" + query)
932+ result.SetDndURI("http://localhost_dnduri" + query)
933+ result.SetTitle("TEST-FROM-SCOPE-2_1" + query)
934+ result.SetArt("https://help.ubuntu.com/stable/ubuntu-help/figures/ubuntu-logo.png")
935+ result.Set("test_value_bool", true)
936+ result.Set("test_value_string", "test_value"+query)
937+ result.Set("test_value_int", 1999)
938+ result.Set("test_value_float", 1.999)
939+ if err := reply.Push(result); err != nil {
940+ return err
941+ }
942+
943+ result.SetURI("http://TEST-FROM-SCOPE-2_2/" + query)
944+ result.SetDndURI("http://localhost_dnduri2" + query)
945+ result.SetTitle("TEST-FROM-SCOPE-2_1")
946+ result.SetArt("https://help.ubuntu.com/stable/ubuntu-help/figures/ubuntu-logo.png")
947+ result.Set("test_value_bool", false)
948+ result.Set("test_value_string", "test_value2"+query)
949+ result.Set("test_value_int", 2000)
950+ result.Set("test_value_float", 2.100)
951+
952+ // add a variant map value
953+ m := make(map[string]interface{})
954+ m["value1"] = 1
955+ m["value2"] = "string_value"
956+ result.Set("test_value_map", m)
957+
958+ // add a variant array value
959+ l := make([]interface{}, 0)
960+ l = append(l, 1999)
961+ l = append(l, "string_value")
962+ result.Set("test_value_array", l)
963+ if err := reply.Push(result); err != nil {
964+ return err
965+ }
966+
967+ return nil
968+}
969+
970+// MAIN ************************************************************************
971+
972+func main() {
973+ var sc MyScope
974+ scope_interface = &sc
975+
976+ if err := scopes.Run(&MyScope{}); err != nil {
977+ log.Fatalln(err)
978+ }
979+}
980
981=== added file 'tests/simple-scope/simple-scope.go'
982--- tests/simple-scope/simple-scope.go 1970-01-01 00:00:00 +0000
983+++ tests/simple-scope/simple-scope.go 2015-06-17 07:29:19 +0000
984@@ -0,0 +1,157 @@
985+package main
986+
987+import (
988+ "launchpad.net/go-unityscopes/v2"
989+ "log"
990+)
991+
992+const searchCategoryTemplate = `{
993+ "schema-version": 1,
994+ "template": {
995+ "category-layout": "grid",
996+ "card-size": "small"
997+ },
998+ "components": {
999+ "title": "title",
1000+ "art": "art",
1001+ "subtitle": "username"
1002+ }
1003+}`
1004+
1005+// SCOPE ***********************************************************************
1006+
1007+var scope_interface scopes.Scope
1008+
1009+type MyScope struct {
1010+ base *scopes.ScopeBase
1011+}
1012+
1013+func (s *MyScope) Preview(result *scopes.Result, metadata *scopes.ActionMetadata, reply *scopes.PreviewReply, cancelled <-chan bool) error {
1014+ layout1col := scopes.NewColumnLayout(1)
1015+ layout2col := scopes.NewColumnLayout(2)
1016+ layout3col := scopes.NewColumnLayout(3)
1017+
1018+ // Single column layout
1019+ layout1col.AddColumn("image", "header", "summary", "actions")
1020+
1021+ // Two column layout
1022+ layout2col.AddColumn("image")
1023+ layout2col.AddColumn("header", "summary", "actions")
1024+
1025+ // Three cokumn layout
1026+ layout3col.AddColumn("image")
1027+ layout3col.AddColumn("header", "summary", "actions")
1028+ layout3col.AddColumn()
1029+
1030+ // Register the layouts we just created
1031+ reply.RegisterLayout(layout1col, layout2col, layout3col)
1032+
1033+ header := scopes.NewPreviewWidget("header", "header")
1034+
1035+ // It has title and a subtitle properties
1036+ header.AddAttributeMapping("title", "title")
1037+ header.AddAttributeMapping("subtitle", "subtitle")
1038+
1039+ // Define the image section
1040+ image := scopes.NewPreviewWidget("image", "image")
1041+ // It has a single source property, mapped to the result's art property
1042+ image.AddAttributeMapping("source", "art")
1043+
1044+ // Define the summary section
1045+ description := scopes.NewPreviewWidget("summary", "text")
1046+ // It has a text property, mapped to the result's description property
1047+ description.AddAttributeMapping("text", "description")
1048+
1049+ // build variant map.
1050+ tuple1 := make(map[string]interface{})
1051+ tuple1["id"] = "open"
1052+ tuple1["label"] = "Open"
1053+ tuple1["uri"] = "application:///tmp/non-existent.desktop"
1054+
1055+ tuple2 := make(map[string]interface{})
1056+ tuple1["id"] = "download"
1057+ tuple1["label"] = "Download"
1058+
1059+ tuple3 := make(map[string]interface{})
1060+ tuple1["id"] = "hide"
1061+ tuple1["label"] = "Hide"
1062+
1063+ actions := scopes.NewPreviewWidget("actions", "actions")
1064+ actions.AddAttributeValue("actions", []interface{}{tuple1, tuple2, tuple3})
1065+
1066+ var scope_data string
1067+ metadata.ScopeData(scope_data)
1068+ if len(scope_data) > 0 {
1069+ extra := scopes.NewPreviewWidget("extra", "text")
1070+ extra.AddAttributeValue("text", "test Text")
1071+ reply.PushWidgets(header, image, description, actions, extra)
1072+ } else {
1073+ reply.PushWidgets(header, image, description, actions)
1074+ }
1075+
1076+ return nil
1077+}
1078+
1079+func (s *MyScope) Search(query *scopes.CannedQuery, metadata *scopes.SearchMetadata, reply *scopes.SearchReply, cancelled <-chan bool) error {
1080+ return s.AddQueryResults(reply, query.QueryString())
1081+}
1082+
1083+func (s *MyScope) SetScopeBase(base *scopes.ScopeBase) {
1084+ s.base = base
1085+}
1086+
1087+// RESULTS *********************************************************************
1088+
1089+func (s *MyScope) AddQueryResults(reply *scopes.SearchReply, query string) error {
1090+ cat := reply.RegisterCategory("category", "Category", "", searchCategoryTemplate)
1091+
1092+ result := scopes.NewCategorisedResult(cat)
1093+ result.SetURI("http://localhost/" + query)
1094+ result.SetDndURI("http://localhost_dnduri" + query)
1095+ result.SetTitle("TEST" + query)
1096+ result.SetArt("https://pbs.twimg.com/profile_images/1117820653/5ttls5.jpg.png")
1097+ result.Set("test_value_bool", true)
1098+ result.Set("test_value_string", "test_value"+query)
1099+ result.Set("test_value_int", 1999)
1100+ result.Set("test_value_float", 1.999)
1101+ if err := reply.Push(result); err != nil {
1102+ return err
1103+ }
1104+
1105+ result.SetURI("http://localhost2/" + query)
1106+ result.SetDndURI("http://localhost_dnduri2" + query)
1107+ result.SetTitle("TEST2")
1108+ result.SetArt("https://pbs.twimg.com/profile_images/1117820653/5ttls5.jpg.png")
1109+ result.Set("test_value_bool", false)
1110+ result.Set("test_value_string", "test_value2"+query)
1111+ result.Set("test_value_int", 2000)
1112+ result.Set("test_value_float", 2.100)
1113+
1114+ // add a variant map value
1115+ m := make(map[string]interface{})
1116+ m["value1"] = 1
1117+ m["value2"] = "string_value"
1118+ result.Set("test_value_map", m)
1119+
1120+ // add a variant array value
1121+ l := make([]interface{}, 0)
1122+ l = append(l, 1999)
1123+ l = append(l, "string_value")
1124+ result.Set("test_value_array", l)
1125+ if err := reply.Push(result); err != nil {
1126+ return err
1127+ }
1128+
1129+ return nil
1130+}
1131+
1132+// MAIN ************************************************************************
1133+
1134+func main() {
1135+ var sc MyScope
1136+ scope_interface = &sc
1137+
1138+ if err := scopes.Run(&MyScope{}); err != nil {
1139+ log.Fatalln(err)
1140+ }
1141+}
1142
1143=== modified file 'unityscope.go'
1144--- unityscope.go 2015-06-17 07:29:19 +0000
1145+++ unityscope.go 2015-06-17 07:29:19 +0000
1146@@ -41,9 +41,19 @@
1147 Preview(result *Result, metadata *ActionMetadata, reply *PreviewReply, cancelled <-chan bool) error
1148 }
1149
1150+// AggregatedScope defines the interface that scopes should implement to
1151+// aggregate sub scopes.
1152 type AggregatedScope interface {
1153 Scope
1154 FindChildScopes() []*ChildScope
1155+ GetScopeBase() *ScopeBase
1156+}
1157+
1158+// SearchListener is an interface that should be implemented for aggregated
1159+// scopes to retrieve results from their child scopes.
1160+type SearchListener interface {
1161+ FilterResult(result *CategorisedResult) bool
1162+ Finished()
1163 }
1164
1165 // Activator is an interface that should be implemented by scopes that
1166@@ -61,10 +71,10 @@
1167 }
1168
1169 //export callScopeSearch
1170-func callScopeSearch(scope Scope, queryPtr, metadataPtr unsafe.Pointer, replyData *C.uintptr_t, cancel <-chan bool) {
1171+func callScopeSearch(scope Scope, queryPtr, metadataPtr unsafe.Pointer, replyData *C.uintptr_t, queryBase *C._SearchQueryBase, cancel <-chan bool) {
1172 query := makeCannedQuery((*C._CannedQuery)(queryPtr))
1173 metadata := makeSearchMetadata((*C._SearchMetadata)(metadataPtr))
1174- reply := makeSearchReply(replyData)
1175+ reply := makeSearchReply(replyData, queryBase)
1176
1177 go func() {
1178 err := scope.Search(query, metadata, reply, cancel)
1179@@ -72,6 +82,12 @@
1180 reply.Error(err)
1181 return
1182 }
1183+ switch scope.(type) {
1184+ case AggregatedScope:
1185+ reply.waitForChildScopesFinished()
1186+ default:
1187+ // nothing
1188+ }
1189 reply.Finished()
1190 }()
1191 }
1192@@ -212,7 +228,7 @@
1193 var nb_scopes C.int
1194 var c_array **C._ChildScope = C.list_child_scopes(b.b, &nb_scopes)
1195 defer C.free(unsafe.Pointer(c_array))
1196-
1197+
1198 length := int(nb_scopes)
1199 // create a very big slice and then slice it to the number of scopes metadata
1200 slice := (*[1 << 27]*C._ChildScope)(unsafe.Pointer(c_array))[:length:length]

Subscribers

People subscribed via source and target branches

to all changes: