#include "config.h"
#ifdef __linux__
#include <features.h>
#endif
#include <ctype.h>
#include <Eet.h>
#include <Eeze.h>
#include <Eeze_Disk.h>
#include <Ecore_Con.h>

#include "e_fm_shared_device.h"
#include "e_fm_shared_codec.h"
#include "e_fm_ipc.h"
#include <eeze_scanner.h>

#include "e_macros.h"
#include "e_user.h"
#include "e_fm_main.h"
#include "e_fm_main_eeze.h"
#define E_TYPEDEFS
#include "e_fm_op.h"

static void _e_fm_main_eeze_storage_rescan(const char *syspath);
static void _e_fm_main_eeze_volume_rescan(const char *syspath);
static E_Volume *_e_fm_main_eeze_volume_find_fast(const char *syspath);
static E_Storage *_e_fm_main_eeze_storage_find_fast(const char *syspath);
static void _e_fm_main_eeze_volume_del(const char *syspath);

static Eina_List *_e_stores = NULL;
static Eina_List *_e_vols = NULL;
static Eina_Bool _e_fm_eeze_init = EINA_FALSE;

static Ecore_Exe *scanner = NULL;
static Ecore_Con_Server *svr = NULL;
static Eet_Data_Descriptor *es_edd = NULL;
static Eet_Connection *es_con = NULL;
static Eina_Prefix *pfx = NULL;
static Ecore_Poller *scanner_poller = NULL;

void
e_util_env_set(const char *var, const char *val)
{
   if (val)
     {
#ifdef HAVE_SETENV
	setenv(var, val, 1);
#else
	char buf[8192];

	snprintf(buf, sizeof(buf), "%s=%s", var, val);
	if (getenv(var))
	  putenv(buf);
	else
	  putenv(strdup(buf));
#endif
     }
   else
     {
#ifdef HAVE_UNSETENV
	unsetenv(var);
#else
	if (getenv(var)) putenv(var);
#endif
     }
}

static const char *
_mount_point_escape(const char *path)
{
   const char *p;
   if (!path) return NULL;

   /* Most app doesn't handle the hostname in the uri so it's put to NULL */
   for (p = path; *p; p++)
     if ((!isalnum(*p)) && (*p != '/') && (*p != '_')) return NULL;

   return eina_stringshare_add(path);
}

static void
_e_fm_main_eeze_mount_point_set(E_Volume *v)
{
   const char *str;
   char path[PATH_MAX];

   str = strrchr(v->udi, '/');
   if ((!str) || (!str[1]))
     {
        CRI("DEV CHANGED, ABORTING");
        eina_stringshare_replace(&v->mount_point, NULL);
        eeze_disk_mount_point_set(v->disk, NULL);
        return;
     }
   /* here we arbitrarily mount everything to /media/$devnode regardless of fstab */
   eina_stringshare_del(v->mount_point);
   snprintf(path, sizeof(path), "/media%s", str);
   v->mount_point = _mount_point_escape(path);
   eeze_disk_mount_point_set(v->disk, v->mount_point);
}

static int
_e_fm_main_eeze_format_error_msg(char     **buf,
                                 E_Volume  *v,
                                 const char *name,
                                 const char *msg)
{
   int size, vu, vm, en;
   char *tmp;

   vu = strlen(v->udi) + 1;
   vm = (v->mount_point ? strlen(v->mount_point) : 0) + 1;
   en = strlen(name) + 1;
   size = vu + vm + en + strlen(msg) + 1;
   tmp = *buf = malloc(size);

   strcpy(tmp, v->udi);
   tmp += vu;
   if (v->mount_point)
     strcpy(tmp, v->mount_point);
   else
     tmp[0] = 0;
   tmp += vm;
   strcpy(tmp, name);
   tmp += en;
   strcpy(tmp, msg);

   return size;
}

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

   v->guard = NULL;
   eeze_disk_cancel(v->disk);
   size = _e_fm_main_eeze_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 Eina_Bool
