Merge lp:~kamstrup/libunity/faves-enumerator into lp:libunity

Proposed by Mikkel Kamstrup Erlandsen
Status: Merged
Approved by: Michal Hruby
Approved revision: 99
Merged at revision: 96
Proposed branch: lp:~kamstrup/libunity/faves-enumerator
Merge into: lp:libunity
Diff against target: 472 lines (+338/-9)
9 files modified
src/unity-appinfo-manager.vala (+7/-1)
src/unity-launcher.vala (+115/-2)
test/Makefile.am (+1/-0)
test/data/applications/asdasdasd.desktop (+11/-0)
test/data/applications/testapp1.desktop (+11/-0)
test/vala/Makefile.integration_tests (+10/-0)
test/vala/test-appinfo-manager.vala (+6/-6)
test/vala/test-launcher-integration.vala (+176/-0)
test/vala/test-launcher.vala (+1/-0)
To merge this branch: bzr merge lp:~kamstrup/libunity/faves-enumerator
Reviewer Review Type Date Requested Status
Michal Hruby (community) Approve
Review via email: mp+84470@code.launchpad.net

Description of the change

Add Unity.LauncherFavorites API for inspecting the favorite apps in the launcher

Note that this is a read-only API intentionally. The Unity policy is that the
user alone is in control of the launcher. You can listen for changes, and the
ordering is guaranteed to match that of Unity.

To post a comment you must log in.
Revision history for this message
Michal Hruby (mhr3) wrote :

Looks good, just two minor issues:

1) libunity is inconsistent in method names to get singleton instance (get_instance vs get_default), I'd rather see us using get_default which is more common in gobject world.
2) it'd be nice to add `public bool contains (string id)`, to support 'in' operator.

review: Needs Fixing
Revision history for this message
Mikkel Kamstrup Erlandsen (kamstrup) wrote :

> 1) libunity is inconsistent in method names to get singleton instance
> (get_instance vs get_default), I'd rather see us using get_default which is
> more common in gobject world.

I thought quite a lot about this when writing the API. The idea was that get_instance() had the semantics of a "pure" singleton, which you can only ever have precisely one of; while get_default() implies that you can have different instances, but normally just one.

But thinking about it again today I don't think API consumers is gonna appreciate this pedantry ;-) I'll rename it to get_default().

In the same spirit I think we should rename Unity.AppInfoManager.get_instance() as well - although that may be too hard an API break..?

> 2) it'd be nice to add `public bool contains (string id)`, to support 'in'
> operator.

Also thought about that one. If we could overload it with a version that takes an AppInfo then maybe, but this is too much of a Valaism to my liking. The lib is meant to have a sensible C and Python API as well. If we can fix it with some clever #defines or -custom.vala overrides then I am OK with it, but as a part of the ABI I don't like it...

Revision history for this message
Michal Hruby (mhr3) wrote :

> I thought quite a lot about this when writing the API. The idea was that
> get_instance() had the semantics of a "pure" singleton, which you can only
> ever have precisely one of; while get_default() implies that you can have
> different instances, but normally just one.

You can't really have "pure" singleton with gobject (g_object_new (...)), that's why everyone uses get_default.

> In the same spirit I think we should rename
> Unity.AppInfoManager.get_instance() as well - although that may be too hard an
> API break..?
>

I was thinking about that as well, but yea don't like the API break there, maybe we can add get_default and mark get_instance deprecated for now?

> > 2) it'd be nice to add `public bool contains (string id)`, to support 'in'
> > operator.
>
> Also thought about that one. If we could overload it with a version that takes
> an AppInfo then maybe, but this is too much of a Valaism to my liking. The lib
> is meant to have a sensible C and Python API as well. If we can fix it with
> some clever #defines or -custom.vala overrides then I am OK with it, but as a
> part of the ABI I don't like it...

