aboutsummaryrefslogtreecommitdiffstats
path: root/lib/ephy-web-app-utils.c
diff options
context:
space:
mode:
Diffstat (limited to 'lib/ephy-web-app-utils.c')
-rw-r--r--lib/ephy-web-app-utils.c498
1 files changed, 498 insertions, 0 deletions
diff --git a/lib/ephy-web-app-utils.c b/lib/ephy-web-app-utils.c
new file mode 100644
index 000000000..53bf687db
--- /dev/null
+++ b/lib/ephy-web-app-utils.c
@@ -0,0 +1,498 @@
+/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=2 sts=2 et: */
+/*
+ * Copyright © 2011 Igalia S.L.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "config.h"
+#include "ephy-web-app-utils.h"
+
+#include "ephy-debug.h"
+#include "ephy-file-helpers.h"
+
+#include <glib/gstdio.h>
+#include <libsoup/soup-gnome.h>
+#include <webkit/webkit.h>
+
+#define EPHY_WEB_APP_DESKTOP_FILE_PREFIX "epiphany-"
+
+/* This is necessary because of gnome-shell's guessing of a .desktop
+ filename from WM_CLASS property. */
+static char *
+get_wm_class_from_app_title (const char *title)
+{
+ char *normal_title;
+ char *wm_class;
+ char *checksum;
+
+ normal_title = g_utf8_strdown (title, -1);
+ g_strdelimit (normal_title, " ", '-');
+ g_strdelimit (normal_title, G_DIR_SEPARATOR_S, '-');
+ checksum = g_compute_checksum_for_string (G_CHECKSUM_SHA1, title, -1);
+ wm_class = g_strconcat (EPHY_WEB_APP_DESKTOP_FILE_PREFIX, normal_title, "-", checksum, NULL);
+
+ g_free (checksum);
+ g_free (normal_title);
+
+ return wm_class;
+}
+
+/* Gets the proper .desktop filename from a WM_CLASS string,
+ converting to the local charset when needed. */
+static char *
+desktop_filename_from_wm_class (char *wm_class)
+{
+ char *encoded;
+ char *filename = NULL;
+ GError *error = NULL;
+
+ encoded = g_filename_from_utf8 (wm_class, -1, NULL, NULL, &error);
+ if (error) {
+ g_warning ("%s", error->message);
+ g_error_free (error);
+ return NULL;
+ }
+ filename = g_strconcat (encoded, ".desktop", NULL);
+ g_free (encoded);
+
+ return filename;
+}
+
+/**
+ * ephy_web_application_get_profile_directory:
+ * @name: the application name
+ *
+ * Gets the directory where the profile for @name is meant to be stored.
+ *
+ * Returns: (transfer full): A newly allocated string.
+ **/
+char *
+ephy_web_application_get_profile_directory (const char *name)
+{
+ char *app_dir, *wm_class, *profile_dir, *encoded;
+ GError *error = NULL;
+
+ wm_class = get_wm_class_from_app_title (name);
+ encoded = g_filename_from_utf8 (wm_class, -1, NULL, NULL, &error);
+ g_free (wm_class);
+
+ if (error) {
+ g_warning ("%s", error->message);
+ g_error_free (error);
+ return NULL;
+ }
+
+ app_dir = g_strconcat (EPHY_WEB_APP_PREFIX, encoded, NULL);
+ profile_dir = g_build_filename (ephy_dot_dir (), app_dir, NULL);
+ g_free (encoded);
+ g_free (app_dir);
+
+ return profile_dir;
+}
+
+/**
+ * ephy_web_application_delete:
+ * @name: the name of the web application do delete
+ *
+ * Deletes all the data associated with a Web Application created by
+ * Epiphany.
+ *
+ * Returns: %TRUE if the web app was succesfully deleted, %FALSE otherwise
+ **/
+gboolean
+ephy_web_application_delete (const char *name)
+{
+ char *profile_dir = NULL;
+ char *desktop_file = NULL, *desktop_path = NULL;
+ char *wm_class;
+ GFile *profile = NULL, *launcher = NULL;
+ gboolean return_value = FALSE;
+
+ g_return_val_if_fail (name, FALSE);
+
+ profile_dir = ephy_web_application_get_profile_directory (name);
+ if (!profile_dir)
+ goto out;
+
+ /* If there's no profile dir for this app, it means it does not
+ * exist. */
+ if (!g_file_test (profile_dir, G_FILE_TEST_IS_DIR)) {
+ g_warning ("No application with name '%s' is installed.\n", name);
+ goto out;
+ }
+
+ profile = g_file_new_for_path (profile_dir);
+ if (!ephy_file_delete_dir_recursively (profile, NULL))
+ goto out;
+ LOG ("Deleted application profile.\n");
+
+ wm_class = get_wm_class_from_app_title (name);
+ desktop_file = desktop_filename_from_wm_class (wm_class);
+ g_free (wm_class);
+ if (!desktop_file)
+ goto out;
+ desktop_path = g_build_filename (g_get_user_data_dir (), "applications", desktop_file, NULL);
+ launcher = g_file_new_for_path (desktop_path);
+ if (!g_file_delete (launcher, NULL, NULL))
+ goto out;
+ LOG ("Deleted application launcher.\n");
+
+ return_value = TRUE;
+
+out:
+
+ if (profile)
+ g_object_unref (profile);
+ g_free (profile_dir);
+
+ if (launcher)
+ g_object_unref (launcher);
+ g_free (desktop_file);
+ g_free (desktop_path);
+
+ return return_value;
+}
+
+#define EPHY_WEB_APP_TOOLBAR "<?xml version=\"1.0\"?>" \
+ "<toolbars version=\"1.1\">" \
+ " <toolbar name=\"DefaultToolbar\" hidden=\"true\" editable=\"false\">" \
+ " <toolitem name=\"NavigationBack\"/>" \
+ " <toolitem name=\"NavigationForward\"/>" \
+ " <toolitem name=\"ViewReload\"/>" \
+ " <toolitem name=\"ViewCancel\"/>" \
+ " </toolbar>" \
+ "</toolbars>"
+
+#define EPHY_TOOLBARS_XML_FILE "epiphany-toolbars-3.xml"
+
+static char *
+create_desktop_file (const char *address,
+ const char *profile_dir,
+ const char *title,
+ GdkPixbuf *icon)
+{
+ GKeyFile *file = NULL;
+ char *exec_string;
+ char *data = NULL;
+ char *filename, *apps_path, *desktop_file_path = NULL;
+ char *link_path;
+ char *wm_class;
+ GFile *link;
+ GError *error = NULL;
+
+ g_return_val_if_fail (profile_dir, NULL);
+
+ wm_class = get_wm_class_from_app_title (title);
+ filename = desktop_filename_from_wm_class (wm_class);
+
+ if (!filename)
+ goto out;
+
+ file = g_key_file_new ();
+ g_key_file_set_value (file, "Desktop Entry", "Name", title);
+ exec_string = g_strdup_printf ("epiphany --application-mode --profile=\"%s\" %s",
+ profile_dir,
+ address);
+ g_key_file_set_value (file, "Desktop Entry", "Exec", exec_string);
+ g_free (exec_string);
+ g_key_file_set_value (file, "Desktop Entry", "StartupNotify", "true");
+ g_key_file_set_value (file, "Desktop Entry", "Terminal", "false");
+ g_key_file_set_value (file, "Desktop Entry", "Type", "Application");
+
+ if (icon) {
+ GOutputStream *stream;
+ char *path;
+ GFile *image;
+
+ path = g_build_filename (profile_dir, EPHY_WEB_APP_ICON_NAME, NULL);
+ image = g_file_new_for_path (path);
+
+ stream = (GOutputStream*)g_file_create (image, 0, NULL, NULL);
+ gdk_pixbuf_save_to_stream (icon, stream, "png", NULL, NULL, NULL);
+ g_key_file_set_value (file, "Desktop Entry", "Icon", path);
+
+ g_object_unref (stream);
+ g_object_unref (image);
+ g_free (path);
+ }
+
+ g_key_file_set_value (file, "Desktop Entry", "StartupWMClass", wm_class);
+ data = g_key_file_to_data (file, NULL, NULL);
+
+ desktop_file_path = g_build_filename (profile_dir, filename, NULL);
+
+ if (!g_file_set_contents (desktop_file_path, data, -1, NULL)) {
+ g_free (desktop_file_path);
+ desktop_file_path = NULL;
+ }
+
+ /* Create a symlink in XDG_DATA_DIR/applications for the Shell to
+ * pick up this application. */
+ apps_path = g_build_filename (g_get_user_data_dir (), "applications", NULL);
+ if (ephy_ensure_dir_exists (apps_path, &error)) {
+ link_path = g_build_filename (apps_path, filename, NULL);
+ link = g_file_new_for_path (link_path);
+ g_free (link_path);
+ g_file_make_symbolic_link (link, desktop_file_path, NULL, NULL);
+ g_object_unref (link);
+ } else {
+ g_warning ("Error creating application symlink: %s", error->message);
+ g_error_free (error);
+ }
+ g_free (apps_path);
+ g_free (filename);
+
+out:
+ g_free (wm_class);
+ g_free (data);
+ g_key_file_free (file);
+
+ return desktop_file_path;
+}
+
+static void
+create_cookie_jar_for_domain (const char *address, const char *directory)
+{
+ SoupSession *session;
+ GSList *cookies, *p;
+ SoupCookieJar *current_jar, *new_jar;
+ char *domain, *filename;
+ SoupURI *uri;
+
+ /* Create the new cookie jar */
+ filename = g_build_filename (directory, "cookies.sqlite", NULL);
+ new_jar = (SoupCookieJar*)soup_cookie_jar_sqlite_new (filename, FALSE);
+ g_free (filename);
+
+ /* The app domain for the current view */
+ uri = soup_uri_new (address);
+ domain = uri->host;
+
+ /* The current cookies */
+ session = webkit_get_default_session ();
+ current_jar = (SoupCookieJar*)soup_session_get_feature (session, SOUP_TYPE_COOKIE_JAR);
+ cookies = soup_cookie_jar_all_cookies (current_jar);
+
+ for (p = cookies; p; p = p->next) {
+ SoupCookie *cookie = (SoupCookie*)p->data;
+
+ if (g_str_has_suffix (cookie->domain, domain))
+ soup_cookie_jar_add_cookie (new_jar, cookie);
+ else
+ soup_cookie_free (cookie);
+ }
+
+ soup_uri_free (uri);
+ g_slist_free (cookies);
+}
+
+/**
+ * ephy_web_application_create:
+ * @address: the address of the new web application
+ * @name: the name for the new web application
+ * @icon: the icon for the new web application
+ *
+ * Creates a new Web Application for @address.
+ *
+ * Returns: (transfer-full): the path to the desktop file representing the new application
+ **/
+char *
+ephy_web_application_create (const char *address, const char *name, GdkPixbuf *icon)
+{
+ char *profile_dir = NULL;
+ char *toolbar_path = NULL;
+ char *desktop_file_path = NULL;
+
+ /* If there's already a WebApp profile for the contents of this
+ * view, do nothing. */
+ profile_dir = ephy_web_application_get_profile_directory (name);
+ if (g_file_test (profile_dir, G_FILE_TEST_IS_DIR))
+ goto out;
+
+ /* Create the profile directory, populate it. */
+ if (g_mkdir (profile_dir, 488) == -1) {
+ LOG ("Failed to create directory %s", profile_dir);
+ goto out;
+ }
+
+ /* Things we need in a WebApp's profile:
+ - Toolbar layout
+ - Our own cookies file, copying the relevant cookies for the
+ app's domain.
+ */
+ toolbar_path = g_build_filename (profile_dir, EPHY_TOOLBARS_XML_FILE, NULL);
+ if (!g_file_set_contents (toolbar_path, EPHY_WEB_APP_TOOLBAR, -1, NULL))
+ goto out;
+
+ create_cookie_jar_for_domain (address, profile_dir);
+
+ /* Create the deskop file. */
+ desktop_file_path = create_desktop_file (address, profile_dir, name, icon);
+
+out:
+ if (toolbar_path)
+ g_free (toolbar_path);
+
+ if (profile_dir)
+ g_free (profile_dir);
+
+ return desktop_file_path;
+}
+
+/**
+ * ephy_web_application_get_application_list:
+ *
+ * Gets a list of the currently installed web applications.
+ * Free the returned GList with
+ * ephy_web_application_free_application_list.
+ *
+ * Returns: (transfer-full): a #GList of #EphyWebApplication objects
+ **/
+GList *
+ephy_web_application_get_application_list ()
+{
+ GFileEnumerator *children = NULL;
+ GFileInfo *info;
+ GList *applications = NULL;
+ GFile *dot_dir;
+
+ dot_dir = g_file_new_for_path (ephy_dot_dir ());
+ children = g_file_enumerate_children (dot_dir,
+ "standard::name",
+ 0, NULL, NULL);
+ g_object_unref (dot_dir);
+
+ info = g_file_enumerator_next_file (children, NULL, NULL);
+ while (info) {
+ EphyWebApplication *app;
+ const char *name;
+ glong prefix_length = g_utf8_strlen (EPHY_WEB_APP_PREFIX, -1);
+
+ name = g_file_info_get_name (info);
+ if (g_str_has_prefix (name, EPHY_WEB_APP_PREFIX)) {
+ char *profile_dir;
+ guint64 created;
+ GDate *date;
+ char *desktop_file, *desktop_file_path;
+ char *contents;
+ GFileInfo *desktop_info;
+
+ app = g_slice_new0 (EphyWebApplication);
+
+ profile_dir = g_build_filename (ephy_dot_dir (), name, NULL);
+ app->icon_url = g_build_filename (profile_dir, EPHY_WEB_APP_ICON_NAME, NULL);
+
+ desktop_file = g_strconcat (name + prefix_length, ".desktop", NULL);
+ desktop_file_path = g_build_filename (profile_dir, desktop_file, NULL);
+ if (g_file_get_contents (desktop_file_path, &contents, NULL, NULL)) {
+ char *exec;
+ char **strings;
+ GKeyFile *key;
+ int i;
+ GFile *file;
+
+ key = g_key_file_new ();
+ g_key_file_load_from_data (key, contents, -1, 0, NULL);
+ app->name = g_key_file_get_string (key, "Desktop Entry", "Name", NULL);
+ exec = g_key_file_get_string (key, "Desktop Entry", "Exec", NULL);
+ strings = g_strsplit (exec, " ", -1);
+
+ for (i = 0; strings[i]; i++);
+ app->url = g_strdup (strings[i - 1]);
+
+ g_strfreev (strings);
+ g_free (exec);
+ g_key_file_free (key);
+
+ file = g_file_new_for_path (desktop_file_path);
+
+ /* FIXME: this should use TIME_CREATED but it does not seem to be working. */
+ desktop_info = g_file_query_info (file, G_FILE_ATTRIBUTE_TIME_MODIFIED, 0, NULL, NULL);
+ created = g_file_info_get_attribute_uint64 (desktop_info, G_FILE_ATTRIBUTE_TIME_MODIFIED);
+
+ date = g_date_new ();
+ g_date_set_time_t (date, (time_t)created);
+ g_date_strftime (app->install_date, 127, "%x", date);
+
+ g_date_free (date);
+ g_object_unref (file);
+ g_object_unref (desktop_info);
+
+ applications = g_list_append (applications, app);
+ }
+
+ g_free (contents);
+ g_free (desktop_file);
+ g_free (profile_dir);
+ g_free (desktop_file_path);
+ }
+
+ g_object_unref (info);
+
+ info = g_file_enumerator_next_file (children, NULL, NULL);
+ }
+
+ g_object_unref (children);
+
+ return applications;
+}
+
+static void
+ephy_web_application_free (EphyWebApplication *app)
+{
+ g_free (app->name);
+ g_free (app->icon_url);
+ g_free (app->url);
+ g_slice_free (EphyWebApplication, app);
+}
+
+/**
+ * ephy_web_application_free_application_list:
+ * @list: an #EphyWebApplication GList
+ *
+ * Frees a @list as given by ephy_web_application_get_application_list.
+ **/
+void
+ephy_web_application_free_application_list (GList *list)
+{
+ GList *p;
+
+ for (p = list; p; p = p->next)
+ ephy_web_application_free ((EphyWebApplication*)p->data);
+
+ g_list_free (list);
+}
+
+/**
+ * ephy_web_application_exists:
+ * @name: the potential name of the web application
+ *
+ * Returns: whether an application with @name exists.
+ **/
+gboolean
+ephy_web_application_exists (const char *name)
+{
+ char *profile_dir;
+ gboolean profile_exists;
+
+ profile_dir = ephy_web_application_get_profile_directory (name);
+ profile_exists = g_file_test (profile_dir, G_FILE_TEST_IS_DIR);
+ g_free (profile_dir);
+
+ return profile_exists;
+}