Merge lp:~mhr3/unity-lens-applications/secondary-sorting into lp:unity-lens-applications

Proposed by Michal Hruby
Status: Merged
Approved by: Mikkel Kamstrup Erlandsen
Approved revision: 276
Merged at revision: 276
Proposed branch: lp:~mhr3/unity-lens-applications/secondary-sorting
Merge into: lp:unity-lens-applications
Diff against target: 256 lines (+120/-20)
4 files modified
src/daemon.vala (+115/-18)
src/unity-package-search.cc (+3/-1)
src/unity-package-search.h (+1/-0)
vapi/unity-package-search.vapi (+1/-1)
To merge this branch: bzr merge lp:~mhr3/unity-lens-applications/secondary-sorting
Reviewer Review Type Date Requested Status
Mikkel Kamstrup Erlandsen (community) Approve
Review via email: mp+96993@code.launchpad.net

Commit message

Cluster applications according to relevancy of the match when searching and if multiple applications fall into the same cluster sort them according to frequency of use.

Description of the change

Implements secondary sorting of applications based on frequency of use.

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

Note that there are pending changes to the lens view (it should display just 6 recent apps and these should be only filtered out on search), so currently this affects only the home view.

274. By Michal Hruby

Make sure we check the cancellable after updating popularities

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

52 + if (popularities_dirty)
53 + yield update_popularities ();

Check for cancellation after here?

91 + popularity_map[uri] = relevancy--;

Should the decrement not be moved to the outermost for-loop? If one event has many apps they should all rank alike, no?

139 + results.results.sort_with_data ((a, b) =>
140 + {
141 + int rel_a = a.relevancy / 10;
142 + int rel_b = b.relevancy / 10;
        ...

Can you add a comment explaining the sorting please (also noting the rounding trick)? For the uninitiated this will look like dark magic.

I was wondering if it would be a significant speed increase if you simply iterated the results GSList but then inserted the hits sorted in a GSequence. I don't know the performance profile of sorting a GSList, but I'd assume it to be weak... But this prolly clashes with code elsewhere expecting the glist..?

184 + /* Make sure fresh install learns quickly */
185 + if (popularity_map.size <= 5) popularities_dirty = true;

Like this tweak. Clever you are!

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

75 + // simulate a kind of frecency

Also - this is not "frecency", but rather "relequency" or "frelevancy" whatever you wanna clal it :-)

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

> kamstrup wrote 4 minutes ago:
> 52 + if (popularities_dirty)
> 53 + yield update_popularities ();
>
> Check for cancellation after here?

8 minutes ago
Make sure we check the cancellable after updating popularities

Received the tachyons ;)

> Should the decrement not be moved to the outermost for-loop? If one event has many apps they should all rank alike, no?

I suppose you're right, at least I don't have to use 256 event limit and magical 512 constant five lines later.

> Can you add a comment explaining the sorting please (also noting the rounding trick)? For the uninitiated this will look like dark magic.

Fixed.

> I was wondering if it would be a significant speed increase if you simply iterated the results GSList but then inserted the hits sorted in a GSequence. I don't know the performance profile of sorting a GSList, but I'd assume it to be weak... But this prolly clashes with code elsewhere expecting the glist..?

I was thinking about this as well, but since there are usually just a few hundred apps (and only a few dozen search hits), the time spent building a sequence and sorting it would probably be higher than just sorting the linked list. (moreover looking at glib's sources it seems to be using mergesort which is O(N logN) therefore a non-issue really).

275. By Michal Hruby

Explain the magical sorting

276. By Michal Hruby

Treat multi-subject events equally

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

> 75 + // simulate a kind of frecency
>
> Also - this is not "frecency", but rather "relequency" or "frelevancy"
> whatever you wanna clal it :-)

Actually that was a comment on the time range I'm using there (only last 3 weeks), so it indeed is frecency in this context. (so overall the sorting is "frecevancy" ;)

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

