summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--docs/reference/endless/endless-sections.txt1
-rw-r--r--endless/eosapplication.c148
-rw-r--r--endless/eosapplication.h9
-rw-r--r--test/test-application.c140
4 files changed, 295 insertions, 3 deletions
diff --git a/docs/reference/endless/endless-sections.txt b/docs/reference/endless/endless-sections.txt
index de69f31..3857e8f 100644
--- a/docs/reference/endless/endless-sections.txt
+++ b/docs/reference/endless/endless-sections.txt
@@ -11,6 +11,7 @@ EOS_ENUM_VALUE
<FILE>application</FILE>
EosApplication
eos_application_new
+eos_application_get_config_dir
<SUBSECTION Standard>
EosApplicationClass
EOS_APPLICATION
diff --git a/endless/eosapplication.c b/endless/eosapplication.c
index b7a9163..5429669 100644
--- a/endless/eosapplication.c
+++ b/endless/eosapplication.c
@@ -55,9 +55,49 @@ G_DEFINE_TYPE (EosApplication, eos_application, GTK_TYPE_APPLICATION)
struct _EosApplicationPrivate
{
+ GOnce init_config_dir_once;
+ GFile *config_dir;
+
EosWindow *main_application_window;
};
+enum
+{
+ PROP_0,
+ PROP_CONFIG_DIR,
+ NPROPS
+};
+
+static GParamSpec *eos_application_props[NPROPS] = { NULL, };
+
+static void
+eos_application_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ EosApplication *self = EOS_APPLICATION (object);
+
+ switch (property_id)
+ {
+ case PROP_CONFIG_DIR:
+ g_value_set_object (value, eos_application_get_config_dir (self));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ }
+}
+
+static void
+eos_application_finalize (GObject *object)
+{
+ EosApplication *self = EOS_APPLICATION (object);
+ g_clear_object (&self->priv->config_dir);
+
+ G_OBJECT_CLASS (eos_application_parent_class)->finalize (object);
+}
+
static void
eos_application_activate (GApplication *application)
{
@@ -76,6 +116,58 @@ eos_application_activate (GApplication *application)
/* TODO: Should it be required to override activate() as in GApplication? */
}
+static gpointer
+ensure_config_dir_exists_and_is_writable (EosApplication *self)
+{
+ const gchar *xdg_path = g_get_user_config_dir ();
+ const gchar *app_id = g_application_get_application_id (G_APPLICATION (self));
+ GFile *xdg_dir = g_file_new_for_path (xdg_path);
+ GFile *config_dir = g_file_get_child (xdg_dir, app_id);
+ gchar *config_path = g_file_get_path (config_dir); /* For error reporting */
+
+ g_object_unref (xdg_dir);
+
+ GError *error = NULL;
+ if (!g_file_make_directory_with_parents (config_dir, NULL, &error))
+ {
+ if (error->domain == G_IO_ERROR && error->code == G_IO_ERROR_EXISTS)
+ {
+ g_clear_error (&error); /* Ignore G_IO_ERROR_EXISTS */
+ }
+ else
+ {
+ g_error ("There was an error creating the user config directory %s: "
+ "%s",
+ config_path, error->message);
+ }
+ }
+
+ GFileInfo *info = g_file_query_info (config_dir,
+ G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE,
+ G_FILE_QUERY_INFO_NONE,
+ NULL,
+ &error);
+ if (info == NULL)
+ {
+ g_error ("Checking the user config directory %s failed. This means "
+ "something strange is going on in your home directory: %s",
+ config_path, error->message);
+ }
+ if (!g_file_info_get_attribute_boolean(info,
+ G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE))
+ {
+ g_error ("Your user config directory %s is not writable. This means "
+ "something strange is going on in your home directory.",
+ config_path);
+ }
+
+ g_object_unref (info);
+ g_free (config_path);
+
+ self->priv->config_dir = config_dir;
+ return NULL;
+}
+
static void
eos_application_startup (GApplication *application)
{
@@ -96,6 +188,10 @@ eos_application_startup (GApplication *application)
g_debug ("Initialized theme\n");
g_object_unref (provider);
+
+ EosApplication *self = EOS_APPLICATION (application);
+ g_once (&self->priv->init_config_dir_once,
+ (GThreadFunc)ensure_config_dir_exists_and_is_writable, self);
}
static void
@@ -162,21 +258,43 @@ on_app_id_set (EosApplication *self)
static void
eos_application_class_init (EosApplicationClass *klass)
{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
GApplicationClass *g_application_class = G_APPLICATION_CLASS (klass);
GtkApplicationClass *gtk_application_class = GTK_APPLICATION_CLASS (klass);
g_type_class_add_private (klass, sizeof (EosApplicationPrivate));
+ object_class->get_property = eos_application_get_property;
+ object_class->finalize = eos_application_finalize;
g_application_class->activate = eos_application_activate;
g_application_class->startup = eos_application_startup;
gtk_application_class->window_added = eos_application_window_added;
gtk_application_class->window_removed = eos_application_window_removed;
+
+ /**
+ * EosApplication:config-dir:
+ *
+ * A directory appropriate for storing per-user configuration information for
+ * this application.
+ * Accessing this property guarantees that the directory exists and is
+ * writable.
+ * See also eos_application_get_config_dir() for more information.
+ */
+ eos_application_props[PROP_CONFIG_DIR] =
+ g_param_spec_object ("config-dir", "Config dir",
+ "User configuration directory for this application",
+ G_TYPE_FILE,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+ g_object_class_install_properties (object_class, NPROPS,
+ eos_application_props);
}
static void
eos_application_init (EosApplication *self)
{
self->priv = APPLICATION_PRIVATE (self);
+ self->priv->init_config_dir_once = (GOnce)G_ONCE_INIT;
g_signal_connect (self, "notify::application-id",
G_CALLBACK (on_app_id_set), self);
}
@@ -204,3 +322,33 @@ eos_application_new (const gchar *application_id,
"flags", flags,
NULL);
}
+
+/**
+ * eos_application_get_config_dir:
+ * @self: the application
+ *
+ * Gets a #GFile pointing to the application-specific user configuration
+ * directory.
+ * This directory is located in <code>XDG_USER_CONFIG_DIR</code>, which usually
+ * expands to <filename class="directory">~/.config</filename>.
+ * The directory name is the same as the application's unique ID (see
+ * #GApplication:application-id.)
+ *
+ * You should use this directory to store configuration data specific to your
+ * application and specific to one user, such as cookies.
+ *
+ * Calling this function will also ensure that the directory exists and is
+ * writable.
+ * If it does not exist, it will be created.
+ * If it cannot be created, or it exists but is not writable, the program will
+ * abort.
+ *
+ * Returns: (transfer none): A #GFile pointing to the user config directory.
+ */
+GFile *
+eos_application_get_config_dir (EosApplication *self)
+{
+ g_once (&self->priv->init_config_dir_once,
+ (GThreadFunc)ensure_config_dir_exists_and_is_writable, self);
+ return self->priv->config_dir;
+}
diff --git a/endless/eosapplication.h b/endless/eosapplication.h
index f809522..0ce6553 100644
--- a/endless/eosapplication.h
+++ b/endless/eosapplication.h
@@ -59,11 +59,14 @@ struct _EosApplicationClass
};
EOS_SDK_ALL_API_VERSIONS
-GType eos_application_get_type (void) G_GNUC_CONST;
+GType eos_application_get_type (void) G_GNUC_CONST;
EOS_SDK_ALL_API_VERSIONS
-EosApplication *eos_application_new (const gchar *application_id,
- GApplicationFlags flags);
+EosApplication *eos_application_new (const gchar *application_id,
+ GApplicationFlags flags);
+
+EOS_SDK_ALL_API_VERSIONS
+GFile *eos_application_get_config_dir (EosApplication *self);
G_END_DECLS
diff --git a/test/test-application.c b/test/test-application.c
index 7f291b1..f417b5a 100644
--- a/test/test-application.c
+++ b/test/test-application.c
@@ -1,12 +1,23 @@
/* Copyright 2013 Endless Mobile, Inc. */
#include <stdlib.h>
+#include <inttypes.h> /* For PRIi64 */
+#include <sys/stat.h> /* For file mode constants */
#include <gtk/gtk.h>
#include <endless/endless.h>
#include "run-tests.h"
#define EXPECTED_TWO_WINDOW_ERRMSG "*You should not add more than one application window*"
+#define EXPECTED_CONFIG_NOT_WRITABLE_ERRMSG "*Your user config directory*is not writable*"
+
+#define APPLICATION_TEST_ID_BASE "com.endlessm.eosapplication.test"
+
+typedef struct
+{
+ gchar *unique_id;
+ EosApplication *app;
+} ConfigDirFixture;
static void
test_two_windows (EosApplication *app)
@@ -27,8 +38,137 @@ test_two_windows (EosApplication *app)
gtk_widget_destroy (win1);
}
+static gchar *
+generate_unique_app_id (void)
+{
+ return g_strdup_printf ("%s%" PRIi64,
+ APPLICATION_TEST_ID_BASE,
+ g_get_real_time ());
+}
+
+static void
+config_dir_setup (ConfigDirFixture *fixture,
+ gconstpointer unused)
+{
+ fixture->unique_id = generate_unique_app_id ();
+ fixture->app = eos_application_new (fixture->unique_id,
+ G_APPLICATION_FLAGS_NONE);
+}
+
+static void
+config_dir_teardown (ConfigDirFixture *fixture,
+ gconstpointer unused)
+{
+ /* Clean up the temporary config directory */
+ GFile *config_dir = eos_application_get_config_dir (fixture->app);
+ g_assert (g_file_delete (config_dir, NULL, NULL));
+
+ g_free (fixture->unique_id);
+ g_object_unref (fixture->app);
+}
+
+static void
+test_config_dir_get (ConfigDirFixture *fixture,
+ gconstpointer unused)
+{
+ GFile *dir1 = eos_application_get_config_dir (fixture->app);
+ GFile *dir2;
+ g_object_get (fixture->app, "config-dir", &dir2, NULL);
+
+ g_assert (dir1 != NULL);
+ g_assert (G_IS_FILE (dir1));
+ g_assert (dir1 == dir2);
+
+ g_object_unref (dir2);
+}
+
+static void
+test_config_dir_returns_expected_path (ConfigDirFixture *fixture,
+ gconstpointer unused)
+{
+ GFile *config_dir = eos_application_get_config_dir (fixture->app);
+
+ char *basename = g_file_get_basename (config_dir);
+ g_assert_cmpstr (basename, ==, fixture->unique_id);
+ g_free (basename);
+
+ GFile *parent = g_file_get_parent (config_dir);
+ char *dirname = g_file_get_path (parent);
+ g_object_unref (parent);
+ g_assert_cmpstr (dirname, ==, g_get_user_config_dir ());
+ g_free (dirname);
+}
+
+static void
+test_config_dir_exists (ConfigDirFixture *fixture,
+ gconstpointer unused)
+{
+ GFile *config_dir = eos_application_get_config_dir (fixture->app);
+ g_assert (g_file_query_exists (config_dir, NULL));
+}
+
+/* Helper function */
+static void
+set_writable (GFile *file,
+ gboolean writable)
+{
+ guint32 unwritable_mode = S_IRUSR | S_IXUSR | S_IRGRP | S_IXGRP;
+ guint32 writable_mode = unwritable_mode | S_IWUSR | S_IWGRP;
+
+ g_assert (g_file_set_attribute_uint32 (file,
+ G_FILE_ATTRIBUTE_UNIX_MODE,
+ writable? writable_mode : unwritable_mode,
+ G_FILE_QUERY_INFO_NONE,
+ NULL, NULL));
+}
+
+static void
+test_config_dir_fails_if_not_writable (ConfigDirFixture *fixture,
+ gconstpointer unused)
+{
+ /* Pre-create the config dir and make it non-writable */
+ char *config_path = g_build_filename (g_get_user_config_dir (),
+ fixture->unique_id,
+ NULL);
+ GFile *precreated_config_dir = g_file_new_for_path (config_path);
+ g_free (config_path);
+ g_assert (g_file_make_directory (precreated_config_dir, NULL, NULL));
+
+ set_writable (precreated_config_dir, FALSE);
+
+ /* Unix-only test */
+ if (g_test_trap_fork(0 /* timeout */, G_TEST_TRAP_SILENCE_STDERR))
+ {
+ GFile *config_dir = eos_application_get_config_dir (fixture->app);
+ }
+
+ g_test_trap_assert_failed ();
+ g_test_trap_assert_stderr (EXPECTED_CONFIG_NOT_WRITABLE_ERRMSG);
+
+ set_writable (precreated_config_dir, TRUE);
+
+ g_object_unref (precreated_config_dir);
+}
+
void
add_application_tests (void)
{
ADD_APP_WINDOW_TEST ("/application/two-windows", test_two_windows);
+ g_test_add ("/application/config-dir-get", ConfigDirFixture, NULL,
+ config_dir_setup,
+ test_config_dir_get,
+ config_dir_teardown);
+ g_test_add ("/application/config-dir-expected-path", ConfigDirFixture, NULL,
+ config_dir_setup,
+ test_config_dir_returns_expected_path,
+ config_dir_teardown);
+ g_test_add ("/application/config-dir-exists", ConfigDirFixture, NULL,
+ config_dir_setup,
+ test_config_dir_exists,
+ config_dir_teardown);
+ g_test_add ("/application/config-dir-fails-if-not-writable", ConfigDirFixture,
+ NULL,
+ config_dir_setup,
+ test_config_dir_fails_if_not_writable,
+ config_dir_teardown);
}