Merge lp:~jamesh/unity-lens-applications/split-scope into lp:unity-lens-applications

Proposed by James Henstridge
Status: Merged
Approved by: Michal Hruby
Approved revision: 380
Merged at revision: 356
Proposed branch: lp:~jamesh/unity-lens-applications/split-scope
Merge into: lp:unity-lens-applications
Diff against target: 4225 lines (+1769/-1504)
21 files modified
.bzrignore (+33/-40)
Makefile.am (+0/-17)
configure.ac (+3/-3)
data/Makefile.am (+23/-1)
data/applications.scope.in.in (+3/-0)
data/commands.scope.in.in (+2/-0)
data/scopes.scope.in.in (+15/-0)
data/unity-scope-applications.service.in (+1/-1)
debian/changelog (+6/-0)
debian/rules (+1/-0)
po/POTFILES.in (+5/-3)
po/POTFILES.skip (+2/-2)
src/Makefile.am (+25/-28)
src/commands-scope.vala (+289/-254)
src/daemon.vala (+775/-1046)
src/main.vala (+24/-88)
src/scopes-scope.vala (+510/-0)
src/software-center-data-cache.vala (+3/-3)
src/software-center-data-provider.vala (+12/-12)
tests/unit/software-center-data-provider-mock.vala (+2/-2)
tests/unit/test-software-center-data-cache.vala (+35/-4)
To merge this branch: bzr merge lp:~jamesh/unity-lens-applications/split-scope
Reviewer Review Type Date Requested Status
Michal Hruby (community) Approve
PS Jenkins bot (community) continuous-integration Approve
Review via email: mp+175982@code.launchpad.net

Commit message

Convert the Applications scope over to the Unity.AbstractScope API.

Description of the change

Convert the Applications scope over to the Unity.AbstractScope API.

To post a comment you must log in.
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :

FAILED: Continuous integration, rev:367
No commit message was specified in the merge proposal. Click on the following link and set the commit message (if you want a jenkins rebuild you need to trigger it yourself):
https://code.launchpad.net/~jamesh/unity-lens-applications/split-scope/+merge/175982/+edit-commit-message

http://jenkins.qa.ubuntu.com/job/unity-lens-applications-ci/22/
Executed test runs:
    FAILURE: http://jenkins.qa.ubuntu.com/job/unity-lens-applications-saucy-amd64-ci/11/console
    FAILURE: http://jenkins.qa.ubuntu.com/job/unity-lens-applications-saucy-armhf-ci/11/console
    FAILURE: http://jenkins.qa.ubuntu.com/job/unity-lens-applications-saucy-i386-ci/11/console

Click here to trigger a rebuild:
http://s-jenkins:8080/job/unity-lens-applications-ci/22/rebuild

review: Needs Fixing (continuous-integration)
368. By James Henstridge

Merge from trunk, fixing conflict

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

Note that this branch depends on there being an apps master scope in place, so should probably not be fully approved until we're ready to bring that back. I'd still appreciate review feedback though.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :

FAILED: Continuous integration, rev:368
No commit message was specified in the merge proposal. Click on the following link and set the commit message (if you want a jenkins rebuild you need to trigger it yourself):
https://code.launchpad.net/~jamesh/unity-lens-applications/split-scope/+merge/175982/+edit-commit-message

http://jenkins.qa.ubuntu.com/job/unity-lens-applications-ci/23/
Executed test runs:
    FAILURE: http://jenkins.qa.ubuntu.com/job/unity-lens-applications-saucy-amd64-ci/12/console
    FAILURE: http://jenkins.qa.ubuntu.com/job/unity-lens-applications-saucy-armhf-ci/12/console
    FAILURE: http://jenkins.qa.ubuntu.com/job/unity-lens-applications-saucy-i386-ci/12/console

Click here to trigger a rebuild:
http://s-jenkins:8080/job/unity-lens-applications-ci/23/rebuild

review: Needs Fixing (continuous-integration)
369. By James Henstridge

Change "-rpath" to "-R", since that is libtool's equivalent flag.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :

FAILED: Continuous integration, rev:369
No commit message was specified in the merge proposal. Click on the following link and set the commit message (if you want a jenkins rebuild you need to trigger it yourself):
https://code.launchpad.net/~jamesh/unity-lens-applications/split-scope/+merge/175982/+edit-commit-message

http://jenkins.qa.ubuntu.com/job/unity-lens-applications-ci/24/
Executed test runs:
    SUCCESS: http://jenkins.qa.ubuntu.com/job/unity-lens-applications-saucy-amd64-ci/13
    SUCCESS: http://jenkins.qa.ubuntu.com/job/unity-lens-applications-saucy-armhf-ci/13
    SUCCESS: http://jenkins.qa.ubuntu.com/job/unity-lens-applications-saucy-i386-ci/13

Click here to trigger a rebuild:
http://s-jenkins:8080/job/unity-lens-applications-ci/24/rebuild

review: Needs Fixing (continuous-integration)
Revision history for this message
Michal Hruby (mhr3) wrote :

First of all, could we rename the main apps scope to something more appropriate? A scope with ID applications-applications.scope looks just wrong.

1315 + var search_string = context.search_query.strip();

AbstractScope has normalize_search_query method, pls override that instead. (SearchContext will have the normalized string in its search_query field).

