/* -*- Mode: C; indent-tabs-mode: nil; tab-width: 2 -*-
 *
 * Copyright (C) 2009 Canonical Ltd.
 * Authors:
 *  Gustavo Sverzut Barbieri <gustavo.barbieri@canonical.com>
 *
 * This file is part of Netbook Launcher EFL.
 *
 * Netbook Launcher EFL 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, version 3 of the License.
 *
 * Netbook Launcher EFL 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 Netbook Launcher EFL.  If not, see <http://www.gnu.org/licenses/>.
 */

#include "netbook-launcher.h"

#include <Elementary.h>
#include <glib/gi18n.h>
#include <gconf/gconf.h>
#include <gconf/gconf-client.h>
#include <gio/gio.h>
#include <gtk/gtk.h>

static const char _EDJE_GROUP_ITEM[] = "e/netbook/launcher/main/places/storage/item";
static const char _EDJE_PART_CONTENTS[] = "e.box.contents";
static const char _EDJE_PART_ICON[] = "e.swallow.icon";
static const char _EDJE_PART_LABEL[] = "e.text.label";
static const char _EDJE_PART_ACTION[] = "e.text.action";
static const char _EDJE_PART_EJECT[] = "e.text.eject";

static const char _EDJE_SIG_SRC[] = "e";
static const char _EDJE_SIG_SELECT[] = "gui,action,select";
static const char _EDJE_SIG_UNSELECT[] = "gui,action,unselect";
static const char _EDJE_SIG_FOCUS_OUT[] = "gui,action,focus,out";
static const char _EDJE_SIG_FOCUS_IN[] = "gui,action,focus,in";
static const char _EDJE_SIG_EXECUTED[] = "gui,action,executed";
static const char _EDJE_SIG_EJECTED[] = "gui,action,ejected";
static const char _EDJE_SIG_CAN_EJECT[] = "gui,state,can_eject";
static const char _EDJE_SIG_CANNOT_EJECT[] = "gui,state,cannot_eject";
static const char _EDJE_SIG_MOUNTED[] = "gui,state,mounted";
static const char _EDJE_SIG_UNMOUNTED[] = "gui,state,unmounted";

static const char _IGNORE_DIR[] = "/apps/netbook-launcher";
static const char _IGNORE_PATH[] = "/apps/netbook-launcher/volume_exclude_list";

static const char *_flags[] = {
  "", /* none */
  "e", /* PLACES_STORAGE_FLAG_CAN_EJECT */
  "m", /* PLACES_STORAGE_FLAG_CAN_MOUNT */
  "e+m", /* PLACES_STORAGE_FLAG_CAN_EJECT + PLACES_STORAGE_FLAG_CAN_MOUNT */
  "M", /* PLACES_STORAGE_FLAG_IS_MOUNTED */
  "e+M", /* PLACES_STORAGE_FLAG_CAN_EJECT + PLACES_STORAGE_FLAG_IS_MOUNTED */
  "m+M", /* PLACES_STORAGE_FLAG_CAN_MOUNT + PLACES_STORAGE_FLAG_IS_MOUNTED */
  "e+m+M", /* all */
};
enum
{
  PLACES_STORAGE_FLAG_CAN_EJECT     = 1,
  PLACES_STORAGE_FLAG_CAN_MOUNT     = 2,
  PLACES_STORAGE_FLAG_IS_MOUNTED    = 4,
  PLACES_STORAGE_FLAG_ALL           = 7
};

struct places_storage_context
{
  Evas_Object *section;
  Evas_Object *parent;
  struct item_size item;
  const char *pending_launch;
  GVolumeMonitor *monitor;
  GConfClient *gconf;
  GSList *ignored;
  GSList *pending_action;
  struct
  {
    gulong volume_add;
    gulong volume_del;
    gulong volume_change;
    gulong mount_add;
    gint ignore_changed;
  } notify;
  guint timer_volume_reload;
};

struct places_storage_glib_async_action
{
  struct places_storage_context *ctxt;
  GCancellable *cancellable;
  char uid[];
};

struct places_storage_item_data
{
  struct places_item_common base;
  struct places_storage_context *ctxt;
  const char *name;
  const char *icon_name;
  unsigned int flags;
};

static void _places_storage_glib_reload(struct places_storage_context *ctxt);

static void
_places_storage_calc_sizes(struct places_storage_context *ctxt)
{
  ctxt->item.icon_size = 32;
  ctxt->item.w = 50;
  ctxt->item.h = 50;
  _nl_edje_item_size_calc(&ctxt->item, ctxt->section, _EDJE_GROUP_ITEM);
}

static void
_places_storage_item_in(void *data, Evas_Object *obj, const char *signal __UNUSED__, const char *source __UNUSED__)
{
  struct places_storage_item_data *id = data;
  const char *str = edje_object_data_get(obj, "selectraise");
  if (str && (strcmp(str, "on") == 0))
    evas_object_raise(id->base.obj);
  places_item_common_select(id->ctxt->parent, &id->base);
}

