medit/moo/mooutils/moohistorymgr.c
2015-12-25 21:30:53 -08:00

1918 lines
52 KiB
C

/*
* moohistorymgr.h
*
* Copyright (C) 2004-2010 by Yevgen Muntyan <emuntyan@users.sourceforge.net>
*
* This file is part of medit. medit is free software; you can
* redistribute it and/or modify it under the terms of the
* GNU Lesser General Public License as published by the
* Free Software Foundation; either version 2.1 of the License,
* or (at your option) any later version.
*
* You should have received a copy of the GNU Lesser General Public
* License along with medit. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* class:MooHistoryMgr: (parent GObject) (moo.private 1)
**/
#include "mooutils/moohistorymgr.h"
#include "mooutils/moofileicon.h"
#include "mooutils/mooapp-ipc.h"
#include "mooutils/moofilewatch.h"
#include "mooutils/mooutils-misc.h"
#include "mooutils/mooutils-gobject.h"
#include "mooutils/mooutils-fs.h"
#include "mooutils/mooutils-treeview.h"
#include "mooutils/mooprefs.h"
#include "mooutils/moomarkup.h"
#include "mooutils/mooutils-thread.h"
#include "mooutils/moolist.h"
#include "mooutils/mootype-macros.h"
#include "marshals.h"
#include <stdarg.h>
#include <string.h>
#define N_MENU_ITEMS 10
#define MAX_ITEM_NUMBER 5000
#define IPC_ID "MooHistoryMgr"
MOO_DEFINE_DLIST (WidgetList, widget_list, GtkWidget)
MOO_DEFINE_QUEUE (MooHistoryItem, moo_history_item)
MOO_DEFINE_QUARK (moo-history-mgr-parse-error, moo_history_mgr_parse_error_quark)
struct _MooHistoryMgrPrivate {
char *filename;
char *basename;
char *name;
char *ipc_id;
guint save_idle;
guint update_widgets_idle;
WidgetList *widgets;
MooHistoryItemQueue *files;
GHashTable *hash;
guint loaded : 1;
};
typedef struct {
MooHistoryCallback callback;
gpointer data;
GDestroyNotify notify;
} CallbackData;
struct _MooHistoryItem {
char *uri;
GData *data;
MooFileIcon *icon;
};
typedef enum {
UPDATE_ITEM_UPDATE,
UPDATE_ITEM_REMOVE,
UPDATE_ITEM_ADD
} UpdateType;
static GObject *moo_history_mgr_constructor (GType type,
guint n_props,
GObjectConstructParam *props);
static void moo_history_mgr_dispose (GObject *object);
static void moo_history_mgr_set_property(GObject *object,
guint property_id,
const GValue *value,
GParamSpec *pspec);
static void moo_history_mgr_get_property(GObject *object,
guint property_id,
GValue *value,
GParamSpec *pspec);
static const char *get_filename (MooHistoryMgr *mgr);
static const char *get_basename (MooHistoryMgr *mgr);
static void ensure_files (MooHistoryMgr *mgr);
static void schedule_save (MooHistoryMgr *mgr);
static void moo_history_mgr_save (MooHistoryMgr *mgr);
static void populate_menu (MooHistoryMgr *mgr,
GtkWidget *menu);
static void schedule_update_widgets (MooHistoryMgr *mgr);
static void moo_history_item_format (MooHistoryItem *item,
GString *buffer);
static gboolean moo_history_item_equal (MooHistoryItem *item1,
MooHistoryItem *item2);
static MooFileIcon *moo_history_item_get_icon (MooHistoryItem *item);
static char *uri_get_basename (const char *uri);
static char *uri_get_display_name (const char *uri);
static void ipc_callback (GObject *obj,
const char *data,
gsize len);
static void ipc_notify_add_file (MooHistoryMgr *mgr,
MooHistoryItem *item);
static void ipc_notify_update_file (MooHistoryMgr *mgr,
MooHistoryItem *item);
static void ipc_notify_remove_file (MooHistoryMgr *mgr,
MooHistoryItem *item);
G_DEFINE_TYPE (MooHistoryMgr, moo_history_mgr, G_TYPE_OBJECT)
enum {
PROP_0,
PROP_NAME,
PROP_EMPTY
};
enum {
CHANGED,
N_SIGNALS
};
static guint signals[N_SIGNALS];
static void
moo_history_mgr_class_init (MooHistoryMgrClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
g_type_class_add_private (klass, sizeof (MooHistoryMgrPrivate));
object_class->constructor = moo_history_mgr_constructor;
object_class->set_property = moo_history_mgr_set_property;
object_class->get_property = moo_history_mgr_get_property;
object_class->dispose = moo_history_mgr_dispose;
g_object_class_install_property (object_class, PROP_NAME,
g_param_spec_string ("name", "name", "name",
NULL, (GParamFlags) (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)));
g_object_class_install_property (object_class, PROP_EMPTY,
g_param_spec_boolean ("empty", "empty", "empty",
TRUE, G_PARAM_READABLE));
signals[CHANGED] =
_moo_signal_new_cb ("changed",
MOO_TYPE_HISTORY_MGR,
G_SIGNAL_RUN_LAST,
G_CALLBACK (schedule_update_widgets),
NULL, NULL,
_moo_marshal_VOID__VOID,
G_TYPE_NONE, 0);
}
static void
moo_history_mgr_init (MooHistoryMgr *mgr)
{
mgr->priv = G_TYPE_INSTANCE_GET_PRIVATE (mgr, MOO_TYPE_HISTORY_MGR, MooHistoryMgrPrivate);
mgr->priv->filename = NULL;
mgr->priv->basename = NULL;
mgr->priv->files = moo_history_item_queue_new ();
mgr->priv->hash = g_hash_table_new_full (g_str_hash, g_str_equal,
g_free, NULL);
mgr->priv->loaded = FALSE;
}
static GObject *
moo_history_mgr_constructor (GType type,
guint n_props,
GObjectConstructParam *props)
{
GObject *object;
MooHistoryMgr *mgr;
object = G_OBJECT_CLASS (moo_history_mgr_parent_class)->
constructor (type, n_props, props);
mgr = MOO_HISTORY_MGR (object);
if (mgr->priv->name)
{
mgr->priv->ipc_id = g_strdup_printf (IPC_ID "/%s", mgr->priv->name);
moo_ipc_register_client (G_OBJECT (mgr), mgr->priv->ipc_id, ipc_callback);
}
return object;
}
static void
moo_history_mgr_dispose (GObject *object)
{
MooHistoryMgr *mgr = MOO_HISTORY_MGR (object);
if (mgr->priv)
{
moo_history_mgr_shutdown (mgr);
if (mgr->priv->files)
{
moo_history_item_queue_foreach (mgr->priv->files, (MooHistoryItemListFunc) moo_history_item_free, NULL);
moo_history_item_queue_free_links (mgr->priv->files);
g_hash_table_destroy (mgr->priv->hash);
}
g_free (mgr->priv->name);
g_free (mgr->priv->filename);
g_free (mgr->priv->basename);
mgr->priv = NULL;
}
G_OBJECT_CLASS (moo_history_mgr_parent_class)->dispose (object);
}
void
moo_history_mgr_shutdown (MooHistoryMgr *mgr)
{
g_return_if_fail (MOO_IS_HISTORY_MGR (mgr));
if (!mgr->priv)
return;
if (mgr->priv->ipc_id)
{
moo_ipc_unregister_client (G_OBJECT (mgr), mgr->priv->ipc_id);
g_free (mgr->priv->ipc_id);
mgr->priv->ipc_id = NULL;
}
if (mgr->priv->save_idle)
{
g_source_remove (mgr->priv->save_idle);
mgr->priv->save_idle = 0;
moo_history_mgr_save (mgr);
}
if (mgr->priv->update_widgets_idle)
{
g_source_remove (mgr->priv->update_widgets_idle);
mgr->priv->update_widgets_idle = 0;
}
while (mgr->priv->widgets)
gtk_widget_destroy (mgr->priv->widgets->data);
}
static void
moo_history_mgr_set_property (GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
MooHistoryMgr *mgr = MOO_HISTORY_MGR (object);
switch (prop_id)
{
case PROP_NAME:
MOO_ASSIGN_STRING (mgr->priv->name, g_value_get_string (value));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
moo_history_mgr_get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
MooHistoryMgr *mgr = MOO_HISTORY_MGR (object);
switch (prop_id)
{
case PROP_NAME:
g_value_set_string (value, mgr->priv->name);
break;
case PROP_EMPTY:
g_value_set_boolean (value, moo_history_mgr_get_n_items (mgr) == 0);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static const char *
get_basename (MooHistoryMgr *mgr)
{
if (!mgr->priv->basename)
{
if (mgr->priv->name)
{
char *name = g_ascii_strdown (mgr->priv->name, -1);
mgr->priv->basename = g_strdup_printf ("recent-files-%s.xml", name);
g_free (name);
}
else
{
mgr->priv->basename = g_strdup ("recent-files.xml");
}
}
return mgr->priv->basename;
}
static const char *
get_filename (MooHistoryMgr *mgr)
{
if (!mgr->priv->filename)
mgr->priv->filename = moo_get_user_cache_file (get_basename (mgr));
return mgr->priv->filename;
}
char *
_moo_history_mgr_get_filename (MooHistoryMgr *mgr)
{
g_return_val_if_fail (MOO_IS_HISTORY_MGR (mgr), NULL);
return g_strdup (get_filename (mgr));
}
/*****************************************************************/
/* Loading and saving
*/
#define ELM_ROOT "md-recent-files"
#define ELM_UPDATE "md-recent-files-update"
#define ELM_ITEM "item"
#define ELM_DATA "data"
#define PROP_VERSION "version"
#define PROP_VERSION_VALUE "1.0"
#define PROP_URI "uri"
#define PROP_KEY "key"
#define PROP_VALUE "value"
#define PROP_TYPE "type"
#define ELM_RECENT_ITEMS "recent-items"
static void
add_file (MooHistoryMgr *mgr,
MooHistoryItem *item)
{
const char *uri;
uri = moo_history_item_get_uri (item);
if (g_hash_table_lookup (mgr->priv->hash, uri) != NULL)
{
g_critical ("duplicated uri '%s'", uri);
moo_history_item_free (item);
return;
}
moo_history_item_queue_push_tail (mgr->priv->files, item);
g_hash_table_insert (mgr->priv->hash, g_strdup (uri),
mgr->priv->files->tail);
}
static gboolean
parse_element (const char *filename,
MooMarkupNode *elm,
MooHistoryItem **item_p)
{
const char *uri;
MooHistoryItem *item;
MooMarkupNode *child;
if (strcmp (elm->name, ELM_ITEM) != 0)
{
g_critical ("in file '%s': invalid element '%s'",
filename ? filename : "NONE", elm->name);
return FALSE;
}
if (!(uri = moo_markup_get_prop (elm, PROP_URI)))
{
g_critical ("in file '%s': attribute '%s' missing",
filename ? filename : "NONE", PROP_URI);
return FALSE;
}
item = moo_history_item_new (uri, NULL);
g_return_val_if_fail (item != NULL, FALSE);
for (child = elm->children; child != NULL; child = child->next)
{
const char *key, *value;
if (!MOO_MARKUP_IS_ELEMENT (child))
continue;
if (strcmp (child->name, ELM_DATA) != 0)
{
g_critical ("in file '%s': invalid element '%s'",
filename ? filename : "NONE", child->name);
continue;
}
key = moo_markup_get_prop (child, PROP_KEY);
value = moo_markup_get_prop (child, PROP_VALUE);
if (!key || !key[0])
{
g_critical ("in file '%s': attribute '%s' missing",
filename ? filename : "NONE", PROP_KEY);
continue;
}
else if (!value || !value[0])
{
g_critical ("in file '%s': attribute '%s' missing",
filename ? filename : "NONE", PROP_VALUE);
continue;
}
moo_history_item_set (item, key, value);
}
*item_p = item;
return TRUE;
}
typedef enum {
ELEMENT_NONE = 0,
ELEMENT_ROOT,
ELEMENT_ITEM,
ELEMENT_DATA
} Element;
typedef struct {
gboolean seen_root;
Element element;
MooHistoryItem *item;
MooHistoryMgr *mgr;
} ParserData;
static void
start_element_root (const gchar *element_name,
const gchar **attribute_names,
const gchar **attribute_values,
ParserData *data,
GError **error)
{
gboolean seen_version = FALSE;
const char **p, **v;
if (data->seen_root)
{
g_set_error (error, MOO_HISTORY_MGR_PARSE_ERROR,
MOO_HISTORY_MGR_PARSE_ERROR_INVALID_CONTENT,
"invalid element '%s'", element_name);
return;
}
if (strcmp (element_name, ELM_ROOT) != 0)
{
g_set_error (error, MOO_HISTORY_MGR_PARSE_ERROR,
MOO_HISTORY_MGR_PARSE_ERROR_INVALID_CONTENT,
"invalid element '%s'", element_name);
return;
}
for (p = attribute_names, v = attribute_values; p && *p; p++, v++)
{
if (seen_version || strcmp (*p, PROP_VERSION) != 0)
{
g_set_error (error, MOO_HISTORY_MGR_PARSE_ERROR,
MOO_HISTORY_MGR_PARSE_ERROR_INVALID_CONTENT,
"invalid attribute '%s'", *p);
return;
}
if (strcmp (*v, PROP_VERSION_VALUE) != 0)
{
g_set_error (error, MOO_HISTORY_MGR_PARSE_ERROR,
MOO_HISTORY_MGR_PARSE_ERROR_INVALID_CONTENT,
"invalid version value '%s'", *v);
return;
}
seen_version = TRUE;
}
if (!seen_version)
{
g_set_error (error, MOO_HISTORY_MGR_PARSE_ERROR,
MOO_HISTORY_MGR_PARSE_ERROR_INVALID_CONTENT,
"version attribute missing");
return;
}
data->seen_root = TRUE;
data->element = ELEMENT_ROOT;
}
static void
start_element_item (const gchar *element_name,
const gchar **attribute_names,
const gchar **attribute_values,
ParserData *data,
GError **error)
{
MooHistoryItem *item = NULL;
const char **p, **v;
if (strcmp (element_name, ELM_ITEM) != 0)
{
g_set_error (error, MOO_HISTORY_MGR_PARSE_ERROR,
MOO_HISTORY_MGR_PARSE_ERROR_INVALID_CONTENT,
"invalid element '%s'",
element_name);
return;
}
data->element = ELEMENT_ITEM;
for (p = attribute_names, v = attribute_values; p && *p; p++, v++)
{
if (strcmp (*p, PROP_URI) != 0)
{
g_set_error (error, MOO_HISTORY_MGR_PARSE_ERROR,
MOO_HISTORY_MGR_PARSE_ERROR_INVALID_CONTENT,
"invalid attribute '%s'", *v);
return;
}
if (item != NULL)
{
g_set_error (error, MOO_HISTORY_MGR_PARSE_ERROR,
MOO_HISTORY_MGR_PARSE_ERROR_INVALID_CONTENT,
"duplicate attribute '%s'", *v);
return;
}
item = moo_history_item_new (*v, NULL);
g_return_if_fail (item != NULL);
}
if (!item)
{
g_set_error (error, MOO_HISTORY_MGR_PARSE_ERROR,
MOO_HISTORY_MGR_PARSE_ERROR_INVALID_CONTENT,
"missing attribute '%s'", PROP_URI);
return;
}
data->item = item;
}
static void
start_element_data (const gchar *element_name,
const gchar **attribute_names,
const gchar **attribute_values,
ParserData *data,
GError **error)
{
const char **p, **v;
const char *key = NULL;
const char *value = NULL;
g_return_if_fail (data->item != NULL);
if (strcmp (element_name, ELM_DATA) != 0)
{
g_set_error (error, MOO_HISTORY_MGR_PARSE_ERROR,
MOO_HISTORY_MGR_PARSE_ERROR_INVALID_CONTENT,
"invalid element '%s'",
element_name);
return;
}
data->element = ELEMENT_DATA;
for (p = attribute_names, v = attribute_values; p && *p; p++, v++)
{
if (strcmp (*p, PROP_KEY) == 0)
{
key = *v;
}
else if (strcmp (*p, PROP_VALUE) == 0)
{
value = *v;
}
else
{
g_set_error (error, MOO_HISTORY_MGR_PARSE_ERROR,
MOO_HISTORY_MGR_PARSE_ERROR_INVALID_CONTENT,
"invalid attribute '%s'", *v);
return;
}
}
if (!key || !key[0])
{
g_set_error (error, MOO_HISTORY_MGR_PARSE_ERROR,
MOO_HISTORY_MGR_PARSE_ERROR_INVALID_CONTENT,
"missing attribute '%s'", PROP_KEY);
return;
}
if (!value)
{
g_set_error (error, MOO_HISTORY_MGR_PARSE_ERROR,
MOO_HISTORY_MGR_PARSE_ERROR_INVALID_CONTENT,
"missing attribute '%s'", PROP_VALUE);
return;
}
moo_history_item_set (data->item, key, value);
}
static void
parser_start_element (G_GNUC_UNUSED GMarkupParseContext *context,
const gchar *element_name,
const gchar **attribute_names,
const gchar **attribute_values,
ParserData *data,
GError **error)
{
switch (data->element)
{
case ELEMENT_NONE:
start_element_root (element_name, attribute_names,
attribute_values, data, error);
break;
case ELEMENT_ROOT:
start_element_item (element_name, attribute_names,
attribute_values, data, error);
break;
case ELEMENT_ITEM:
start_element_data (element_name, attribute_names,
attribute_values, data, error);
break;
case ELEMENT_DATA:
g_set_error (error, MOO_HISTORY_MGR_PARSE_ERROR,
MOO_HISTORY_MGR_PARSE_ERROR_INVALID_CONTENT,
"invalid element '%s'", element_name);
break;
}
}
static void
parser_end_element (G_GNUC_UNUSED GMarkupParseContext *context,
G_GNUC_UNUSED const gchar *element_name,
ParserData *data,
G_GNUC_UNUSED GError **error)
{
switch (data->element)
{
case ELEMENT_ROOT:
data->element = ELEMENT_NONE;
break;
case ELEMENT_ITEM:
data->element = ELEMENT_ROOT;
if (data->item)
{
add_file (data->mgr, data->item);
data->item = NULL;
}
break;
case ELEMENT_DATA:
data->element = ELEMENT_ITEM;
break;
case ELEMENT_NONE:
g_assert_not_reached ();
break;
}
}
static void
load_file (MooHistoryMgr *mgr)
{
const char *filename;
GMarkupParser parser = {0};
ParserData data = {0};
GError *error = NULL;
mgr->priv->loaded = TRUE;
filename = get_filename (mgr);
g_return_if_fail (filename != NULL);
if (!g_file_test (filename, G_FILE_TEST_EXISTS))
return;
parser.start_element = (MooMarkupStartElementFunc) parser_start_element;
parser.end_element = (MooMarkupEndElementFunc) parser_end_element;
data.seen_root = FALSE;
data.element = ELEMENT_NONE;
data.item = NULL;
data.mgr = mgr;
if (!moo_parse_markup_file (filename, &parser, &data, &error))
{
g_critical ("could not load file '%s': %s",
filename, moo_error_message (error));
g_error_free (error);
}
if (data.item)
moo_history_item_free (data.item);
}
static gboolean
parse_update_item (MooMarkupDoc *xml,
MooHistoryItem **item,
UpdateType *type)
{
const char *version;
const char *update_type_string;
MooMarkupNode *root, *child;
if (!(root = moo_markup_get_root_element (xml, ELM_UPDATE)))
{
g_critical ("missing element %s", ELM_UPDATE);
return FALSE;
}
if (!(version = moo_markup_get_prop (root, PROP_VERSION)) ||
strcmp (version, PROP_VERSION_VALUE) != 0)
{
g_critical ("invalid version value '%s'",
version ? version : "(null)");
return FALSE;
}
if (!(update_type_string = moo_markup_get_prop (root, PROP_TYPE)))
{
g_critical ("attribute '%s' missing", PROP_TYPE);
return FALSE;
}
if (strcmp (update_type_string, "add") == 0)
*type = UPDATE_ITEM_ADD;
else if (strcmp (update_type_string, "remove") == 0)
*type = UPDATE_ITEM_REMOVE;
else if (strcmp (update_type_string, "update") == 0)
*type = UPDATE_ITEM_UPDATE;
else
{
g_critical ("invalid value '%s' for attribute '%s'",
update_type_string, PROP_TYPE);
return FALSE;
}
for (child = root->children; child != NULL; child = child->next)
if (MOO_MARKUP_IS_ELEMENT (child))
return parse_element (NULL, child, item);
g_critical ("element '%s' missing", ELM_ITEM);
return FALSE;
}
static void
ensure_files (MooHistoryMgr *mgr)
{
if (!mgr->priv->loaded)
load_file (mgr);
}
static gboolean
save_in_idle (MooHistoryMgr *mgr)
{
mgr->priv->save_idle = 0;
moo_history_mgr_save (mgr);
return FALSE;
}
static void
schedule_save (MooHistoryMgr *mgr)
{
if (!mgr->priv->save_idle)
mgr->priv->save_idle = g_idle_add ((GSourceFunc) save_in_idle, mgr);
}
static void
moo_history_mgr_save (MooHistoryMgr *mgr)
{
const char *filename;
GError *error = NULL;
MooFileWriter *writer;
g_return_if_fail (MOO_IS_HISTORY_MGR (mgr));
if (!mgr->priv->files)
return;
filename = get_filename (mgr);
if (!mgr->priv->files->length)
{
mgw_errno_t err;
mgw_unlink (filename, &err);
return;
}
if ((writer = moo_config_writer_new (filename, FALSE, &error)))
{
GString *string;
MooHistoryItemList *l;
moo_file_writer_write (writer, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n", -1);
moo_file_writer_write (writer, "<" ELM_ROOT " " PROP_VERSION "=\"" PROP_VERSION_VALUE "\">\n", -1);
string = g_string_new (NULL);
for (l = mgr->priv->files->head; l != NULL; l = l->next)
{
MooHistoryItem *item = l->data;
g_string_truncate (string, 0);
moo_history_item_format (item, string);
if (!moo_file_writer_write (writer, string->str, -1))
break;
}
g_string_free (string, TRUE);
moo_file_writer_write (writer, "</" ELM_ROOT ">\n", -1);
moo_file_writer_close (writer, &error);
}
if (error)
{
g_critical ("could not save file '%s': %s",
filename, moo_error_message (error));
g_error_free (error);
}
}
static char *
format_for_update (MooHistoryItem *item,
UpdateType type)
{
GString *buffer;
const char *update_types[3] = {"update", "remove", "add"};
g_return_val_if_fail (type < 3, NULL);
buffer = g_string_new (NULL);
g_string_append_printf (buffer, "<%s %s=\"%s\" %s=\"%s\">\n",
ELM_UPDATE, PROP_VERSION, PROP_VERSION_VALUE,
PROP_TYPE, update_types[type]);
moo_history_item_format (item, buffer);
g_string_append (buffer, "</" ELM_UPDATE ">\n");
return g_string_free (buffer, FALSE);
}
guint
moo_history_mgr_get_n_items (MooHistoryMgr *mgr)
{
g_return_val_if_fail (MOO_IS_HISTORY_MGR (mgr), 0);
ensure_files (mgr);
return mgr->priv->files->length;
}
void
moo_history_mgr_add_uri (MooHistoryMgr *mgr,
const char *uri)
{
MooHistoryItem *freeme = NULL;
MooHistoryItem *item;
g_return_if_fail (MOO_IS_HISTORY_MGR (mgr));
g_return_if_fail (uri && uri[0]);
if (!(item = moo_history_mgr_find_uri (mgr, uri)))
{
freeme = moo_history_item_new (uri, NULL);
item = freeme;
}
moo_history_mgr_add_file (mgr, item);
moo_history_item_free (freeme);
}
static void
moo_history_mgr_add_file_real (MooHistoryMgr *mgr,
MooHistoryItem *item,
gboolean notify)
{
const char *uri;
MooHistoryItemList *link;
MooHistoryItem *new_item = NULL;
g_return_if_fail (MOO_IS_HISTORY_MGR (mgr));
g_return_if_fail (item != NULL);
ensure_files (mgr);
uri = moo_history_item_get_uri (item);
link = (MooHistoryItemList*) g_hash_table_lookup (mgr->priv->hash, uri);
if (!link)
{
MooHistoryItem *copy = moo_history_item_copy (item);
moo_history_item_queue_push_head (mgr->priv->files, copy);
new_item = copy;
g_hash_table_insert (mgr->priv->hash, g_strdup (uri),
mgr->priv->files->head);
}
else if (link != mgr->priv->files->head ||
!moo_history_item_equal (item, link->data))
{
MooHistoryItem *tmp = link->data;
moo_history_item_queue_unlink (mgr->priv->files, link);
moo_history_item_queue_push_head_link (mgr->priv->files, link);
new_item = link->data = moo_history_item_copy (item);
moo_history_item_free (tmp);
}
if (new_item)
{
g_signal_emit (mgr, signals[CHANGED], 0);
if (notify)
{
schedule_save (mgr);
ipc_notify_add_file (mgr, new_item);
}
if (mgr->priv->files->length == 1)
g_object_notify (G_OBJECT (mgr), "empty");
}
if (mgr->priv->files->length > MAX_ITEM_NUMBER)
moo_history_mgr_remove_uri (mgr,
moo_history_item_get_uri (mgr->priv->files->tail->data));
}
void
moo_history_mgr_add_file (MooHistoryMgr *mgr,
MooHistoryItem *item)
{
moo_history_mgr_add_file_real (mgr, item, TRUE);
}
static void
moo_history_mgr_update_file_real (MooHistoryMgr *mgr,
MooHistoryItem *file,
gboolean notify)
{
const char *uri;
MooHistoryItemList *link;
g_return_if_fail (MOO_IS_HISTORY_MGR (mgr));
g_return_if_fail (file != NULL);
ensure_files (mgr);
uri = moo_history_item_get_uri (file);
link = (MooHistoryItemList*) g_hash_table_lookup (mgr->priv->hash, uri);
if (!link)
{
moo_history_mgr_add_file (mgr, file);
}
else if (!moo_history_item_equal (link->data, file))
{
MooHistoryItem *tmp = link->data;
link->data = moo_history_item_copy (file);
moo_history_item_free (tmp);
g_signal_emit (mgr, signals[CHANGED], 0);
if (notify)
{
schedule_save (mgr);
ipc_notify_update_file (mgr, link->data);
}
}
}
void
moo_history_mgr_update_file (MooHistoryMgr *mgr,
MooHistoryItem *file)
{
moo_history_mgr_update_file_real (mgr, file, TRUE);
}
MooHistoryItem *
moo_history_mgr_find_uri (MooHistoryMgr *mgr,
const char *uri)
{
MooHistoryItemList *link;
g_return_val_if_fail (MOO_IS_HISTORY_MGR (mgr), NULL);
g_return_val_if_fail (uri != NULL, NULL);
ensure_files (mgr);
link = (MooHistoryItemList*) g_hash_table_lookup (mgr->priv->hash, uri);
return link ? link->data : NULL;
}
static void
moo_history_mgr_remove_uri_real (MooHistoryMgr *mgr,
const char *uri,
gboolean notify)
{
MooHistoryItemList *link;
MooHistoryItem *item;
g_return_if_fail (MOO_IS_HISTORY_MGR (mgr));
g_return_if_fail (uri != NULL);
ensure_files (mgr);
link = (MooHistoryItemList*) g_hash_table_lookup (mgr->priv->hash, uri);
if (!link)
return;
item = link->data;
g_hash_table_remove (mgr->priv->hash, uri);
moo_history_item_queue_delete_link (mgr->priv->files, link);
g_signal_emit (mgr, signals[CHANGED], 0);
if (notify)
{
schedule_save (mgr);
ipc_notify_remove_file (mgr, item);
}
if (mgr->priv->files->length == 0)
g_object_notify (G_OBJECT (mgr), "empty");
moo_history_item_free (item);
}
void
moo_history_mgr_remove_uri (MooHistoryMgr *mgr,
const char *uri)
{
moo_history_mgr_remove_uri_real (mgr, uri, TRUE);
}
static void
ipc_callback (GObject *obj,
const char *data,
gsize len)
{
MooHistoryMgr *mgr;
MooMarkupDoc *xml;
GError *error = NULL;
MooHistoryItem *item;
UpdateType type;
g_return_if_fail (MOO_IS_HISTORY_MGR (obj));
mgr = MOO_HISTORY_MGR (obj);
ensure_files (mgr);
xml = moo_markup_parse_memory (data, len, &error);
if (!xml)
{
g_critical ("got invalid data: %.*s", (int) len, data);
return;
}
#if 0
g_print ("%s: got data: %.*s\n", G_STRLOC, (int) len, data);
#endif
if (parse_update_item (xml, &item, &type))
{
switch (type)
{
case UPDATE_ITEM_UPDATE:
moo_history_mgr_update_file_real (mgr, item, FALSE);
break;
case UPDATE_ITEM_ADD:
moo_history_mgr_add_file_real (mgr, item, FALSE);
break;
case UPDATE_ITEM_REMOVE:
moo_history_mgr_remove_uri_real (mgr, moo_history_item_get_uri (item), FALSE);
break;
}
moo_history_item_free (item);
}
moo_markup_doc_unref (xml);
}
static void
ipc_notify (MooHistoryMgr *mgr,
MooHistoryItem *item,
UpdateType type)
{
if (mgr->priv->ipc_id)
{
char *string = format_for_update (item, type);
moo_ipc_send (G_OBJECT (mgr), mgr->priv->ipc_id, string, -1);
g_free (string);
}
}
static void
ipc_notify_add_file (MooHistoryMgr *mgr,
MooHistoryItem *item)
{
ipc_notify (mgr, item, UPDATE_ITEM_ADD);
}
static void
ipc_notify_update_file (MooHistoryMgr *mgr,
MooHistoryItem *item)
{
ipc_notify (mgr, item, UPDATE_ITEM_UPDATE);
}
static void
ipc_notify_remove_file (MooHistoryMgr *mgr,
MooHistoryItem *item)
{
ipc_notify (mgr, item, UPDATE_ITEM_REMOVE);
}
/*****************************************************************/
/* Menu
*/
static void
callback_data_free (CallbackData *data)
{
if (data)
{
if (data->notify)
data->notify (data->data);
g_slice_free (CallbackData, data);
}
}
static void
view_destroyed (GtkWidget *widget,
MooHistoryMgr *mgr)
{
g_object_set_data (G_OBJECT (widget), "moo-history-mgr-callback-data", NULL);
mgr->priv->widgets = widget_list_remove (mgr->priv->widgets, widget);
}
static void
update_menu (MooHistoryMgr *mgr,
GtkWidget *menu)
{
WidgetList *children;
children = widget_list_from_glist (gtk_container_get_children (GTK_CONTAINER (menu)));
while (children)
{
GtkWidget *item = children->data;
if (g_object_get_data (G_OBJECT (item), "moo-history-menu-item-file"))
gtk_widget_destroy (item);
children = widget_list_delete_link (children, children);
}
populate_menu (mgr, menu);
}
GtkWidget *
moo_history_mgr_create_menu (MooHistoryMgr *mgr,
MooHistoryCallback callback,
gpointer data,
GDestroyNotify notify)
{
GtkWidget *menu;
CallbackData *cb_data;
g_return_val_if_fail (MOO_IS_HISTORY_MGR (mgr), NULL);
g_return_val_if_fail (callback != NULL, NULL);
menu = gtk_menu_new ();
gtk_widget_show (menu);
g_signal_connect (menu, "destroy", G_CALLBACK (view_destroyed), mgr);
cb_data = g_slice_new0 (CallbackData);
cb_data->callback = callback;
cb_data->data = data;
cb_data->notify = notify;
g_object_set_data_full (G_OBJECT (menu), "moo-history-mgr-callback-data",
cb_data, (GDestroyNotify) callback_data_free);
populate_menu (mgr, menu);
mgr->priv->widgets = widget_list_prepend (mgr->priv->widgets, menu);
return menu;
}
static void
menu_item_activated (GtkWidget *menu_item)
{
GtkWidget *parent = menu_item->parent;
CallbackData *data;
MooHistoryItem *item;
GSList *list;
g_return_if_fail (parent != NULL);
data = (CallbackData*) g_object_get_data (G_OBJECT (parent), "moo-history-mgr-callback-data");
item = (MooHistoryItem*) g_object_get_data (G_OBJECT (menu_item), "moo-history-menu-item-file");
g_return_if_fail (data && item);
list = g_slist_prepend (NULL, moo_history_item_copy (item));
data->callback (list, data->data);
moo_history_item_free ((MooHistoryItem*) list->data);
g_slist_free (list);
}
static void
populate_menu (MooHistoryMgr *mgr,
GtkWidget *menu)
{
guint n_items, i;
MooHistoryItemList *l;
ensure_files (mgr);
n_items = MIN (mgr->priv->files->length, N_MENU_ITEMS);
for (i = 0, l = mgr->priv->files->head; i < n_items; i++, l = l->next)
{
GtkWidget *item, *image;
MooHistoryItem *hist_item = l->data;
char *display_name, *display_basename;
GdkPixbuf *pixbuf;
display_basename = uri_get_basename (hist_item->uri);
display_name = uri_get_display_name (hist_item->uri);
item = gtk_image_menu_item_new_with_label (display_basename);
_moo_widget_set_tooltip (item, display_name);
gtk_widget_show (item);
gtk_menu_shell_insert (GTK_MENU_SHELL (menu), item, i);
/* XXX */
pixbuf = moo_file_icon_get_pixbuf (moo_history_item_get_icon (hist_item),
GTK_WIDGET (item),
GTK_ICON_SIZE_MENU);
image = gtk_image_new_from_pixbuf (pixbuf);
gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
g_object_set_data_full (G_OBJECT (item), "moo-history-menu-item-file",
moo_history_item_copy (hist_item),
(GDestroyNotify) moo_history_item_free);
g_signal_connect (item, "activate", G_CALLBACK (menu_item_activated), NULL);
g_free (display_basename);
g_free (display_name);
}
}
enum {
COLUMN_PIXBUF,
COLUMN_NAME,
COLUMN_TOOLTIP,
COLUMN_URI,
N_COLUMNS
};
static void
open_selected (GtkTreeView *tree_view)
{
CallbackData *data;
GtkTreeIter iter;
GtkTreeModel *model;
GtkTreeSelection *selection;
MooHistoryMgr *mgr;
GList *selected;
GSList *items;
mgr = (MooHistoryMgr*) g_object_get_data (G_OBJECT (tree_view), "moo-history-mgr");
g_return_if_fail (MOO_IS_HISTORY_MGR (mgr));
data = (CallbackData*) g_object_get_data (G_OBJECT (tree_view), "moo-history-mgr-callback-data");
g_return_if_fail (data != NULL);
selection = gtk_tree_view_get_selection (tree_view);
selected = gtk_tree_selection_get_selected_rows (selection, &model);
for (items = NULL; selected != NULL; )
{
char *uri = NULL;
MooHistoryItem *item;
GtkTreePath *path = (GtkTreePath*) selected->data;
gtk_tree_model_get_iter (model, &iter, path);
gtk_tree_model_get (model, &iter, COLUMN_URI, &uri, -1);
item = moo_history_mgr_find_uri (mgr, uri);
if (item)
items = g_slist_prepend (items, moo_history_item_copy (item));
g_free (uri);
gtk_tree_path_free (path);
selected = g_list_delete_link (selected, selected);
}
items = g_slist_reverse (items);
if (items)
data->callback (items, data->data);
g_slist_foreach (items, (GFunc) moo_history_item_free, NULL);
g_slist_free (items);
}
static GtkWidget *
create_tree_view (void)
{
GtkWidget *tree_view;
GtkTreeViewColumn *column;
GtkCellRenderer *cell;
GtkListStore *store;
tree_view = gtk_tree_view_new ();
store = gtk_list_store_new (N_COLUMNS, GDK_TYPE_PIXBUF, G_TYPE_STRING,
G_TYPE_STRING, G_TYPE_STRING);
gtk_tree_view_set_model (GTK_TREE_VIEW (tree_view), GTK_TREE_MODEL (store));
gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (tree_view), FALSE);
gtk_tree_view_set_tooltip_column (GTK_TREE_VIEW (tree_view),
COLUMN_TOOLTIP);
gtk_tree_selection_set_mode (gtk_tree_view_get_selection (GTK_TREE_VIEW (tree_view)),
GTK_SELECTION_MULTIPLE);
column = gtk_tree_view_column_new ();
gtk_tree_view_append_column (GTK_TREE_VIEW (tree_view), column);
cell = gtk_cell_renderer_pixbuf_new ();
gtk_tree_view_column_pack_start (column, cell, FALSE);
gtk_tree_view_column_set_attributes (column, cell, "pixbuf", COLUMN_PIXBUF, NULL);
cell = gtk_cell_renderer_text_new ();
gtk_tree_view_column_pack_start (column, cell, TRUE);
gtk_tree_view_column_set_attributes (column, cell, "text", COLUMN_NAME, NULL);
return tree_view;
}
#define FIRST_CHUNK_SIZE 100
#define CHUNK_SIZE 100
#define LOADING_TIMEOUT 40
#define LOADING_PRIORITY G_PRIORITY_DEFAULT_IDLE
typedef struct {
MooHistoryItemList *items;
guint idle;
GtkWidget *tree_view;
GtkListStore *store;
} IdleLoader;
static void
cancel_idle_loading (GtkWidget *tree_view)
{
g_object_set_data (G_OBJECT (tree_view),
"moo-history-mgr-idle-loader",
NULL);
}
static void
idle_loader_free (IdleLoader *data)
{
if (data->idle)
g_source_remove (data->idle);
moo_history_item_list_foreach (data->items, (MooHistoryItemListFunc) moo_history_item_free, NULL);
moo_history_item_list_free_links (data->items);
g_free (data);
}
static void
add_entry (MooHistoryItem *item,
GtkListStore *store,
GtkWidget *tree_view)
{
char *display_name, *display_basename;
GdkPixbuf *pixbuf;
GtkTreeIter iter;
display_basename = uri_get_basename (item->uri);
display_name = uri_get_display_name (item->uri);
pixbuf = moo_file_icon_get_pixbuf (moo_history_item_get_icon (item),
tree_view, GTK_ICON_SIZE_MENU);
gtk_list_store_append (store, &iter);
gtk_list_store_set (store, &iter,
COLUMN_PIXBUF, pixbuf,
COLUMN_NAME, display_basename,
COLUMN_TOOLTIP, display_name,
COLUMN_URI, moo_history_item_get_uri (item),
-1);
g_free (display_basename);
g_free (display_name);
}
static gboolean
idle_loader (IdleLoader *data)
{
int count;
for (count = 0; data->items != NULL && count < CHUNK_SIZE; count++)
{
add_entry (data->items->data, data->store, data->tree_view);
moo_history_item_free (data->items->data);
data->items = moo_history_item_list_delete_link (data->items, data->items);
}
if (!data->items)
{
data->idle = 0;
g_object_set_data (G_OBJECT (data->tree_view),
"moo-history-mgr-idle-loader",
NULL);
return FALSE;
}
else
{
return TRUE;
}
}
static void
populate_tree_view (MooHistoryMgr *mgr,
GtkWidget *tree_view)
{
GtkListStore *store;
GtkTreeModel *model;
MooHistoryItemList *l;
int count;
ensure_files (mgr);
cancel_idle_loading (tree_view);
model = gtk_tree_view_get_model (GTK_TREE_VIEW (tree_view));
store = GTK_LIST_STORE (model);
for (l = mgr->priv->files->head, count = 0;
l != NULL && count < FIRST_CHUNK_SIZE;
l = l->next, count++)
{
add_entry (l->data, store, tree_view);
}
if (l != NULL)
{
IdleLoader *data = g_new0 (IdleLoader, 1);
while (l != NULL)
{
data->items = moo_history_item_list_prepend (data->items, moo_history_item_copy (l->data));
l = l->next;
}
data->items = moo_history_item_list_reverse (data->items);
data->idle = g_timeout_add_full (LOADING_PRIORITY,
LOADING_TIMEOUT,
(GSourceFunc) idle_loader,
data, NULL);
data->tree_view = tree_view;
data->store = store;
g_object_set_data_full (G_OBJECT (tree_view),
"moo-history-mgr-idle-loader", data,
(GDestroyNotify) idle_loader_free);
}
}
static void
update_tree_view (MooHistoryMgr *mgr,
GtkWidget *tree_view)
{
GtkTreeModel *model = gtk_tree_view_get_model (GTK_TREE_VIEW (tree_view));
gtk_list_store_clear (GTK_LIST_STORE (model));
populate_tree_view (mgr, tree_view);
}
static GtkWidget *
moo_history_mgr_create_tree_view (MooHistoryMgr *mgr,
MooHistoryCallback callback,
gpointer data,
GDestroyNotify notify)
{
GtkWidget *tree_view;
CallbackData *cb_data;
g_return_val_if_fail (MOO_IS_HISTORY_MGR (mgr), NULL);
g_return_val_if_fail (callback != NULL, NULL);
tree_view = create_tree_view ();
gtk_widget_show (tree_view);
g_signal_connect (tree_view, "destroy", G_CALLBACK (view_destroyed), mgr);
cb_data = g_slice_new0 (CallbackData);
cb_data->callback = callback;
cb_data->data = data;
cb_data->notify = notify;
g_object_set_data_full (G_OBJECT (tree_view), "moo-history-mgr-callback-data",
cb_data, (GDestroyNotify) callback_data_free);
g_object_set_data (G_OBJECT (tree_view), "moo-history-mgr", mgr);
populate_tree_view (mgr, tree_view);
if (mgr->priv->files->head)
_moo_tree_view_select_first (GTK_TREE_VIEW (tree_view));
mgr->priv->widgets = widget_list_prepend (mgr->priv->widgets, tree_view);
return tree_view;
}
static void
dialog_response (GtkTreeView *tree_view,
int response)
{
if (response == GTK_RESPONSE_OK)
open_selected (tree_view);
}
static void
row_activated (GtkDialog *dialog)
{
gtk_dialog_response (dialog, GTK_RESPONSE_OK);
}
GtkWidget *
moo_history_mgr_create_dialog (MooHistoryMgr *mgr,
MooHistoryCallback callback,
gpointer data,
GDestroyNotify notify)
{
GtkWidget *dialog, *swin, *tree_view;
g_return_val_if_fail (MOO_IS_HISTORY_MGR (mgr), NULL);
g_return_val_if_fail (callback != NULL, NULL);
dialog = gtk_dialog_new_with_buttons ("", NULL, GTK_DIALOG_DESTROY_WITH_PARENT,
GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
GTK_STOCK_OPEN, GTK_RESPONSE_OK,
NULL);
gtk_dialog_set_alternative_button_order (GTK_DIALOG (dialog),
GTK_RESPONSE_OK,
GTK_RESPONSE_CANCEL,
-1);
swin = gtk_scrolled_window_new (NULL, NULL);
gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (swin),
GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
tree_view = moo_history_mgr_create_tree_view (mgr, callback, data, notify);
gtk_container_add (GTK_CONTAINER (swin), tree_view);
gtk_widget_show_all (swin);
gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dialog)->vbox), swin, TRUE, TRUE, 0);
g_signal_connect_swapped (tree_view, "row-activated",
G_CALLBACK (row_activated), dialog);
g_signal_connect_swapped (dialog, "response",
G_CALLBACK (dialog_response), tree_view);
return dialog;
}
static gboolean
do_update_widgets (MooHistoryMgr *mgr)
{
WidgetList *l;
mgr->priv->update_widgets_idle = 0;
for (l = mgr->priv->widgets; l != NULL; l = l->next)
{
GtkWidget *widget = l->data;
if (GTK_IS_MENU (widget))
update_menu (mgr, widget);
else if (GTK_IS_TREE_VIEW (widget))
update_tree_view (mgr, widget);
else
g_critical ("%s: oops", G_STRFUNC);
}
return FALSE;
}
static void
schedule_update_widgets (MooHistoryMgr *mgr)
{
if (!mgr->priv->update_widgets_idle && mgr->priv->widgets)
mgr->priv->update_widgets_idle =
g_idle_add ((GSourceFunc) do_update_widgets, mgr);
}
void
moo_history_item_free (MooHistoryItem *item)
{
if (item)
{
g_free (item->uri);
g_datalist_clear (&item->data);
moo_file_icon_free (item->icon);
g_slice_free (MooHistoryItem, item);
}
}
static MooHistoryItem *
moo_history_item_new_uri (const char *uri)
{
MooHistoryItem *item = g_slice_new (MooHistoryItem);
item->uri = g_strdup (uri);
item->data = NULL;
item->icon = NULL;
return item;
}
static MooHistoryItem *
moo_history_item_newv (const char *uri,
const char *first_key,
va_list args)
{
const char *key;
MooHistoryItem *item;
item = moo_history_item_new_uri (uri);
for (key = first_key; key != NULL; )
{
const char *value = va_arg (args, const char *);
moo_history_item_set (item, key, value);
key = va_arg (args, const char *);
}
return item;
}
MooHistoryItem *
moo_history_item_new (const char *uri,
const char *first_key,
...)
{
va_list args;
MooHistoryItem *item;
g_return_val_if_fail (uri != NULL, NULL);
va_start (args, first_key);
item = moo_history_item_newv (uri, first_key, args);
va_end (args);
return item;
}
static void
copy_data (GQuark key,
const char *value,
MooHistoryItem *dest)
{
g_datalist_id_set_data_full (&dest->data, key, g_strdup (value), g_free);
}
MooHistoryItem *
moo_history_item_copy (MooHistoryItem *item)
{
MooHistoryItem *copy;
if (!item)
return NULL;
copy = moo_history_item_new_uri (item->uri);
g_datalist_foreach (&item->data, (GDataForeachFunc) copy_data, copy);
copy->icon = moo_file_icon_copy (item->icon);
return copy;
}
typedef struct {
MooHistoryItem *item;
gboolean equal;
} CmpData;
static void
cmp_data (GQuark key,
const char *value,
CmpData *data)
{
const char *value2;
if (!data->equal)
return;
value2 = (const char*) g_datalist_id_get_data (&data->item->data, key);
if (!value2 || strcmp (value2, value) != 0)
data->equal = FALSE;
}
static gboolean
moo_history_item_equal (MooHistoryItem *item1,
MooHistoryItem *item2)
{
CmpData data;
g_return_val_if_fail (item1 && item2, FALSE);
if (item1 == item2)
return TRUE;
if (strcmp (item1->uri, item2->uri) != 0)
return FALSE;
data.equal = TRUE;
data.item = item1;
g_datalist_foreach (&item2->data, (GDataForeachFunc) cmp_data, &data);
if (data.equal)
{
data.item = item2;
g_datalist_foreach (&item1->data, (GDataForeachFunc) cmp_data, &data);
}
return data.equal;
}
void
moo_history_item_set (MooHistoryItem *item,
const char *key,
const char *value)
{
g_return_if_fail (item != NULL);
g_return_if_fail (key != NULL);
if (value)
g_datalist_set_data_full (&item->data, key, g_strdup (value), g_free);
else
g_datalist_remove_data (&item->data, key);
}
const char *
moo_history_item_get (MooHistoryItem *item,
const char *key)
{
g_return_val_if_fail (item != NULL, NULL);
g_return_val_if_fail (key != NULL, NULL);
return (const char*) g_datalist_get_data (&item->data, key);
}
const char *
moo_history_item_get_uri (MooHistoryItem *item)
{
g_return_val_if_fail (item != NULL, NULL);
return item->uri;
}
static MooFileIcon *
moo_history_item_get_icon (MooHistoryItem *item)
{
g_return_val_if_fail (item != NULL, NULL);
if (!item->icon)
{
char *display_name;
item->icon = moo_file_icon_new ();
display_name = uri_get_display_name (item->uri);
/* XXX */
moo_file_icon_for_file (item->icon, display_name);
g_free (display_name);
}
return item->icon;
}
static void
format_data (GQuark key_id,
const char *value,
GString *dest)
{
const char *key = g_quark_to_string (key_id);
char *key_escaped = g_markup_escape_text (key, -1);
char *value_escaped = g_markup_escape_text (value, -1);
g_string_append_printf (dest, " <data key=\"%s\" value=\"%s\"/>\n",
key_escaped, value_escaped);
g_free (value_escaped);
g_free (key_escaped);
}
static void
moo_history_item_format (MooHistoryItem *item,
GString *dest)
{
char *uri_escaped;
g_return_if_fail (item != NULL);
g_return_if_fail (dest != NULL);
uri_escaped = g_markup_escape_text (item->uri, -1);
if (item->data)
{
g_string_append_printf (dest, " <item uri=\"%s\">\n", uri_escaped);
g_datalist_foreach (&item->data, (GDataForeachFunc) format_data, dest);
g_string_append (dest, " </item>\n");
}
else
{
g_string_append_printf (dest, " <item uri=\"%s\"/>\n", uri_escaped);
}
g_free (uri_escaped);
}
void
moo_history_item_foreach (MooHistoryItem *item,
GDataForeachFunc func,
gpointer user_data)
{
g_return_if_fail (item != NULL);
g_return_if_fail (func != NULL);
g_datalist_foreach (&item->data, func, user_data);
}
static char *
uri_get_basename (const char *uri)
{
const char *last_slash;
g_return_val_if_fail (uri != NULL, NULL);
if (g_str_has_prefix (uri, "file://"))
{
char *filename = g_filename_from_uri (uri, NULL, NULL);
if (filename)
{
char *display_name = g_filename_display_basename (filename);
if (display_name)
{
g_free (filename);
return display_name;
}
g_free (filename);
}
}
/* XXX percent encoding */
last_slash = strrchr (uri, '/');
if (last_slash)
return g_strdup (last_slash + 1);
else
return g_strdup (uri);
}
static char *
uri_get_display_name (const char *uri)
{
g_return_val_if_fail (uri != NULL, NULL);
if (g_str_has_prefix (uri, "file://"))
{
char *filename = g_filename_from_uri (uri, NULL, NULL);
if (filename)
{
char *display_name = g_filename_display_name (filename);
if (display_name)
{
g_free (filename);
return display_name;
}
g_free (filename);
}
}
return g_strdup (uri);
}