Great! You have me sold!

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'src/daemon.vala'
--- src/daemon.vala 2012-02-20 12:35:16 +0000
+++ src/daemon.vala 2012-03-12 11:46:18 +0000
@@ -33,7 +33,10 @@
33 private Zeitgeist.Log log;33 private Zeitgeist.Log log;
34 private Zeitgeist.Index zg_index;34 private Zeitgeist.Index zg_index;
35 private Zeitgeist.Monitor monitor;35 private Zeitgeist.Monitor monitor;
36 36
37 private Map<string, int> popularity_map;
38 private bool popularities_dirty;
39
37 /* The searcher for online material may be null if it fails to load40 /* The searcher for online material may be null if it fails to load
38 * the Xapian index from the Software Center */41 * the Xapian index from the Software Center */
39 private Unity.Package.Searcher? pkgsearcher;42 private Unity.Package.Searcher? pkgsearcher;
@@ -92,6 +95,15 @@
92 monitor.events_deleted.connect (mark_dirty);95 monitor.events_deleted.connect (mark_dirty);
93 log.install_monitor (monitor);96 log.install_monitor (monitor);
94 97
98 popularity_map = new HashMap<string, int> ();
99 popularities_dirty = true;
100 // refresh the popularities every now and then
101 Timeout.add_seconds (30 * 60, () =>
102 {
103 popularities_dirty = true;
104 return true;
105 });
106
95 this.gp_settings = new Settings ("com.canonical.Unity.ApplicationsLens");107 this.gp_settings = new Settings ("com.canonical.Unity.ApplicationsLens");
96 this.gp_settings.bind(DISPLAY_RECENT_APPS_KEY, this, "display_recent_apps", SettingsBindFlags.GET);108 this.gp_settings.bind(DISPLAY_RECENT_APPS_KEY, this, "display_recent_apps", SettingsBindFlags.GET);
97 this.gp_settings.bind(DISPLAY_AVAILABLE_APPS_KEY, this, "display_available_apps", SettingsBindFlags.GET);109 this.gp_settings.bind(DISPLAY_AVAILABLE_APPS_KEY, this, "display_available_apps", SettingsBindFlags.GET);
@@ -143,10 +155,7 @@
143 /* Listen for changes to the lens scope search */155 /* Listen for changes to the lens scope search */
144 scope.search_changed.connect ((lens_search, search_type, cancellable) =>156 scope.search_changed.connect ((lens_search, search_type, cancellable) =>
145 {157 {
146 if (search_type == SearchType.DEFAULT)158 dispatch_search (lens_search, search_type, cancellable);
147 update_scope_search.begin (lens_search, cancellable);
148 else
149 update_global_search.begin (lens_search, cancellable);
150 });159 });
151 160
152 /* Re-do the search if the filters changed */161 /* Re-do the search if the filters changed */
@@ -194,6 +203,22 @@
194 lens.export ();203 lens.export ();
195 }204 }
196205
206 private async void dispatch_search (LensSearch lens_search,
207 SearchType search_type,
208 Cancellable cancellable)
209 {
210 if (popularities_dirty)
211 {
212 yield update_popularities ();
213 if (cancellable.is_cancelled ()) return;
214 }
215
216 if (search_type == SearchType.DEFAULT)
217 yield update_scope_search (lens_search, cancellable);
218 else
219 yield update_global_search (lens_search, cancellable);
220 }
221
197 /* Pre-populates the type queries so it's easier/faster to build our search */222 /* Pre-populates the type queries so it's easier/faster to build our search */
198 private void populate_type_queries ()223 private void populate_type_queries ()
199 {224 {
@@ -376,6 +401,41 @@
376 return s + " AND " + extract_type_query (options);401 return s + " AND " + extract_type_query (options);
377 }402 }
378403
404 private async void update_popularities ()
405 {
406 try
407 {
408 // simulate a kind of frecency
409 var end_ts = Timestamp.now ();
410 var start_ts = end_ts - Timestamp.WEEK * 3;
411 var rs = yield log.find_events (new TimeRange (start_ts, end_ts),
412 zg_templates,
413 StorageState.ANY,
414 256,
415 ResultType.MOST_POPULAR_SUBJECTS,
416 null);
417
418 // most popular apps must have high value, so unknown apps (where
419 // popularity_map[app] == 0 aren't considered super popular
420 int relevancy = 256;
421 foreach (unowned Event event in rs)
422 {
423 for (int i = 0; i < event.num_subjects (); i++)
424 {
425 unowned string uri = event.get_subject (i).get_uri ();
426 if (uri == null) continue;
427 popularity_map[uri] = relevancy;
428 }
429 relevancy--;
430 }
431 }
432 catch (GLib.Error err)
433 {
434 warning ("%s", err.message);
435 }
436 popularities_dirty = false;
437 }
438
379 private async void update_scope_search (Unity.LensSearch search,439 private async void update_scope_search (Unity.LensSearch search,
380 Cancellable cancellable)440 Cancellable cancellable)
381 {441 {
@@ -701,6 +761,25 @@
701 761
702 return icon;762 return icon;
703 }763 }
764
765 private string extract_desktop_id (string? desktop_file,
766 bool unmangle = false)
767 {
768 if (desktop_file == null) return "";
769
770 string desktop_id = Path.get_basename (desktop_file);
771 /* S-C uses "app_name:desktop_id.desktop", get rid of the prefix */
772 int colon_pos = desktop_id.index_of (":");
773 if (unmangle && colon_pos > 0)
774 {
775 desktop_id = desktop_id[colon_pos+1:desktop_id.length];
776 /* well it's still not real desktop_id, S-C converts slashes to "__"
777 * if the desktop file is in /usr/share/applications */
778 desktop_id = desktop_id.replace ("__", "-");
779 }
780
781 return desktop_id;
782 }
704 783
705 private void add_pkg_search_result (Unity.Package.SearchResult results,784 private void add_pkg_search_result (Unity.Package.SearchResult results,
706 Set<string> installed_uris,785 Set<string> installed_uris,
@@ -712,25 +791,39 @@
712 var appmanager = AppInfoManager.get_default();791 var appmanager = AppInfoManager.get_default();
713 var ratings_filter = scope.get_filter ("rating") as RatingsFilter;792 var ratings_filter = scope.get_filter ("rating") as RatingsFilter;
714 uint n_added = 0;793 uint n_added = 0;
715 794
795 if (category != Category.AVAILABLE)
796 {
797 results.results.sort_with_data ((a, b) =>
798 {
799 /* We'll cluster the relevancies into a couple of bins, because
800 * there can be multiple terms adding to the relevancy (especially
801 * when doing one/two character prefix searches - ie a "f*" search
802 * will have slightly higher relevancy for item with name "Search
803 * _f_or _f_iles" than "_F_irefox", and that's not what we want)
804 */
805 int rel_a = a.relevancy / 10;
806 int rel_b = b.relevancy / 10;
807 if (rel_a == rel_b)
808 {
809 string id_a = extract_desktop_id (a.desktop_file);
810 string id_b = extract_desktop_id (b.desktop_file);
811 rel_a = popularity_map["application://" + id_a];
812 rel_b = popularity_map["application://" + id_b];
813 }
814 return rel_b - rel_a; // we want higher relevancy first
815 });
816 }
817
716 foreach (var pkginfo in results.results)818 foreach (var pkginfo in results.results)
717 {819 {
718 if (pkginfo.desktop_file == null)820 if (pkginfo.desktop_file == null)
719 continue;821 continue;
720 822
721 string desktop_id = Path.get_basename (pkginfo.desktop_file);823 string desktop_id = extract_desktop_id (pkginfo.desktop_file,
824 category == Category.AVAILABLE);
722 string full_path;825 string full_path;
723826
724 /* S-C uses "app_name:desktop_id.desktop", get rid of the prefix */
725 int colon_pos = desktop_id.index_of (":");
726 if (category == Category.AVAILABLE && colon_pos > 0)
727 {
728 desktop_id = desktop_id[colon_pos+1:desktop_id.length];
729 /* well it's still not real desktop_id, S-C converts slashes to "__"
730 * if the desktop file is in /usr/share/applications */
731 desktop_id = desktop_id.replace ("__", "-");
732 }
733
734 AppInfo? app = appmanager.lookup (desktop_id);827 AppInfo? app = appmanager.lookup (desktop_id);
735 full_path = appmanager.get_path (desktop_id);828 full_path = appmanager.get_path (desktop_id);
736 829
@@ -852,7 +945,11 @@
852 }945 }
853 else946 else
854 {947 {
855 debug ("Declined activation of URI '%s': Expected URI scheme unity-install:// or unity-runner://", uri);948 /* Activation of standard application:// uris */
949
950 /* Make sure fresh install learns quickly */
951 if (popularity_map.size <= 5) popularities_dirty = true;
952
856 return new Unity.ActivationResponse(Unity.HandledType.NOT_HANDLED);953 return new Unity.ActivationResponse(Unity.HandledType.NOT_HANDLED);
857 }954 }
858955
859956
=== modified file 'src/unity-package-search.cc'
--- src/unity-package-search.cc 2012-02-20 11:08:48 +0000
+++ src/unity-package-search.cc 2012-03-12 11:46:18 +0000
@@ -437,10 +437,11 @@
437 try437 try
438 {438 {
439 // Perform search439 // Perform search
440 searcher->enquire->set_collapse_key(XAPIAN_VALUE_DESKTOP_FILE);
440 max_hits = (max_hits != 0 ? max_hits : searcher->db->get_doccount ());441 max_hits = (max_hits != 0 ? max_hits : searcher->db->get_doccount ());
441 searcher->enquire->set_query(query);442 searcher->enquire->set_query(query);
442 Xapian::MSet matches =443 Xapian::MSet matches =
443 searcher->enquire->get_mset(0, max_hits); 444 searcher->enquire->get_mset(0, max_hits);
444445
445 // Retrieve the results, note that we build the result->results446 // Retrieve the results, note that we build the result->results
446 // list in reverse order and then reverse it before we return it447 // list in reverse order and then reverse it before we return it
@@ -453,6 +454,7 @@
453 {454 {
454 Xapian::Document doc = i.get_document();455 Xapian::Document doc = i.get_document();
455 UnityPackageInfo *pkginfo = _pkginfo_from_document (doc);456 UnityPackageInfo *pkginfo = _pkginfo_from_document (doc);
457 pkginfo->relevancy = i.get_percent ();
456 result->results = g_slist_prepend (result->results, pkginfo);458 result->results = g_slist_prepend (result->results, pkginfo);
457 }459 }
458 catch (Xapian::Error e)460 catch (Xapian::Error e)
459461
=== modified file 'src/unity-package-search.h'
--- src/unity-package-search.h 2011-03-09 14:50:33 +0000
+++ src/unity-package-search.h 2012-03-12 11:46:18 +0000
@@ -46,6 +46,7 @@
46 gchar *application_name;46 gchar *application_name;
47 gchar *desktop_file;47 gchar *desktop_file;
48 gchar *icon;48 gchar *icon;
49 gint relevancy;
49} UnityPackageInfo;50} UnityPackageInfo;
5051
51#ifdef __cplusplus52#ifdef __cplusplus
5253
=== modified file 'vapi/unity-package-search.vapi'
--- vapi/unity-package-search.vapi 2011-03-09 14:50:33 +0000
+++ vapi/unity-package-search.vapi 2012-03-12 11:46:18 +0000
@@ -34,7 +34,6 @@
34 public class SearchResult {34 public class SearchResult {
35 public GLib.SList<unowned PackageInfo> results;35 public GLib.SList<unowned PackageInfo> results;
36 public int num_hits;36 public int num_hits;
37
38 }37 }
39 38
40 [Compact]39 [Compact]
@@ -44,6 +43,7 @@
44 public string application_name;43 public string application_name;
45 public string desktop_file;44 public string desktop_file;
46 public string icon;45 public string icon;
46 public int relevancy;
47 }47 }
48 }48 }
49}49}

Subscribers

People subscribed via source and target branches