static void
_places_storage_item_out(void *data, Evas_Object *obj __UNUSED__, const char *signal __UNUSED__, const char *source __UNUSED__)
{
  struct places_storage_item_data *id = data;
  places_item_common_unselect(id->ctxt->parent, &id->base);
}

struct places_storage_hide_data
{
  struct places_storage_context *ctxt;
  const char *uid;
};

static void
_places_storage_ecore_item_launch_hide(void *data)
{
  struct places_storage_hide_data *hide_data = data;
  struct places_storage_context *ctxt = hide_data->ctxt;

  if (ctxt->pending_launch == hide_data->uid)
    ctxt->pending_launch = NULL;

  eina_stringshare_del(hide_data->uid);
  free(hide_data);
}

/* given uid string is stringshared */
static void
_places_storage_ecore_item_launch(struct places_storage_context *ctxt, const char *uid, const char *icon, const char *name)
{
  struct places_storage_hide_data *hide_data;

  if (ctxt->pending_launch == uid)
    return;

  hide_data = malloc(sizeof(*hide_data));
  if (!hide_data)
  {
    ERR("could not allocate hide_data.\n");
    return;
  }
  hide_data->ctxt = ctxt;
  hide_data->uid = eina_stringshare_ref(uid);
  ctxt->pending_launch = uid;

  launchfeedback_show_full
    (icon, name, 10.0, _places_storage_ecore_item_launch_hide, hide_data);
}

static void
_places_storage_ecore_item_done(struct places_storage_context *ctxt, const char *uid)
{
  if (ctxt->pending_launch && strcmp(ctxt->pending_launch, uid) == 0)
    launchfeedback_hide();
}

static void
_places_storage_ecore_item_success(void *data, const char *uid)
{
  struct places_storage_context *ctxt = data;
  _places_storage_ecore_item_done(ctxt, uid);
}

static Eina_Bool
_places_storage_glib_item_success(struct places_storage_context *ctxt, const char *uid)
{
  return gstuff_ecore_run_ptr_string
    (_places_storage_ecore_item_success, ctxt, uid);
}

static void
_places_storage_ecore_item_error(void *data, unsigned int len, const char **strings)
{
  struct places_storage_context *ctxt = data;
  const char *uid, *message;
  if (len != 2)
  {
    ERR("2 strings expected, got %u\n", len);
    return;
  }
  uid = strings[0];
  message = strings[1];
  _places_storage_ecore_item_done(ctxt, uid);
  error_show(message);
}

static Eina_Bool
_places_storage_glib_item_error(struct places_storage_context *ctxt, const char *uid, const char *message)
{
  const char *strings[] = {uid, message, NULL};
  return gstuff_ecore_run_ptr_string_array
    (_places_storage_ecore_item_error, ctxt, 2, strings);
}

static Eina_Bool
_places_storage_glib_item_error_printf(struct places_storage_context *ctxt, const char *uid, const char *fmt, ...)
{
  Eina_Bool ret;
  char *msg = NULL;
  va_list ap;

  va_start(ap, fmt);
  if (vasprintf(&msg, fmt, ap) > 0)
  {
    ret = _places_storage_glib_item_error(ctxt, uid, msg);
    free(msg);
  } else
    ret = 0;

  va_end(ap);

  return ret;
}

static void
_places_storage_glib_async_action_free(struct places_storage_glib_async_action *gaa)
{
  g_debug("free GIO action on uid '%s'", gaa->uid);
  if (gaa->ctxt)
    gaa->ctxt->pending_action = g_slist_remove(gaa->ctxt->pending_action, gaa);
  g_object_unref(gaa->cancellable);
  free(gaa);
}

static void
_places_storage_glib_async_action_cancel(struct places_storage_glib_async_action *gaa)
{
  g_debug("canceling pending GIO action on uid '%s'", gaa->uid);
  if (gaa->ctxt)
  {
    gaa->ctxt->pending_action = g_slist_remove(gaa->ctxt->pending_action, gaa);
    gaa->ctxt = NULL;
  }
  g_cancellable_cancel(gaa->cancellable);
}

static struct places_storage_glib_async_action *
_places_storage_glib_async_action_new(struct places_storage_context *ctxt, const char *uid)
{
  struct places_storage_glib_async_action *gaa;
  size_t len = strlen(uid);

  gaa = malloc(sizeof(*gaa) + len + 1);
  if (!gaa)
  {
    g_critical("Could not allocate memory for gaa");
    return NULL;
  }

  gaa->cancellable = g_cancellable_new();
  if (!gaa->cancellable)
  {
    g_critical("Could not create gcancellable");
    free(gaa);
    return NULL;
  }

  gaa->ctxt = ctxt;
  memcpy(gaa->uid, uid, len + 1);
  ctxt->pending_action = g_slist_prepend(ctxt->pending_action, gaa);

  return gaa;
}

