Merge lp:~xavi-garcia-mena/go-unityscopes/go-subsearch into lp:go-unityscopes/v2
- go-subsearch
- Merge into v2
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 |
Related bugs: |
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.
- 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
- 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.
Kyle Fazzari (kyrofa) wrote : | # |
A few little things, comments inline.
- 84. By Xavi Garcia
-
added suggested changes
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 ;).
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.
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
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] |
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).