1 /*
2 Tracks which menu items get used by users and works to promote those
3 higher in the search rankings than others.
4
5 Copyright 2011 Canonical Ltd.
6
7 Authors:
8 Ted Gould <ted@canonical.com>
9
10 This program is free software: you can redistribute it and/or modify it
11 under the terms of the GNU General Public License version 3, as published
12 by the Free Software Foundation.
13
14 This program is distributed in the hope that it will be useful, but
15 WITHOUT ANY WARRANTY; without even the implied warranties of
16 MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
17 PURPOSE. See the GNU General Public License for more details.
18
19 You should have received a copy of the GNU General Public License along
20 with this program. If not, see <http://www.gnu.org/licenses/>.
21 */
22
23 #define G_LOG_DOMAIN "usagetracker"
24
25 #ifdef HAVE_CONFIG_H
26 #include "config.h"
27 #endif
28
29 #include "usage-tracker.h"
30
31 #include <glib.h>
32 #include <glib/gstdio.h>
33 #include <gio/gio.h>
34 #include <sqlite3.h>
35 #include "load-app-info.h"
36 #include "create-db.h"
37 #include "hudsettings.h"
38
39 struct _UsageTrackerPrivate {
40 gchar * cachefile;
41 sqlite3 * db;
42 guint drop_timer;
43
44 /* SQL Statements */
45 sqlite3_stmt * insert_entry;
46 sqlite3_stmt * entry_count;
47 sqlite3_stmt * delete_aged;
48 sqlite3_stmt * application_count;
49 };
50
51 typedef enum {
52 SQL_VAR_APPLICATION = 1,
53 SQL_VAR_ENTRY = 2
54 } sql_variables;
55
56 #define SQL_VARS_APPLICATION "1"
57 #define SQL_VARS_ENTRY "2"
58
59 #define USAGE_TRACKER_GET_PRIVATE(o) \
60 (G_TYPE_INSTANCE_GET_PRIVATE ((o), USAGE_TRACKER_TYPE, UsageTrackerPrivate))
61
62 static void usage_tracker_dispose (GObject *object);
63 static void usage_tracker_finalize (GObject *object);
64 static void cleanup_db (UsageTracker * self);
65 static void configure_db (UsageTracker * self);
66 static void prepare_statements (UsageTracker * self);
67 static void build_db (UsageTracker * self);
68 static gboolean drop_entries (gpointer user_data);
69 static void check_app_init (UsageTracker * self, const gchar * application);
70
71 G_DEFINE_TYPE (UsageTracker, usage_tracker, G_TYPE_OBJECT);
72
73 static void
74 usage_tracker_class_init (UsageTrackerClass *klass)
75 {
76 GObjectClass *object_class = G_OBJECT_CLASS (klass);
77
78 g_type_class_add_private (klass, sizeof (UsageTrackerPrivate));
79
80 object_class->dispose = usage_tracker_dispose;
81 object_class->finalize = usage_tracker_finalize;
82
83 return;
84 }
85
86 static void
87 usage_tracker_init (UsageTracker *self)
88 {
89 self->priv = USAGE_TRACKER_GET_PRIVATE(self);
90
91 self->priv->cachefile = NULL;
92 self->priv->db = NULL;
93 self->priv->drop_timer = 0;
94
95 self->priv->insert_entry = NULL;
96 self->priv->entry_count = NULL;
97 self->priv->delete_aged = NULL;
98 self->priv->application_count = NULL;
99
100 configure_db(self);
101
102 /* Drop entries daily if we run for a really long time */
103 self->priv->drop_timer = g_timeout_add_seconds(24 * 60 * 60, drop_entries, self);
104
105 return;
106 }
107
108 static void
109 usage_tracker_dispose (GObject *object)
110 {
111 UsageTracker * self = USAGE_TRACKER(object);
112
113 cleanup_db(self);
114
115 if (self->priv->drop_timer != 0) {
116 g_source_remove(self->priv->drop_timer);
117 self->priv->drop_timer = 0;
118 }
119
120 G_OBJECT_CLASS (usage_tracker_parent_class)->dispose (object);
121 return;
122 }
123
124 static void
125 usage_tracker_finalize (GObject *object)
126 {
127 UsageTracker * self = USAGE_TRACKER(object);
128
129 if (self->priv->cachefile != NULL) {
130 g_free(self->priv->cachefile);
131 self->priv->cachefile = NULL;
132 }
133
134 G_OBJECT_CLASS (usage_tracker_parent_class)->finalize (object);
135 return;
136 }
137
138 /* Small function to make sure we get all the DB components cleaned
139 up in the spaces we need them */
140 static void
141 cleanup_db (UsageTracker * self)
142 {
143 if (self->priv->insert_entry != NULL) {
144 sqlite3_finalize(self->priv->insert_entry);
145 self->priv->insert_entry = NULL;
146 }
147
148 if (self->priv->entry_count != NULL) {
149 sqlite3_finalize(self->priv->entry_count);
150 self->priv->entry_count = NULL;
151 }
152
153 if (self->priv->delete_aged != NULL) {
154 sqlite3_finalize(self->priv->delete_aged);
155 self->priv->delete_aged = NULL;
156 }
157
158 if (self->priv->application_count != NULL) {
159 sqlite3_finalize(self->priv->application_count);
160 self->priv->application_count = NULL;
161 }
162
163 if (self->priv->db != NULL) {
164 sqlite3_close(self->priv->db);
165 self->priv->db = NULL;
166 }
167
168 return;
169 }
170
171 UsageTracker *
172 usage_tracker_new (void)
173 {
174 return g_object_new(USAGE_TRACKER_TYPE, NULL);
175 }
176
177 /* Configure which database we should be using */
178 static void
179 configure_db (UsageTracker * self)
180 {
181 /* Removing the previous database */
182 cleanup_db(self);
183
184 if (self->priv->cachefile != NULL) {
185 g_free(self->priv->cachefile);
186 self->priv->cachefile = NULL;
187 }
188
189 /* Determine where his database should be built */
190 gboolean usage_data = hud_settings.store_usage_data;
191
192 if (g_getenv("HUD_NO_STORE_USAGE_DATA") != NULL) {
193 usage_data = FALSE;
194 }
195
196 if (usage_data) {
197 g_debug("Storing usage data on filesystem");
198 }
199
200 /* Setting up the new database */
201 gboolean db_exists = FALSE;
202
203 if (usage_data) {
204 /* If we're storing the usage data we need to figure out
205 how to do it on disk */
206
207 const gchar * basecachedir = g_getenv("HUD_CACHE_DIR");
208 if (basecachedir == NULL) {
209 basecachedir = g_get_user_cache_dir();
210 }
211
212 gchar * cachedir = g_build_filename(basecachedir, "indicator-appmenu", NULL);
213 if (!g_file_test(cachedir, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR)) {
CID 11391 - CHECKED_RETURN
Calling function "mkdir(cachedir, 448U)" without checking return value. This library function may fail and return an error code.
No check of the return value of "mkdir(cachedir, 448U)".
214 g_mkdir(cachedir, 1 << 6 | 1 << 7 | 1 << 8); // 700
215 }
216 g_free(cachedir);
217
218 self->priv->cachefile = g_build_filename(basecachedir, "indicator-appmenu", "hud-usage-log.sqlite", NULL);
219 db_exists = g_file_test(self->priv->cachefile, G_FILE_TEST_EXISTS);
220 int open_status = sqlite3_open(self->priv->cachefile, &self->priv->db);
221
222 if (open_status != SQLITE_OK) {
223 g_warning("Error building LRU DB");
224 sqlite3_close(self->priv->db);
225 self->priv->db = NULL;
226 }
227 } else {
228 /* If we're not storing it, let's make an in memory database
229 so that we can use the app-info, and get better, but we don't
230 give anyone that data. */
231 self->priv->cachefile = g_strdup(":memory:");
232
233 int open_status = sqlite3_open(self->priv->cachefile, &self->priv->db);
234
235 if (open_status != SQLITE_OK) {
236 g_warning("Error building LRU DB");
237 sqlite3_close(self->priv->db);
238 self->priv->db = NULL;
239 }
240 }
241
242 if (self->priv->db != NULL && !db_exists) {
243 build_db(self);
244 }
245
246 prepare_statements(self);
247
248 drop_entries(self);
249
250 return;
251 }
252
253 /* Build all the prepared statments */
254 static void
255 prepare_statements (UsageTracker * self)
256 {
257 if (self->priv->db == NULL) {
258 return;
259 }
260
261 /* These should never happen, but let's just check to make sure */
262 g_return_if_fail(self->priv->insert_entry == NULL);
263 g_return_if_fail(self->priv->entry_count == NULL);
264 g_return_if_fail(self->priv->delete_aged == NULL);
265 g_return_if_fail(self->priv->application_count == NULL);
266
267 int prepare_status = SQLITE_OK;
268
269 /* Insert Statement */
270 prepare_status = sqlite3_prepare_v2(self->priv->db,
271 "insert into usage (application, entry, timestamp) values (?" SQL_VARS_APPLICATION ", ?" SQL_VARS_ENTRY ", date('now', 'utc'));",
272 -1, /* length */
273 &(self->priv->insert_entry),
274 NULL); /* unused stmt */
275
276 if (prepare_status != SQLITE_OK) {
277 g_warning("Unable to prepare insert entry statement: %s", sqlite3_errmsg(self->priv->db));
278 self->priv->insert_entry = NULL;
279 }
280
281 /* Entry Count Statement */
282 prepare_status = sqlite3_prepare_v2(self->priv->db,
283 "select count(*) from usage where application = ?" SQL_VARS_APPLICATION " and entry = ?" SQL_VARS_ENTRY " and timestamp > date('now', 'utc', '-30 days');",
284 -1, /* length */
285 &(self->priv->entry_count),
286 NULL); /* unused stmt */
287
288 if (prepare_status != SQLITE_OK) {
289 g_warning("Unable to prepare entry count statement: %s", sqlite3_errmsg(self->priv->db));
290 self->priv->entry_count = NULL;
291 }
292
293 /* Delete Aged Statement */
294 prepare_status = sqlite3_prepare_v2(self->priv->db,
295 "delete from usage where timestamp < date('now', 'utc', '-30 days');",
296 -1, /* length */
297 &(self->priv->delete_aged),
298 NULL); /* unused stmt */
299
300 if (prepare_status != SQLITE_OK) {
301 g_warning("Unable to prepare delete aged statement: %s", sqlite3_errmsg(self->priv->db));
302 self->priv->delete_aged = NULL;
303 }
304
305 /* Application Count Statement */
306 prepare_status = sqlite3_prepare_v2(self->priv->db,
307 "select count(*) from usage where application = ?" SQL_VARS_APPLICATION ";",
308 -1, /* length */
309 &(self->priv->application_count),
310 NULL); /* unused stmt */
311
312 if (prepare_status != SQLITE_OK) {
313 g_warning("Unable to prepare application count statement: %s", sqlite3_errmsg(self->priv->db));
314 self->priv->application_count = NULL;
315 }
316
317 return;
318 }
319
320 /* Build the database */
321 static void
322 build_db (UsageTracker * self)
323 {
324 g_debug("New database, initializing");
325
326 /* Create the table */
327 int exec_status = SQLITE_OK;
328 gchar * failstring = NULL;
329 exec_status = sqlite3_exec(self->priv->db,
330 create_db,
331 NULL, NULL, &failstring);
332 if (exec_status != SQLITE_OK) {
333 g_warning("Unable to create table: %s", failstring);
334 }
335
336 return;
337 }
338
339 void
340 usage_tracker_mark_usage (UsageTracker * self, const gchar * application, const gchar * entry)
341 {
342 g_return_if_fail(IS_USAGE_TRACKER(self));
343 g_return_if_fail(self->priv->db != NULL);
344
345 g_debug ("Marking %s %s", application, entry);
346
347 check_app_init(self, application);
348
349 sqlite3_reset(self->priv->insert_entry);
350
351 int bind_status = SQLITE_OK;
352
353 bind_status = sqlite3_bind_text(self->priv->insert_entry, SQL_VAR_APPLICATION, application, -1, SQLITE_TRANSIENT);
354 if (bind_status != SQLITE_OK) {
355 g_warning("Unable to bind application info: %s", sqlite3_errmsg(self->priv->db));
356 return;
357 }
358
359 bind_status = sqlite3_bind_text(self->priv->insert_entry, SQL_VAR_ENTRY, entry, -1, SQLITE_TRANSIENT);
360 if (bind_status != SQLITE_OK) {
361 g_warning("Unable to bind entry info: %s", sqlite3_errmsg(self->priv->db));
362 return;
363 }
364
365 int exec_status = SQLITE_ROW;
366 while ((exec_status = sqlite3_step(self->priv->insert_entry)) == SQLITE_ROW) {
367 }
368
369 if (exec_status != SQLITE_DONE) {
370 g_warning("Unknown status from executing insert_entry: %d", exec_status);
371 }
372
373 return;
374 }
375
376 guint
377 usage_tracker_get_usage (UsageTracker * self, const gchar * application, const gchar * entry)
378 {
379 g_return_val_if_fail(IS_USAGE_TRACKER(self), 0);
380 g_return_val_if_fail(self->priv->db != NULL, 0);
381
382 check_app_init(self, application);
383
384 sqlite3_reset(self->priv->entry_count);
385
386 int bind_status = SQLITE_OK;
387
388 bind_status = sqlite3_bind_text(self->priv->entry_count, SQL_VAR_APPLICATION, application, -1, SQLITE_TRANSIENT);
389 if (bind_status != SQLITE_OK) {
390 g_warning("Unable to bind application info: %s", sqlite3_errmsg(self->priv->db));
391 return 0;
392 }
393
394 bind_status = sqlite3_bind_text(self->priv->entry_count, SQL_VAR_ENTRY, entry, -1, SQLITE_TRANSIENT);
395 if (bind_status != SQLITE_OK) {
396 g_warning("Unable to bind entry info: %s", sqlite3_errmsg(self->priv->db));
397 return 0;
398 }
399
400 int exec_status = SQLITE_ROW;
401 guint count = 0;
402
403 while ((exec_status = sqlite3_step(self->priv->entry_count)) == SQLITE_ROW) {
404 count = sqlite3_column_int(self->priv->entry_count, 0);
405 }
406
407 if (exec_status != SQLITE_DONE) {
408 g_warning("Unknown status from executing entry_count: %d", exec_status);
409 }
410
411 g_debug ("Usage of %s %s is %u", application, entry, count);
412
413 return count;
414 }
415
416 /* Drop the entries from the database that have expired as they are
417 over 30 days old */
418 static gboolean
419 drop_entries (gpointer user_data)
420 {
421 g_return_val_if_fail(IS_USAGE_TRACKER(user_data), FALSE);
422 UsageTracker * self = USAGE_TRACKER(user_data);
423
424 if (self->priv->db == NULL) {
425 return TRUE;
426 }
427
428 sqlite3_reset(self->priv->delete_aged);
429
430 int exec_status = SQLITE_ROW;
431 while ((exec_status = sqlite3_step(self->priv->delete_aged)) == SQLITE_ROW) {
432 }
433
434 if (exec_status != SQLITE_DONE) {
435 g_warning("Unknown status from executing delete_aged: %d", exec_status);
436 }
437
438 return TRUE;
439 }
440
441 static void
442 check_app_init (UsageTracker * self, const gchar * application)
443 {
444 sqlite3_reset(self->priv->application_count);
445
446 int bind_status = SQLITE_OK;
447 bind_status = sqlite3_bind_text(self->priv->application_count, SQL_VAR_APPLICATION, application, -1, SQLITE_TRANSIENT);
448 if (bind_status != SQLITE_OK) {
449 g_warning("Unable to bind application info: %s", sqlite3_errmsg(self->priv->db));
450 return;
451 }
452
453 int exec_status = SQLITE_ROW;
454 guint count = 0;
455
456 while ((exec_status = sqlite3_step(self->priv->application_count)) == SQLITE_ROW) {
457 count = sqlite3_column_int(self->priv->application_count, 0);
458 }
459
460 if (exec_status != SQLITE_DONE) {
461 g_warning("Unknown status from executing application_count: %d", exec_status);
462 }
463
464 if (count > 0) {
465 return;
466 }
467
468 g_debug("Initializing application: %s", application);
469 gchar * basename = g_path_get_basename(application);
470
471 gchar * app_info_path = NULL;
472
473 if (g_getenv("HUD_APP_INFO_DIR") != NULL) {
474 app_info_path = g_strdup(g_getenv("HUD_APP_INFO_DIR"));
475 } else {
476 app_info_path = g_build_filename(DATADIR, "indicator-appmenu", "hud", "app-info", NULL);
477 }
478
479 gchar * app_info_filename = g_strdup_printf("%s.hud-app-info", basename);
480 gchar * app_info = g_build_filename(app_info_path, app_info_filename, NULL);
481
482 if (!load_app_info(app_info, self->priv->db)) {
483 if (g_file_test(app_info, G_FILE_TEST_EXISTS)) {
484 g_warning("Unable to load application information for application '%s' at path '%s'", application, app_info);
485 }
486 }
487
488 g_free(app_info);
489 g_free(app_info_filename);
490 g_free(app_info_path);
491 g_free(basename);
492
493 return;
494 }
495
496 UsageTracker *
497 usage_tracker_get_instance (void)
498 {
499 static UsageTracker *usage_tracker_instance;
500
501 if (usage_tracker_instance == NULL)
502 usage_tracker_instance = usage_tracker_new ();
503
504 return usage_tracker_instance;
505 }