static GVolume *
_places_storage_glib_volume_find(struct places_storage_context *ctxt, const char *uid)
{
  GList *lst;
  GVolume *vol = NULL;

  lst = g_volume_monitor_get_volumes(ctxt->monitor);
  while (lst)
  {
    GVolume *cur = lst->data;

    if (!vol)
    {
      gchar *cur_uid = g_volume_get_identifier
        (cur, G_VOLUME_IDENTIFIER_KIND_UUID);

      if (cur_uid && strcmp(uid, cur_uid) == 0)
      {
        vol = cur;
        g_object_ref(vol);
      }

      g_free(cur_uid);
    }

    g_object_unref(cur);
    lst = g_list_delete_link(lst, lst);
  }

  return vol;
}

static void
_places_storage_glib_item_open(struct places_storage_context *ctxt, const char *uid, GMount *mount)
{
  GFile *root;
  gchar *path;
  GError *err = NULL;

  root = g_mount_get_root(mount);
  if (!root)
  {
    gchar *name = g_mount_get_name(mount);
    g_critical("Could not get root for mount '%s'", name);
    _places_storage_glib_item_error_printf
      (ctxt, uid, _("Could not get root for mount '%s'"), name);
    g_free(name);
    return;
  }

  path = g_file_get_uri(root);
  if (!path)
  {
    gchar *name = g_mount_get_name(mount);
    g_critical("Could not get root path for mount '%s'", name);
    _places_storage_glib_item_error_printf
      (ctxt, uid, _("Could not get root path for mount '%s'"), name);
    g_free(name);
    g_object_unref(root);
    return;
  }

  g_object_unref(root);

  g_app_info_launch_default_for_uri(path, NULL, &err);
  if (err)
  {
    g_critical("Could not open path '%s': %s", path, err->message);
    _places_storage_glib_item_error_printf
      (ctxt, uid, _("Could not open path '%s': %s"), path, err->message);
    g_error_free(err);
  }
  else
    _places_storage_glib_item_success(ctxt, uid);

  g_free(path);
}

static void
_places_storage_glib_item_mount_end(GObject *o, GAsyncResult *res, gpointer data)
{
  GVolume *vol = G_VOLUME(o);
  struct places_storage_glib_async_action *gaa = data;
  GMount *mount;
  GError *err = NULL;

  g_debug("_places_storage_glib_item_mount_end: uid=%s, vol=%p", gaa->uid, vol);
  g_volume_mount_finish(vol, res, &err);

  _places_storage_glib_reload(gaa->ctxt);

  if (err)
  {
    gchar *name = g_volume_get_name(vol);
    g_critical("Error mounting '%s': %s", name, err->message);
    _places_storage_glib_item_error_printf
      (gaa->ctxt, gaa->uid, _("Error mounting '%s': %s"), name, err->message);
    g_free(name);
    g_error_free(err);
    _places_storage_glib_async_action_free(gaa);
    return;
  }

  mount = g_volume_get_mount(vol);
  g_debug("_places_storage_glib_item_mount_end: uid=%s, vol=%p, open mount: %p",
          gaa->uid, vol, mount);
  _places_storage_glib_item_open(gaa->ctxt, gaa->uid, mount);
  g_object_unref(mount);
  _places_storage_glib_async_action_free(gaa);
}

static void
_places_storage_glib_item_mount(struct places_storage_context *ctxt, const char *uid, GVolume *vol)
{
  struct places_storage_glib_async_action *gaa;

  gaa = _places_storage_glib_async_action_new(ctxt, uid);
  if (!gaa)
  {
    gchar *name = g_volume_get_name(vol);
    _places_storage_glib_item_error_printf
      (ctxt, uid, _("Could not mount '%s': not enough memory."), name);
    g_free(name);
    return;
  }

  // TODO: create and handle life of GtkMountOperation to query passwords.
  g_debug("_places_storage_glib_item_mount: uid=%s, vol=%p", uid, vol);
  g_volume_mount
    (vol, G_MOUNT_MOUNT_NONE, NULL, gaa->cancellable,
     _places_storage_glib_item_mount_end, gaa);
}

