// fs backend for regular fs files - opens a dir, lists, monitors and
// sends all files it finds nd all metadata and any changes that happen
// to that dir. it only handles a single dir. in theory you can write complex
// fs handlers that could do this over a network protocol or merge multiple
// real fs locations into a single apparent dir to the user. the idea is
// the front end that consumes the output of this should do NO file access
// itself at all and do everything via fs handlers for open, delete, rename
// copy, import, export etc.
#include "cmd.h"
#include "sha.h"
#include <Ecore_File.h>
#include <Efreet.h>
#include <Efreet_Mime.h>
#include <Eet.h>

#include <sys/types.h>
#include <pwd.h>
#include <grp.h>

#include "thumb_check.h"

static const char         *icon_theme = NULL;
static const char         *config_dir = NULL;
static const char         *home_dir = NULL;

static Ecore_File_Monitor *mon = NULL;

typedef struct
{
   const char  *path;
   const char  *mime;
   const char  *thumb;
   Ecore_Exe   *exe;
   Ecore_Timer *busy_delay_timer;
} Thumb;

static Ecore_Event_Handler *thumb_exe_del_handler = NULL;
static Eina_List           *thumb_queue = NULL;
static Eina_List           *thumb_busy_queue = NULL;
static unsigned int         thumb_busy_max = 8;
static double               thumb_update_delay = 0.25;

static Eina_Bool _file_add_mod_info(Eina_Strbuf *strbuf, const char *path, Eina_Bool delay);

static void
_strbuf_append_file_escaped(Eina_Strbuf *strbuf, const char *path)
{ // append a filename and escape special chars for cmdline args
   const char *s;

   for (s = path; *s; s++)
     {
        // if it's a special char - escale it
        if ((*s ==  ' ') || (*s == '\t') || (*s == '\n') || (*s == '\\') ||
            (*s == '\'') || (*s == '\"') || (*s ==  ';') || (*s ==  '!') ||
            (*s ==  '#') || (*s ==  '$') || (*s ==  '%') || (*s ==  '&') ||
            (*s ==  '*') || (*s ==  '(') || (*s ==  ')') || (*s ==  '[') ||
            (*s ==  ']') || (*s ==  '{') || (*s ==  '}') || (*s ==  '|') ||
            (*s ==  '<') || (*s ==  '>') || (*s ==  '?'))
          eina_strbuf_append_char(strbuf, '\\');
        eina_strbuf_append_char(strbuf, *s);
     }
}

static void
_file_thumb_flush(void)
{ // while count of busy list < max, pick from queue and launch ecore exe
   Thumb *th;
   Eina_List *l;
   Eina_Strbuf *strbuf;
   const char *dir = getenv("EFM_BACKEND_DIR");

   if (!dir)
     {
        fprintf(stderr, "EFM_BACKEND_DIR not set to path to backend dirs\n");
        abort();
     }
   strbuf = eina_strbuf_new();
   if (!strbuf) return;
   while (eina_list_count(thumb_busy_queue) < thumb_busy_max)
     {
        if (!thumb_queue) break;

        // find first item in out queue that is noy delaying until it is busy
        EINA_LIST_FOREACH(thumb_queue, l, th)
          {
             if (!th->busy_delay_timer) break;
             th = NULL;
          }
        if (!th) break; // we didn't find anything that is not busy sleeping
        // remove from queue and put on busys queue
        thumb_queue = eina_list_remove_list(thumb_queue, l);
        thumb_busy_queue = eina_list_append(thumb_busy_queue, th);
        // build thumb command:
        //   thumb FILE_PATH MIME THUMBNAIL_PATH
        // e.g.
        //   thumb /path/to/f.jpg image/jpeg /home/u/.e/e/thumbs/f8/2a18.eet
        eina_strbuf_reset(strbuf);
        eina_strbuf_append(strbuf, getenv("EFM_BACKEND_DIR"));
        eina_strbuf_append(strbuf, "/thumb ");
        _strbuf_append_file_escaped(strbuf, th->path);
        eina_strbuf_append_char(strbuf, ' ');
        _strbuf_append_file_escaped(strbuf, th->mime);
        eina_strbuf_append_char(strbuf, ' ');
        _strbuf_append_file_escaped(strbuf, th->thumb);
        th->exe = ecore_exe_run(eina_strbuf_string_get(strbuf), th);
     }
   eina_strbuf_free(strbuf);
}

static Eina_Bool
_file_thumb(const char *path EINA_UNUSED, const char *mime)
{ // return true if we should have/generate a thumb for this
   return check_thumb_any(path, mime);
}

