diff options
Diffstat (limited to 'panels/background/cc-background-xml.c')
-rw-r--r-- | panels/background/cc-background-xml.c | 648 |
1 files changed, 648 insertions, 0 deletions
diff --git a/panels/background/cc-background-xml.c b/panels/background/cc-background-xml.c new file mode 100644 index 0000000..270fcc2 --- /dev/null +++ b/panels/background/cc-background-xml.c @@ -0,0 +1,648 @@ +/* + * Authors: Rodney Dawes <dobey@ximian.com> + * Bastien Nocera <hadess@hadess.net> + * + * Copyright 2003-2006 Novell, Inc. (www.novell.com) + * Copyright 2011 Red Hat Inc. + * + * 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 of the License, 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, see <http://www.gnu.org/licenses/>. + */ + +#include <gio/gio.h> +#include <string.h> +#include <libxml/parser.h> +#include <gdesktop-enums.h> + +#include "gdesktop-enums-types.h" +#include "cc-background-item.h" +#include "cc-background-xml.h" + +/* The number of items we signal as "added" before + * returning to the main loop */ +#define NUM_ITEMS_PER_BATCH 1 + +struct _CcBackgroundXml +{ + GObject parent_instance; + + GHashTable *wp_hash; + GAsyncQueue *item_added_queue; + guint item_added_id; + GSList *monitors; /* GSList of GFileMonitor */ +}; + +enum { + ADDED, + LAST_SIGNAL +}; + +static guint signals[LAST_SIGNAL] = { 0 }; + +G_DEFINE_TYPE (CcBackgroundXml, cc_background_xml, G_TYPE_OBJECT) + +static gboolean +cc_background_xml_get_bool (const xmlNode *parent, + const gchar *prop_name) +{ + xmlChar *prop; + gboolean ret_val = FALSE; + + g_return_val_if_fail (parent != NULL, FALSE); + g_return_val_if_fail (prop_name != NULL, FALSE); + + prop = xmlGetProp ((xmlNode *) parent, (xmlChar*)prop_name); + if (prop != NULL) { + if (!g_ascii_strcasecmp ((gchar *)prop, "true") || !g_ascii_strcasecmp ((gchar *)prop, "1")) { + ret_val = TRUE; + } else { + ret_val = FALSE; + } + xmlFree (prop); + } + + return ret_val; +} + +static struct { + int value; + const char *string; +} lookups[] = { + { G_DESKTOP_BACKGROUND_SHADING_HORIZONTAL, "horizontal-gradient" }, + { G_DESKTOP_BACKGROUND_SHADING_VERTICAL, "vertical-gradient" }, +}; + +static int +enum_string_to_value (GType type, + const char *string) +{ + GEnumClass *eclass; + GEnumValue *value; + + eclass = G_ENUM_CLASS (g_type_class_peek (type)); + value = g_enum_get_value_by_nick (eclass, string); + + /* Here's a bit of hand-made parsing, bad bad */ + if (value == NULL) { + guint i; + for (i = 0; i < G_N_ELEMENTS (lookups); i++) { + if (g_str_equal (lookups[i].string, string)) + return lookups[i].value; + } + g_warning ("Unhandled value '%s' for enum '%s'", + string, G_FLAGS_CLASS_TYPE_NAME (eclass)); + return 0; + } + + return value->value; +} + +static gboolean +idle_emit (CcBackgroundXml *xml) +{ + gint i; + + g_async_queue_lock (xml->item_added_queue); + + for (i = 0; i < NUM_ITEMS_PER_BATCH; i++) { + g_autoptr(GObject) item = NULL; + + item = g_async_queue_try_pop_unlocked (xml->item_added_queue); + if (item == NULL) + break; + g_signal_emit (G_OBJECT (xml), signals[ADDED], 0, item); + } + + g_async_queue_unlock (xml->item_added_queue); + + if (g_async_queue_length (xml->item_added_queue) > 0) { + return TRUE; + } else { + xml->item_added_id = 0; + return FALSE; + } +} + +static void +emit_added_in_idle (CcBackgroundXml *xml, + GObject *object) +{ + g_async_queue_lock (xml->item_added_queue); + g_async_queue_push_unlocked (xml->item_added_queue, object); + if (xml->item_added_id == 0) + xml->item_added_id = g_idle_add ((GSourceFunc) idle_emit, xml); + g_async_queue_unlock (xml->item_added_queue); +} + +#define NONE "(none)" +#define UNSET_FLAG(flag) G_STMT_START{ (flags&=~(flag)); }G_STMT_END +#define SET_FLAG(flag) G_STMT_START{ (flags|=flag); }G_STMT_END + +static gboolean +cc_background_xml_load_xml_internal (CcBackgroundXml *xml, + const gchar *filename, + gboolean in_thread) +{ + xmlDoc * wplist; + xmlNode * root, * list, * wpa; + xmlChar * nodelang; + const gchar * const * syslangs; + gint i; + gboolean retval; + + wplist = xmlParseFile (filename); + retval = FALSE; + + if (!wplist) + return retval; + + syslangs = g_get_language_names (); + + root = xmlDocGetRootElement (wplist); + + for (list = root->children; list != NULL; list = list->next) { + if (!strcmp ((gchar *)list->name, "wallpaper")) { + g_autoptr(CcBackgroundItem) item = NULL; + CcBackgroundItemFlags flags; + g_autofree gchar *uri = NULL; + g_autofree gchar *cname = NULL; + g_autofree gchar *id = NULL; + + flags = 0; + item = cc_background_item_new (NULL); + + g_object_set (G_OBJECT (item), + "is-deleted", cc_background_xml_get_bool (list, "deleted"), + "source-xml", filename, + NULL); + + for (wpa = list->children; wpa != NULL; wpa = wpa->next) { + if (wpa->type == XML_COMMENT_NODE) { + continue; + } else if (!strcmp ((gchar *)wpa->name, "filename")) { + if (wpa->last != NULL && wpa->last->content != NULL) { + gchar *content = g_strstrip ((gchar *)wpa->last->content); + g_autofree gchar *bg_uri = NULL; + + /* FIXME same rubbish as in other parts of the code */ + if (strcmp (content, NONE) == 0) { + bg_uri = NULL; + } else { + g_autoptr(GFile) file = NULL; + g_autofree gchar *dirname = NULL; + + dirname = g_path_get_dirname (filename); + file = g_file_new_for_commandline_arg_and_cwd (content, dirname); + bg_uri = g_file_get_uri (file); + } + SET_FLAG(CC_BACKGROUND_ITEM_HAS_URI); + g_object_set (G_OBJECT (item), "uri", bg_uri, NULL); + } else { + break; + } + } else if (!strcmp ((gchar *)wpa->name, "name")) { + if (wpa->last != NULL && wpa->last->content != NULL) { + g_autofree gchar *name = NULL; + nodelang = xmlNodeGetLang (wpa->last); + + g_object_get (G_OBJECT (item), "name", &name, NULL); + + if (name == NULL && nodelang == NULL) { + g_free (cname); + cname = g_strdup (g_strstrip ((gchar *)wpa->last->content)); + g_object_set (G_OBJECT (item), "name", cname, NULL); + } else { + for (i = 0; syslangs[i] != NULL; i++) { + if (!strcmp (syslangs[i], (gchar *)nodelang)) { + g_object_set (G_OBJECT (item), "name", + g_strstrip ((gchar *)wpa->last->content), NULL); + break; + } + } + } + + xmlFree (nodelang); + } else { + break; + } + } else if (!strcmp ((gchar *)wpa->name, "options")) { + if (wpa->last != NULL) { + g_object_set (G_OBJECT (item), "placement", + enum_string_to_value (G_DESKTOP_TYPE_DESKTOP_BACKGROUND_STYLE, + g_strstrip ((gchar *)wpa->last->content)), NULL); + SET_FLAG(CC_BACKGROUND_ITEM_HAS_PLACEMENT); + } + } else if (!strcmp ((gchar *)wpa->name, "shade_type")) { + if (wpa->last != NULL) { + g_object_set (G_OBJECT (item), "shading", + enum_string_to_value (G_DESKTOP_TYPE_DESKTOP_BACKGROUND_SHADING, + g_strstrip ((gchar *)wpa->last->content)), NULL); + SET_FLAG(CC_BACKGROUND_ITEM_HAS_SHADING); + } + } else if (!strcmp ((gchar *)wpa->name, "pcolor")) { + if (wpa->last != NULL) { + g_object_set (G_OBJECT (item), "primary-color", + g_strstrip ((gchar *)wpa->last->content), NULL); + SET_FLAG(CC_BACKGROUND_ITEM_HAS_PCOLOR); + } + } else if (!strcmp ((gchar *)wpa->name, "scolor")) { + if (wpa->last != NULL) { + g_object_set (G_OBJECT (item), "secondary-color", + g_strstrip ((gchar *)wpa->last->content), NULL); + SET_FLAG(CC_BACKGROUND_ITEM_HAS_SCOLOR); + } + } else if (!strcmp ((gchar *)wpa->name, "source_url")) { + if (wpa->last != NULL) { + g_object_set (G_OBJECT (item), + "source-url", g_strstrip ((gchar *)wpa->last->content), + "needs-download", FALSE, + NULL); + } + } else if (!strcmp ((gchar *)wpa->name, "text")) { + /* Do nothing here, libxml2 is being weird */ + } else { + g_debug ("Unknown Tag in %s: %s", filename, wpa->name); + } + } + + /* Check whether the target file exists */ + { + const char *uri; + + uri = cc_background_item_get_uri (item); + if (uri != NULL) + { + g_autoptr(GFile) file = NULL; + + file = g_file_new_for_uri (uri); + if (g_file_query_exists (file, NULL) == FALSE) + { + g_clear_pointer (&cname, g_free); + g_clear_object (&item); + continue; + } + } + } + + /* FIXME, this is a broken way of doing, + * need to use proper code here */ + uri = g_filename_to_uri (filename, NULL, NULL); + id = g_strdup_printf ("%s#%s", uri, cname); + + /* Make sure we don't already have this one and that filename exists */ + if (g_hash_table_lookup (xml->wp_hash, id) != NULL) { + continue; + } + + g_object_set (G_OBJECT (item), "flags", flags, NULL); + g_hash_table_insert (xml->wp_hash, + g_strdup (id), + g_object_ref (item)); + if (in_thread) + emit_added_in_idle (xml, g_object_ref (G_OBJECT (item))); + else + g_signal_emit (G_OBJECT (xml), signals[ADDED], 0, item); + retval = TRUE; + } + } + xmlFreeDoc (wplist); + + return retval; +} + +static void +gnome_wp_file_changed (GFileMonitor *monitor, + GFile *file, + GFile *other_file, + GFileMonitorEvent event_type, + CcBackgroundXml *data) +{ + g_autofree gchar *filename = NULL; + + switch (event_type) { + case G_FILE_MONITOR_EVENT_CHANGED: + case G_FILE_MONITOR_EVENT_CREATED: + filename = g_file_get_path (file); + cc_background_xml_load_xml_internal (data, filename, FALSE); + break; + default: + break; + } +} + +static void +cc_background_xml_add_monitor (GFile *directory, + CcBackgroundXml *data) +{ + GFileMonitor *monitor; + g_autoptr(GError) error = NULL; + + monitor = g_file_monitor_directory (directory, + G_FILE_MONITOR_NONE, + NULL, + &error); + if (error != NULL) { + g_autofree gchar *path = NULL; + + path = g_file_get_parse_name (directory); + g_warning ("Unable to monitor directory %s: %s", + path, error->message); + return; + } + + g_signal_connect (monitor, "changed", + G_CALLBACK (gnome_wp_file_changed), + data); + + data->monitors = g_slist_prepend (data->monitors, monitor); +} + +static void +cc_background_xml_load_from_dir (const gchar *path, + CcBackgroundXml *data, + gboolean in_thread) +{ + g_autoptr(GFile) directory = NULL; + g_autoptr(GFileEnumerator) enumerator = NULL; + g_autoptr(GError) error = NULL; + + if (!g_file_test (path, G_FILE_TEST_IS_DIR)) { + return; + } + + directory = g_file_new_for_path (path); + enumerator = g_file_enumerate_children (directory, + G_FILE_ATTRIBUTE_STANDARD_NAME, + G_FILE_QUERY_INFO_NONE, + NULL, + &error); + if (error != NULL) { + g_warning ("Unable to check directory %s: %s", path, error->message); + return; + } + + while (TRUE) { + g_autoptr(GFileInfo) info = NULL; + const gchar *filename; + g_autofree gchar *fullpath = NULL; + + info = g_file_enumerator_next_file (enumerator, NULL, NULL); + if (info == NULL) { + g_file_enumerator_close (enumerator, NULL, NULL); + cc_background_xml_add_monitor (directory, data); + return; + } + + filename = g_file_info_get_name (info); + fullpath = g_build_filename (path, filename, NULL); + + cc_background_xml_load_xml_internal (data, fullpath, in_thread); + } +} + +static void +cc_background_xml_load_list (CcBackgroundXml *data, + gboolean in_thread) +{ + const char * const *system_data_dirs; + g_autofree gchar *datadir = NULL; + gint i; + + datadir = g_build_filename (g_get_user_data_dir (), + "gnome-background-properties", + NULL); + cc_background_xml_load_from_dir (datadir, data, in_thread); + + system_data_dirs = g_get_system_data_dirs (); + for (i = 0; system_data_dirs[i]; i++) { + g_autofree gchar *sdatadir = NULL; + sdatadir = g_build_filename (system_data_dirs[i], + "gnome-background-properties", + NULL); + cc_background_xml_load_from_dir (sdatadir, data, in_thread); + } +} + +gboolean +cc_background_xml_load_list_finish (CcBackgroundXml *xml, + GAsyncResult *result, + GError **error) +{ + g_return_val_if_fail (g_task_is_valid (result, xml), FALSE); + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + return g_task_propagate_boolean (G_TASK (result), error); +} + +static void +load_list_thread (GTask *task, + gpointer source_object, + gpointer task_data, + GCancellable *cancellable) +{ + CcBackgroundXml *xml = CC_BACKGROUND_XML (source_object); + cc_background_xml_load_list (xml, TRUE); + g_task_return_boolean (task, TRUE); +} + +void +cc_background_xml_load_list_async (CcBackgroundXml *xml, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + g_autoptr(GTask) task = NULL; + + g_return_if_fail (CC_IS_BACKGROUND_XML (xml)); + + task = g_task_new (xml, cancellable, callback, user_data); + g_task_run_in_thread (task, load_list_thread); +} + +gboolean +cc_background_xml_load_xml (CcBackgroundXml *xml, + const gchar *filename) +{ + g_return_val_if_fail (CC_IS_BACKGROUND_XML (xml), FALSE); + + if (g_file_test (filename, G_FILE_TEST_IS_REGULAR) == FALSE) + return FALSE; + + return cc_background_xml_load_xml_internal (xml, filename, FALSE); +} + +static void +single_xml_added (CcBackgroundXml *xml, + CcBackgroundItem *item, + CcBackgroundItem **ret) +{ + g_assert (*ret == NULL); + *ret = g_object_ref (item); +} + +CcBackgroundItem * +cc_background_xml_get_item (const char *filename) +{ + g_autoptr(CcBackgroundXml) xml = NULL; + CcBackgroundItem *item = NULL; + + if (g_file_test (filename, G_FILE_TEST_IS_REGULAR) == FALSE) + return NULL; + + xml = cc_background_xml_new (); + g_signal_connect (G_OBJECT (xml), "added", + G_CALLBACK (single_xml_added), &item); + if (cc_background_xml_load_xml (xml, filename) == FALSE) + return NULL; + + return item; +} + +static const char * +enum_to_str (GType type, + int v) +{ + GEnumClass *eclass; + GEnumValue *value; + + eclass = G_ENUM_CLASS (g_type_class_peek (type)); + value = g_enum_get_value (eclass, v); + + g_assert (value); + + return value->value_nick; +} + +void +cc_background_xml_save (CcBackgroundItem *item, + const char *filename) +{ + xmlDoc *wp; + xmlNode *root, *wallpaper; + xmlNode *xml_item G_GNUC_UNUSED; + const char * none = "(none)"; + const char *placement_str, *shading_str; + g_autofree gchar *name = NULL; + g_autofree gchar *pcolor = NULL; + g_autofree gchar *scolor = NULL; + g_autofree gchar *uri = NULL; + g_autofree gchar *source_url = NULL; + CcBackgroundItemFlags flags; + GDesktopBackgroundStyle placement; + GDesktopBackgroundShading shading; + + xmlKeepBlanksDefault (0); + + wp = xmlNewDoc ((xmlChar *)"1.0"); + xmlCreateIntSubset (wp, (xmlChar *)"wallpapers", NULL, (xmlChar *)"gnome-wp-list.dtd"); + root = xmlNewNode (NULL, (xmlChar *)"wallpapers"); + xmlDocSetRootElement (wp, root); + + g_object_get (G_OBJECT (item), + "name", &name, + "uri", &uri, + "shading", &shading, + "placement", &placement, + "primary-color", &pcolor, + "secondary-color", &scolor, + "source-url", &source_url, + "flags", &flags, + NULL); + + placement_str = enum_to_str (G_DESKTOP_TYPE_DESKTOP_BACKGROUND_STYLE, placement); + shading_str = enum_to_str (G_DESKTOP_TYPE_DESKTOP_BACKGROUND_SHADING, shading); + + wallpaper = xmlNewChild (root, NULL, (xmlChar *)"wallpaper", NULL); + xml_item = xmlNewTextChild (wallpaper, NULL, (xmlChar *)"name", (xmlChar *)name); + if (flags & CC_BACKGROUND_ITEM_HAS_URI && + uri != NULL) + { + g_autoptr(GFile) file = NULL; + g_autofree gchar *fname = NULL; + + file = g_file_new_for_commandline_arg (uri); + fname = g_file_get_path (file); + xml_item = xmlNewTextChild (wallpaper, NULL, (xmlChar *)"filename", (xmlChar *)fname); + } + else if (flags & CC_BACKGROUND_ITEM_HAS_URI) + { + xml_item = xmlNewTextChild (wallpaper, NULL, (xmlChar *)"filename", (xmlChar *)none); + } + + if (flags & CC_BACKGROUND_ITEM_HAS_PLACEMENT) + xml_item = xmlNewTextChild (wallpaper, NULL, (xmlChar *)"options", (xmlChar *)placement_str); + if (flags & CC_BACKGROUND_ITEM_HAS_SHADING) + xml_item = xmlNewTextChild (wallpaper, NULL, (xmlChar *)"shade_type", (xmlChar *)shading_str); + if (flags & CC_BACKGROUND_ITEM_HAS_PCOLOR) + xml_item = xmlNewTextChild (wallpaper, NULL, (xmlChar *)"pcolor", (xmlChar *)pcolor); + if (flags & CC_BACKGROUND_ITEM_HAS_SCOLOR) + xml_item = xmlNewTextChild (wallpaper, NULL, (xmlChar *)"scolor", (xmlChar *)scolor); + if (source_url != NULL) + xml_item = xmlNewTextChild (wallpaper, NULL, (xmlChar *)"source_url", (xmlChar *)source_url); + + xmlSaveFormatFile (filename, wp, 1); + xmlFreeDoc (wp); +} + +static void +cc_background_xml_finalize (GObject *object) +{ + CcBackgroundXml *xml; + + g_return_if_fail (object != NULL); + g_return_if_fail (CC_IS_BACKGROUND_XML (object)); + + xml = CC_BACKGROUND_XML (object); + + g_slist_free_full (xml->monitors, g_object_unref); + + g_clear_pointer (&xml->wp_hash, g_hash_table_destroy); + if (xml->item_added_id != 0) { + g_source_remove (xml->item_added_id); + xml->item_added_id = 0; + } + g_clear_pointer (&xml->item_added_queue, g_async_queue_unref); + + G_OBJECT_CLASS (cc_background_xml_parent_class)->finalize (object); +} + +static void +cc_background_xml_class_init (CcBackgroundXmlClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->finalize = cc_background_xml_finalize; + + signals[ADDED] = g_signal_new ("added", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST, + 0, + NULL, NULL, + g_cclosure_marshal_VOID__OBJECT, + G_TYPE_NONE, 1, CC_TYPE_BACKGROUND_ITEM); +} + +static void +cc_background_xml_init (CcBackgroundXml *xml) +{ + xml->wp_hash = g_hash_table_new_full (g_str_hash, + g_str_equal, + (GDestroyNotify) g_free, + (GDestroyNotify) g_object_unref); + xml->item_added_queue = g_async_queue_new_full ((GDestroyNotify) g_object_unref); +} + +CcBackgroundXml * +cc_background_xml_new (void) +{ + return CC_BACKGROUND_XML (g_object_new (CC_TYPE_BACKGROUND_XML, NULL)); +} |