static void
_places_storage_glib_item_action(void *data, const char *uid)
{
  struct places_storage_context *ctxt = data;
  GVolume *vol = _places_storage_glib_volume_find(ctxt, uid);
  GMount *mount;

  g_debug("_places_storage_glib_item_action: uid=%s, vol=%p", uid, vol);
  if (!vol)
  {
    g_critical("Could not find volume '%s'", uid);
    _places_storage_glib_item_error_printf
      (ctxt, uid, _("Could not find volume '%s'"), uid);
    return;
  }

  mount = g_volume_get_mount(vol);
  g_debug("_places_storage_glib_item_action: uid=%s, vol=%p, mount=%p",
          uid, vol, mount);
  if (!mount)
  {
    if (!g_volume_can_mount(vol))
    {
      gchar *name = g_volume_get_name(vol);
      g_critical("Could not mount volume '%s'", name);
      _places_storage_glib_item_error_printf
        (ctxt, uid, _("Could not mount volume '%s'"), name);
      g_free(name);
      g_object_unref(vol);
      return;
    }

    g_debug("_places_storage_glib_item_action: uid=%s, vol=%p, ask mount",
            uid, vol);
    _places_storage_glib_item_mount(ctxt, uid, vol);
    g_object_unref(vol);
    return;
  }

  g_debug("_places_storage_glib_item_action: uid=%s, vol=%p, open mount=%p",
          uid, vol, mount);
  _places_storage_glib_item_open(ctxt, uid, mount);
  g_object_unref(mount);
  g_object_unref(vol);
}

static void
_places_storage_item_action(void *data, Evas_Object *obj __UNUSED__, const char *signal __UNUSED__, const char *source __UNUSED__)
{
  struct places_storage_item_data *id = data;
  const char *uid = id->base.id;

  DBG("storage activate: %s (%s)\n", id->name, uid);
  _places_storage_ecore_item_launch(id->ctxt, uid, id->icon_name, id->name);
  gstuff_glib_run_ptr_string
    (_places_storage_glib_item_action, id->ctxt, uid);
  sound_play(id->base.obj, "button-pressed");
  sound_play(id->base.obj, "button-released");
}

static void
_places_storage_glib_item_eject_end(GObject *o, GAsyncResult *res, gpointer data)
{
  GVolume *vol = G_VOLUME(o);
  struct places_storage_glib_async_action *gaa = data;
  GError *err = NULL;

  g_debug("_places_storage_glib_item_eject_end: uid=%s, vol=%p",
          gaa->uid, vol);
  g_volume_eject_finish(vol, res, &err);

  _places_storage_glib_reload(gaa->ctxt);

  if (err)
  {
    gchar *name = g_volume_get_name(vol);
    g_critical("Error ejecting volume '%s': %s", name, err->message);
    _places_storage_glib_item_error_printf
      (gaa->ctxt, gaa->uid,
       _("Error ejecting volume '%s': %s"), name, err->message);
    g_free(name);
    g_error_free(err);
    _places_storage_glib_async_action_free(gaa);
    return;
  }

  _places_storage_glib_item_success(gaa->ctxt, gaa->uid);
  g_debug("_places_storage_glib_item_eject_end: uid=%s, vol=%p, success!",
          gaa->uid, vol);
  _places_storage_glib_async_action_free(gaa);
}

static void
_places_storage_glib_item_unmount_end(GObject *o, GAsyncResult *res, gpointer data)
{
  GMount *mount = G_MOUNT(o);
  struct places_storage_glib_async_action *gaa = data;
  GError *err = NULL;

  g_debug("_places_storage_glib_item_unmount_end: uid=%s, mount=%p",
          gaa->uid, mount);
  g_mount_unmount_finish(mount, res, &err);

  _places_storage_glib_reload(gaa->ctxt);

  if (err)
  {
    gchar *name = g_mount_get_name(mount);
    g_critical("Error unmounting '%s': %s", name, err->message);
    _places_storage_glib_item_error_printf
      (gaa->ctxt, gaa->uid,
       _("Error unmounting '%s': %s"), name, err->message);
    g_free(name);
    g_error_free(err);
    _places_storage_glib_async_action_free(gaa);
    return;
  }

  _places_storage_glib_item_success(gaa->ctxt, gaa->uid);
  g_debug("_places_storage_glib_item_unmount_end: uid=%s, mount=%p, success!",
          gaa->uid, mount);
  _places_storage_glib_async_action_free(gaa);
}