static char *
_file_thumb_find(const char *path, const char *mime EINA_UNUSED)
{ // find the thumb file
   unsigned char sha[20];
   char buf[PATH_MAX], shastr[41];

   eina_sha1((const unsigned char *)path, strlen(path), sha);
   sha1_str(sha, shastr);
   snprintf(buf, sizeof(buf), "%s/thumbs/%c%c/%s.eet", config_dir,
            shastr[0], shastr[1], shastr + 2);
   return strdup(buf);
}

static Eina_Bool
_cb_thumb_delay(void *data)
{ // when a delay timer expires - flush the queue to the busy queue
   Thumb *th = data;

   th->busy_delay_timer = NULL;
   _file_thumb_flush();
   return EINA_FALSE;
}

static void
_file_thumb_queue(const char *path, const char *mime, Eina_Bool delay)
{ // append a thumb generation item to the queue with a delay timer
   Thumb *th = calloc(1, sizeof(Thumb));
   char *thumb;

   if (!th) return;
   thumb = _file_thumb_find(path, mime);
   if (!thumb)
     {
        free(th);
        return;
     }
   th->path = eina_stringshare_add(path);
   th->mime = eina_stringshare_add(mime);
   th->thumb = eina_stringshare_add(thumb);
   if (delay)
     th->busy_delay_timer = ecore_timer_add(thumb_update_delay,
                                            _cb_thumb_delay, th);
   thumb_queue = eina_list_append(thumb_queue, th);
   free(thumb);
}

static void
_file_thumb_gen(const char *path, const char *mime, Eina_Bool delay)
{ // queue path + mime and kick off queue porcessor if idle
   Eina_List *l;
   Thumb *th;
   Eina_Bool found = EINA_FALSE;

   // if already on queue - just moved to back
   EINA_LIST_FOREACH(thumb_queue, l, th)
     {
        if (!strcmp(th->path, path))
          {
             // move to end of queue
             thumb_queue = eina_list_demote_list(thumb_queue, l);
             // reset timer to start again
             ecore_timer_reset(th->busy_delay_timer);
             found = EINA_TRUE;
             break;
          }
     }
   if (!found) _file_thumb_queue(path, mime, delay);
   _file_thumb_flush();
}

static Eina_Bool
_cb_thumb_exe_del(void *data EINA_UNUSED, int ev_type EINA_UNUSED, void *event)
{ // thumb slave exited - exit code 0 == all ok
   Ecore_Exe_Event_Del *ev = event;
   Thumb *th;
   Eina_List *l;

   // find exe in our busy queue - if it is there
   EINA_LIST_FOREACH(thumb_busy_queue, l, th)
     {
        if (th->exe != ev->exe) continue; // it's not our desired exe - next
        // remove this thumb from the busy list
        thumb_busy_queue = eina_list_remove_list(thumb_busy_queue, l);
        if (ev->exit_code == 0)
          { // thumb generation was all ok so send a file update
             Eina_Strbuf *strbuf = cmd_strbuf_new("file-mod");
             cmd_strbuf_append(strbuf, "path", th->path);
             if (!_file_add_mod_info(strbuf, th->path, EINA_FALSE))
               eina_strbuf_free(strbuf);
             else
               cmd_strbuf_print_consume(strbuf);
          }
        // free up this thumb in our busy queue as we're done with it
        eina_stringshare_del(th->path);
        eina_stringshare_del(th->mime);
        eina_stringshare_del(th->thumb);
        free(th);
        // we may have spare spots on our busy queue so flush to it
        _file_thumb_flush();
        break;
     }
   return ECORE_CALLBACK_PASS_ON; // let others handle this exe if they want
}

static void
_file_thumb_handle(Eina_Strbuf *strbuf, const char *path, const char *mime, struct stat *st, Eina_Bool delay)
{ // handle finding the thumb of file and checking it's valid etc.
   char *thumb;

   // does this file type do/need thumbnails
   if (!_file_thumb(path, mime)) return;

   // get what the path to the target thumb should be
   thumb = _file_thumb_find(path, mime);
   if (thumb)
     { // open the thumb and let's see if the stat info is up to date
        Eet_File *ef = eet_open(thumb, EET_FILE_MODE_READ);

        if (ef)
          { // thumb file exists - check meta data
             int size = 0;
             unsigned char statsha1[20];
             unsigned char *origstatsha1;
             Eina_Bool ok = EINA_FALSE;

             // sha1 the relevant stat data at this point
             sha1_stat(st, statsha1);
             // load the stored sha1 of stat data from the thumb file
             origstatsha1 = eet_read(ef, "orig/stat/sha1", &size);
             if ((origstatsha1) && (size == 20))
               {
                  if (!memcmp(statsha1, origstatsha1, 20))
                    ok = EINA_TRUE; // sha1 of stat data matches - ok
               }
             eet_close(ef);
             if (!ok)
               { // thumb stat data doesn'yt match file state data
                  free(thumb);
                  thumb = NULL;
               }
          }
        else
          { // doesn't exist so no valid thumb
             free(thumb);
             thumb = NULL;
          }
     }
   if (!thumb)
     { // no valid thumb - generate one
        _file_thumb_gen(path, mime, delay);
     }
   else
     { // add the thumb property with full path to thumb
        cmd_strbuf_append(strbuf, "thumb", thumb);
        // XXX: add if tumb has alpha or not...
        free(thumb);
     }
}

