Back to index

indicator-appmenu  12.10.0
usage-tracker.c
Go to the documentation of this file.
00001 /*
00002 Tracks which menu items get used by users and works to promote those
00003 higher in the search rankings than others.
00004 
00005 Copyright 2011 Canonical Ltd.
00006 
00007 Authors:
00008     Ted Gould <ted@canonical.com>
00009 
00010 This program is free software: you can redistribute it and/or modify it 
00011 under the terms of the GNU General Public License version 3, as published 
00012 by the Free Software Foundation.
00013 
00014 This program is distributed in the hope that it will be useful, but 
00015 WITHOUT ANY WARRANTY; without even the implied warranties of 
00016 MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR 
00017 PURPOSE.  See the GNU General Public License for more details.
00018 
00019 You should have received a copy of the GNU General Public License along 
00020 with this program.  If not, see <http://www.gnu.org/licenses/>.
00021 */
00022 
00023 #define G_LOG_DOMAIN "usagetracker"
00024 
00025 #ifdef HAVE_CONFIG_H
00026 #include "config.h"
00027 #endif
00028 
00029 #include "usage-tracker.h"
00030 
00031 #include <glib.h>
00032 #include <glib/gstdio.h>
00033 #include <gio/gio.h>
00034 #include <sqlite3.h>
00035 #include "load-app-info.h"
00036 #include "create-db.h"
00037 #include "hudsettings.h"
00038 
00039 struct _UsageTrackerPrivate {
00040        gchar * cachefile;
00041        sqlite3 * db;
00042        guint drop_timer;
00043 
00044        /* SQL Statements */
00045        sqlite3_stmt * insert_entry;
00046        sqlite3_stmt * entry_count;
00047        sqlite3_stmt * delete_aged;
00048        sqlite3_stmt * application_count;
00049 };
00050 
00051 typedef enum {
00052        SQL_VAR_APPLICATION = 1,
00053        SQL_VAR_ENTRY = 2
00054 } sql_variables;
00055 
00056 #define SQL_VARS_APPLICATION  "1"
00057 #define SQL_VARS_ENTRY        "2"
00058 
00059 #define USAGE_TRACKER_GET_PRIVATE(o) \
00060 (G_TYPE_INSTANCE_GET_PRIVATE ((o), USAGE_TRACKER_TYPE, UsageTrackerPrivate))
00061 
00062 static void usage_tracker_dispose    (GObject *object);
00063 static void usage_tracker_finalize   (GObject *object);
00064 static void cleanup_db               (UsageTracker * self);
00065 static void configure_db             (UsageTracker * self);
00066 static void prepare_statements       (UsageTracker * self);
00067 static void build_db                 (UsageTracker * self);
00068 static gboolean drop_entries         (gpointer user_data);
00069 static void check_app_init (UsageTracker * self, const gchar * application);
00070 
00071 G_DEFINE_TYPE (UsageTracker, usage_tracker, G_TYPE_OBJECT);
00072 
00073 static void
00074 usage_tracker_class_init (UsageTrackerClass *klass)
00075 {
00076        GObjectClass *object_class = G_OBJECT_CLASS (klass);
00077 
00078        g_type_class_add_private (klass, sizeof (UsageTrackerPrivate));
00079 
00080        object_class->dispose = usage_tracker_dispose;
00081        object_class->finalize = usage_tracker_finalize;
00082 
00083        return;
00084 }
00085 
00086 static void
00087 usage_tracker_init (UsageTracker *self)
00088 {
00089        self->priv = USAGE_TRACKER_GET_PRIVATE(self);
00090 
00091        self->priv->cachefile = NULL;
00092        self->priv->db = NULL;
00093        self->priv->drop_timer = 0;
00094 
00095        self->priv->insert_entry = NULL;
00096        self->priv->entry_count = NULL;
00097        self->priv->delete_aged = NULL;
00098        self->priv->application_count = NULL;
00099 
00100        configure_db(self);
00101 
00102        /* Drop entries daily if we run for a really long time */
00103        self->priv->drop_timer = g_timeout_add_seconds(24 * 60 * 60, drop_entries, self);
00104        
00105        return;
00106 }
00107 
00108 static void
00109 usage_tracker_dispose (GObject *object)
00110 {
00111        UsageTracker * self = USAGE_TRACKER(object);
00112 
00113        cleanup_db(self);
00114 
00115        if (self->priv->drop_timer != 0) {
00116               g_source_remove(self->priv->drop_timer);
00117               self->priv->drop_timer = 0;
00118        }
00119 
00120        G_OBJECT_CLASS (usage_tracker_parent_class)->dispose (object);
00121        return;
00122 }
00123 
00124 static void
00125 usage_tracker_finalize (GObject *object)
00126 {
00127        UsageTracker * self = USAGE_TRACKER(object);
00128 
00129        if (self->priv->cachefile != NULL) {
00130               g_free(self->priv->cachefile);
00131               self->priv->cachefile = NULL;
00132        }
00133 
00134        G_OBJECT_CLASS (usage_tracker_parent_class)->finalize (object);
00135        return;
00136 }
00137 
00138 /* Small function to make sure we get all the DB components cleaned
00139    up in the spaces we need them */
00140 static void
00141 cleanup_db (UsageTracker * self)
00142 {
00143        if (self->priv->insert_entry != NULL) {
00144               sqlite3_finalize(self->priv->insert_entry);
00145               self->priv->insert_entry = NULL;
00146        }
00147 
00148        if (self->priv->entry_count != NULL) {
00149               sqlite3_finalize(self->priv->entry_count);
00150               self->priv->entry_count = NULL;
00151        }
00152 
00153        if (self->priv->delete_aged != NULL) {
00154               sqlite3_finalize(self->priv->delete_aged);
00155               self->priv->delete_aged = NULL;
00156        }
00157 
00158        if (self->priv->application_count != NULL) {
00159               sqlite3_finalize(self->priv->application_count);
00160               self->priv->application_count = NULL;
00161        }
00162 
00163        if (self->priv->db != NULL) {
00164               sqlite3_close(self->priv->db);
00165               self->priv->db = NULL;
00166        }
00167 
00168        return;
00169 }
00170 
00171 UsageTracker *
00172 usage_tracker_new (void)
00173 {
00174        return g_object_new(USAGE_TRACKER_TYPE, NULL);
00175 }
00176 
00177 /* Configure which database we should be using */
00178 static void
00179 configure_db (UsageTracker * self)
00180 {
00181        /* Removing the previous database */
00182        cleanup_db(self);
00183 
00184        if (self->priv->cachefile != NULL) {
00185               g_free(self->priv->cachefile);
00186               self->priv->cachefile = NULL;
00187        }
00188        
00189        /* Determine where his database should be built */
00190        gboolean usage_data = hud_settings.store_usage_data;
00191 
00192        if (g_getenv("HUD_NO_STORE_USAGE_DATA") != NULL) {
00193               usage_data = FALSE;
00194        }
00195 
00196        if (usage_data) {
00197               g_debug("Storing usage data on filesystem");
00198        }
00199 
00200        /* Setting up the new database */
00201        gboolean db_exists = FALSE;
00202 
00203        if (usage_data) {
00204               /* If we're storing the usage data we need to figure out
00205                  how to do it on disk */
00206 
00207               const gchar * basecachedir = g_getenv("HUD_CACHE_DIR");
00208               if (basecachedir == NULL) {
00209                      basecachedir = g_get_user_cache_dir();
00210               }
00211 
00212               gchar * cachedir = g_build_filename(basecachedir, "indicator-appmenu", NULL);
00213               if (!g_file_test(cachedir, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR)) {
00214                      g_mkdir(cachedir, 1 << 6 | 1 << 7 | 1 << 8); // 700
00215               }
00216               g_free(cachedir);
00217 
00218               self->priv->cachefile = g_build_filename(basecachedir, "indicator-appmenu", "hud-usage-log.sqlite", NULL);
00219               db_exists = g_file_test(self->priv->cachefile, G_FILE_TEST_EXISTS);
00220               int open_status = sqlite3_open(self->priv->cachefile, &self->priv->db); 
00221 
00222               if (open_status != SQLITE_OK) {
00223                      g_warning("Error building LRU DB");
00224                      sqlite3_close(self->priv->db);
00225                      self->priv->db = NULL;
00226               }
00227        } else {
00228               /* If we're not storing it, let's make an in memory database
00229                  so that we can use the app-info, and get better, but we don't
00230                  give anyone that data. */
00231               self->priv->cachefile = g_strdup(":memory:");
00232 
00233               int open_status = sqlite3_open(self->priv->cachefile, &self->priv->db); 
00234 
00235               if (open_status != SQLITE_OK) {
00236                      g_warning("Error building LRU DB");
00237                      sqlite3_close(self->priv->db);
00238                      self->priv->db = NULL;
00239               }
00240        }
00241 
00242        if (self->priv->db != NULL && !db_exists) {
00243               build_db(self);
00244        }
00245 
00246        prepare_statements(self);
00247 
00248        drop_entries(self);
00249 
00250        return;
00251 }
00252 
00253 /* Build all the prepared statments */
00254 static void
00255 prepare_statements (UsageTracker * self)
00256 {
00257        if (self->priv->db == NULL) {
00258               return;
00259        }
00260 
00261        /* These should never happen, but let's just check to make sure */
00262        g_return_if_fail(self->priv->insert_entry == NULL);
00263        g_return_if_fail(self->priv->entry_count == NULL);
00264        g_return_if_fail(self->priv->delete_aged == NULL);
00265        g_return_if_fail(self->priv->application_count == NULL);
00266 
00267        int prepare_status = SQLITE_OK;
00268 
00269        /* Insert Statement */
00270        prepare_status = sqlite3_prepare_v2(self->priv->db,
00271                                            "insert into usage (application, entry, timestamp) values (?" SQL_VARS_APPLICATION ", ?" SQL_VARS_ENTRY ", date('now', 'utc'));",
00272                                            -1, /* length */
00273                                            &(self->priv->insert_entry),
00274                                            NULL); /* unused stmt */
00275 
00276        if (prepare_status != SQLITE_OK) {
00277               g_warning("Unable to prepare insert entry statement: %s", sqlite3_errmsg(self->priv->db));
00278               self->priv->insert_entry = NULL;
00279        }
00280 
00281        /* Entry Count Statement */
00282        prepare_status = sqlite3_prepare_v2(self->priv->db,
00283                                            "select count(*) from usage where application = ?" SQL_VARS_APPLICATION " and entry = ?" SQL_VARS_ENTRY " and timestamp > date('now', 'utc', '-30 days');",
00284                                            -1, /* length */
00285                                            &(self->priv->entry_count),
00286                                            NULL); /* unused stmt */
00287 
00288        if (prepare_status != SQLITE_OK) {
00289               g_warning("Unable to prepare entry count statement: %s", sqlite3_errmsg(self->priv->db));
00290               self->priv->entry_count = NULL;
00291        }
00292 
00293        /* Delete Aged Statement */
00294        prepare_status = sqlite3_prepare_v2(self->priv->db,
00295                                            "delete from usage where timestamp < date('now', 'utc', '-30 days');",
00296                                            -1, /* length */
00297                                            &(self->priv->delete_aged),
00298                                            NULL); /* unused stmt */
00299 
00300        if (prepare_status != SQLITE_OK) {
00301               g_warning("Unable to prepare delete aged statement: %s", sqlite3_errmsg(self->priv->db));
00302               self->priv->delete_aged = NULL;
00303        }
00304 
00305        /* Application Count Statement */
00306        prepare_status = sqlite3_prepare_v2(self->priv->db,
00307                                            "select count(*) from usage where application = ?" SQL_VARS_APPLICATION ";",
00308                                            -1, /* length */
00309                                            &(self->priv->application_count),
00310                                            NULL); /* unused stmt */
00311 
00312        if (prepare_status != SQLITE_OK) {
00313               g_warning("Unable to prepare application count statement: %s", sqlite3_errmsg(self->priv->db));
00314               self->priv->application_count = NULL;
00315        }
00316 
00317        return;
00318 }
00319 
00320 /* Build the database */
00321 static void
00322 build_db (UsageTracker * self)
00323 {
00324        g_debug("New database, initializing");
00325 
00326        /* Create the table */
00327        int exec_status = SQLITE_OK;
00328        gchar * failstring = NULL;
00329        exec_status = sqlite3_exec(self->priv->db,
00330                                   create_db,
00331                                   NULL, NULL, &failstring);
00332        if (exec_status != SQLITE_OK) {
00333               g_warning("Unable to create table: %s", failstring);
00334        }
00335 
00336        return;
00337 }
00338 
00339 void
00340 usage_tracker_mark_usage (UsageTracker * self, const gchar * application, const gchar * entry)
00341 {
00342        g_return_if_fail(IS_USAGE_TRACKER(self));
00343        g_return_if_fail(self->priv->db != NULL);
00344 
00345        g_debug ("Marking %s %s", application, entry);
00346 
00347        check_app_init(self, application);
00348 
00349        sqlite3_reset(self->priv->insert_entry);
00350 
00351        int bind_status = SQLITE_OK;
00352 
00353        bind_status = sqlite3_bind_text(self->priv->insert_entry, SQL_VAR_APPLICATION, application, -1, SQLITE_TRANSIENT);
00354        if (bind_status != SQLITE_OK) {
00355               g_warning("Unable to bind application info: %s", sqlite3_errmsg(self->priv->db));
00356               return;
00357        }
00358 
00359        bind_status = sqlite3_bind_text(self->priv->insert_entry, SQL_VAR_ENTRY, entry, -1, SQLITE_TRANSIENT);
00360        if (bind_status != SQLITE_OK) {
00361               g_warning("Unable to bind entry info: %s", sqlite3_errmsg(self->priv->db));
00362               return;
00363        }
00364 
00365        int exec_status = SQLITE_ROW;
00366        while ((exec_status = sqlite3_step(self->priv->insert_entry)) == SQLITE_ROW) {
00367        }
00368 
00369        if (exec_status != SQLITE_DONE) {
00370               g_warning("Unknown status from executing insert_entry: %d", exec_status);
00371        }
00372 
00373        return;
00374 }
00375 
00376 guint
00377 usage_tracker_get_usage (UsageTracker * self, const gchar * application, const gchar * entry)
00378 {
00379        g_return_val_if_fail(IS_USAGE_TRACKER(self), 0);
00380        g_return_val_if_fail(self->priv->db != NULL, 0);
00381 
00382        check_app_init(self, application);
00383 
00384        sqlite3_reset(self->priv->entry_count);
00385 
00386        int bind_status = SQLITE_OK;
00387 
00388        bind_status = sqlite3_bind_text(self->priv->entry_count, SQL_VAR_APPLICATION, application, -1, SQLITE_TRANSIENT);
00389        if (bind_status != SQLITE_OK) {
00390               g_warning("Unable to bind application info: %s", sqlite3_errmsg(self->priv->db));
00391               return 0;
00392        }
00393 
00394        bind_status = sqlite3_bind_text(self->priv->entry_count, SQL_VAR_ENTRY, entry, -1, SQLITE_TRANSIENT);
00395        if (bind_status != SQLITE_OK) {
00396               g_warning("Unable to bind entry info: %s", sqlite3_errmsg(self->priv->db));
00397               return 0;
00398        }
00399 
00400        int exec_status = SQLITE_ROW;
00401        guint count = 0;
00402 
00403        while ((exec_status = sqlite3_step(self->priv->entry_count)) == SQLITE_ROW) {
00404               count = sqlite3_column_int(self->priv->entry_count, 0);
00405        }
00406 
00407        if (exec_status != SQLITE_DONE) {
00408               g_warning("Unknown status from executing entry_count: %d", exec_status);
00409        }
00410 
00411        g_debug ("Usage of %s %s is %u", application, entry, count);
00412 
00413        return count;
00414 }
00415 
00416 /* Drop the entries from the database that have expired as they are
00417    over 30 days old */
00418 static gboolean
00419 drop_entries (gpointer user_data)
00420 {
00421        g_return_val_if_fail(IS_USAGE_TRACKER(user_data), FALSE);
00422        UsageTracker * self = USAGE_TRACKER(user_data);
00423 
00424        if (self->priv->db == NULL) {
00425               return TRUE;
00426        }
00427 
00428        sqlite3_reset(self->priv->delete_aged);
00429 
00430        int exec_status = SQLITE_ROW;
00431        while ((exec_status = sqlite3_step(self->priv->delete_aged)) == SQLITE_ROW) {
00432        }
00433 
00434        if (exec_status != SQLITE_DONE) {
00435               g_warning("Unknown status from executing delete_aged: %d", exec_status);
00436        }
00437 
00438        return TRUE;
00439 }
00440 
00441 static void
00442 check_app_init (UsageTracker * self, const gchar * application)
00443 {
00444        sqlite3_reset(self->priv->application_count);
00445 
00446        int bind_status = SQLITE_OK;
00447        bind_status = sqlite3_bind_text(self->priv->application_count, SQL_VAR_APPLICATION, application, -1, SQLITE_TRANSIENT);
00448        if (bind_status != SQLITE_OK) {
00449               g_warning("Unable to bind application info: %s", sqlite3_errmsg(self->priv->db));
00450               return;
00451        }
00452 
00453        int exec_status = SQLITE_ROW;
00454        guint count = 0;
00455 
00456        while ((exec_status = sqlite3_step(self->priv->application_count)) == SQLITE_ROW) {
00457               count = sqlite3_column_int(self->priv->application_count, 0);
00458        }
00459 
00460        if (exec_status != SQLITE_DONE) {
00461               g_warning("Unknown status from executing application_count: %d", exec_status);
00462        }
00463 
00464        if (count > 0) {
00465               return;
00466        }
00467 
00468        g_debug("Initializing application: %s", application);
00469        gchar * basename = g_path_get_basename(application);
00470 
00471        gchar * app_info_path = NULL;
00472 
00473        if (g_getenv("HUD_APP_INFO_DIR") != NULL) {
00474               app_info_path = g_strdup(g_getenv("HUD_APP_INFO_DIR"));
00475        } else {
00476               app_info_path = g_build_filename(DATADIR, "indicator-appmenu", "hud", "app-info", NULL);
00477        }
00478 
00479        gchar * app_info_filename = g_strdup_printf("%s.hud-app-info", basename);
00480        gchar * app_info = g_build_filename(app_info_path, app_info_filename, NULL);
00481 
00482        if (!load_app_info(app_info, self->priv->db)) {
00483               if (g_file_test(app_info, G_FILE_TEST_EXISTS)) {
00484                      g_warning("Unable to load application information for application '%s' at path '%s'", application, app_info);
00485               }
00486        }
00487 
00488        g_free(app_info);
00489        g_free(app_info_filename);
00490        g_free(app_info_path);
00491        g_free(basename);
00492 
00493        return;
00494 }
00495 
00496 UsageTracker *
00497 usage_tracker_get_instance (void)
00498 {
00499   static UsageTracker *usage_tracker_instance;
00500 
00501   if (usage_tracker_instance == NULL)
00502     usage_tracker_instance = usage_tracker_new ();
00503 
00504   return usage_tracker_instance;
00505 }