#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#ifdef HAVE_ALLOCA_H
# include <alloca.h>
#elif defined __GNUC__
# define alloca __builtin_alloca
#elif defined _AIX
# define alloca __alloca
#elif defined _MSC_VER
# include <malloc.h>
# define alloca _alloca
#else
# include <stddef.h>
# ifdef  __cplusplus
extern "C"
# endif
void *alloca(size_t);
#endif

#ifdef __linux__
#include <features.h>
#endif
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/param.h>
#include <utime.h>
#include <math.h>
#include <fnmatch.h>
#include <limits.h>
#include <ctype.h>
#include <time.h>
#include <dirent.h>
#include <pwd.h>
#include <glob.h>
#include <errno.h>
#include <signal.h>
#include <Ecore.h>
#include <Ecore_Ipc.h>
#include <Ecore_File.h>
#include <Eet.h>
#include <Eldbus.h>

#include "e_fm_shared_device.h"
#include "e_fm_shared_codec.h"
#include "e_fm_ipc.h"
#include "e_fm_device.h"

#include "e_fm_main_udisks2.h"
#include "e_fm_main.h"

#define UDISKS2_BUS "org.freedesktop.UDisks2"
#define UDISKS2_PATH "/org/freedesktop/UDisks2"
#define UDISKS2_INTERFACE "org.freedesktop.UDisks2"
#define UDISKS2_INTERFACE_BLOCK "org.freedesktop.UDisks2.Block"
#define UDISKS2_INTERFACE_DRIVE "org.freedesktop.UDisks2.Drive"
#define UDISKS2_INTERFACE_FILESYSTEM "org.freedesktop.UDisks2.Filesystem"

typedef struct U2_Block
{
   size_t Size;
   Eina_Stringshare *Device;
   Eina_Stringshare *parent;
   Eina_Bool volume;
   Eina_Stringshare *IdType;
   Eina_Stringshare *IdLabel;
   Eina_Stringshare *IdUUID;
   Eina_Bool HintIgnore;
   Eina_Bool HintSystem;
   Eina_Stringshare *HintName;
   Eina_Stringshare *HintIconName;
} U2_Block;

static Eldbus_Connection *_e_fm_main_udisks2_conn = NULL;
static Eldbus_Proxy *_e_fm_main_udisks2_proxy = NULL;
static Eina_List *_e_stores = NULL;
static Eina_List *_e_vols = NULL;

static void _e_fm_main_udisks2_cb_dev_all(void *data, const Eldbus_Message *msg,
                                         Eldbus_Pending *pending);
static void _e_fm_main_udisks2_cb_dev_add(void *data, const Eldbus_Message *msg);
static void _e_fm_main_udisks2_cb_dev_del(void *data, const Eldbus_Message *msg);
static void _e_fm_main_udisks2_cb_vol_mounted(E_Volume *v);
static void _e_fm_main_udisks2_cb_vol_unmounted(E_Volume *v);

static void      _e_fm_main_udisks2_cb_vol_ejected(E_Volume *v);
static int _e_fm_main_udisks2_format_error_msg(char     **buf,
                                              E_Volume  *v,
                                              const char *name,
                                              const char *message);
static void _e_fm_main_udisks2_cb_storage_prop_modified(void *data, const Eldbus_Message *msg, Eldbus_Pending *p);
static void _e_fm_main_udisks2_volume_mounts_update(E_Volume *v, Eldbus_Message_Iter *arr3, Eina_Bool first);
static Eina_Bool _e_fm_main_udisks2_vol_mount_timeout(E_Volume *v);
static Eina_Bool _e_fm_main_udisks2_vol_unmount_timeout(E_Volume *v);
static Eina_Bool _e_fm_main_udisks2_vol_eject_timeout(E_Volume *v);
static E_Storage *_storage_find_by_dbus_path(const char *path);
static E_Volume *_volume_find_by_dbus_path(const char *path);
static void _volume_del(E_Volume *v);
static Eina_Bool _storage_del(void *data);
static void _e_fm_main_udisks2_cb_storage_prop_modified_cb(void *data, const Eldbus_Message *msg);
static E_Storage * _e_fm_main_udisks2_storage_drive_add(const char *udi, E_Storage *s, Eldbus_Message_Iter *arr3);

static Eina_List *vols_ejecting = NULL;

static Eina_List *stores_registered = NULL;

static Eina_Stringshare *
_util_fuckyouglib_convert(Eldbus_Message_Iter *fuckyouglib)
{
   Eldbus_Message_Iter *no_really;
   unsigned char c, buf[PATH_MAX] = {0};
   unsigned int x = 0;

   if (!eldbus_message_iter_arguments_get(fuckyouglib, "ay", &no_really)) return NULL;
   while (eldbus_message_iter_get_and_next(no_really, 'y', &c))
     {
        buf[x] = c;
        x++;
     }
   if (!buf[0]) return NULL;
   return eina_stringshare_add((char*)buf);
}

static void
_e_fm_main_udisks2_name_start(void *data __UNUSED__, const Eldbus_Message *msg,
                             Eldbus_Pending *pending __UNUSED__)
{
   unsigned flag = 0;
   Eldbus_Object *obj;

   if (!eldbus_message_arguments_get(msg, "u", &flag) || !flag)
     {
        _e_fm_main_udisks2_catch(EINA_FALSE);
        return;
     }
   obj = eldbus_object_get(_e_fm_main_udisks2_conn, UDISKS2_BUS, UDISKS2_PATH);
   eldbus_object_managed_objects_get(obj, _e_fm_main_udisks2_cb_dev_all, NULL);

   _e_fm_main_udisks2_proxy = eldbus_proxy_get(obj, ELDBUS_FDO_INTERFACE_OBJECT_MANAGER);
   eldbus_proxy_signal_handler_add(_e_fm_main_udisks2_proxy, "InterfacesAdded",
                                  _e_fm_main_udisks2_cb_dev_add, NULL);
   eldbus_proxy_signal_handler_add(_e_fm_main_udisks2_proxy, "InterfacesRemoved",
                                  _e_fm_main_udisks2_cb_dev_del, NULL);
   //eldbus_signal_handler_add(_e_fm_main_udisks2_conn, NULL,
     //NULL, ELDBUS_FDO_INTERFACE_PROPERTIES, "PropertiesChanged",
     //_e_fm_main_udisks2_cb_dev_add, NULL);
   _e_fm_main_udisks2_catch(EINA_TRUE); /* signal usage of udisks2 for mounting */
}

