update for beta release
[framework/uifw/e17.git] / src / bin / e_fm / e_fm_ipc.c
1 #include "config.h"
2
3 #ifndef _FILE_OFFSET_BITS
4 #define _FILE_OFFSET_BITS  64
5 #endif
6
7
8 #ifdef __linux__
9 #include <features.h>
10 #endif
11 #include <stdio.h>
12 #include <stdlib.h>
13 #include <unistd.h>
14 #include <string.h>
15 #include <sys/types.h>
16 #include <sys/stat.h>
17 #include <sys/time.h>
18 #include <sys/param.h>
19 #include <utime.h>
20 #include <math.h>
21 #include <fnmatch.h>
22 #include <limits.h>
23 #include <ctype.h>
24 #include <time.h>
25 #include <dirent.h>
26 #include <pwd.h>
27 #include <glob.h>
28 #include <errno.h>
29 #include <signal.h>
30 #include <Ecore.h>
31 #include <Ecore_Ipc.h>
32 #include <Ecore_File.h>
33 #include <Evas.h>
34 #include <Efreet.h>
35 #include <Eet.h>
36
37 #include <eina_stringshare.h>
38
39 #include "e.h"
40 #include "e_fm_ipc.h"
41 //#include "e_fm_shared_c.h"
42 #include "e_fm_op.h"
43
44
45 #define DEF_SYNC_NUM 8
46 #define DEF_ROUND_TRIP 0.05
47 #define DEF_ROUND_TRIP_TOLERANCE 0.01
48 #define DEF_MOD_BACKOFF 0.2
49
50 typedef struct _E_Dir E_Dir;
51 typedef struct _E_Fop E_Fop;
52 typedef struct _E_Mod E_Mod;
53 typedef struct _e_fm_ipc_slave E_Fm_Slave;
54 typedef struct _E_Fm_Task E_Fm_Task;
55
56 struct _E_Dir
57 {
58    int                 id;
59    const char         *dir;
60    Ecore_File_Monitor *mon;
61    int                 mon_ref;
62    E_Dir              *mon_real;
63    Eina_List          *fq;
64    Ecore_Idler        *idler;
65    int                 dot_order;
66    int                 sync;
67    double              sync_time;
68    int                 sync_num;
69    Eina_List          *recent_mods;
70    Ecore_Timer        *recent_clean;
71    unsigned char       cleaning : 1;
72 };
73
74 struct _E_Fop
75 {
76    int                 id;
77    const char         *src;
78    const char         *dst;
79    const char         *rel;
80    int                 rel_to;
81    int                 x, y;
82    unsigned char       del_after : 1;
83    unsigned char       gone_bad : 1;
84    Ecore_Idler        *idler;
85    void               *data;
86 };
87
88 struct _E_Mod
89 {
90    const char    *path;
91    double         timestamp;
92    unsigned char  add : 1;
93    unsigned char  del : 1;
94    unsigned char  mod : 1;
95    unsigned char  done : 1;
96 };
97
98 struct _e_fm_ipc_slave
99 {
100    Ecore_Exe *exe;
101    int id;
102 };
103
104 struct _E_Fm_Task
105 {
106    int id;
107    E_Fm_Op_Type type;
108    E_Fm_Slave *slave;
109    const char *src;
110    const char *dst;
111    const char *rel;
112    int rel_to;
113    int x,y;
114 };
115
116
117 /* local subsystem globals */
118 Ecore_Ipc_Server *_e_fm_ipc_server = NULL;
119
120 static Eina_List *_e_dirs = NULL;
121 static Eina_List *_e_fops = NULL;
122 static int _e_sync_num = 0;
123
124
125 static Eina_List *_e_fm_ipc_slaves = NULL;
126 static Eina_List *_e_fm_tasks = NULL;
127
128 /* local subsystem functions */
129 static Eina_Bool _e_fm_ipc_cb_server_add(void *data, int type, void *event);
130 static Eina_Bool _e_fm_ipc_cb_server_del(void *data, int type, void *event);
131 static Eina_Bool _e_fm_ipc_cb_server_data(void *data, int type, void *event);
132
133 static void _e_fm_ipc_monitor_start(int id, const char *path);
134 static void _e_fm_ipc_monitor_start_try(E_Fm_Task *task);
135 static void _e_fm_ipc_monitor_end(int id, const char *path);
136 static E_Fm_Task *_e_fm_ipc_task_get(int id);
137 static Eina_List *_e_fm_ipc_task_node_get(int id);
138 static void _e_fm_ipc_task_remove(E_Fm_Task *task);
139 static void _e_fm_ipc_mkdir_try(E_Fm_Task *task);
140 static void _e_fm_ipc_mkdir(int id, const char *src, const char *rel, int rel_to, int x, int y);
141 static void _e_fm_ipc_handle_error_response(int id, E_Fm_Op_Type type);
142
143 static int _e_fm_ipc_client_send(int id, E_Fm_Op_Type type, void *data, int size);
144
145 static int _e_fm_ipc_slave_run(E_Fm_Op_Type type, const char *args, int id);
146 static E_Fm_Slave *_e_fm_ipc_slave_get(int id);
147 static int _e_fm_ipc_slave_send(E_Fm_Slave *slave, E_Fm_Op_Type type, void *data, int size);
148
149 static void _e_fm_ipc_cb_file_monitor(void *data, Ecore_File_Monitor *em, Ecore_File_Event event, const char *path);
150 static Eina_Bool _e_fm_ipc_cb_recent_clean(void *data);
151
152 static void _e_fm_ipc_file_add_mod(E_Dir *ed, const char *path, E_Fm_Op_Type op, int listing);
153 static void _e_fm_ipc_file_add(E_Dir *ed, const char *path, int listing);
154 static void _e_fm_ipc_file_del(E_Dir *ed, const char *path);
155 static void _e_fm_ipc_file_mod(E_Dir *ed, const char *path);
156 static void _e_fm_ipc_file_mon_dir_del(E_Dir *ed, const char *path);
157 static void _e_fm_ipc_file_mon_list_sync(E_Dir *ed);
158
159 static Eina_Bool _e_fm_ipc_cb_file_mon_list_idler(void *data);
160 static Eina_Bool _e_fm_ipc_cb_fop_trash_idler(void *data);
161 static char *_e_str_list_remove(Eina_List **list, char *str);
162 static void _e_fm_ipc_reorder(const char *file, const char *dst, const char *relative, int after);
163 static void _e_fm_ipc_dir_del(E_Dir *ed);
164
165 static const char *_e_fm_ipc_prepare_command(E_Fm_Op_Type type, const char *args);
166
167
168 /* local subsystem functions */
169 int
170 _e_fm_ipc_init(void)
171 {
172    char *sdir;
173    
174    sdir = getenv("E_IPC_SOCKET");
175    if (!sdir)
176      {
177         printf("The E_IPC_SOCKET environment variable is not set. This is\n"
178                "exported by Enlightenment to all processes it launches.\n"
179                "This environment variable must be set and must point to\n"
180                "Enlightenment's IPC socket file (minus port number).\n");
181         return 0;
182      }
183    _e_fm_ipc_server = ecore_ipc_server_connect(ECORE_IPC_LOCAL_SYSTEM, sdir, 0, NULL);
184    if (!_e_fm_ipc_server)
185      {
186         printf("Cannot connect to enlightenment - abort\n");
187         return 0;
188      }
189    
190    ecore_event_handler_add(ECORE_IPC_EVENT_SERVER_ADD, _e_fm_ipc_cb_server_add, NULL);
191    ecore_event_handler_add(ECORE_IPC_EVENT_SERVER_DEL, _e_fm_ipc_cb_server_del, NULL);
192    ecore_event_handler_add(ECORE_IPC_EVENT_SERVER_DATA, _e_fm_ipc_cb_server_data, NULL);
193    
194    return 1;
195 }
196
197 static Eina_Bool
198 _e_fm_ipc_cb_server_add(void *data __UNUSED__, int type __UNUSED__, void *event)
199 {
200    Ecore_Ipc_Event_Server_Add *e;
201    
202    e = event;
203    ecore_ipc_server_send(e->server, 
204                          6/*E_IPC_DOMAIN_FM*/,
205                          E_FM_OP_HELLO, 
206                          0, 0, 0, NULL, 0); /* send hello */
207    return ECORE_CALLBACK_PASS_ON;
208 }
209
210 static Eina_Bool
211 _e_fm_ipc_cb_server_del(void *data __UNUSED__, int type __UNUSED__, void *event __UNUSED__)
212 {
213    /* quit now */
214    ecore_main_loop_quit();
215    return ECORE_CALLBACK_PASS_ON;
216 }
217
218 static void
219 _e_fm_ipc_monitor_start(int id, const char *path)
220 {
221    E_Fm_Task *task = malloc(sizeof(E_Fm_Task));
222
223    if (!task) return;
224
225    task->id = id;
226    task->type = E_FM_OP_MONITOR_START;
227    task->slave = NULL;
228    task->src = eina_stringshare_add(path);
229    task->dst = NULL;
230    task->rel = NULL;
231    task->rel_to = 0;
232    task->x = 0;
233    task->y = 0;
234
235    _e_fm_tasks = eina_list_append(_e_fm_tasks, task);
236
237    _e_fm_ipc_monitor_start_try(task);
238 }
239
240 static void
241 _e_fm_ipc_monitor_start_try(E_Fm_Task *task)
242 {
243    E_Dir *ed, *ped = NULL;
244    
245    DIR *dir;
246    Eina_List *l;
247    
248    /* look for any previous dir entries monitoring this dir */
249    EINA_LIST_FOREACH(_e_dirs, l, ed)
250      {
251         if ((ed->mon) && (!strcmp(ed->dir, task->src)))
252           {
253              /* found a previous dir - save it in ped */
254              ped = ed;
255              break;
256           }
257      }
258
259    /* open the dir to list */
260    dir = opendir(task->src);
261    if (!dir)
262      {
263         char buf[PATH_MAX + 4096];
264
265         snprintf(buf, sizeof(buf), "Cannot open directory '%s': %s.", task->src, strerror(errno));
266         _e_fm_ipc_client_send(task->id, E_FM_OP_ERROR_RETRY_ABORT, buf, strlen(buf) + 1);
267      }
268    else
269      {
270         Eina_List *files = NULL;
271         struct dirent *dp;
272         int dot_order = 0;
273         char buf[4096];
274         FILE *f;
275         
276         /* create a new dir entry */
277         ed = calloc(1, sizeof(E_Dir));
278         ed->id = task->id;
279         ed->dir = eina_stringshare_add(task->src);
280         if (!ped)
281           {
282              /* if no previous monitoring dir exists - this one 
283               * becomes the master monitor enty */
284              ed->mon = ecore_file_monitor_add(ed->dir, _e_fm_ipc_cb_file_monitor, ed);
285              ed->mon_ref = 1;
286           }
287         else
288           {
289              /* an existing monitor exists - ref it up */
290              ed->mon_real = ped;
291              ped->mon_ref++;
292           }
293         _e_dirs = eina_list_append(_e_dirs, ed);
294         
295         /* read everything except a .order, . and .. */
296         while ((dp = readdir(dir)))
297           {
298              if ((!strcmp(dp->d_name, ".")) || (!strcmp(dp->d_name, "..")))
299                continue;
300              if (!strcmp(dp->d_name, ".order")) 
301                {
302                   dot_order = 1;
303                   continue;
304                }
305              files = eina_list_append(files, strdup(dp->d_name));
306           }
307         closedir(dir);
308         /* if there was a .order - we need to parse it */
309         if (dot_order)
310           {
311              snprintf(buf, sizeof(buf), "%s/.order", task->src);
312              f = fopen(buf, "r");
313              if (f)
314                {
315                   Eina_List *f2 = NULL;
316                   int len;
317                   char *s;
318                   
319                   /* inset files in order if the existed in file 
320                    * list before */
321                   while (fgets(buf, sizeof(buf), f))
322                     {
323                        len = strlen(buf);
324                        if (len > 0) buf[len - 1] = 0;
325                        s = _e_str_list_remove(&files, buf);
326                        if (s) f2 = eina_list_append(f2, s);
327                     }
328                   fclose(f);
329                   /* append whats left */
330                   files = eina_list_merge(f2, files);
331                }
332           }
333         ed->fq = files;
334         /* FIXME: if .order file- load it, sort all items int it
335          * that are in files then just append whatever is left in
336          * alphabetical order
337          */
338         /* FIXME: maybe one day we can sort files here and handle
339          * .order file stuff here - but not today
340          */
341         /* note that we had a .order at all */
342         ed->dot_order = dot_order;
343         if (dot_order)
344           {
345              /* if we did - tell the E about this FIRST - it will
346               * decide what to do if it first sees a .order or not */
347              if (!strcmp(task->src, "/"))
348                snprintf(buf, sizeof(buf), "/.order");
349              else
350                snprintf(buf, sizeof(buf), "%s/.order", task->src);
351              if (eina_list_count(files) == 1)
352                _e_fm_ipc_file_add(ed, buf, 2);
353              else
354                _e_fm_ipc_file_add(ed, buf, 1);
355           }
356         /* send empty file - indicate empty dir */
357         if (!files) _e_fm_ipc_file_add(ed, "", 2);
358         /* and in an idler - list files, statting them etc. */
359         ed->idler = ecore_idler_add(_e_fm_ipc_cb_file_mon_list_idler, ed);
360         ed->sync_num = DEF_SYNC_NUM;
361      }
362 }
363
364 static void
365 _e_fm_ipc_monitor_end(int id, const char *path)
366 {
367    E_Fm_Task *task;
368    Eina_List *l;
369         E_Dir *ed;
370         
371    EINA_LIST_FOREACH(_e_dirs, l, ed)
372         /* look for the dire entry to stop monitoring */
373         if ((id == ed->id) && (!strcmp(ed->dir, path)))
374           {
375              /* if this is not the real monitoring node - unref the
376               * real one */
377              if (ed->mon_real)
378                {
379                   /* unref original monitor node */
380                   ed->mon_real->mon_ref--;
381                   if (ed->mon_real->mon_ref == 0)
382                     {
383                        /* original is at 0 ref - free it */
384                        _e_fm_ipc_dir_del(ed->mon_real);
385                        ed->mon_real = NULL;
386                     }
387                   /* free this node */
388                   _e_fm_ipc_dir_del(ed);
389                }
390              /* this is a core monitoring node - remove ref */
391              else
392                {
393                   ed->mon_ref--;
394                   /* we are the last ref - free */
395                   if (ed->mon_ref == 0) _e_fm_ipc_dir_del(ed);
396                }
397              /* remove from dirs list anyway */
398              _e_dirs = eina_list_remove_list(_e_dirs, l);
399              break;
400           }
401
402    task = _e_fm_ipc_task_get(id);
403    if (task) _e_fm_ipc_task_remove(task);
404 }
405
406 static E_Fm_Task *
407 _e_fm_ipc_task_get(int id)
408 {
409    Eina_List *l = _e_fm_ipc_task_node_get(id);
410
411    return (E_Fm_Task *)eina_list_data_get(l);
412 }
413
414 static Eina_List *
415 _e_fm_ipc_task_node_get(int id)
416 {
417    E_Fm_Task *task;
418    Eina_List *l;
419
420    EINA_LIST_FOREACH(_e_fm_tasks, l, task)
421         if (task->id == id)
422           return l;
423
424    return NULL;
425 }
426
427 static void
428 _e_fm_ipc_task_remove(E_Fm_Task *task)
429 {
430    Eina_List *l = _e_fm_ipc_task_node_get(task->id);
431
432    switch(task->type)
433      {
434       case E_FM_OP_MONITOR_START:
435            {
436               E_Dir ted;
437               
438               /* we can't open the dir - tell E the dir is deleted as
439                * * we can't look in it */
440               memset(&ted, 0, sizeof(E_Dir));
441               ted.id = task->id;
442               _e_fm_ipc_file_mon_dir_del(&ted, task->src);
443            }
444          break;
445       default:
446          break;
447      }
448
449    _e_fm_tasks = eina_list_remove_list(_e_fm_tasks, l);
450
451    if (task->src) eina_stringshare_del(task->src);
452    if (task->dst) eina_stringshare_del(task->dst);
453    if (task->rel) eina_stringshare_del(task->rel);
454
455    free(task);
456 }
457
458 static void
459 _e_fm_ipc_mkdir_try(E_Fm_Task *task)
460 {
461    char buf[PATH_MAX + 4096];
462
463    if (mkdir(task->src, S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH) < 0)
464      {
465         snprintf(buf, sizeof(buf), "Cannot make directory '%s': %s.", task->src, strerror(errno));
466         _e_fm_ipc_client_send(task->id, E_FM_OP_ERROR_RETRY_ABORT, buf, strlen(buf) + 1);
467      }
468    else
469      {
470         _e_fm_ipc_reorder(ecore_file_file_get(task->src), ecore_file_dir_get(task->src), task->rel, task->rel_to);
471         _e_fm_ipc_task_remove(task);
472      }
473 }
474
475 static void
476 _e_fm_ipc_mkdir(int id, const char *src, const char *rel, int rel_to __UNUSED__, int x, int y)
477 {
478    E_Fm_Task *task;
479
480    task = malloc(sizeof(E_Fm_Task));
481
482    task->id = id;
483    task->type = E_FM_OP_MKDIR;
484    task->slave = NULL;
485    task->src = eina_stringshare_add(src);
486    task->dst = NULL;
487    task->rel = eina_stringshare_add(rel);
488    task->x = x;
489    task->y = y;
490
491    _e_fm_tasks = eina_list_append(_e_fm_tasks, task);
492
493    _e_fm_ipc_mkdir_try(task);
494 }
495
496 static void
497 _e_fm_ipc_handle_error_response(int id, E_Fm_Op_Type type)
498 {
499    E_Fm_Task *task = _e_fm_ipc_task_get(id);
500    E_Fm_Slave *slave = NULL;
501
502    if (!task)
503      {
504         slave = _e_fm_ipc_slave_get(id);
505         if (slave) _e_fm_ipc_slave_send(slave, type, NULL, 0);
506         return;
507      }
508
509    if (type == E_FM_OP_ERROR_RESPONSE_ABORT)
510      {
511         _e_fm_ipc_task_remove(task);
512      }
513    else if (type == E_FM_OP_ERROR_RESPONSE_RETRY)
514      {
515         switch(task->type)
516           {
517            case E_FM_OP_MKDIR:
518               _e_fm_ipc_mkdir_try(task);
519               break;
520
521            case E_FM_OP_MONITOR_START:
522               _e_fm_ipc_monitor_start_try(task);
523            default:
524               break;
525           }
526      }
527 }
528
529
530 static Eina_Bool
531 _e_fm_ipc_cb_server_data(void *data __UNUSED__, int type __UNUSED__, void *event)
532 {
533    Ecore_Ipc_Event_Server_Data *e;
534    
535    e = event;
536    if (e->major != 6/*E_IPC_DOMAIN_FM*/) return ECORE_CALLBACK_PASS_ON;
537    switch (e->minor)
538      {
539       case E_FM_OP_MONITOR_START: /* monitor dir (and implicitly list) */
540           {
541              _e_fm_ipc_monitor_start(e->ref, e->data);
542           }
543         break;
544       case E_FM_OP_MONITOR_END: /* monitor dir end */
545           {
546 //           printf("End listing directory: %s\n", e->data);
547              _e_fm_ipc_monitor_end(e->ref, e->data);
548           }
549         break;
550       case E_FM_OP_REMOVE: /* fop delete file/dir */
551           {
552              _e_fm_ipc_slave_run(E_FM_OP_REMOVE, (const char *)e->data, e->ref);
553           }
554         break;
555       case E_FM_OP_TRASH: /* fop trash file/dir */
556           {
557              E_Fop *fop;
558              
559              fop = calloc(1, sizeof(E_Fop));
560              if (fop)
561                {
562                   fop->id = e->ref;
563                   fop->src = eina_stringshare_add(e->data);
564                   _e_fops = eina_list_append(_e_fops, fop);
565                   fop->idler = ecore_idler_add(_e_fm_ipc_cb_fop_trash_idler, fop);
566                }
567           }
568         break;
569       case E_FM_OP_MOVE: /* fop mv file/dir */
570           {
571              _e_fm_ipc_slave_run(E_FM_OP_MOVE, (const char *)e->data, e->ref);
572           }
573         break;
574       case E_FM_OP_COPY: /* fop cp file/dir */
575           {
576              _e_fm_ipc_slave_run(E_FM_OP_COPY, (const char *)e->data, e->ref);
577           }
578         break;
579       case E_FM_OP_SYMLINK: /* fop ln -s */
580           {
581              _e_fm_ipc_slave_run(E_FM_OP_SYMLINK, (const char *)e->data, e->ref);
582           }
583         break;
584       case E_FM_OP_MKDIR: /* fop mkdir */
585           {
586              const char *src, *rel;
587              int rel_to, x, y;
588              
589              src = e->data;
590              rel = src + strlen(src) + 1;
591              memcpy(&rel_to, rel + strlen(rel) + 1, sizeof(int));
592              memcpy(&x, rel + strlen(rel) + 1 + sizeof(int), sizeof(int));
593              memcpy(&y, rel + strlen(rel) + 1 + sizeof(int), sizeof(int));
594
595              _e_fm_ipc_mkdir(e->ref, src, rel, rel_to, x, y);
596           }
597         break;
598       case E_FM_OP_MOUNT: /* mount udi mountpoint */
599           {
600              E_Volume *v;
601              const char *udi, *mountpoint;
602              
603              udi = e->data;          
604              mountpoint = udi + strlen(udi) + 1;
605              v = e_volume_find(udi);
606 //             printf("REQ M %p (find from %s -> %s)\n", v, udi, mountpoint); fflush(stdout);
607              if (v)
608                {
609                   if (mountpoint[0])
610                     {
611                        if (v->mount_point) eina_stringshare_del(v->mount_point);
612                        v->mount_point = eina_stringshare_add(mountpoint);
613                     }
614                   e_volume_mount(v);
615                }
616           }
617         break;
618       case E_FM_OP_UNMOUNT:/* unmount udi */
619           {
620              E_Volume *v;
621              const char *udi;
622              
623              udi = e->data;
624              v = e_volume_find(udi);
625              if (v)
626                {
627 //                printf("REQ UM\n"); fflush(stdout);
628                   e_volume_unmount(v);
629                }
630           }
631         break;
632       case E_FM_OP_EJECT:/* eject udi */
633           {
634              E_Volume *v;
635              const char *udi;
636              
637              udi = e->data;
638              v = e_volume_find(udi);
639              if (v)
640                e_volume_eject(v);
641           }
642         break;
643       case E_FM_OP_QUIT: /* quit */
644         ecore_main_loop_quit();
645         break;
646       case E_FM_OP_MONITOR_SYNC: /* mon list sync */
647           {
648              Eina_List *l;
649              E_Dir *ed;
650              double stime;
651              
652              EINA_LIST_FOREACH(_e_dirs, l, ed)
653                {
654                   if (ed->fq)
655                     {
656                        if (ed->sync == e->response)
657                          {
658                             stime = ecore_time_get() - ed->sync_time;
659                             /* try keep round trips to round trip tolerance */
660                             if 
661                               (stime < (DEF_ROUND_TRIP - DEF_ROUND_TRIP_TOLERANCE))
662                               ed->sync_num += 1;
663                             else if
664                               (stime > (DEF_ROUND_TRIP + DEF_ROUND_TRIP_TOLERANCE))
665                               ed->sync_num -= 1;
666                             /* always sync at least 1 file */
667                             if (ed->sync_num < 1) ed->sync_num = 1;
668                             ed->idler = ecore_idler_add(_e_fm_ipc_cb_file_mon_list_idler, ed);
669                             break;
670                          }
671                     }
672                }
673           }
674         break;
675       case E_FM_OP_ABORT: // abort copy/move/delete operation by user
676           {
677              E_Fm_Slave *slave = _e_fm_ipc_slave_get(e->ref);
678              if (slave)
679                 _e_fm_ipc_slave_send(slave, e->minor, NULL, 0);
680           }
681         break; 
682       case E_FM_OP_ERROR_RESPONSE_IGNORE_THIS:
683       case E_FM_OP_ERROR_RESPONSE_IGNORE_ALL:
684       case E_FM_OP_ERROR_RESPONSE_ABORT:
685       case E_FM_OP_ERROR_RESPONSE_RETRY:
686           {
687              _e_fm_ipc_handle_error_response(e->ref, e->minor);
688           }
689         break;
690       case E_FM_OP_OVERWRITE_RESPONSE_NO:
691       case E_FM_OP_OVERWRITE_RESPONSE_NO_ALL:
692       case E_FM_OP_OVERWRITE_RESPONSE_YES:
693       case E_FM_OP_OVERWRITE_RESPONSE_YES_ALL:
694           {
695              _e_fm_ipc_slave_send(_e_fm_ipc_slave_get(e->ref), e->minor, NULL, 0);
696           }
697         break;
698       case E_FM_OP_REORDER:
699           {
700              const char *file, *dst, *relative;
701              int after;
702              char *p = e->data;
703
704              file = p;
705              p += strlen(file) + 1;
706
707              dst = p;
708              p += strlen(dst) + 1;
709
710              relative = p;
711              p += strlen(relative) + 1;
712
713              after = *((int *)p);
714              
715              _e_fm_ipc_reorder(file, dst, relative, after);
716           }
717         break;
718       default:
719         break;
720      }
721    /* always send back an "OK" for each request so e can basically keep a
722     * count of outstanding requests - maybe for balancing between fm
723     * slaves later. ref_to is set to the the ref id in the request to 
724     * allow for async handling later */
725    ecore_ipc_server_send(_e_fm_ipc_server,
726                          6/*E_IPC_DOMAIN_FM*/,
727                          E_FM_OP_OK,
728                          0, e->ref, 0, NULL, 0);
729    return ECORE_CALLBACK_PASS_ON;
730 }
731
732 static int _e_fm_ipc_client_send(int id, E_Fm_Op_Type type, void *data, int size)
733 {
734    return ecore_ipc_server_send(_e_fm_ipc_server,
735          6/*E_IPC_DOMAIN_FM*/,
736          type,
737          id, 0, 0, data, size);
738 }
739
740 static int _e_fm_ipc_slave_run(E_Fm_Op_Type type, const char *args, int id)
741 {
742    E_Fm_Slave *slave;
743    const char *command;
744
745    slave = malloc(sizeof(E_Fm_Slave));
746
747    if (!slave) return 0;
748              
749    command = eina_stringshare_add(_e_fm_ipc_prepare_command(type, args));
750
751    slave->id = id;
752    slave->exe = ecore_exe_pipe_run(command, ECORE_EXE_PIPE_WRITE | ECORE_EXE_PIPE_READ | ECORE_EXE_PIPE_ERROR, slave );
753 //   printf("EFM command: %s\n", command);
754    
755    eina_stringshare_del(command);
756
757    _e_fm_ipc_slaves = eina_list_append(_e_fm_ipc_slaves, slave);
758
759    return (!!slave->exe);
760 }
761
762 static E_Fm_Slave *_e_fm_ipc_slave_get(int id)
763 {
764    Eina_List *l;
765    E_Fm_Slave *slave;
766
767    EINA_LIST_FOREACH(_e_fm_ipc_slaves, l, slave)
768      {
769         if (slave->id == id)
770           return slave;
771      }
772
773    return NULL;
774 }
775
776 static int _e_fm_ipc_slave_send(E_Fm_Slave *slave, E_Fm_Op_Type type, void *data, int size)
777 {
778    char *sdata;
779    int ssize;
780    int magic = E_FM_OP_MAGIC;
781    int result;
782
783    ssize = 3 * sizeof(int) + size;
784    sdata = malloc(ssize);
785
786    if (!sdata) return 0;
787
788    memcpy(sdata,                                      &magic, sizeof(int));
789    memcpy(sdata + sizeof(int),                        &type, sizeof(E_Fm_Op_Type));
790    memcpy(sdata + sizeof(int) + sizeof(E_Fm_Op_Type), &size, sizeof(int));
791
792    memcpy(sdata + 2 * sizeof(int) + sizeof(E_Fm_Op_Type), data, size);
793
794    result = ecore_exe_send(slave->exe, sdata, ssize);
795
796    free(sdata);
797
798    return result;
799 }
800
801 Eina_Bool
802 _e_fm_ipc_slave_data_cb(void *data __UNUSED__, int type __UNUSED__, void *event)
803 {
804    Ecore_Exe_Event_Data *e = event;
805    E_Fm_Slave *slave;
806    int magic, id, size;
807    char *sdata;
808    int ssize;
809
810    if (!e) return ECORE_CALLBACK_PASS_ON;
811
812    slave = ecore_exe_data_get(e->exe);
813    if (!slave) return ECORE_CALLBACK_RENEW;
814
815    sdata = e->data;
816    ssize = e->size;
817
818    while ((unsigned int)ssize >= 3 * sizeof(int))
819      {
820         memcpy(&magic, sdata,                             sizeof(int));
821         memcpy(&id,    sdata + sizeof(int),               sizeof(int));
822         memcpy(&size,  sdata + sizeof(int) + sizeof(int), sizeof(int));
823
824         if (magic != E_FM_OP_MAGIC)
825           {
826              printf("%s:%s(%d) Wrong magic number from slave #%d. ", __FILE__, __FUNCTION__, __LINE__, slave->id);
827              break;
828           }
829
830         sdata += 3 * sizeof(int);
831         ssize -= 3 * sizeof(int);
832
833         if (id == E_FM_OP_OVERWRITE)
834           {
835              _e_fm_ipc_client_send(slave->id, E_FM_OP_OVERWRITE, sdata, size);
836              printf("%s:%s(%d) Overwrite sent to client from slave #%d.\n", __FILE__, __FUNCTION__, __LINE__, slave->id);
837           }
838         else if (id == E_FM_OP_ERROR)
839           {
840              _e_fm_ipc_client_send(slave->id, E_FM_OP_ERROR, sdata, size);
841           }
842         else if (id == E_FM_OP_PROGRESS)
843           {
844              _e_fm_ipc_client_send(slave->id, E_FM_OP_PROGRESS, sdata, size);
845           }
846
847         sdata += size;
848         ssize -= size;
849      }
850
851    return ECORE_CALLBACK_PASS_ON;
852 }
853
854 Eina_Bool
855 _e_fm_ipc_slave_error_cb(void *data __UNUSED__, int type __UNUSED__, void *event)
856 {
857    Ecore_Exe_Event_Data *e = event;
858    E_Fm_Slave *slave;
859
860    if (!e) return 1;
861
862    slave = ecore_exe_data_get(e->exe);
863    if (!slave) return ECORE_CALLBACK_RENEW;
864
865    printf("EFM: Data from STDERR of slave #%d: %.*s", slave->id, e->size, (char *)e->data);
866
867    return 1;
868 }
869
870 Eina_Bool
871 _e_fm_ipc_slave_del_cb(void *data __UNUSED__, int type __UNUSED__, void *event)
872 {
873    Ecore_Exe_Event_Del *e = event;
874    E_Fm_Slave *slave;
875
876    if (!e) return 1;
877
878    slave = ecore_exe_data_get(e->exe);
879    if (!slave) return 1;
880    _e_fm_ipc_client_send(slave->id, E_FM_OP_QUIT, NULL, 0);
881
882    _e_fm_ipc_slaves = eina_list_remove(_e_fm_ipc_slaves, (void *)slave);
883    free(slave);
884
885    return 1;
886 }
887
888 static void
889 _e_fm_ipc_cb_file_monitor(void *data __UNUSED__, Ecore_File_Monitor *em __UNUSED__, Ecore_File_Event event, const char *path)
890 {
891    E_Dir *ed;
892    char *dir, *rp, *drp;
893    Eina_List *l;
894
895    dir = ecore_file_dir_get(path);
896    /* FIXME: get no create events if dir is empty */
897    if ((event == ECORE_FILE_EVENT_CREATED_FILE) ||
898        (event == ECORE_FILE_EVENT_CREATED_DIRECTORY))
899      {
900         rp = ecore_file_realpath(dir);
901         EINA_LIST_FOREACH(_e_dirs, l, ed)
902           {
903              drp = ecore_file_realpath(ed->dir);
904              if (drp)
905                {
906                   if (!strcmp(rp, drp))
907                     _e_fm_ipc_file_add(ed, path, 0);
908                   free(drp);
909                }
910           }
911         free(rp);
912      }
913    else if ((event == ECORE_FILE_EVENT_DELETED_FILE) ||
914             (event == ECORE_FILE_EVENT_DELETED_DIRECTORY))
915      {
916         rp = ecore_file_realpath(dir);
917         EINA_LIST_FOREACH(_e_dirs, l, ed)
918           {
919              drp = ecore_file_realpath(ed->dir);
920              if (drp)
921                {
922                   if (!strcmp(rp, drp))
923                     _e_fm_ipc_file_del(ed, path);
924                   free(drp);
925                }
926           }
927         free(rp);
928      }
929    else if (event == ECORE_FILE_EVENT_MODIFIED)
930      {
931         rp = ecore_file_realpath(dir);
932         EINA_LIST_FOREACH(_e_dirs, l, ed)
933           {
934              drp = ecore_file_realpath(ed->dir);
935              if (drp)
936                {
937                   if (!strcmp(rp, drp))
938                     _e_fm_ipc_file_mod(ed, path);
939                   free(drp);
940                }
941           }
942         free(rp);
943      }
944    else if (event == ECORE_FILE_EVENT_DELETED_SELF)
945      {
946         rp = ecore_file_realpath(path);
947         EINA_LIST_FOREACH(_e_dirs, l, ed)
948           {
949              drp = ecore_file_realpath(ed->dir);
950              if (drp)
951                {
952                   if (!strcmp(rp, drp))
953                     _e_fm_ipc_file_mon_dir_del(ed, path);
954                   free(drp);
955                }
956           }
957         free(rp);
958      }
959    free(dir);
960 }
961
962 static Eina_Bool
963 _e_fm_ipc_cb_recent_clean(void *data)
964 {
965    E_Dir *ed;
966    Eina_List *l, *pl;
967    E_Mod *m;
968    double t_now;
969    
970    ed = data;
971    ed->cleaning = 1;
972    t_now = ecore_time_unix_get();
973    EINA_LIST_FOREACH_SAFE(ed->recent_mods, pl, l, m)
974         if ((m->mod) && ((t_now - m->timestamp) >= DEF_MOD_BACKOFF))
975           {
976              ed->recent_mods = eina_list_remove_list(ed->recent_mods, pl);
977              if (!m->done) _e_fm_ipc_file_add_mod(ed, m->path, 5, 0);
978              eina_stringshare_del(m->path);
979              free(m);
980           }
981    ed->cleaning = 0;
982    if (ed->recent_mods) return ECORE_CALLBACK_RENEW;
983    ed->recent_clean = NULL;
984    return ECORE_CALLBACK_CANCEL;
985 }
986                                
987
988 static void
989 _e_fm_ipc_file_add_mod(E_Dir *ed, const char *path, E_Fm_Op_Type op, int listing)
990 {
991    struct stat st;
992    char *lnk = NULL, *rlnk = NULL;
993    int broken_lnk = 0;
994    int bsz = 0;
995    unsigned char *p, buf
996      /* file add/change format is as follows:
997       * 
998       * stat_info[stat size] + broken_link[1] + path[n]\0 + lnk[n]\0 + rlnk[n]\0 */
999      [sizeof(struct stat) + 1 + 4096 + 4096 + 4096];
1000
1001    /* FIXME: handle BACKOFF */
1002    if ((!listing) && (op == E_FM_OP_FILE_CHANGE) && (!ed->cleaning)) /* 5 == mod */
1003      {
1004         Eina_List *l;
1005         E_Mod *m;
1006         double t_now;
1007         int skip = 0;
1008         
1009         t_now = ecore_time_unix_get();
1010         EINA_LIST_FOREACH(ed->recent_mods, l, m)
1011           {
1012              if ((m->mod) && (!strcmp(m->path, path)))
1013                {
1014                   if ((t_now - m->timestamp) < DEF_MOD_BACKOFF)
1015                     {
1016                        m->done = 0;
1017                        skip = 1;
1018                     }
1019                }
1020           }
1021         if (!skip)
1022           {
1023              m = calloc(1, sizeof(E_Mod));
1024              m->path = eina_stringshare_add(path);
1025              m->mod = 1;
1026              m->done = 1;
1027              m->timestamp = t_now;
1028              ed->recent_mods = eina_list_append(ed->recent_mods, m);
1029           }
1030         if ((!ed->recent_clean) && (ed->recent_mods))
1031           ed->recent_clean = ecore_timer_add(DEF_MOD_BACKOFF, _e_fm_ipc_cb_recent_clean, ed);
1032         if (skip)
1033           {
1034 //           printf("SKIP MOD %s %3.3f\n", path, t_now);
1035              return;
1036           }
1037      }
1038 //   printf("MOD %s %3.3f\n", path, ecore_time_unix_get());
1039    lnk = ecore_file_readlink(path);
1040    memset(&st, 0, sizeof(struct stat));
1041    if (stat(path, &st) == -1)
1042      {
1043         if ((path[0] == 0) || (lnk)) broken_lnk = 1;
1044         else return;
1045      }
1046    if ((lnk) && (lnk[0] != '/')) rlnk = ecore_file_realpath(path);
1047    else if (lnk) rlnk = strdup(lnk);
1048    if (!lnk) lnk = strdup("");
1049    if (!rlnk) rlnk = strdup("");
1050
1051    p = buf;
1052    /* NOTE: i am NOT converting this data to portable arch/os independent
1053     * format. i am ASSUMING e_fm_main and e are local and built together
1054     * and thus this will work. if this ever changes this here needs to
1055     * change */
1056    memcpy(buf, &st, sizeof(struct stat));
1057    p += sizeof(struct stat);
1058    
1059    p[0] = broken_lnk;
1060    p += 1;
1061    
1062    strcpy((char *)p, path);
1063    p += strlen(path) + 1;
1064    
1065    strcpy((char *)p, lnk);
1066    p += strlen(lnk) + 1;
1067    
1068    strcpy((char *)p, rlnk);
1069    p += strlen(rlnk) + 1;
1070    
1071    bsz = p - buf;
1072    ecore_ipc_server_send(_e_fm_ipc_server, 6/*E_IPC_DOMAIN_FM*/, op, 0, ed->id,
1073                          listing, buf, bsz);
1074    if (lnk) free(lnk);
1075    if (rlnk) free(rlnk);
1076 }
1077
1078 static void
1079 _e_fm_ipc_file_add(E_Dir *ed, const char *path, int listing)
1080 {
1081    if (!listing)
1082      {
1083         /* FIXME: handle BACKOFF */
1084      }
1085    _e_fm_ipc_file_add_mod(ed, path, E_FM_OP_FILE_ADD, listing);/*file add*/
1086 }
1087
1088 static void
1089 _e_fm_ipc_file_del(E_Dir *ed, const char *path)
1090 {
1091      {
1092         /* FIXME: handle BACKOFF */
1093      }
1094    ecore_ipc_server_send(_e_fm_ipc_server,
1095                          6/*E_IPC_DOMAIN_FM*/,
1096                          E_FM_OP_FILE_DEL,
1097                          0, ed->id, 0, (void *)path, strlen(path) + 1);
1098 }
1099
1100 static void
1101 _e_fm_ipc_file_mod(E_Dir *ed, const char *path)
1102 {
1103      {
1104         /* FIXME: handle BACKOFF */
1105      }
1106    _e_fm_ipc_file_add_mod(ed, path, E_FM_OP_FILE_CHANGE, 0);/*file change*/
1107 }
1108
1109 static void
1110 _e_fm_ipc_file_mon_dir_del(E_Dir *ed, const char *path)
1111 {
1112    ecore_ipc_server_send(_e_fm_ipc_server,
1113                          6/*E_IPC_DOMAIN_FM*/,
1114                          E_FM_OP_MONITOR_END,
1115                          0, ed->id, 0, (void *)path, strlen(path) + 1);
1116 }
1117
1118 static void
1119 _e_fm_ipc_file_mon_list_sync(E_Dir *ed)
1120 {
1121    _e_sync_num++;
1122    if (_e_sync_num == 0) _e_sync_num = 1;
1123    ed->sync = _e_sync_num;
1124    ed->sync_time = ecore_time_get();
1125    ecore_ipc_server_send(_e_fm_ipc_server,
1126                          6/*E_IPC_DOMAIN_FM*/,
1127                          E_FM_OP_MONITOR_SYNC,
1128                          0, ed->id, ed->sync, NULL, 0);
1129 }
1130
1131 static Eina_Bool
1132 _e_fm_ipc_cb_file_mon_list_idler(void *data)
1133 {
1134    E_Dir *ed;
1135    int n = 0;
1136    char *file, buf[4096];
1137    
1138    ed = data;
1139    /* FIXME: spool off files in idlers and handle sync req's */
1140    while (ed->fq)
1141      {
1142         file = eina_list_data_get(ed->fq);
1143         if (!((ed->dot_order) && (!strcmp(file, ".order"))))
1144           {
1145              if (!strcmp(ed->dir, "/"))
1146                snprintf(buf, sizeof(buf), "/%s", file);
1147              else
1148                snprintf(buf, sizeof(buf), "%s/%s", ed->dir, file);
1149              _e_fm_ipc_file_add(ed, buf, 1);
1150           }
1151         free(file);
1152         ed->fq = eina_list_remove_list(ed->fq, ed->fq);
1153         n++;
1154         if (n == ed->sync_num)
1155           {
1156              _e_fm_ipc_file_mon_list_sync(ed);
1157              ed->idler = NULL;
1158              if (!ed->fq) _e_fm_ipc_file_add(ed, "", 2);
1159              return 0;
1160           }
1161      }
1162    ed->sync_num = DEF_SYNC_NUM;
1163    ed->sync = 0;
1164    ed->sync_time = 0.0;
1165    ed->idler = NULL;
1166    if (!ed->fq) _e_fm_ipc_file_add(ed, "", 2);
1167    return ECORE_CALLBACK_CANCEL;
1168 }
1169
1170 static Eina_Bool
1171 _e_fm_ipc_cb_fop_trash_idler(void *data)
1172 {
1173    E_Fop *fop = NULL;
1174    FILE *info = NULL;
1175    const char *trash_dir = NULL;
1176    const char *filename = NULL;
1177    const char *escname = NULL;
1178    const char *dest = NULL;
1179    char buf[4096];
1180    unsigned int i = 0;
1181    struct tm *lt;
1182    time_t t;
1183
1184    /* FIXME: For now, this will only implement 'home trash' 
1185     * Later, implement mount/remote/removable device trash, if wanted. */
1186
1187    fop = (E_Fop *)data;
1188    if (!fop) return 0;
1189
1190    /* Check that 'home trash' and subsequesnt dirs exists, create if not */
1191    snprintf(buf, sizeof(buf), "%s/Trash", efreet_data_home_get());
1192    trash_dir = eina_stringshare_add(buf);
1193    snprintf(buf, sizeof(buf), "%s/files", trash_dir);
1194    if (!ecore_file_mkpath(buf)) return 0;
1195    snprintf(buf, sizeof(buf), "%s/info", trash_dir);
1196    if (!ecore_file_mkpath(buf)) return 0;
1197
1198    filename = eina_stringshare_add(strrchr(fop->src, '/'));
1199    escname = ecore_file_escape_name(filename);
1200    eina_stringshare_del(filename);
1201
1202    /* Find path for info file. Pointer address is part of the filename to
1203     * alleviate some of the looping in case of multiple filenames with the
1204     * same name. Also use the name of the file to help */
1205    do 
1206      {
1207         snprintf(buf, sizeof(buf), "%s/file%s.%p.%u", trash_dir, escname, 
1208                  fop, i++);
1209      }
1210    while (ecore_file_exists(buf));
1211    dest = eina_stringshare_add(buf);
1212    
1213    /* Try to move the file */
1214    if (rename(fop->src, dest)) 
1215      {
1216         if (errno == EXDEV) 
1217           {
1218              /* Move failed. Spec says delete files that can't be trashed */
1219              ecore_file_unlink(fop->src);
1220              return ECORE_CALLBACK_CANCEL;
1221           }
1222      }
1223
1224    /* Move worked. Create info file */
1225    snprintf(buf, sizeof(buf), "%s/info%s.%p.%u.trashinfo", trash_dir, 
1226             escname, fop, --i);
1227    info = fopen(buf, "w");
1228    if (info) 
1229      {
1230         t = time(NULL);
1231         lt = localtime(&t);
1232
1233         /* Insert info for trashed file */
1234         fprintf(info, 
1235                 "[Trash Info]\nPath=%s\nDeletionDate=%04u%02u%02uT%02u:%02u:%02u",
1236                 fop->src, lt->tm_year+1900, lt->tm_mon+1, lt->tm_mday,
1237                 lt->tm_hour, lt->tm_min, lt->tm_sec);
1238         fclose(info);
1239      }
1240    else
1241      /* Could not create info file. Spec says to put orig file back */
1242      rename(dest, fop->src);
1243
1244    if (dest) eina_stringshare_del(dest);
1245    if (trash_dir) eina_stringshare_del(trash_dir);
1246    eina_stringshare_del(fop->src);
1247    eina_stringshare_del(fop->dst);
1248    free(fop);
1249    _e_fops = eina_list_remove(_e_fops, fop);
1250    return ECORE_CALLBACK_CANCEL;
1251 }
1252
1253 static char *
1254 _e_str_list_remove(Eina_List **list, char *str)
1255 {
1256    Eina_List *l;
1257         char *s;
1258         
1259    EINA_LIST_FOREACH(*list, l, s)
1260         if (!strcmp(s, str))
1261           {
1262              *list = eina_list_remove_list(*list, l);
1263              return s;
1264           }
1265
1266    return NULL;
1267 }
1268
1269 static void
1270 _e_fm_ipc_reorder(const char *file, const char *dst, const char *relative, int after)
1271 {
1272    char buffer[PATH_MAX];
1273    char order[PATH_MAX];
1274
1275    if (!file || !dst || !relative) return;
1276    if (after != 0 && after != 1 && after != 2) return;
1277 //   printf("%s:%s(%d) Reorder:\n\tfile = %s\n\tdst = %s\n\trelative = %s\n\tafter = %d\n", __FILE__, __FUNCTION__, __LINE__, file, dst, relative, after);
1278
1279    snprintf(order, sizeof(order), "%s/.order", dst);
1280    if (ecore_file_exists(order))
1281      {
1282         FILE *forder;
1283         Eina_List *files = NULL, *l;
1284         char *str;
1285         
1286         forder = fopen(order, "r");
1287         if (forder)
1288           {
1289              int len;
1290              
1291              /* inset files in order if the existed in file 
1292               * list before */
1293              while (fgets(buffer, sizeof(buffer), forder))
1294                {
1295                   len = strlen(buffer);
1296                   if (len > 0) buffer[len - 1] = 0;
1297                   files = eina_list_append(files, strdup(buffer));
1298                }
1299              fclose(forder);
1300           }
1301         /* remove dest file from .order - if there */
1302         EINA_LIST_FOREACH(files, l, str)
1303           if (!strcmp(str, file))
1304                {
1305                free(str);
1306                   files = eina_list_remove_list(files, l);
1307                   break;
1308                }
1309         /* now insert dest into list or replace entry */
1310         EINA_LIST_FOREACH(files, l, str)
1311           {
1312              if (!strcmp(str, relative))
1313                {
1314                   if (after == 2) /* replace */
1315                     {
1316                        free(str);
1317                        l->data = strdup(file);
1318                     }
1319                   else if (after == 0) /* before */
1320                     {
1321                        files = eina_list_prepend_relative_list(files, strdup(file), l);
1322                     }
1323                   else if (after == 1) /* after */
1324                     {
1325                        files = eina_list_append_relative_list(files, strdup(file), l);
1326                     }
1327                   break;
1328                }
1329           }
1330
1331         forder = fopen(order, "w");
1332         if (forder)
1333           {
1334              EINA_LIST_FREE(files, str)
1335                {
1336                   fprintf(forder, "%s\n", str);
1337                   free(str);
1338                }
1339              fclose(forder);
1340           }
1341      }
1342 }
1343
1344 static void
1345 _e_fm_ipc_dir_del(E_Dir *ed)
1346 {
1347    void *data;
1348    E_Mod *m;
1349
1350    eina_stringshare_del(ed->dir);
1351    if (ed->idler) ecore_idler_del(ed->idler);
1352    if (ed->recent_clean)
1353      ecore_timer_del(ed->recent_clean);
1354    EINA_LIST_FREE(ed->recent_mods, m)
1355      {
1356         eina_stringshare_del(m->path);
1357         free(m);
1358      }
1359    EINA_LIST_FREE(ed->fq, data)
1360      free(data);
1361    free(ed);
1362 }
1363
1364 static const char *
1365 _e_fm_ipc_prepare_command(E_Fm_Op_Type type, const char *args)
1366 {
1367    char *buffer;
1368    unsigned int length = 0;
1369    char command[4];
1370
1371    if (type == E_FM_OP_MOVE)
1372      strcpy(command, "mv");
1373    else if (type == E_FM_OP_REMOVE)
1374      strcpy(command, "rm");
1375    else if (type == E_FM_OP_COPY)
1376      strcpy(command, "cp");
1377    else if (type == E_FM_OP_SYMLINK)
1378      strcpy(command, "lns");
1379    else
1380      strcpy(command, "???");
1381
1382    length = 256 + strlen(getenv("E_LIB_DIR")) + strlen(args);
1383    buffer = malloc(length);
1384    snprintf(buffer, length, 
1385                      "%s/enlightenment/utils/enlightenment_fm_op %s %s", 
1386                      getenv("E_LIB_DIR"), command, args);
1387
1388    return buffer;
1389 }