Right, it'd be best to have that only in the vapi, unfortunately valac doesn't do -custom.vala when you're generating vapi from vala sources. The only thing we could do would be some sed :( Let's forget about that then...

Revision history for this message
Mikkel Kamstrup Erlandsen (kamstrup) wrote :

> > I thought quite a lot about this when writing the API. The idea was that
> > get_instance() had the semantics of a "pure" singleton, which you can only
> > ever have precisely one of; while get_default() implies that you can have
> > different instances, but normally just one.
>
> You can't really have "pure" singleton with gobject (g_object_new (...)),
> that's why everyone uses get_default.

Sure you can, just override GObject->constructor(), but that aside. We agree on the solution :-)

> > In the same spirit I think we should rename
> > Unity.AppInfoManager.get_instance() as well - although that may be too hard
> an
> > API break..?
> >
>
> I was thinking about that as well, but yea don't like the API break there,
> maybe we can add get_default and mark get_instance deprecated for now?

ok

> > > 2) it'd be nice to add `public bool contains (string id)`, to support 'in'
> > > operator.
> >
> > Also thought about that one. If we could overload it with a version that
> takes
> > an AppInfo then maybe, but this is too much of a Valaism to my liking. The
> lib
> > is meant to have a sensible C and Python API as well. If we can fix it with
> > some clever #defines or -custom.vala overrides then I am OK with it, but as
> a
> > part of the ABI I don't like it...
>
> Right, it'd be best to have that only in the vapi, unfortunately valac doesn't
> do -custom.vala when you're generating vapi from vala sources. The only thing
> we could do would be some sed :( Let's forget about that then...

We can look at it down the road if we find time for it

98. By Mikkel Kamstrup Erlandsen

Rename method LauncherFavorites.get_instance() to get_default()

99. By Mikkel Kamstrup Erlandsen

Deprecate AppInfoManager.get_instance() and rename it to get_default().

This make sure that all singletons in libunity are accessed similarly

Revision history for this message
Mikkel Kamstrup Erlandsen (kamstrup) wrote :

Pushed r99. Please re-review

Revision history for this message
Michal Hruby (mhr3) wrote :

> > You can't really have "pure" singleton with gobject (g_object_new (...)),
> > that's why everyone uses get_default.
>
> Sure you can, just override GObject->constructor(), but that aside. We agree
> on the solution :-)
>

Hmm, interesting, in that case people are just too lazy to do it I guess :)