static void
_places_storage_glib_item_eject(void *data, const char *uid)
{
  struct places_storage_context *ctxt = data;
  GVolume *vol = _places_storage_glib_volume_find(ctxt, uid);
  GMount *mount;
  struct places_storage_glib_async_action *gaa;

  g_debug("_places_storage_glib_item_eject: uid=%s, vol=%p", uid, vol);
  if (!vol)
  {
    g_critical("Could not find volume '%s'", uid);
    _places_storage_glib_item_error_printf
      (ctxt, uid, _("Could not find volume '%s'"), uid);
    return;
  }

  if (g_volume_can_eject(vol))
  {
    g_debug("_places_storage_glib_item_eject: uid=%s, vol=%p, eject", uid, vol);
    gaa = _places_storage_glib_async_action_new(ctxt, uid);
    if (!gaa)
    {
      gchar *name = g_volume_get_name(vol);
      _places_storage_glib_item_error_printf
        (ctxt, uid, _("Could not eject '%s': not enough memory."), name);
      g_free(name);
      g_object_unref(vol);
      return;
    }
    g_volume_eject
      (vol, G_MOUNT_UNMOUNT_NONE, gaa->cancellable,
       _places_storage_glib_item_eject_end, gaa);
    g_object_unref(vol);
    return;
  }

  mount = g_volume_get_mount(vol);
  g_object_unref(vol);
  if (!mount)
  {
    gchar *name = g_volume_get_name(vol);
    g_critical("Trying to mount unmounted volume '%s'", uid);
    _places_storage_glib_item_error_printf
      (ctxt, uid, _("Trying to mount unmounted volume '%s'"), name);
    g_free(name);
    return;
  }

  gaa = _places_storage_glib_async_action_new(ctxt, uid);
  if (!gaa)
  {
    gchar *name = g_volume_get_name(vol);
    _places_storage_glib_item_error_printf
      (ctxt, uid, _("Could not eject '%s': not enough memory."), name);
    g_free(name);
    g_object_unref(mount);
    return;
  }
  g_debug("_places_storage_glib_item_eject: uid=%s, vol=%p, unmount %p",
          uid, vol, mount);
  g_mount_unmount
    (mount, G_MOUNT_UNMOUNT_NONE, gaa->cancellable,
     _places_storage_glib_item_unmount_end, gaa);
  g_object_unref(mount);
}

static void
_places_storage_item_eject(void *data, Evas_Object *obj __UNUSED__, const char *signal __UNUSED__, const char *source __UNUSED__)
{
  struct places_storage_item_data *id = data;
  const char *uid = id->base.id;

  DBG("storage eject: %s (%s)\n", id->name, uid);
  _places_storage_ecore_item_launch(id->ctxt, uid, id->icon_name, id->name);
  gstuff_glib_run_ptr_string
    (_places_storage_glib_item_eject, id->ctxt, uid);
  sound_play(id->base.obj, "button-pressed");
  sound_play(id->base.obj, "button-released");
}

static void
_places_storage_item_del(void *data, Evas *e __UNUSED__, Evas_Object *obj __UNUSED__, void *event_info __UNUSED__)
{
  struct places_storage_item_data *id = data;
  DBG("storage del: '%s' '%s' '%s'\n", id->name, id->icon_name, id->base.id);
  places_navigation_item_del(id->ctxt->parent, &id->base);
  eina_stringshare_del(id->name);
  eina_stringshare_del(id->icon_name);
  eina_stringshare_del(id->base.id);
  free(id);
}

static unsigned int
_places_storage_flags_parse(const char *flags)
{
  unsigned int val, max = sizeof(_flags)/sizeof(_flags[0]);
  for (val = 0; val < max; val++)
    if (strcmp(flags, _flags[val]) == 0)
      return val;

  ERR("could not parse flags: '%s', return PLACES_STORAGE_FLAG_ALL\n", flags);
  return PLACES_STORAGE_FLAG_ALL;
}

static void
_places_storage_item_common_select(struct places_item_common *common)
{
  struct places_storage_item_data *id;

  edje_object_signal_emit(common->obj, _EDJE_SIG_SELECT, _EDJE_SIG_SRC);

  id = (struct places_storage_item_data *)common;
  _nl_icon_info_event_enter(id->name, id->icon_name, _("storage device"));
}

static void
_places_storage_item_common_unselect(struct places_item_common *common)
{
  edje_object_signal_emit(common->obj, _EDJE_SIG_UNSELECT, _EDJE_SIG_SRC);
  _nl_icon_info_event_leave();
}

static void
_places_storage_item_common_key_down(struct places_item_common *common, Evas_Event_Key_Down *ev)
{
  if (strcmp(ev->keyname, "Return") == 0)
    _places_storage_item_action(common, NULL, NULL, NULL);
  else if (strcmp(ev->keyname, "XF86Eject") == 0)
    _places_storage_item_eject(common, NULL, NULL, NULL);
}