static char *
_icon_resolve(const char *path, const char *icon, struct stat *st)
{
   struct passwd *pw;
   char buf[PATH_MAX], *dir, *p, *user;

   if (!icon) return NULL;
   // /path/to/file.png
   if (icon[0] == '/') return strdup(icon);
   if ((icon[0] == '.') && (icon[1] == '/'))
     { // ./path/file.png
        if ((st->st_mode & S_IFMT) == S_IFDIR)
          dir = strdup(path);
        else
          dir = ecore_file_dir_get(path);
        if (dir)
          {
             snprintf(buf, sizeof(buf), "%s/%s", dir, icon + 2);
             return strdup(buf);
          }
        else return NULL;
     }
   if ((icon[0] == '~') && (icon[1] == '/'))
     { // ~/path/file.png
        pw = getpwuid(st->st_uid);
        if ((pw) && (pw->pw_dir))
          {
             snprintf(buf, sizeof(buf), "%s/%s", pw->pw_dir, icon + 2);
             return strdup(buf);
          }
        else return NULL;
     }
   if (icon[0] == '~')
     { // ~user/path/file.png
        p = strchr(icon, '/');
        if (p)
          {
             user = malloc(p - icon);
             if (user)
               {
                  strncpy(user, icon + 1, p - icon - 1);
                  user[p - icon - 1] = 0;
                  pw = getpwnam(user);
                  if ((pw) && (pw->pw_dir))
                    {
                       snprintf(buf, sizeof(buf), "%s/%s", pw->pw_dir, p + 1);
                       return strdup(buf);
                    }
                  else return NULL;
               }
             else return NULL;
          }
        else return NULL;
     }
   return NULL;
}

static const char *
_desktop_x_field(Efreet_Desktop *desktop, const char *key)
{
   if (!desktop->x) return NULL;
   return eina_hash_find(desktop->x, key);
}

static void
_cmd_desktop_x_field_append(Eina_Strbuf *strbuf, Efreet_Desktop *desktop, const char *cmd_key, const char *key)
{
   const char *s = _desktop_x_field(desktop, key);
   if (s) cmd_strbuf_append(strbuf, cmd_key, s);
}

static void
_cmd_desktop_x_field_icon_resolve_append(Eina_Strbuf *strbuf, Efreet_Desktop *desktop, const char *cmd_key, const char *key, const char *path, struct stat *st)
{
   const char *s = _desktop_x_field(desktop, key);
   char *icf;

   if (!s) return;
   icf = _icon_resolve(path, s, st);
   if (!icf) return;
   cmd_strbuf_append(strbuf, cmd_key, icf);
   free(icf);
}

const char *
_mime_get(const char *file)
{
   if (eina_fnmatch("*.edj", file, EINA_FNMATCH_CASEFOLD))
     return "application/x-edje";
   return efreet_mime_type_get(file);
}