static void
_e_fm_main_udisks2_block_clear(U2_Block *u2)
{
   eina_stringshare_del(u2->Device);
   eina_stringshare_del(u2->parent);
   eina_stringshare_del(u2->IdType);
   eina_stringshare_del(u2->IdLabel);
   eina_stringshare_del(u2->IdUUID);
   eina_stringshare_del(u2->HintIconName);
   eina_stringshare_del(u2->HintName);

}

static void
_e_fm_main_udisks2_storage_block_add(E_Storage *s, U2_Block *u2)
{
   s->media_size = u2->Size;
   eina_stringshare_replace(&s->icon.drive, u2->HintIconName);
   s->system_internal = u2->HintIgnore;
}

static void
_e_fm_main_udisks2_volume_block_add(E_Volume *v, U2_Block *u2)
{
   v->validated = u2->volume && u2->Device && u2->parent && (!u2->HintSystem);
   if (!v->validated) return;
   v->size = u2->Size;
   eina_stringshare_replace(&v->udi, u2->Device);
   eina_stringshare_replace(&v->parent, u2->parent);
   eina_stringshare_replace(&v->icon, u2->HintIconName);
   eina_stringshare_replace(&v->uuid, u2->IdUUID);
   eina_stringshare_replace(&v->fstype, u2->IdType);
   eina_stringshare_replace(&v->label, u2->IdLabel);
   if (!v->label) v->label = eina_stringshare_ref(u2->HintName);
   
}

static void
_e_fm_main_udisks2_storage_add_send(E_Storage *s)
{
   void *msg_data;
   int msg_size;

   if (!s->validated) return;
   if (eina_list_data_find(stores_registered, s)) return;
   if ((!s->removable) && (!s->hotpluggable) && (!s->requires_eject))
     {
        DBG("removing likely internal storage %s", s->dbus_path);
        s->validated = 0;
        ecore_idler_add(_storage_del, s->dbus_path);
        return;
     }
   stores_registered = eina_list_append(stores_registered, s);
   msg_data = _e_fm_shared_codec_storage_encode(s, &msg_size);
   if (msg_data)
     {
        ecore_ipc_server_send(_e_fm_ipc_server,
                              6 /*E_IPC_DOMAIN_FM*/,
                              E_FM_OP_STORAGE_ADD,
                              0, 0, 0, msg_data, msg_size);
        free(msg_data);
     }
}

static int
_e_fm_main_udisks2_block_handle(Eldbus_Message_Iter *arr3, U2_Block *u2)
{
   Eldbus_Message_Iter *dict3;
   int block = 0;

   while (eldbus_message_iter_get_and_next(arr3, 'e', &dict3))
     {
        Eldbus_Message_Iter *var;
        char *key2, *val, *type;
        uint64_t u;
        Eina_Bool b;

        if (!eldbus_message_iter_arguments_get(dict3, "sv", &key2, &var))
          continue;

        type = eldbus_message_iter_signature_get(var);
        switch (type[0])
          {
           case 's':
           case 'o':
             eldbus_message_iter_arguments_get(var, type, &val);
             if ((!val) || (!val[0])) continue;
             break;
           case 't':
             eldbus_message_iter_arguments_get(var, type, &u);
             break;
           case 'b':
             eldbus_message_iter_arguments_get(var, type, &b);
             break;
           default: break;
          }
        if (!strcmp(key2, "Device"))
          {
             u2->Device = _util_fuckyouglib_convert(var);
             if (!strncmp(u2->Device, "/dev/ram", 8)) return -1;
          }
        else if (!strcmp(key2, "Size"))
          u2->Size = u;
        else if (!strcmp(key2, "Drive"))
          u2->parent = eina_stringshare_add(val);
        else if (!strcmp(key2, "IdUsage"))
          {
             if (!strcmp(val, "other")) return -1;
             u2->volume = !strcmp(val, "filesystem");
          }
        else if (!strcmp(key2, "IdType"))
          u2->IdType = eina_stringshare_add(val);
        else if (!strcmp(key2, "IdLabel"))
          u2->IdLabel = eina_stringshare_add(val);
        else if (!strcmp(key2, "IdUUID"))
          u2->IdUUID = eina_stringshare_add(val);
        else if (!strcmp(key2, "HintIgnore"))
          u2->HintIgnore = !!b;
        else if (!strcmp(key2, "HintSystem"))
          u2->HintSystem = !!b;
        else if (!strcmp(key2, "HintName"))
          u2->HintName = eina_stringshare_add(val);
        else if (!strcmp(key2, "HintIconName"))
          u2->HintIconName = eina_stringshare_add(val);
         block = 1;
     }
   return block;
}