static void
_places_storage_ecore_reload(void *data, unsigned int len, const char **strings)
{
  struct places_storage_context *ctxt = data;
  Evas_Object *ed;
  unsigned int i;

  ed = elm_layout_edje_get(ctxt->section);
  if (!edje_object_part_box_remove_all(ed, _EDJE_PART_CONTENTS, 1))
  {
    ERR("could not clear storage section\n");
    return;
  }
  evas_object_size_hint_min_set(ctxt->section, 0, 0);

  for (i = 0; i < len; i += 4)
  {
    Evas_Object *item, *icon, *item_ed;
    const char *name, *icon_name, *flags, *uid;
    struct places_storage_item_data *id;

    name = strings[i];
    icon_name = strings[i + 1];
    flags = strings[i + 2];
    uid = strings[i + 3];

    DBG("storage add: '%s' '%s' '%s' '%s'\n", name, uid, icon_name, flags);

    id = malloc(sizeof(*id));
    if (!id)
    {
      ERR("could not allocate places_storage_item_data\n");
      continue;
    }
    id->ctxt = ctxt;
    id->name = eina_stringshare_add(name);
    id->icon_name = eina_stringshare_add(icon_name);
    id->flags = _places_storage_flags_parse(flags);
    id->base.id = eina_stringshare_add(uid);
    id->base.select = _places_storage_item_common_select;
    id->base.unselect = _places_storage_item_common_unselect;
    id->base.key_down = _places_storage_item_common_key_down;

    icon = _nl_icon_load_sized(ctxt->section, icon_name, ctxt->item.icon_size);

    item = elm_layout_add(ctxt->section);
    if (!item)
      goto error;
    if (!elm_layout_file_set(item, _nl_prefs.theme, _EDJE_GROUP_ITEM))
      goto error;

    if (icon)
      elm_layout_content_set(item, _EDJE_PART_ICON, icon);

    item_ed = elm_layout_edje_get(item);
    edje_object_part_text_set(item_ed, _EDJE_PART_LABEL, name);

    evas_object_event_callback_add
      (item, EVAS_CALLBACK_FREE, _places_storage_item_del, id);

    evas_object_resize(item, ctxt->item.w, ctxt->item.h);
    evas_object_show(item);

    edje_object_signal_callback_add
      (item_ed, _EDJE_SIG_FOCUS_IN, _EDJE_SIG_SRC,
       _places_storage_item_in, id);
    edje_object_signal_callback_add
      (item_ed, _EDJE_SIG_FOCUS_OUT, _EDJE_SIG_SRC,
       _places_storage_item_out, id);
    edje_object_signal_callback_add
      (item_ed, _EDJE_SIG_EXECUTED, _EDJE_SIG_SRC,
       _places_storage_item_action, id);
    edje_object_signal_callback_add
      (item_ed, _EDJE_SIG_EJECTED, _EDJE_SIG_SRC,
       _places_storage_item_eject, id);

    id->base.obj = item_ed;

    edje_object_part_text_set(item_ed, _EDJE_PART_ACTION, _("open"));

    if (id->flags & PLACES_STORAGE_FLAG_IS_MOUNTED)
      edje_object_signal_emit(item_ed, _EDJE_SIG_MOUNTED, _EDJE_SIG_SRC);
    else
      edje_object_signal_emit(item_ed, _EDJE_SIG_UNMOUNTED, _EDJE_SIG_SRC);

    if (id->flags & PLACES_STORAGE_FLAG_CAN_EJECT)
    {
      edje_object_part_text_set(item_ed, _EDJE_PART_EJECT, _("eject"));
      edje_object_signal_emit(item_ed, _EDJE_SIG_CAN_EJECT, _EDJE_SIG_SRC);
    }
    else if (id->flags & PLACES_STORAGE_FLAG_IS_MOUNTED)
    {
      edje_object_part_text_set(item_ed, _EDJE_PART_EJECT, _("safely remove"));
      edje_object_signal_emit(item_ed, _EDJE_SIG_CAN_EJECT, _EDJE_SIG_SRC);
    } else
      edje_object_signal_emit(item_ed, _EDJE_SIG_CANNOT_EJECT, _EDJE_SIG_SRC);

    edje_object_part_box_append(ed, _EDJE_PART_CONTENTS, item);
    places_navigation_item_add(ctxt->parent, &id->base);
    continue;

  error:
    if (item)
      evas_object_del(item);
    if (icon)
      evas_object_del(icon);
    eina_stringshare_del(id->name);
    eina_stringshare_del(id->icon_name);
    eina_stringshare_del(id->base.id);
    free(id);
  }
}

static void
_places_storage_volume_list_free(GList *lst)
{
  while (lst)
  {
    g_object_unref(lst->data);
    lst = g_list_delete_link(lst, lst);
  }
}

static Eina_Bool
_places_storage_is_ignored(const struct places_storage_context *ctxt, const char *uid)
{
  const GSList *n;
  for (n = ctxt->ignored; n != NULL; n = n->next)
    if (n->data && strstr(n->data, uid))
      return 1;
  return 0;
}

static const char *
_places_storage_icon_get(const struct places_storage_context *ctxt, GVolume *vol)
{
  const char *icon_path = NULL;
  GIcon *icon;

  icon = g_volume_get_icon(vol);
  if (!icon)
    icon_path = NULL;
  else
  {
    icon_path = icon_gicon_path_get(icon, ctxt->item.icon_size);
    g_object_unref(icon);
  }

  return icon_path ? icon_path : icon_path_get("folder", ctxt->item.icon_size);
}