static Eina_Bool
_file_add_mod_info(Eina_Strbuf *strbuf, const char *path, Eina_Bool delay)
{ // add file metadata info on file add or modfiy
   char dst[PATH_MAX], buf[256], buf2[PATH_MAX], *icf;
   struct stat st;
   int mode;
   struct passwd *pw;
   struct group *gr;
   const char *mime, *ext, *icon;
   Efreet_Desktop *desktop;
   Eina_Bool have_label = EINA_FALSE;
// XXX: x & y pos @ wxh view

   if (lstat(path, &st) != 0) return EINA_FALSE;
   if ((st.st_mode & S_IFMT) == S_IFLNK)
     {
        struct stat stdst;

        cmd_strbuf_append(strbuf, "type", "link");
        if (stat(path, &stdst) == 0)
          {
             ssize_t sz;

             sz = readlink(path, dst, sizeof(dst) - 1);
             if (sz > 0)
               {
                  dst[sz] = 0;
                  cmd_strbuf_append(strbuf, "link", dst);
                  if ((stdst.st_mode & S_IFMT) == S_IFLNK)
                    cmd_strbuf_append(strbuf, "link-type", "link");
                  else if ((stdst.st_mode & S_IFMT) == S_IFBLK)
                    cmd_strbuf_append(strbuf, "link-type", "block");
                  else if ((stdst.st_mode & S_IFMT) == S_IFCHR)
                    cmd_strbuf_append(strbuf, "link-type", "char");
                  else if ((stdst.st_mode & S_IFMT) == S_IFIFO)
                    cmd_strbuf_append(strbuf, "link-type", "fifo");
                  else if ((stdst.st_mode & S_IFMT) == S_IFSOCK)
                    cmd_strbuf_append(strbuf, "link-type", "socket");
                  else if ((stdst.st_mode & S_IFMT) == S_IFDIR)
                    {
                       cmd_strbuf_append(strbuf, "link-type", "dir");
                       snprintf(buf2, sizeof(buf2), "%s/.dir.desktop", path);
                       desktop = efreet_desktop_get(buf2);
                       if (desktop)
                         {
                            cmd_strbuf_append(strbuf, "link-label", desktop->name ? desktop->name : ecore_file_file_get(path));
                            cmd_strbuf_append(strbuf, "link-desktop-generic-name", desktop->generic_name ? "true" : "false");
                            cmd_strbuf_append(strbuf, "link-desktop-comment", desktop->comment ? desktop->comment : "");
                            icf = _icon_resolve(path, desktop->icon, &stdst);
                            if (icf)
                              {
                                 cmd_strbuf_append(strbuf, "link-desktop-icon", icf);
                                 free(icf);
                              }
                            else
                              cmd_strbuf_append(strbuf, "link-desktop-icon", desktop->icon ? desktop->icon : "");
                            cmd_strbuf_append(strbuf, "link-desktop-try-exec", desktop->try_exec ? desktop->try_exec : "");
                            cmd_strbuf_append(strbuf, "link-desktop-exec", desktop->exec ? desktop->exec : "");
                            cmd_strbuf_append(strbuf, "link-desktop-url", desktop->url ? desktop->url : "");
                            cmd_strbuf_append(strbuf, "link-desktop-no-display", desktop->no_display ? "true" : "false");
                            cmd_strbuf_append(strbuf, "link-desktop-hidden", desktop->hidden ? "true" : "false");
                            cmd_strbuf_append(strbuf, "link-desktop-terminal", desktop->terminal ? "true" : "false");
                            cmd_strbuf_append(strbuf, "link-desktop-startup-notify", desktop->startup_notify ? "true" : "false");
                            if ((desktop->icon) && (desktop->icon[0] != '/'))
                              {
                                 icon = efreet_mime_type_icon_get(desktop->icon, icon_theme, 128);
                                 cmd_strbuf_append(strbuf, "link-desktop-icon.lookup", icon ? icon : "");
                              }
                            _cmd_desktop_x_field_append(strbuf, desktop, "link-label-clicked", "X-NameClicked");
                            _cmd_desktop_x_field_append(strbuf, desktop, "link-label-selected", "X-NameSelected");
                            _cmd_desktop_x_field_icon_resolve_append(strbuf, desktop, "link-desktop-icon-clicked", "X-IconClicked", path, &stdst);
                            _cmd_desktop_x_field_icon_resolve_append(strbuf, desktop, "link-desktop-icon-selected", "X-IconSelected", path, &stdst);
                            efreet_desktop_free(desktop);
                            have_label = EINA_TRUE;
                         }
                    }
                  else
                    cmd_strbuf_append(strbuf, "link-type", "file");
                  mime = _mime_get(dst);
                  if (mime)
                    {
                       cmd_strbuf_append(strbuf, "mime", mime);
                       icon = efreet_mime_type_icon_get(mime, icon_theme, 128);
                       if (icon) cmd_strbuf_append(strbuf, "mime-icon", icon);
                       _file_thumb_handle(strbuf, dst, mime, &stdst, delay);
                    }
                  ext = strrchr(dst, '.');
                  if ((ext) && (!strcasecmp(ext, ".desktop")) && (S_ISREG(stdst.st_mode)))
                    {
                       desktop = efreet_desktop_get(path);
                       if (desktop)
                         {
                            cmd_strbuf_append(strbuf, "link-label", desktop->name ? desktop->name : ecore_file_file_get(path));
                            cmd_strbuf_append(strbuf, "link-desktop-generic-name", desktop->generic_name ? "true" : "false");
                            cmd_strbuf_append(strbuf, "link-desktop-comment", desktop->comment ? desktop->comment : "");
                            icf = _icon_resolve(path, desktop->icon, &stdst);
                            if (icf)
                              {
                                 cmd_strbuf_append(strbuf, "link-desktop-icon", icf);
                                 free(icf);
                              }
                            else
                              cmd_strbuf_append(strbuf, "link-desktop-icon", desktop->icon ? desktop->icon : "");
                            cmd_strbuf_append(strbuf, "link-desktop-try-exec", desktop->try_exec ? desktop->try_exec : "");
                            cmd_strbuf_append(strbuf, "link-desktop-exec", desktop->exec ? desktop->exec : "");
                            cmd_strbuf_append(strbuf, "link-desktop-url", desktop->url ? desktop->url : "");
                            cmd_strbuf_append(strbuf, "link-desktop-no-display", desktop->no_display ? "true" : "false");
                            cmd_strbuf_append(strbuf, "link-desktop-hidden", desktop->hidden ? "true" : "false");
                            cmd_strbuf_append(strbuf, "link-desktop-terminal", desktop->terminal ? "true" : "false");
                            cmd_strbuf_append(strbuf, "link-desktop-startup-notify", desktop->startup_notify ? "true" : "false");
                            if ((desktop->icon) && (desktop->icon[0] != '/'))
                              {
                                 icon = efreet_mime_type_icon_get(desktop->icon, icon_theme, 128);
                                 cmd_strbuf_append(strbuf, "link-desktop-icon.lookup", icon ? icon : "");
                              }
                            _cmd_desktop_x_field_append(strbuf, desktop, "link-label-clicked", "X-NameClicked");
                            _cmd_desktop_x_field_append(strbuf, desktop, "link-label-selected", "X-NameSelected");
                            _cmd_desktop_x_field_icon_resolve_append(strbuf, desktop, "link-desktop-icon-clicked", "X-IconClicked", path, &stdst);
                            _cmd_desktop_x_field_icon_resolve_append(strbuf, desktop, "link-desktop-icon-selected", "X-IconSelected", path, &stdst);
                            efreet_desktop_free(desktop);
                         }
                       else
                         cmd_strbuf_append(strbuf, "link-label", ecore_file_file_get(path));
                    }
                  else
                    cmd_strbuf_append(strbuf, "link-label", ecore_file_file_get(path));
                  pw = getpwuid(stdst.st_uid);
                  if (pw) cmd_strbuf_append(strbuf, "link-user", pw->pw_name);
                  gr = getgrgid(stdst.st_gid);
                  if (gr) cmd_strbuf_append(strbuf, "link-group", gr->gr_name);
                  mode = stdst.st_mode &
                  (S_ISUID | S_ISGID | S_ISVTX | S_IRUSR | S_IWUSR | S_IXUSR |
                   S_IRGRP | S_IWGRP | S_IXGRP | S_IROTH | S_IWOTH | S_IXOTH);
                  snprintf(buf, sizeof(buf), "%x", mode);
                  cmd_strbuf_append(strbuf, "link-mode", buf);
                  snprintf(buf, sizeof(buf), "%llu", (unsigned long long)stdst.st_ino);
                  cmd_strbuf_append(strbuf, "link-inode", buf);
                  snprintf(buf, sizeof(buf), "%llu", (unsigned long long)stdst.st_nlink);
                  cmd_strbuf_append(strbuf, "link-nlink", buf);
                  snprintf(buf, sizeof(buf), "%llu", (unsigned long long)stdst.st_gid);
                  cmd_strbuf_append(strbuf, "link-gid", buf);
                  snprintf(buf, sizeof(buf), "%llu", (unsigned long long)stdst.st_size);
                  cmd_strbuf_append(strbuf, "link-size", buf);
                  snprintf(buf, sizeof(buf), "%llu", (unsigned long long)stdst.st_blksize);
                  cmd_strbuf_append(strbuf, "link-blksize", buf);
                  snprintf(buf, sizeof(buf), "%llu", (unsigned long long)stdst.st_blocks);
                  cmd_strbuf_append(strbuf, "link-blocks", buf);
                  snprintf(buf, sizeof(buf), "%llu", (unsigned long long)stdst.st_atime);
                  cmd_strbuf_append(strbuf, "link-atime", buf);
                  snprintf(buf, sizeof(buf), "%llu", (unsigned long long)stdst.st_mtime);
                  cmd_strbuf_append(strbuf, "link-mtime", buf);
                  snprintf(buf, sizeof(buf), "%llu", (unsigned long long)stdst.st_ctime);
                  cmd_strbuf_append(strbuf, "link-ctime", buf);
               }
          }
        else // stat of original failed - what do we do?
          {
             cmd_strbuf_append(strbuf, "broken-link", "true");
             mime = _mime_get(path);
             if (mime)
               {
                  cmd_strbuf_append(strbuf, "mime", mime);
                  icon = efreet_mime_type_icon_get(mime, icon_theme, 128);
                  if (icon) cmd_strbuf_append(strbuf, "mime-icon", icon);
               }
          }
     }
   else
     {
        if ((st.st_mode & S_IFMT) == S_IFBLK)
          {
             cmd_strbuf_append(strbuf, "type", "block");
             snprintf(buf, sizeof(buf), "%i", (int)st.st_dev);
             cmd_strbuf_append(strbuf, "dev", buf);
             snprintf(buf, sizeof(buf), "%i", (int)st.st_rdev);
             cmd_strbuf_append(strbuf, "rdev", buf);
          }
        else if ((st.st_mode & S_IFMT) == S_IFCHR)
          {
             cmd_strbuf_append(strbuf, "type", "char");
             snprintf(buf, sizeof(buf), "%i", (int)st.st_dev);
             cmd_strbuf_append(strbuf, "dev", buf);
             snprintf(buf, sizeof(buf), "%i", (int)st.st_rdev);
             cmd_strbuf_append(strbuf, "rdev", buf);
          }
        else if ((st.st_mode & S_IFMT) == S_IFIFO)
          {
             cmd_strbuf_append(strbuf, "type", "fifo");
          }
        else if ((st.st_mode & S_IFMT) == S_IFSOCK)
          {
             cmd_strbuf_append(strbuf, "type", "socket");
          }
        else if ((st.st_mode & S_IFMT) == S_IFDIR)
          {
             cmd_strbuf_append(strbuf, "type", "dir");
             snprintf(buf2, sizeof(buf2), "%s/.dir.desktop", path);
             desktop = efreet_desktop_get(buf2);
             if (desktop)
               {
                  cmd_strbuf_append(strbuf, "label", desktop->name ? desktop->name : ecore_file_file_get(path));
                  cmd_strbuf_append(strbuf, "desktop-generic-name", desktop->generic_name ? "true" : "false");
                  cmd_strbuf_append(strbuf, "desktop-comment", desktop->comment ? desktop->comment : "");
                  icf = _icon_resolve(path, desktop->icon, &st);
                  if (icf)
                    {
                       cmd_strbuf_append(strbuf, "desktop-icon", icf);
                       free(icf);
                    }
                  else
                    cmd_strbuf_append(strbuf, "desktop-icon", desktop->icon ? desktop->icon : "");
                  cmd_strbuf_append(strbuf, "desktop-try-exec", desktop->try_exec ? desktop->try_exec : "");
                  cmd_strbuf_append(strbuf, "desktop-exec", desktop->exec ? desktop->exec : "");
                  cmd_strbuf_append(strbuf, "desktop-url", desktop->url ? desktop->url : "");
                  cmd_strbuf_append(strbuf, "desktop-no-display", desktop->no_display ? "true" : "false");
                  cmd_strbuf_append(strbuf, "desktop-hidden", desktop->hidden ? "true" : "false");
                  cmd_strbuf_append(strbuf, "desktop-terminal", desktop->terminal ? "true" : "false");
                  cmd_strbuf_append(strbuf, "desktop-startup-notify", desktop->startup_notify ? "true" : "false");
                  if ((desktop->icon) && (desktop->icon[0] != '/'))
                    {
                       icon = efreet_mime_type_icon_get(desktop->icon, icon_theme, 128);
                       cmd_strbuf_append(strbuf, "desktop-icon.lookup", icon ? icon : "");
                    }
                  _cmd_desktop_x_field_append(strbuf, desktop, "label-clicked", "X-NameClicked");
                  _cmd_desktop_x_field_append(strbuf, desktop, "label-selected", "X-NameSelected");
                  _cmd_desktop_x_field_icon_resolve_append(strbuf, desktop, "desktop-icon-clicked", "X-IconClicked", path, &st);
                  _cmd_desktop_x_field_icon_resolve_append(strbuf, desktop, "desktop-icon-selected", "X-IconSelected", path, &st);
                  efreet_desktop_free(desktop);
                  have_label = EINA_TRUE;
               }
          }
        else
          cmd_strbuf_append(strbuf, "type", "file");
        mime = _mime_get(path);
        if (mime)
          {
             cmd_strbuf_append(strbuf, "mime", mime);
             icon = efreet_mime_type_icon_get(mime, icon_theme, 128);
             if (icon) cmd_strbuf_append(strbuf, "mime-icon", icon);
             _file_thumb_handle(strbuf, path, mime, &st, delay);
          }
        ext = strrchr(path, '.');
        if ((ext) && (!strcasecmp(ext, ".desktop")) && (S_ISREG(st.st_mode)))
          {
             desktop = efreet_desktop_get(path);
             if (desktop)
               {
                  cmd_strbuf_append(strbuf, "label", desktop->name ? desktop->name : ecore_file_file_get(path));
                  cmd_strbuf_append(strbuf, "desktop-generic-name", desktop->generic_name ? "true" : "false");
                  cmd_strbuf_append(strbuf, "desktop-comment", desktop->comment ? desktop->comment : "");
                  icf = _icon_resolve(path, desktop->icon, &st);
                  if (icf)
                    {
                       cmd_strbuf_append(strbuf, "desktop-icon", icf);
                       free(icf);
                    }
                  else
                    cmd_strbuf_append(strbuf, "desktop-icon", desktop->icon ? desktop->icon : "");
                  cmd_strbuf_append(strbuf, "desktop-try-exec", desktop->try_exec ? desktop->try_exec : "");
                  cmd_strbuf_append(strbuf, "desktop-exec", desktop->exec ? desktop->exec : "");
                  cmd_strbuf_append(strbuf, "desktop-url", desktop->url ? desktop->url : "");
                  cmd_strbuf_append(strbuf, "desktop-no-display", desktop->no_display ? "true" : "false");
                  cmd_strbuf_append(strbuf, "desktop-hidden", desktop->hidden ? "true" : "false");
                  cmd_strbuf_append(strbuf, "desktop-terminal", desktop->terminal ? "true" : "false");
                  cmd_strbuf_append(strbuf, "desktop-startup-notify", desktop->startup_notify ? "true" : "false");
                  if ((desktop->icon) && (desktop->icon[0] != '/'))
                    {
                       icon = efreet_mime_type_icon_get(desktop->icon, icon_theme, 128);
                       cmd_strbuf_append(strbuf, "desktop-icon.lookup", icon ? icon : "");
                    }
                  _cmd_desktop_x_field_append(strbuf, desktop, "label-clicked", "X-NameClicked");
                  _cmd_desktop_x_field_append(strbuf, desktop, "label-selected", "X-NameSelected");
                  _cmd_desktop_x_field_icon_resolve_append(strbuf, desktop, "desktop-icon-clicked", "X-IconClicked", path, &st);
                  _cmd_desktop_x_field_icon_resolve_append(strbuf, desktop, "desktop-icon-selected", "X-IconSelected", path, &st);
                  efreet_desktop_free(desktop);
               }
             else if (!have_label)
               cmd_strbuf_append(strbuf, "label", ecore_file_file_get(path));
          }
        else if (!have_label)
          cmd_strbuf_append(strbuf, "label", ecore_file_file_get(path));
     }
   pw = getpwuid(st.st_uid);
   if (pw) cmd_strbuf_append(strbuf, "user", pw->pw_name);
   gr = getgrgid(st.st_gid);
   if (gr) cmd_strbuf_append(strbuf, "group", gr->gr_name);
   mode = st.st_mode /*&
   (S_ISUID | S_ISGID | S_ISVTX | S_IRUSR | S_IWUSR | S_IXUSR |
    S_IRGRP | S_IWGRP | S_IXGRP | S_IROTH | S_IWOTH | S_IXOTH)*/;
   snprintf(buf, sizeof(buf), "%x", mode);
   cmd_strbuf_append(strbuf, "mode", buf);
   snprintf(buf, sizeof(buf), "%llu", (unsigned long long)st.st_ino);
   cmd_strbuf_append(strbuf, "inode", buf);
   snprintf(buf, sizeof(buf), "%llu", (unsigned long long)st.st_nlink);
   cmd_strbuf_append(strbuf, "nlink", buf);
   snprintf(buf, sizeof(buf), "%llu", (unsigned long long)st.st_gid);
   cmd_strbuf_append(strbuf, "gid", buf);
   snprintf(buf, sizeof(buf), "%llu", (unsigned long long)st.st_size);
   cmd_strbuf_append(strbuf, "size", buf);
   snprintf(buf, sizeof(buf), "%llu", (unsigned long long)st.st_blksize);
   cmd_strbuf_append(strbuf, "blksize", buf);
   snprintf(buf, sizeof(buf), "%llu", (unsigned long long)st.st_blocks);
   cmd_strbuf_append(strbuf, "blocks", buf);
   snprintf(buf, sizeof(buf), "%llu", (unsigned long long)st.st_atime);
   cmd_strbuf_append(strbuf, "atime", buf);
   snprintf(buf, sizeof(buf), "%llu", (unsigned long long)st.st_mtime);
   cmd_strbuf_append(strbuf, "mtime", buf);
   snprintf(buf, sizeof(buf), "%llu", (unsigned long long)st.st_ctime);
   cmd_strbuf_append(strbuf, "ctime", buf);

   return EINA_TRUE;
}