static void
_e_fm_main_udisks2_handle_device(Eldbus_Message_Iter *dict)
{
   const char *udi;
   Eldbus_Message_Iter *arr2, *dict2;
   E_Volume *v = NULL;
   E_Storage *s = NULL;
   U2_Block u2;
   int block = 0;
   unsigned int pnum = UINT_MAX;
   Eina_Stringshare *pname = NULL;

   if (!eldbus_message_iter_arguments_get(dict, "oa{sa{sv}}", &udi, &arr2))
     return;
   memset(&u2, 0, sizeof(U2_Block));
   while (eldbus_message_iter_get_and_next(arr2, 'e', &dict2))
     {
        Eldbus_Message_Iter *arr3, *dict3;
        char *interface;

        if (!eldbus_message_iter_arguments_get(dict2, "sa{sv}", &interface, &arr3))
          continue;
        interface += sizeof(UDISKS2_INTERFACE);
        if (!strcmp(interface, "Partition"))
          {
             while (eldbus_message_iter_get_and_next(arr3, 'e', &dict3))
               {
                  Eldbus_Message_Iter *var;
                  char *key2, *val, *type;

                  if (!eldbus_message_iter_arguments_get(dict3, "sv", &key2, &var))
                    continue;

                  type = eldbus_message_iter_signature_get(var);
                  switch (type[0])
                    {
                     case 's':
                       if (strcmp(key2, "Name")) continue;
                       if (!eldbus_message_iter_arguments_get(var, type, &val)) continue;
                       if (val && val[0]) pname = eina_stringshare_add(val);
                       break;
                     case 'u':
                       if (strcmp(key2, "Number")) continue;
                       eldbus_message_iter_arguments_get(var, type, &pnum);
                       break;
                     default: break;
                    }
               }
             if (v)
               {
                  v->partition_label = pname;
                  pname = NULL;
                  v->partition_number = pnum;
                  v->partition = (v->partition_label || (pnum != UINT_MAX));
               }
          }
        else if (!strcmp(interface, "Filesystem"))
          {
             if (!v) v = _volume_find_by_dbus_path(udi);
             if (!v) v = _e_fm_main_udisks2_volume_add(udi, EINA_TRUE);
             _e_fm_main_udisks2_volume_mounts_update(v, arr3, 1);
             v->partition_label = pname;
             pname = NULL;
             if (pnum != UINT_MAX)
               {
                  v->partition_number = pnum;
                  v->partition = 1;
               }
             if (block)
               {
                  _e_fm_main_udisks2_volume_block_add(v, &u2);
                  if (!v->validated) E_FREE_FUNC(v, _volume_del);
               }
          }
        else if (!strcmp(interface, "Block"))
          {
             block = _e_fm_main_udisks2_block_handle(arr3, &u2);
             switch (block)
               {
                case -1: goto out;
                case 0: continue;
                default: break;
               }
             if ((!s) && (!v))
               {
                  v = _volume_find_by_dbus_path(udi);
                  s = _e_fm_main_udisks2_storage_find(u2.parent);
               }
             if (s)
               _e_fm_main_udisks2_storage_block_add(s, &u2);
             if (v)
               {
                  _e_fm_main_udisks2_volume_block_add(v, &u2);
                  if (!v->validated) E_FREE_FUNC(v, _volume_del);
               }
          }
        else if (!strcmp(interface, "Drive"))
          {
             s = _e_fm_main_udisks2_storage_drive_add(udi, s, arr3);
             if (block) _e_fm_main_udisks2_storage_block_add(s, &u2);
          }
     }
out:
   _e_fm_main_udisks2_block_clear(&u2);
   eina_stringshare_del(pname);
   if (v && v->validated) e_fm_ipc_volume_add(v);
   if (s)
     {
        Eina_List *l;

        if (!block) //cdrom :/
          s->udi = eina_stringshare_add(udi);
        EINA_LIST_FOREACH(_e_vols, l, v)
          {
             if ((v->parent == s->udi) || (v->parent == s->dbus_path))
               {
                  if (!v->storage)
                    {
                       v->storage = s;
                       s->volumes = eina_list_append(s->volumes, v);
                    }
               }
          }
        _e_fm_main_udisks2_storage_add_send(s);
     }
}

static void
_e_fm_main_udisks2_cb_dev_all(void *data __UNUSED__, const Eldbus_Message *msg,
                             Eldbus_Pending *pending __UNUSED__)
{
   const char *name, *txt;
   Eldbus_Message_Iter *arr1, *dict1;

   if (eldbus_message_error_get(msg, &name, &txt))
     {
        ERR("Error %s %s.", name, txt);
        return;
     }

   if (!eldbus_message_arguments_get(msg, "a{oa{sa{sv}}}", &arr1))
     {
        ERR("Error getting arguments.");
        return;
     }

   while (eldbus_message_iter_get_and_next(arr1, 'e', &dict1))
     _e_fm_main_udisks2_handle_device(dict1);
}

static void
_e_fm_main_udisks2_cb_vol_props(void *data, const Eldbus_Message *msg, Eldbus_Pending *p EINA_UNUSED)
{
   U2_Block u2;
   int block;
   E_Volume *v = data;
   Eldbus_Message_Iter *arr;

   memset(&u2, 0, sizeof(U2_Block));
   if (!eldbus_message_arguments_get(msg, "a{sv}", &arr))
     {
        ERR("WTF");
        return;
     }
   block = _e_fm_main_udisks2_block_handle(arr, &u2);
   if (block == 1)
     {
        Eina_Bool valid = v->validated;

        _e_fm_main_udisks2_volume_block_add(v, &u2);
        if (v->validated && (valid != v->validated))
          e_fm_ipc_volume_add(v);
        else if (!v->validated)
          _volume_del(v);
     }
   _e_fm_main_udisks2_block_clear(&u2);
}

