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 }