static void
_file_add(const char *path)
{
   Eina_Strbuf *strbuf;

   strbuf = cmd_strbuf_new("file-add");
   cmd_strbuf_append(strbuf, "path", path);
   if (!_file_add_mod_info(strbuf, path, EINA_FALSE))
     eina_strbuf_free(strbuf);
   else
     cmd_strbuf_print_consume(strbuf);
}

static void
_file_del(const char *path)
{
   Eina_Strbuf *strbuf;

   strbuf = cmd_strbuf_new("file-del");
   cmd_strbuf_append(strbuf, "path", path);
   cmd_strbuf_print_consume(strbuf);
}

static void
_file_mod(const char *path)
{
   Eina_Strbuf *strbuf;

   strbuf = cmd_strbuf_new("file-mod");
   cmd_strbuf_append(strbuf, "path", path);
   if (!_file_add_mod_info(strbuf, path, EINA_TRUE))
     eina_strbuf_free(strbuf);
   else
     cmd_strbuf_print_consume(strbuf);
}

static void
_dir_del(const char *path)
{
   Eina_Strbuf *strbuf;

   strbuf = cmd_strbuf_new("dir-del");
   cmd_strbuf_append(strbuf, "path", path);
   cmd_strbuf_print_consume(strbuf);
}

static void
_cb_mon(void *data EINA_UNUSED, Ecore_File_Monitor *em EINA_UNUSED, Ecore_File_Event event, const char *path)
{
   if ((event == ECORE_FILE_EVENT_CREATED_FILE) ||
       (event == ECORE_FILE_EVENT_CREATED_DIRECTORY))
     _file_add(path);
   else if ((event == ECORE_FILE_EVENT_DELETED_FILE) ||
            (event == ECORE_FILE_EVENT_DELETED_DIRECTORY))
     _file_del(path);
   else if (event == ECORE_FILE_EVENT_MODIFIED)
     _file_mod(path);
   else if (event == ECORE_FILE_EVENT_DELETED_SELF)
     _dir_del(path);
}