static void
_e_fm_main_udisks2_cb_dev_add(void *data __UNUSED__, const Eldbus_Message *msg)
{
   const char *interface;

   interface = eldbus_message_interface_get(msg);
   //path = eldbus_message_path_get(msg);
   //if (!strncmp(path, "/org/freedesktop/UDisks2/drives", sizeof("/org/freedesktop/UDisks2/drives") - 1))
     //{
        //E_Storage *s;
//
        //if (!eldbus_message_arguments_get(msg, "sa{sv}as", &interface, &arr, &arr2))
          //return;
        //interface += sizeof(UDISKS2_INTERFACE);
        //if (strcmp(interface, "Drive")) return;
        //s = _storage_find_by_dbus_path(path);
        //if (!s) return;
        //valid = s->validated;
        //_e_fm_main_udisks2_storage_drive_add(s->udi, s, arr);
        //if (valid != s->validated)
          //_e_fm_main_udisks2_storage_add_send(s);
     //}
   //else if (!strncmp(path, "/org/freedesktop/UDisks2/block_devices", sizeof("/org/freedesktop/UDisks2/block_devices") - 1))
     //{
        //E_Volume *v;
//
        //if (!eldbus_message_arguments_get(msg, "sa{sv}as", &interface, &arr, &arr2))
          //return;
        //v = _volume_find_by_dbus_path(path);
        //if (!v) v = _e_fm_main_udisks2_volume_add(path, 1);
//
        //interface += sizeof(UDISKS2_INTERFACE);
        //if (!strcmp(interface, "Filesystem"))
          //_e_fm_main_udisks2_volume_mounts_update(v, arr, 0);
        //else if (!strcmp(interface, "Block"))
          //{
             //U2_Block u2;
             //int block;
//
             //memset(&u2, 0, sizeof(U2_Block));
             //block = _e_fm_main_udisks2_block_handle(arr, &u2);
             //if (block == 1)
               //{
                  //valid = v->validated;
//
                  //_e_fm_main_udisks2_volume_block_add(v, &u2);
                  //if (v->validated && (valid != v->validated))
                    //e_fm_ipc_volume_add(v);
                  //else if (!v->validated)
                    //_volume_del(v);
               //}
             //_e_fm_main_udisks2_block_clear(&u2);
          //}
     //}
   //else
   E_Storage *s = eina_list_last_data_get(_e_stores);
   E_Volume *v = eina_list_last_data_get(_e_vols);
   if (!strcmp(interface, ELDBUS_FDO_INTERFACE_OBJECT_MANAGER))
     _e_fm_main_udisks2_handle_device(eldbus_message_iter_get(msg));
   if (_e_stores && (s != eina_list_last_data_get(_e_stores)))
     {
        s = eina_list_last_data_get(_e_stores);
        if (s->proxy)
          eldbus_proxy_property_get_all(s->proxy, _e_fm_main_udisks2_cb_storage_prop_modified, s);
     }
   if (_e_vols && (v != eina_list_last_data_get(_e_vols)))
     {
        Eldbus_Message *m;
        Eldbus_Object *obj;

        v = eina_list_last_data_get(_e_vols);
        m = eldbus_message_method_call_new(UDISKS2_BUS, v->dbus_path, ELDBUS_FDO_INTERFACE_PROPERTIES, "GetAll");
        eldbus_message_arguments_append(m, "s", UDISKS2_INTERFACE_BLOCK);
        obj = eldbus_proxy_object_get(v->proxy);
        eldbus_object_send(obj, m, _e_fm_main_udisks2_cb_vol_props, v, -1);
     }
}

static void
_e_fm_main_udisks2_cb_dev_del(void *data __UNUSED__, const Eldbus_Message *msg)
{
   char *path, *interface;
   Eldbus_Message_Iter *arr;
   E_Volume *v;
   Eina_Bool vol = EINA_FALSE, sto = EINA_FALSE;

   if (!eldbus_message_arguments_get(msg, "oas", &path, &arr))
     return;
   DBG("DB DEV-: %s", path);

   while (eldbus_message_iter_get_and_next(arr, 's', &interface))
     {
        interface += sizeof(UDISKS2_INTERFACE);
        if (!strcmp(interface, "Filesystem"))
          vol = EINA_TRUE;
        else if (!strcmp(interface, "Drive"))
          sto = EINA_TRUE;
     }
   if (vol)
     {
        v = _volume_find_by_dbus_path(path);
        if (v)
          {
             if (v->mounted)
               {
                  v->optype = E_VOLUME_OP_TYPE_EJECT;
                  _e_fm_main_udisks2_cb_vol_unmounted(v);
               }
             if (v->optype == E_VOLUME_OP_TYPE_EJECT)
               _e_fm_main_udisks2_cb_vol_ejected(v);
             _volume_del(v);
          }
     }
   if (sto) _e_fm_main_udisks2_storage_del(path);
}

static void
_e_fm_main_udisks2_cb_storage_prop_modified(void *data, const Eldbus_Message *msg, Eldbus_Pending *p EINA_UNUSED)
{
   Eldbus_Message_Iter *arr;
   E_Storage *s = data;
   Eina_Bool valid;

   {
     const char *type = eldbus_message_signature_get(msg);
     if (type[0] == 's')
       {
          char *txt;
          if (eldbus_message_arguments_get(msg, type, &txt))
            ERR("%s", txt);
       }
   }
   if (!eldbus_message_arguments_get(msg, "a{sv}", &arr))
     return;
   valid = s->validated;
   _e_fm_main_udisks2_storage_drive_add(s->udi, s, arr);
   if (valid != s->validated)
     _e_fm_main_udisks2_storage_add_send(s);
}

static void
_e_fm_main_udisks2_cb_storage_prop_modified_cb(void *data, const Eldbus_Message *msg)
{
   const char *interface;
   Eldbus_Message_Iter *arr, *arr2;
   E_Storage *s = data;
   Eina_Bool valid;

   if (!eldbus_message_arguments_get(msg, "sa{sv}as", &interface, &arr, &arr2))
     return;
   valid = s->validated;
   _e_fm_main_udisks2_storage_drive_add(s->udi, s, arr);
   if (valid != s->validated)
     _e_fm_main_udisks2_storage_add_send(s);
}

static void
_e_fm_main_udisks2_cb_vol_prop_modified(void *data, const Eldbus_Message *msg, Eldbus_Pending *p EINA_UNUSED)
{
   const char *interface;
   Eldbus_Message_Iter *arr, *arr2;
   E_Volume *v = data;

   if (!eldbus_message_arguments_get(msg, "sa{sv}as", &interface, &arr, &arr2))
     return;
   interface += sizeof(UDISKS2_INTERFACE);
   if (!strcmp(interface, "Filesystem"))
     _e_fm_main_udisks2_volume_mounts_update(v, arr, 0);
   else if (!strcmp(interface, "Block"))
     {
        U2_Block u2;
        int block;

        memset(&u2, 0, sizeof(U2_Block));
        block = _e_fm_main_udisks2_block_handle(arr, &u2);
        if (block == 1)
          {
             Eina_Bool valid = v->validated;

             _e_fm_main_udisks2_volume_block_add(v, &u2);
             if (v->validated && (valid != v->validated))
               e_fm_ipc_volume_add(v);
             else if (!v->validated)
               _volume_del(v);
          }
        _e_fm_main_udisks2_block_clear(&u2);
     }
}

static void
_e_fm_main_udisks2_cb_vol_prop_modified_cb(void *data, const Eldbus_Message *msg)
{
   _e_fm_main_udisks2_cb_vol_prop_modified(data, msg, NULL);
}

