aboutsummaryrefslogtreecommitdiff
path: root/src/application-service-lru-file.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/application-service-lru-file.c')
-rw-r--r--src/application-service-lru-file.c337
1 files changed, 337 insertions, 0 deletions
diff --git a/src/application-service-lru-file.c b/src/application-service-lru-file.c
new file mode 100644
index 0000000..95ffb3c
--- /dev/null
+++ b/src/application-service-lru-file.c
@@ -0,0 +1,337 @@
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <string.h>
+#include <gio/gio.h>
+#include "application-service-lru-file.h"
+
+typedef struct _AppLruFilePrivate AppLruFilePrivate;
+struct _AppLruFilePrivate {
+ GHashTable * apps;
+ gboolean dirty;
+ guint timer;
+ gchar * filename;
+};
+
+typedef struct _AppData AppData;
+struct _AppData {
+ gchar * category;
+ GTimeVal last_touched;
+ GTimeVal first_touched;
+};
+
+#define APP_LRU_FILE_GET_PRIVATE(o) \
+ (G_TYPE_INSTANCE_GET_PRIVATE ((o), APP_LRU_FILE_TYPE, AppLruFilePrivate))
+
+static void app_lru_file_class_init (AppLruFileClass *klass);
+static void app_lru_file_init (AppLruFile *self);
+static void app_lru_file_dispose (GObject *object);
+static void app_lru_file_finalize (GObject *object);
+static void app_data_free (gpointer data);
+static gboolean load_from_file (gpointer data);
+static void clean_off_hash_cb (gpointer key, gpointer value, gpointer data);
+static void clean_off_write_end_cb (GObject * obj, GAsyncResult * res, gpointer data);
+
+G_DEFINE_TYPE (AppLruFile, app_lru_file, G_TYPE_OBJECT);
+
+/* Set up the class variable stuff */
+static void
+app_lru_file_class_init (AppLruFileClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ g_type_class_add_private (klass, sizeof (AppLruFilePrivate));
+
+ object_class->dispose = app_lru_file_dispose;
+ object_class->finalize = app_lru_file_finalize;
+
+ return;
+}
+
+/* Set all the data of the per-instance variables */
+static void
+app_lru_file_init (AppLruFile *self)
+{
+ AppLruFilePrivate * priv = APP_LRU_FILE_GET_PRIVATE(self);
+
+ /* Default values */
+ priv->apps = NULL;
+ priv->dirty = FALSE;
+ priv->timer = 0;
+ priv->filename = NULL;
+
+ /* Now let's build some stuff */
+ priv->apps = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, app_data_free);
+ priv->filename = g_build_filename(g_get_user_config_dir(), "indicators", "application", "lru-file.json", NULL);
+
+ /* No reason to delay other stuff for this, we'll
+ merge any values that get touched. */
+ g_idle_add(load_from_file, self);
+
+ return;
+}
+
+/* Get rid of objects and other big things */
+static void
+app_lru_file_dispose (GObject *object)
+{
+ AppLruFilePrivate * priv = APP_LRU_FILE_GET_PRIVATE(object);
+
+ if (priv->timer != 0) {
+ g_source_remove(priv->timer);
+ priv->timer = 0;
+ }
+
+ if (priv->apps != NULL) {
+ /* Cleans up the keys and entries itself */
+ g_hash_table_destroy(priv->apps);
+ priv->apps = NULL;
+ }
+
+ G_OBJECT_CLASS (app_lru_file_parent_class)->dispose (object);
+ return;
+}
+
+/* Deallocate memory */
+static void
+app_lru_file_finalize (GObject *object)
+{
+ AppLruFilePrivate * priv = APP_LRU_FILE_GET_PRIVATE(object);
+
+ if (priv->filename != NULL) {
+ g_free(priv->filename);
+ priv->filename = NULL;
+ }
+
+ G_OBJECT_CLASS (app_lru_file_parent_class)->finalize (object);
+ return;
+}
+
+/* Takes an AppData structure and free's the
+ memory from it. */
+static void
+app_data_free (gpointer data)
+{
+ AppData * appdata = (AppData *)data;
+
+ if (appdata->category != NULL) {
+ g_free(appdata->category);
+ }
+
+ g_free(appdata);
+
+ return;
+}
+
+/* Loads all of the data out of a json file */
+static gboolean
+load_from_file (gpointer data)
+{
+
+ return FALSE;
+}
+
+/* Write out our cache to a file so that we can unmark the dirty
+ bit and be happy. */
+static gboolean
+clean_off (gpointer data)
+{
+ AppLruFile * lrufile = (AppLruFile *)data;
+ AppLruFilePrivate * priv = APP_LRU_FILE_GET_PRIVATE(lrufile);
+ priv->timer = 0;
+
+ GError * error = NULL;
+
+ /* Check to see if our directory exists. Build it if not. */
+ gchar * dirname = g_build_filename(g_get_user_config_dir(), "indicators", "application", NULL);
+ if (!g_file_test(dirname, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR)) {
+ GFile * dirfile = g_file_new_for_path(dirname);
+ g_file_make_directory_with_parents(dirfile, NULL, NULL);
+ g_object_unref(dirfile);
+ }
+ g_free(dirname);
+
+ GFile * file = g_file_new_for_path(priv->filename);
+ GFileOutputStream * ostream = g_file_replace(file,
+ NULL, /* etag */
+ TRUE, /* backup */
+ G_FILE_CREATE_NONE, /* flags */
+ NULL, /* cancelable */
+ &error);
+ if (error != NULL) {
+ g_warning("Unable to open a file to store LRU file: %s", error->message);
+ return FALSE;
+ }
+
+ /* This is how the file will start */
+ GString * filestring = g_string_new("{\n \"version\": 1");
+
+ /* Put the middle in. */
+ g_hash_table_foreach (priv->apps, clean_off_hash_cb, filestring);
+
+ /* And then tack on the end. */
+ g_string_append(filestring, "\n}\n");
+ gchar * filedata = g_string_free(filestring, FALSE);
+ g_output_stream_write_async(G_OUTPUT_STREAM(ostream),
+ filedata,
+ strlen(filedata),
+ G_PRIORITY_DEFAULT_IDLE,
+ NULL,
+ clean_off_write_end_cb,
+ filedata);
+
+ return FALSE; /* drop the timer */
+}
+
+/* Looks at every value in the applications hash table and
+ turns it into a string for writing out. */
+static void
+clean_off_hash_cb (gpointer key, gpointer value, gpointer data)
+{
+ /* Mega-cast */
+ gchar * id = (gchar *)key;
+ AppData * appdata = (AppData *)value;
+ GString * string = (GString *)data;
+
+ gchar * firsttime = g_time_val_to_iso8601(&appdata->first_touched);
+ gchar * lasttime = g_time_val_to_iso8601(&appdata->last_touched);
+
+ g_string_append_printf(string, ",\n \"%s\": { \"first-time\": \"%s\", \"last-time\": \"%s\"}", id, firsttime, lasttime);
+
+ g_free(lasttime);
+ g_free(firsttime);
+
+ return;
+}
+
+/* Very much like clean_off_write_cb except that it is the
+ last actor on this Output Stream so it closes it. */
+static void
+clean_off_write_end_cb (GObject * obj, GAsyncResult * res, gpointer data)
+{
+ g_free(data);
+
+ GError * error = NULL;
+ g_output_stream_close(G_OUTPUT_STREAM(obj), NULL, &error);
+
+ if (error != NULL) {
+ g_warning("Unable to close LRU File: %s", error->message);
+ g_error_free(error);
+ }
+
+ return;
+}
+
+/* Sets the dirty bit if not already set and makes sure that
+ we have a timer to fix that at some point. */
+static void
+get_dirty (AppLruFile * lrufile)
+{
+ AppLruFilePrivate * priv = APP_LRU_FILE_GET_PRIVATE(lrufile);
+
+ priv->dirty = TRUE;
+
+ if (priv->timer == 0) {
+ priv->timer = g_timeout_add_seconds(60, clean_off, lrufile);
+ }
+
+ return;
+}
+
+/* API */
+
+/* Simple helper to create a new object */
+AppLruFile *
+app_lru_file_new (void)
+{
+ return APP_LRU_FILE(g_object_new(APP_LRU_FILE_TYPE, NULL));
+}
+
+/* This updates the timestamp for a particular
+ entry in the database. It also queues up a
+ write out of the database. But that'll happen
+ later. */
+void
+app_lru_file_touch (AppLruFile * lrufile, const gchar * id, const gchar * category)
+{
+ g_return_if_fail(IS_APP_LRU_FILE(lrufile));
+ g_return_if_fail(id != NULL && id[0] != '\0');
+ g_return_if_fail(category != NULL && category[0] != '\0');
+
+ AppData * appdata = NULL;
+ AppLruFilePrivate * priv = APP_LRU_FILE_GET_PRIVATE(lrufile);
+ gpointer data = g_hash_table_lookup(priv->apps, id);
+
+ if (data == NULL) {
+ /* Oh, we don't have one, let's build it and put it
+ into the hash table ourselves */
+ appdata = g_new0(AppData, 1);
+
+ appdata->category = g_strdup(category);
+ g_get_current_time(&(appdata->first_touched));
+ /* NOTE: last touched set below */
+
+ g_hash_table_insert(priv->apps, g_strdup(id), appdata);
+ } else {
+ /* Boring, we've got this one already */
+ appdata = (AppData *)data;
+ }
+
+ /* Touch it and mark the DB as dirty */
+ g_get_current_time(&(appdata->last_touched));
+ get_dirty(lrufile);
+ return;
+}
+
+/* Used to sort or compare different applications. */
+gint
+app_lru_file_sort (AppLruFile * lrufile, const gchar * id_a, const gchar * id_b)
+{
+ g_return_val_if_fail(IS_APP_LRU_FILE(lrufile), -1);
+
+ /* Let's first look to see if the IDs are the same, this
+ really shouldn't happen, but it'll be confusing if we
+ don't get it out of the way to start. */
+ if (g_strcmp0(id_a, id_b) == 0) {
+ return 0;
+ }
+
+ AppLruFilePrivate * priv = APP_LRU_FILE_GET_PRIVATE(lrufile);
+
+ /* Now make sure we have app data for both of these. If
+ not we'll assume that the one without is newer? */
+ gpointer data_a = g_hash_table_lookup(priv->apps, id_a);
+ if (data_a == NULL) {
+ return -1;
+ }
+
+ gpointer data_b = g_hash_table_lookup(priv->apps, id_b);
+ if (data_b == NULL) {
+ return 1;
+ }
+
+ /* Ugly casting */
+ AppData * appdata_a = (AppData *)data_a;
+ AppData * appdata_b = (AppData *)data_b;
+
+ /* Look at categories, we'll put the categories in alpha
+ order if they're not the same. */
+ gint catcompare = g_strcmp0(appdata_a->category, appdata_b->category);
+ if (catcompare != 0) {
+ return catcompare;
+ }
+
+ /* Now we're looking at the first time that these two were
+ seen in this account. Only using seconds. I mean, seriously. */
+ if (appdata_a->first_touched.tv_sec < appdata_b->first_touched.tv_sec) {
+ return -1;
+ }
+ if (appdata_b->first_touched.tv_sec < appdata_a->first_touched.tv_sec) {
+ return 1;
+ }
+
+ /* Eh, this seems roughly impossible. But if we have to choose,
+ I like A better. */
+ return 1;
+}