_e_fm_main_eeze_cb_vol_mounted(void *user_data EINA_UNUSED,
                               int type EINA_UNUSED,
                               Eeze_Event_Disk_Mount *ev)
{
   E_Volume *v;
   char *buf;
   int size;

   v = eeze_disk_data_get(ev->disk);
   if (!v) return ECORE_CALLBACK_RENEW;
   if (!eina_list_data_find(_e_vols, v)) return ECORE_CALLBACK_RENEW;
   if (v->guard)
     {
        ecore_timer_del(v->guard);
        v->guard = NULL;
     }

   if (!eeze_disk_mounted_get(ev->disk))
     {
        ERR("Mount of '%s' failed!", v->udi);
        return ECORE_CALLBACK_RENEW;
     }
   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);
   return ECORE_CALLBACK_RENEW;
}

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

   v->guard = NULL;
   eeze_disk_cancel(v->disk);
   size = _e_fm_main_eeze_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 Eina_Bool
_e_fm_main_eeze_cb_vol_error(void *user_data EINA_UNUSED,
                             int type EINA_UNUSED,
                             Eeze_Event_Disk_Error *ev)
{
   E_Volume *v;
   char *buf;
   int size;

   v = eeze_disk_data_get(ev->disk);
   if (!v) return ECORE_CALLBACK_RENEW;
   if (!eina_list_data_find(_e_vols, v)) return ECORE_CALLBACK_RENEW;
   if (v->mounted)
     {
        size = _e_fm_main_eeze_format_error_msg(&buf, v, "org.enlightenment.fm2.UnmountError", ev->message);
        ecore_ipc_server_send(_e_fm_ipc_server, 6 /*E_IPC_DOMAIN_FM*/, E_FM_OP_UNMOUNT_ERROR,
                              0, 0, 0, buf, size);
     }
   else
     {
        size = _e_fm_main_eeze_format_error_msg(&buf, v, "org.enlightenment.fm2.MountError", ev->message);
        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_RENEW;
}

static Eina_Bool
_e_fm_main_eeze_cb_vol_unmounted(void *user_data EINA_UNUSED,
                                 int type EINA_UNUSED,
                                 Eeze_Event_Disk_Unmount *ev)
{
   char *buf;
   int size;
   E_Volume *v;

   v = eeze_disk_data_get(ev->disk);
   if (!v) return ECORE_CALLBACK_RENEW;
   if (!eina_list_data_find(_e_vols, v)) return ECORE_CALLBACK_RENEW;
   if (v->guard)
     {
        ecore_timer_del(v->guard);
        v->guard = NULL;
     }

   v->mounted = EINA_FALSE;
   INF("UNMOUNT: %s from %s", v->udi, v->mount_point);
   if (!strncmp(v->mount_point, e_user_dir_get(), strlen(e_user_dir_get())))
     if (unlink(v->mount_point))
       printf("Error unlinking mount point!\n");
   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);
   return ECORE_CALLBACK_RENEW;
}

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

   v->guard = NULL;
   eeze_disk_cancel(v->disk);
   size = _e_fm_main_eeze_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 Eina_Bool
_e_fm_main_eeze_cb_vol_ejected(void *user_data EINA_UNUSED,
                               int type EINA_UNUSED,
                               Eeze_Event_Disk_Eject *ev)
{
   E_Volume *v;
   char *buf;
   int size;

   v = eeze_disk_data_get(ev->disk);
   if (!v) return ECORE_CALLBACK_RENEW;
   if (!eina_list_data_find(_e_vols, v)) return ECORE_CALLBACK_RENEW;
   if (v->guard)
     {
        ecore_timer_del(v->guard);
        v->guard = NULL;
     }

   v->mounted = EINA_FALSE;
   if (!v->mount_point) _e_fm_main_eeze_mount_point_set(v);
   if (!v->mount_point)
     {
        /* we're aborting here, so just fail */
        return ECORE_CALLBACK_RENEW;
     }
   INF("EJECT: %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_EJECT_DONE,
                         0, 0, 0, buf, size);
   return ECORE_CALLBACK_RENEW;
}

void
_e_fm_main_eeze_volume_eject(E_Volume *v)
{
   if (!v || v->guard) return;
   if (!eeze_disk_mount_wrapper_get(v->disk))
     {
        char buf[PATH_MAX];

        snprintf(buf, sizeof(buf), "%s/enlightenment/utils/enlightenment_sys", eina_prefix_lib_get(pfx));
        eeze_disk_mount_wrapper_set(v->disk, buf);
     }
   v->guard = ecore_timer_loop_add(E_FM_EJECT_TIMEOUT, (Ecore_Task_Cb)_e_fm_main_eeze_vol_eject_timeout, v);
   eeze_disk_eject(v->disk);
}