static Eina_Bool
_storage_del(void *data)
{
   const char *path = data;
   _e_fm_main_udisks2_storage_del(path);
   return ECORE_CALLBACK_CANCEL;
}

static E_Storage *
_e_fm_main_udisks2_storage_drive_add(const char *udi, E_Storage *s, Eldbus_Message_Iter *arr3)
{
   Eldbus_Message_Iter *dict3;

   if (!s)
     {
        if (!s) s = _e_fm_main_udisks2_storage_find(udi);
        if (!s) s = _e_fm_main_udisks2_storage_add(udi);
        if (!s) return NULL;
     }
   if (s->system_internal)
     {
        DBG("removing storage internal %s", s->dbus_path);
        s->validated = 0;
        ecore_idler_add(_storage_del, s->dbus_path);
        return NULL;
     }
   while (eldbus_message_iter_get_and_next(arr3, 'e', &dict3))
     {
        Eldbus_Message_Iter *var;
        char *key2, *val, *type;
        Eina_Bool b;
        uint64_t t;

        if (!eldbus_message_iter_arguments_get(dict3, "sv", &key2, &var))
          continue;

        type = eldbus_message_iter_signature_get(var);
        switch (type[0])
          {
           case 's':
           case 'o':
             eldbus_message_iter_arguments_get(var, type, &val);
             break;
           case 'b':
             eldbus_message_iter_arguments_get(var, type, &b);
             break;
           case 't':
             eldbus_message_iter_arguments_get(var, type, &t);
             break;
           default: break;
          }

        if (!strcmp(key2, "ConnectionBus"))
          eina_stringshare_replace(&s->bus, val);
        else if (!strcmp(key2, "MediaCompatibility"))
          {
             Eldbus_Message_Iter *inner_array;
             const char *media;

             if (!eldbus_message_iter_arguments_get(var, "as", &inner_array))
               continue;

             if (eldbus_message_iter_get_and_next(inner_array, 's', &media))
               eina_stringshare_replace(&s->drive_type, media);
          }
        else if (!strcmp(key2, "Model"))
          eina_stringshare_replace(&s->model, (val && val[0]) ? val : NULL);
        else if (!strcmp(key2, "Vendor"))
           eina_stringshare_replace(&s->vendor, (val && val[0]) ? val : NULL);
        else if (!strcmp(key2, "Serial"))
          eina_stringshare_replace(&s->serial, (val && val[0]) ? val : NULL);
        else if (!strcmp(key2, "MediaAvailable"))
          s->media_available = !!b;
        else if (!strcmp(key2, "Size"))
          s->media_size = t;
        else if (!strcmp(key2, "Ejectable"))
          s->requires_eject = !!b;
        else if (!strcmp(key2, "Removable"))
          s->hotpluggable = !!b;
        else if (!strcmp(key2, "MediaRemovable"))
          s->removable = !!b;
     }

   INF("++STO:\n  udi: %s\n  bus: %s\n  drive_type: %s\n  model: %s\n  vendor: %s\n  serial: %s\n  icon.drive: %s\n  icon.volume: %s\n", s->udi, s->bus, s->drive_type, s->model, s->vendor, s->serial, s->icon.drive, s->icon.volume);
   s->validated = EINA_TRUE;

   if (!s->proxy)
     {
        Eldbus_Object *obj;

        obj = eldbus_object_get(_e_fm_main_udisks2_conn, UDISKS2_BUS, s->udi);
        s->proxy = eldbus_proxy_get(obj, UDISKS2_INTERFACE_DRIVE);
        eldbus_proxy_properties_monitor(s->proxy, 1);
        eldbus_proxy_properties_changed_callback_add(s->proxy, _e_fm_main_udisks2_cb_storage_prop_modified_cb, s);
     }
   return s;
}

static int
_e_fm_main_udisks2_format_error_msg(char     **buf,
                                   E_Volume  *v,
                                   const char *name,
                                   const char *message)
{
   int size, vu, vm = 1, en;
   char *tmp;

   vu = strlen(v->udi) + 1;
   if (v->mount_point) vm = strlen(v->mount_point) + 1;
   en = strlen(name) + 1;
   size = vu + vm + en + strlen(message) + 1;
   tmp = *buf = malloc(size);

   strcpy(tmp, v->udi);
   tmp += vu;
   if (v->mount_point)
     {
        strcpy(tmp, v->mount_point);
        tmp += vm;
     }
   else
     {
        *tmp = 0;
        tmp += vm;
     }
   strcpy(tmp, name);
   tmp += en;
   strcpy(tmp, message);

   return size;
}

static Eina_Bool
_e_fm_main_udisks2_vol_mount_timeout(E_Volume *v)
{
   char *buf;
   int size;

   v->guard = NULL;

   E_FREE_FUNC(v->op, eldbus_pending_cancel);
   v->optype = E_VOLUME_OP_TYPE_NONE;
   size = _e_fm_main_udisks2_format_error_msg(&buf, v,
                       "org.enlightenment.fm2.MountTimeout",
                       "Unable to mount the volume with specified time-out.");
   ecore_ipc_server_send(_e_fm_ipc_server, 6 /*E_IPC_DOMAIN_FM*/, E_FM_OP_MOUNT_ERROR,
                         0, 0, 0, buf, size);
   free(buf);

   return ECORE_CALLBACK_CANCEL;
}

static void
_e_fm_main_udisks2_cb_vol_mounted(E_Volume *v)
{
   char *buf;
   int size;

   E_FREE_FUNC(v->guard, ecore_timer_del);
   if (!v->mount_point) return; /* come back later */

   v->optype = E_VOLUME_OP_TYPE_NONE;
   v->op = NULL;
   v->mounted = EINA_TRUE;
   INF("MOUNT: %s from %s", v->udi, v->mount_point);
   size = strlen(v->udi) + 1 + strlen(v->mount_point) + 1;
   buf = alloca(size);
   strcpy(buf, v->udi);
   strcpy(buf + strlen(buf) + 1, v->mount_point);
   ecore_ipc_server_send(_e_fm_ipc_server,
                         6 /*E_IPC_DOMAIN_FM*/,
                         E_FM_OP_MOUNT_DONE,
                         0, 0, 0, buf, size);
}

