Merge lp:~stolowski/unity-lens-applications/show-more-apps-on-no-query into lp:unity-lens-applications

Proposed by Paweł Stołowski
Status: Merged
Approved by: Neil J. Patel
Approved revision: 302
Merged at revision: 302
Proposed branch: lp:~stolowski/unity-lens-applications/show-more-apps-on-no-query
Merge into: lp:unity-lens-applications
Diff against target: 508 lines (+275/-24)
8 files modified
src/Makefile.am (+1/-0)
src/daemon.vala (+108/-23)
src/runner.vala (+1/-1)
src/software-center-data-cache.vala (+71/-0)
src/software-center-data-provider.vala (+20/-0)
src/unity-package-search.cc (+62/-0)
src/unity-package-search.h (+9/-0)
vapi/unity-package-search.vapi (+3/-0)
To merge this branch: bzr merge lp:~stolowski/unity-lens-applications/show-more-apps-on-no-query
Reviewer Review Type Date Requested Status
Neil J. Patel (community) Approve
Review via email: mp+123983@code.launchpad.net

Commit message

List 10 'What's new' apps and the 12 'Top Rated' apps from Software Center in 'Apps Available for Download' before user performs any search if no filters are active.

List 100 top rated apps according to active filters if search string is empty.

Description of the change

List 10 'What's new' apps and the 12 'Top Rated' apps from Software Center in 'Apps Available for Download' before user performs any search if no filters are active.

List 100 top rated apps according to active filters if search string is empty.

WARNING! Don't approve until Software Center changes are merged.

To post a comment you must log in.
300. By Paweł Stołowski

Updated comment.

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

93 + var whats_new = sc_data_provider.get_items_for_category ("What’s New"); //make sure there is unicode char U+2019 (right quotation mark)

Very nasty that we need to pass an exact unicode string, imo the service should be fixed.

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

465 +typedef int (*AppFilterCallback)(UnityPackageInfo *, void *);

Shouldn't this return a gboolean instead? Or an enum perhaps? It's not obvious what the callback should be returning.

301. By Paweł Stołowski

Make filtering callback use bool return value.

302. By Paweł Stołowski

Use 'unity-whats-new' and 'unity-top-rated' categories instead of 'What's new' (requiring utf)
and 'Top Rated'. Requires lp:~mvo/software-center/extra-unity-categories.

Revision history for this message
Paweł Stołowski (stolowski) wrote :

Software Center has been fixed to provide unity-whats-new and unity-top-rated categories instead. Updated the code. Ready for re-review.

Revision history for this message
Neil J. Patel (njpatel) wrote :

Hi, this is approved pending that we can get the Software Centre bits landed for b2.

