1 #ifdef HAVE_CONFIG_H
2 # include <config.h>
3 #endif
4 
5 #include <Eina.h>
6 #include <Ecore.h>
7 #include <Ecore_File.h>
8 #include <Eio.h>
9 #include <Eet.h>
10 #include "efreetd.h"
11 #include "efreetd_ipc.h"
12 
13 #include "Efreet.h"
14 #define EFREET_MODULE_LOG_DOM efreetd_log_dom
15 #include "efreet_private.h"
16 #include "efreetd_cache.h"
17 
18 #include <sys/types.h>
19 #include <sys/stat.h>
20 #include <unistd.h>
21 
22 extern FILE *efreetd_log_file;
23 
24 static Eina_Hash *icon_change_monitors = NULL;
25 static Eina_Hash *icon_change_monitors_mon = NULL;
26 static Eina_Hash *desktop_change_monitors = NULL;
27 static Eina_Hash *desktop_change_monitors_mon = NULL;
28 
29 static Ecore_Event_Handler *cache_exe_del_handler = NULL;
30 static Ecore_Event_Handler *cache_exe_data_handler = NULL;
31 static Ecore_Exe           *icon_cache_exe = NULL;
32 static Ecore_Exe           *desktop_cache_exe = NULL;
33 static Ecore_Timer         *icon_cache_timer = NULL;
34 static Ecore_Timer         *desktop_cache_timer = NULL;
35 static Eina_Prefix         *pfx = NULL;
36 
37 static Eina_Bool  desktop_exists = EINA_FALSE;
38 
39 static Eina_List *desktop_system_dirs = NULL;
40 static Eina_List *desktop_extra_dirs = NULL;
41 static Eina_List *icon_extra_dirs = NULL;
42 static Eina_List *icon_exts = NULL;
43 static Eina_Bool  icon_flush = EINA_FALSE;
44 
45 static Eina_Bool desktop_queue = EINA_FALSE;
46 static Eina_Bool icon_queue = EINA_FALSE;
47 
48 static Eina_List *_handlers = NULL;
49 
50 static void icon_changes_listen(void);
51 static void desktop_changes_listen(void);
52 
53 /* internal */
54 typedef struct _Subdir_Cache Subdir_Cache;
55 typedef struct _Subdir_Cache_Dir Subdir_Cache_Dir;
56 
57 struct _Subdir_Cache
58 {
59    Eina_Hash *dirs;
60 };
61 
62 struct _Subdir_Cache_Dir
63 {
64    unsigned long long dev;
65    unsigned long long ino;
66    unsigned long long mode;
67    unsigned long long uid;
68    unsigned long long gid;
69    unsigned long long size;
70    unsigned long long mtim;
71    unsigned long long ctim;
72    const char **dirs;
73    unsigned int dirs_count;
74 };
75 
76 static Eet_Data_Descriptor *subdir_edd = NULL;
77 static Eet_Data_Descriptor *subdir_dir_edd = NULL;
78 static Subdir_Cache        *subdir_cache = NULL;
79 static Eina_Bool            subdir_need_save = EINA_FALSE;
80 
81 static Eina_Hash *mime_monitors = NULL;
82 static Eina_Hash *mime_monitors_mon = NULL;
83 static Ecore_Timer *mime_update_timer = NULL;
84 static Ecore_Exe *mime_cache_exe = NULL;
85 
86 static void mime_cache_init(void);
87 static void mime_cache_shutdown(void);
88 static Eina_Bool mime_update_cache_cb(void *data EINA_UNUSED);
89 
90 static void
subdir_cache_dir_free(Subdir_Cache_Dir * cd)91 subdir_cache_dir_free(Subdir_Cache_Dir *cd)
92 {
93    unsigned int i;
94    if (!cd) return;
95    if (cd->dirs)
96      {
97         for (i = 0; i < cd->dirs_count; i++)
98           eina_stringshare_del(cd->dirs[i]);
99         free(cd->dirs);
100      }
101    free(cd);
102 }
103 
104 static void *
subdir_cache_hash_add(void * hash,const char * key,void * data)105 subdir_cache_hash_add(void *hash, const char *key, void *data)
106 {
107    if (!hash) hash = eina_hash_string_superfast_new(EINA_FREE_CB(subdir_cache_dir_free));
108    if (!hash) return NULL;
109    eina_hash_add(hash, key, data);
110    return hash;
111 }
112 
113 static void
subdir_cache_init(void)114 subdir_cache_init(void)
115 {
116    Eet_Data_Descriptor_Class eddc;
117    Eet_File *ef;
118    Eina_Strbuf *buf = eina_strbuf_new();
119    if (!buf) return;
120 
121    // set up data codecs for subdirs in memory
122    eet_eina_stream_data_descriptor_class_set(&eddc, sizeof(Subdir_Cache_Dir), "D", sizeof(Subdir_Cache_Dir));
123    EET_EINA_FILE_DATA_DESCRIPTOR_CLASS_SET(&eddc, Subdir_Cache_Dir);
124    subdir_dir_edd = eet_data_descriptor_stream_new(&eddc);
125    EET_DATA_DESCRIPTOR_ADD_BASIC(subdir_dir_edd, Subdir_Cache_Dir, "0", dev, EET_T_ULONG_LONG);
126    EET_DATA_DESCRIPTOR_ADD_BASIC(subdir_dir_edd, Subdir_Cache_Dir, "1", ino, EET_T_ULONG_LONG);
127    EET_DATA_DESCRIPTOR_ADD_BASIC(subdir_dir_edd, Subdir_Cache_Dir, "2", mode, EET_T_ULONG_LONG);
128    EET_DATA_DESCRIPTOR_ADD_BASIC(subdir_dir_edd, Subdir_Cache_Dir, "3", uid, EET_T_ULONG_LONG);
129    EET_DATA_DESCRIPTOR_ADD_BASIC(subdir_dir_edd, Subdir_Cache_Dir, "4", gid, EET_T_ULONG_LONG);
130    EET_DATA_DESCRIPTOR_ADD_BASIC(subdir_dir_edd, Subdir_Cache_Dir, "5", size, EET_T_ULONG_LONG);
131    EET_DATA_DESCRIPTOR_ADD_BASIC(subdir_dir_edd, Subdir_Cache_Dir, "6", mtim, EET_T_ULONG_LONG);
132    EET_DATA_DESCRIPTOR_ADD_BASIC(subdir_dir_edd, Subdir_Cache_Dir, "7", ctim, EET_T_ULONG_LONG);
133    EET_DATA_DESCRIPTOR_ADD_VAR_ARRAY_STRING(subdir_dir_edd, Subdir_Cache_Dir, "d", dirs);
134 
135    eet_eina_stream_data_descriptor_class_set(&eddc, sizeof(Subdir_Cache), "C", sizeof(Subdir_Cache));
136    eddc.func.hash_add = subdir_cache_hash_add;
137    subdir_edd = eet_data_descriptor_stream_new(&eddc);
138    EET_DATA_DESCRIPTOR_ADD_HASH(subdir_edd, Subdir_Cache, "dirs", dirs, subdir_dir_edd);
139 
140    // load subdirs from the cache file
141    eina_strbuf_append_printf(buf, "%s/efreet/subdirs_%s.eet",
142                              efreet_cache_home_get(), efreet_hostname_get());
143    ef = eet_open(eina_strbuf_string_get(buf), EET_FILE_MODE_READ);
144    if (ef)
145      {
146         subdir_cache = eet_data_read(ef, subdir_edd, "subdirs");
147         eet_close(ef);
148      }
149    eina_strbuf_free(buf);
150 
151    // if we don't have a decoded subdir cache - allocate one
152    if (!subdir_cache) subdir_cache = calloc(1, sizeof(Subdir_Cache));
153    if (!subdir_cache)
154      {
155         ERR("Cannot allocate subdir cache in memory");
156         return;
157      }
158 
159    // if we don't have a hash in the subdir cache - allocate it
160    if (!subdir_cache->dirs)
161      subdir_cache->dirs = eina_hash_string_superfast_new(EINA_FREE_CB(subdir_cache_dir_free));
162 }
163 
164 static void
subdir_cache_shutdown(void)165 subdir_cache_shutdown(void)
166 {
167    // free up in-memory subdir scan info - don't need it anymore
168    if (subdir_cache)
169      {
170         if (subdir_cache->dirs) eina_hash_free(subdir_cache->dirs);
171         free(subdir_cache);
172      }
173    eet_data_descriptor_free(subdir_dir_edd);
174    eet_data_descriptor_free(subdir_edd);
175    subdir_cache = NULL;
176    subdir_dir_edd = NULL;
177    subdir_edd = NULL;
178 }
179 
180 static void
subdir_cache_save(void)181 subdir_cache_save(void)
182 {
183    Eina_Strbuf *buf;
184    Eet_File *ef;
185    Eina_Tmpstr *tmpstr = NULL;
186    int tmpfd;
187 
188    // only if subdirs need saving... and we have subdirs.
189    if (!subdir_need_save) return;
190    if (!subdir_cache) return;
191    if (!subdir_cache->dirs) return;
192 
193    buf = eina_strbuf_new();
194    if (!buf) return;
195 
196    // save to tmp file first
197    eina_strbuf_append_printf(buf, "%s/efreet/subdirs_%s.eet.XXXXXX.cache",
198                              efreet_cache_home_get(), efreet_hostname_get());
199 
200    tmpfd = eina_file_mkstemp(eina_strbuf_string_get(buf), &tmpstr);
201    if (tmpfd < 0)
202      {
203         eina_strbuf_free(buf);
204         return;
205      }
206 
207    eina_strbuf_reset(buf);
208 
209    // write out eet file to tmp file
210    ef = eet_open(tmpstr, EET_FILE_MODE_WRITE);
211    eet_data_write(ef, subdir_edd, "subdirs", subdir_cache, EET_COMPRESSION_SUPERFAST);
212    eet_close(ef);
213 
214    /*
215     * On Windows, buf2 has one remaining ref, hence it can not be renamed below.
216     * Stupid NTFS... So we close it first. "Magically", on Windows, this
217     * temporary file is not deleted...
218     */
219 #ifdef _WIN32
220    close(tmpfd);
221 #endif
222 
223    // atomically rename subdirs file on top from tmp file
224    eina_strbuf_append_printf(buf, "%s/efreet/subdirs_%s.eet",
225                              efreet_cache_home_get(), efreet_hostname_get());
226 
227    if (rename(tmpstr, eina_strbuf_string_get(buf)) < 0)
228      {
229         unlink(tmpstr);
230         ERR("Can't save subdir cache %s", eina_strbuf_string_get(buf));
231      }
232    // we dont need saving anymore - we just did
233    subdir_need_save = EINA_FALSE;
234    eina_tmpstr_del(tmpstr);
235    eina_strbuf_free(buf);
236 }
237 
238 static const Subdir_Cache_Dir *
subdir_cache_get(const struct stat * st,const char * path)239 subdir_cache_get(const struct stat *st, const char *path)
240 {
241    Eina_Iterator *it;
242    Eina_File_Direct_Info *info;
243    Subdir_Cache_Dir *cd;
244    Eina_List *files = NULL;
245    int i = 0;
246    const char *file;
247 
248    // if no subdir cache at all - return null
249    if (!subdir_cache) return NULL;
250    if (!subdir_cache->dirs) return NULL;
251 
252    // if found but something invalid in stored stat info...
253    cd = eina_hash_find(subdir_cache->dirs, path);
254    if ((cd) &&
255        ((cd->dev != (unsigned long long)st->st_dev) ||
256         (cd->ino != (unsigned long long)st->st_ino) ||
257         (cd->mode != (unsigned long long)st->st_mode) ||
258         (cd->uid != (unsigned long long)st->st_uid) ||
259         (cd->gid != (unsigned long long)st->st_gid) ||
260         (cd->size != (unsigned long long)st->st_size) ||
261         (cd->mtim != (unsigned long long)st->st_mtime) ||
262         (cd->ctim != (unsigned long long)st->st_ctime)))
263      {
264         // delete old node and prepare to scan a new one
265         eina_hash_del(subdir_cache->dirs, path, cd);
266         cd = NULL;
267      }
268    // if cached dir is ok by now - return it
269    if (cd) return cd;
270 
271    // we need a new node (fesh or invalid)
272    cd = calloc(1, sizeof(Subdir_Cache_Dir));
273    if (!cd) return NULL;
274 
275    // store stat info
276    cd->dev = (unsigned long long)st->st_dev;
277    cd->ino = (unsigned long long)st->st_ino;
278    cd->mode = (unsigned long long)st->st_mode;
279    cd->uid = (unsigned long long)st->st_uid;
280    cd->gid = (unsigned long long)st->st_gid;
281    cd->size = (unsigned long long)st->st_size;
282    cd->mtim = (unsigned long long)st->st_mtime;
283    cd->ctim = (unsigned long long)st->st_ctime;
284 
285    // go through content finding directories
286    it = eina_file_stat_ls(path);
287    if (!it) return cd;
288 
289    EINA_ITERATOR_FOREACH(it, info)
290      {
291         // if ., .. or other "hidden" dot files - ignore
292         if (info->path[info->name_start] == '.') continue;
293         // if it's a dir or link to a dir - store it.
294         if (((info->type == EINA_FILE_LNK) && (ecore_file_is_dir(info->path))) ||
295             (info->type == EINA_FILE_DIR))
296           {
297              // store just the name, not the full path
298              files = eina_list_append
299                (files, eina_stringshare_add(info->path + info->name_start));
300           }
301      }
302    eina_iterator_free(it);
303 
304    // now convert our temporary list into an array of stringshare strings
305    cd->dirs_count = eina_list_count(files);
306    if (cd->dirs_count > 0)
307      {
308         cd->dirs = malloc(cd->dirs_count * sizeof(char *));
309         EINA_LIST_FREE(files, file)
310           {
311              cd->dirs[i] = file;
312              i++;
313           }
314      }
315    // add cache dir to hash with full path as key
316    eina_hash_add(subdir_cache->dirs, path, cd);
317    // mark subdirs as needing a save - something changed
318    subdir_need_save = EINA_TRUE;
319    return cd;
320 }
321 
322 static Eina_Bool
icon_cache_update_cache_cb(void * data EINA_UNUSED)323 icon_cache_update_cache_cb(void *data EINA_UNUSED)
324 {
325    Eina_Strbuf *file = eina_strbuf_new();
326    if (!file) return EINA_FALSE;
327 
328    icon_cache_timer = NULL;
329 
330    if (icon_cache_exe)
331      {
332         icon_queue = EINA_TRUE;
333         eina_strbuf_free(file);
334         return ECORE_CALLBACK_CANCEL;
335      }
336    icon_queue = EINA_FALSE;
337    if ((!icon_flush) && (!icon_exts))
338      {
339         eina_strbuf_free(file);
340         return ECORE_CALLBACK_CANCEL;
341      }
342 
343    if (icon_change_monitors) eina_hash_free(icon_change_monitors);
344    if (icon_change_monitors_mon) eina_hash_free(icon_change_monitors_mon);
345    icon_change_monitors = eina_hash_string_superfast_new
346      (EINA_FREE_CB(eio_monitor_del));
347    icon_change_monitors_mon = eina_hash_pointer_new(NULL);
348    icon_changes_listen();
349    subdir_cache_save();
350 
351    /* TODO: Queue if already running */
352    eina_strbuf_append_printf(file, "%s/efreet/" MODULE_ARCH "/efreet_icon_cache_create",
353                              eina_prefix_lib_get(pfx));
354    if (icon_extra_dirs)
355      {
356         Eina_List *ll;
357         char *p;
358 
359         eina_strbuf_append(file, " -d");
360         EINA_LIST_FOREACH(icon_extra_dirs, ll, p)
361           {
362              eina_strbuf_append(file, " ");
363              eina_strbuf_append(file, p);
364           }
365      }
366    if (icon_exts)
367      {
368         Eina_List *ll;
369         char *p;
370 
371         eina_strbuf_append(file, " -e");
372         EINA_LIST_FOREACH(icon_exts, ll, p)
373           {
374              eina_strbuf_append(file, " ");
375              eina_strbuf_append(file, p);
376           }
377      }
378    if (icon_flush)
379      eina_strbuf_append(file, " -f");
380    icon_flush = EINA_FALSE;
381    fprintf(efreetd_log_file, "[%09.3f] Run:\n  %s\n", ecore_time_get(),
382            eina_strbuf_string_get(file));
383    fflush(efreetd_log_file);
384    icon_cache_exe = ecore_exe_pipe_run
385      (eina_strbuf_string_get(file),
386       ECORE_EXE_PIPE_READ | ECORE_EXE_PIPE_READ_LINE_BUFFERED,
387       NULL);
388 
389    eina_strbuf_free(file);
390 
391    return ECORE_CALLBACK_CANCEL;
392 }
393 
394 static Eina_Bool
desktop_cache_update_cache_cb(void * data EINA_UNUSED)395 desktop_cache_update_cache_cb(void *data EINA_UNUSED)
396 {
397    Eina_Strbuf *file;
398 
399    desktop_cache_timer = NULL;
400 
401    if (desktop_cache_exe)
402      {
403         desktop_queue = EINA_TRUE;
404         return ECORE_CALLBACK_CANCEL;
405      }
406    desktop_queue = EINA_FALSE;
407    file = eina_strbuf_new();
408 
409    if (desktop_change_monitors) eina_hash_free(desktop_change_monitors);
410    if (desktop_change_monitors_mon) eina_hash_free(desktop_change_monitors_mon);
411    desktop_change_monitors = eina_hash_string_superfast_new
412      (EINA_FREE_CB(eio_monitor_del));
413    desktop_change_monitors_mon = eina_hash_pointer_new(NULL);
414    desktop_changes_listen();
415    subdir_cache_save();
416 
417    eina_strbuf_append_printf(file, "%s/efreet/" MODULE_ARCH "/efreet_desktop_cache_create",
418                             eina_prefix_lib_get(pfx));
419    if (desktop_extra_dirs)
420      {
421         Eina_List *ll;
422         const char *str;
423 
424         eina_strbuf_append(file, " -d");
425         EINA_LIST_FOREACH(desktop_extra_dirs, ll, str)
426           {
427              eina_strbuf_append(file, " ");
428              eina_strbuf_append(file, str);
429           }
430      }
431    INF("Run desktop cache creation: %s", eina_strbuf_string_get(file));
432    fprintf(efreetd_log_file, "[%09.3f] Run:\n  %s\n", ecore_time_get(),
433            eina_strbuf_string_get(file));
434    fflush(efreetd_log_file);
435    desktop_cache_exe = ecore_exe_pipe_run
436      (eina_strbuf_string_get(file),
437       ECORE_EXE_PIPE_READ | ECORE_EXE_PIPE_READ_LINE_BUFFERED,
438       NULL);
439 
440    eina_strbuf_free(file);
441 
442    return ECORE_CALLBACK_CANCEL;
443 }
444 
445 static void
cache_icon_update(Eina_Bool flush)446 cache_icon_update(Eina_Bool flush)
447 {
448    if (icon_cache_timer) ecore_timer_del(icon_cache_timer);
449    if (flush) icon_flush = flush;
450    icon_cache_timer = ecore_timer_add(0.2, icon_cache_update_cache_cb, NULL);
451 }
452 
453 void
cache_desktop_update(void)454 cache_desktop_update(void)
455 {
456    if (desktop_cache_timer) ecore_timer_del(desktop_cache_timer);
457    desktop_cache_timer = ecore_timer_add(0.2, desktop_cache_update_cache_cb, NULL);
458 }
459 
460 static Eina_Bool
_cb_monitor_event(void * data EINA_UNUSED,int type EINA_UNUSED,void * event)461 _cb_monitor_event(void *data EINA_UNUSED, int type EINA_UNUSED, void *event)
462 {
463    Eio_Monitor_Event *ev = event;
464 
465    // if it's an icon
466    if (eina_hash_find(icon_change_monitors_mon, &(ev->monitor)))
467      {
468         cache_icon_update(EINA_FALSE);
469      }
470    // if it's a desktop
471    else if (eina_hash_find(desktop_change_monitors_mon, &(ev->monitor)))
472      {
473         cache_desktop_update();
474      }
475    // if it's a mime file
476    else if (eina_hash_find(mime_monitors_mon, &(ev->monitor)))
477      {
478         if ((!strcmp("/etc/mime.types", ev->filename)) ||
479             (!strcmp("globs", ecore_file_file_get(ev->filename))))
480           {
481              mime_cache_shutdown();
482              mime_cache_init();
483              if (mime_update_timer) ecore_timer_del(mime_update_timer);
484              mime_update_timer = ecore_timer_add(0.2, mime_update_cache_cb, NULL);
485           }
486      }
487    return ECORE_CALLBACK_PASS_ON;
488 }
489 
490 static void
icon_changes_monitor_add(const struct stat * st,const char * path)491 icon_changes_monitor_add(const struct stat *st, const char *path)
492 {
493    Eio_Monitor *mon;
494    char *realp = NULL;
495    const char *monpath = path;
496 
497    if (eina_hash_find(icon_change_monitors, path)) return;
498 #ifndef _WIN32
499    if (S_ISLNK(st->st_mode))
500      {
501         realp = ecore_file_realpath(path);
502         if (!realp) return;
503         monpath = realp;
504      }
505 #endif
506    if (ecore_file_is_dir(monpath))
507      {
508         mon = eio_monitor_add(monpath);
509         if (mon)
510           {
511              eina_hash_add(icon_change_monitors, path, mon);
512              eina_hash_add(icon_change_monitors_mon, &mon, mon);
513           }
514      }
515    free(realp);
516 }
517 
518 static void
desktop_changes_monitor_add(const struct stat * st,const char * path)519 desktop_changes_monitor_add(const struct stat *st, const char *path)
520 {
521    Eio_Monitor *mon;
522    char *realp = NULL;
523    const char *monpath = path;
524 
525    if (eina_hash_find(desktop_change_monitors, path)) return;
526 #ifndef _WIN32
527    if (S_ISLNK(st->st_mode))
528      {
529         realp = ecore_file_realpath(path);
530         if (!realp) return;
531         monpath = realp;
532      }
533 #endif
534    if (ecore_file_is_dir(monpath))
535      {
536         mon = eio_monitor_add(monpath);
537         if (mon)
538           {
539              eina_hash_add(desktop_change_monitors, path, mon);
540              eina_hash_add(desktop_change_monitors_mon, &mon, mon);
541           }
542      }
543    free(realp);
544 }
545 
546 static int
stat_cmp(const void * a,const void * b)547 stat_cmp(const void *a, const void *b)
548 {
549    const struct stat *st1 = a;
550    const struct stat *st2 = b;
551 
552    if ((st2->st_dev == st1->st_dev) && (st2->st_ino == st1->st_ino))
553      return 0;
554    return 1;
555 }
556 
557 static Eina_Bool
_check_recurse_monitor_sanity(Eina_Inarray * stack,const char * path,unsigned int stack_limit)558 _check_recurse_monitor_sanity(Eina_Inarray *stack, const char *path, unsigned int stack_limit)
559 {
560    const char *home = eina_environment_home_get();
561 
562    // protect against too deep recursion even if it's valid.
563    if (eina_inarray_count(stack) >= stack_limit)
564      {
565         ERR("Recursing too far. Level %i. Stopping at %s\n", stack_limit, path);
566         return EINA_FALSE;
567      }
568    // detect if we start recursing at $HOME - a sign of something wrong
569    if ((home) && (!strcmp(home, path)))
570      {
571         ERR("Recursively monitor homedir! Ignore.");
572         return EINA_FALSE;
573      }
574    return EINA_TRUE;
575 }
576 
577 static void
icon_changes_listen_recursive(Eina_Inarray * stack,const char * path,Eina_Bool base)578 icon_changes_listen_recursive(Eina_Inarray *stack, const char *path, Eina_Bool base)
579 {
580    struct stat *st = eina_mempool_malloc(efreetd_mp_stat, sizeof(struct stat));
581    if (!st) return;
582 
583    if (stat(path, st) == -1) return;
584    if (eina_inarray_search(stack, st, stat_cmp) >= 0) return;
585    if (!_check_recurse_monitor_sanity(stack, path, 10)) return;
586    eina_inarray_push(stack, st);
587 
588    if ((!S_ISDIR(st->st_mode)) && (base))
589      {
590         // XXX: if it doesn't exist... walk the parent dirs back down
591         // to this path until we find one that doesn't exist, then
592         // monitor its parent, and treat it specially as it needs
593         // to look for JUST the creation of this specific child
594         // and when this child is created, replace this monitor with
595         // monitoring the next specific child dir down until we are
596         // monitoring the original path again.
597      }
598    if (S_ISDIR(st->st_mode))
599      {
600         unsigned int i;
601         const Subdir_Cache_Dir *cd = subdir_cache_get(st, path);
602         icon_changes_monitor_add(st, path);
603         if (cd)
604           {
605              Eina_Strbuf *buf = eina_strbuf_new();
606              if (!buf) return;
607              for (i = 0; i < cd->dirs_count; i++)
608                {
609 
610                   eina_strbuf_append_printf(buf,  "%s/%s", path, cd->dirs[i]);
611                   icon_changes_listen_recursive(stack, eina_strbuf_string_get(buf), EINA_FALSE);
612                   eina_strbuf_reset(buf);
613                }
614              eina_strbuf_free(buf);
615           }
616      }
617    eina_inarray_pop(stack);
618    eina_mempool_free(efreetd_mp_stat, st);
619 }
620 
621 static void
desktop_changes_listen_recursive(Eina_Inarray * stack,const char * path,Eina_Bool base)622 desktop_changes_listen_recursive(Eina_Inarray *stack, const char *path, Eina_Bool base)
623 {
624    struct stat *st = eina_mempool_malloc(efreetd_mp_stat, sizeof(struct stat));
625    if (!st) return;
626 
627    if (stat(path, st) == -1) return;
628    if (eina_inarray_search(stack, st, stat_cmp) >= 0) return;
629    if (!_check_recurse_monitor_sanity(stack, path, 10)) return;
630    eina_inarray_push(stack, st);
631 
632    if ((!S_ISDIR(st->st_mode)) && (base))
633      {
634         // XXX: if it doesn't exist... walk the parent dirs back down
635         // to this path until we find one that doesn't exist, then
636         // monitor its parent, and treat it specially as it needs
637         // to look for JUST the creation of this specific child
638         // and when this child is created, replace this monitor with
639         // monitoring the next specific child dir down until we are
640         // monitoring the original path again.
641      }
642    if (S_ISDIR(st->st_mode))
643      {
644         unsigned int i;
645         const Subdir_Cache_Dir *cd = subdir_cache_get(st, path);
646         desktop_changes_monitor_add(st, path);
647         if (cd)
648           {
649              Eina_Strbuf *buf = eina_strbuf_new();
650              if (!buf) return;
651              for (i = 0; i < cd->dirs_count; i++)
652                {
653                   eina_strbuf_append_printf(buf, "%s/%s", path, cd->dirs[i]);
654                   desktop_changes_listen_recursive(stack, eina_strbuf_string_get(buf), EINA_FALSE);
655                   eina_strbuf_reset(buf);
656                }
657              eina_strbuf_free(buf);
658           }
659      }
660    eina_inarray_pop(stack);
661    eina_mempool_free(efreetd_mp_stat, st);
662 }
663 
664 static void
icon_changes_listen(void)665 icon_changes_listen(void)
666 {
667    Eina_List *l;
668    Eina_List *xdg_dirs;
669    const char *dir;
670    Eina_Inarray *stack;
671    Eina_Strbuf *buf = eina_strbuf_new();
672    if (!buf) return;
673 
674    stack = eina_inarray_new(sizeof(struct stat), 16);
675    if (!stack)
676      {
677         eina_strbuf_free(buf);
678         return;
679      }
680    icon_changes_listen_recursive(stack, efreet_icon_deprecated_user_dir_get(), EINA_TRUE);
681    eina_inarray_flush(stack);
682    icon_changes_listen_recursive(stack, efreet_icon_user_dir_get(), EINA_TRUE);
683    EINA_LIST_FOREACH(icon_extra_dirs, l, dir)
684      {
685         if (!strcmp(dir, "/")) continue;
686         eina_inarray_flush(stack);
687         icon_changes_listen_recursive(stack, dir, EINA_TRUE);
688      }
689 
690    xdg_dirs = efreet_data_dirs_get();
691    EINA_LIST_FOREACH(xdg_dirs, l, dir)
692      {
693         eina_strbuf_append_printf(buf, "%s/icons", dir);
694         eina_inarray_flush(stack);
695         icon_changes_listen_recursive(stack, eina_strbuf_string_get(buf), EINA_TRUE);
696         eina_strbuf_reset(buf);
697      }
698 
699 #ifndef STRICT_SPEC
700    EINA_LIST_FOREACH(xdg_dirs, l, dir)
701      {
702         eina_strbuf_append_printf(buf, "%s/pixmaps", dir);
703         eina_inarray_flush(stack);
704         icon_changes_listen_recursive(stack, eina_strbuf_string_get(buf), EINA_TRUE);
705         eina_strbuf_reset(buf);
706      }
707 #endif
708    eina_inarray_flush(stack);
709    icon_changes_listen_recursive(stack, "/usr/local/share/pixmaps", EINA_TRUE);
710    icon_changes_listen_recursive(stack, "/usr/share/pixmaps", EINA_TRUE);
711    eina_inarray_free(stack);
712    eina_strbuf_free(buf);
713 }
714 
715 static void
desktop_changes_listen(void)716 desktop_changes_listen(void)
717 {
718    Eina_List *l;
719    const char *path;
720    Eina_Inarray *stack;
721 
722    stack = eina_inarray_new(sizeof(struct stat), 16);
723    if (!stack) return;
724    EINA_LIST_FOREACH(desktop_system_dirs, l, path)
725      {
726         eina_inarray_flush(stack);
727         desktop_changes_listen_recursive(stack, path, EINA_TRUE);
728      }
729    EINA_LIST_FOREACH(desktop_extra_dirs, l, path)
730      {
731         eina_inarray_flush(stack);
732         desktop_changes_listen_recursive(stack, path, EINA_TRUE);
733      }
734    eina_inarray_free(stack);
735 }
736 
737 static void
fill_list(const char * file,Eina_List ** l)738 fill_list(const char *file, Eina_List **l)
739 {
740    Eina_File *f = NULL;
741    Eina_Iterator *it = NULL;
742    Eina_File_Line *line = NULL;
743    Eina_Strbuf *buf = eina_strbuf_new();
744    if (!buf) return;
745 
746    eina_strbuf_append_printf(buf, "%s/efreet/%s", efreet_cache_home_get(), file);
747    f = eina_file_open(eina_strbuf_string_get(buf), EINA_FALSE);
748    if (!f) goto error_buf;
749    it = eina_file_map_lines(f);
750    if (!it) goto error;
751    EINA_ITERATOR_FOREACH(it, line)
752      {
753         if (line->end > line->start)
754           {
755              const char *s = eina_stringshare_add_length(line->start, line->end - line->start);
756              if (s) *l = eina_list_append(*l, s);
757           }
758      }
759    eina_iterator_free(it);
760 error:
761    eina_file_close(f);
762 error_buf:
763    eina_strbuf_free(buf);
764 }
765 
766 static void
read_lists(void)767 read_lists(void)
768 {
769 // dont use extra dirs as the only way to get extra dirs is by loading a
770 // specific desktop file at a specific path, and this is wrong
771 //   fill_list("extra_desktops.dirs", &desktop_extra_dirs);
772    fill_list("extra_icons.dirs", &icon_extra_dirs);
773    fill_list("icons.exts", &icon_exts);
774 }
775 
776 static void
save_list(const char * file,Eina_List * l)777 save_list(const char *file, Eina_List *l)
778 {
779    FILE *f;
780    Eina_List *ll;
781    const char *path;
782    Eina_Strbuf *buf = eina_strbuf_new();
783    if (!buf) return;
784 
785    eina_strbuf_append_printf(buf, "%s/efreet/%s", efreet_cache_home_get(), file);
786    f = fopen(eina_strbuf_string_get(buf), "wb");
787    if (!f)
788      {
789         eina_strbuf_free(buf);
790         return;
791      }
792    EINA_LIST_FOREACH(l, ll, path)
793       fprintf(f, "%s\n", path);
794    fclose(f);
795    eina_strbuf_free(buf);
796 }
797 
798 static int
strcmplen(const void * data1,const void * data2)799 strcmplen(const void *data1, const void *data2)
800 {
801    return strncmp(data1, data2, eina_stringshare_strlen(data1));
802 }
803 
804 static Eina_Bool
cache_exe_data_cb(void * data EINA_UNUSED,int type EINA_UNUSED,void * event)805 cache_exe_data_cb(void *data EINA_UNUSED, int type EINA_UNUSED, void *event)
806 {
807    Ecore_Exe_Event_Data *ev = event;
808 
809    if (ev->exe == desktop_cache_exe)
810      {
811         Eina_Bool update = EINA_FALSE;
812 
813         fprintf(efreetd_log_file, "[%09.3f] Data desktop_cache_create\n", ecore_time_get());
814         fflush(efreetd_log_file);
815         if ((ev->lines) && (*ev->lines->line == 'c')) update = EINA_TRUE;
816         if (!desktop_exists)
817           send_signal_desktop_cache_build();
818         desktop_exists = EINA_TRUE;
819         send_signal_desktop_cache_update(update);
820      }
821    else if (ev->exe == icon_cache_exe)
822      {
823         Eina_Bool update = EINA_FALSE;
824 
825         fprintf(efreetd_log_file, "[%09.3f] Data icon_cache_create\n", ecore_time_get());
826         fflush(efreetd_log_file);
827         if ((ev->lines) && (*ev->lines->line == 'c')) update = EINA_TRUE;
828         send_signal_icon_cache_update(update);
829      }
830    else if (ev->exe == mime_cache_exe)
831      {
832         fprintf(efreetd_log_file, "[%09.3f] Data mime_cache_create\n", ecore_time_get());
833         fflush(efreetd_log_file);
834         // XXX: ZZZ: handle stdout here from cache updater... if needed
835      }
836    return ECORE_CALLBACK_RENEW;
837 }
838 
839 static Eina_Bool
cache_exe_del_cb(void * data EINA_UNUSED,int type EINA_UNUSED,void * event)840 cache_exe_del_cb(void *data EINA_UNUSED, int type EINA_UNUSED, void *event)
841 {
842    Ecore_Exe_Event_Del *ev = event;
843 
844    if (ev->exe == desktop_cache_exe)
845      {
846         fprintf(efreetd_log_file, "[%09.3f] Exit desktop_cache_create\n", ecore_time_get());
847         fflush(efreetd_log_file);
848         desktop_cache_exe = NULL;
849         if (desktop_queue) cache_desktop_update();
850      }
851    else if (ev->exe == icon_cache_exe)
852      {
853         fprintf(efreetd_log_file, "[%09.3f] Exit icon_cache_create\n", ecore_time_get());
854         fflush(efreetd_log_file);
855         icon_cache_exe = NULL;
856         if (icon_queue) cache_icon_update(EINA_FALSE);
857      }
858    else if (ev->exe == mime_cache_exe)
859      {
860         fprintf(efreetd_log_file, "[%09.3f] Exit mime_cache_create\n", ecore_time_get());
861         fflush(efreetd_log_file);
862         mime_cache_exe = NULL;
863         send_signal_mime_cache_build();
864      }
865    return ECORE_CALLBACK_RENEW;
866 }
867 
868 /* external */
869 void
cache_desktop_dir_add(const char * dir)870 cache_desktop_dir_add(const char *dir)
871 {
872    char *san;
873    Eina_List *l;
874 
875    san = eina_file_path_sanitize(dir);
876    if (!san) return;
877    if ((l = eina_list_search_unsorted_list(desktop_system_dirs, strcmplen, san)))
878      {
879         /* Path is registered, but maybe not monitored */
880         const char *path = eina_list_data_get(l);
881         if (!eina_hash_find(desktop_change_monitors, path))
882           cache_desktop_update();
883      }
884    else if (!eina_list_search_unsorted_list(desktop_extra_dirs, EINA_COMPARE_CB(strcmp), san))
885      {
886         /* Not a registered path */
887         desktop_extra_dirs = eina_list_append(desktop_extra_dirs, eina_stringshare_add(san));
888         save_list("extra_desktops.dirs", desktop_extra_dirs);
889         cache_desktop_update();
890      }
891    free(san);
892 }
893 
894 void
cache_icon_dir_add(const char * dir)895 cache_icon_dir_add(const char *dir)
896 {
897    char *san;
898 
899    san = eina_file_path_sanitize(dir);
900    if (!san) return;
901    if (!eina_list_search_unsorted_list(icon_extra_dirs, EINA_COMPARE_CB(strcmp), san))
902      {
903         if (!strcmp(san, "/")) goto out;
904         icon_extra_dirs = eina_list_append(icon_extra_dirs, eina_stringshare_add(san));
905         save_list("extra_icons.dirs", icon_extra_dirs);
906         cache_icon_update(EINA_TRUE);
907      }
908 out:
909    free(san);
910 }
911 
912 void
cache_icon_ext_add(const char * ext)913 cache_icon_ext_add(const char *ext)
914 {
915    if (!eina_list_search_unsorted_list(icon_exts, EINA_COMPARE_CB(strcmp), ext))
916      {
917         icon_exts = eina_list_append(icon_exts, eina_stringshare_add(ext));
918         save_list("icons.exts", icon_exts);
919         cache_icon_update(EINA_TRUE);
920      }
921 }
922 
923 Eina_Bool
cache_desktop_exists(void)924 cache_desktop_exists(void)
925 {
926    return desktop_exists;
927 }
928 
929 static void
mime_update_launch(void)930 mime_update_launch(void)
931 {
932    Eina_Strbuf *file = eina_strbuf_new();
933    if (!file) return;
934 
935    eina_strbuf_append_printf(file,
936             "%s/efreet/" MODULE_ARCH "/efreet_mime_cache_create",
937             eina_prefix_lib_get(pfx));
938    mime_cache_exe = ecore_exe_pipe_run(eina_strbuf_string_get(file),
939                                        ECORE_EXE_PIPE_READ |
940                                        ECORE_EXE_PIPE_READ_LINE_BUFFERED,
941                                        NULL);
942    eina_strbuf_free(file);
943 }
944 
945 static Eina_Bool
mime_update_cache_cb(void * data EINA_UNUSED)946 mime_update_cache_cb(void *data EINA_UNUSED)
947 {
948    mime_update_timer = NULL;
949    if (mime_cache_exe)
950      {
951         ecore_exe_kill(mime_cache_exe);
952         ecore_exe_free(mime_cache_exe);
953      }
954    mime_update_launch();
955    return EINA_FALSE;
956 }
957 
958 static void
mime_cache_init(void)959 mime_cache_init(void)
960 {
961    Eio_Monitor *mon;
962    Eina_List *datadirs, *l;
963    const char *s;
964    Eina_Strbuf *buf = eina_strbuf_new();
965    if (!buf) return;
966 
967    mime_monitors = eina_hash_string_superfast_new
968      (EINA_FREE_CB(eio_monitor_del));
969    mime_monitors_mon = eina_hash_pointer_new(NULL);
970 
971    if (ecore_file_is_dir("/etc"))
972      {
973         mon = eio_monitor_add("/etc"); // specifically look at /etc/mime.types
974         if (mon)
975           {
976              eina_hash_add(mime_monitors, "/etc", mon);
977              eina_hash_add(mime_monitors_mon, &mon, mon);
978           }
979      }
980    if (ecore_file_is_dir("/usr/share/mime"))
981      {
982         mon = eio_monitor_add("/usr/share/mime"); // specifically look at /usr/share/mime/globs
983         if (mon)
984           {
985              eina_hash_add(mime_monitors, "/usr/share/mime", mon);
986              eina_hash_add(mime_monitors_mon, &mon, mon);
987           }
988      }
989 
990    datadirs = efreet_data_dirs_get();
991    EINA_LIST_FOREACH(datadirs, l, s)
992      {
993         eina_strbuf_append_printf(buf, "%s/mime", s); // specifically lok at XXX/mime/globs
994         if (ecore_file_is_dir(eina_strbuf_string_get(buf)))
995           {
996              if (!eina_hash_find(mime_monitors, eina_strbuf_string_get(buf)))
997                {
998                   mon = eio_monitor_add(eina_strbuf_string_get(buf));
999                   if (mon)
1000                     {
1001                        eina_hash_add(mime_monitors, eina_strbuf_string_get(buf), mon);
1002                        eina_hash_add(mime_monitors_mon, &mon, mon);
1003                     }
1004                }
1005           }
1006      }
1007    eina_strbuf_free(buf);
1008 }
1009 
1010 static void
mime_cache_shutdown(void)1011 mime_cache_shutdown(void)
1012 {
1013    if (mime_update_timer)
1014      {
1015         ecore_timer_del(mime_update_timer);
1016         mime_update_timer = NULL;
1017      }
1018    if (mime_monitors)
1019      {
1020         eina_hash_free(mime_monitors);
1021         mime_monitors = NULL;
1022      }
1023    if (mime_monitors_mon)
1024      {
1025         eina_hash_free(mime_monitors_mon);
1026         mime_monitors_mon = NULL;
1027      }
1028 }
1029 
1030 Eina_Bool
cache_init(void)1031 cache_init(void)
1032 {
1033    char **argv;
1034 
1035    ecore_app_args_get(NULL, &argv);
1036 
1037    pfx = eina_prefix_new(argv[0], cache_init,
1038                          "EFREET", "efreet", "checkme",
1039                          PACKAGE_BIN_DIR,
1040                          PACKAGE_LIB_DIR,
1041                          PACKAGE_DATA_DIR,
1042                          PACKAGE_DATA_DIR);
1043 
1044    cache_exe_del_handler = ecore_event_handler_add(ECORE_EXE_EVENT_DEL,
1045                                                    cache_exe_del_cb, NULL);
1046    if (!cache_exe_del_handler)
1047      {
1048         ERR("Failed to add exe del handler");
1049         goto error;
1050      }
1051    cache_exe_data_handler = ecore_event_handler_add(ECORE_EXE_EVENT_DATA,
1052                                                     cache_exe_data_cb, NULL);
1053    if (!cache_exe_data_handler)
1054      {
1055         ERR("Failed to add exe data handler");
1056         goto error;
1057      }
1058 
1059    icon_change_monitors = eina_hash_string_superfast_new
1060      (EINA_FREE_CB(eio_monitor_del));
1061    icon_change_monitors_mon = eina_hash_pointer_new(NULL);
1062    desktop_change_monitors = eina_hash_string_superfast_new
1063      (EINA_FREE_CB(eio_monitor_del));
1064    desktop_change_monitors_mon = eina_hash_pointer_new(NULL);
1065 
1066    efreet_cache_update = 0;
1067    if (!efreet_init()) goto error;
1068    eio_init();
1069 
1070 #define MONITOR_EVENT(ev, fn) \
1071 _handlers = eina_list_append(_handlers, ecore_event_handler_add(ev, fn, NULL))
1072    MONITOR_EVENT(EIO_MONITOR_FILE_CREATED,       _cb_monitor_event);
1073    MONITOR_EVENT(EIO_MONITOR_FILE_DELETED,       _cb_monitor_event);
1074    MONITOR_EVENT(EIO_MONITOR_FILE_MODIFIED,      _cb_monitor_event);
1075    MONITOR_EVENT(EIO_MONITOR_DIRECTORY_CREATED,  _cb_monitor_event);
1076    MONITOR_EVENT(EIO_MONITOR_DIRECTORY_DELETED,  _cb_monitor_event);
1077    MONITOR_EVENT(EIO_MONITOR_DIRECTORY_MODIFIED, _cb_monitor_event);
1078    MONITOR_EVENT(EIO_MONITOR_SELF_RENAME,        _cb_monitor_event);
1079    MONITOR_EVENT(EIO_MONITOR_SELF_DELETED,       _cb_monitor_event);
1080 
1081    subdir_cache_init();
1082    mime_cache_init();
1083    mime_update_launch();
1084    read_lists();
1085    /* TODO: Should check if system dirs has changed and handles extra_dirs */
1086    desktop_system_dirs = efreet_default_dirs_get(efreet_data_home_get(),
1087                                                  efreet_data_dirs_get(), "applications");
1088    desktop_system_dirs =
1089       eina_list_merge(
1090          desktop_system_dirs, efreet_default_dirs_get(efreet_data_home_get(),
1091                                                       efreet_data_dirs_get(), "desktop-directories"));
1092    icon_changes_listen();
1093    desktop_changes_listen();
1094    cache_icon_update(EINA_FALSE);
1095    cache_desktop_update();
1096    subdir_cache_save();
1097 
1098    return EINA_TRUE;
1099 error:
1100    if (cache_exe_del_handler) ecore_event_handler_del(cache_exe_del_handler);
1101    cache_exe_del_handler = NULL;
1102    if (cache_exe_data_handler) ecore_event_handler_del(cache_exe_data_handler);
1103    cache_exe_data_handler = NULL;
1104    return EINA_FALSE;
1105 }
1106 
1107 Eina_Bool
cache_shutdown(void)1108 cache_shutdown(void)
1109 {
1110    const char *data;
1111    Ecore_Event_Handler *handler;
1112 
1113    eina_prefix_free(pfx);
1114    pfx = NULL;
1115 
1116    mime_cache_shutdown();
1117    subdir_cache_shutdown();
1118    efreet_shutdown();
1119 
1120    if (cache_exe_del_handler) ecore_event_handler_del(cache_exe_del_handler);
1121    cache_exe_del_handler = NULL;
1122    if (cache_exe_data_handler) ecore_event_handler_del(cache_exe_data_handler);
1123    cache_exe_data_handler = NULL;
1124 
1125    if (icon_change_monitors) eina_hash_free(icon_change_monitors);
1126    icon_change_monitors = NULL;
1127    if (icon_change_monitors_mon) eina_hash_free(icon_change_monitors_mon);
1128    icon_change_monitors_mon = NULL;
1129    if (desktop_change_monitors) eina_hash_free(desktop_change_monitors);
1130    desktop_change_monitors = NULL;
1131    if (desktop_change_monitors_mon) eina_hash_free(desktop_change_monitors_mon);
1132    desktop_change_monitors_mon = NULL;
1133    EINA_LIST_FREE(desktop_system_dirs, data)
1134       eina_stringshare_del(data);
1135    EINA_LIST_FREE(desktop_extra_dirs, data)
1136       eina_stringshare_del(data);
1137    EINA_LIST_FREE(icon_extra_dirs, data)
1138       eina_stringshare_del(data);
1139    EINA_LIST_FREE(icon_exts, data)
1140       eina_stringshare_del(data);
1141    EINA_LIST_FREE(_handlers, handler)
1142       ecore_event_handler_del(handler);
1143    eio_shutdown();
1144    return EINA_TRUE;
1145 }
1146