static Eina_Bool
_e_fm_main_udisks2_vol_unmount_timeout(E_Volume *v)
{
   char *buf;
   int size;

   v->guard = NULL;
   if (v->op)
     eldbus_pending_cancel(v->op);
   v->op = NULL;
   v->optype = E_VOLUME_OP_TYPE_NONE;
   size = _e_fm_main_udisks2_format_error_msg(&buf, v,
                      "org.enlightenment.fm2.UnmountTimeout",
                      "Unable to unmount the volume with specified time-out.");
   ecore_ipc_server_send(_e_fm_ipc_server, 6 /*E_IPC_DOMAIN_FM*/, E_FM_OP_UNMOUNT_ERROR,
                         0, 0, 0, buf, size);
   free(buf);

   return ECORE_CALLBACK_CANCEL;
}

static void
_e_fm_main_udisks2_cb_vol_unmounted(E_Volume *v)
{
   char *buf;
   int size;

   E_FREE_FUNC(v->guard, ecore_timer_del);

   if (v->optype != E_VOLUME_OP_TYPE_EJECT)
     {
        v->optype = E_VOLUME_OP_TYPE_NONE;
        v->op = NULL;
     }

   v->mounted = EINA_FALSE;
   INF("UNMOUNT: %s from %s", v->udi, v->mount_point);
   size = strlen(v->udi) + 1 + strlen(v->mount_point) + 1;
   buf = alloca(size);
   strcpy(buf, v->udi);
   strcpy(buf + strlen(buf) + 1, v->mount_point);
   ecore_ipc_server_send(_e_fm_ipc_server,
                         6 /*E_IPC_DOMAIN_FM*/,
                         E_FM_OP_UNMOUNT_DONE,
                         0, 0, 0, buf, size);
}

static Eina_Bool
_e_fm_main_udisks2_vol_eject_timeout(E_Volume *v)
{
   char *buf;
   int size;

   v->guard = NULL;
   if (v->op)
     eldbus_pending_cancel(v->op);
   v->op = NULL;
   v->optype = E_VOLUME_OP_TYPE_NONE;
   size = _e_fm_main_udisks2_format_error_msg(&buf, v,
                         "org.enlightenment.fm2.EjectTimeout",
                         "Unable to eject the media with specified time-out.");
   ecore_ipc_server_send(_e_fm_ipc_server, 6 /*E_IPC_DOMAIN_FM*/, E_FM_OP_EJECT_ERROR,
                         0, 0, 0, buf, size);
   free(buf);

   return ECORE_CALLBACK_CANCEL;
}

static Eldbus_Pending *
_volume_eject(Eldbus_Proxy *proxy)
{
   Eldbus_Message *msg;
   Eldbus_Message_Iter *array, *main_iter;

   msg = eldbus_proxy_method_call_new(proxy, "Eject");
   main_iter = eldbus_message_iter_get(msg);
   eldbus_message_iter_arguments_append(main_iter, "a{sv}", &array);
   eldbus_message_iter_container_close(main_iter, array);

   return eldbus_proxy_send(proxy, msg, NULL, NULL, -1);
}

static void
_volume_eject_umount_cb(void *data, const Eldbus_Message *msg, Eldbus_Pending *pending EINA_UNUSED)
{
   const char *name, *txt;
   E_Volume *v = data;

   if (eldbus_message_error_get(msg, &name, &txt))
     ERR("%s: %s", name, txt);
   else if (v->optype == E_VOLUME_OP_TYPE_EJECT)
     {
        vols_ejecting = eina_list_remove(vols_ejecting, v);
        _volume_eject(v->storage->proxy);
     }
     
}

static Eldbus_Pending *
_volume_umount(E_Volume *v)
{
   Eldbus_Message *msg;
   Eldbus_Message_Iter *array, *main_iter;

   msg = eldbus_proxy_method_call_new(v->proxy, "Unmount");
   main_iter = eldbus_message_iter_get(msg);
   eldbus_message_iter_arguments_append(main_iter, "a{sv}", &array);
   eldbus_message_iter_container_close(main_iter, array);

   return eldbus_proxy_send(v->proxy, msg, _volume_eject_umount_cb, v, -1);
}

static Eldbus_Pending *
_volume_mount(Eldbus_Proxy *proxy, const char *fstype, const char *buf)
{
   Eldbus_Message *msg;
   Eldbus_Message_Iter *array, *main_iter, *dict, *var;
   char opt_txt[] = "options";
   char fs_txt[] = "fstype";

   msg = eldbus_proxy_method_call_new(proxy, "Mount");
   main_iter = eldbus_message_iter_get(msg);
   if (!eldbus_message_iter_arguments_append(main_iter, "a{sv}", &array))
     ERR("E");
   if (fstype)
     {
        if (!eldbus_message_iter_arguments_append(array, "{sv}", &dict))
          ERR("E");
        eldbus_message_iter_basic_append(dict, 's', fs_txt);

        var = eldbus_message_iter_container_new(dict, 'v', "s");
        eldbus_message_iter_basic_append(var, 's', fstype);
        eldbus_message_iter_container_close(dict, var);
        eldbus_message_iter_container_close(array, dict);

     }
   if (buf[0])
     {
        if (!eldbus_message_iter_arguments_append(array, "{sv}", &dict))
          ERR("E");
        eldbus_message_iter_basic_append(dict, 's', opt_txt);

        var = eldbus_message_iter_container_new(dict, 'v', "s");
        eldbus_message_iter_basic_append(var, 's', buf);
        eldbus_message_iter_container_close(dict, var);
        eldbus_message_iter_container_close(array, dict);
     }
   eldbus_message_iter_container_close(main_iter, array);

   return eldbus_proxy_send(proxy, msg, NULL, NULL, -1);
}