The changes look fine, thanks.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'src/unity-appinfo-manager.vala'
2--- src/unity-appinfo-manager.vala 2011-12-01 12:42:22 +0000
3+++ src/unity-appinfo-manager.vala 2011-12-06 12:05:26 +0000
4@@ -86,10 +86,16 @@
5 }
6 }
7
8+ [Deprecated (replacement = "AppInfoManager.get_default")]
9+ public static AppInfoManager get_instance ()
10+ {
11+ return AppInfoManager.get_default ();
12+ }
13+
14 /**
15 * Get a ref to the singleton AppInfoManager
16 */
17- public static AppInfoManager get_instance ()
18+ public static AppInfoManager get_default ()
19 {
20 if (AppInfoManager.singleton == null)
21 AppInfoManager.singleton = new AppInfoManager();
22
23=== modified file 'src/unity-launcher.vala'
24--- src/unity-launcher.vala 2011-08-02 07:49:39 +0000
25+++ src/unity-launcher.vala 2011-12-06 12:05:26 +0000
26@@ -343,6 +343,119 @@
27 props.insert ("quicklist", l._object_path);
28
29 return props;
30- }
31-
32+ }
33+
34+ public class LauncherFavorites : Object
35+ {
36+ public signal void changed ();
37+
38+ private static LauncherFavorites? singleton = null;
39+ private Settings settings;
40+
41+ /* We keep both a map and a list in order to have the correct sorting */
42+ private HashTable<string,AppInfo?> fav_cache;
43+ private string[] fav_list;
44+
45+ private LauncherFavorites ()
46+ {
47+ settings = new Settings ("com.canonical.Unity.Launcher");
48+ fav_cache = new HashTable<string,AppInfo?> (str_hash, str_equal);
49+ reset_fav_cache ();
50+
51+ settings.changed["favorites"].connect (reset_fav_cache);
52+ }
53+
54+ private void reset_fav_cache ()
55+ {
56+ fav_cache.remove_all ();
57+ fav_list = settings.get_strv ("favorites");
58+ foreach (string id in fav_list)
59+ {
60+ fav_cache.insert (id, null);
61+ }
62+
63+ changed ();
64+ }
65+
66+ /**
67+ * Get the default singleton Unity.LauncherFavorites instance, creating it
68+ * dynamically if necessary.
69+ *
70+ * @return: (transfer none): The singleton Unity.LauncherFavorites.
71+ * If calling from C do not free this instance.
72+ *
73+ */
74+ public static unowned LauncherFavorites get_default ()
75+ {
76+ if (singleton == null)
77+ singleton = new LauncherFavorites ();
78+
79+ return singleton;
80+ }
81+
82+ public bool has_app_info (AppInfo appinfo) {
83+ if (appinfo.get_id () == null)
84+ {
85+ critical ("Can not look up favorite for AppInfo with NULL id");
86+ return false;
87+ }
88+
89+ return has_app_id (appinfo.get_id ());
90+ }
91+
92+ public bool has_app_id (string app_id)
93+ {
94+ return fav_cache.lookup_extended (app_id, null, null);
95+ }
96+
97+ public AppInfo? lookup (string app_id)
98+ {
99+ AppInfo? appinfo;
100+ bool has_id = fav_cache.lookup_extended (app_id, null, out appinfo);
101+
102+ /* If we don't have the appinfo cached, pull it out of the
103+ * AppInfoManager and cache it */
104+ if (has_id)
105+ {
106+ if (appinfo != null)
107+ return appinfo;
108+
109+ var appman = AppInfoManager.get_default ();
110+ appinfo = appman.lookup (app_id);
111+ fav_cache.insert (app_id, appinfo);
112+
113+ // FIXME: We really should return a dummy AppInfo for faves
114+ // where we can't find the .desktop file...
115+ if (appinfo == null)
116+ {
117+ critical ("Can't find AppInfo for favorite '%s'", app_id);
118+ return null;
119+ }
120+
121+ return appinfo;
122+ }
123+ return null;
124+ }
125+
126+ public string[] enumerate_ids ()
127+ {
128+ return fav_list;
129+ }
130+
131+ public AppInfo[] enumerate_app_infos ()
132+ {
133+ int i = 0;
134+ var infos = new AppInfo[fav_cache.size ()];
135+
136+ foreach (string id in fav_list)
137+ {
138+ var appinfo = lookup (id);
139+ infos[i] = appinfo;
140+ i++;
141+ }
142+
143+ return infos;
144+ }
145+ }
146+
147 } /* namespace */
148
149=== modified file 'test/Makefile.am'
150--- test/Makefile.am 2011-12-02 14:37:36 +0000
151+++ test/Makefile.am 2011-12-06 12:05:26 +0000
152@@ -2,5 +2,6 @@
153
154 EXTRA_DIST = \
155 data/applications/ubuntu-about.desktop \
156+ data/applications/asdasdasd.desktop \
157 data/scope0.scope \
158 data/test_desktop_file.desktop
159
160=== added file 'test/data/applications/asdasdasd.desktop'
161--- test/data/applications/asdasdasd.desktop 1970-01-01 00:00:00 +0000
162+++ test/data/applications/asdasdasd.desktop 2011-12-06 12:05:26 +0000
163@@ -0,0 +1,11 @@
164+[Desktop Entry]
165+Encoding=UTF-8
166+Name=libunity bogus app
167+Comment=For testing non-exisiting apps
168+Exec=xterm
169+Icon=distributor-logo
170+Terminal=false
171+Type=Application
172+Categories=GNOME;Application;Core;
173+StartupNotify=true
174+
175
176=== added file 'test/data/applications/testapp1.desktop'
177--- test/data/applications/testapp1.desktop 1970-01-01 00:00:00 +0000
178+++ test/data/applications/testapp1.desktop 2011-12-06 12:05:26 +0000
179@@ -0,0 +1,11 @@
180+[Desktop Entry]
181+Encoding=UTF-8
182+Name=libunity test app 1
183+Comment=Test App 1 for testing non-exisiting apps
184+Exec=xterm
185+Icon=distributor-logo
186+Terminal=false
187+Type=Application
188+Categories=GNOME;Application;Core;
189+StartupNotify=true
190+
191
192=== modified file 'test/vala/Makefile.integration_tests'
193--- test/vala/Makefile.integration_tests 2011-12-01 15:43:28 +0000
194+++ test/vala/Makefile.integration_tests 2011-12-06 12:05:26 +0000
195@@ -1,4 +1,5 @@
196 check_PROGRAMS += \
197+ test-launcher-integration \
198 test-sound-menu \
199 test-mpris-backend-client \
200 test-mpris-backend-server \
201@@ -11,6 +12,7 @@
202 if ENABLE_INTEGRATION_TESTS
203 TEST_PROGS += \
204 test-lens-scope-interactions \
205+ test-launcher-integration \
206 test-sound-menu \
207 test-mpris-backend \
208 test-mpris-backend-prop-updates
209@@ -27,6 +29,14 @@
210 @chmod +x $@
211
212 #########################################
213+## test-launcher-integration
214+#########################################
215+test_launcher_integration_SOURCES = \
216+ test-launcher-integration.vala
217+
218+test_launcher_integration_LDADD = $(test_libs)
219+
220+#########################################
221 ## test-sound-menu
222 #########################################
223 test_sound_menu_SOURCES = \
224
225=== modified file 'test/vala/test-appinfo-manager.vala'
226--- test/vala/test-appinfo-manager.vala 2011-09-27 09:48:39 +0000
227+++ test/vala/test-appinfo-manager.vala 2011-12-06 12:05:26 +0000
228@@ -43,14 +43,14 @@
229 /* Test that we can even get a valid ref to the manager */
230 internal static void test_allocation()
231 {
232- var manager = AppInfoManager.get_instance();
233+ var manager = AppInfoManager.get_default();
234 assert (manager is AppInfoManager);
235 }
236
237 /* Test that we can clear an empty manager */
238 internal static void test_clear_empty()
239 {
240- var manager = AppInfoManager.get_instance();
241+ var manager = AppInfoManager.get_default();
242 manager.clear ();
243 manager.clear ();
244 }
245@@ -58,7 +58,7 @@
246 /* Test that we can clear an empty manager */
247 internal static void test_sync_lookup_missing()
248 {
249- var manager = AppInfoManager.get_instance();
250+ var manager = AppInfoManager.get_default();
251 assert (manager.lookup ("_foobar.desktop") == null);
252 assert (manager.get_categories ("_foobar.desktop") == null);
253 assert (manager.get_keywords ("_foobar.desktop") == null);
254@@ -74,7 +74,7 @@
255
256 internal static async void do_test_async_lookup_missing (MainLoop mainloop)
257 {
258- var manager = AppInfoManager.get_instance();
259+ var manager = AppInfoManager.get_default();
260
261 try {
262 AppInfo? appinfo = yield manager.lookup_async ("_foobar.desktop");
263@@ -92,7 +92,7 @@
264 /* Test that we can lookup something which is indeed there */
265 internal static void test_sync_lookup_ok()
266 {
267- var manager = AppInfoManager.get_instance();
268+ var manager = AppInfoManager.get_default();
269
270 var info = manager.lookup ("ubuntu-about.desktop");
271 assert (info != null);
272@@ -128,7 +128,7 @@
273
274 internal static async void do_test_async_lookup_ok (MainLoop mainloop)
275 {
276- var manager = AppInfoManager.get_instance();
277+ var manager = AppInfoManager.get_default();
278
279 try{
280 var info = yield manager.lookup_async ("ubuntu-about.desktop");
281
282=== added file 'test/vala/test-launcher-integration.vala'
283--- test/vala/test-launcher-integration.vala 1970-01-01 00:00:00 +0000
284+++ test/vala/test-launcher-integration.vala 2011-12-06 12:05:26 +0000
285@@ -0,0 +1,176 @@
286+/*
287+ * Copyright (C) 2011 Canonical Ltd
288+ *
289+ * This program is free software: you can redistribute it and/or modify
290+ * it under the terms of the GNU General Public License version 3 as
291+ * published by the Free Software Foundation.
292+ *
293+ * This program is distributed in the hope that it will be useful,
294+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
295+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
296+ * GNU General Public License for more details.
297+ *
298+ * You should have received a copy of the GNU General Public License
299+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
300+ *
301+ * Authored by Mikkel Kamstrup Erlandsen <mikkel.kamstrup@canonical.com>
302+ *
303+ */
304+
305+/*
306+ * Despite the relative simplicity of this API we still need to relegate it
307+ * to the integration tests suite because it depends on the GSettings schema
308+ * from Unity.
309+ */
310+
311+public class Main
312+{
313+ public static int main (string[] args)
314+ {
315+ /* Prepend test search path for XDG_DATA_DIRS so we can find
316+ * our sample .desktop files */
317+ var datadirs = Environment.get_variable ("XDG_DATA_DIRS");
318+ if (datadirs != null)
319+ datadirs = Config.TESTDIR + "/data:" + datadirs;
320+ else
321+ datadirs = Config.TESTDIR + "/data";
322+ Environment.set_variable ("XDG_DATA_DIRS", datadirs, true);
323+
324+ /* Make sure we don't hose the user env with our tests... */
325+ Environment.set_variable ("GSETTINGS_BACKEND", "memory", true);
326+
327+ Test.init (ref args);
328+
329+ Test.add_data_func ("/Integration/Launcher/Favorites/UnknownApps",
330+ test_has_unknown_apps);
331+
332+ Test.add_data_func ("/Integration/Launcher/Favorites/HasSampleApps",
333+ test_has_sample_apps);
334+
335+ Test.add_data_func ("/Integration/Launcher/Favorites/Lookup",
336+ test_lookup);
337+
338+ Test.add_data_func ("/Integration/Launcher/Favorites/EnumerateIds",
339+ test_enumerate_ids);
340+
341+ Test.add_data_func ("/Integration/Launcher/Favorites/EnumerateAppInfos",
342+ test_enumerate_app_infos);
343+
344+ Test.add_data_func ("/Integration/Launcher/Favorites/Changes",
345+ test_changes);
346+
347+ Test.run ();
348+
349+ return 0;
350+ }
351+
352+ internal static void set_up () {
353+ assert ("memory" == Environment.get_variable ("GSETTINGS_BACKEND"));
354+ var settings = new Settings ("com.canonical.Unity.Launcher");
355+
356+ string[] faves = { "rhythmbox.desktop", "testapp1.desktop", "ubuntu-about.desktop" };
357+ settings.set_strv ("favorites", faves);
358+ }
359+
360+ internal static void test_has_unknown_apps ()
361+ {
362+ set_up ();
363+
364+ var faves = Unity.LauncherFavorites.get_default ();
365+ assert (!faves.has_app_id ("hulabaloola"));
366+
367+ var appinfo = new DesktopAppInfo ("asdasdasd.desktop");
368+ assert (appinfo != null);
369+ assert (!faves.has_app_info (appinfo));
370+ }
371+
372+ internal static void test_has_sample_apps ()
373+ {
374+ set_up ();
375+
376+ var faves = Unity.LauncherFavorites.get_default ();
377+ assert (faves.has_app_id ("rhythmbox.desktop"));
378+ assert (faves.has_app_id ("testapp1.desktop"));
379+ assert (faves.has_app_id ("ubuntu-about.desktop"));
380+
381+ var appinfo = new DesktopAppInfo ("rhythmbox.desktop");
382+ assert (faves.has_app_info (appinfo));
383+ appinfo = new DesktopAppInfo ("testapp1.desktop");
384+ assert (faves.has_app_info (appinfo));
385+ appinfo = new DesktopAppInfo ("ubuntu-about.desktop");
386+ assert (faves.has_app_info (appinfo));
387+ }
388+
389+ internal static void test_lookup ()
390+ {
391+ set_up ();
392+
393+ var faves = Unity.LauncherFavorites.get_default ();
394+ var appinfo = faves.lookup ("rhythmbox.desktop");
395+ assert (appinfo.get_name () == "Rhythmbox");
396+
397+ appinfo = faves.lookup ("testapp1.desktop");
398+ assert (appinfo.get_name () == "libunity test app 1");
399+
400+ appinfo = faves.lookup ("ubuntu-about.desktop");
401+ assert (appinfo.get_name () == "About Ubuntu");
402+
403+ appinfo = faves.lookup ("pakupachupikamachu.desktop");
404+ assert (appinfo == null);
405+ }
406+
407+ internal static void test_enumerate_ids ()
408+ {
409+ set_up ();
410+
411+ var faves = Unity.LauncherFavorites.get_default ();
412+ var ids = faves.enumerate_ids ();
413+
414+ assert (ids.length == 3);
415+ assert (ids[0] == "rhythmbox.desktop");
416+ assert (ids[1] == "testapp1.desktop");
417+ assert (ids[2] == "ubuntu-about.desktop");
418+ }
419+
420+ internal static void test_enumerate_app_infos ()
421+ {
422+ set_up ();
423+
424+ var faves = Unity.LauncherFavorites.get_default ();
425+ var infos = faves.enumerate_app_infos ();
426+
427+ assert (infos.length == 3);
428+ assert (infos[0].get_name() == "Rhythmbox");
429+ assert (infos[1].get_name() == "libunity test app 1");
430+ assert (infos[2].get_name() == "About Ubuntu");
431+ }
432+
433+ internal static void test_changes ()
434+ {
435+ set_up ();
436+
437+ var faves = Unity.LauncherFavorites.get_default ();
438+ bool was_changed = false;
439+
440+ faves.changed.connect ( () => {
441+ was_changed = true;
442+ });
443+
444+ /* Change the faves */
445+ var settings = new Settings ("com.canonical.Unity.Launcher");
446+ string[] new_faves = { "rhythmbox.desktop" };
447+ settings.set_strv ("favorites", new_faves);
448+
449+ /* Wait for updates */
450+ var ml = new MainLoop ();
451+ Idle.add (() => { ml.quit(); return false; });
452+ ml.run ();
453+
454+ assert (was_changed == true);
455+ assert (faves.enumerate_ids().length == 1);
456+ assert (faves.enumerate_ids()[0] == "rhythmbox.desktop");
457+ }
458+
459+
460+
461+}
462
463=== modified file 'test/vala/test-launcher.vala'
464--- test/vala/test-launcher.vala 2011-04-01 20:19:52 +0000
465+++ test/vala/test-launcher.vala 2011-12-06 12:05:26 +0000
466@@ -84,5 +84,6 @@
467
468 // FIXME: We're not testing the quicklist here, that's a bit tricky
469 }
470+
471 }
472 }

Subscribers

People subscribed via source and target branches