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
=== modified file 'src/unity-appinfo-manager.vala'
--- src/unity-appinfo-manager.vala 2011-12-01 12:42:22 +0000
+++ src/unity-appinfo-manager.vala 2011-12-06 12:05:26 +0000
@@ -86,10 +86,16 @@
86 }86 }
87 }87 }
88 88
89 [Deprecated (replacement = "AppInfoManager.get_default")]
90 public static AppInfoManager get_instance ()
91 {
92 return AppInfoManager.get_default ();
93 }
94
89 /**95 /**
90 * Get a ref to the singleton AppInfoManager96 * Get a ref to the singleton AppInfoManager
91 */97 */
92 public static AppInfoManager get_instance ()98 public static AppInfoManager get_default ()
93 {99 {
94 if (AppInfoManager.singleton == null)100 if (AppInfoManager.singleton == null)
95 AppInfoManager.singleton = new AppInfoManager(); 101 AppInfoManager.singleton = new AppInfoManager();
96102
=== modified file 'src/unity-launcher.vala'
--- src/unity-launcher.vala 2011-08-02 07:49:39 +0000
+++ src/unity-launcher.vala 2011-12-06 12:05:26 +0000
@@ -343,6 +343,119 @@
343 props.insert ("quicklist", l._object_path);343 props.insert ("quicklist", l._object_path);
344 344
345 return props;345 return props;
346 } 346 }
347347
348 public class LauncherFavorites : Object
349 {
350 public signal void changed ();
351
352 private static LauncherFavorites? singleton = null;
353 private Settings settings;
354
355 /* We keep both a map and a list in order to have the correct sorting */
356 private HashTable<string,AppInfo?> fav_cache;
357 private string[] fav_list;
358
359 private LauncherFavorites ()
360 {
361 settings = new Settings ("com.canonical.Unity.Launcher");
362 fav_cache = new HashTable<string,AppInfo?> (str_hash, str_equal);
363 reset_fav_cache ();
364
365 settings.changed["favorites"].connect (reset_fav_cache);
366 }
367
368 private void reset_fav_cache ()
369 {
370 fav_cache.remove_all ();
371 fav_list = settings.get_strv ("favorites");
372 foreach (string id in fav_list)
373 {
374 fav_cache.insert (id, null);
375 }
376
377 changed ();
378 }
379
380 /**
381 * Get the default singleton Unity.LauncherFavorites instance, creating it
382 * dynamically if necessary.
383 *
384 * @return: (transfer none): The singleton Unity.LauncherFavorites.
385 * If calling from C do not free this instance.
386 *
387 */
388 public static unowned LauncherFavorites get_default ()
389 {
390 if (singleton == null)
391 singleton = new LauncherFavorites ();
392
393 return singleton;
394 }
395
396 public bool has_app_info (AppInfo appinfo) {
397 if (appinfo.get_id () == null)
398 {
399 critical ("Can not look up favorite for AppInfo with NULL id");
400 return false;
401 }
402
403 return has_app_id (appinfo.get_id ());
404 }
405
406 public bool has_app_id (string app_id)
407 {
408 return fav_cache.lookup_extended (app_id, null, null);
409 }
410
411 public AppInfo? lookup (string app_id)
412 {
413 AppInfo? appinfo;
414 bool has_id = fav_cache.lookup_extended (app_id, null, out appinfo);
415
416 /* If we don't have the appinfo cached, pull it out of the
417 * AppInfoManager and cache it */
418 if (has_id)
419 {
420 if (appinfo != null)
421 return appinfo;
422
423 var appman = AppInfoManager.get_default ();
424 appinfo = appman.lookup (app_id);
425 fav_cache.insert (app_id, appinfo);
426
427 // FIXME: We really should return a dummy AppInfo for faves
428 // where we can't find the .desktop file...
429 if (appinfo == null)
430 {
431 critical ("Can't find AppInfo for favorite '%s'", app_id);
432 return null;
433 }
434
435 return appinfo;
436 }
437 return null;
438 }
439
440 public string[] enumerate_ids ()
441 {
442 return fav_list;
443 }
444
445 public AppInfo[] enumerate_app_infos ()
446 {
447 int i = 0;
448 var infos = new AppInfo[fav_cache.size ()];
449
450 foreach (string id in fav_list)
451 {
452 var appinfo = lookup (id);
453 infos[i] = appinfo;
454 i++;
455 }
456
457 return infos;
458 }
459 }
460
348} /* namespace */461} /* namespace */
349462
=== modified file 'test/Makefile.am'
--- test/Makefile.am 2011-12-02 14:37:36 +0000
+++ test/Makefile.am 2011-12-06 12:05:26 +0000
@@ -2,5 +2,6 @@
22
3EXTRA_DIST = \3EXTRA_DIST = \
4 data/applications/ubuntu-about.desktop \4 data/applications/ubuntu-about.desktop \
5 data/applications/asdasdasd.desktop \
5 data/scope0.scope \6 data/scope0.scope \
6 data/test_desktop_file.desktop7 data/test_desktop_file.desktop
78
=== added file 'test/data/applications/asdasdasd.desktop'
--- test/data/applications/asdasdasd.desktop 1970-01-01 00:00:00 +0000
+++ test/data/applications/asdasdasd.desktop 2011-12-06 12:05:26 +0000
@@ -0,0 +1,11 @@
1[Desktop Entry]
2Encoding=UTF-8
3Name=libunity bogus app
4Comment=For testing non-exisiting apps
5Exec=xterm
6Icon=distributor-logo
7Terminal=false
8Type=Application
9Categories=GNOME;Application;Core;
10StartupNotify=true
11
012
=== added file 'test/data/applications/testapp1.desktop'
--- test/data/applications/testapp1.desktop 1970-01-01 00:00:00 +0000
+++ test/data/applications/testapp1.desktop 2011-12-06 12:05:26 +0000
@@ -0,0 +1,11 @@
1[Desktop Entry]
2Encoding=UTF-8
3Name=libunity test app 1
4Comment=Test App 1 for testing non-exisiting apps
5Exec=xterm
6Icon=distributor-logo
7Terminal=false
8Type=Application
9Categories=GNOME;Application;Core;
10StartupNotify=true
11
012
=== modified file 'test/vala/Makefile.integration_tests'
--- test/vala/Makefile.integration_tests 2011-12-01 15:43:28 +0000
+++ test/vala/Makefile.integration_tests 2011-12-06 12:05:26 +0000
@@ -1,4 +1,5 @@
1check_PROGRAMS += \1check_PROGRAMS += \
2 test-launcher-integration \
2 test-sound-menu \3 test-sound-menu \
3 test-mpris-backend-client \4 test-mpris-backend-client \
4 test-mpris-backend-server \5 test-mpris-backend-server \
@@ -11,6 +12,7 @@
11if ENABLE_INTEGRATION_TESTS12if ENABLE_INTEGRATION_TESTS
12TEST_PROGS += \13TEST_PROGS += \
13 test-lens-scope-interactions \14 test-lens-scope-interactions \
15 test-launcher-integration \
14 test-sound-menu \16 test-sound-menu \
15 test-mpris-backend \17 test-mpris-backend \
16 test-mpris-backend-prop-updates18 test-mpris-backend-prop-updates
@@ -27,6 +29,14 @@
27 @chmod +x $@29 @chmod +x $@
2830
29#########################################31#########################################
32## test-launcher-integration
33#########################################
34test_launcher_integration_SOURCES = \
35 test-launcher-integration.vala
36
37test_launcher_integration_LDADD = $(test_libs)
38
39#########################################
30## test-sound-menu40## test-sound-menu
31#########################################41#########################################
32test_sound_menu_SOURCES = \42test_sound_menu_SOURCES = \
3343
=== modified file 'test/vala/test-appinfo-manager.vala'
--- test/vala/test-appinfo-manager.vala 2011-09-27 09:48:39 +0000
+++ test/vala/test-appinfo-manager.vala 2011-12-06 12:05:26 +0000
@@ -43,14 +43,14 @@
43 /* Test that we can even get a valid ref to the manager */43 /* Test that we can even get a valid ref to the manager */
44 internal static void test_allocation()44 internal static void test_allocation()
45 {45 {
46 var manager = AppInfoManager.get_instance();46 var manager = AppInfoManager.get_default();
47 assert (manager is AppInfoManager);47 assert (manager is AppInfoManager);
48 }48 }
4949
50 /* Test that we can clear an empty manager */50 /* Test that we can clear an empty manager */
51 internal static void test_clear_empty()51 internal static void test_clear_empty()
52 {52 {
53 var manager = AppInfoManager.get_instance();53 var manager = AppInfoManager.get_default();
54 manager.clear ();54 manager.clear ();
55 manager.clear ();55 manager.clear ();
56 }56 }
@@ -58,7 +58,7 @@
58 /* Test that we can clear an empty manager */58 /* Test that we can clear an empty manager */
59 internal static void test_sync_lookup_missing()59 internal static void test_sync_lookup_missing()
60 {60 {
61 var manager = AppInfoManager.get_instance();61 var manager = AppInfoManager.get_default();
62 assert (manager.lookup ("_foobar.desktop") == null);62 assert (manager.lookup ("_foobar.desktop") == null);
63 assert (manager.get_categories ("_foobar.desktop") == null);63 assert (manager.get_categories ("_foobar.desktop") == null);
64 assert (manager.get_keywords ("_foobar.desktop") == null);64 assert (manager.get_keywords ("_foobar.desktop") == null);
@@ -74,7 +74,7 @@
74 74
75 internal static async void do_test_async_lookup_missing (MainLoop mainloop)75 internal static async void do_test_async_lookup_missing (MainLoop mainloop)
76 {76 {
77 var manager = AppInfoManager.get_instance();77 var manager = AppInfoManager.get_default();
78 78
79 try {79 try {
80 AppInfo? appinfo = yield manager.lookup_async ("_foobar.desktop");80 AppInfo? appinfo = yield manager.lookup_async ("_foobar.desktop");
@@ -92,7 +92,7 @@
92 /* Test that we can lookup something which is indeed there */92 /* Test that we can lookup something which is indeed there */
93 internal static void test_sync_lookup_ok()93 internal static void test_sync_lookup_ok()
94 {94 {
95 var manager = AppInfoManager.get_instance(); 95 var manager = AppInfoManager.get_default();
96 96
97 var info = manager.lookup ("ubuntu-about.desktop");97 var info = manager.lookup ("ubuntu-about.desktop");
98 assert (info != null);98 assert (info != null);
@@ -128,7 +128,7 @@
128 128
129 internal static async void do_test_async_lookup_ok (MainLoop mainloop)129 internal static async void do_test_async_lookup_ok (MainLoop mainloop)
130 {130 {
131 var manager = AppInfoManager.get_instance();131 var manager = AppInfoManager.get_default();
132 132
133 try{133 try{
134 var info = yield manager.lookup_async ("ubuntu-about.desktop");134 var info = yield manager.lookup_async ("ubuntu-about.desktop");
135135
=== added file 'test/vala/test-launcher-integration.vala'
--- test/vala/test-launcher-integration.vala 1970-01-01 00:00:00 +0000
+++ test/vala/test-launcher-integration.vala 2011-12-06 12:05:26 +0000
@@ -0,0 +1,176 @@
1/*
2 * Copyright (C) 2011 Canonical Ltd
3 *
4 * This program is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License version 3 as
6 * published by the Free Software Foundation.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 *
16 * Authored by Mikkel Kamstrup Erlandsen <mikkel.kamstrup@canonical.com>
17 *
18 */
19
20/*
21 * Despite the relative simplicity of this API we still need to relegate it
22 * to the integration tests suite because it depends on the GSettings schema
23 * from Unity.
24 */
25
26public class Main
27{
28 public static int main (string[] args)
29 {
30 /* Prepend test search path for XDG_DATA_DIRS so we can find
31 * our sample .desktop files */
32 var datadirs = Environment.get_variable ("XDG_DATA_DIRS");
33 if (datadirs != null)
34 datadirs = Config.TESTDIR + "/data:" + datadirs;
35 else
36 datadirs = Config.TESTDIR + "/data";
37 Environment.set_variable ("XDG_DATA_DIRS", datadirs, true);
38
39 /* Make sure we don't hose the user env with our tests... */
40 Environment.set_variable ("GSETTINGS_BACKEND", "memory", true);
41
42 Test.init (ref args);
43
44 Test.add_data_func ("/Integration/Launcher/Favorites/UnknownApps",
45 test_has_unknown_apps);
46
47 Test.add_data_func ("/Integration/Launcher/Favorites/HasSampleApps",
48 test_has_sample_apps);
49
50 Test.add_data_func ("/Integration/Launcher/Favorites/Lookup",
51 test_lookup);
52
53 Test.add_data_func ("/Integration/Launcher/Favorites/EnumerateIds",
54 test_enumerate_ids);
55
56 Test.add_data_func ("/Integration/Launcher/Favorites/EnumerateAppInfos",
57 test_enumerate_app_infos);
58
59 Test.add_data_func ("/Integration/Launcher/Favorites/Changes",
60 test_changes);
61
62 Test.run ();
63
64 return 0;
65 }
66
67 internal static void set_up () {
68 assert ("memory" == Environment.get_variable ("GSETTINGS_BACKEND"));
69 var settings = new Settings ("com.canonical.Unity.Launcher");
70
71 string[] faves = { "rhythmbox.desktop", "testapp1.desktop", "ubuntu-about.desktop" };
72 settings.set_strv ("favorites", faves);
73 }
74
75 internal static void test_has_unknown_apps ()
76 {
77 set_up ();
78
79 var faves = Unity.LauncherFavorites.get_default ();
80 assert (!faves.has_app_id ("hulabaloola"));
81
82 var appinfo = new DesktopAppInfo ("asdasdasd.desktop");
83 assert (appinfo != null);
84 assert (!faves.has_app_info (appinfo));
85 }
86
87 internal static void test_has_sample_apps ()
88 {
89 set_up ();
90
91 var faves = Unity.LauncherFavorites.get_default ();
92 assert (faves.has_app_id ("rhythmbox.desktop"));
93 assert (faves.has_app_id ("testapp1.desktop"));
94 assert (faves.has_app_id ("ubuntu-about.desktop"));
95
96 var appinfo = new DesktopAppInfo ("rhythmbox.desktop");
97 assert (faves.has_app_info (appinfo));
98 appinfo = new DesktopAppInfo ("testapp1.desktop");
99 assert (faves.has_app_info (appinfo));
100 appinfo = new DesktopAppInfo ("ubuntu-about.desktop");
101 assert (faves.has_app_info (appinfo));
102 }
103
104 internal static void test_lookup ()
105 {
106 set_up ();
107
108 var faves = Unity.LauncherFavorites.get_default ();
109 var appinfo = faves.lookup ("rhythmbox.desktop");
110 assert (appinfo.get_name () == "Rhythmbox");
111
112 appinfo = faves.lookup ("testapp1.desktop");
113 assert (appinfo.get_name () == "libunity test app 1");
114
115 appinfo = faves.lookup ("ubuntu-about.desktop");
116 assert (appinfo.get_name () == "About Ubuntu");
117
118 appinfo = faves.lookup ("pakupachupikamachu.desktop");
119 assert (appinfo == null);
120 }
121
122 internal static void test_enumerate_ids ()
123 {
124 set_up ();
125
126 var faves = Unity.LauncherFavorites.get_default ();
127 var ids = faves.enumerate_ids ();
128
129 assert (ids.length == 3);
130 assert (ids[0] == "rhythmbox.desktop");
131 assert (ids[1] == "testapp1.desktop");
132 assert (ids[2] == "ubuntu-about.desktop");
133 }
134
135 internal static void test_enumerate_app_infos ()
136 {
137 set_up ();
138
139 var faves = Unity.LauncherFavorites.get_default ();
140 var infos = faves.enumerate_app_infos ();
141
142 assert (infos.length == 3);
143 assert (infos[0].get_name() == "Rhythmbox");
144 assert (infos[1].get_name() == "libunity test app 1");
145 assert (infos[2].get_name() == "About Ubuntu");
146 }
147
148 internal static void test_changes ()
149 {
150 set_up ();
151
152 var faves = Unity.LauncherFavorites.get_default ();
153 bool was_changed = false;
154
155 faves.changed.connect ( () => {
156 was_changed = true;
157 });
158
159 /* Change the faves */
160 var settings = new Settings ("com.canonical.Unity.Launcher");
161 string[] new_faves = { "rhythmbox.desktop" };
162 settings.set_strv ("favorites", new_faves);
163
164 /* Wait for updates */
165 var ml = new MainLoop ();
166 Idle.add (() => { ml.quit(); return false; });
167 ml.run ();
168
169 assert (was_changed == true);
170 assert (faves.enumerate_ids().length == 1);
171 assert (faves.enumerate_ids()[0] == "rhythmbox.desktop");
172 }
173
174
175
176}
0177
=== modified file 'test/vala/test-launcher.vala'
--- test/vala/test-launcher.vala 2011-04-01 20:19:52 +0000
+++ test/vala/test-launcher.vala 2011-12-06 12:05:26 +0000
@@ -84,5 +84,6 @@
84 84
85 // FIXME: We're not testing the quicklist here, that's a bit tricky85 // FIXME: We're not testing the quicklist here, that's a bit tricky
86 }86 }
87
87 }88 }
88}89}

Subscribers

People subscribed via source and target branches