static void
_e_fm_main_udisks2_cb_vol_ejected(E_Volume *v)
{
   char *buf;
   int size;

   E_FREE_FUNC(v->guard, ecore_timer_del);
   v->optype = E_VOLUME_OP_TYPE_NONE;

   size = strlen(v->udi) + 1;
   buf = alloca(size);
   strcpy(buf, v->udi);
   ecore_ipc_server_send(_e_fm_ipc_server,
                         6 /*E_IPC_DOMAIN_FM*/,
                         E_FM_OP_EJECT_DONE,
                         0, 0, 0, buf, size);
}

static void
_e_fm_main_udisks2_volume_mounts_update(E_Volume *v, Eldbus_Message_Iter *arr3, Eina_Bool first)
{
   Eldbus_Message_Iter *dict3;
   Eina_List *mounts = NULL;

   while (eldbus_message_iter_get_and_next(arr3, 'e', &dict3))
     {
        Eldbus_Message_Iter *var;
        char *key2, *type;

        if (!eldbus_message_iter_arguments_get(dict3, "sv", &key2, &var))
          continue;

        type = eldbus_message_iter_signature_get(var);
        switch (type[0])
          {
           Eldbus_Message_Iter *fuckyouglib;

           case 'a':
             if (strcmp(key2, "MountPoints")) continue;
             /* glib developers are assholes, so this is "aay" instead of just "as" */
             if (!eldbus_message_iter_arguments_get(var, type, &fuckyouglib))
               {
                  ERR("fucked by glib I guess");
                  continue;
               }
             do
               {
                  Eina_Stringshare *m;

                  m = _util_fuckyouglib_convert(fuckyouglib);
                  if (m) mounts = eina_list_append(mounts, m);
               }
             while (eldbus_message_iter_next(fuckyouglib));
           default: continue;
          }
     }
   if (first)
     {
        eina_stringshare_replace(&v->mount_point, eina_list_last_data_get(mounts));
        v->mounted = !!v->mount_point;
        if (v->storage)
          v->storage->media_available = v->mounted;
     }
   else if ((!v->mounted) || (!eina_list_data_find(mounts, v->mount_point)))
     {
        if (mounts)
          eina_stringshare_replace(&v->mount_point, eina_list_last_data_get(mounts));
        v->mounted = !!mounts;
        if (v->storage)
          v->storage->media_available = v->mounted;
        switch (v->optype)
          {
             case E_VOLUME_OP_TYPE_MOUNT:
               _e_fm_main_udisks2_cb_vol_mounted(v);
               break;
             case E_VOLUME_OP_TYPE_UNMOUNT:
               _e_fm_main_udisks2_cb_vol_unmounted(v);
               break;
             case E_VOLUME_OP_TYPE_EJECT:
               if (vols_ejecting && eina_list_data_find(vols_ejecting, v))
                 _e_fm_main_udisks2_cb_vol_unmounted(v);
               else
                 _e_fm_main_udisks2_cb_vol_ejected(v);
               break;
             default: break;
          }
     }
   E_FREE_LIST(mounts, eina_stringshare_del);
}

E_Volume *
_e_fm_main_udisks2_volume_add(const char *path, Eina_Bool first_time)
{
   E_Volume *v;
   Eldbus_Object *obj;

   if (!path) return NULL;
   if (_volume_find_by_dbus_path(path)) return NULL;
   v = calloc(1, sizeof(E_Volume));
   if (!v) return NULL;
   v->dbus_path = eina_stringshare_add(path);
   INF("VOL+ %s", path);
   v->efm_mode = EFM_MODE_USING_UDISKS2_MOUNT;
   v->first_time = first_time;
   _e_vols = eina_list_append(_e_vols, v);
   obj = eldbus_object_get(_e_fm_main_udisks2_conn, UDISKS2_BUS, path);
   v->proxy = eldbus_proxy_get(obj, UDISKS2_INTERFACE_FILESYSTEM);
   eldbus_proxy_properties_monitor(v->proxy, 1);
   eldbus_proxy_properties_changed_callback_add(v->proxy, _e_fm_main_udisks2_cb_vol_prop_modified_cb, v);

   return v;
}

void
_e_fm_main_udisks2_volume_del(const char *path)
{
   E_Volume *v;

   v = _volume_find_by_dbus_path(path);
   if (!v) return;
   _volume_del(v);
}

static void
_volume_del(E_Volume *v)
{
   E_FREE_FUNC(v->guard, ecore_timer_del);
   if (v->validated)
     {
        INF("--VOL %s", v->udi);
        /* FIXME: send event of storage volume (disk) removed */
           ecore_ipc_server_send(_e_fm_ipc_server,
                                 6 /*E_IPC_DOMAIN_FM*/,
                                 E_FM_OP_VOLUME_DEL,
                                 0, 0, 0, v->udi, eina_stringshare_strlen(v->udi) + 1);
     }
   v->optype = E_VOLUME_OP_TYPE_NONE;
   if (v->proxy)
     {
        Eldbus_Object *obj = eldbus_proxy_object_get(v->proxy);
        eldbus_proxy_unref(v->proxy);
        eldbus_object_unref(obj);
     }
   _e_vols = eina_list_remove(_e_vols, v);
   _e_fm_shared_device_volume_free(v);
}

E_Volume *
_e_fm_main_udisks2_volume_find(const char *udi)
{
   Eina_List *l;
   E_Volume *v;

   if (!udi) return NULL;
   EINA_LIST_FOREACH(_e_vols, l, v)
     {
        if (!v->udi) continue;
        if (!strcmp(udi, v->udi)) return v;
     }
   return NULL;
}

static E_Volume *
_volume_find_by_dbus_path(const char *path)
{
   Eina_List *l;
   E_Volume *v;

   if (!path) return NULL;
   EINA_LIST_FOREACH(_e_vols, l, v)
     if (!strcmp(path, v->dbus_path)) return v;
   return NULL;
}