E_Volume *
_e_fm_main_eeze_volume_add(const char *syspath,
                           Eina_Bool   first_time)
{
   E_Volume *v;
   const char *str;
   Eeze_Disk_Type type;

   if (!syspath) return NULL;
   if (_e_fm_main_eeze_volume_find_fast(syspath)) return NULL;
   v = calloc(1, sizeof(E_Volume));
   if (!v) return NULL;
   v->disk = eeze_disk_new(syspath);
   if (!v->disk)
     {
        free(v);
        return NULL;
     }
   INF("VOL+ %s", syspath);
   eeze_disk_scan(v->disk);
   type = eeze_disk_type_get(v->disk);
   if ((type == EEZE_DISK_TYPE_INTERNAL) || (type == EEZE_DISK_TYPE_UNKNOWN))
     {
        str = eeze_disk_mount_point_get(v->disk);
        if ((type != EEZE_DISK_TYPE_INTERNAL) || (str && (strncmp(str, "/media/", 7))))
          {
             INF("VOL is %s, ignoring", (type == EEZE_DISK_TYPE_INTERNAL) ? "internal" : "unknown");
             eeze_disk_free(v->disk);
             free(v);
             return NULL;
          }
     }
   eeze_disk_data_set(v->disk, v);
   v->udi = eina_stringshare_add(syspath);
   v->icon = NULL;
   v->first_time = first_time;
   v->efm_mode = EFM_MODE_USING_EEZE_MOUNT;
   _e_vols = eina_list_append(_e_vols, v);
   v->uuid = eeze_disk_uuid_get(v->disk);
   v->label = eeze_disk_label_get(v->disk);
   v->fstype = eeze_disk_fstype_get(v->disk);
   v->parent = eeze_disk_udev_get_parent(v->disk);
   {
      /* check for device being its own parent: this is the case
       * for devices such as cdroms
       */
      str = eeze_udev_syspath_get_subsystem(v->parent);
      if (strcmp(str, "block"))
        eina_stringshare_replace(&v->parent, v->udi);
   }
   str = eeze_disk_udev_get_sysattr(v->disk, "queue/hw_sector_size");
   if (!str)
     str = eeze_udev_syspath_get_sysattr(v->parent, "queue/hw_sector_size");
   if (str)
     {
        int64_t sector_size, size;
        sector_size = strtoll(str, NULL, 10);
        eina_stringshare_del(str);
        str = eeze_disk_udev_get_sysattr(v->disk, "size");
        if (str)
          {
             size = strtoll(str, NULL, 10);
             v->size = size * sector_size;
          }
        eina_stringshare_del(str);
     }
   v->mounted = eeze_disk_mounted_get(v->disk);
   str = eeze_disk_udev_get_property(v->disk, "DEVTYPE");
   if (str && (!strcmp(str, "partition")))
     v->partition = EINA_TRUE;
   eina_stringshare_del(str);
   if (v->mounted)
     v->mount_point = eina_stringshare_add(eeze_disk_mount_point_get(v->disk));

   if (v->partition)
     {
        str = eeze_disk_udev_get_sysattr(v->disk, "partition");
        if (str)
           v->partition_number = strtol(str, NULL, 10);
        eina_stringshare_del(str);
        v->partition_label = eeze_disk_udev_get_property(v->disk, "ID_FS_LABEL");
     }

   if (v->parent)
     {
        E_Storage *s;

        if (v->partition)
          {
             /* prevent having storage and volume from same device */
             E_Volume *v2 = _e_fm_main_eeze_volume_find_fast(v->parent);

             if ((v2) && (v2->partition == 0))
               _e_fm_main_eeze_volume_del(v2->udi);
          }

      s = e_storage_find(v->parent);
      INF("++VOL:\n  syspath: %s\n  uuid: %s\n  fstype: %s\n  size: %llu\n label: %s\n"
          "  partition: %d\n  partition_number: %d\n partition_label: %s\n  mounted: %d\n  mount_point: %s",
          v->udi, v->uuid, v->fstype, v->size, v->label, v->partition, v->partition_number,
          v->partition ? v->partition_label : "(not a partition)", v->mounted, v->mount_point);
      if (s)
        v->storage = s;
      else
        s = v->storage = _e_fm_main_eeze_storage_add(v->parent);

      if (s)
        {
           s->volumes = eina_list_append(s->volumes, v);
           INF("  for storage: %s", s->udi);
        }
      else INF("  storage unknown");
   }

   v->validated = EINA_TRUE;
   e_fm_ipc_volume_add(v);
   return v;
}