DO NOT APPROVE THIS UNTIL THEN.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'src/Makefile.am'
2--- src/Makefile.am 2012-08-08 16:30:35 +0000
3+++ src/Makefile.am 2012-09-17 15:59:25 +0000
4@@ -61,6 +61,7 @@
5 utils.vala \
6 aptd-client.vala \
7 software-center-data-provider.vala \
8+ software-center-data-cache.vala \
9 $(NULL)
10
11 unity-package-search.o : $(srcdir)/unity-package-search.cc $(srcdir)/unity-package-search.h
12
13=== modified file 'src/daemon.vala'
14--- src/daemon.vala 2012-09-13 17:12:02 +0000
15+++ src/daemon.vala 2012-09-17 15:59:25 +0000
16@@ -26,6 +26,20 @@
17
18 namespace Unity.ApplicationsLens {
19
20+ /* Number of 'Apps available for download' to show if no search query is provided AND a filter is active.
21+ It shouldn't be too high as this may impact lens performance.
22+ */
23+ const uint MAX_APP_FOR_DOWNLOAD_FOR_EMPTY_QUERY = 100;
24+
25+ /* Number of top rated apps to show in 'Apps available for download' if no search query is provided AND NO filter is active. */
26+ const uint MAX_TOP_RATED_APPS_FOR_EMPTY_QUERY = 12;
27+
28+ /* Number of "What's new" apps to show in 'Apps available for download' if no search query is provided AND NO filter is active. */
29+ const uint MAX_WHATS_NEW_APPS_FOR_EMPTY_QUERY = 10;
30+
31+ /* Time between queries to SoftwareCenterDataProvider */
32+ const int64 TOP_RATED_ITEMS_CACHE_LIFETIME = 24*3600; // 24 hours
33+
34 const string ICON_PATH = Config.DATADIR + "/icons/unity-icon-theme/places/svg/";
35 const string ICON_APP_INSTALL_PATH = Config.DATADIR + "/app-install/icons";
36
37@@ -462,6 +476,14 @@
38 }
39 }
40
41+ /* Returns TRUE if application is NOT installed */
42+ public bool filter_cb (Unity.Package.PackageInfo pkginfo)
43+ {
44+ var appmanager = AppInfoManager.get_default();
45+ AppInfo? app = appmanager.lookup (pkginfo.desktop_file);
46+ return app == null;
47+ }
48+
49 private async void update_scope_search (Unity.LensSearch search,
50 Cancellable cancellable)
51 {
52@@ -475,6 +497,7 @@
53
54 string pkg_search_string = prepare_pkg_search_string (search, filter);
55
56+ bool has_filter = (filter != null && filter.filtering);
57 bool has_search = !Utils.is_search_empty (search);
58
59 Timer timer = new Timer ();
60@@ -556,16 +579,43 @@
61 debug ("Entry search listed %i Available apps in %fms for query: %s",
62 pkgresults.num_hits, timer.elapsed ()*1000, pkg_search_string);
63 }
64- else
65+ else if (has_filter) /* Empty search string + active filters should get lots of results from selected categories */
66 {
67 timer.start ();
68 string? filter_query = prepare_pkg_search_string (search, filter);
69- var random_pkgresults = pkgsearcher.get_random_apps (filter_query, 12);
70- add_pkg_search_result (random_pkgresults, installed_uris, available_uris,
71- model, Category.AVAILABLE, 6);
72- timer.stop ();
73- debug ("Entry search listed %i random Available apps in %fms",
74- random_pkgresults.num_hits, timer.elapsed ()*1000);
75+
76+ var pkgresults = pkgsearcher.get_apps (filter_query, MAX_APP_FOR_DOWNLOAD_FOR_EMPTY_QUERY, filter_cb);
77+ add_pkg_search_result (pkgresults, installed_uris, available_uris, model, Category.AVAILABLE, MAX_APP_FOR_DOWNLOAD_FOR_EMPTY_QUERY);
78+ timer.stop ();
79+ debug ("Entry search listed %i Available apps in %fms",
80+ pkgresults.num_hits, timer.elapsed ()*1000);
81+ }
82+ else
83+ {
84+ timer.start ();
85+
86+ uint hits = 0;
87+ try
88+ {
89+ Set<string> duplicates_lookup = new HashSet<string> ();
90+
91+ if (sc_data_provider == null)
92+ sc_data_provider = new SoftwareCenterDataCache (TOP_RATED_ITEMS_CACHE_LIFETIME);
93+
94+ var whats_new = sc_data_provider.get_items_for_category ("unity-whats-new");
95+ hits = add_sc_category_results (whats_new, model, Category.AVAILABLE, ref duplicates_lookup, MAX_WHATS_NEW_APPS_FOR_EMPTY_QUERY);
96+
97+ var top_rated = sc_data_provider.get_items_for_category ("unity-top-rated");
98+ hits += add_sc_category_results (top_rated, model, Category.AVAILABLE, ref duplicates_lookup, MAX_TOP_RATED_APPS_FOR_EMPTY_QUERY);
99+ }
100+ catch (GLib.Error e)
101+ {
102+ warning ("Failed to get top rated apps: %s", e.message);
103+ }
104+
105+ timer.stop ();
106+ debug ("Entry search listed %u top rated/new available apps in %fms",
107+ hits, timer.elapsed ()*1000);
108 }
109 }
110
111@@ -749,25 +799,25 @@
112 return default_icon.to_string ();
113 }
114
115- public Icon find_pkg_icon (Unity.Package.PackageInfo pkginfo)
116+ public Icon find_pkg_icon (string desktop_file, string icon_name)
117 {
118- string desktop_id = Path.get_basename (pkginfo.desktop_file);
119+ string desktop_id = Path.get_basename (desktop_file);
120 bool installed = AppInfoManager.get_default().lookup (desktop_id) != null;
121
122 /* If the app is already installed we should be able to pull the
123 * icon from the theme */
124 if (installed)
125- return new ThemedIcon (pkginfo.icon);
126+ return new ThemedIcon (icon_name);
127
128 /* App is not installed - we need to find the right icon in the bowels
129 * of the software center */
130- if (pkginfo.icon.has_prefix ("/"))
131+ if (icon_name.has_prefix ("/"))
132 {
133- return new FileIcon (File.new_for_path (pkginfo.icon));
134+ return new FileIcon (File.new_for_path (icon_name));
135 }
136 else
137 {
138- Icon icon = file_icon_cache.lookup (pkginfo.icon);
139+ Icon icon = file_icon_cache.lookup (icon_name);
140
141 if (icon != null)
142 return icon;
143@@ -775,24 +825,24 @@
144 /* If the icon name contains a . it probably already have a
145 * type postfix - so test icon name directly */
146 string path;
147- if ("." in pkginfo.icon)
148+ if ("." in icon_name)
149 {
150- path = @"$(Config.DATADIR)/app-install/icons/$(pkginfo.icon)";
151+ path = @"$(Config.DATADIR)/app-install/icons/$(icon_name)";
152 if (FileUtils.test (path, FileTest.EXISTS))
153 {
154 icon = new FileIcon (File.new_for_path (path));
155- file_icon_cache.insert (pkginfo.icon, icon);
156+ file_icon_cache.insert (icon_name, icon);
157 return icon;
158 }
159 /* Try also software center cache dir */
160 path = Path.build_filename (Environment.get_user_cache_dir (),
161 "software-center",
162 "icons",
163- pkginfo.icon);
164+ icon_name);
165 if (FileUtils.test (path, FileTest.EXISTS))
166 {
167 icon = new FileIcon (File.new_for_path (path));
168- file_icon_cache.insert (pkginfo.icon, icon);
169+ file_icon_cache.insert (icon_name, icon);
170 return icon;
171 }
172 }
173@@ -800,12 +850,12 @@
174 /* Now try appending all the image extensions we know */
175 foreach (var ext in image_extensions)
176 {
177- path = @"$(Config.DATADIR)/app-install/icons/$(pkginfo.icon).$(ext)";
178+ path = @"$(Config.DATADIR)/app-install/icons/$(icon_name).$(ext)";
179 if (FileUtils.test (path, FileTest.EXISTS))
180 {
181 /* Got it! Cache the icon path and return the icon */
182 icon = new FileIcon (File.new_for_path (path));
183- file_icon_cache.insert (pkginfo.icon, icon);
184+ file_icon_cache.insert (icon_name, icon);
185 return icon;
186 }
187 }
188@@ -813,7 +863,7 @@
189
190 /* Cache the fact that we couldn't find this icon */
191 var icon = new ThemedIcon ("applications-other");
192- file_icon_cache.insert (pkginfo.icon, icon);
193+ file_icon_cache.insert (icon_name, icon);
194
195 return icon;
196 }
197@@ -880,6 +930,41 @@
198 return GLib.Path.get_basename (name);
199 }
200
201+ /**
202+ * Add all results obtained from SoftwareCenterDataProvider
203+ */
204+ private uint add_sc_category_results (SoftwareCenterDataProviderService.AppInfo?[] results,
205+ Dee.Model model,
206+ Category category,
207+ ref Set<string> duplicates_lookup,
208+ uint max_results)
209+ {
210+ uint i = 0;
211+ foreach (SoftwareCenterDataProviderService.AppInfo app in results)
212+ {
213+ string uri = @"unity-install://$(app.package_name)/$(app.application_name)";
214+
215+ if (uri in duplicates_lookup)
216+ {
217+ continue;
218+ }
219+
220+ Icon icon = find_pkg_icon (app.desktop_file, app.icon);
221+
222+ model.append (uri, icon.to_string (),
223+ category,
224+ "application/x-desktop",
225+ app.application_name,
226+ "", //comment
227+ "file://" + app.desktop_file);
228+ duplicates_lookup.add (uri);
229+ i++;
230+ if (i == max_results)
231+ break;
232+ }
233+ return i;
234+ }
235+
236 private void add_pkg_search_result (Unity.Package.SearchResult results,
237 Set<string> installed_uris,
238 Set<string> available_uris,
239@@ -953,7 +1038,7 @@
240 available_uris.add (uri);
241 }
242
243- Icon icon = find_pkg_icon (pkginfo);
244+ Icon icon = find_pkg_icon (pkginfo.desktop_file, pkginfo.icon);
245
246 model.append (uri, icon.to_string (),
247 category,"application/x-desktop",
248@@ -1159,7 +1244,7 @@
249 try {
250 if (sc_data_provider == null)
251 {
252- sc_data_provider = new SoftwareCenterDataProviderProxy ();
253+ sc_data_provider = new SoftwareCenterDataCache (TOP_RATED_ITEMS_CACHE_LIFETIME);
254 sc_data_provider.connect_to ();
255 }
256
257
258=== modified file 'src/runner.vala'
259--- src/runner.vala 2012-08-14 14:32:55 +0000
260+++ src/runner.vala 2012-09-17 15:59:25 +0000
261@@ -415,7 +415,7 @@
262 continue;
263
264 // pick the first one
265- icon = this.daemon.find_pkg_icon (pkginfo);
266+ icon = this.daemon.find_pkg_icon (pkginfo.desktop_file, pkginfo.icon);
267 return exec_string;
268
269 }
270
271=== added file 'src/software-center-data-cache.vala'
272--- src/software-center-data-cache.vala 1970-01-01 00:00:00 +0000
273+++ src/software-center-data-cache.vala 2012-09-17 15:59:25 +0000
274@@ -0,0 +1,71 @@
275+/*
276+ * Copyright (C) 2012 Canonical Ltd
277+ *
278+ * This program is free software: you can redistribute it and/or modify
279+ * it under the terms of the GNU General Public License version 3 as
280+ * published by the Free Software Foundation.
281+ *
282+ * This program is distributed in the hope that it will be useful,
283+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
284+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
285+ * GNU General Public License for more details.
286+ *
287+ * You should have received a copy of the GNU General Public License
288+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
289+ *
290+ * Authored by Pawel Stolowski <pawel.stolowski@canonical.com>
291+ */
292+
293+namespace Unity.ApplicationsLens {
294+
295+public class SoftwareCenterDataCache: SoftwareCenterDataProviderProxy
296+{
297+ public int64 category_items_lifetime { get; set; }
298+ private HashTable<string, int64?> category_items_last_update;
299+ private HashTable<string, Gee.ArrayList<SoftwareCenterDataProviderService.AppInfo?>> category_items_cached;
300+
301+ public SoftwareCenterDataCache (int64 category_items_lifetime)
302+ {
303+ category_items_last_update = new HashTable<string, int64?> (str_hash, str_equal);
304+ category_items_cached = new HashTable<string, Gee.ArrayList<SoftwareCenterDataProviderService.AppInfo?>> (str_hash, str_equal);
305+ this.category_items_lifetime = category_items_lifetime;
306+ }
307+
308+ internal static bool outdated (ref int64 last_update, int64 lifetime)
309+ {
310+ var current_time = new GLib.DateTime.now_utc ();
311+ int64 current_unix_time = current_time.to_unix ();
312+ if (current_unix_time > last_update + lifetime)
313+ {
314+ last_update = current_unix_time;
315+ return true;
316+ }
317+ return false;
318+ }
319+
320+ public override SoftwareCenterDataProviderService.AppInfo?[] get_items_for_category (string category_name) throws Error
321+ {
322+ int64 last_update = 0;
323+ if (category_items_last_update.contains (category_name))
324+ {
325+ last_update = category_items_last_update[category_name];
326+ }
327+
328+ if (outdated (ref last_update, category_items_lifetime))
329+ {
330+ category_items_last_update[category_name] = last_update;
331+
332+ var data = base.get_items_for_category (category_name);
333+ var results = new Gee.ArrayList<SoftwareCenterDataProviderService.AppInfo?> ();
334+ foreach (var item in data)
335+ {
336+ results.add (item);
337+ }
338+ category_items_cached[category_name] = results;
339+ return data;
340+ }
341+ return category_items_cached[category_name].to_array ();
342+ }
343+}
344+
345+}
346\ No newline at end of file
347
348=== modified file 'src/software-center-data-provider.vala'
349--- src/software-center-data-provider.vala 2012-09-06 08:11:17 +0000
350+++ src/software-center-data-provider.vala 2012-09-17 15:59:25 +0000
351@@ -24,7 +24,18 @@
352 [DBus (name = "com.ubuntu.SoftwareCenterDataProvider")]
353 public interface SoftwareCenterDataProviderService: GLib.Object
354 {
355+ public struct AppInfo
356+ {
357+ string application_name;
358+ string package_name;
359+ string icon;
360+ string desktop_file;
361+ }
362+
363 public abstract HashTable<string, Variant> get_app_details (string appname, string pkgname) throws Error;
364+ public abstract string[] get_available_categories () throws Error;
365+ public abstract string[] get_available_subcategories () throws Error;
366+ public abstract AppInfo?[] get_items_for_category (string category_name) throws Error;
367 }
368
369 public class SoftwareCenterDataProviderProxy: GLib.Object
370@@ -125,6 +136,15 @@
371 return data;
372 }
373
374+ public virtual SoftwareCenterDataProviderService.AppInfo?[] get_items_for_category (string category_name) throws Error
375+ {
376+ if (_service == null)
377+ connect_to ();
378+
379+ SoftwareCenterDataProviderService.AppInfo?[] data = _service.get_items_for_category (category_name);
380+ return data;
381+ }
382+
383 private SoftwareCenterDataProviderService _service;
384 }
385 }
386
387=== modified file 'src/unity-package-search.cc'
388--- src/unity-package-search.cc 2012-08-08 16:57:47 +0000
389+++ src/unity-package-search.cc 2012-09-17 15:59:25 +0000
390@@ -475,6 +475,68 @@
391 return result;
392 }
393
394+/**
395+ * Get applications matching given xapian filter query and additionally filter results out
396+ * using AppFilterCallback, until n_apps matching apps are found.
397+ * Results are filtered out if AppFilterCallback returns FALSE.
398+ */
399+UnityPackageSearchResult* unity_package_searcher_get_apps (UnityPackageSearcher *searcher,
400+ const gchar *filter_query,
401+ guint n_apps,
402+ AppFilterCallback cb,
403+ gpointer data)
404+{
405+ UnityPackageSearchResult *result;
406+ GHashTable *unique;
407+ guint num_matches = 0;
408+
409+ g_return_val_if_fail (searcher != NULL, NULL);
410+
411+ unique = g_hash_table_new (g_str_hash, g_str_equal);
412+
413+ result = g_slice_new0 (UnityPackageSearchResult);
414+
415+ g_debug ("FILTER %s", filter_query);
416+
417+ Xapian::Query query;
418+ try
419+ {
420+ query = searcher->query_parser->parse_query (filter_query, QUERY_PARSER_FILTER_FLAGS);
421+ searcher->enquire->set_sort_by_relevance ();
422+ searcher->enquire->set_query(query);
423+
424+ Xapian::MSet matches = searcher->enquire->get_mset(0, searcher->db->get_doccount ());
425+ Xapian::MSetIterator iter = matches.begin();
426+ while (num_matches < n_apps && iter != matches.end())
427+ {
428+ Xapian::Document doc = iter.get_document ();
429+ UnityPackageInfo *pkginfo = _pkginfo_from_document (doc);
430+
431+ if (g_hash_table_lookup_extended (unique, pkginfo->package_name, NULL, NULL) || cb(pkginfo, data) == FALSE)
432+ {
433+ _free_package_info (pkginfo);
434+ }
435+ else
436+ {
437+ g_hash_table_insert (unique, pkginfo->package_name, NULL);
438+ result->results = g_slist_prepend (result->results, pkginfo);
439+ num_matches++;
440+ }
441+ ++iter;
442+ }
443+ }
444+ catch (Xapian::Error e)
445+ {
446+ g_debug ("Error getting apps for query '%s': %s", filter_query, e.get_msg().c_str());
447+ return g_slice_new0 (UnityPackageSearchResult);
448+ }
449+
450+ g_hash_table_unref (unique);
451+
452+ result->num_hits = num_matches;
453+ return result;
454+}
455+
456 UnityPackageSearchResult*
457 unity_package_searcher_get_random_apps (UnityPackageSearcher *searcher,
458 const gchar *filter_query,
459
460=== modified file 'src/unity-package-search.h'
461--- src/unity-package-search.h 2012-07-26 14:07:49 +0000
462+++ src/unity-package-search.h 2012-09-17 15:59:25 +0000
463@@ -49,9 +49,12 @@
464 gint relevancy;
465 } UnityPackageInfo;
466
467+typedef gboolean (*AppFilterCallback)(UnityPackageInfo *, void *);
468+
469 #ifdef __cplusplus
470 extern "C" {
471 #endif
472+
473
474 UnityPackageSearcher* unity_package_searcher_new ();
475
476@@ -69,6 +72,12 @@
477 const gchar *filter_query,
478 guint n_apps);
479
480+UnityPackageSearchResult* unity_package_searcher_get_apps (UnityPackageSearcher *searcher,
481+ const gchar *filter_query,
482+ guint n_apps,
483+ AppFilterCallback cb,
484+ gpointer data);
485+
486 void unity_package_search_result_free (UnityPackageSearchResult *result);
487
488 void unity_package_package_info_free (UnityPackageInfo *pkginfo);
489
490=== modified file 'vapi/unity-package-search.vapi'
491--- vapi/unity-package-search.vapi 2012-08-08 13:52:11 +0000
492+++ vapi/unity-package-search.vapi 2012-09-17 15:59:25 +0000
493@@ -18,6 +18,8 @@
494 BY_RELEVANCY
495 }
496
497+ public delegate bool AppFilterCallback (PackageInfo pkginfo);
498+
499 [Compact]
500 [CCode (free_function = "unity_package_searcher_free", cheader_filename = "unity-package-search.h")]
501 public class Searcher {
502@@ -27,6 +29,7 @@
503 public Searcher.for_menu(GMenu.Tree menu);
504 public SearchResult search (string search_string, uint max_hits, Unity.Package.SearchType search_type, Unity.Package.Sort sort);
505 public SearchResult get_random_apps (string? filter_query, uint n_apps);
506+ public SearchResult get_apps (string? filter_query, uint n_apps, AppFilterCallback cb);
507 public PackageInfo? get_by_desktop_file (string desktop_file);
508 }
509

Subscribers

People subscribed via source and target branches