static unsigned int
_places_storage_flag_id_get(const struct places_storage_context *ctxt __UNUSED__, GVolume *vol)
{
  unsigned int flag_id = 0;
  GMount *mount;

  if (g_volume_can_eject(vol))
    flag_id |= PLACES_STORAGE_FLAG_CAN_EJECT;

  if (g_volume_can_mount(vol))
    flag_id |= PLACES_STORAGE_FLAG_CAN_MOUNT;

  mount = g_volume_get_mount(vol);
  if (mount)
  {
    flag_id |= PLACES_STORAGE_FLAG_IS_MOUNTED;
    g_object_unref(mount);
  }

  return flag_id;
}

static Eina_Bool
_places_storage_glib_reload_work(struct places_storage_context *ctxt)
{
  GList *lst, *n;
  unsigned int i, u, len;
  const char **strings;
  gchar **uids;

  lst = g_volume_monitor_get_volumes(ctxt->monitor);
  len = g_list_length(lst);

  strings = alloca((len * 4 + 1) * sizeof(char *));
  if (!strings)
  {
    g_critical("could not allocate strings array on stack.");
    goto error;
  }

  uids = alloca(len * sizeof(char *));
  if (!uids)
  {
    g_critical("could not allocate uids array on stack.");
    goto error;
  }

  for (i = 0, u = 0, n = lst; n != NULL; n = n->next)
  {
    GVolume *vol = n->data;
    const char *icon_path;
    unsigned int flag_id;
    gchar *uid;

    uid = g_volume_get_identifier(vol, G_VOLUME_IDENTIFIER_KIND_UUID);
    if (!uid)
      continue;
    uids[u++] = uid;

    if (_places_storage_is_ignored(ctxt, uid))
      continue;

    icon_path = _places_storage_icon_get(ctxt, vol);
    flag_id = _places_storage_flag_id_get(ctxt, vol);

    strings[i + 0] = g_volume_get_name(vol);
    strings[i + 1] = icon_path ? icon_path : "";
    strings[i + 2] = _flags[flag_id];
    strings[i + 3] = uid;

    i += 4;
  }
  strings[i] = NULL;

  gstuff_ecore_run_ptr_string_array
    (_places_storage_ecore_reload, ctxt, i, strings);

  if (i > 0)
  {
    do
    {
      i -= 4;
      g_free((gchar *)strings[i]); /* volume name must be freed */
    } while (i > 0);
  }

  for (i = 0; i < u; i++)
    g_free(uids[i]);

  _places_storage_volume_list_free(lst);
  return 1;

error:
  _places_storage_volume_list_free(lst);
  return 0;
}

static gboolean
_places_storage_glib_reload_timer(gpointer data)
{
  struct places_storage_context *ctxt = data;
  _places_storage_glib_reload_work(ctxt);
  ctxt->timer_volume_reload = 0;
  return 0;
}

static void
_places_storage_glib_reload(struct places_storage_context *ctxt)
{
  if (!ctxt)
    return;

  if (ctxt->timer_volume_reload)
    g_source_remove(ctxt->timer_volume_reload);
  ctxt->timer_volume_reload = g_timeout_add
    (1000, _places_storage_glib_reload_timer, ctxt);
}

static void
_places_storage_glib_free_ignored(struct places_storage_context *ctxt)
{
  while (ctxt->ignored)
  {
    g_free(ctxt->ignored->data);
    ctxt->ignored = g_slist_delete_link(ctxt->ignored, ctxt->ignored);
  }
}

static void
_places_storage_glib_ignore_changed(GConfClient *client __UNUSED__, guint id __UNUSED__, GConfEntry *entry __UNUSED__, gpointer data)
{
  struct places_storage_context *ctxt = data;
  _places_storage_glib_free_ignored(ctxt);
  ctxt->ignored = gconf_client_get_list
    (ctxt->gconf, _IGNORE_PATH, GCONF_VALUE_STRING, NULL);
  _places_storage_glib_reload(ctxt);
}

static void
_places_storage_glib_volume_add(GVolumeMonitor *monitor __UNUSED__, GVolume *volume, gpointer data)
{
  struct places_storage_context *ctxt = data;
  gchar *uid;

  _places_storage_glib_reload(ctxt);

  uid = g_volume_get_identifier(volume, G_VOLUME_IDENTIFIER_KIND_UUID);
  if (!uid)
    return;
  _places_storage_glib_item_action(ctxt, uid);
  g_free(uid);
}

static void
_places_storage_glib_volume_del(GVolumeMonitor *monitor __UNUSED__, GVolume *volume __UNUSED__, gpointer data)
{
  struct places_storage_context *ctxt = data;

  if (ctxt->pending_launch)
  {
    launchfeedback_hide();
    ctxt->pending_launch = NULL;
  }

  _places_storage_glib_reload(ctxt);
}

static void
_places_storage_glib_volume_change(GVolumeMonitor *monitor __UNUSED__, GVolume *volume __UNUSED__, gpointer data)
{
  struct places_storage_context *ctxt = data;
  _places_storage_glib_reload(ctxt);
}

