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
1=== modified file 'src/daemon.vala'
2--- src/daemon.vala 2012-02-20 12:35:16 +0000
3+++ src/daemon.vala 2012-03-12 11:46:18 +0000
4@@ -33,7 +33,10 @@
5 private Zeitgeist.Log log;
6 private Zeitgeist.Index zg_index;
7 private Zeitgeist.Monitor monitor;
8-
9+
10+ private Map<string, int> popularity_map;
11+ private bool popularities_dirty;
12+
13 /* The searcher for online material may be null if it fails to load
14 * the Xapian index from the Software Center */
15 private Unity.Package.Searcher? pkgsearcher;
16@@ -92,6 +95,15 @@
17 monitor.events_deleted.connect (mark_dirty);
18 log.install_monitor (monitor);
19
20+ popularity_map = new HashMap<string, int> ();
21+ popularities_dirty = true;
22+ // refresh the popularities every now and then
23+ Timeout.add_seconds (30 * 60, () =>
24+ {
25+ popularities_dirty = true;
26+ return true;
27+ });
28+
29 this.gp_settings = new Settings ("com.canonical.Unity.ApplicationsLens");
30 this.gp_settings.bind(DISPLAY_RECENT_APPS_KEY, this, "display_recent_apps", SettingsBindFlags.GET);
31 this.gp_settings.bind(DISPLAY_AVAILABLE_APPS_KEY, this, "display_available_apps", SettingsBindFlags.GET);
32@@ -143,10 +155,7 @@
33 /* Listen for changes to the lens scope search */
34 scope.search_changed.connect ((lens_search, search_type, cancellable) =>
35 {
36- if (search_type == SearchType.DEFAULT)
37- update_scope_search.begin (lens_search, cancellable);
38- else
39- update_global_search.begin (lens_search, cancellable);
40+ dispatch_search (lens_search, search_type, cancellable);
41 });
42
43 /* Re-do the search if the filters changed */
44@@ -194,6 +203,22 @@
45 lens.export ();
46 }
47
48+ private async void dispatch_search (LensSearch lens_search,
49+ SearchType search_type,
50+ Cancellable cancellable)
51+ {
52+ if (popularities_dirty)
53+ {
54+ yield update_popularities ();
55+ if (cancellable.is_cancelled ()) return;
56+ }
57+
58+ if (search_type == SearchType.DEFAULT)
59+ yield update_scope_search (lens_search, cancellable);
60+ else
61+ yield update_global_search (lens_search, cancellable);
62+ }
63+
64 /* Pre-populates the type queries so it's easier/faster to build our search */
65 private void populate_type_queries ()
66 {
67@@ -376,6 +401,41 @@
68 return s + " AND " + extract_type_query (options);
69 }
70
71+ private async void update_popularities ()
72+ {
73+ try
74+ {
75+ // simulate a kind of frecency
76+ var end_ts = Timestamp.now ();
77+ var start_ts = end_ts - Timestamp.WEEK * 3;
78+ var rs = yield log.find_events (new TimeRange (start_ts, end_ts),
79+ zg_templates,
80+ StorageState.ANY,
81+ 256,
82+ ResultType.MOST_POPULAR_SUBJECTS,
83+ null);
84+
85+ // most popular apps must have high value, so unknown apps (where
86+ // popularity_map[app] == 0 aren't considered super popular
87+ int relevancy = 256;
88+ foreach (unowned Event event in rs)
89+ {
90+ for (int i = 0; i < event.num_subjects (); i++)
91+ {
92+ unowned string uri = event.get_subject (i).get_uri ();
93+ if (uri == null) continue;
94+ popularity_map[uri] = relevancy;
95+ }
96+ relevancy--;
97+ }
98+ }
99+ catch (GLib.Error err)
100+ {
101+ warning ("%s", err.message);
102+ }
103+ popularities_dirty = false;
104+ }
105+
106 private async void update_scope_search (Unity.LensSearch search,
107 Cancellable cancellable)
108 {
109@@ -701,6 +761,25 @@
110
111 return icon;
112 }
113+
114+ private string extract_desktop_id (string? desktop_file,
115+ bool unmangle = false)
116+ {
117+ if (desktop_file == null) return "";
118+
119+ string desktop_id = Path.get_basename (desktop_file);
120+ /* S-C uses "app_name:desktop_id.desktop", get rid of the prefix */
121+ int colon_pos = desktop_id.index_of (":");
122+ if (unmangle && colon_pos > 0)
123+ {
124+ desktop_id = desktop_id[colon_pos+1:desktop_id.length];
125+ /* well it's still not real desktop_id, S-C converts slashes to "__"
126+ * if the desktop file is in /usr/share/applications */
127+ desktop_id = desktop_id.replace ("__", "-");
128+ }
129+
130+ return desktop_id;
131+ }
132
133 private void add_pkg_search_result (Unity.Package.SearchResult results,
134 Set<string> installed_uris,
135@@ -712,25 +791,39 @@
136 var appmanager = AppInfoManager.get_default();
137 var ratings_filter = scope.get_filter ("rating") as RatingsFilter;
138 uint n_added = 0;
139-
140+
141+ if (category != Category.AVAILABLE)
142+ {
143+ results.results.sort_with_data ((a, b) =>
144+ {
145+ /* We'll cluster the relevancies into a couple of bins, because
146+ * there can be multiple terms adding to the relevancy (especially
147+ * when doing one/two character prefix searches - ie a "f*" search
148+ * will have slightly higher relevancy for item with name "Search
149+ * _f_or _f_iles" than "_F_irefox", and that's not what we want)
150+ */
151+ int rel_a = a.relevancy / 10;
152+ int rel_b = b.relevancy / 10;
153+ if (rel_a == rel_b)
154+ {
155+ string id_a = extract_desktop_id (a.desktop_file);
156+ string id_b = extract_desktop_id (b.desktop_file);
157+ rel_a = popularity_map["application://" + id_a];
158+ rel_b = popularity_map["application://" + id_b];
159+ }
160+ return rel_b - rel_a; // we want higher relevancy first
161+ });
162+ }
163+
164 foreach (var pkginfo in results.results)
165 {
166 if (pkginfo.desktop_file == null)
167 continue;
168
169- string desktop_id = Path.get_basename (pkginfo.desktop_file);
170+ string desktop_id = extract_desktop_id (pkginfo.desktop_file,
171+ category == Category.AVAILABLE);
172 string full_path;
173
174- /* S-C uses "app_name:desktop_id.desktop", get rid of the prefix */
175- int colon_pos = desktop_id.index_of (":");
176- if (category == Category.AVAILABLE && colon_pos > 0)
177- {
178- desktop_id = desktop_id[colon_pos+1:desktop_id.length];
179- /* well it's still not real desktop_id, S-C converts slashes to "__"
180- * if the desktop file is in /usr/share/applications */
181- desktop_id = desktop_id.replace ("__", "-");
182- }
183-
184 AppInfo? app = appmanager.lookup (desktop_id);
185 full_path = appmanager.get_path (desktop_id);
186
187@@ -852,7 +945,11 @@
188 }
189 else
190 {
191- debug ("Declined activation of URI '%s': Expected URI scheme unity-install:// or unity-runner://", uri);
192+ /* Activation of standard application:// uris */
193+
194+ /* Make sure fresh install learns quickly */
195+ if (popularity_map.size <= 5) popularities_dirty = true;
196+
197 return new Unity.ActivationResponse(Unity.HandledType.NOT_HANDLED);
198 }
199
200
201=== modified file 'src/unity-package-search.cc'
202--- src/unity-package-search.cc 2012-02-20 11:08:48 +0000
203+++ src/unity-package-search.cc 2012-03-12 11:46:18 +0000
204@@ -437,10 +437,11 @@
205 try
206 {
207 // Perform search
208+ searcher->enquire->set_collapse_key(XAPIAN_VALUE_DESKTOP_FILE);
209 max_hits = (max_hits != 0 ? max_hits : searcher->db->get_doccount ());
210 searcher->enquire->set_query(query);
211 Xapian::MSet matches =
212- searcher->enquire->get_mset(0, max_hits);
213+ searcher->enquire->get_mset(0, max_hits);
214
215 // Retrieve the results, note that we build the result->results
216 // list in reverse order and then reverse it before we return it
217@@ -453,6 +454,7 @@
218 {
219 Xapian::Document doc = i.get_document();
220 UnityPackageInfo *pkginfo = _pkginfo_from_document (doc);
221+ pkginfo->relevancy = i.get_percent ();
222 result->results = g_slist_prepend (result->results, pkginfo);
223 }
224 catch (Xapian::Error e)
225
226=== modified file 'src/unity-package-search.h'
227--- src/unity-package-search.h 2011-03-09 14:50:33 +0000
228+++ src/unity-package-search.h 2012-03-12 11:46:18 +0000
229@@ -46,6 +46,7 @@
230 gchar *application_name;
231 gchar *desktop_file;
232 gchar *icon;
233+ gint relevancy;
234 } UnityPackageInfo;
235
236 #ifdef __cplusplus
237
238=== modified file 'vapi/unity-package-search.vapi'
239--- vapi/unity-package-search.vapi 2011-03-09 14:50:33 +0000
240+++ vapi/unity-package-search.vapi 2012-03-12 11:46:18 +0000
241@@ -34,7 +34,6 @@
242 public class SearchResult {
243 public GLib.SList<unowned PackageInfo> results;
244 public int num_hits;
245-
246 }
247
248 [Compact]
249@@ -44,6 +43,7 @@
250 public string application_name;
251 public string desktop_file;
252 public string icon;
253+ public int relevancy;
254 }
255 }
256 }

Subscribers

People subscribed via source and target branches