1491 + /* XXX:
1492 if (model.get_n_rows () == 0)

Another prop that's not exposed by the new API? We should add it to ResultSet imo, thoughts?

2258 + public override Unity.AbstractPreview? run ()

Isn't this is doing blocking dbus calls to get the preview? Why isn't it async then?

2844 + private class RunnerScope : Unity.AbstractScope

Pls rename to CommandsScope, not sure why we have the disparity in the code and the .scope file.

2940 + // force a search to refresh history order (TODO: be more clever in the future)
2941 + // XXX: No way to queue result update
2942 + //scope.queue_search_changed (SearchType.DEFAULT);

There is now :)

3541 + public ScopesScope (ApplicationsScope appscope)
2869 + public RunnerScope (ApplicationsScope appscope)

Not liking the circular dependencies, but I suppose it doesn't make much sense to try to get rid of them.

2849 + // The ApplicationsScope holds a reference to us, so avoid a
2850 + // reference cycle.

But, can we at least get rid of this? Seems it's just one method AppsScope uses, can we make that one static?

That's it for the first round... The diff is huge but it's mostly moving stuff around, so it's not too bad.

370. By James Henstridge

Rename RunnerScope to CommandsScope, to match scope name.

371. By James Henstridge

Use results_invalidted in CommandsScope too.

372. By James Henstridge

No need to normalise query in search method.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
373. By James Henstridge

Make software-center D-Bus calls in previewers async.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
374. By James Henstridge

Fix tests to account for changing Software Center code to async.

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

The installed scope files are not pointing to correct .so location (cause it's using multiarch), therefore the final scope doesn't work.

review: Needs Fixing
375. By James Henstridge

Substitute @libdir@ in the scope files so we correctly handle multi-lib.

376. By James Henstridge

Bump version number.

377. By James Henstridge

Don't install the libtool .la file.

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

There's a couple functional issues:

1) The scope loader daemon is trying to exit after a couple of seconds of inactivity, but that is not desired as the startup of the xapian-and-io-heavy apps scope is very expensive. Also, for some reason it failed to exit and that resulted it apps no longer appearing in the dash.

2) The ordering of the items in the apps scope is now undefined, but according to design scopes should be displayed after installed (or available) apps. But this should be brought up to design, maybe we want a separate category for scopes now, similarly to the phone.

3) Performing a search in the apps lens causes the results to flicker a lot. This will be because finishing a scope search flushes the results, so if the scopes scope finishes very quickly it'll flush empty result set, so the dash will remove everything and a few frames later the apps scope will flush its non-empty result set causing another redraw.

review: Needs Fixing
378. By Michal Hruby

Mark scope results to aid sorting in the master scope.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
379. By James Henstridge

Merge from trunk, fixing conflicts

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
380. By James Henstridge

Disable timeout for the applications scope.

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

Should be good with the fixes that landed in home scope and libunity.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file '.bzrignore'
2--- .bzrignore 2011-08-03 21:52:17 +0000
3+++ .bzrignore 2013-08-06 08:19:29 +0000
4@@ -1,8 +1,8 @@
5-
6-src/.deps
7-tests/.deps
8-src/.libs
9-tests/.libs
10+.deps
11+.libs
12+*.lo
13+*.la
14+ABOUT-NLS
15 INSTALL
16 Makefile
17 Makefile.in
18@@ -13,6 +13,7 @@
19 config.h
20 config.h.in
21 config.log
22+config.rpath
23 config.status
24 config.sub
25 configure
26@@ -20,33 +21,27 @@
27 install-sh
28 libtool
29 ltmain.sh
30-m4/
31 missing
32-stamp-h1
33-data/Makefile
34-data/Makefile.in
35-src/Makefile
36-src/Makefile.in
37-src/main.c
38-src/unity-place-applications
39-src/unity_place_applications.vala.stamp
40-tests/Makefile
41-tests/Makefile.in
42-tests/test-place-applications
43-tests/test-place-applications.c
44-tests/test_place_applications_vala.stamp
45-vapi/Makefile
46-vapi/Makefile.in
47-unity-apps_version.tar.gz
48-tests/place-applications-check-results.xml
49 mkinstalldirs
50-data/unity-place-applications.service
51+stamp-*
52+data/*.directory
53+data/*.gschema.valid
54+data/*.gschema.xml
55+data/*.gschema.xml.in
56+data/*.menu
57+data/*.scope
58+data/*.scope.in
59+data/*.service
60+m4/intltool.m4
61+m4/libtool.m4
62+m4/ltoptions.m4
63+m4/ltsugar.m4
64+m4/ltversion.m4
65+m4/lt~obsolete.m4
66+po/*.gmo
67 po/POTFILES
68 po/stamp-it
69-src/applications.c
70-src/utils.c
71 po/Makefile.in.in
72-unity-place-applications.desktop
73 po/.intltool-merge-cache
74 po/Makevars.template
75 po/Rules-quot
76@@ -57,17 +52,15 @@
77 po/remove-potcdate.sin
78 po/boldquot.sed
79 po/quot.sed
80-ABOUT-NLS
81-config.rpath
82-po/unity-place-applications.pot
83-applications.place
84-applications.place.in
85-data/X-Unity-All-Applications.directory
86-data/unity-place-applications.menu
87-po/da.gmo
88-src/config.c
89+po/unity-lens-applications.pot
90+src/*.c
91 src/config.vala
92-src/daemon.c
93-src/schemas.c
94-src/unity-applications-daemon
95-src/unity_applications_daemon.vala.stamp
96+src/unity-scope-applications.vala.stamp
97+tests/unit/*.c
98+tests/unit/*.stamp
99+tests/unit/test-purchase-info-helper
100+tests/unit/test-software-center-data-cache
101+tests/unit/test-software-center-details
102+tests/unit/test-software-center-utils
103+tests/unit/test-utils
104+tests/unit/test-xapian-utils
105
106=== modified file 'Makefile.am'
107--- Makefile.am 2013-07-28 10:26:17 +0000
108+++ Makefile.am 2013-08-06 08:19:29 +0000
109@@ -1,21 +1,6 @@
110 NULL =
111 SUBDIRS = src data po tests/unit
112
113-#
114-# Install the applications.lens file
115-#
116-scope_in_files = applications.scope.in commands.scope.in
117-
118-scopedir = $(datadir)/unity/scopes
119-scope_DATA = commands.scope
120-appscopedir = $(scopedir)/applications
121-appscope_DATA = applications.scope
122-
123-icondir = $(datadir)/unity/themes
124-icon_DATA = applications.png
125-
126-@INTLTOOL_SCOPE_RULE@
127-
128 DISTCHECK_CONFIGURE_FLAGS = --enable-localinstall
129
130 # ChangeLog file created at distcheck time
131@@ -34,9 +19,7 @@
132 fi
133
134 EXTRA_DIST = \
135- applications.png \
136 autogen.sh \
137- $(scope_in_files) \
138 AUTHORS \
139 COPYING \
140 HACKING \
141
142=== modified file 'configure.ac'
143--- configure.ac 2013-05-16 21:13:36 +0000
144+++ configure.ac 2013-08-06 08:19:29 +0000
145@@ -1,7 +1,9 @@
146-AC_INIT(unity-lens-applications, 7.0.0, https://launchpad.net/unity-lens-applications)
147+AC_INIT(unity-lens-applications, 7.1.0, https://launchpad.net/unity-lens-applications)
148 AC_COPYRIGHT([Copyright 2010-2012 Canonical])
149 AM_INIT_AUTOMAKE([1.11])
150
151+LT_INIT
152+
153 #############################################
154 # Silent build rules
155 #############################################
156@@ -166,8 +168,6 @@
157 #############################################
158 AC_CONFIG_FILES([
159 Makefile
160- applications.scope.in
161- commands.scope.in
162 data/com.canonical.Unity.AppsLens.gschema.xml.in
163 data/Makefile
164 data/X-Unity-All-Applications.directory
165
166=== modified file 'data/Makefile.am'
167--- data/Makefile.am 2013-05-16 11:37:05 +0000
168+++ data/Makefile.am 2013-08-06 08:19:29 +0000
169@@ -1,4 +1,22 @@
170 #############################################################
171+# Unity scope files
172+#############################################################
173+@INTLTOOL_SCOPE_RULE@
174+
175+%.scope.in: %.scope.in.in
176+ sed -e "s|\@prefix\@|$(prefix)|" -e "s|\@libdir\@|$(libdir)|" $< > $@
177+
178+scope_in_in_files = applications.scope.in.in scopes.scope.in.in commands.scope.in.in
179+
180+scopedir = $(datadir)/unity/scopes
181+scope_DATA = commands.scope
182+appscopedir = $(scopedir)/applications
183+appscope_DATA = applications.scope scopes.scope
184+
185+icondir = $(datadir)/unity/themes
186+icon_DATA = applications.png
187+
188+#############################################################
189 # DBus service files
190 #############################################################
191 dbus_servicesdir = $(DBUSSERVICEDIR)
192@@ -6,7 +24,7 @@
193 dbus_services_DATA = $(service_in_files:.service.in=.service)
194
195 %.service: %.service.in
196- sed -e "s|\@pkglibexecdir\@|$(pkglibexecdir)|" $< > $@
197+ sed -e "s|\@bindir\@|$(bindir)|" $< > $@
198
199 #############################################################
200 # XDG Menu files
201@@ -33,12 +51,16 @@
202 @GSETTINGS_RULES@
203
204 EXTRA_DIST = \
205+ applications.png \
206+ $(scope_in_in_files) \
207 com.canonical.Unity.AppsLens.gschema.xml.in.in \
208 $(service_in_files) \
209 $(directory_in_files) \
210 $(menu_in_files)
211
212 CLEANFILES = \
213+ $(scope_DATA) \
214+ $(scope_in_in_files:.scope.in.in=.scope.in) \
215 unity-scope-applications.service \
216 $(gsettings_SCHEMAS)
217
218
219=== renamed file 'applications.png' => 'data/applications.png'
220=== renamed file 'applications.scope.in.in' => 'data/applications.scope.in.in'
221--- applications.scope.in.in 2013-06-20 13:11:17 +0000
222+++ data/applications.scope.in.in 2013-08-06 08:19:29 +0000
223@@ -1,6 +1,9 @@
224 [Scope]
225 DBusName=com.canonical.Unity.Scope.Applications
226 DBusPath=/com/canonical/unity/scope/applications
227+Module=@libdir@/unity/unity-scope-applications.so
228+ModuleType=C
229+Timeout=-1
230 Icon=@prefix@/share/unity/icons/lens-nav-app.svg
231 _Name=Applications
232 _Description=This is an Ubuntu search plugin that enables information from local applications to be searched and displayed in the Dash underneath the Applications header. If you do not wish to search this content source, you can disable this search plugin.
233
234=== renamed file 'commands.scope.in.in' => 'data/commands.scope.in.in'
235--- commands.scope.in.in 2013-05-16 11:37:05 +0000
236+++ data/commands.scope.in.in 2013-08-06 08:19:29 +0000
237@@ -1,6 +1,8 @@
238 [Scope]
239 DBusName=com.canonical.Unity.Scope.Applications
240 DBusPath=/com/canonical/unity/scope/commands
241+Module=@libdir@/unity/unity-scope-applications.so
242+ModuleType=C
243 _Name=Commands
244 _Description=This is an Ubuntu search plugin that enables information from local binaries to be searched and displayed in the Dash. If you do not wish to search this content source, you can disable this search plugin.
245 Icon=
246
247=== added file 'data/scopes.scope.in.in'
248--- data/scopes.scope.in.in 1970-01-01 00:00:00 +0000
249+++ data/scopes.scope.in.in 2013-08-06 08:19:29 +0000
250@@ -0,0 +1,15 @@
251+[Scope]
252+DBusName=com.canonical.Unity.Scope.Applications
253+DBusPath=/com/canonical/unity/scope/scopes
254+Module=@libdir@/unity/unity-scope-applications.so
255+ModuleType=C
256+Icon=@prefix@/share/unity/icons/lens-nav-app.svg
257+_Name=Scopes
258+_Description=This is an Ubuntu search plugin that enables information from available search plugins to be searched and displayed in the Dash underneath the Applications header. If you do not wish to search this content source, you can disable this search plugin.
259+Type=applications
260+_SearchHint=Search search plugins
261+OptionalMetadata=is_scope[u]
262+
263+[Desktop Entry]
264+X-Ubuntu-Gettext-Domain=unity-lens-applications
265+
266
267=== modified file 'data/unity-scope-applications.service.in'
268--- data/unity-scope-applications.service.in 2013-05-16 11:37:05 +0000
269+++ data/unity-scope-applications.service.in 2013-08-06 08:19:29 +0000
270@@ -1,3 +1,3 @@
271 [D-BUS Service]
272 Name=com.canonical.Unity.Scope.Applications
273-Exec=@pkglibexecdir@/unity-applications-daemon
274+Exec=@bindir@/unity-scope-loader applications/applications.scope applications/scopes.scope commands.scope
275
276=== modified file 'debian/changelog'
277--- debian/changelog 2013-07-29 04:02:27 +0000
278+++ debian/changelog 2013-08-06 08:19:29 +0000
279@@ -1,3 +1,9 @@
280+unity-lens-applications (7.1.0-0ubuntu1) UNRELEASED; urgency=low
281+
282+ * Convert scope to run as a unity-scope-loader plugin.
283+
284+ -- James Henstridge <james.henstridge@canonical.com> Fri, 26 Jul 2013 17:09:10 +0800
285+
286 unity-lens-applications (7.0.0+13.10.20130729-0ubuntu1) saucy; urgency=low
287
288 [ Michal Hruby ]
289
290=== modified file 'debian/rules'
291--- debian/rules 2013-04-12 12:21:04 +0000
292+++ debian/rules 2013-08-06 08:19:29 +0000
293@@ -10,3 +10,4 @@
294
295 override_dh_install:
296 dh_install --fail-missing
297+ rm -f debian/unity-lens-applications/usr/lib/*/unity/*.la
298
299=== modified file 'po/POTFILES.in'
300--- po/POTFILES.in 2013-05-16 11:37:05 +0000
301+++ po/POTFILES.in 2013-08-06 08:19:29 +0000
302@@ -1,8 +1,10 @@
303 [encoding: UTF-8]
304 src/daemon.vala
305-src/runner.vala
306+src/commands-scope.vala
307+src/scopes-scope.vala
308 src/utils.vala
309 src/main.vala
310 data/X-Unity-All-Applications.directory.in
311-[type: gettext/ini]applications.scope.in.in
312-[type: gettext/ini]commands.scope.in.in
313+[type: gettext/ini]data/applications.scope.in.in
314+[type: gettext/ini]data/commands.scope.in.in
315+[type: gettext/ini]data/scopes.scope.in.in
316
317=== modified file 'po/POTFILES.skip'
318--- po/POTFILES.skip 2011-03-10 14:49:07 +0000
319+++ po/POTFILES.skip 2013-08-06 08:19:29 +0000
320@@ -1,5 +1,5 @@
321 src/main.c
322 src/daemon.c
323 src/utils.c
324-src/runner.c
325-
326+src/commands-scope.c
327+src/scopes-scope.c
328
329=== modified file 'src/Makefile.am'
330--- src/Makefile.am 2013-05-16 11:37:05 +0000
331+++ src/Makefile.am 2013-08-06 08:19:29 +0000
332@@ -1,14 +1,14 @@
333 NULL =
334 BUILT_SOURCES =
335 CLEANFILES =
336-EXTRA_DIST =
337+EXTRA_DIST =
338
339 DATADIR = $(datadir)
340
341-pkglibexec_PROGRAMS = \
342- unity-applications-daemon
343+unitylibdir = $(libdir)/unity
344+unitylib_LTLIBRARIES = unity-scope-applications.la
345
346-unity_applications_daemon_CPPFLAGS = \
347+AM_CPPFLAGS = \
348 -DDATADIR=\"$(DATADIR)\" \
349 -DPKGDATADIR=\"$(PKGDATADIR)\" \
350 -DGETTEXT_PACKAGE=\"$(GETTEXT_PACKAGE)\" \
351@@ -21,12 +21,14 @@
352 -g
353
354 if !ENABLE_C_WARNINGS
355- unity_applications_daemon_CPPFLAGS += -w
356+ AM_CPPFLAGS += -w
357 endif
358
359+AM_CXXFLAGS = --std=c++11
360+
361 # Note that we require glib 2.26 because libzeitgeist
362 # expects us to use g_ptr_array_unref instead of g_ptr_array_free.
363-unity_applications_daemon_VALAFLAGS = \
364+AM_VALAFLAGS = \
365 -C \
366 --vapidir=$(top_srcdir)/vapi \
367 --pkg posix \
368@@ -47,23 +49,24 @@
369
370 unity_ratings_db_libs = -ldb
371
372-unity_applications_daemon_LDADD = \
373+unity_scope_applications_la_LIBADD = \
374 $(LENS_DAEMON_LIBS) \
375 $(unity_package_search_libs) \
376 $(unity_ratings_db_libs) \
377- unity-package-search.o \
378- unity-ratings-db.o \
379 $(NULL)
380
381-unity_applications_daemon_LDFLAGS = -rpath $(PROTOCOLPRIVATELIBDIR) $(COVERAGE_LDFLAGS)
382+unity_scope_applications_la_LDFLAGS = \
383+ -module -shared -avoid-version \
384+ -R $(PROTOCOLPRIVATELIBDIR) $(COVERAGE_LDFLAGS)
385
386-unity_applications_daemon_VALASOURCES = \
387+unity_scope_applications_la_VALASOURCES = \
388 app-watcher.vala \
389 config.vala \
390 daemon.vala \
391 launcher-client.vala \
392 main.vala \
393- runner.vala \
394+ commands-scope.vala \
395+ scopes-scope.vala \
396 schemas.vala \
397 utils.vala \
398 aptd-client.vala \
399@@ -75,40 +78,34 @@
400 xapian-utils.vala \
401 $(NULL)
402
403-unity-package-search.o : $(srcdir)/unity-package-search.cc $(srcdir)/unity-package-search.h
404- $(AM_V_GEN)$(CXX) --std=c++11 $(COVERAGE_CFLAGS) -g $(unity_package_search_libs) -DGMENU_I_KNOW_THIS_IS_UNSTABLE `pkg-config --cflags --libs glib-2.0 libgnome-menu-3.0 unity gee-1.0 libcolumbus0` -c $(srcdir)/unity-package-search.cc
405-
406-unity-ratings-db.o : $(srcdir)/unity-ratings-db.c $(srcdir)/unity-ratings-db.h
407- $(AM_V_CC)$(CC) -g $(COVERAGE_CFLAGS) $(unity_ratings_db_libs) `pkg-config --cflags --libs glib-2.0` -c $(srcdir)/unity-ratings-db.c
408-
409-unity_applications_daemon_SOURCES = \
410- $(unity_applications_daemon_VALASOURCES:.vala=.c) \
411+unity_scope_applications_la_SOURCES = \
412+ $(unity_scope_applications_la_VALASOURCES:.vala=.c) \
413+ unity-package-search.cc \
414 unity-package-search.h \
415+ unity-ratings-db.c \
416 unity-ratings-db.h \
417 $(NULL)
418
419 BUILT_SOURCES += \
420- unity_applications_daemon.vala.stamp \
421+ unity-scope-applications.vala.stamp \
422 $(NULL)
423
424 EXTRA_DIST += \
425- unity_applications_daemon.vala.stamp \
426- $(unity_applications_daemon_VALASOURCES) \
427+ unity-scope-applications.vala.stamp \
428+ $(unity_scope_applications_la_VALASOURCES) \
429 unity-package-search.cc \
430 unity-package-search.h \
431 unity-ratings-db.c \
432 unity-ratings-db.h \
433 $(NULL)
434
435-unity_applications_daemon.vala.stamp: $(unity_applications_daemon_VALASOURCES)
436- $(AM_V_GEN) $(VALAC) $(unity_applications_daemon_VALAFLAGS) $^
437+unity-scope-applications.vala.stamp: $(unity_scope_applications_la_VALASOURCES)
438+ $(AM_V_GEN) $(VALAC) $(AM_VALAFLAGS) $^
439 @touch $@
440
441 CLEANFILES += \
442 *.stamp \
443 *.vapi \
444- $(unity_applications_daemon_VALASOURCES:.vala=.c) \
445- unity-package-search.o \
446+ $(unity_scope_applications_la_VALASOURCES:.vala=.c) \
447 unity-package-search.h.gch \
448- unity-ratings-db.o \
449 $(NULL)
450
451=== renamed file 'src/runner.vala' => 'src/commands-scope.vala'
452--- src/runner.vala 2013-07-01 12:17:01 +0000
453+++ src/commands-scope.vala 2013-08-06 08:19:29 +0000
454@@ -1,5 +1,6 @@
455+/* -*- mode: vala; c-basic-offset: 2; indent-tabs-mode: nil -*- */
456 /*
457- * Copyright (C) 2011 Canonical Ltd
458+ * Copyright (C) 2011-2013 Canonical Ltd
459 *
460 * This program is free software: you can redistribute it and/or modify
461 * it under the terms of the GNU General Public License version 3 as
462@@ -38,47 +39,25 @@
463 }
464 }
465
466- public class Runner: GLib.Object
467+ private class CommandsScope : Unity.AbstractScope
468 {
469-
470- private Unity.ApplicationsLens.Daemon daemon;
471- private const string BUS_NAME_PREFIX = "com.canonical.Unity.ApplicationsLens.Runner";
472+ // The ApplicationsScope holds a reference to us, so avoid a
473+ // reference cycle.
474+ public unowned ApplicationsScope appscope;
475
476 /* for now, load the same keys as gnome-panel */
477 private const string HISTORY_KEY = "history";
478 private const int MAX_HISTORY = 10;
479
480- public Unity.DeprecatedScope scope;
481-
482- private Gee.HashMap<string,AboutEntry> about_entries;
483- private Gee.List<string> history;
484- private ExecSearcher exec_searcher = new ExecSearcher ();
485+ public Gee.HashMap<string,AboutEntry> about_entries;
486+ public Gee.List<string> history;
487+ public ExecSearcher exec_searcher = new ExecSearcher ();
488
489 private Settings gp_settings;
490
491- public Runner (Unity.ApplicationsLens.Daemon daemon)
492+ public CommandsScope (ApplicationsScope appscope)
493 {
494- /* First create scope */
495- scope = new Unity.DeprecatedScope ("/com/canonical/unity/scope/commands",
496- "commands");
497- scope.search_in_global = false;
498- scope.search_hint = _("Run a command");
499- scope.visible = false;
500- populate_categories ();
501-
502- /* Listen for changes to the lens scope search */
503- scope.search_changed.connect ((lens_search, search_type, cancellable) =>
504- {
505- update_search.begin (lens_search);
506- });
507-
508- /* Make sure we ignore extra whitespace on searches */
509- scope.generate_search_key.connect ((lens_search) =>
510- {
511- return lens_search.search_string.strip ();
512- });
513-
514- scope.activate_uri.connect (daemon.activate);
515+ this.appscope = appscope;
516
517 /* create the private about entries */
518 about_entries = new Gee.HashMap<string,AboutEntry> ();
519@@ -87,20 +66,92 @@
520 this.gp_settings = new Settings ("com.canonical.Unity.Runner");
521 history = new Gee.ArrayList<string> ();
522 load_history ();
523-
524- this.daemon = daemon;
525-
526- try
527- {
528- scope.export ();
529- }
530- catch (Error err)
531- {
532- error ("Unable to export scope: %s", err.message);
533- }
534- }
535-
536- private void populate_categories ()
537+ }
538+
539+ public void add_history (string last_command)
540+ {
541+
542+ // new history list: better, greatest, latest!
543+ var new_history = new Gee.ArrayList<string> ();
544+ var history_store = new string [this.history.size + 1];
545+ int i = 1;
546+
547+ new_history.add (last_command);
548+ history_store[0] = last_command;
549+ for (var j = 0; (j < this.history.size) && (i < MAX_HISTORY); j++)
550+ {
551+ if (this.history[j] == last_command)
552+ continue;
553+
554+ new_history.add(history[j]);
555+ history_store[i] = history[j];
556+ i++;
557+ }
558+ this.history = new_history;
559+
560+ // store in gsettings
561+ this.gp_settings.set_strv (HISTORY_KEY, history_store);
562+
563+ // force a search to refresh history order (TODO: be more clever in the future)
564+ results_invalidated (SearchType.DEFAULT);
565+ }
566+
567+ private void load_history ()
568+ {
569+ int i = 0;
570+ string[] history_store = this.gp_settings.get_strv (HISTORY_KEY);
571+ foreach (var command in history_store)
572+ {
573+ if (i >= MAX_HISTORY)
574+ break;
575+ this.history.add((string) command.data);
576+ i++;
577+ }
578+ }
579+
580+ private void load_about_entries ()
581+ {
582+ AboutEntry entry;
583+ string name;
584+ string exec;
585+ Icon icon;
586+
587+ // first about:config
588+ name = "about:config";
589+ exec = "ccsm -p unityshell";
590+ try {
591+ icon = Icon.new_for_string (@"$(Config.PREFIX)/share/ccsm/icons/hicolor/64x64/apps/plugin-unityshell.png");
592+ }
593+ catch (Error err) {
594+ warning ("Can't find unityshell icon: %s", err.message);
595+ icon = new ThemedIcon ("gtk-execute");
596+ }
597+ entry = new AboutEntry (name, exec, icon);
598+
599+ about_entries[name] = entry;
600+ about_entries[exec] = entry;
601+
602+ // second about:robots
603+ name = "Robots have a plan.";
604+ exec = "firefox about:robots";
605+ entry = new AboutEntry (name, exec, icon = new ThemedIcon ("battery"));
606+
607+ about_entries["about:robots"] = entry;
608+ about_entries[exec] = entry;
609+
610+ }
611+
612+ public override string get_group_name ()
613+ {
614+ return "com.canonical.Unity.Scope.Applications";
615+ }
616+
617+ public override string get_unique_name ()
618+ {
619+ return "/com/canonical/unity/scope/commands";
620+ }
621+
622+ public override Unity.CategorySet get_categories ()
623 {
624 var categories = new Unity.CategorySet ();
625 var icon_dir = File.new_for_path (ICON_PATH);
626@@ -113,33 +164,98 @@
627
628 categories.add (cat);
629
630- scope.categories = categories;
631- }
632-
633- private void add_result (Dee.Model model, string uri, string icon_hint,
634- uint category_id, string mimetype, string title)
635- {
636- Variant row_data[9];
637- model.append_row (
638- model.build_named_row_static (row_data, "uri", uri,
639- "icon_hint", icon_hint,
640- "category", category_id,
641- "result_type", ResultType.DEFAULT,
642- "mimetype", mimetype,
643- "title", title,
644- "comment", "",
645- "dnd_uri", uri));
646- }
647-
648- private async void update_search (DeprecatedScopeSearch search)
649- {
650- var model = search.results_model;
651+ return categories;
652+ }
653+
654+ public override Unity.FilterSet get_filters ()
655+ {
656+ var filters = new Unity.FilterSet ();
657+ return filters;
658+ }
659+
660+ public override Unity.Schema get_schema ()
661+ {
662+ var schema = new Unity.Schema ();
663+ return schema;
664+ }
665+
666+ public override string get_search_hint ()
667+ {
668+ return _("Run a command");
669+ }
670+
671+ public override string normalize_search_query (string search_query)
672+ {
673+ return search_query.strip();
674+ }
675+
676+ public override Unity.ScopeSearchBase create_search_for_query (Unity.SearchContext search_context)
677+ {
678+ return new CommandsSearch (this, search_context);
679+ }
680+
681+ public override Unity.ResultPreviewer create_previewer (Unity.ScopeResult result, Unity.SearchMetadata metadata)
682+ {
683+ return null;
684+ }
685+
686+ public override Unity.ActivationResponse? activate (Unity.ScopeResult result, Unity.SearchMetadata metadata, string? action_id)
687+ {
688+ return appscope.activate (result, metadata, action_id);
689+ }
690+ }
691+
692+ private class CommandsSearch : Unity.ScopeSearchBase
693+ {
694+ private CommandsScope scope;
695+
696+ public CommandsSearch (CommandsScope scope, Unity.SearchContext search_context)
697+ {
698+ this.scope = scope;
699+ set_search_context (search_context);
700+ }
701+
702+ public override void run ()
703+ {
704+ var ml = new MainLoop ();
705+ run_async (() => { ml.quit (); });
706+ ml.run ();
707+ }
708+
709+ public override void run_async (Unity.ScopeSearchBaseCallback async_callback)
710+ {
711+ update_search.begin (
712+ () => {
713+ async_callback (this);
714+ });
715+ }
716+
717+ private void add_result (Unity.ResultSet result_set, string uri,
718+ string icon_hint, uint category_id,
719+ string mimetype, string title)
720+ {
721+ var result = Unity.ScopeResult ();
722+ result.uri = uri;
723+ result.icon_hint = icon_hint;
724+ result.category = category_id;
725+ result.result_type = ResultType.DEFAULT;
726+ result.mimetype = mimetype;
727+ result.title = title;
728+ result.comment = "";
729+ result.dnd_uri = uri;
730+ result.metadata = new HashTable<string, Variant> (str_hash, str_equal);
731+ result_set.add_result (result);
732+ }
733+
734+ private async void update_search ()
735+ {
736+ var context = this.search_context;
737+ var result_set = context.result_set;
738 var executables_match = new Gee.ArrayList<string> ();
739 var dirs_match = new Gee.ArrayList<string> ();
740- model.clear ();
741
742- var search_string = search.search_string;
743- bool has_search = !Utils.is_search_empty (search.search_string);
744+ var search_string = context.search_query;
745+ bool has_search = !Utils.is_search_empty (search_string);
746
747 string uri;
748 Icon icon;
749@@ -147,16 +263,15 @@
750 string display_name;
751 var category_id = RunnerCategory.HISTORY;
752
753- foreach (var command in this.history)
754+ foreach (var command in scope.history)
755 {
756 display_name = get_icon_uri_and_mimetype (command, out icon, out uri, out mimetype);
757- add_result (model, uri, icon.to_string (), category_id, mimetype,
758+ add_result (result_set, uri, icon.to_string (), category_id, mimetype,
759 display_name);
760 }
761
762 if (!has_search)
763 {
764- search.finished ();
765 return;
766 }
767
768@@ -168,9 +283,8 @@
769 uri = "about:blank";
770 string commenteaster = _("There is no easter egg in Unity");
771 icon = new ThemedIcon ("gnome-panel-fish");
772- add_result (model, uri, icon.to_string (), 0, "text/plain",
773+ add_result (result_set, uri, icon.to_string (), 0, "text/plain",
774 commenteaster);
775- search.finished ();
776 return;
777 }
778 else if (search_string == "gegls from outer space")
779@@ -178,9 +292,8 @@
780 uri = "about:blank";
781 string commentnoeaster = _("Still no easter egg in Unity");
782 icon = new ThemedIcon ("gnome-panel-fish");
783- add_result (model, uri, icon.to_string (), 0, "text/plain",
784+ add_result (result_set, uri, icon.to_string (), 0, "text/plain",
785 commentnoeaster);
786- search.finished ();
787 return;
788
789 }
790@@ -238,7 +351,7 @@
791 /* complete again system executables */
792 else
793 {
794- var matching_executables = yield exec_searcher.find_prefixed (search_string);
795+ var matching_executables = yield scope.exec_searcher.find_prefixed (search_string);
796 foreach (var matching_exec in matching_executables)
797 {
798 executables_match.add (matching_exec);
799@@ -255,7 +368,7 @@
800 if ((executables_match.size == 0) && (dirs_match.size == 0))
801 {
802 display_name = get_icon_uri_and_mimetype (search_string, out icon, out uri, out mimetype);
803- add_result (model, uri.strip (), icon.to_string (),
804+ add_result (result_set, uri.strip (), icon.to_string (),
805 category_id, mimetype, display_name);
806 }
807
808@@ -265,7 +378,7 @@
809 foreach (var dir in dirs_match)
810 {
811 uri = UNITY_RUNNER_PREFIX + dir;
812- add_result (model, uri, icon.to_string (),
813+ add_result (result_set, uri, icon.to_string (),
814 category_id, mimetype, dir);
815 }
816
817@@ -275,104 +388,13 @@
818 // TODO: try to match to a desktop file for the icon
819 uri = UNITY_RUNNER_PREFIX + final_exec;
820 display_name = get_icon_uri_and_mimetype (final_exec, out icon, out uri, out mimetype);
821- add_result (model, uri, icon.to_string (),
822+ add_result (result_set, uri, icon.to_string (),
823 category_id, mimetype, display_name);
824 }
825
826 timer.stop ();
827 debug ("Entry search listed %i dir matches and %i exec matches in %fms for search: %s",
828 dirs_match.size, executables_match.size, timer.elapsed ()*1000, search_string);
829-
830-
831- search.finished ();
832- }
833-
834- private class ExecSearcher: Object
835- {
836- public ExecSearcher ()
837- {
838- Object ();
839- }
840-
841- private Gee.List<string> executables;
842-
843- construct
844- {
845- executables = new Gee.ArrayList<string> ();
846- listing_status = ListingStatus.NOT_STARTED;
847- }
848-
849- // TODO: add reload
850- private async void find_system_executables ()
851- {
852- if (this.executables.size > 0)
853- return;
854-
855- foreach (var path_directory in Environment.get_variable ("PATH").split(":"))
856- {
857- var dir = File.new_for_path (path_directory);
858- try {
859- var e = yield dir.enumerate_children_async (FileAttribute.STANDARD_NAME + "," + FileAttribute.ACCESS_CAN_EXECUTE,
860- 0, Priority.DEFAULT, null);
861- while (true) {
862- var files = yield e.next_files_async (64, Priority.DEFAULT, null);
863- if (files == null)
864- break;
865-
866- foreach (var info in files) {
867- if (info.get_attribute_boolean (FileAttribute.ACCESS_CAN_EXECUTE))
868- {
869- this.executables.add (info.get_name ());
870- }
871- }
872- }
873- }
874- catch (Error err) {
875- warning("Error listing directory executables: %s\n", err.message);
876- }
877- }
878- }
879-
880- private enum ListingStatus
881- {
882- NOT_STARTED,
883- STARTED,
884- FINISHED
885- }
886-
887- public int listing_status { get; private set; }
888-
889- public async Gee.Collection<string> find_prefixed (string search_string)
890- {
891- // initialize the available binaries lazily
892- if (listing_status == ListingStatus.NOT_STARTED)
893- {
894- listing_status = ListingStatus.STARTED;
895- yield find_system_executables ();
896- listing_status = ListingStatus.FINISHED;
897- }
898- else if (listing_status == ListingStatus.STARTED)
899- {
900- var sig_id = this.notify["listing-status"].connect (() =>
901- {
902- if (listing_status == ListingStatus.FINISHED)
903- find_prefixed.callback ();
904- });
905- yield;
906- SignalHandler.disconnect (this, sig_id);
907- }
908-
909- var matching = new Gee.ArrayList<string> ();
910- foreach (var exec_candidate in executables)
911- {
912- if (exec_candidate.has_prefix (search_string))
913- {
914- matching.add (exec_candidate);
915- }
916- }
917-
918- return matching;
919- }
920 }
921
922 private string get_icon_uri_and_mimetype (string exec_string, out Icon? icon, out string? uri, out string? mimetype)
923@@ -381,7 +403,7 @@
924 AboutEntry? entry = null;
925
926 mimetype = "application/x-unity-run";
927- entry = about_entries[exec_string];
928+ entry = scope.about_entries[exec_string];
929 if (entry != null)
930 {
931 uri = UNITY_RUNNER_PREFIX + entry.exec;
932@@ -401,9 +423,10 @@
933 }
934
935 var s = exec_string.delimit ("-", '_').split (" ", 0)[0];
936- var appresults = this.daemon.appsearcher.search (@"type:Application AND exec:$s", 0,
937- Unity.Package.SearchType.EXACT,
938- Unity.Package.Sort.BY_NAME);
939+ var appresults = scope.appscope.appsearcher.search (
940+ @"type:Application AND exec:$s", 0,
941+ Unity.Package.SearchType.EXACT,
942+ Unity.Package.Sort.BY_NAME);
943 foreach (unowned Unity.Package.PackageInfo pkginfo in appresults.results)
944 {
945
946@@ -411,7 +434,7 @@
947 continue;
948
949 // pick the first one
950- icon = this.daemon.find_pkg_icon (pkginfo.desktop_file, pkginfo.icon);
951+ icon = scope.appscope.find_pkg_icon (pkginfo.desktop_file, pkginfo.icon);
952 return exec_string;
953
954 }
955@@ -421,81 +444,93 @@
956 return exec_string;
957
958 }
959-
960-
961- public void add_history (string last_command)
962- {
963-
964- // new history list: better, greatest, latest!
965- var new_history = new Gee.ArrayList<string> ();
966- var history_store = new string [this.history.size + 1];
967- int i = 1;
968-
969- new_history.add (last_command);
970- history_store[0] = last_command;
971- for (var j = 0; (j < this.history.size) && (i < MAX_HISTORY); j++)
972- {
973- if (this.history[j] == last_command)
974- continue;
975-
976- new_history.add(history[j]);
977- history_store[i] = history[j];
978- i++;
979- }
980- this.history = new_history;
981-
982- // store in gsettings
983- this.gp_settings.set_strv (HISTORY_KEY, history_store);
984-
985- // force a search to refresh history order (TODO: be more clever in the future)
986- scope.queue_search_changed (SearchType.DEFAULT);
987- }
988-
989- private void load_history ()
990- {
991- int i = 0;
992- string[] history_store = this.gp_settings.get_strv (HISTORY_KEY);
993- foreach (var command in history_store)
994- {
995- if (i >= MAX_HISTORY)
996- break;
997- this.history.add((string) command.data);
998- i++;
999- }
1000- }
1001-
1002- private void load_about_entries ()
1003- {
1004- AboutEntry entry;
1005- string name;
1006- string exec;
1007- Icon icon;
1008-
1009- // first about:config
1010- name = "about:config";
1011- exec = "ccsm -p unityshell";
1012- try {
1013- icon = Icon.new_for_string (@"$(Config.PREFIX)/share/ccsm/icons/hicolor/64x64/apps/plugin-unityshell.png");
1014- }
1015- catch (Error err) {
1016- warning ("Can't find unityshell icon: %s", err.message);
1017- icon = new ThemedIcon ("gtk-execute");
1018- }
1019- entry = new AboutEntry (name, exec, icon);
1020-
1021- about_entries[name] = entry;
1022- about_entries[exec] = entry;
1023-
1024- // second about:robots
1025- name = "Robots have a plan.";
1026- exec = "firefox about:robots";
1027- entry = new AboutEntry (name, exec, icon = new ThemedIcon ("battery"));
1028-
1029- about_entries["about:robots"] = entry;
1030- about_entries[exec] = entry;
1031-
1032- }
1033-
1034- }
1035-
1036+ }
1037+
1038+ private class ExecSearcher: Object
1039+ {
1040+ public ExecSearcher ()
1041+ {
1042+ Object ();
1043+ }
1044+
1045+ private Gee.List<string> executables;
1046+
1047+ construct
1048+ {
1049+ executables = new Gee.ArrayList<string> ();
1050+ listing_status = ListingStatus.NOT_STARTED;
1051+ }
1052+
1053+ // TODO: add reload
1054+ private async void find_system_executables ()
1055+ {
1056+ if (this.executables.size > 0)
1057+ return;
1058+
1059+ foreach (var path_directory in Environment.get_variable ("PATH").split(":"))
1060+ {
1061+ var dir = File.new_for_path (path_directory);
1062+ try {
1063+ var e = yield dir.enumerate_children_async (FileAttribute.STANDARD_NAME + "," + FileAttribute.ACCESS_CAN_EXECUTE,
1064+ 0, Priority.DEFAULT, null);
1065+ while (true) {
1066+ var files = yield e.next_files_async (64, Priority.DEFAULT, null);
1067+ if (files == null)
1068+ break;
1069+
1070+ foreach (var info in files) {
1071+ if (info.get_attribute_boolean (FileAttribute.ACCESS_CAN_EXECUTE))
1072+ {
1073+ this.executables.add (info.get_name ());
1074+ }
1075+ }
1076+ }
1077+ }
1078+ catch (Error err) {
1079+ warning("Error listing directory executables: %s\n", err.message);
1080+ }
1081+ }
1082+ }
1083+
1084+ private enum ListingStatus
1085+ {
1086+ NOT_STARTED,
1087+ STARTED,
1088+ FINISHED
1089+ }
1090+
1091+ public int listing_status { get; private set; }
1092+
1093+ public async Gee.Collection<string> find_prefixed (string search_string)
1094+ {
1095+ // initialize the available binaries lazily
1096+ if (listing_status == ListingStatus.NOT_STARTED)
1097+ {
1098+ listing_status = ListingStatus.STARTED;
1099+ yield find_system_executables ();
1100+ listing_status = ListingStatus.FINISHED;
1101+ }
1102+ else if (listing_status == ListingStatus.STARTED)
1103+ {
1104+ var sig_id = this.notify["listing-status"].connect (() =>
1105+ {
1106+ if (listing_status == ListingStatus.FINISHED)
1107+ find_prefixed.callback ();
1108+ });
1109+ yield;
1110+ SignalHandler.disconnect (this, sig_id);
1111+ }
1112+
1113+ var matching = new Gee.ArrayList<string> ();
1114+ foreach (var exec_candidate in executables)
1115+ {
1116+ if (exec_candidate.has_prefix (search_string))
1117+ {
1118+ matching.add (exec_candidate);
1119+ }
1120+ }
1121+
1122+ return matching;
1123+ }
1124+ }
1125 }
1126
1127=== modified file 'src/daemon.vala'
1128--- src/daemon.vala 2013-07-18 21:11:13 +0000
1129+++ src/daemon.vala 2013-08-06 08:19:29 +0000
1130@@ -1,5 +1,6 @@
1131+/* -*- mode: vala; c-basic-offset: 2; indent-tabs-mode: nil -*- */
1132 /*
1133- * Copyright (C) 2010 Canonical Ltd
1134+ * Copyright (C) 2010-2013 Canonical Ltd
1135 *
1136 * This program is free software: you can redistribute it and/or modify
1137 * it under the terms of the GNU General Public License version 3 as
1138@@ -42,66 +43,57 @@
1139
1140 const string ICON_PATH = Config.DATADIR + "/icons/unity-icon-theme/places/svg/";
1141 const string GENERIC_APP_ICON = "applications-other";
1142- const string GENERIC_SCOPE_ICON = ICON_PATH + "service-generic.svg";
1143
1144 const string LIBUNITY_SCHEMA = "com.canonical.Unity.Lenses";
1145
1146- public class Daemon : GLib.Object
1147+ private class ApplicationsScope : Unity.AbstractScope
1148 {
1149- private Variant empty_asv;
1150- private Zeitgeist.Log log;
1151- private Zeitgeist.Index zg_index;
1152+ public Zeitgeist.Log log;
1153+ public Zeitgeist.Index zg_index;
1154 private Zeitgeist.Monitor monitor;
1155
1156- private Map<string, int> popularity_map;
1157- private bool popularities_dirty;
1158+ public Map<string, int> popularity_map;
1159+ public bool popularities_dirty;
1160
1161 /* The searcher for online material may be null if it fails to load
1162 * the Xapian index from the Software Center */
1163- private Unity.Package.Searcher? pkgsearcher;
1164- private Unity.Package.Searcher? scopesearcher;
1165+ public Unity.Package.Searcher? pkgsearcher;
1166 public Unity.Package.Searcher appsearcher;
1167
1168 /* Read the app ratings dumped by the Software Center */
1169 private bool ratings_db_initialized = false;
1170- private Unity.Ratings.Database? ratings = null;
1171-
1172- private Unity.DeprecatedScope scope;
1173+ public Unity.Ratings.Database? ratings = null;
1174
1175 /* Support aptd dbus interface; created when application install/remove was requested by preview action */
1176 private AptdProxy aptdclient;
1177 private AptdTransactionProxy aptd_transaction;
1178
1179- private SoftwareCenterUtils.MangledDesktopFileLookup sc_mangler;
1180+ public SoftwareCenterUtils.MangledDesktopFileLookup sc_mangler;
1181
1182 /* Used for adding launcher icon on app installation from the preview */
1183 private LauncherProxy launcherservice;
1184
1185 /* Desktop file & icon name for unity-install:// install candidate displayed in last preview; we store
1186 * them here to avoid extra query for app details if app install action is activated */
1187- private string preview_installable_desktop_file;
1188- private string preview_installable_icon_file;
1189+ public string preview_installable_desktop_file;
1190+ public string preview_installable_icon_file;
1191
1192- private string preview_developer_website;
1193+ public string preview_developer_website;
1194
1195 /* SoftwareCenter data provider used for app preview details */
1196- private SoftwareCenterDataProviderProxy sc_data_provider;
1197-
1198- private Unity.ApplicationsLens.Runner runner;
1199-
1200- /* Keep references to the FilterOptions for sources */
1201- private FilterOption local_apps_option;
1202- private FilterOption usc_apps_option;
1203+ public SoftwareCenterDataProviderProxy sc_data_provider;
1204+
1205+ public Unity.ApplicationsLens.CommandsScope commands_scope;
1206
1207 private Gee.List<string> image_extensions;
1208 private HashTable<string,Icon> file_icon_cache;
1209
1210 /* Monitor the favorite apps in the launcher, so we can filter them
1211 * out of the results for Recent Apps */
1212- private Unity.LauncherFavorites favorite_apps;
1213- private AppWatcher app_watcher;
1214+ public Unity.LauncherFavorites favorite_apps;
1215+ public AppWatcher app_watcher;
1216
1217- private PtrArray zg_templates;
1218+ public PtrArray zg_templates;
1219
1220 /* Gnome menu structure - also used to check whether apps are installed */
1221 private uint app_menu_changed_reindex_timeout = 0;
1222@@ -111,7 +103,6 @@
1223 private Regex mountable_regex;
1224
1225 private Settings gp_settings;
1226- private HashTable<unowned string, unowned string> disabled_scope_ids;
1227
1228 private const string DISPLAY_RECENT_APPS_KEY = "display-recent-apps";
1229 private const string DISPLAY_AVAILABLE_APPS_KEY = "display-available-apps";
1230@@ -120,10 +111,7 @@
1231 public bool display_available_apps { get; set; default = true; }
1232 public bool force_small_icons_for_suggestions { get; set; default = true; }
1233
1234- private PurchaseInfoHelper purchase_info = null;
1235- private Dee.Model remote_scopes_model;
1236- private Dee.Index scopes_index;
1237- private Dee.Analyzer analyzer;
1238+ public PurchaseInfoHelper purchase_info = null;
1239
1240 construct
1241 {
1242@@ -131,7 +119,6 @@
1243
1244 log = new Zeitgeist.Log();
1245 zg_index = new Zeitgeist.Index();
1246- empty_asv = new Variant.array (new VariantType ("{sv}"), {});
1247 monitor = new Zeitgeist.Monitor (new Zeitgeist.TimeRange.from_now (),
1248 zg_templates);
1249 monitor.events_inserted.connect (mark_dirty);
1250@@ -168,55 +155,15 @@
1251 image_extensions.add ("jpg");
1252
1253 build_app_menu_index ();
1254- build_scope_index.begin ();
1255
1256 file_icon_cache = new HashTable<string,Icon>(str_hash, str_equal);
1257 sc_mangler = new SoftwareCenterUtils.MangledDesktopFileLookup ();
1258
1259- scope = new Unity.DeprecatedScope ("/com/canonical/unity/scope/applications",
1260- "applications");
1261-
1262- scope.search_hint = _("Search applications");
1263- scope.search_in_global = true;
1264- // scope.sources_display_name = _("Sources");
1265- // TRANSLATORS: Please make sure this string is short enough to fit
1266- // into the filter button
1267- local_apps_option = scope.sources.add_option ("local", _("Local apps"));
1268- if (display_available_apps)
1269- {
1270- // TRANSLATORS: Please make sure this string is short enough to fit
1271- // into the filter button
1272- usc_apps_option = scope.sources.add_option ("usc", _("Software center"));
1273- }
1274-
1275- populate_categories ();
1276- populate_filters();
1277- //scope.icon = @"$(Config.PREFIX)/share/unity/themes/applications.png";
1278-
1279- scope.generate_search_key.connect ((lens_search) =>
1280- {
1281- return lens_search.search_string.strip ();
1282- });
1283- /* Listen for changes to the lens scope search */
1284- scope.search_changed.connect ((lens_search, search_type, cancellable) =>
1285- {
1286- dispatch_search.begin (lens_search, search_type, cancellable);
1287- });
1288-
1289- /* Re-do the search if the sources change */
1290- scope.active_sources_changed.connect (() =>
1291- {
1292- scope.queue_search_changed (SearchType.DEFAULT);
1293- });
1294-
1295- scope.activate_uri.connect (activate);
1296- scope.preview_uri.connect (preview);
1297-
1298 /* Listen for changes in the installed applications */
1299 AppInfoManager.get_default().changed.connect (mark_dirty);
1300
1301 /* Now start the RunEntry */
1302- runner = new Unity.ApplicationsLens.Runner (this);
1303+ commands_scope = new Unity.ApplicationsLens.CommandsScope (this);
1304
1305 try {
1306 uri_regex = new Regex ("^[a-z]+:.+$");
1307@@ -234,46 +181,9 @@
1308
1309 aptdclient = new AptdProxy ();
1310 launcherservice = new LauncherProxy ();
1311-
1312- try
1313- {
1314- scope.export ();
1315- }
1316- catch (Error err)
1317- {
1318- error ("Unable to export scope: %s", err.message);
1319- }
1320-
1321- remote_scopes_model = new Dee.SharedModel ("com.canonical.Unity.SmartScopes.RemoteScopesModel");
1322- remote_scopes_model.set_schema ("s", "s", "s", "s", "s", "as");
1323-
1324- scopes_index = Utils.prepare_index (remote_scopes_model,
1325- RemoteScopesColumn.NAME,
1326- (model, iter) =>
1327- {
1328- unowned string name = model.get_string (iter, RemoteScopesColumn.NAME);
1329- return "%s\n%s".printf (_("scope"), name);
1330- }, out analyzer);
1331-
1332- disabled_scope_ids = new HashTable<unowned string, unowned string> (str_hash, str_equal);
1333- update_disabled_scopes_hash ();
1334-
1335- var pref_man = PreferencesManager.get_default ();
1336- pref_man.notify["disabled-scopes"].connect (update_disabled_scopes_hash);
1337- }
1338-
1339- private void update_disabled_scopes_hash ()
1340- {
1341- disabled_scope_ids.remove_all ();
1342- var pref_man = PreferencesManager.get_default ();
1343- foreach (unowned string scope_id in pref_man.disabled_scopes)
1344- {
1345- // using HashTable as a set (optimized in glib when done like this)
1346- disabled_scope_ids[scope_id] = scope_id;
1347- }
1348- }
1349-
1350- private void init_ratings_db ()
1351+ }
1352+
1353+ public void init_ratings_db ()
1354 {
1355 if (ratings_db_initialized) return;
1356 try
1357@@ -288,35 +198,19 @@
1358 ratings_db_initialized = true;
1359 }
1360
1361- private async void dispatch_search (DeprecatedScopeSearch search,
1362- SearchType search_type,
1363- GLib.Cancellable cancellable)
1364- {
1365- try
1366- {
1367- if (popularities_dirty)
1368- {
1369- popularities_dirty = false;
1370- // we're not passing the cancellable, cause cancelling this search
1371- // shouldn't cancel getting most popular apps
1372- yield update_popularities ();
1373- if (cancellable.is_cancelled ()) return;
1374- }
1375-
1376- if (search_type == SearchType.DEFAULT)
1377- yield update_scope_search (search, cancellable);
1378- else
1379- yield update_global_search (search, cancellable);
1380- }
1381- finally
1382- {
1383- search.finished ();
1384- }
1385- }
1386-
1387- private void populate_categories ()
1388- {
1389- Unity.CategorySet categories = new Unity.CategorySet ();
1390+ public override string get_group_name ()
1391+ {
1392+ return "com.canonical.Unity.Scope.Applications";
1393+ }
1394+
1395+ public override string get_unique_name ()
1396+ {
1397+ return "/com/canonical/unity/scope/applications";
1398+ }
1399+
1400+ public override Unity.CategorySet get_categories ()
1401+ {
1402+ var categories = new Unity.CategorySet ();
1403 File icon_dir = File.new_for_path (ICON_PATH);
1404
1405 var cat = new Unity.Category ("apps", _("Applications"),
1406@@ -339,12 +233,12 @@
1407 new FileIcon (icon_dir.get_child ("group-treat-yourself.svg")));
1408 categories.add (cat);
1409
1410- scope.categories = categories;
1411+ return categories;
1412 }
1413
1414- private void populate_filters()
1415+ public override Unity.FilterSet get_filters()
1416 {
1417- Unity.FilterSet filters = new Unity.FilterSet ();
1418+ var filters = new Unity.FilterSet ();
1419
1420 /* Type filter */
1421 {
1422@@ -369,23 +263,310 @@
1423 filters.add (filter);
1424 }
1425
1426- scope.filters = filters;
1427- }
1428-
1429- private bool local_apps_active (DeprecatedScopeSearch search)
1430- {
1431- var filter = search.get_filter (scope.sources.id) as Unity.OptionsFilter;
1432- if (filter.filtering && local_apps_option != null)
1433- return filter.get_option (local_apps_option.id).active;
1434- return true;
1435- }
1436-
1437- private bool usc_apps_active (DeprecatedScopeSearch search)
1438- {
1439- var filter = search.get_filter (scope.sources.id) as Unity.OptionsFilter;
1440- if (filter.filtering && usc_apps_option != null)
1441- return filter.get_option (usc_apps_option.id).active;
1442- return true;
1443+ /* Sources */
1444+ {
1445+ var filter = new CheckOptionFilter ("unity-sources", _("Sources"));
1446+ filter.sort_type = Unity.OptionsFilter.SortType.DISPLAY_NAME;
1447+
1448+ // TRANSLATORS: Please make sure this string is short enough to fit
1449+ // into the filter button
1450+ filter.add_option ("local", _("Local apps"));
1451+ if (display_available_apps)
1452+ {
1453+ // TRANSLATORS: Please make sure this string is short enough to fit
1454+ // into the filter button
1455+ filter.add_option ("usc", _("Software center"));
1456+ }
1457+
1458+ filters.add (filter);
1459+ }
1460+
1461+ return filters;
1462+ }
1463+
1464+ public override Unity.Schema get_schema ()
1465+ {
1466+ var schema = new Unity.Schema ();
1467+ return schema;
1468+ }
1469+
1470+ public override string get_search_hint ()
1471+ {
1472+ return _("Search applications");
1473+ }
1474+
1475+ public override string normalize_search_query (string search_query)
1476+ {
1477+ return search_query.strip ();
1478+ }
1479+
1480+ public override Unity.ScopeSearchBase create_search_for_query (Unity.SearchContext search_context)
1481+ {
1482+ return new ApplicationsSearch (this, search_context);
1483+ }
1484+
1485+ public override Unity.ResultPreviewer create_previewer (Unity.ScopeResult result, Unity.SearchMetadata metadata)
1486+ {
1487+ return new ApplicationsResultPreviewer (this, result, metadata);
1488+ }
1489+
1490+ public override Unity.ActivationResponse? activate (Unity.ScopeResult result, Unity.SearchMetadata metadata, string? action_id)
1491+ {
1492+ if (action_id == "buy")
1493+ return start_software_center (result.uri);
1494+ else if (action_id == "install")
1495+ return app_preview_install (result.uri);
1496+ else if (action_id == "install-paid")
1497+ return start_software_center (result.uri);
1498+ else if (action_id == "uninstall")
1499+ return app_preview_uninstall (result.uri);
1500+ else if (action_id == "website")
1501+ return app_preview_website (result.uri);
1502+ else
1503+ return app_launch (result, metadata);
1504+ }
1505+
1506+ private Unity.ActivationResponse app_launch (Unity.ScopeResult result, Unity.SearchMetadata metadata)
1507+ {
1508+ string[] args;
1509+ string exec_or_dir = null;
1510+ if (result.uri.has_prefix ("unity-install://"))
1511+ {
1512+ var prv = create_previewer (result, metadata).run ();
1513+ if (prv is Unity.Preview)
1514+ {
1515+ return new Unity.ActivationResponse.with_preview (
1516+ prv as Unity.Preview);
1517+ }
1518+ else
1519+ {
1520+ warning ("Failed to generate preview for %s", result.uri);
1521+ return new Unity.ActivationResponse (Unity.HandledType.NOT_HANDLED);
1522+ }
1523+ }
1524+ else if (result.uri.has_prefix (UNITY_RUNNER_PREFIX))
1525+ {
1526+ string orig;
1527+ orig = result.uri.offset (UNITY_RUNNER_PREFIX.length);
1528+ if (orig.has_prefix("\\\\"))
1529+ orig = orig.replace ("\\\\","smb://");
1530+ if (uri_regex != null && uri_regex.match (orig)) {
1531+ try {
1532+ /* this code ensures that a file manager will be used
1533+ * if uri it's a remote location that should be mounted */
1534+ if (mountable_regex.match (orig)) {
1535+ var muris = new GLib.List<string>();
1536+ muris.prepend (orig);
1537+ var file_manager = AppInfo.get_default_for_type("inode/directory", true);
1538+ file_manager.launch_uris(muris,null);
1539+ } else {
1540+ AppInfo.launch_default_for_uri (orig, null);
1541+ }
1542+ } catch (GLib.Error error) {
1543+ warning ("Failed to launch URI %s", orig);
1544+ return new Unity.ActivationResponse(Unity.HandledType.NOT_HANDLED);
1545+ }
1546+ return new Unity.ActivationResponse(Unity.HandledType.HIDE_DASH);
1547+
1548+ } else {
1549+ exec_or_dir = Utils.subst_tilde (orig);
1550+ args = exec_or_dir.split (" ", 0);
1551+ for (int i = 0; i < args.length; i++)
1552+ args[i] = Utils.subst_tilde (args[i]);
1553+ }
1554+ this.commands_scope.add_history (orig);
1555+ }
1556+ else
1557+ {
1558+ /* Activation of standard application:// uris */
1559+
1560+ /* Make sure fresh install learns quickly */
1561+ if (popularity_map.size <= 5) popularities_dirty = true;
1562+
1563+ return new Unity.ActivationResponse(Unity.HandledType.NOT_HANDLED);
1564+ }
1565+
1566+ if ((exec_or_dir != null) && FileUtils.test (exec_or_dir, FileTest.IS_DIR))
1567+ {
1568+ try {
1569+ AppInfo.launch_default_for_uri ("file://" + exec_or_dir, null);
1570+ } catch (GLib.Error err) {
1571+ warning ("Failed to open current folder '%s' in file manager: %s",
1572+ exec_or_dir, err.message);
1573+ return new Unity.ActivationResponse(Unity.HandledType.NOT_HANDLED);
1574+ }
1575+ }
1576+ else
1577+ {
1578+ try {
1579+ unowned string home_dir = GLib.Environment.get_home_dir ();
1580+ Process.spawn_async (home_dir, args, null, SpawnFlags.SEARCH_PATH, null, null);
1581+ } catch (SpawnError e) {
1582+ warning ("Failed to spawn software-center or direct URI activation '%s': %s",
1583+ result.uri, e.message);
1584+ return new Unity.ActivationResponse(Unity.HandledType.NOT_HANDLED);
1585+ }
1586+ }
1587+
1588+ return new Unity.ActivationResponse(Unity.HandledType.HIDE_DASH);
1589+ }
1590+
1591+ private async void call_install_packages (string package_name, out string tid) throws IOError
1592+ {
1593+ tid = yield aptdclient.install_packages ({package_name});
1594+ }
1595+
1596+ private async void call_remove_packages (string package_name, out string tid) throws IOError
1597+ {
1598+ tid = yield aptdclient.remove_packages ({package_name});
1599+ }
1600+
1601+ /**
1602+ * Handler for free apps installation.
1603+ * Triggers package installation via apt-daemon DBus service
1604+ */
1605+ private Unity.ActivationResponse app_preview_install (string uri)
1606+ {
1607+ if (uri.has_prefix ("unity-install://"))
1608+ {
1609+ string app = uri.substring (16); // trim "unity-install://"
1610+ string[] parts = app.split ("/");
1611+
1612+ if (parts.length > 1)
1613+ {
1614+ string pkgname = parts[0];
1615+ string appname = parts[1];
1616+ try
1617+ {
1618+ aptdclient.connect_to_aptd ();
1619+ }
1620+ catch (IOError e)
1621+ {
1622+ warning ("Failed to connect to aptd: '%s'", e.message);
1623+ return new Unity.ActivationResponse(Unity.HandledType.NOT_HANDLED);
1624+ }
1625+ call_install_packages.begin (pkgname, (obj, res) =>
1626+ {
1627+ try {
1628+ string tid;
1629+ call_install_packages.end (res, out tid);
1630+ debug ("transaction started: %s, pkg: %s\n", tid, pkgname);
1631+ aptd_transaction = new AptdTransactionProxy ();
1632+ aptd_transaction.connect_to_aptd (tid);
1633+ aptd_transaction.simulate ();
1634+ aptd_transaction.run ();
1635+
1636+ launcherservice.connect_to_launcher ();
1637+ string desktop_file = preview_installable_desktop_file;
1638+ Icon icon = find_pkg_icon (null, preview_installable_icon_file);
1639+ launcherservice.add_launcher_item_from_position.begin (appname, icon.to_string (), 0, 0, 32, desktop_file, tid);
1640+ }
1641+ catch (IOError e)
1642+ {
1643+ warning ("Package '%s' installation failed: %s", pkgname, e.message);
1644+ }
1645+ });
1646+ }
1647+ else
1648+ {
1649+ warning ("Bad install uri: '%s'", uri);
1650+ }
1651+ }
1652+ else
1653+ {
1654+ warning ("Can't handle '%s' in app_preview_install handler", uri);
1655+ return new Unity.ActivationResponse(Unity.HandledType.NOT_HANDLED);
1656+ }
1657+ return new Unity.ActivationResponse(Unity.HandledType.HIDE_DASH);
1658+ }
1659+
1660+ private Unity.ActivationResponse start_software_center (string uri)
1661+ {
1662+ unowned string pkg = uri.offset (16); // strip off "unity-install://" prefix
1663+ debug ("Installing: %s", pkg);
1664+
1665+ var args = new string[2];
1666+ args[0] = "software-center";
1667+ args[1] = pkg;
1668+
1669+ try
1670+ {
1671+ Process.spawn_async (GLib.Environment.get_home_dir (), args, null, SpawnFlags.SEARCH_PATH, null, null);
1672+ }
1673+ catch (SpawnError e)
1674+ {
1675+ warning ("Failed to spawn software-center for uri '%s': %s", uri, e.message);
1676+ return new Unity.ActivationResponse(Unity.HandledType.NOT_HANDLED);
1677+ }
1678+
1679+ return new Unity.ActivationResponse(Unity.HandledType.HIDE_DASH);
1680+ }
1681+
1682+ private Unity.ActivationResponse app_preview_website (string uri)
1683+ {
1684+ try
1685+ {
1686+ AppInfo.launch_default_for_uri (preview_developer_website, null);
1687+ return new Unity.ActivationResponse(Unity.HandledType.HIDE_DASH);
1688+ }
1689+ catch (Error e)
1690+ {
1691+ warning ("Failed to launch a web browser for uri '%s': '%s'", uri, e.message);
1692+ }
1693+ return new Unity.ActivationResponse(Unity.HandledType.NOT_HANDLED);
1694+ }
1695+
1696+ private Unity.ActivationResponse app_preview_uninstall (string uri)
1697+ {
1698+ if (uri.has_prefix ("application://"))
1699+ {
1700+ string desktopfile = uri.substring (14); // trim "application://"
1701+
1702+ // de-mangle desktop file names back to what S-C expects
1703+ if (sc_mangler.contains (desktopfile))
1704+ {
1705+ desktopfile = sc_mangler.get (desktopfile);
1706+ }
1707+
1708+ var pkginfo = pkgsearcher.get_by_desktop_file (desktopfile);
1709+
1710+ if (pkginfo != null && pkginfo.package_name != null)
1711+ {
1712+ try
1713+ {
1714+ aptdclient.connect_to_aptd ();
1715+ }
1716+ catch (IOError e)
1717+ {
1718+ warning ("Failed to connect to aptd: '%s'", e.message);
1719+ return new Unity.ActivationResponse(Unity.HandledType.NOT_HANDLED);
1720+ }
1721+
1722+ call_remove_packages.begin (pkginfo.package_name, (obj, res) =>
1723+ {
1724+ try {
1725+ string tid;
1726+ call_remove_packages.end (res, out tid);
1727+ debug ("transaction started: %s, pkg: %s\n", tid, pkginfo.package_name);
1728+ aptd_transaction = new AptdTransactionProxy ();
1729+ aptd_transaction.connect_to_aptd (tid);
1730+ aptd_transaction.simulate ();
1731+ aptd_transaction.run ();
1732+ }
1733+ catch (IOError e)
1734+ {
1735+ warning (@"Package '$(pkginfo.package_name)' removal failed: $(e.message)");
1736+ }
1737+ });
1738+ return new Unity.ActivationResponse(Unity.HandledType.HIDE_DASH);
1739+ }
1740+ else
1741+ {
1742+ warning (@"Cannot find package info for $uri");
1743+ }
1744+ }
1745+ warning (@"Can't handle '%s' in app_preview_uninstall handler", uri);
1746+ return new Unity.ActivationResponse(Unity.HandledType.NOT_HANDLED);
1747 }
1748
1749 /* Load xdg menu info and build a Xapian index over it.
1750@@ -447,22 +628,6 @@
1751 return false;
1752 }
1753
1754- private async void build_scope_index ()
1755- {
1756- try
1757- {
1758- var scope_registry = yield Unity.Protocol.ScopeRegistry.find_scopes (
1759- Config.PKGDATADIR + "/scopes");
1760-
1761- debug ("Indexing scopes");
1762- scopesearcher = new Unity.Package.Searcher.for_scopes (scope_registry);
1763- }
1764- catch (Error err)
1765- {
1766- warning ("Unable to find scopes: %s", err.message);
1767- }
1768- }
1769-
1770 private void populate_zg_templates ()
1771 {
1772 /* Create a template that activation of applications */
1773@@ -477,11 +642,11 @@
1774
1775 private void mark_dirty ()
1776 {
1777- scope.queue_search_changed (SearchType.DEFAULT);
1778- scope.queue_search_changed (SearchType.GLOBAL);
1779+ results_invalidated (SearchType.DEFAULT);
1780+ results_invalidated (SearchType.GLOBAL);
1781 }
1782
1783- private async void update_popularities ()
1784+ public async void update_popularities ()
1785 {
1786 try
1787 {
1788@@ -515,6 +680,189 @@
1789 }
1790 }
1791
1792+ public Icon find_pkg_icon (string? desktop_file, string icon_name)
1793+ {
1794+ if (desktop_file != null)
1795+ {
1796+ string desktop_id = Path.get_basename (desktop_file);
1797+ bool installed = AppInfoManager.get_default().lookup (desktop_id) != null;
1798+
1799+ /* If the app is already installed we should be able to pull the
1800+ * icon from the theme */
1801+ if (installed)
1802+ return new ThemedIcon (icon_name);
1803+ }
1804+
1805+ /* App is not installed - we need to find the right icon in the bowels
1806+ * of the software center */
1807+ if (icon_name.has_prefix ("/"))
1808+ {
1809+ return new FileIcon (File.new_for_path (icon_name));
1810+ }
1811+ else
1812+ {
1813+ Icon icon = file_icon_cache.lookup (icon_name);
1814+
1815+ if (icon != null)
1816+ return icon;
1817+
1818+ /* If the icon name contains a . it probably already have a
1819+ * type postfix - so test icon name directly */
1820+ string path;
1821+ if ("." in icon_name)
1822+ {
1823+ path = @"$(Config.DATADIR)/app-install/icons/$(icon_name)";
1824+ if (FileUtils.test (path, FileTest.EXISTS))
1825+ {
1826+ icon = new FileIcon (File.new_for_path (path));
1827+ file_icon_cache.insert (icon_name, icon);
1828+ return icon;
1829+ }
1830+ /* Try also software center cache dir */
1831+ path = Path.build_filename (Environment.get_user_cache_dir (),
1832+ "software-center",
1833+ "icons",
1834+ icon_name);
1835+ if (FileUtils.test (path, FileTest.EXISTS))
1836+ {
1837+ icon = new FileIcon (File.new_for_path (path));
1838+ file_icon_cache.insert (icon_name, icon);
1839+ return icon;
1840+ }
1841+ }
1842+
1843+ /* Now try appending all the image extensions we know */
1844+ foreach (var ext in image_extensions)
1845+ {
1846+ path = @"$(Config.DATADIR)/app-install/icons/$(icon_name).$(ext)";
1847+ if (FileUtils.test (path, FileTest.EXISTS))
1848+ {
1849+ /* Got it! Cache the icon path and return the icon */
1850+ icon = new FileIcon (File.new_for_path (path));
1851+ file_icon_cache.insert (icon_name, icon);
1852+ return icon;
1853+ }
1854+ }
1855+ }
1856+
1857+ /* Cache the fact that we couldn't find this icon */
1858+ var icon = new ThemedIcon (GENERIC_APP_ICON);
1859+ file_icon_cache.insert (icon_name, icon);
1860+
1861+ return icon;
1862+ }
1863+
1864+ public async SoftwareCenterData.AppDetailsData get_app_details (string appname, string pkgname) throws Error
1865+ {
1866+ if (sc_data_provider == null)
1867+ {
1868+ sc_data_provider = new SoftwareCenterDataCache (TOP_RATED_ITEMS_CACHE_LIFETIME);
1869+ }
1870+
1871+ debug ("Requesting pkg info: %s, %s\n", pkgname, appname);
1872+ return yield sc_data_provider.get_app_details (appname, pkgname);
1873+ }
1874+
1875+ public async bool get_version_and_screenshot (string app_id, out string version, out string screenshot)
1876+ {
1877+ version = null;
1878+ screenshot = null;
1879+ // de-mangle desktop file names back to what S-C expects
1880+ string mangled_id;
1881+ if (sc_mangler.contains (app_id))
1882+ {
1883+ mangled_id = sc_mangler.get (app_id);
1884+ }
1885+ else
1886+ {
1887+ mangled_id = app_id;
1888+ }
1889+ var pkginfo = pkgsearcher.get_by_desktop_file (mangled_id);
1890+ if (pkginfo != null)
1891+ {
1892+ SoftwareCenterData.AppDetailsData? details;
1893+ try {
1894+ details = yield get_app_details(pkginfo.application_name,
1895+ pkginfo.package_name);
1896+ } catch (Error e) {
1897+ return false;
1898+ }
1899+ if (details != null) {
1900+ version = details.version;
1901+ screenshot = details.screenshot;
1902+ return true;
1903+ }
1904+ }
1905+ return false;
1906+ }
1907+ }
1908+
1909+ private class ApplicationsSearch : Unity.ScopeSearchBase
1910+ {
1911+ private ApplicationsScope scope;
1912+
1913+ public ApplicationsSearch (ApplicationsScope scope, Unity.SearchContext search_context)
1914+ {
1915+ this.scope = scope;
1916+ set_search_context (search_context);
1917+ }
1918+
1919+ public override void run ()
1920+ {
1921+ var ml = new MainLoop ();
1922+ run_async (() => { ml.quit (); });
1923+ ml.run ();
1924+ }
1925+
1926+ public override void run_async (Unity.ScopeSearchBaseCallback async_callback)
1927+ {
1928+ dispatch_search.begin (
1929+ () => {
1930+ async_callback (this);
1931+ });
1932+ }
1933+
1934+ private async void dispatch_search ()
1935+ {
1936+ var context = this.search_context;
1937+
1938+ if (scope.popularities_dirty)
1939+ {
1940+ scope.popularities_dirty = false;
1941+ // we're not passing the cancellable, cause cancelling this search
1942+ // shouldn't cancel getting most popular apps
1943+ yield scope.update_popularities ();
1944+ if (context.cancellable.is_cancelled ()) return;
1945+ }
1946+
1947+ if (context.search_type == SearchType.DEFAULT)
1948+ yield search_default ();
1949+ else
1950+ yield search_global ();
1951+ }
1952+
1953+ private bool local_apps_active ()
1954+ {
1955+ var filter = search_context.filter_state.get_filter_by_id ("unity-sources") as Unity.OptionsFilter;
1956+ if (filter.filtering)
1957+ {
1958+ var option = filter.get_option ("local");
1959+ return option == null || option.active;
1960+ }
1961+ return true;
1962+ }
1963+
1964+ private bool usc_apps_active ()
1965+ {
1966+ var filter = search_context.filter_state.get_filter_by_id ("unity-sources") as Unity.OptionsFilter;
1967+ if (filter.filtering)
1968+ {
1969+ var option = filter.get_option ("usc");
1970+ return option == null || option.active;
1971+ }
1972+ return true;
1973+ }
1974+
1975 /* Returns TRUE if application is NOT installed */
1976 public bool filter_cb (Unity.Package.PackageInfo pkginfo)
1977 {
1978@@ -523,41 +871,33 @@
1979 return app == null;
1980 }
1981
1982- private bool is_phablet_ui (DeprecatedScopeSearch search)
1983+ private bool is_phablet_ui ()
1984 {
1985- // XXX: this will need changing when the hint is finalised.
1986- unowned Variant? v = search.hints["form-factor"];
1987- if (v != null)
1988- {
1989- string form_factor = v.get_string();
1990- return form_factor == "phone" || form_factor == "tablet";
1991- }
1992- return false;
1993+ var form_factor = search_context.search_metadata.form_factor;
1994+
1995+ return form_factor == "phone" || form_factor == "tablet";
1996 }
1997
1998- private async void update_scope_search (DeprecatedScopeSearch search,
1999- GLib.Cancellable cancellable)
2000+ private async void search_default ()
2001 {
2002- var model = search.results_model;
2003+ var context = this.search_context;
2004+ var result_set = context.result_set;
2005 /* We'll clear the model once we finish waiting for the dbus-call
2006 * to finish, to prevent flicker. */
2007
2008- var search_string = search.search_string.strip ();
2009+ var search_string = context.search_query;
2010 debug ("Searching for: %s", search_string);
2011
2012- var type_filter = search.get_filter ("type") as OptionsFilter;
2013+ var type_filter = context.filter_state.get_filter_by_id ("type") as OptionsFilter;
2014
2015 string pkg_search_string = XapianUtils.prepare_pkg_search_string (search_string, type_filter);
2016
2017 bool has_filter = (type_filter != null && type_filter.filtering);
2018 bool has_search = !Utils.is_search_empty (search_string);
2019- bool running_on_phablet = is_phablet_ui (search);
2020+ bool running_on_phablet = is_phablet_ui ();
2021
2022 Timer timer = new Timer ();
2023
2024- var transaction = new Dee.Transaction (model);
2025- transaction.clear ();
2026-
2027 /* Even though the Installed apps search is super fast, we wait here
2028 * for the Most Popular search to finish, because otherwise we'll update
2029 * the Installed category too soon and this will cause flicker
2030@@ -565,16 +905,16 @@
2031
2032 Set<string> installed_uris = new HashSet<string> ();
2033 Set<string> available_uris = new HashSet<string> ();
2034- var appresults = appsearcher.search (pkg_search_string, 0,
2035- Unity.Package.SearchType.PREFIX,
2036- has_search ?
2037- Unity.Package.Sort.BY_RELEVANCY :
2038- Unity.Package.Sort.BY_NAME);
2039- if (local_apps_active (search))
2040+ var appresults = scope.appsearcher.search (
2041+ pkg_search_string, 0, Unity.Package.SearchType.PREFIX,
2042+ has_search ?
2043+ Unity.Package.Sort.BY_RELEVANCY :
2044+ Unity.Package.Sort.BY_NAME);
2045+ if (local_apps_active ())
2046 {
2047 if (has_search) resort_pkg_search_results (appresults);
2048 add_pkg_search_result (appresults, installed_uris, available_uris,
2049- transaction, Category.INSTALLED,
2050+ result_set, Category.INSTALLED,
2051 0, running_on_phablet);
2052 }
2053
2054@@ -582,7 +922,7 @@
2055 debug ("Entry search listed %i Installed apps in %fms for query: %s",
2056 appresults.num_hits, timer.elapsed ()*1000, pkg_search_string);
2057
2058- if (local_apps_active (search) && display_recent_apps)
2059+ if (local_apps_active () && scope.display_recent_apps)
2060 {
2061 try
2062 {
2063@@ -592,15 +932,16 @@
2064 * the search query */
2065 var zg_search_string = XapianUtils.prepare_zg_search_string ("", type_filter);
2066
2067- var results = yield zg_index.search (zg_search_string,
2068- new Zeitgeist.TimeRange.anytime(),
2069- zg_templates,
2070- 0,
2071- 20,
2072- Zeitgeist.ResultType.MOST_RECENT_SUBJECTS,
2073- cancellable);
2074+ var results = yield scope.zg_index.search (
2075+ zg_search_string,
2076+ new Zeitgeist.TimeRange.anytime (),
2077+ scope.zg_templates,
2078+ 0,
2079+ 20,
2080+ Zeitgeist.ResultType.MOST_RECENT_SUBJECTS,
2081+ context.cancellable.get_gcancellable ());
2082
2083- append_events_with_category (results, transaction, Category.RECENT,
2084+ append_events_with_category (results, result_set, Category.RECENT,
2085 false, 6, installed_uris);
2086
2087 timer.stop ();
2088@@ -616,36 +957,21 @@
2089 }
2090 }
2091
2092- // FIXME: use the new API and ResultSet.flush () instead
2093- try
2094- {
2095- transaction.commit ();
2096- }
2097- catch (Error err)
2098- {
2099- warning ("Unable to commit transaction: %s", err.message);
2100- }
2101-
2102- // add scopes (if filter is active)
2103- if (!has_filter || type_filter.get_option ("scopes").active)
2104- {
2105- add_local_scopes_results (search_string, model, installed_uris);
2106- add_remote_scopes_results (search_string, model);
2107- }
2108-
2109- purchase_info = new PurchaseInfoHelper ();
2110+ result_set.flush ();
2111+
2112+ scope.purchase_info = new PurchaseInfoHelper ();
2113
2114 /* If we don't have a search we display 6 random apps */
2115- if (usc_apps_active (search) && display_available_apps && pkgsearcher != null)
2116+ if (usc_apps_active () && scope.display_available_apps && scope.pkgsearcher != null)
2117 {
2118 if (has_search)
2119 {
2120 timer.start ();
2121- var pkgresults = pkgsearcher.search (pkg_search_string, 50,
2122- Unity.Package.SearchType.PREFIX,
2123- Unity.Package.Sort.BY_RELEVANCY);
2124+ var pkgresults = scope.pkgsearcher.search (
2125+ pkg_search_string, 50, Unity.Package.SearchType.PREFIX,
2126+ Unity.Package.Sort.BY_RELEVANCY);
2127 add_pkg_search_result (pkgresults, installed_uris, available_uris,
2128- model, Category.AVAILABLE,
2129+ result_set, Category.AVAILABLE,
2130 0, running_on_phablet);
2131 timer.stop ();
2132 debug ("Entry search listed %i Available apps in %fms for query: %s",
2133@@ -656,9 +982,9 @@
2134 timer.start ();
2135 string? filter_query = XapianUtils.prepare_pkg_search_string (search_string, type_filter);
2136
2137- var pkgresults = pkgsearcher.get_apps (filter_query, MAX_APP_FOR_DOWNLOAD_FOR_EMPTY_QUERY, filter_cb);
2138- purchase_info.from_pkgresults (pkgresults);
2139- add_pkg_search_result (pkgresults, installed_uris, available_uris, model, Category.AVAILABLE, MAX_APP_FOR_DOWNLOAD_FOR_EMPTY_QUERY, running_on_phablet);
2140+ var pkgresults = scope.pkgsearcher.get_apps (filter_query, MAX_APP_FOR_DOWNLOAD_FOR_EMPTY_QUERY, filter_cb);
2141+ scope.purchase_info.from_pkgresults (pkgresults);
2142+ add_pkg_search_result (pkgresults, installed_uris, available_uris, result_set, Category.AVAILABLE, MAX_APP_FOR_DOWNLOAD_FOR_EMPTY_QUERY, running_on_phablet);
2143 timer.stop ();
2144 debug ("Entry search listed %i Available apps in %fms",
2145 pkgresults.num_hits, timer.elapsed ()*1000);
2146@@ -672,20 +998,20 @@
2147 {
2148 Set<string> duplicates_lookup = new HashSet<string> ();
2149
2150- if (sc_data_provider == null)
2151- sc_data_provider = new SoftwareCenterDataCache (TOP_RATED_ITEMS_CACHE_LIFETIME);
2152-
2153- var whats_new = sc_data_provider.get_items_for_category ("unity-whats-new");
2154- var query = purchase_info.create_pkgsearch_query (whats_new);
2155- var tmpresults = pkgsearcher.get_by_exact_names (query);
2156- purchase_info.from_pkgresults (tmpresults);
2157- hits = add_sc_category_results (whats_new, model, Category.AVAILABLE, ref duplicates_lookup, MAX_WHATS_NEW_APPS_FOR_EMPTY_QUERY);
2158-
2159- var top_rated = sc_data_provider.get_items_for_category ("unity-top-rated");
2160- query = purchase_info.create_pkgsearch_query (top_rated);
2161- tmpresults = pkgsearcher.get_by_exact_names (query);
2162- purchase_info.from_pkgresults (tmpresults);
2163- hits += add_sc_category_results (top_rated, model, Category.AVAILABLE, ref duplicates_lookup, MAX_TOP_RATED_APPS_FOR_EMPTY_QUERY);
2164+ if (scope.sc_data_provider == null)
2165+ scope.sc_data_provider = new SoftwareCenterDataCache (TOP_RATED_ITEMS_CACHE_LIFETIME);
2166+
2167+ var whats_new = yield scope.sc_data_provider.get_items_for_category ("unity-whats-new");
2168+ var query = scope.purchase_info.create_pkgsearch_query (whats_new);
2169+ var tmpresults = scope.pkgsearcher.get_by_exact_names (query);
2170+ scope.purchase_info.from_pkgresults (tmpresults);
2171+ hits = add_sc_category_results (whats_new, result_set, Category.AVAILABLE, ref duplicates_lookup, MAX_WHATS_NEW_APPS_FOR_EMPTY_QUERY);
2172+
2173+ var top_rated = yield scope.sc_data_provider.get_items_for_category ("unity-top-rated");
2174+ query = scope.purchase_info.create_pkgsearch_query (top_rated);
2175+ tmpresults = scope.pkgsearcher.get_by_exact_names (query);
2176+ scope.purchase_info.from_pkgresults (tmpresults);
2177+ hits += add_sc_category_results (top_rated, result_set, Category.AVAILABLE, ref duplicates_lookup, MAX_TOP_RATED_APPS_FOR_EMPTY_QUERY);
2178 }
2179 catch (GLib.Error e)
2180 {
2181@@ -698,61 +1024,63 @@
2182 }
2183 }
2184
2185+ /* XXX:
2186 if (model.get_n_rows () == 0)
2187 {
2188 search.set_reply_hint ("no-results-hint",
2189 _("Sorry, there are no applications that match your search."));
2190 }
2191+ */
2192 }
2193
2194- private async void update_global_search (DeprecatedScopeSearch search,
2195- GLib.Cancellable cancellable)
2196+ private async void search_global ()
2197 {
2198+ var context = this.search_context;
2199+ var result_set = context.result_set;
2200 /*
2201 * In global search, with a non-empty search string, we collate all
2202 * hits under one Applications category
2203 */
2204- var search_string = search.search_string.strip ();
2205+ var search_string = context.search_query;
2206
2207 if (Utils.is_search_empty (search_string))
2208 {
2209- yield update_global_without_search (search, cancellable);
2210+ yield update_global_without_search ();
2211 return;
2212 }
2213
2214- bool running_on_phablet = is_phablet_ui (search);
2215- var model = search.results_model;
2216-
2217- model.clear ();
2218+ bool running_on_phablet = is_phablet_ui ();
2219
2220 var pkg_search_string = XapianUtils.prepare_pkg_search_string (search_string, null);
2221 Set<string> installed_uris = new HashSet<string> ();
2222 Set<string> available_uris = new HashSet<string> ();
2223 var timer = new Timer ();
2224- var appresults = appsearcher.search (pkg_search_string, 0,
2225- Unity.Package.SearchType.PREFIX,
2226- Unity.Package.Sort.BY_RELEVANCY);
2227+ var appresults = scope.appsearcher.search (
2228+ pkg_search_string, 0,
2229+ Unity.Package.SearchType.PREFIX,
2230+ Unity.Package.Sort.BY_RELEVANCY);
2231 resort_pkg_search_results (appresults);
2232- add_pkg_search_result (appresults, installed_uris, available_uris, model,
2233- Category.APPLICATIONS, 0, running_on_phablet);
2234+ add_pkg_search_result (appresults, installed_uris, available_uris,
2235+ result_set, Category.APPLICATIONS, 0,
2236+ running_on_phablet);
2237
2238 timer.stop ();
2239 debug ("Global search listed %i Installed apps in %fms for query: %s",
2240 appresults.num_hits, timer.elapsed ()*1000, pkg_search_string);
2241 }
2242
2243- private async void update_global_without_search (DeprecatedScopeSearch search,
2244- GLib.Cancellable cancellable)
2245+ private async void update_global_without_search ()
2246 {
2247 /*
2248 * In global search, with an empty search string, we show just Recent Apps
2249 * Excluding apps with icons in the launcher (be they running or faves)
2250 */
2251- var model = search.results_model;
2252+ var context = this.search_context;
2253+ var result_set = context.result_set;
2254
2255 Timer timer = new Timer ();
2256
2257- if (local_apps_active (search) && display_recent_apps)
2258+ if (local_apps_active () && scope.display_recent_apps)
2259 {
2260 try
2261 {
2262@@ -760,16 +1088,16 @@
2263 null);
2264
2265 var time_range = new Zeitgeist.TimeRange.anytime ();
2266- var results = yield log.find_events (time_range,
2267- zg_templates,
2268- Zeitgeist.StorageState.ANY,
2269- 40,
2270- Zeitgeist.ResultType.MOST_RECENT_SUBJECTS,
2271- cancellable);
2272+ var results = yield scope.log.find_events (
2273+ time_range,
2274+ scope.zg_templates,
2275+ Zeitgeist.StorageState.ANY,
2276+ 40,
2277+ Zeitgeist.ResultType.MOST_RECENT_SUBJECTS,
2278+ context.cancellable.get_gcancellable ());
2279
2280- model.clear ();
2281- append_events_with_category (results, model, Category.RECENT_APPS,
2282- false);
2283+ append_events_with_category (results, result_set,
2284+ Category.RECENT_APPS, false);
2285
2286 timer.stop ();
2287 debug ("Entry search found %u/%u Recently Used apps in %fms for query '%s'",
2288@@ -781,81 +1109,9 @@
2289 return;
2290 } catch (GLib.Error e) {
2291 warning ("Error performing search '%s': %s",
2292- search.search_string, e.message);
2293- }
2294- }
2295- }
2296-
2297- public Icon find_pkg_icon (string? desktop_file, string icon_name)
2298- {
2299- if (desktop_file != null)
2300- {
2301- string desktop_id = Path.get_basename (desktop_file);
2302- bool installed = AppInfoManager.get_default().lookup (desktop_id) != null;
2303-
2304- /* If the app is already installed we should be able to pull the
2305- * icon from the theme */
2306- if (installed)
2307- return new ThemedIcon (icon_name);
2308- }
2309-
2310- /* App is not installed - we need to find the right icon in the bowels
2311- * of the software center */
2312- if (icon_name.has_prefix ("/"))
2313- {
2314- return new FileIcon (File.new_for_path (icon_name));
2315- }
2316- else
2317- {
2318- Icon icon = file_icon_cache.lookup (icon_name);
2319-
2320- if (icon != null)
2321- return icon;
2322-
2323- /* If the icon name contains a . it probably already have a
2324- * type postfix - so test icon name directly */
2325- string path;
2326- if ("." in icon_name)
2327- {
2328- path = @"$(Config.DATADIR)/app-install/icons/$(icon_name)";
2329- if (FileUtils.test (path, FileTest.EXISTS))
2330- {
2331- icon = new FileIcon (File.new_for_path (path));
2332- file_icon_cache.insert (icon_name, icon);
2333- return icon;
2334- }
2335- /* Try also software center cache dir */
2336- path = Path.build_filename (Environment.get_user_cache_dir (),
2337- "software-center",
2338- "icons",
2339- icon_name);
2340- if (FileUtils.test (path, FileTest.EXISTS))
2341- {
2342- icon = new FileIcon (File.new_for_path (path));
2343- file_icon_cache.insert (icon_name, icon);
2344- return icon;
2345- }
2346- }
2347-
2348- /* Now try appending all the image extensions we know */
2349- foreach (var ext in image_extensions)
2350- {
2351- path = @"$(Config.DATADIR)/app-install/icons/$(icon_name).$(ext)";
2352- if (FileUtils.test (path, FileTest.EXISTS))
2353- {
2354- /* Got it! Cache the icon path and return the icon */
2355- icon = new FileIcon (File.new_for_path (path));
2356- file_icon_cache.insert (icon_name, icon);
2357- return icon;
2358- }
2359- }
2360- }
2361-
2362- /* Cache the fact that we couldn't find this icon */
2363- var icon = new ThemedIcon (GENERIC_APP_ICON);
2364- file_icon_cache.insert (icon_name, icon);
2365-
2366- return icon;
2367+ search_context.search_query, e.message);
2368+ }
2369+ }
2370 }
2371
2372 /*
2373@@ -877,10 +1133,10 @@
2374 int delta = (rel_a - rel_b).abs ();
2375 if (delta < 10)
2376 {
2377- string id_a = sc_mangler.extract_desktop_id (a.desktop_file);
2378- string id_b = sc_mangler.extract_desktop_id (b.desktop_file);
2379- rel_a = popularity_map["application://" + id_a];
2380- rel_b = popularity_map["application://" + id_b];
2381+ string id_a = scope.sc_mangler.extract_desktop_id (a.desktop_file);
2382+ string id_b = scope.sc_mangler.extract_desktop_id (b.desktop_file);
2383+ rel_a = scope.popularity_map["application://" + id_a];
2384+ rel_b = scope.popularity_map["application://" + id_b];
2385 }
2386 return rel_b - rel_a; // we want higher relevancy first
2387 });
2388@@ -910,7 +1166,7 @@
2389 {
2390 annotated_icon.ribbon = _("Free");
2391 }
2392- if (force_small_icons_for_suggestions || use_small_icon
2393+ if (scope.force_small_icons_for_suggestions || use_small_icon
2394 || app_icon.to_string () == GENERIC_APP_ICON)
2395 {
2396 annotated_icon.size_hint = IconSizeHint.SMALL;
2397@@ -923,7 +1179,7 @@
2398 * Add all results obtained from SoftwareCenterDataProvider
2399 */
2400 private uint add_sc_category_results (SoftwareCenterData.AppInfo?[] results,
2401- Dee.Model model,
2402+ Unity.ResultSet result_set,
2403 Category category,
2404 ref Set<string> duplicates_lookup,
2405 uint max_results)
2406@@ -936,8 +1192,9 @@
2407 continue;
2408
2409 string icon_obj;
2410- Icon app_icon = find_pkg_icon (app.desktop_file, app.icon);
2411- var pinfo = purchase_info.find (app.application_name, app.package_name);
2412+ Icon app_icon = scope.find_pkg_icon (app.desktop_file, app.icon);
2413+ var pinfo = scope.purchase_info.find (
2414+ app.application_name, app.package_name);
2415 if (pinfo != null)
2416 {
2417 // magazines need to use large icons
2418@@ -954,14 +1211,19 @@
2419 icon_obj = app_icon.to_string ();
2420 }
2421
2422- model.append (uri, icon_obj,
2423- category,
2424- pinfo != null && pinfo.paid ? Unity.ResultType.PERSONAL : Unity.ResultType.DEFAULT,
2425- "application/x-desktop",
2426- app.application_name,
2427- "", //comment
2428- "file://" + app.desktop_file,
2429- empty_asv);
2430+ var result = Unity.ScopeResult ();
2431+ result.uri = uri;
2432+ result.icon_hint = icon_obj;
2433+ result.category = category;
2434+ result.result_type = pinfo != null && pinfo.paid ?
2435+ Unity.ResultType.PERSONAL : Unity.ResultType.DEFAULT;
2436+ result.mimetype = "application/x-desktop";
2437+ result.title = app.application_name;
2438+ result.comment = "";
2439+ result.dnd_uri = "file://" + app.desktop_file;
2440+ result.metadata = new HashTable<string,Variant> (str_hash, str_equal);
2441+
2442+ result_set.add_result (result);
2443 duplicates_lookup.add (uri);
2444 i++;
2445 if (i == max_results)
2446@@ -973,7 +1235,7 @@
2447 private void add_pkg_search_result (Unity.Package.SearchResult results,
2448 Set<string> installed_uris,
2449 Set<string> available_uris,
2450- Dee.Model model,
2451+ Unity.ResultSet result_set,
2452 Category category,
2453 uint max_add,
2454 bool running_on_phablet)
2455@@ -986,8 +1248,8 @@
2456 if (pkginfo.desktop_file == null)
2457 continue;
2458
2459- string desktop_id = sc_mangler.extract_desktop_id (pkginfo.desktop_file,
2460- category == Category.AVAILABLE);
2461+ string desktop_id = scope.sc_mangler.extract_desktop_id (
2462+ pkginfo.desktop_file, category == Category.AVAILABLE);
2463 string full_path;
2464
2465 AppInfo? app = appmanager.lookup (desktop_id);
2466@@ -1026,25 +1288,34 @@
2467 continue;
2468
2469 /* Extract basic metadata and register de-dupe keys */
2470- string display_name;
2471- string comment;
2472+ var res = Unity.ScopeResult();
2473+ res.uri = uri;
2474+ res.category = category;
2475+ res.result_type = (category != Category.AVAILABLE &&
2476+ !results.fuzzy_search) ?
2477+ Unity.ResultType.PERSONAL : Unity.ResultType.DEFAULT;
2478+ res.mimetype = "application/x-desktop";
2479+ res.dnd_uri = full_path != null ? "file://" + full_path : "";
2480+ res.metadata = new HashTable<string, Variant> (str_hash, str_equal);
2481 switch (category)
2482 {
2483 case Category.INSTALLED:
2484 case Category.APPLICATIONS:
2485 installed_uris.add (uri);
2486- display_name = app.get_display_name ();
2487- comment = sanitize_binary_name (app.get_executable ());
2488+ res.title = app.get_display_name ();
2489+ res.comment = sanitize_binary_name (app.get_executable ());
2490 break;
2491 case Category.AVAILABLE:
2492 available_uris.add (uri);
2493- display_name = pkginfo.application_name;
2494- comment = "";
2495+ res.title = pkginfo.application_name;
2496+ res.comment = "";
2497 break;
2498 default:
2499 warning (@"Illegal category for package search $(category)");
2500 continue;
2501 }
2502+ if (res.title == null) res.title = "";
2503+ if (res.comment == null) res.comment = "";
2504
2505 /* We can only chuck out NoDisplay and OnlyShowIn app infos after
2506 * we have registered a de-dupe key for them - which is done in the
2507@@ -1052,8 +1323,7 @@
2508 if (app != null && !app.should_show ())
2509 continue;
2510
2511- Icon icon = find_pkg_icon (pkginfo.desktop_file, pkginfo.icon);
2512- string icon_str;
2513+ Icon icon = scope.find_pkg_icon (pkginfo.desktop_file, pkginfo.icon);
2514 if (category == Category.AVAILABLE)
2515 {
2516 /* If we have an available item, which is not a dupe, but is
2517@@ -1067,30 +1337,21 @@
2518 * use the 'unity-install://pkgname/Full App Name' URI scheme,
2519 * but only use that after we've de-duped the results.
2520 * But only change the URI *after* we've de-duped the results! */
2521- uri = @"unity-install://$(pkginfo.package_name)/$(pkginfo.application_name)";
2522+ res.uri = @"unity-install://$(pkginfo.package_name)/$(pkginfo.application_name)";
2523 available_uris.add (uri);
2524
2525 // magazines need to use large icons
2526 bool use_small_icon = pkginfo.desktop_file.has_suffix (".desktop");
2527- icon_str = get_annotated_icon (icon, pkginfo.price,
2528- !pkginfo.needs_purchase,
2529- use_small_icon);
2530+ res.icon_hint = get_annotated_icon (icon, pkginfo.price,
2531+ !pkginfo.needs_purchase,
2532+ use_small_icon);
2533 }
2534 else
2535 {
2536- icon_str = icon.to_string ();
2537+ res.icon_hint = icon.to_string ();
2538 }
2539
2540- var result_type = (category != Category.AVAILABLE && !results.fuzzy_search) ?
2541- Unity.ResultType.PERSONAL : Unity.ResultType.DEFAULT;
2542- model.append (uri, icon_str,
2543- category,
2544- result_type,
2545- "application/x-desktop",
2546- display_name != null ? display_name : "",
2547- comment != null ? comment : "",
2548- full_path != null ? "file://" + full_path : "",
2549- empty_asv);
2550+ result_set.add_result (res);
2551
2552 /* Stop if we added the number of items requested */
2553 n_added++;
2554@@ -1099,329 +1360,131 @@
2555 }
2556 }
2557
2558- private static Icon? get_default_scope_icon ()
2559- {
2560- try
2561- {
2562- return Icon.new_for_string (GENERIC_SCOPE_ICON);
2563- }
2564- catch (Error err)
2565- {
2566- warning ("%s", err.message);
2567- }
2568- return null;
2569- }
2570-
2571- private void add_local_scopes_results (string search_string,
2572- Dee.Model model,
2573- Set<string> installed_uris)
2574- {
2575- // If the local scopes Xapian index hasn't been built, return.
2576- if (scopesearcher == null)
2577- return;
2578-
2579- bool has_search = !Utils.is_search_empty (search_string);
2580- var pkg_search_string = XapianUtils.prepare_pkg_search_string (
2581- search_string, null);
2582-
2583- var results = scopesearcher.search (pkg_search_string, 0,
2584- Unity.Package.SearchType.PREFIX,
2585- has_search ?
2586- Unity.Package.Sort.BY_RELEVANCY :
2587- Unity.Package.Sort.BY_NAME);
2588-
2589- foreach (unowned Unity.Package.PackageInfo info in results.results)
2590- {
2591- /* De-dupe by "application://foo.scope", since that is what is
2592- * used for software-center results. */
2593- string dedup_key = @"application://$(info.desktop_file)";
2594- if (dedup_key in installed_uris)
2595- continue;
2596- installed_uris.add(dedup_key);
2597-
2598- /* Don't include master scopes in the search results. This is
2599- * performed after deduping so the master scopes don't just
2600- * move to hte "available" category. */
2601- if (info.is_master_scope)
2602- continue;
2603-
2604- var uri = @"scope://$(info.desktop_file)";
2605- var name = info.application_name;
2606- var icon_hint = info.icon;
2607- var category = info.desktop_file in disabled_scope_ids ?
2608- Category.AVAILABLE : Category.INSTALLED;
2609-
2610- try
2611- {
2612- Icon base_icon = icon_hint == null || icon_hint == "" ?
2613- get_default_scope_icon () : Icon.new_for_string (icon_hint);
2614- var anno_icon = new AnnotatedIcon (base_icon);
2615- anno_icon.size_hint = IconSizeHint.SMALL;
2616- icon_hint = anno_icon.to_string ();
2617- }
2618- catch (Error err)
2619- {
2620- icon_hint = "";
2621- }
2622-
2623- model.append (uri,
2624- icon_hint,
2625- category,
2626- ResultType.DEFAULT,
2627- "application/x-unity-scope",
2628- name != "" ? name : uri,
2629- "",
2630- "", // dnd_uri ?!
2631- empty_asv);
2632- }
2633- }
2634-
2635- private void add_remote_scopes_results (string search_string,
2636- Dee.Model model)
2637- {
2638- var results = Utils.search_index (scopes_index, analyzer, search_string);
2639- foreach (var iter in results)
2640- {
2641- unowned string scope_id =
2642- remote_scopes_model.get_string (iter, RemoteScopesColumn.SCOPE_ID);
2643- var uri = @"scope://$(scope_id)";
2644- var name =
2645- remote_scopes_model.get_string (iter, RemoteScopesColumn.NAME);
2646- var icon_hint =
2647- remote_scopes_model.get_string (iter, RemoteScopesColumn.ICON_HINT);
2648-
2649- try
2650- {
2651- Icon base_icon = icon_hint == null || icon_hint == "" ?
2652- get_default_scope_icon () : Icon.new_for_string (icon_hint);
2653- var anno_icon = new AnnotatedIcon (base_icon);
2654- anno_icon.size_hint = IconSizeHint.SMALL;
2655- icon_hint = anno_icon.to_string ();
2656- }
2657- catch (Error err)
2658- {
2659- icon_hint = "";
2660- }
2661-
2662- var category = scope_id in disabled_scope_ids ?
2663- Category.AVAILABLE : Category.INSTALLED;
2664-
2665- model.append (uri,
2666- icon_hint,
2667- category,
2668- ResultType.DEFAULT,
2669- "application/x-unity-scope",
2670- name != "" ? name : uri,
2671- "",
2672- "", // dnd_uri ?!
2673- empty_asv);
2674- }
2675- }
2676- private async void call_install_packages (string package_name, out string tid) throws IOError
2677- {
2678- tid = yield aptdclient.install_packages ({package_name});
2679- }
2680-
2681- private async void call_remove_packages (string package_name, out string tid) throws IOError
2682- {
2683- tid = yield aptdclient.remove_packages ({package_name});
2684- }
2685-
2686- /**
2687- * Handler for free apps installation.
2688- * Triggers package installation via apt-daemon DBus service
2689- */
2690- private Unity.ActivationResponse app_preview_install (string uri)
2691- {
2692- if (uri.has_prefix ("unity-install://"))
2693- {
2694- string app = uri.substring (16); // trim "unity-install://"
2695- string[] parts = app.split ("/");
2696-
2697- if (parts.length > 1)
2698- {
2699- string pkgname = parts[0];
2700- string appname = parts[1];
2701- try
2702- {
2703- aptdclient.connect_to_aptd ();
2704- }
2705- catch (IOError e)
2706- {
2707- warning ("Failed to connect to aptd: '%s'", e.message);
2708- return new Unity.ActivationResponse(Unity.HandledType.NOT_HANDLED);
2709- }
2710- call_install_packages.begin (pkgname, (obj, res) =>
2711- {
2712- try {
2713- string tid;
2714- call_install_packages.end (res, out tid);
2715- debug ("transaction started: %s, pkg: %s\n", tid, pkgname);
2716- aptd_transaction = new AptdTransactionProxy ();
2717- aptd_transaction.connect_to_aptd (tid);
2718- aptd_transaction.simulate ();
2719- aptd_transaction.run ();
2720-
2721- launcherservice.connect_to_launcher ();
2722- string desktop_file = preview_installable_desktop_file;
2723- Icon icon = find_pkg_icon (null, preview_installable_icon_file);
2724- launcherservice.add_launcher_item_from_position.begin (appname, icon.to_string (), 0, 0, 32, desktop_file, tid);
2725- }
2726- catch (IOError e)
2727- {
2728- warning ("Package '%s' installation failed: %s", pkgname, e.message);
2729- }
2730- });
2731- }
2732- else
2733- {
2734- warning ("Bad install uri: '%s'", uri);
2735- }
2736- }
2737- else
2738- {
2739- warning ("Can't handle '%s' in app_preview_install handler", uri);
2740- return new Unity.ActivationResponse(Unity.HandledType.NOT_HANDLED);
2741- }
2742- return new Unity.ActivationResponse(Unity.HandledType.HIDE_DASH);
2743- }
2744-
2745- private Unity.ActivationResponse start_software_center (string uri)
2746- {
2747- unowned string pkg = uri.offset (16); // strip off "unity-install://" prefix
2748- debug ("Installing: %s", pkg);
2749-
2750- var args = new string[2];
2751- args[0] = "software-center";
2752- args[1] = pkg;
2753-
2754- try
2755- {
2756- Process.spawn_async (GLib.Environment.get_home_dir (), args, null, SpawnFlags.SEARCH_PATH, null, null);
2757- }
2758- catch (SpawnError e)
2759- {
2760- warning ("Failed to spawn software-center for uri '%s': %s", uri, e.message);
2761- return new Unity.ActivationResponse(Unity.HandledType.NOT_HANDLED);
2762- }
2763-
2764- return new Unity.ActivationResponse(Unity.HandledType.HIDE_DASH);
2765- }
2766-
2767- private Unity.ActivationResponse app_preview_website (string uri)
2768- {
2769- try
2770- {
2771- AppInfo.launch_default_for_uri (preview_developer_website, null);
2772- return new Unity.ActivationResponse(Unity.HandledType.HIDE_DASH);
2773- }
2774- catch (Error e)
2775- {
2776- warning ("Failed to launch a web browser for uri '%s': '%s'", uri, e.message);
2777- }
2778- return new Unity.ActivationResponse(Unity.HandledType.NOT_HANDLED);
2779- }
2780-
2781- private Unity.ActivationResponse app_preview_uninstall (string uri)
2782- {
2783- if (uri.has_prefix ("application://"))
2784- {
2785- string desktopfile = uri.substring (14); // trim "application://"
2786-
2787- // de-mangle desktop file names back to what S-C expects
2788- if (sc_mangler.contains (desktopfile))
2789- {
2790- desktopfile = sc_mangler.get (desktopfile);
2791- }
2792-
2793- var pkginfo = pkgsearcher.get_by_desktop_file (desktopfile);
2794-
2795- if (pkginfo != null && pkginfo.package_name != null)
2796- {
2797- try
2798- {
2799- aptdclient.connect_to_aptd ();
2800- }
2801- catch (IOError e)
2802- {
2803- warning ("Failed to connect to aptd: '%s'", e.message);
2804- return new Unity.ActivationResponse(Unity.HandledType.NOT_HANDLED);
2805- }
2806-
2807- call_remove_packages.begin (pkginfo.package_name, (obj, res) =>
2808- {
2809- try {
2810- string tid;
2811- call_remove_packages.end (res, out tid);
2812- debug ("transaction started: %s, pkg: %s\n", tid, pkginfo.package_name);
2813- aptd_transaction = new AptdTransactionProxy ();
2814- aptd_transaction.connect_to_aptd (tid);
2815- aptd_transaction.simulate ();
2816- aptd_transaction.run ();
2817- }
2818- catch (IOError e)
2819- {
2820- warning (@"Package '$(pkginfo.package_name)' removal failed: $(e.message)");
2821- }
2822- });
2823- return new Unity.ActivationResponse(Unity.HandledType.HIDE_DASH);
2824- }
2825- else
2826- {
2827- warning (@"Cannot find package info for $uri");
2828- }
2829- }
2830- warning (@"Can't handle '%s' in app_preview_uninstall handler", uri);
2831- return new Unity.ActivationResponse(Unity.HandledType.NOT_HANDLED);
2832- }
2833-
2834- public Unity.Preview preview (string uri)
2835- {
2836- Unity.Preview? preview = null;
2837- bool installed = uri.has_prefix ("application://");
2838- bool is_scope = uri.has_prefix ("scope://");
2839-
2840- if (installed || uri.has_prefix ("unity-install://"))
2841- {
2842- preview = make_app_preview (uri, installed);
2843- }
2844- else if (is_scope)
2845- {
2846- preview = make_scope_preview (uri);
2847- }
2848+ /* Appends the subject URIs from a set of Zeitgeist.Events to our Dee.Model
2849+ * assuming that these events are already sorted */
2850+ public void append_events_with_category (Zeitgeist.ResultSet events,
2851+ Unity.ResultSet result_set,
2852+ uint category_id,
2853+ bool include_favorites,
2854+ int max_results = int.MAX,
2855+ Set<string>? allowed_uris = null)
2856+ {
2857+ int num_results = 0;
2858+ foreach (var ev in events)
2859+ {
2860+ string? app_uri = null;
2861+ if (ev.num_subjects () > 0)
2862+ app_uri = ev.get_subject (0).get_uri ();
2863+
2864+ if (app_uri == null)
2865+ {
2866+ warning ("Unexpected event without subject");
2867+ continue;
2868+ }
2869+
2870+ /* Assert that we indeed have a known application as actor */
2871+ string desktop_id = Utils.get_desktop_id_for_actor (app_uri);
2872+
2873+ /* Discard Recently Used apps that are in the launcher */
2874+ if ((category_id == Category.RECENT ||
2875+ category_id == Category.RECENT_APPS) &&
2876+ !include_favorites &&
2877+ (scope.favorite_apps.has_app_id (desktop_id)
2878+ || scope.app_watcher.has_app_id (desktop_id)))
2879+ continue;
2880+
2881+ var appmanager = AppInfoManager.get_default ();
2882+ AppInfo? app = appmanager.lookup (desktop_id);
2883+
2884+ if (app == null)
2885+ continue;
2886+
2887+ if (!app.should_show ())
2888+ continue;
2889+
2890+ /* HACK! when using the max_results together with allowed_uris,
2891+ * the limit serves as a truncation point - therefore results which
2892+ * are not displayed the first time won't be displayed later
2893+ * (consider results A, B, C - all of them are allowed and we use
2894+ * limit 2 - so only A and B is displayed, later we change allowed to
2895+ * B and C, so normally we would display both B and C, but that's not
2896+ * desired because C wasn't shown in the first place, so we display
2897+ * only B) */
2898+ if (num_results++ >= max_results) break;
2899+
2900+ if (allowed_uris != null && !(app_uri in allowed_uris)) continue;
2901+
2902+ var result = Unity.ScopeResult ();
2903+ result.uri = app_uri;
2904+ result.icon_hint = app.get_icon ().to_string ();
2905+ result.category = category_id;
2906+ result.result_type = Unity.ResultType.DEFAULT;
2907+ result.mimetype = "application/x-desktop";
2908+ result.title = app.get_display_name ();
2909+ result.comment = sanitize_binary_name (app.get_executable ());
2910+ string full_path = appmanager.get_path (desktop_id);
2911+ result.dnd_uri = full_path != null ? "file://" + full_path : app_uri;
2912+ result.metadata = new HashTable<string,Variant> (str_hash, str_equal);
2913+
2914+ result_set.add_result (result);
2915+ }
2916+ }
2917+ }
2918+
2919+ private class ApplicationsResultPreviewer : Unity.ResultPreviewer
2920+ {
2921+ private ApplicationsScope scope;
2922+
2923+ public ApplicationsResultPreviewer (ApplicationsScope scope, Unity.ScopeResult result, Unity.SearchMetadata metadata)
2924+ {
2925+ this.scope = scope;
2926+ set_scope_result (result);
2927+ set_search_metadata (metadata);
2928+ }
2929+
2930+ public override Unity.AbstractPreview? run ()
2931+ {
2932+ var ml = new MainLoop ();
2933+ Unity.AbstractPreview? preview = null;
2934+ run_async ((obj, p) =>
2935+ {
2936+ preview = p;
2937+ ml.quit ();
2938+ });
2939+ ml.run ();
2940 return preview;
2941 }
2942
2943- private SoftwareCenterData.AppDetailsData get_app_details (string appname, string pkgname) throws Error
2944+ public override void run_async (Unity.AbstractPreviewCallback callback)
2945 {
2946- if (sc_data_provider == null)
2947- {
2948- sc_data_provider = new SoftwareCenterDataCache (TOP_RATED_ITEMS_CACHE_LIFETIME);
2949- sc_data_provider.connect_to ();
2950- }
2951-
2952- debug ("Requesting pkg info: %s, %s\n", pkgname, appname);
2953- return sc_data_provider.get_app_details (appname, pkgname);
2954+ make_preview.begin ((obj, res) =>
2955+ {
2956+ var preview = make_preview.end (res);
2957+ callback (this, preview);
2958+ });
2959 }
2960
2961- private Unity.Preview make_app_preview (string uri, bool installed)
2962+ private async Unity.AbstractPreview? make_preview ()
2963 {
2964 Unity.ApplicationPreview? preview = null;
2965 string desktopfile = null;
2966 string pkgname = "";
2967 string appname = "";
2968
2969+ var uri = result.uri;
2970+ bool installed = uri.has_prefix ("application://");
2971+
2972 if (installed)
2973 {
2974 desktopfile = uri.substring (14); //remove "application://" prefix
2975
2976 // de-mangle desktop file names back to what S-C expects
2977- if (sc_mangler.contains (desktopfile))
2978+ if (scope.sc_mangler.contains (desktopfile))
2979 {
2980- desktopfile = sc_mangler.get (desktopfile);
2981+ desktopfile = scope.sc_mangler.get (desktopfile);
2982 }
2983
2984- Unity.Package.PackageInfo pkginfo = pkgsearcher.get_by_desktop_file (desktopfile);
2985+ var pkginfo = scope.pkgsearcher.get_by_desktop_file (desktopfile);
2986 if (pkginfo != null)
2987 {
2988 appname = pkginfo.application_name;
2989@@ -1442,14 +1505,14 @@
2990 if (pkgname != "")
2991 {
2992 try {
2993- var details = get_app_details (appname, pkgname);
2994+ var details = yield scope.get_app_details (appname, pkgname);
2995
2996 Icon? icon = null;
2997 if (installed)
2998 icon = new GLib.ThemedIcon (details.icon);
2999 else
3000 {
3001- icon = find_pkg_icon (null, details.icon);
3002+ icon = scope.find_pkg_icon (null, details.icon);
3003 if (icon.to_string () == GENERIC_APP_ICON && details.icon_url != null && details.icon_url != "")
3004 {
3005 icon = new GLib.FileIcon (File.new_for_uri (details.icon_url));
3006@@ -1476,11 +1539,11 @@
3007 preview = new Unity.ApplicationPreview (details.name, subtitle, details.description, icon, screenshot);
3008 preview.license = details.license;
3009
3010- init_ratings_db ();
3011- if (ratings != null)
3012+ scope.init_ratings_db ();
3013+ if (scope.ratings != null)
3014 {
3015 Unity.Ratings.Result result;
3016- ratings.query (pkgname, out result);
3017+ scope.ratings.query (pkgname, out result);
3018 preview.set_rating (result.average_rating / 5.0f, result.total_rating);
3019 }
3020
3021@@ -1498,7 +1561,6 @@
3022 buy_action.extra_text = details.price;
3023 }
3024
3025- buy_action.activated.connect (start_software_center);
3026 preview.add_action (buy_action);
3027 }
3028 else // uninstalled, purchased before
3029@@ -1508,21 +1570,18 @@
3030 if (details.raw_price == null || details.raw_price == "")
3031 {
3032 install_action = new Unity.PreviewAction ("install", _("Free Download"), null);
3033- install_action.activated.connect (app_preview_install);
3034 }
3035 else
3036 {
3037- install_action = new Unity.PreviewAction ("install", _("Install"), null);
3038- install_action.activated.connect (start_software_center);
3039+ install_action = new Unity.PreviewAction ("install-paid", _("Install"), null);
3040 }
3041 preview.add_action (install_action);
3042 }
3043
3044 if (details.website != null && details.website != "")
3045 {
3046- preview_developer_website = details.website;
3047+ scope.preview_developer_website = details.website;
3048 var website_action = new Unity.PreviewAction ("website", _("Developer Site"), null);
3049- website_action.activated.connect (app_preview_website);
3050 preview.add_action (website_action);
3051 }
3052 }
3053@@ -1534,13 +1593,12 @@
3054 if (!details.is_desktop_dependency)
3055 {
3056 var uninstall_action = new Unity.PreviewAction ("uninstall", _("Uninstall"), null);
3057- uninstall_action.activated.connect (app_preview_uninstall);
3058 preview.add_action (uninstall_action);
3059 }
3060 }
3061
3062- preview_installable_desktop_file = details.desktop_file;
3063- preview_installable_icon_file = details.icon;
3064+ scope.preview_installable_desktop_file = details.desktop_file;
3065+ scope.preview_installable_icon_file = details.icon;
3066 }
3067 catch (Error e)
3068 {
3069@@ -1568,335 +1626,6 @@
3070 }
3071 return preview;
3072 }
3073-
3074- private Unity.Preview make_scope_preview(string uri)
3075- {
3076- Unity.ApplicationPreview? preview = null;
3077- var scope_id = uri.substring (8);
3078- bool scope_disabled = scope_id in disabled_scope_ids;
3079-
3080- // figure out if the scope is remote
3081- bool is_remote_scope = false;
3082- var info = scopesearcher.get_by_desktop_file (scope_id);
3083-
3084- Dee.ModelIter found_iter = null;
3085- if (info == null)
3086- {
3087- var iter = remote_scopes_model.get_first_iter ();
3088- var end_iter = remote_scopes_model.get_last_iter ();
3089- while (iter != end_iter)
3090- {
3091- if (remote_scopes_model.get_string (iter, 0) == scope_id)
3092- {
3093- is_remote_scope = true;
3094- found_iter = iter;
3095- break;
3096- }
3097- iter = remote_scopes_model.next (iter);
3098- }
3099- }
3100-
3101- if (is_remote_scope)
3102- {
3103- var name = remote_scopes_model.get_string (found_iter, 1);
3104- if (name == null || name == "")
3105- name = remote_scopes_model.get_string (found_iter, 0);
3106- var description = remote_scopes_model.get_string (found_iter, 2);
3107- if (description != null)
3108- description = Markup.escape_text(description);
3109- Icon? icon = null;
3110- Icon? screenshot = null;
3111- var icon_hint = remote_scopes_model.get_string (found_iter, 3);
3112- var screenshot_url = remote_scopes_model.get_string (found_iter, 4);
3113- try
3114- {
3115- if (icon_hint != "") icon = Icon.new_for_string (icon_hint);
3116- if (screenshot_url != "") screenshot = Icon.new_for_string (screenshot_url);
3117- }
3118- catch (Error err)
3119- {
3120- warning ("%s", err.message);
3121- }
3122-
3123- if (icon == null) icon = get_default_scope_icon ();
3124-
3125- preview = new Unity.ApplicationPreview (name,
3126- "",
3127- description,
3128- icon,
3129- screenshot);
3130- }
3131- else if (info != null)
3132- {
3133- var name = info.application_name;
3134- if (name == null || name == "")
3135- name = info.desktop_file;
3136- var subtitle = "";
3137- var description = info.description;
3138- if (description != null)
3139- description = Markup.escape_text(description);
3140- Icon? icon = null;
3141- Icon? screenshot = null;
3142- try
3143- {
3144- if (info.icon != null && info.icon != "")
3145- icon = Icon.new_for_string (info.icon);
3146- }
3147- catch (Error err)
3148- {
3149- warning ("%s", err.message);
3150- }
3151- if (icon == null)
3152- icon = get_default_scope_icon ();
3153-
3154- // de-mangle desktop file names back to what S-C expects
3155- string mangled_id;
3156- if (sc_mangler.contains (scope_id))
3157- {
3158- mangled_id = sc_mangler.get (scope_id);
3159- }
3160- else
3161- {
3162- mangled_id = scope_id;
3163- }
3164-
3165- Unity.Package.PackageInfo pkginfo = pkgsearcher.get_by_desktop_file (mangled_id);
3166- if (pkginfo != null)
3167- {
3168- SoftwareCenterData.AppDetailsData? details;
3169- try {
3170- details = get_app_details(pkginfo.application_name,
3171- pkginfo.package_name);
3172- } catch (Error e) {
3173- details = null;
3174- }
3175- if (details != null) {
3176- if (details.version != "")
3177- subtitle = _("Version %s").printf (details.version);
3178- if (details.screenshot != null)
3179- {
3180- File scr_file = File.new_for_uri (details.screenshot);
3181- screenshot = new FileIcon (scr_file);
3182- }
3183- }
3184- }
3185-
3186- preview = new Unity.ApplicationPreview (name,
3187- subtitle,
3188- description,
3189- icon,
3190- screenshot);
3191- }
3192- if (preview != null && scope_id != "home.scope" &&
3193- scope_id != "applications.scope")
3194- {
3195- PreviewAction action;
3196- preview.set_rating(-1.0f, 0);
3197- if (scope_disabled)
3198- {
3199- action = new Unity.PreviewAction ("enable-scope", _("Enable"), null);
3200- action.activated.connect (() =>
3201- {
3202- enable_scope (scope_id);
3203- return new ActivationResponse.with_preview (this.preview (uri));
3204- });
3205- }
3206- else
3207- {
3208- action = new Unity.PreviewAction ("disable-scope", _("Disable"), null);
3209- action.activated.connect (() =>
3210- {
3211- disable_scope (scope_id);
3212- return new ActivationResponse.with_preview (this.preview (uri));
3213- });
3214- }
3215- preview.add_action (action);
3216- }
3217- return preview;
3218- }
3219-
3220- private void disable_scope (string scope_id)
3221- {
3222- if (scope_id in disabled_scope_ids) return;
3223-
3224- var pref_man = PreferencesManager.get_default ();
3225- var disabled_scopes = pref_man.disabled_scopes;
3226- disabled_scopes += scope_id;
3227-
3228- var settings = new Settings (LIBUNITY_SCHEMA);
3229- settings.set_strv ("disabled-scopes", disabled_scopes);
3230- }
3231-
3232- private void enable_scope (string scope_id)
3233- {
3234- if (!(scope_id in disabled_scope_ids)) return;
3235-
3236- var pref_man = PreferencesManager.get_default ();
3237- string[] disabled_scopes = {};
3238- foreach (unowned string disabled_scope_id in pref_man.disabled_scopes)
3239- {
3240- if (disabled_scope_id != scope_id)
3241- disabled_scopes += disabled_scope_id;
3242- }
3243-
3244- var settings = new Settings (LIBUNITY_SCHEMA);
3245- settings.set_strv ("disabled-scopes", disabled_scopes);
3246- }
3247- /**
3248- * Override of the default activation handler. The apps lens daemon
3249- * can handle activation of installable apps using the Software Center
3250- */
3251- public Unity.ActivationResponse activate (string uri)
3252- {
3253- string[] args;
3254- string exec_or_dir = null;
3255- if (uri.has_prefix ("unity-install://") || uri.has_prefix ("scope://"))
3256- {
3257- var prv = preview (uri);
3258- if (prv != null)
3259- {
3260- return new Unity.ActivationResponse.with_preview (prv);
3261- }
3262- else
3263- {
3264- warning ("Failed to generate preview for %s", uri);
3265- return new Unity.ActivationResponse (Unity.HandledType.NOT_HANDLED);
3266- }
3267- }
3268- else if (uri.has_prefix (UNITY_RUNNER_PREFIX))
3269- {
3270- string orig;
3271- orig = uri.offset (UNITY_RUNNER_PREFIX.length);
3272- if (orig.has_prefix("\\\\"))
3273- orig = orig.replace ("\\\\","smb://");
3274- if (uri_regex != null && uri_regex.match (orig)) {
3275- try {
3276- /* this code ensures that a file manager will be used
3277- * if uri it's a remote location that should be mounted */
3278- if (mountable_regex.match (orig)) {
3279- var muris = new GLib.List<string>();
3280- muris.prepend (orig);
3281- var file_manager = AppInfo.get_default_for_type("inode/directory", true);
3282- file_manager.launch_uris(muris,null);
3283- } else {
3284- AppInfo.launch_default_for_uri (orig, null);
3285- }
3286- } catch (GLib.Error error) {
3287- warning ("Failed to launch URI %s", orig);
3288- return new Unity.ActivationResponse(Unity.HandledType.NOT_HANDLED);
3289- }
3290- return new Unity.ActivationResponse(Unity.HandledType.HIDE_DASH);
3291-
3292- } else {
3293- exec_or_dir = Utils.subst_tilde (orig);
3294- args = exec_or_dir.split (" ", 0);
3295- for (int i = 0; i < args.length; i++)
3296- args[i] = Utils.subst_tilde (args[i]);
3297- }
3298- this.runner.add_history (orig);
3299- }
3300- else
3301- {
3302- /* Activation of standard application:// uris */
3303-
3304- /* Make sure fresh install learns quickly */
3305- if (popularity_map.size <= 5) popularities_dirty = true;
3306-
3307- return new Unity.ActivationResponse(Unity.HandledType.NOT_HANDLED);
3308- }
3309-
3310- if ((exec_or_dir != null) && FileUtils.test (exec_or_dir, FileTest.IS_DIR))
3311- {
3312- try {
3313- AppInfo.launch_default_for_uri ("file://" + exec_or_dir, null);
3314- } catch (GLib.Error err) {
3315- warning ("Failed to open current folder '%s' in file manager: %s",
3316- exec_or_dir, err.message);
3317- return new Unity.ActivationResponse(Unity.HandledType.NOT_HANDLED);
3318- }
3319- }
3320- else
3321- {
3322- try {
3323- unowned string home_dir = GLib.Environment.get_home_dir ();
3324- Process.spawn_async (home_dir, args, null, SpawnFlags.SEARCH_PATH, null, null);
3325- } catch (SpawnError e) {
3326- warning ("Failed to spawn software-center or direct URI activation '%s': %s",
3327- uri, e.message);
3328- return new Unity.ActivationResponse(Unity.HandledType.NOT_HANDLED);
3329- }
3330- }
3331-
3332- return new Unity.ActivationResponse(Unity.HandledType.HIDE_DASH);
3333-
3334- }
3335-
3336- /* Appends the subject URIs from a set of Zeitgeist.Events to our Dee.Model
3337- * assuming that these events are already sorted */
3338- public void append_events_with_category (Zeitgeist.ResultSet events,
3339- Dee.Model results,
3340- uint category_id,
3341- bool include_favorites,
3342- int max_results = int.MAX,
3343- Set<string>? allowed_uris = null)
3344- {
3345- int num_results = 0;
3346- foreach (var ev in events)
3347- {
3348- string? app_uri = null;
3349- if (ev.num_subjects () > 0)
3350- app_uri = ev.get_subject (0).get_uri ();
3351-
3352- if (app_uri == null)
3353- {
3354- warning ("Unexpected event without subject");
3355- continue;
3356- }
3357-
3358- /* Assert that we indeed have a known application as actor */
3359- string desktop_id = Utils.get_desktop_id_for_actor (app_uri);
3360-
3361- /* Discard Recently Used apps that are in the launcher */
3362- if ((category_id == Category.RECENT ||
3363- category_id == Category.RECENT_APPS) &&
3364- !include_favorites &&
3365- (favorite_apps.has_app_id (desktop_id)
3366- || app_watcher.has_app_id (desktop_id)))
3367- continue;
3368-
3369- var appmanager = AppInfoManager.get_default ();
3370- AppInfo? app = appmanager.lookup (desktop_id);
3371-
3372- if (app == null)
3373- continue;
3374-
3375- if (!app.should_show ())
3376- continue;
3377-
3378- /* HACK! when using the max_results together with allowed_uris,
3379- * the limit serves as a truncation point - therefore results which
3380- * are not displayed the first time won't be displayed later
3381- * (consider results A, B, C - all of them are allowed and we use
3382- * limit 2 - so only A and B is displayed, later we change allowed to
3383- * B and C, so normally we would display both B and C, but that's not
3384- * desired because C wasn't shown in the first place, so we display
3385- * only B) */
3386- if (num_results++ >= max_results) break;
3387-
3388- if (allowed_uris != null && !(app_uri in allowed_uris)) continue;
3389-
3390- string full_path = appmanager.get_path (desktop_id);
3391- string full_uri = full_path != null ? "file://" + full_path : app_uri;
3392-
3393- results.append (app_uri, app.get_icon().to_string(),
3394- category_id, Unity.ResultType.DEFAULT,
3395- "application/x-desktop", app.get_display_name (),
3396- sanitize_binary_name (app.get_executable ()),
3397- full_uri,
3398- empty_asv);
3399- }
3400- }
3401-
3402- } /* END: class Daemon */
3403+ }
3404
3405 } /* namespace */
3406
3407=== modified file 'src/main.vala'
3408--- src/main.vala 2013-07-01 11:20:42 +0000
3409+++ src/main.vala 2013-08-06 08:19:29 +0000
3410@@ -1,5 +1,6 @@
3411+/* -*- mode: vala; c-basic-offset: 2; indent-tabs-mode: nil -*- */
3412 /*
3413- * Copyright (C) 2011 Canonical Ltd
3414+ * Copyright (C) 2011-2013 Canonical Ltd
3415 *
3416 * This program is free software: you can redistribute it and/or modify
3417 * it under the terms of the GNU General Public License version 3 as
3418@@ -20,90 +21,25 @@
3419 using GLib;
3420 using Config;
3421
3422-namespace Unity.ApplicationsLens {
3423-
3424- const string DBUS_NAME = "com.canonical.Unity.Scope.Applications";
3425- static Application? app = null;
3426- static Daemon? daemon = null;
3427-
3428- /* Check if a given well known DBus is owned.
3429- * WARNING: This does sync IO! */
3430- public static bool dbus_name_has_owner (string name)
3431- {
3432- try {
3433- bool has_owner;
3434- DBusConnection bus = Bus.get_sync (BusType.SESSION);
3435- Variant result = bus.call_sync ("org.freedesktop.DBus",
3436- "/org/freedesktop/dbus",
3437- "org.freedesktop.DBus",
3438- "NameHasOwner",
3439- new Variant ("(s)", name),
3440- new VariantType ("(b)"),
3441- DBusCallFlags.NO_AUTO_START,
3442- -1);
3443- result.get ("(b)", out has_owner);
3444- return has_owner;
3445- } catch (Error e) {
3446- warning ("Unable to decide whether '%s' is running: %s", name, e.message);
3447- }
3448-
3449- return false;
3450- }
3451-
3452- public static int main (string[] args)
3453- {
3454- /* Sort up locale to get translations but also sorting and
3455- * punctuation right */
3456- GLib.Intl.textdomain (Config.PACKAGE);
3457- GLib.Intl.bindtextdomain (Config.PACKAGE, Config.LOCALEDIR);
3458- GLib.Intl.bind_textdomain_codeset (Config.PACKAGE, "UTF-8");
3459- GLib.Intl.setlocale(GLib.LocaleCategory.ALL, "");
3460-
3461- /* Make sure the desktop appinfos are picked up correctly */
3462- DesktopAppInfo.set_desktop_env ("Unity");
3463-
3464- /* Sub processes inheriting our environment may be confused if they
3465- * see a starter bus set */
3466- Environment.unset_variable ("DBUS_STARTER_ADDRESS");
3467- Environment.unset_variable ("DBUS_STARTER_BUS_TYPE");
3468-
3469- /* Workaround for https://bugzilla.gnome.org/show_bug.cgi?id=640714
3470- * GApplication.register() call owns our DBus name in a sync manner
3471- * making it race against GDBus' worker thread to export our
3472- * objects on the bus before/after owning our name and receiving
3473- * method calls on our objects (which may not yet be up!)*/
3474- if (dbus_name_has_owner (DBUS_NAME))
3475- {
3476- print ("Another instance of the Unity Applications Daemon " +
3477- "already appears to be running.\nBailing out.\n");
3478- return 2;
3479- }
3480-
3481- /* Now register our DBus objects *before* acquiring the name!
3482- * See above for reasons */
3483- daemon = new Daemon ();
3484-
3485- /* Use GApplication directly for single instance app functionality */
3486- app = new Application (DBUS_NAME, ApplicationFlags.IS_SERVICE);
3487- try {
3488- app.register ();
3489- } catch (Error e) {
3490- /* FIXME: We get this error if another daemon is already running,
3491- * but it uses a generic error so we can't detect this reliably... */
3492- print ("Failed to start applications daemon: %s\n", e.message);
3493- return 1;
3494- }
3495-
3496- if (app.get_is_remote ())
3497- {
3498- print ("Another instance of the Unity Applications Daemon " +
3499- "already appears to be running.\nBailing out.\n");
3500- return 2;
3501- }
3502-
3503- /* Hold()ing the app makes sure the GApplication doesn't exit */
3504- app.hold();
3505- return app.run ();
3506- }
3507-
3508-} /* namespace */
3509+public int unity_scope_module_get_version ()
3510+{
3511+ return Unity.SCOPE_API_VERSION;
3512+}
3513+
3514+public List<Unity.AbstractScope> unity_scope_module_load_scopes () throws Error
3515+{
3516+ /* Sort up locale to get translations but also sorting and
3517+ * punctuation right */
3518+ GLib.Intl.bindtextdomain (Config.PACKAGE, Config.LOCALEDIR);
3519+ GLib.Intl.bind_textdomain_codeset (Config.PACKAGE, "UTF-8");
3520+ GLib.Intl.setlocale(GLib.LocaleCategory.ALL, "");
3521+
3522+ var scopes = new List<Unity.AbstractScope> ();
3523+ var apps_scope = new Unity.ApplicationsLens.ApplicationsScope ();
3524+ var scopes_scope = new Unity.ApplicationsLens.ScopesScope (apps_scope);
3525+
3526+ scopes.append(apps_scope);
3527+ scopes.append(apps_scope.commands_scope);
3528+ scopes.append(scopes_scope);
3529+ return scopes;
3530+}
3531
3532=== added file 'src/scopes-scope.vala'
3533--- src/scopes-scope.vala 1970-01-01 00:00:00 +0000
3534+++ src/scopes-scope.vala 2013-08-06 08:19:29 +0000
3535@@ -0,0 +1,510 @@
3536+/* -*- mode: vala; c-basic-offset: 2; indent-tabs-mode: nil -*- */
3537+/*
3538+ * Copyright (C) 2013 Canonical Ltd
3539+ *
3540+ * This program is free software: you can redistribute it and/or modify
3541+ * it under the terms of the GNU General Public License version 3 as
3542+ * published by the Free Software Foundation.
3543+ *
3544+ * This program is distributed in the hope that it will be useful,
3545+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
3546+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
3547+ * GNU General Public License for more details.
3548+ *
3549+ * You should have received a copy of the GNU General Public License
3550+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
3551+ *
3552+ * Authored by James Henstridge <james.henstridge@canonical.com>
3553+ *
3554+ */
3555+
3556+namespace Unity.ApplicationsLens {
3557+ const string GENERIC_SCOPE_ICON = ICON_PATH + "service-generic.svg";
3558+
3559+ private class ScopesScope : Unity.AbstractScope
3560+ {
3561+ public ApplicationsScope appscope;
3562+ public Unity.Package.Searcher? searcher;
3563+ public HashTable<unowned string, unowned string> disabled_scope_ids;
3564+ public Dee.Model remote_scopes_model;
3565+ public Dee.Index scopes_index;
3566+ public Dee.Analyzer analyzer;
3567+
3568+ public ScopesScope (ApplicationsScope appscope)
3569+ {
3570+ this.appscope = appscope;
3571+ // Track the remote scopes
3572+ remote_scopes_model = new Dee.SharedModel ("com.canonical.Unity.SmartScopes.RemoteScopesModel");
3573+ remote_scopes_model.set_schema ("s", "s", "s", "s", "s", "as");
3574+
3575+ scopes_index = Utils.prepare_index (remote_scopes_model,
3576+ RemoteScopesColumn.NAME,
3577+ (model, iter) =>
3578+ {
3579+ unowned string name = model.get_string (iter, RemoteScopesColumn.NAME);
3580+ return "%s\n%s".printf (_("scope"), name);
3581+ }, out analyzer);
3582+
3583+ disabled_scope_ids = new HashTable<unowned string, unowned string> (str_hash, str_equal);
3584+ update_disabled_scopes_hash ();
3585+ var pref_man = PreferencesManager.get_default ();
3586+ pref_man.notify["disabled-scopes"].connect (update_disabled_scopes_hash);
3587+
3588+ build_scope_index.begin ();
3589+ }
3590+
3591+ private void update_disabled_scopes_hash ()
3592+ {
3593+ disabled_scope_ids.remove_all ();
3594+ var pref_man = PreferencesManager.get_default ();
3595+ foreach (unowned string scope_id in pref_man.disabled_scopes)
3596+ {
3597+ // using HashTable as a set (optimized in glib when done like this)
3598+ disabled_scope_ids[scope_id] = scope_id;
3599+ }
3600+ }
3601+
3602+ private void disable_scope (string scope_id)
3603+ {
3604+ if (scope_id in disabled_scope_ids) return;
3605+
3606+ var pref_man = PreferencesManager.get_default ();
3607+ var disabled_scopes = pref_man.disabled_scopes;
3608+ disabled_scopes += scope_id;
3609+
3610+ var settings = new Settings (LIBUNITY_SCHEMA);
3611+ settings.set_strv ("disabled-scopes", disabled_scopes);
3612+ }
3613+
3614+ private void enable_scope (string scope_id)
3615+ {
3616+ if (!(scope_id in disabled_scope_ids)) return;
3617+
3618+ var pref_man = PreferencesManager.get_default ();
3619+ string[] disabled_scopes = {};
3620+ foreach (unowned string disabled_scope_id in pref_man.disabled_scopes)
3621+ {
3622+ if (disabled_scope_id != scope_id)
3623+ disabled_scopes += disabled_scope_id;
3624+ }
3625+
3626+ var settings = new Settings (LIBUNITY_SCHEMA);
3627+ settings.set_strv ("disabled-scopes", disabled_scopes);
3628+ }
3629+
3630+ private async void build_scope_index ()
3631+ {
3632+ try
3633+ {
3634+ var scope_registry = yield Unity.Protocol.ScopeRegistry.find_scopes (
3635+ Config.PKGDATADIR + "/scopes");
3636+ searcher = new Unity.Package.Searcher.for_scopes (scope_registry);
3637+ }
3638+ catch (Error err)
3639+ {
3640+ warning ("Unable to find scopes: %s", err.message);
3641+ }
3642+ }
3643+
3644+ public override string get_group_name ()
3645+ {
3646+ return "com.canonical.Unity.Scope.Applications";
3647+ }
3648+
3649+ public override string get_unique_name ()
3650+ {
3651+ return "/com/canonical/unity/scope/scopes";
3652+ }
3653+
3654+ public override Unity.FilterSet get_filters ()
3655+ {
3656+ var filters = new Unity.FilterSet ();
3657+
3658+ /* Type filter */
3659+ {
3660+ var filter = new CheckOptionFilter ("type", _("Type"));
3661+ filter.sort_type = Unity.OptionsFilter.SortType.DISPLAY_NAME;
3662+
3663+ filter.add_option ("accessories", _("Accessories"));
3664+ filter.add_option ("education", _("Education"));
3665+ filter.add_option ("game", _("Games"));
3666+ filter.add_option ("graphics", _("Graphics"));
3667+ filter.add_option ("internet", _("Internet"));
3668+ filter.add_option ("fonts", _("Fonts"));
3669+ filter.add_option ("office", _("Office"));
3670+ filter.add_option ("media", _("Media"));
3671+ filter.add_option ("customization", _("Customization"));
3672+ filter.add_option ("accessibility", _("Accessibility"));
3673+ filter.add_option ("developer", _("Developer"));
3674+ filter.add_option ("science-and-engineering", _("Science & engineering"));
3675+ filter.add_option ("scopes", _("Search plugins"));
3676+ filter.add_option ("system", _("System"));
3677+
3678+ filters.add (filter);
3679+ }
3680+ return filters;
3681+ }
3682+
3683+ public override Unity.CategorySet get_categories ()
3684+ {
3685+ Unity.CategorySet categories = new Unity.CategorySet ();
3686+ File icon_dir = File.new_for_path (ICON_PATH);
3687+
3688+ var cat = new Unity.Category ("apps", _("Applications"),
3689+ new FileIcon (icon_dir.get_child ("group-apps.svg")));
3690+ categories.add (cat);
3691+
3692+ cat = new Unity.Category ("recently-used", _("Recently used"),
3693+ new FileIcon (icon_dir.get_child ("group-recent.svg")));
3694+ categories.add (cat);
3695+
3696+ cat = new Unity.Category ("recent", _("Recent apps"),
3697+ new FileIcon (icon_dir.get_child ("group-apps.svg")));
3698+ categories.add (cat);
3699+
3700+ cat = new Unity.Category ("installed", _("Installed"),
3701+ new FileIcon (icon_dir.get_child ("group-installed.svg")));
3702+ categories.add (cat);
3703+
3704+ cat = new Unity.Category ("more", _("More suggestions"),
3705+ new FileIcon (icon_dir.get_child ("group-treat-yourself.svg")));
3706+ categories.add (cat);
3707+
3708+ return categories;
3709+ }
3710+
3711+ public override Unity.Schema get_schema ()
3712+ {
3713+ var schema = new Unity.Schema ();
3714+ schema.add_field ("is_scope", "u", Schema.FieldType.OPTIONAL);
3715+ return schema;
3716+ }
3717+
3718+ public override string normalize_search_query (string search_query)
3719+ {
3720+ return search_query.strip ();
3721+ }
3722+
3723+ public override Unity.ScopeSearchBase create_search_for_query (Unity.SearchContext search_context)
3724+ {
3725+ return new ScopesSearch (this, search_context);
3726+ }
3727+
3728+ public override Unity.ResultPreviewer create_previewer (Unity.ScopeResult result, Unity.SearchMetadata metadata)
3729+ {
3730+ return new ScopesResultPreviewer (this, result, metadata);
3731+ }
3732+
3733+ public override Unity.ActivationResponse? activate (Unity.ScopeResult result, Unity.SearchMetadata metadata, string? action_id)
3734+ {
3735+ var scope_id = result.uri.substring (8);
3736+ if (action_id == "enable-scope")
3737+ {
3738+ enable_scope (scope_id);
3739+ }
3740+ else if (action_id == "disable-scope")
3741+ {
3742+ disable_scope (scope_id);
3743+ }
3744+ /* Make a preview */
3745+ var preview = create_previewer (result, metadata).run ();
3746+ if (preview is Unity.Preview)
3747+ {
3748+ return new Unity.ActivationResponse.with_preview (preview as Unity.Preview);
3749+ }
3750+ else
3751+ {
3752+ warning ("Failed to generate preview for %s", result.uri);
3753+ return new Unity.ActivationResponse (Unity.HandledType.NOT_HANDLED);
3754+ }
3755+ }
3756+
3757+ public static Icon? get_default_icon ()
3758+ {
3759+ try
3760+ {
3761+ return Icon.new_for_string (GENERIC_SCOPE_ICON);
3762+ }
3763+ catch (Error err)
3764+ {
3765+ warning ("%s", err.message);
3766+ }
3767+ return null;
3768+ }
3769+ }
3770+
3771+ private class ScopesSearch : Unity.ScopeSearchBase
3772+ {
3773+ private ScopesScope scope;
3774+
3775+ public ScopesSearch (ScopesScope scope, Unity.SearchContext search_context)
3776+ {
3777+ this.scope = scope;
3778+ this.search_context = search_context;
3779+ }
3780+
3781+ public override void run ()
3782+ {
3783+ if (this.scope.searcher == null)
3784+ return;
3785+
3786+ var context = this.search_context;
3787+ // Don't add results to global searches
3788+ if (context.search_type == SearchType.GLOBAL)
3789+ return;
3790+
3791+ var filter = context.filter_state.get_filter_by_id ("type") as OptionsFilter;
3792+ if (filter != null && filter.filtering &&
3793+ !filter.get_option ("scopes").active)
3794+ return;
3795+
3796+ add_local_results (context.search_query, context.result_set);
3797+ add_remote_results (context.search_query, context.result_set);
3798+ }
3799+
3800+ private void add_local_results (string search_query, Unity.ResultSet result_set)
3801+ {
3802+ bool has_search = !Utils.is_search_empty (search_query);
3803+ var pkg_search_string = XapianUtils.prepare_pkg_search_string (
3804+ search_query, null);
3805+
3806+ var search_result = this.scope.searcher.search (
3807+ pkg_search_string, 0, Unity.Package.SearchType.PREFIX,
3808+ has_search ?
3809+ Unity.Package.Sort.BY_RELEVANCY : Unity.Package.Sort.BY_NAME);
3810+
3811+ foreach (unowned Unity.Package.PackageInfo info in search_result.results)
3812+ {
3813+ /* Don't include master scopes in the search results. This is
3814+ * performed after deduping so the master scopes don't just
3815+ * move to hte "available" category. */
3816+ if (info.is_master_scope)
3817+ continue;
3818+
3819+ var result = Unity.ScopeResult ();
3820+ result.uri = @"scope://$(info.desktop_file)";
3821+ result.category = info.desktop_file in scope.disabled_scope_ids ?
3822+ Category.AVAILABLE : Category.INSTALLED;
3823+ result.result_type = Unity.ResultType.DEFAULT;
3824+ result.mimetype = "application/x-unity-scope";
3825+ result.title = info.application_name;
3826+ if (result.title == null)
3827+ result.title = "";
3828+ result.comment = "";
3829+ result.dnd_uri = "";
3830+ result.metadata = new HashTable<string, Variant> (str_hash, str_equal);
3831+ result.metadata["is_scope"] = new Variant.uint32 (1);
3832+
3833+ var icon_hint = info.icon;
3834+ try
3835+ {
3836+ Icon base_icon = icon_hint == null || icon_hint == "" ?
3837+ ScopesScope.get_default_icon () : Icon.new_for_string (icon_hint);
3838+ var anno_icon = new AnnotatedIcon (base_icon);
3839+ anno_icon.size_hint = IconSizeHint.SMALL;
3840+ icon_hint = anno_icon.to_string ();
3841+ }
3842+ catch (Error err)
3843+ {
3844+ icon_hint = "";
3845+ }
3846+ result.icon_hint = icon_hint;
3847+
3848+ result_set.add_result (result);
3849+ }
3850+ }
3851+
3852+ private void add_remote_results (string search_query, Unity.ResultSet result_set)
3853+ {
3854+ var results = Utils.search_index (
3855+ scope.scopes_index, scope.analyzer, search_query);
3856+ foreach (var iter in results)
3857+ {
3858+ var result = Unity.ScopeResult ();
3859+ unowned string scope_id =
3860+ scope.remote_scopes_model.get_string (iter, RemoteScopesColumn.SCOPE_ID);
3861+ result.uri = @"scope://$(scope_id)";
3862+ result.category = scope_id in scope.disabled_scope_ids ?
3863+ Category.AVAILABLE : Category.INSTALLED;
3864+ result.result_type = Unity.ResultType.DEFAULT;
3865+ result.mimetype = "application/x-unity-scope";
3866+ result.title =
3867+ scope.remote_scopes_model.get_string (iter, RemoteScopesColumn.NAME);
3868+ result.comment = "";
3869+ result.dnd_uri = "";
3870+ result.metadata = new HashTable<string, Variant> (str_hash, str_equal);
3871+ result.metadata["is_scope"] = new Variant.uint32 (1);
3872+
3873+ var icon_hint =
3874+ scope.remote_scopes_model.get_string (iter, RemoteScopesColumn.ICON_HINT);
3875+ try
3876+ {
3877+ Icon base_icon = icon_hint == null || icon_hint == "" ?
3878+ ScopesScope.get_default_icon () : Icon.new_for_string (icon_hint);
3879+ var anno_icon = new AnnotatedIcon (base_icon);
3880+ anno_icon.size_hint = IconSizeHint.SMALL;
3881+ icon_hint = anno_icon.to_string ();
3882+ }
3883+ catch (Error err)
3884+ {
3885+ icon_hint = "";
3886+ }
3887+ result.icon_hint = icon_hint;
3888+
3889+ result_set.add_result (result);
3890+ }
3891+ }
3892+ }
3893+
3894+ private class ScopesResultPreviewer : Unity.ResultPreviewer
3895+ {
3896+ private ScopesScope scope;
3897+
3898+ public ScopesResultPreviewer (ScopesScope scope, Unity.ScopeResult result, Unity.SearchMetadata metadata)
3899+ {
3900+ this.scope = scope;
3901+ set_scope_result (result);
3902+ set_search_metadata (metadata);
3903+ }
3904+
3905+ public override Unity.AbstractPreview? run ()
3906+ {
3907+ var ml = new MainLoop ();
3908+ Unity.AbstractPreview? preview = null;
3909+ run_async ((obj, p) =>
3910+ {
3911+ preview = p;
3912+ ml.quit ();
3913+ });
3914+ ml.run ();
3915+ return preview;
3916+ }
3917+
3918+ public override void run_async (Unity.AbstractPreviewCallback callback)
3919+ {
3920+ make_preview.begin ((obj, res) =>
3921+ {
3922+ var preview = make_preview.end (res);
3923+ callback (this, preview);
3924+ });
3925+ }
3926+
3927+ private async Unity.AbstractPreview? make_preview ()
3928+ {
3929+ Unity.ApplicationPreview? preview = null;
3930+ var scope_id = result.uri.substring (8);
3931+ bool scope_disabled = scope_id in scope.disabled_scope_ids;
3932+
3933+ // figure out if the scope is remote
3934+ bool is_remote_scope = false;
3935+ var info = scope.searcher.get_by_desktop_file (scope_id);
3936+
3937+ Dee.ModelIter found_iter = null;
3938+ if (info == null)
3939+ {
3940+ var iter = scope.remote_scopes_model.get_first_iter ();
3941+ var end_iter = scope.remote_scopes_model.get_last_iter ();
3942+ while (iter != end_iter)
3943+ {
3944+ if (scope.remote_scopes_model.get_string (iter, 0) == scope_id)
3945+ {
3946+ is_remote_scope = true;
3947+ found_iter = iter;
3948+ break;
3949+ }
3950+ iter = scope.remote_scopes_model.next (iter);
3951+ }
3952+ }
3953+
3954+ if (is_remote_scope)
3955+ {
3956+ var name = scope.remote_scopes_model.get_string (found_iter, 1);
3957+ if (name == null || name == "")
3958+ name = scope.remote_scopes_model.get_string (found_iter, 0);
3959+ var description = scope.remote_scopes_model.get_string (found_iter, 2);
3960+ if (description != null)
3961+ description = Markup.escape_text(description);
3962+ Icon? icon = null;
3963+ Icon? screenshot = null;
3964+ var icon_hint = scope.remote_scopes_model.get_string (found_iter, 3);
3965+ var screenshot_url = scope.remote_scopes_model.get_string (found_iter, 4);
3966+ try
3967+ {
3968+ if (icon_hint != "") icon = Icon.new_for_string (icon_hint);
3969+ if (screenshot_url != "") screenshot = Icon.new_for_string (screenshot_url);
3970+ }
3971+ catch (Error err)
3972+ {
3973+ warning ("%s", err.message);
3974+ }
3975+
3976+ if (icon == null) icon = ScopesScope.get_default_icon ();
3977+
3978+ preview = new Unity.ApplicationPreview (name,
3979+ "",
3980+ description,
3981+ icon,
3982+ screenshot);
3983+ }
3984+ else if (info != null)
3985+ {
3986+ var name = info.application_name;
3987+ if (name == null || name == "")
3988+ name = info.desktop_file;
3989+ var subtitle = "";
3990+ var description = info.description;
3991+ if (description != null)
3992+ description = Markup.escape_text(description);
3993+ Icon? icon = null;
3994+ Icon? screenshot = null;
3995+ try
3996+ {
3997+ if (info.icon != null && info.icon != "")
3998+ icon = Icon.new_for_string (info.icon);
3999+ }
4000+ catch (Error err)
4001+ {
4002+ warning ("%s", err.message);
4003+ }
4004+ if (icon == null)
4005+ icon = ScopesScope.get_default_icon ();
4006+
4007+ string version, screenshot_uri;
4008+ if (yield scope.appscope.get_version_and_screenshot (
4009+ scope_id, out version, out screenshot_uri))
4010+ {
4011+ if (version != "")
4012+ subtitle = _("Version %s").printf (version);
4013+ if (screenshot_uri != null)
4014+ {
4015+ File scr_file = File.new_for_uri (screenshot_uri);
4016+ screenshot = new FileIcon (scr_file);
4017+ }
4018+ }
4019+
4020+ preview = new Unity.ApplicationPreview (name,
4021+ subtitle,
4022+ description,
4023+ icon,
4024+ screenshot);
4025+ }
4026+ if (preview != null && scope_id != "home.scope" &&
4027+ scope_id != "applications/applications.scope" &&
4028+ scope_id != "applications/scopes.scope")
4029+ {
4030+ PreviewAction action;
4031+ preview.set_rating(-1.0f, 0);
4032+ if (scope_disabled)
4033+ {
4034+ action = new Unity.PreviewAction ("enable-scope", _("Enable"), null);
4035+ }
4036+ else
4037+ {
4038+ action = new Unity.PreviewAction ("disable-scope", _("Disable"), null);
4039+ }
4040+ preview.add_action (action);
4041+ }
4042+ return preview;
4043+ }
4044+ }
4045+}
4046
4047=== modified file 'src/software-center-data-cache.vala'
4048--- src/software-center-data-cache.vala 2012-10-04 16:15:09 +0000
4049+++ src/software-center-data-cache.vala 2013-08-06 08:19:29 +0000
4050@@ -43,7 +43,7 @@
4051 return false;
4052 }
4053
4054- public override SoftwareCenterData.AppInfo?[] get_items_for_category (string category_name) throws Error
4055+ public override async SoftwareCenterData.AppInfo?[] get_items_for_category (string category_name) throws Error
4056 {
4057 int64 last_update = 0;
4058 if (category_items_last_update.contains (category_name))
4059@@ -55,7 +55,7 @@
4060 {
4061 category_items_last_update[category_name] = last_update;
4062
4063- var data = base.get_items_for_category (category_name);
4064+ var data = yield base.get_items_for_category (category_name);
4065 var results = new Gee.ArrayList<SoftwareCenterData.AppInfo?> ();
4066 foreach (var item in data)
4067 {
4068@@ -68,4 +68,4 @@
4069 }
4070 }
4071
4072-}
4073\ No newline at end of file
4074+}
4075
4076=== modified file 'src/software-center-data-provider.vala'
4077--- src/software-center-data-provider.vala 2012-10-04 16:15:09 +0000
4078+++ src/software-center-data-provider.vala 2013-08-06 08:19:29 +0000
4079@@ -24,10 +24,10 @@
4080 [DBus (name = "com.ubuntu.SoftwareCenterDataProvider")]
4081 public interface SoftwareCenterDataProviderService: GLib.Object
4082 {
4083- public abstract HashTable<string, Variant> get_app_details (string appname, string pkgname) throws Error;
4084- public abstract string[] get_available_categories () throws Error;
4085- public abstract string[] get_available_subcategories () throws Error;
4086- public abstract SoftwareCenterData.AppInfo?[] get_items_for_category (string category_name) throws Error;
4087+ public abstract async HashTable<string, Variant> get_app_details (string appname, string pkgname) throws Error;
4088+ public abstract async string[] get_available_categories () throws Error;
4089+ public abstract async string[] get_available_subcategories () throws Error;
4090+ public abstract async SoftwareCenterData.AppInfo?[] get_items_for_category (string category_name) throws Error;
4091 }
4092
4093 public class SoftwareCenterDataProviderProxy: GLib.Object
4094@@ -37,9 +37,9 @@
4095 Bus.watch_name (BusType.SESSION, SCDP_DBUS_NAME, BusNameWatcherFlags.NONE, null, on_sc_dbus_name_vanished);
4096 }
4097
4098- public void connect_to () throws Error
4099+ private async void connect_to () throws Error
4100 {
4101- _service = Bus.get_proxy_sync (BusType.SESSION, SCDP_DBUS_NAME, SCDP_DBUS_PATH);
4102+ _service = yield Bus.get_proxy (BusType.SESSION, SCDP_DBUS_NAME, SCDP_DBUS_PATH);
4103 }
4104
4105 private void on_sc_dbus_name_vanished (DBusConnection conn, string name)
4106@@ -47,22 +47,22 @@
4107 _service = null;
4108 }
4109
4110- public SoftwareCenterData.AppDetailsData get_app_details (string appname, string pkgname) throws Error
4111+ public async SoftwareCenterData.AppDetailsData get_app_details (string appname, string pkgname) throws Error
4112 {
4113 if (_service == null)
4114- connect_to ();
4115+ yield connect_to ();
4116
4117- HashTable<string, Variant> data = _service.get_app_details (appname, pkgname);
4118+ HashTable<string, Variant> data = yield _service.get_app_details (appname, pkgname);
4119 var details = new SoftwareCenterData.AppDetailsData (data);
4120 return details;
4121 }
4122
4123- public virtual SoftwareCenterData.AppInfo?[] get_items_for_category (string category_name) throws Error
4124+ public virtual async SoftwareCenterData.AppInfo?[] get_items_for_category (string category_name) throws Error
4125 {
4126 if (_service == null)
4127- connect_to ();
4128+ yield connect_to ();
4129
4130- SoftwareCenterData.AppInfo?[] data = _service.get_items_for_category (category_name);
4131+ SoftwareCenterData.AppInfo?[] data = yield _service.get_items_for_category (category_name);
4132 return data;
4133 }
4134
4135
4136=== modified file 'tests/unit/software-center-data-provider-mock.vala'
4137--- tests/unit/software-center-data-provider-mock.vala 2012-10-09 16:38:12 +0000
4138+++ tests/unit/software-center-data-provider-mock.vala 2013-08-06 08:19:29 +0000
4139@@ -45,9 +45,9 @@
4140 data[category].clear ();
4141 }
4142
4143- public virtual SoftwareCenterData.AppInfo?[] get_items_for_category (string category_name) throws Error
4144+ public virtual async SoftwareCenterData.AppInfo?[] get_items_for_category (string category_name) throws Error
4145 {
4146 return data[category_name].to_array ();
4147 }
4148 }
4149-}
4150\ No newline at end of file
4151+}
4152
4153=== modified file 'tests/unit/test-software-center-data-cache.vala'
4154--- tests/unit/test-software-center-data-cache.vala 2012-10-08 15:48:54 +0000
4155+++ tests/unit/test-software-center-data-cache.vala 2013-08-06 08:19:29 +0000
4156@@ -40,6 +40,37 @@
4157 return false;
4158 }
4159
4160+ internal static bool run_with_timeout (MainLoop ml, uint timeout_ms = 5000)
4161+ {
4162+ bool timeout_reached = false;
4163+ var t_id = Timeout.add (timeout_ms, () =>
4164+ {
4165+ timeout_reached = true;
4166+ debug ("Timeout reached");
4167+ ml.quit ();
4168+ return false;
4169+ });
4170+
4171+ ml.run ();
4172+
4173+ if (!timeout_reached) Source.remove (t_id);
4174+
4175+ return !timeout_reached;
4176+ }
4177+
4178+ internal static SoftwareCenterData.AppInfo?[] get_items (SoftwareCenterDataCache cache, string category)
4179+ {
4180+ var ml = new MainLoop ();
4181+ SoftwareCenterData.AppInfo?[] items = null;
4182+ cache.get_items_for_category.begin (category, (obj, result) =>
4183+ {
4184+ items = cache.get_items_for_category.end (result);
4185+ ml.quit ();
4186+ });
4187+ assert (run_with_timeout (ml));
4188+ return items;
4189+ }
4190+
4191 internal static void test_category_cache ()
4192 {
4193 var cache = new SoftwareCenterDataCache (1); // lifetime of cache items = 1s
4194@@ -48,7 +79,7 @@
4195 cache.mock_add (TOP_RATED, "Evince", "evince");
4196
4197 // at this point items are fetched by cache and kept for 1s
4198- var items = cache.get_items_for_category (TOP_RATED);
4199+ var items = get_items (cache, TOP_RATED);
4200 assert (items.length == 1);
4201
4202 // remove & add new items to the mock, but get_items_for_category should still
4203@@ -57,7 +88,7 @@
4204 cache.mock_add (TOP_RATED, "Blender", "blender");
4205 cache.mock_add (TOP_RATED, "Firefox", "firefox");
4206 cache.mock_add (TOP_RATED, "Thunderbird", "thunderbird");
4207- var items_2 = cache.get_items_for_category (TOP_RATED);
4208+ var items_2 = get_items (cache, TOP_RATED);
4209 assert (items_2.length == 1);
4210
4211 assert (has_app (items_2, "Evince", "evince") == true);
4212@@ -66,10 +97,10 @@
4213 GLib.Thread.usleep (2 * 1000000);
4214
4215 // cache will fetch new items now.
4216- var items_3 = cache.get_items_for_category (TOP_RATED);
4217+ var items_3 = get_items (cache, TOP_RATED);
4218 assert (items_3.length == 3);
4219 assert (has_app (items_3, "Blender", "blender") == true);
4220 assert (has_app (items_3, "Firefox", "firefox") == true);
4221 assert (has_app (items_3, "Thunderbird", "thunderbird") == true);
4222 }
4223-}
4224\ No newline at end of file
4225+}

Subscribers

People subscribed via source and target branches