static void
_e_fm_main_eeze_volume_del(const char *syspath)
{
   E_Volume *v;

   v = _e_fm_main_eeze_volume_find_fast(syspath);
   if (!v) return;
   if (v->guard)
     {
        ecore_timer_del(v->guard);
        v->guard = NULL;
     }
   if (v->validated)
     {
        INF("--VOL %s\n", 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);
     }
   _e_vols = eina_list_remove(_e_vols, v);
   v->uuid = NULL;
   v->label = NULL;
   v->fstype = NULL;
   _e_fm_shared_device_volume_free(v);
}

static E_Volume *
_e_fm_main_eeze_volume_find_fast(const char *syspath)
{
   Eina_List *l;
   E_Volume *v;

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

E_Volume *
_e_fm_main_eeze_volume_find(const char *syspath)
{
   Eina_List *l;
   E_Volume *v;

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

void
_e_fm_main_eeze_volume_unmount(E_Volume *v)
{
   if (!v || v->guard) return;

   INF("unmount %s %s", v->udi, v->mount_point);

   if (!eeze_disk_mount_wrapper_get(v->disk))
     {
        char buf[PATH_MAX];

        snprintf(buf, sizeof(buf), "%s/enlightenment/utils/enlightenment_sys", eina_prefix_lib_get(pfx));
        eeze_disk_mount_wrapper_set(v->disk, buf);
     }
   v->guard = ecore_timer_loop_add(E_FM_UNMOUNT_TIMEOUT, (Ecore_Task_Cb)_e_fm_main_eeze_vol_unmount_timeout, v);
   eeze_disk_unmount(v->disk);
}

void
_e_fm_main_eeze_volume_mount(E_Volume *v)
{
   int size, opts = 0;
   char *buf;

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

   if (v->fstype)
     {
        if ((!strstr(v->fstype, "fat")) ||
            (!strcmp(v->fstype, "ntfs")) ||
            (!strcmp(v->fstype, "iso9660")) ||
            (!strcmp(v->fstype, "jfs")))
          {
             opts |= EEZE_DISK_MOUNTOPT_UTF8 | EEZE_DISK_MOUNTOPT_UID;
          }
     }
   opts |= EEZE_DISK_MOUNTOPT_NOSUID | EEZE_DISK_MOUNTOPT_NODEV | EEZE_DISK_MOUNTOPT_NOEXEC;

   _e_fm_main_eeze_mount_point_set(v);
   if (!v->mount_point) goto error;
   INF("mount %s %s [fs type = %s]", v->udi, v->mount_point, v->fstype);
   eeze_disk_mountopts_set(v->disk, opts);
   if (!eeze_disk_mount_wrapper_get(v->disk))
     {
        char buf2[PATH_MAX];

        snprintf(buf2, sizeof(buf2), "%s/enlightenment/utils/enlightenment_sys", eina_prefix_lib_get(pfx));
        eeze_disk_mount_wrapper_set(v->disk, buf2);
     }
   v->guard = ecore_timer_loop_add(E_FM_MOUNT_TIMEOUT, (Ecore_Task_Cb)_e_fm_main_eeze_vol_mount_timeout, v);
   INF("MOUNT: %s", v->udi);
   if (!eeze_disk_mount(v->disk)) goto error;
   return;
error:
   size = _e_fm_main_eeze_format_error_msg(&buf, v, "org.enlightenment.fm2.MountError", "Critical error occurred during mounting!");
   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);
   CRI("ERROR WHEN ATTEMPTING TO MOUNT!");
}

static void
eet_setup(void)
{
   Eet_Data_Descriptor_Class eddc;

   if (!eet_eina_stream_data_descriptor_class_set(&eddc, sizeof(eddc), "eeze_scanner_event", sizeof(Eeze_Scanner_Event)))
     {
        ERR("Could not create eet data descriptor!");
        _e_fm_main_catch(EFM_MODE_USING_RASTER_MOUNT);
     }

   es_edd = eet_data_descriptor_stream_new(&eddc);
   EEZE_SCANNER_EDD_SETUP(es_edd);
}
static Eina_Bool
_scanner_poll(void *data EINA_UNUSED)
{
   if (svr) return EINA_FALSE;
   svr = ecore_con_server_connect(ECORE_CON_LOCAL_SYSTEM, "eeze_scanner", 0, NULL);
   if (!svr) return EINA_FALSE;
   return EINA_TRUE;
}

static Eina_Bool
_scanner_add(void *data, int type EINA_UNUSED, Ecore_Exe_Event_Add *ev)
{
   if (data != ecore_exe_data_get(ev->exe)) return ECORE_CALLBACK_PASS_ON;
   INF("Scanner started");
   if ((!_scanner_poll(NULL)) && (!scanner_poller))
     scanner_poller = ecore_poller_add(ECORE_POLLER_CORE, 8, (Ecore_Task_Cb)_scanner_poll, NULL);
   return ECORE_CALLBACK_RENEW;
}

static Eina_Bool
_scanner_del(void *data, int type EINA_UNUSED, Ecore_Exe_Event_Del *ev)
{
   if (data != ecore_exe_data_get(ev->exe)) return ECORE_CALLBACK_PASS_ON;
   if (!svr)
     {
        const char *str = "blame cedric";

        if (ev->exit_code == 1)
          str = "unable to allocate memory";
        else if (ev->exit_code == 2)
          str = "unable to create local socket; check \"/$TMPDIR/.ecore_service/\" for stale files";
        INF("scanner connection dead (%s), exiting", str);
        _e_fm_main_catch(EFM_MODE_USING_RASTER_MOUNT);
     }
   INF("lost connection to scanner");
   scanner = NULL;
   return ECORE_CALLBACK_RENEW;
}

static Eina_Bool
_scanner_read(const void *data, size_t size, void *d EINA_UNUSED)
{
   Eeze_Scanner_Event *ev = NULL;

   ev = eet_data_descriptor_decode(es_edd, data, size);
   INF("Received %s '%s'", ev->volume ? "volume" : "storage", ev->device);
   switch (ev->type)
     {
      case EEZE_SCANNER_EVENT_TYPE_ADD:
        if (ev->volume) _e_fm_main_eeze_volume_add(ev->device, EINA_TRUE);
        else _e_fm_main_eeze_storage_add(ev->device);
        break;
      case EEZE_SCANNER_EVENT_TYPE_REMOVE:
        if (ev->volume) _e_fm_main_eeze_volume_del(ev->device);
        else _e_fm_main_eeze_storage_del(ev->device);
        break;
      default:
        if (ev->volume) _e_fm_main_eeze_volume_rescan(ev->device);
        else _e_fm_main_eeze_storage_rescan(ev->device);
        break;
     }
   eina_stringshare_del(ev->device);
   free(ev);
   return EINA_TRUE;
}

static Eina_Bool
_scanner_write(const void *eet_data EINA_UNUSED, size_t size EINA_UNUSED, void *user_data EINA_UNUSED)
{
   return EINA_TRUE;
}

static void
_scanner_run(void)
{
   static int count;

   scanner = ecore_exe_pipe_run("eeze_scanner", ECORE_EXE_NOT_LEADER | ECORE_EXE_TERM_WITH_PARENT, pfx);
   if (!scanner)
     if (++count == 3)
       _e_fm_main_catch(EFM_MODE_USING_RASTER_MOUNT);
}


static Eina_Bool
_scanner_con(void *data EINA_UNUSED, int type EINA_UNUSED, Ecore_Con_Event_Server_Del *ev EINA_UNUSED)
{
   _e_fm_main_catch(EFM_MODE_USING_EEZE_MOUNT);
   E_FREE_FUNC(scanner_poller, ecore_poller_del);
   INF("Scanner connected");
   es_con = eet_connection_new(_scanner_read, _scanner_write, NULL);
   return ECORE_CALLBACK_RENEW;
}

static Eina_Bool
_scanner_disc(void *data EINA_UNUSED, int type EINA_UNUSED, Ecore_Con_Event_Server_Del *ev EINA_UNUSED)
{
   INF("Scanner disconnected");
   if (!_scanner_poll(NULL))
     _scanner_run();
   return ECORE_CALLBACK_RENEW;
}

static Eina_Bool
_scanner_err(void *data EINA_UNUSED, int type EINA_UNUSED, Ecore_Con_Event_Server_Error *ev EINA_UNUSED)
{
   INF("Scanner connection error");
   return ECORE_CALLBACK_RENEW;
}

static Eina_Bool
_scanner_data(void *data EINA_UNUSED, int type EINA_UNUSED, Ecore_Con_Event_Server_Data *ev)
{
   eet_connection_received(es_con, ev->data, ev->size);
   return ECORE_CALLBACK_RENEW;
}

void
_e_fm_main_eeze_init(void)
{
   char **argv;
   int argc;

   if (_e_fm_eeze_init) return;
   ecore_app_args_get(&argc, &argv);
   pfx = eina_prefix_new(argv[0], e_storage_find,
                         "E", "enlightenment", "AUTHORS",
                         PACKAGE_BIN_DIR, PACKAGE_LIB_DIR,
                         PACKAGE_DATA_DIR, LOCALE_DIR);
   if (!pfx)
     {
        ERR("Could not determine prefix!");
        _e_fm_main_catch(EFM_MODE_USING_RASTER_MOUNT);
     }
   _e_fm_eeze_init = EINA_TRUE;
   ecore_con_init();
   eeze_init();
   eina_log_domain_level_set("eeze_disk", EINA_LOG_LEVEL_DBG);
   eeze_mount_tabs_watch();
   ecore_event_handler_add(EEZE_EVENT_DISK_MOUNT,
                           (Ecore_Event_Handler_Cb)_e_fm_main_eeze_cb_vol_mounted, NULL);
   ecore_event_handler_add(EEZE_EVENT_DISK_UNMOUNT,
                           (Ecore_Event_Handler_Cb)_e_fm_main_eeze_cb_vol_unmounted, NULL);
   ecore_event_handler_add(EEZE_EVENT_DISK_EJECT,
                           (Ecore_Event_Handler_Cb)_e_fm_main_eeze_cb_vol_ejected, NULL);
   ecore_event_handler_add(EEZE_EVENT_DISK_ERROR,
                           (Ecore_Event_Handler_Cb)_e_fm_main_eeze_cb_vol_error, NULL);

   ecore_event_handler_add(ECORE_EXE_EVENT_ADD,
                           (Ecore_Event_Handler_Cb)_scanner_add, pfx);
   ecore_event_handler_add(ECORE_EXE_EVENT_DEL,
                           (Ecore_Event_Handler_Cb)_scanner_del, pfx);

   ecore_event_handler_add(ECORE_CON_EVENT_SERVER_ADD,
                           (Ecore_Event_Handler_Cb)_scanner_con, NULL);
   ecore_event_handler_add(ECORE_CON_EVENT_SERVER_DEL,
                           (Ecore_Event_Handler_Cb)_scanner_disc, NULL);
   ecore_event_handler_add(ECORE_CON_EVENT_SERVER_DATA,
                           (Ecore_Event_Handler_Cb)_scanner_data, NULL);
   ecore_event_handler_add(ECORE_CON_EVENT_SERVER_ERROR,
                           (Ecore_Event_Handler_Cb)_scanner_err, NULL);

   eet_setup();
   if (!_scanner_poll(NULL))
     _scanner_run();
}

void
_e_fm_main_eeze_shutdown(void)
{
   if (scanner) ecore_exe_free(scanner);
   E_FREE_FUNC(scanner_poller, ecore_poller_del);
   eeze_mount_tabs_unwatch();
   eeze_shutdown();
   ecore_con_shutdown();
}


static void
_e_fm_main_eeze_storage_rescan(const char *syspath)
{
   E_Storage *s;

   s = _e_fm_main_eeze_storage_find_fast(syspath);
   if (s) _e_fm_main_eeze_storage_del(syspath);
   _e_fm_main_eeze_storage_add(syspath);
}

static void
_e_fm_main_eeze_volume_rescan(const char *syspath)
{
   E_Volume *v;

   v = _e_fm_main_eeze_volume_find_fast(syspath);
   if (v) _e_fm_main_eeze_volume_del(syspath);
   _e_fm_main_eeze_volume_add(syspath, EINA_FALSE);
}

E_Storage *
_e_fm_main_eeze_storage_add(const char *syspath)
{
   E_Storage *s;
   const char *str;

   if (!syspath) return NULL;
   if (_e_fm_main_eeze_storage_find_fast(syspath)) return NULL;

   str = eeze_udev_syspath_get_property(syspath, "ID_TYPE");
   if (!str)
     {
        const char *p, *subsystem;

        p = eeze_udev_syspath_get_parent(syspath);
        if (!p) return NULL;
        subsystem = eeze_udev_syspath_get_subsystem(p);
        if ((!subsystem) || strcmp(subsystem, "mmc"))
          {
             eina_stringshare_del(subsystem);
             return NULL;
          }
        eina_stringshare_del(subsystem);
     }
   eina_stringshare_del(str);
   s = calloc(1, sizeof(E_Storage));
   if (!s) return NULL;
   s->disk = eeze_disk_new(syspath);
   if (!s->disk) goto error;
   _e_stores = eina_list_append(_e_stores, s);
   eeze_disk_scan(s->disk); /* performs all necessary udev lookups at once */
   s->udi = eina_stringshare_add(eeze_disk_syspath_get(s->disk));
   switch (eeze_disk_type_get(s->disk))
     {
      case EEZE_DISK_TYPE_CDROM:
        s->drive_type = eina_stringshare_add("cdrom");
        break;
      case EEZE_DISK_TYPE_USB:
      case EEZE_DISK_TYPE_INTERNAL:
        s->drive_type = eina_stringshare_add("disk");
        break;
      case EEZE_DISK_TYPE_FLASH:
        s->drive_type = eina_stringshare_add("compact_flash");
        break;
      default:
        s->drive_type = eina_stringshare_add("floppy");
        break;
     }
   /* FIXME: this may not be congruent with the return types of other mounters,
    * but we don't use it anyway, so it's probably fine
    */
   s->bus = eeze_disk_udev_get_property(s->disk, "ID_BUS");
   s->model = eina_stringshare_add(eeze_disk_model_get(s->disk));
   s->vendor = eina_stringshare_add(eeze_disk_vendor_get(s->disk));
   s->serial = eina_stringshare_add(eeze_disk_serial_get(s->disk));
   s->removable = !!eeze_disk_removable_get(s->disk);

   if (s->removable)
     {
        int64_t size, sector_size;

        s->media_available = EINA_TRUE; /* all storage with removable=1 in udev has media available */
        str = eeze_disk_udev_get_sysattr(s->disk, "queue/hw_sector_size");
        if (str)
          {
             sector_size = strtoll(str, NULL, 10);
             eina_stringshare_del(str);
             str = eeze_disk_udev_get_sysattr(s->disk, "size");
             if (str)
               {
                  size = strtoll(str, NULL, 10);
                  s->media_size = size * sector_size;
                  eina_stringshare_del(str);
               }
          }
        s->hotpluggable = EINA_TRUE;
     }

   if (eeze_disk_type_get(s->disk) == EEZE_DISK_TYPE_CDROM)
     s->requires_eject = EINA_TRUE; /* only true on cdroms */
   s->media_check_enabled = s->removable;

   INF("++STO:\n  syspath: %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",
       s->udi, s->bus, s->drive_type, s->model, s->vendor, s->serial, s->icon.drive, s->icon.volume);
   s->validated = EINA_TRUE;
   {
      void *msg_data;
      int msg_size;

      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);
        }
   }
   return s;

error:
   ERR("ERR %s", s->udi);
   _e_fm_shared_device_storage_free(s);
   return NULL;
}

void
_e_fm_main_eeze_storage_del(const char *syspath)
{
   E_Storage *s;

   s = e_storage_find(syspath);
   if (!s) return;
   if (s->validated)
     {
        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, eina_stringshare_strlen(s->udi) + 1);
     }
   _e_stores = eina_list_remove(_e_stores, s);
   _e_fm_shared_device_storage_free(s);
}

static E_Storage *
_e_fm_main_eeze_storage_find_fast(const char *syspath)
{
   Eina_List *l;
   E_Storage *s;

   EINA_LIST_FOREACH(_e_stores, l, s)
     {
        if (syspath == s->udi) return s;
     }
   return NULL;
}

E_Storage *
_e_fm_main_eeze_storage_find(const char *syspath)
{
   Eina_List *l;
   E_Storage *s;

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