/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */

/* nautilus-convert-metadata.c - Convert old metadata format to gvfs metadata.
 *
 * Copyright (C) 2009 Alexander Larsson
 *
 * Nautilus 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.
 *
 * Nautilus 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; see the file COPYING.  If not,
 * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA 02111-1307, USA.
 *
 * Authors:
 *   Alexander Larsson <alexl@redhat.com>
 */

#include <config.h>

#include <glib.h>
#include <gio/gio.h>
#include <glib/gi18n.h>
#include <string.h>
#include <libxml/tree.h>
#include <gconf/gconf-client.h>

#include <libnautilus-private/nautilus-metadata.h>

#define NAUTILUS_DESKTOP_METADATA_GCONF_PATH "/apps/nautilus/desktop-metadata"

static gboolean quiet = FALSE;
static char *
get_metadata_gconf_path (const char *name,
			 const char *key)
{
	char *res, *escaped_name;

	escaped_name = gconf_escape_key (name, -1);
	res = g_build_filename (NAUTILUS_DESKTOP_METADATA_GCONF_PATH, escaped_name, key + strlen ("metadata::"), NULL);
	g_free (escaped_name);

	return res;
}

static void
desktop_set_metadata_string (GFile *file,
			     const char *key,
			     const char *string)
{
	GConfClient *client;
	char *gconf_key;
	GFile *parent;
	char *name;

	parent = g_file_get_parent (file);
	if (parent == NULL) {
		name = g_strdup ("directory");
	} else {
		g_object_unref (parent);
		name = g_file_get_basename (file);
	}

	client = gconf_client_get_default ();
	gconf_key = get_metadata_gconf_path (name, key);

	gconf_client_set_string (client, gconf_key, string, NULL);

	g_free (gconf_key);
	g_free (name);
	g_object_unref (client);
}

static void
desktop_set_metadata_stringv (GFile *file,
			      const char *key,
			      char **stringv)
{
	GConfClient *client;
	char *gconf_key;
	GSList *list;
	int i;
	GFile *parent;
	char *name;

	parent = g_file_get_parent (file);
	if (parent == NULL) {
		name = g_strdup ("directory");
	} else {
		g_object_unref (parent);
		name = g_file_get_basename (file);
	}

	client = gconf_client_get_default ();
	gconf_key = get_metadata_gconf_path (name, key);

	list = NULL;
	for (i = 0; stringv[i] != NULL; i++) {
		list = g_slist_prepend (list, stringv[i]);
	}
	list = g_slist_reverse (list);

	gconf_client_set_list (client, gconf_key,
			       GCONF_VALUE_STRING,
			       list, NULL);

	g_slist_free (list);
	g_free (gconf_key);
	g_free (name);
	g_object_unref (client);
}

static xmlNodePtr
xml_get_children (xmlNodePtr parent)
{
	if (parent == NULL) {
		return NULL;
	}
	return parent->children;
}

static xmlNodePtr
xml_get_root_children (xmlDocPtr document)
{
	return xml_get_children (xmlDocGetRootElement (document));
}


static char *
get_uri_from_nautilus_metafile_name (const char *filename)
{
	GString *s;
	char c;
	char *base_name, *p;
	int len;

	base_name = g_path_get_basename (filename);
	len = strlen (base_name);
	if (len <=  4 ||
	    strcmp (base_name + len - 4, ".xml") != 0) {
		g_free (base_name);
		return NULL;
	}
	base_name[len-4] = 0;

	s = g_string_new (NULL);

	p = base_name;
	while (*p) {
		c = *p++;
		if (c == '%') {
			c = g_ascii_xdigit_value (p[0]) << 4 |
			    g_ascii_xdigit_value (p[1]);
			p += 2;
		}
		g_string_append_c (s, c);
	}
	g_free (base_name);

	return g_string_free (s, FALSE);
}