void
_e_fm_main_udisks2_volume_eject(E_Volume *v)
{
   if (!v || v->guard) return;
   if (!v->storage) v->storage = _e_fm_main_udisks2_storage_find(v->parent);
   if (!v->storage)
     {
        ERR("COULD NOT FIND DISK STORAGE!!!");
        return;
     }
   if (v->mounted)
     {
        v->guard = ecore_timer_add(E_FM_UNMOUNT_TIMEOUT, (Ecore_Task_Cb)_e_fm_main_udisks2_vol_unmount_timeout, v);
        v->op = _volume_umount(v);
        vols_ejecting = eina_list_append(vols_ejecting, v);
     }
   else
     {
        v->guard = ecore_timer_add(E_FM_EJECT_TIMEOUT, (Ecore_Task_Cb)_e_fm_main_udisks2_vol_eject_timeout, v);
        v->op = _volume_eject(v->storage->proxy);
     }
   v->optype = E_VOLUME_OP_TYPE_EJECT;
}

void
_e_fm_main_udisks2_volume_unmount(E_Volume *v)
{
   if (!v || v->guard) return;
   INF("unmount %s %s", v->udi, v->mount_point);

   v->guard = ecore_timer_add(E_FM_UNMOUNT_TIMEOUT, (Ecore_Task_Cb)_e_fm_main_udisks2_vol_unmount_timeout, v);
   v->op = _volume_umount(v);
   v->optype = E_VOLUME_OP_TYPE_UNMOUNT;
}

void
_e_fm_main_udisks2_volume_mount(E_Volume *v)
{
   char buf[256] = {0};

   if ((!v) || (v->guard))
     return;

   INF("mount %s %s [fs type = %s]", v->udi, v->mount_point, v->fstype);

   // Map uid to current user if possible
   // Possible filesystems found in udisks2 source (src/udisks2linuxfilesystem.c) as of 2012-07-11
   if (!strcmp(v->fstype, "vfat"))
     snprintf(buf, sizeof(buf), "uid=%i,utf8=1", (int)getuid());
   else if ((!strcmp(v->fstype, "iso9660")) || (!strcmp(v->fstype, "udf")))
   // force utf8 as the encoding - e only likes/handles utf8. its the
   // pseudo-standard these days anyway for "linux" for intl text to work
   // everywhere. problem is some fs's use differing options and udisks2
   // doesn't allow some options with certain filesystems.
   // Valid options found in udisks2 (src/udisks2linuxfilesystem.c) as of 2012-07-11
   // Note that these options are default with the latest udisks2, kept here to
   // avoid breakage in the future (hopefully).
     snprintf(buf, sizeof(buf), "uid=%i,iocharset=utf8", (int)getuid());
   else if (!strcmp(v->fstype, "ntfs"))
     snprintf(buf, sizeof(buf), "uid=%i", (int)getuid());

   v->guard = ecore_timer_add(E_FM_MOUNT_TIMEOUT, (Ecore_Task_Cb)_e_fm_main_udisks2_vol_mount_timeout, v);

   // It was previously noted here that ubuntu 10.04 failed to mount if opt was passed to
   // e_udisks2_volume_mount.  The reason at the time was unknown and apparently never found.
   // I theorize that this was due to improper mount options being passed (namely the utf8 options).
   // If this still fails on Ubuntu 10.04 then an actual fix should be found.
   v->op = _volume_mount(v->proxy, v->fstype, buf);

   v->optype = E_VOLUME_OP_TYPE_MOUNT;
}

void
_e_fm_main_udisks2_init(void)
{
   eldbus_init();
   _e_fm_main_udisks2_conn = eldbus_connection_get(ELDBUS_CONNECTION_TYPE_SYSTEM);
   if (!_e_fm_main_udisks2_conn) return;

   eldbus_name_start(_e_fm_main_udisks2_conn, UDISKS2_BUS, 0,
                    _e_fm_main_udisks2_name_start, NULL);
}

void
_e_fm_main_udisks2_shutdown(void)
{
   if (_e_fm_main_udisks2_proxy)
     {
        Eldbus_Object *obj;
        obj = eldbus_proxy_object_get(_e_fm_main_udisks2_proxy);
        eldbus_proxy_unref(_e_fm_main_udisks2_proxy);
        eldbus_object_unref(obj);
     }
   if (_e_fm_main_udisks2_conn)
     {
        eldbus_connection_unref(_e_fm_main_udisks2_conn);
        eldbus_shutdown();
     }
}

E_Storage *
_e_fm_main_udisks2_storage_add(const char *path)
{
   E_Storage *s;

   if (!path) return NULL;
   if (_storage_find_by_dbus_path(path)) return NULL;
   s = calloc(1, sizeof(E_Storage));
   if (!s) return NULL;

   DBG("STORAGE+=%s", path);
   s->dbus_path = eina_stringshare_add(path);
   s->udi = eina_stringshare_ref(s->dbus_path);
   _e_stores = eina_list_append(_e_stores, s);
   return s;
}

void
_e_fm_main_udisks2_storage_del(const char *path)
{
   E_Storage *s;

   s = _storage_find_by_dbus_path(path);
   if (!s) return;
   if (s->validated)
     {
        stores_registered = eina_list_remove(stores_registered, s);
        INF("--STO %s", s->udi);
          ecore_ipc_server_send(_e_fm_ipc_server,
                                6 /*E_IPC_DOMAIN_FM*/,
                                E_FM_OP_STORAGE_DEL,
                                0, 0, 0, s->udi, strlen(s->udi) + 1);
     }
   _e_stores = eina_list_remove(_e_stores, s);
   if (s->proxy)
     {
        Eldbus_Object *obj = eldbus_proxy_object_get(s->proxy);
        eldbus_proxy_unref(s->proxy);
        eldbus_object_unref(obj);
     }
   while (s->volumes)
     _volume_del(eina_list_data_get(s->volumes));
   _e_fm_shared_device_storage_free(s);
}

static E_Storage *
_storage_find_by_dbus_path(const char *path)
{
   Eina_List *l;
   E_Storage *s;

   EINA_LIST_FOREACH(_e_stores, l, s)
     if (!strcmp(path, s->dbus_path)) return s;
   return NULL;
}

E_Storage *
_e_fm_main_udisks2_storage_find(const char *udi)
{
   Eina_List *l;
   E_Storage *s;

   EINA_LIST_FOREACH(_e_stores, l, s)
     {
        if (!s->udi) continue;
        if (!strcmp(udi, s->udi)) return s;
     }
   return NULL;
}