static void
_places_storage_glib_mount_add(GVolumeMonitor *monitor __UNUSED__, GVolume *volume __UNUSED__, gpointer data)
{
  struct places_storage_context *ctxt = data;
  _places_storage_glib_reload(ctxt);
}

static void
_places_storage_glib_start(void *data)
{
  struct places_storage_context *ctxt = data;
  GError *err = NULL;

  ctxt->gconf = gconf_client_get_default();
  if (!ctxt->gconf)
  {
    g_critical("could not get default gconf client.");
    return;
  }

  gconf_client_add_dir
    (ctxt->gconf, _IGNORE_DIR, GCONF_CLIENT_PRELOAD_NONE, &err);
  if (err)
  {
    g_critical("could not add gconf dir to be monitored '%s': %s",
               _IGNORE_DIR, err->message);
    g_error_free(err);
    return;
  }

  ctxt->notify.ignore_changed = gconf_client_notify_add
    (ctxt->gconf, _IGNORE_PATH, _places_storage_glib_ignore_changed,
     ctxt, NULL, &err);
  if (err)
  {
    g_critical("could not add gconf notify path '%s': %s",
               _IGNORE_PATH, err->message);
    g_error_free(err);
    return;
  }
  ctxt->ignored = gconf_client_get_list
    (ctxt->gconf, _IGNORE_PATH, GCONF_VALUE_STRING, NULL);

  ctxt->monitor = g_volume_monitor_get();
  if (!ctxt->monitor)
  {
    g_critical("could not get volume monitor.");
    return;
  }

  ctxt->notify.volume_add = g_signal_connect
    (ctxt->monitor, "volume-added",
     G_CALLBACK(_places_storage_glib_volume_add), ctxt);
  ctxt->notify.volume_del = g_signal_connect
    (ctxt->monitor, "volume-removed",
     G_CALLBACK(_places_storage_glib_volume_del), ctxt);
  ctxt->notify.volume_change = g_signal_connect
    (ctxt->monitor, "volume-changed",
     G_CALLBACK(_places_storage_glib_volume_change), ctxt);
  ctxt->notify.mount_add = g_signal_connect
    (ctxt->monitor, "mount-added",
     G_CALLBACK(_places_storage_glib_mount_add), ctxt);

  _places_storage_glib_reload(ctxt);
}

static void
_places_storage_glib_stop(void *data)
{
  struct places_storage_context *ctxt = data;

  volatile GSList *l;

  for (l = ctxt->pending_action; l != NULL;)
  {
    struct places_storage_glib_async_action *gaa = l->data;
    l = l->next;
    _places_storage_glib_async_action_cancel(gaa);
  }

  if (ctxt->notify.volume_add)
    g_signal_handler_disconnect(ctxt->monitor, ctxt->notify.volume_add);
  if (ctxt->notify.volume_del)
    g_signal_handler_disconnect(ctxt->monitor, ctxt->notify.volume_del);
  if (ctxt->notify.volume_change)
    g_signal_handler_disconnect(ctxt->monitor, ctxt->notify.volume_change);
  if (ctxt->notify.mount_add)
    g_signal_handler_disconnect(ctxt->monitor, ctxt->notify.mount_add);

  if (ctxt->monitor)
    g_object_unref(ctxt->monitor);

  _places_storage_glib_free_ignored(ctxt);

  if (ctxt->notify.ignore_changed)
    gconf_client_notify_remove(ctxt->gconf, ctxt->notify.ignore_changed);

  if (ctxt->gconf)
  {
    gconf_client_remove_dir(ctxt->gconf, _IGNORE_DIR, NULL);
    g_object_unref(ctxt->gconf);
  }

  if (ctxt->timer_volume_reload)
    g_source_remove(ctxt->timer_volume_reload);

  free(ctxt);
}

static void
_places_storage_del(void *data, Evas *e __UNUSED__, Evas_Object *o __UNUSED__, void *event_info __UNUSED__)
{
  struct places_storage_context *ctxt = data;
  ctxt->section = NULL;
  gstuff_glib_run_simple(_places_storage_glib_stop, ctxt);
}

Evas_Object *
places_storage_add(Evas_Object *parent)
{
  struct places_storage_context *ctxt;
  Evas_Object *section;

  section = places_section_add(parent, _("Storage"));
  if (!section)
  {
    ERR("could not create places storage section.\n");
    return NULL;
  }

  ctxt = calloc(1, sizeof(*ctxt));
  if (!ctxt)
  {
    evas_object_del(section);
    return NULL;
  }

  ctxt->section = section;
  ctxt->parent = parent;
  _places_storage_calc_sizes(ctxt);

  evas_object_event_callback_add
    (section, EVAS_CALLBACK_FREE, _places_storage_del, ctxt);

  gstuff_glib_run_simple(_places_storage_glib_start, ctxt);

  return section;
}