static struct {
	const char *old_key;
	const char *new_key;
} metadata_keys[] = {
	{"default_component", "metadata::" NAUTILUS_METADATA_KEY_DEFAULT_VIEW},
	{"background_color", "metadata::" NAUTILUS_METADATA_KEY_LOCATION_BACKGROUND_COLOR},
	{"background_tile_image", "metadata::" NAUTILUS_METADATA_KEY_LOCATION_BACKGROUND_IMAGE},
	{"icon_view_zoom_level", "metadata::" NAUTILUS_METADATA_KEY_ICON_VIEW_ZOOM_LEVEL},
	{"icon_view_auto_layout", "metadata::" NAUTILUS_METADATA_KEY_ICON_VIEW_AUTO_LAYOUT},
	{"icon_view_tighter_layout", "metadata::" NAUTILUS_METADATA_KEY_ICON_VIEW_TIGHTER_LAYOUT},
	{"icon_view_sort_by", "metadata::" NAUTILUS_METADATA_KEY_ICON_VIEW_SORT_BY},
	{"icon_view_sort_reversed", "metadata::" NAUTILUS_METADATA_KEY_ICON_VIEW_SORT_REVERSED},
	{"icon_view_keep_aligned", "metadata::" NAUTILUS_METADATA_KEY_ICON_VIEW_KEEP_ALIGNED},
	{"icon_view_layout_timestamp", "metadata::" NAUTILUS_METADATA_KEY_ICON_VIEW_LAYOUT_TIMESTAMP},
	{"list_view_zoom_level", "metadata::" NAUTILUS_METADATA_KEY_LIST_VIEW_ZOOM_LEVEL},
	{"list_view_sort_column", "metadata::" NAUTILUS_METADATA_KEY_LIST_VIEW_SORT_COLUMN},
	{"list_view_sort_reversed", "metadata::" NAUTILUS_METADATA_KEY_LIST_VIEW_SORT_REVERSED},
	{"list_view_visible_columns", "metadata::" NAUTILUS_METADATA_KEY_LIST_VIEW_VISIBLE_COLUMNS},
	{"list_view_column_order", "metadata::" NAUTILUS_METADATA_KEY_LIST_VIEW_COLUMN_ORDER},
	{"compact_view_zoom_level", "metadata::" NAUTILUS_METADATA_KEY_COMPACT_VIEW_ZOOM_LEVEL},
	{"window_geometry", "metadata::" NAUTILUS_METADATA_KEY_WINDOW_GEOMETRY},
	{"window_scroll_position", "metadata::" NAUTILUS_METADATA_KEY_WINDOW_SCROLL_POSITION},
	{"window_show_hidden_files", "metadata::" NAUTILUS_METADATA_KEY_WINDOW_SHOW_HIDDEN_FILES},
	{"window_maximized", "metadata::" NAUTILUS_METADATA_KEY_WINDOW_MAXIMIZED},
	{"window_sticky", "metadata::" NAUTILUS_METADATA_KEY_WINDOW_STICKY},
	{"window_keep_above", "metadata::" NAUTILUS_METADATA_KEY_WINDOW_KEEP_ABOVE},
	{"sidebar_background_color", "metadata::" NAUTILUS_METADATA_KEY_SIDEBAR_BACKGROUND_COLOR},
	{"sidebar_background_tile_image", "metadata::" NAUTILUS_METADATA_KEY_SIDEBAR_BACKGROUND_IMAGE},
	{"sidebar_buttons", "metadata::" NAUTILUS_METADATA_KEY_SIDEBAR_BUTTONS},
	{"annotation", "metadata::" NAUTILUS_METADATA_KEY_ANNOTATION},
	{"icon_position", "metadata::" NAUTILUS_METADATA_KEY_ICON_POSITION},
	{"icon_position_timestamp", "metadata::" NAUTILUS_METADATA_KEY_ICON_POSITION_TIMESTAMP},
	{"icon_scale", "metadata::" NAUTILUS_METADATA_KEY_ICON_SCALE},
	{"custom_icon", "metadata::" NAUTILUS_METADATA_KEY_CUSTOM_ICON},
	{"screen", "metadata::" NAUTILUS_METADATA_KEY_SCREEN},
	{"keyword", "metadata::" NAUTILUS_METADATA_KEY_EMBLEMS},
};

static const char *
convert_key_name (const char *old_key)
{
	int i;

	for (i = 0; i < G_N_ELEMENTS (metadata_keys); i++) {
		if (strcmp (metadata_keys[i].old_key, old_key) == 0) {
			return metadata_keys[i].new_key;
		}
	}

	return NULL;
}