static void
_monitor(const char *path)
{
   Eina_Iterator *it;
   Eina_File_Direct_Info *info;
   Eina_Strbuf *strbuf;

   if (mon) return;

   // tell the front end out listing is beginning
   strbuf = cmd_strbuf_new("list-begin");
   cmd_strbuf_print_consume(strbuf);

   mon = ecore_file_monitor_add(path, _cb_mon, NULL);
   it = eina_file_direct_ls(path);
   if (!it)
     {
        // XXX: error output
        goto err;
     }
   EINA_ITERATOR_FOREACH(it, info) _file_add(info->path);
   eina_iterator_free(it);
err:
   // tell the front end out listing is done
   strbuf = cmd_strbuf_new("list-end");
   cmd_strbuf_print_consume(strbuf);
}

void
do_handle_cmd(Cmd *c)
{
   if (!strcmp(c->command, "dir-set"))
     {
        int i = 0;

        for (i = 0; c->dict[i]; i += 2)
          {
             if (!strcmp(c->dict[i], "path")) _monitor(c->dict[i + 1]);
          }
     }
//   cmd_dump_sterr(c);
}

int
do_init(int argc EINA_UNUSED, const char **argv EINA_UNUSED)
{
   ecore_file_init();
   efreet_init();
   efreet_mime_init();

   icon_theme = getenv("E_ICON_THEME");
   config_dir = getenv("E_HOME_DIR");
   home_dir = getenv("HOME");
   if (!home_dir) return 0;
   if (!config_dir)
     {
        char buf[PATH_MAX];

        snprintf(buf, sizeof(buf), "%s/.e/e", home_dir);
        home_dir = eina_stringshare_add(buf);
     }

   // maximum number of back-end thumbnailer slaves is num cores - 2
   thumb_busy_max = eina_cpu_count() - 2;
   if (thumb_busy_max < 1) thumb_busy_max = 1;

   // we want to listen for when thumbnails slaves finish
   thumb_exe_del_handler = ecore_event_handler_add(ECORE_EXE_EVENT_DEL,
                                                   _cb_thumb_exe_del, NULL);
   return 0;
}

void
do_shutdown(void)
{
   ecore_event_handler_del(thumb_exe_del_handler);
   thumb_exe_del_handler = NULL;

   if (mon) ecore_file_monitor_del(mon);
   mon = NULL;

   efreet_mime_shutdown();
   efreet_shutdown();
   ecore_file_shutdown();
}