static void
parse_xml_node (GFile *file,
		xmlNodePtr filenode)
{
	xmlNodePtr node;
	xmlAttrPtr attr;
	xmlChar *property;
	const char *new_key;
	GHashTable *list_keys;
	GList *keys, *l;
	GHashTableIter iter;
	GFileInfo *info;
	int i;
	char **strv;
	GError *error;

	info = NULL;
	if (!g_file_has_uri_scheme (file, "x-nautilus-desktop")) {
		info = g_file_info_new ();
	}

	for (attr = filenode->properties; attr != NULL; attr = attr->next) {
		if (strcmp ((char *)attr->name, "name") == 0 ||
		    strcmp ((char *)attr->name, "timestamp") == 0) {
			continue;
		}

		new_key = convert_key_name (attr->name);
		if (new_key) {
			property = xmlGetProp (filenode, attr->name);
			if (property) {
				if (info) {
					g_file_info_set_attribute_string (info,
									  new_key,
									  property);
				} else {
					desktop_set_metadata_string (file, new_key, property);
				}
				xmlFree (property);
			}
		}
	}

	list_keys = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, NULL);
	for (node = filenode->children; node != NULL; node = node->next) {
		for (attr = node->properties; attr != NULL; attr = attr->next) {
			new_key = convert_key_name (node->name);
			if (new_key) {
				property = xmlGetProp (node, attr->name);
				if (property) {
					keys = g_hash_table_lookup (list_keys, new_key);
					keys = g_list_append (keys, property);
					g_hash_table_replace (list_keys, (char *)new_key, keys);
				}
			}
		}
	}

	g_hash_table_iter_init (&iter, list_keys);
	while (g_hash_table_iter_next (&iter, (void **)&new_key, (void **)&keys)) {
		strv = g_new0 (char *, g_list_length (keys) + 1);

		for (l = keys, i = 0; l != NULL; l = l->next, i++) {
			strv[i] = l->data;
		}
		if (info) {
			g_file_info_set_attribute_stringv (info,
							   new_key,
							   strv);
		} else {
			desktop_set_metadata_stringv (file, new_key, strv);
		}
		g_free (strv);
		g_list_foreach (keys, (GFunc)xmlFree, NULL);
		g_list_free (keys);
	}
	g_hash_table_destroy (list_keys);

	if (info) {
		error = NULL;
		if (!g_file_set_attributes_from_info (file,
						      info,
						      0, NULL, &error)) {
			char *uri;

			uri = g_file_get_uri (file);
			if (!quiet) {
				g_print ("error setting info for %s: %s\n", uri, error->message);
			}
			g_free (uri);
			g_error_free (error);
		}
		g_object_unref (info);
	}
}

static void
convert_xml_file (xmlDocPtr xml,
		  GFile *dir)
{
	xmlNodePtr node;
	xmlChar *name;
	char *unescaped_name;
	GFile *file;

	for (node = xml_get_root_children (xml);
	     node != NULL; node = node->next) {
		if (strcmp ((char *)node->name, "file") == 0) {
			name = xmlGetProp (node, (xmlChar *)"name");
			unescaped_name = g_uri_unescape_string ((char *)name, "/");
			xmlFree (name);

			if (unescaped_name == NULL) {
				continue;
			}

			if (strcmp (unescaped_name, ".") == 0) {
				file = g_object_ref (dir);
			} else  {
			    file = g_file_get_child (dir, unescaped_name);
			}

			parse_xml_node (file, node);
			g_object_unref (file);
			g_free (unescaped_name);
		}
	}
}

static void
convert_nautilus_file (char *file)
{
	GFile *dir;
	char *uri;
	gchar *contents;
	gsize length;
	xmlDocPtr xml;

	if (!g_file_get_contents (file, &contents, &length, NULL)) {
		if (!quiet) {
			g_print ("failed to load %s\n", file);
		}
		return;
	}

	uri = get_uri_from_nautilus_metafile_name (file);
	if (uri == NULL) {
		g_free (contents);
		return;
	}

	dir = g_file_new_for_uri (uri);
	g_free (uri);

	xml = xmlParseMemory (contents, length);
	g_free (contents);
	if (xml == NULL) {
		return;
	}

	convert_xml_file (xml, dir);
	xmlFreeDoc (xml);
}

static GOptionEntry entries[] =
{
	{ "quiet", 'q', 0, G_OPTION_ARG_NONE, &quiet,
	  "Don't show errors", NULL },
	{ NULL }
};

int
main (int argc, char *argv[])
{
	GOptionContext *context;
	GError *error = NULL;
	int i;

	g_type_init ();

	context = g_option_context_new ("<nautilus metadata files> - convert nautilus metadata");
	g_option_context_add_main_entries (context, entries, NULL);
	if (!g_option_context_parse (context, &argc, &argv, &error)) {
		g_printerr ("option parsing failed: %s\n", error->message);
		return 1;
	}

	if (argc < 2) {
		GDir *dir;
		char *metafile_dir;
		char *file;
		const char *entry;

		/* Convert all metafiles */

		metafile_dir = g_build_filename (g_get_home_dir (),
						 ".nautilus/metafiles", NULL);

		dir = g_dir_open (metafile_dir, 0, NULL);
		if (dir) {
			while ((entry = g_dir_read_name (dir)) != NULL) {
				file = g_build_filename (metafile_dir, entry, NULL);
				if (g_str_has_suffix (file, ".xml"))
					convert_nautilus_file (file);
				g_free (file);
			}
			g_dir_close (dir);
		}
		g_free (metafile_dir);
	} else {
		for (i = 1; i < argc; i++) {
			convert_nautilus_file (argv[i]);
		}
	}

	return 0;
}
