EFL 1.7 svn doobies
[profile/ivi/efreet.git] / src / bin / efreet_icon_cache_create.c
1 #ifdef HAVE_CONFIG_H
2 # include <config.h>
3 #endif
4 #include <sys/stat.h>
5 #include <fcntl.h>
6 #include <unistd.h>
7 #include <errno.h>
8
9 #include <Eina.h>
10 #include <Eet.h>
11 #include <Ecore.h>
12 #include <Ecore_File.h>
13
14 #define EFREET_MODULE_LOG_DOM _efreet_icon_cache_log_dom
15 static int _efreet_icon_cache_log_dom = -1;
16
17 #include "Efreet.h"
18 #include "efreet_private.h"
19 #include "efreet_cache_private.h"
20
21 /* TODO:
22  * - Need to handle programs using different exts
23  */
24
25 static Eina_Array *exts = NULL;
26 static Eina_Array *extra_dirs = NULL;
27 static Eina_Array *strs = NULL;
28 static Eina_Hash *icon_themes = NULL;
29
30 static Eina_Bool
31 cache_directory_modified(Eina_Hash *dirs, const char *dir)
32 {
33     Efreet_Cache_Directory *dcache;
34     struct stat st;
35
36     if (!dirs) return EINA_TRUE;
37
38     if (stat(dir, &st) < 0) return EINA_FALSE;
39     dcache = eina_hash_find(dirs, dir);
40     if (!dcache)
41     {
42         dcache = malloc(sizeof (Efreet_Cache_Directory));
43         if (!dcache) return EINA_TRUE;
44
45         dcache->modified_time = (long long) st.st_mtime;
46         eina_hash_add(dirs, dir, dcache);
47     }
48     else if (dcache->modified_time == (long long) st.st_mtime) return EINA_FALSE;
49     dcache->modified_time = st.st_mtime;
50
51     return EINA_TRUE;
52 }
53
54 static Eina_Bool
55 cache_extension_lookup(const char *ext)
56 {
57     unsigned int i;
58
59     for (i = 0; i < exts->count; ++i)
60         if (!strcmp(exts->data[i], ext))
61             return EINA_TRUE;
62     return EINA_FALSE;
63 }
64
65 static Eina_Bool
66 cache_fallback_scan_dir(Eina_Hash *icons, Eina_Hash *dirs, const char *dir)
67 {
68     Eina_Iterator *it;
69     Eina_File_Direct_Info *entry;
70
71     if (!cache_directory_modified(dirs, dir)) return EINA_TRUE;
72
73     it = eina_file_stat_ls(dir);
74     if (!it) return EINA_TRUE;
75
76     EINA_ITERATOR_FOREACH(it, entry)
77     {
78         Efreet_Cache_Fallback_Icon *icon;
79         char *name;
80         char *ext;
81         unsigned int i;
82
83         if (entry->type == EINA_FILE_DIR)
84             continue;
85
86         ext = strrchr(entry->path + entry->name_start, '.');
87         if (!ext || !cache_extension_lookup(ext))
88             continue;
89
90         /* icon with known extension */
91         name = entry->path + entry->name_start;
92         *ext = '\0';
93
94         icon = eina_hash_find(icons, name);
95         if (!icon)
96         {
97             icon = NEW(Efreet_Cache_Fallback_Icon, 1);
98             icon->theme = NULL;
99             eina_hash_add(icons, name, icon);
100         }
101
102         *ext = '.';
103
104         for (i = 0; i < icon->icons_count; ++i)
105             if (!strcmp(icon->icons[i], entry->path))
106                 break;
107
108         if (i != icon->icons_count)
109             continue;
110
111         icon->icons = realloc(icon->icons, sizeof (char *) * (icon->icons_count + 1));
112         icon->icons[icon->icons_count] = eina_stringshare_add(entry->path);
113         eina_array_push(strs, icon->icons[icon->icons_count++]);
114     }
115
116     eina_iterator_free(it);
117
118     return EINA_TRUE;
119 }
120
121 static Eina_Bool
122 cache_fallback_scan(Eina_Hash *icons, Eina_Hash *dirs)
123 {
124     unsigned int i;
125     Eina_List *xdg_dirs, *l;
126     const char *dir;
127     char path[PATH_MAX];
128
129     for (i = 0; i < extra_dirs->count; i++)
130         cache_fallback_scan_dir(icons, dirs, extra_dirs->data[i]);
131
132     cache_fallback_scan_dir(icons, dirs, efreet_icon_deprecated_user_dir_get());
133     cache_fallback_scan_dir(icons, dirs, efreet_icon_user_dir_get());
134
135     xdg_dirs = efreet_data_dirs_get();
136     EINA_LIST_FOREACH(xdg_dirs, l, dir)
137     {
138         snprintf(path, sizeof(path), "%s/icons", dir);
139         cache_fallback_scan_dir(icons, dirs, path);
140     }
141
142 #ifndef STRICT_SPEC
143     EINA_LIST_FOREACH(xdg_dirs, l, dir)
144     {
145         snprintf(path, sizeof(path), "%s/pixmaps", dir);
146         cache_fallback_scan_dir(icons, dirs, path);
147     }
148 #endif
149
150     cache_fallback_scan_dir(icons, dirs, "/usr/share/pixmaps");
151
152     return EINA_TRUE;
153 }
154
155 static Eina_Bool
156 check_fallback_changed(Efreet_Cache_Icon_Theme *theme)
157 {
158     unsigned int i;
159     Eina_List *xdg_dirs, *l;
160     const char *dir;
161     char path[PATH_MAX];
162
163     /* Check if the dirs we have cached are changed */
164     if (theme->dirs)
165     {
166         Eina_Iterator *it;
167         Eina_Bool changed = EINA_FALSE;
168
169         it = eina_hash_iterator_key_new(theme->dirs);
170         EINA_ITERATOR_FOREACH(it, dir)
171         {
172             changed = !ecore_file_exists(dir);
173             if (changed) break;
174             changed = cache_directory_modified(theme->dirs, dir);
175             if (changed) break;
176         }
177         eina_iterator_free(it);
178         if (changed) return EINA_TRUE;
179     }
180
181     /* Check if spec dirs have changed */
182     for (i = 0; i < extra_dirs->count; i++)
183         if (cache_directory_modified(theme->dirs, extra_dirs->data[i])) return EINA_TRUE;
184
185     if (cache_directory_modified(theme->dirs, efreet_icon_deprecated_user_dir_get())) return EINA_TRUE;
186     if (cache_directory_modified(theme->dirs, efreet_icon_user_dir_get())) return EINA_TRUE;
187
188     xdg_dirs = efreet_data_dirs_get();
189     EINA_LIST_FOREACH(xdg_dirs, l, dir)
190     {
191         snprintf(path, sizeof(path), "%s/icons", dir);
192         if (cache_directory_modified(theme->dirs, path)) return EINA_TRUE;
193     }
194
195 #ifndef STRICT_SPEC
196     EINA_LIST_FOREACH(xdg_dirs, l, dir)
197     {
198         snprintf(path, sizeof(path), "%s/pixmaps", dir);
199         if (cache_directory_modified(theme->dirs, path)) return EINA_TRUE;
200     }
201 #endif
202
203     if (cache_directory_modified(theme->dirs, "/usr/share/pixmaps")) return EINA_TRUE;
204     return EINA_FALSE;
205 }
206
207 static Eina_Bool
208 cache_scan_path_dir(Efreet_Icon_Theme *theme,
209                     const char *path,
210                     Efreet_Icon_Theme_Directory *dir,
211                     Eina_Hash *icons)
212 {
213     Eina_Iterator *it;
214     char buf[PATH_MAX];
215     Eina_File_Direct_Info *entry;
216
217     snprintf(buf, sizeof(buf), "%s/%s", path, dir->name);
218
219     it = eina_file_stat_ls(buf);
220     if (!it) return EINA_TRUE;
221
222     EINA_ITERATOR_FOREACH(it, entry)
223     {
224         Efreet_Cache_Icon *icon;
225         char *name;
226         char *ext;
227         unsigned int i;
228
229         if (entry->type == EINA_FILE_DIR)
230             continue;
231
232         ext = strrchr(entry->path + entry->name_start, '.');
233         if (!ext || !cache_extension_lookup(ext))
234             continue;
235
236         /* icon with known extension */
237         name = entry->path + entry->name_start;
238         *ext = '\0';
239
240         icon = eina_hash_find(icons, name);
241         if (!icon)
242         {
243             icon = NEW(Efreet_Cache_Icon, 1);
244             icon->theme = eina_stringshare_add(theme->name.internal);
245             eina_array_push(strs, icon->theme);
246             eina_hash_add(icons, name, icon);
247         }
248         else if (icon->theme && strcmp(icon->theme, theme->name.internal))
249         {
250             /* We got this icon from a parent theme */
251             continue;
252         }
253
254         /* find if we have the same icon in another type */
255         for (i = 0; i < icon->icons_count; ++i)
256         {
257             if ((icon->icons[i]->type == dir->type) &&
258                 (icon->icons[i]->normal == dir->size.normal) &&
259                 (icon->icons[i]->max == dir->size.max) &&
260                 (icon->icons[i]->min == dir->size.min))
261                 break;
262         }
263
264         *ext = '.';
265
266         if (i != icon->icons_count)
267         {
268             unsigned int j;
269
270             /* check if the path already exist */
271             for (j = 0; j < icon->icons[i]->paths_count; ++j)
272                 if (!strcmp(icon->icons[i]->paths[j], entry->path))
273                     break;
274
275             if (j != icon->icons[i]->paths_count)
276                 continue;
277         }
278         /* no icon match so add a new one */
279         else
280         {
281             icon->icons = realloc(icon->icons,
282                                   sizeof (Efreet_Cache_Icon_Element*) * (++icon->icons_count));
283             icon->icons[i] = NEW(Efreet_Cache_Icon_Element, 1);
284             icon->icons[i]->type = dir->type;
285             icon->icons[i]->normal = dir->size.normal;
286             icon->icons[i]->min = dir->size.min;
287             icon->icons[i]->max = dir->size.max;
288             icon->icons[i]->paths = NULL;
289             icon->icons[i]->paths_count = 0;
290         }
291
292         /* and finally store the path */
293         icon->icons[i]->paths = realloc(icon->icons[i]->paths,
294                                         sizeof (char*) * (icon->icons[i]->paths_count + 1));
295         icon->icons[i]->paths[icon->icons[i]->paths_count] = eina_stringshare_add(entry->path);
296         eina_array_push(strs, icon->icons[i]->paths[icon->icons[i]->paths_count++]);
297     }
298
299     eina_iterator_free(it);
300
301     return EINA_TRUE;
302 }
303
304 static Eina_Bool
305 cache_scan_path(Efreet_Icon_Theme *theme, Eina_Hash *icons, const char *path)
306 {
307     Eina_List *l;
308     Efreet_Icon_Theme_Directory *dir;
309
310     EINA_LIST_FOREACH(theme->directories, l, dir)
311         if (!cache_scan_path_dir(theme, path, dir, icons)) return EINA_FALSE;
312
313     return EINA_TRUE;
314 }
315
316 static Eina_Bool
317 cache_scan(Efreet_Icon_Theme *theme, Eina_Hash *themes, Eina_Hash *icons)
318 {
319     Eina_List *l;
320     const char *path;
321     const char *name;
322
323     if (!theme) return EINA_TRUE;
324     if (eina_hash_find(themes, theme->name.internal)) return EINA_TRUE;
325     eina_hash_direct_add(themes, theme->name.internal, theme);
326
327     /* scan theme */
328     EINA_LIST_FOREACH(theme->paths, l, path)
329         if (!cache_scan_path(theme, icons, path)) return EINA_FALSE;
330
331     /* scan inherits */
332     if (theme->inherits)
333     {
334         EINA_LIST_FOREACH(theme->inherits, l, name)
335         {
336             Efreet_Icon_Theme *inherit;
337
338             inherit = eina_hash_find(icon_themes, name);
339             if (!inherit)
340                 INF("Theme `%s` not found for `%s`.",
341                     name, theme->name.internal);
342             if (!cache_scan(inherit, themes, icons)) return EINA_FALSE;
343         }
344     }
345     else if (strcmp(theme->name.internal, "hicolor"))
346     {
347         theme = eina_hash_find(icon_themes, "hicolor");
348         if (!cache_scan(theme, themes, icons)) return EINA_FALSE;
349     }
350
351     return EINA_TRUE;
352 }
353
354 static Eina_Bool
355 check_changed(Efreet_Cache_Icon_Theme *theme)
356 {
357     Eina_List *l;
358     const char *name;
359
360     if (!theme) return EINA_FALSE;
361
362     if (theme->changed) return EINA_TRUE;
363     if (theme->theme.inherits)
364     {
365         EINA_LIST_FOREACH(theme->theme.inherits, l, name)
366         {
367             Efreet_Cache_Icon_Theme *inherit;
368
369             inherit = eina_hash_find(icon_themes, name);
370             if (!inherit)
371                 INF("Theme `%s` not found for `%s`.",
372                         name, theme->theme.name.internal);
373             if (check_changed(inherit)) return EINA_TRUE;
374         }
375     }
376     else if (strcmp(theme->theme.name.internal, "hicolor"))
377     {
378         theme = eina_hash_find(icon_themes, "hicolor");
379         if (check_changed(theme)) return EINA_TRUE;
380     }
381     return EINA_FALSE;
382 }
383
384 static Efreet_Icon_Theme_Directory *
385 icon_theme_directory_new(Efreet_Ini *ini, const char *name)
386 {
387     Efreet_Icon_Theme_Directory *dir;
388     int val;
389     const char *tmp;
390
391     if (!ini) return NULL;
392
393     dir = NEW(Efreet_Icon_Theme_Directory, 1);
394     if (!dir) return NULL;
395     dir->name = eina_stringshare_add(name);
396     eina_array_push(strs, dir->name);
397
398     efreet_ini_section_set(ini, name);
399
400     tmp = efreet_ini_string_get(ini, "Context");
401     if (tmp)
402     {
403         if (!strcasecmp(tmp, "Actions"))
404             dir->context = EFREET_ICON_THEME_CONTEXT_ACTIONS;
405
406         else if (!strcasecmp(tmp, "Devices"))
407             dir->context = EFREET_ICON_THEME_CONTEXT_DEVICES;
408
409         else if (!strcasecmp(tmp, "FileSystems"))
410             dir->context = EFREET_ICON_THEME_CONTEXT_FILESYSTEMS;
411
412         else if (!strcasecmp(tmp, "MimeTypes"))
413             dir->context = EFREET_ICON_THEME_CONTEXT_MIMETYPES;
414     }
415
416     /* Threshold is fallback  */
417     dir->type = EFREET_ICON_SIZE_TYPE_THRESHOLD;
418
419     tmp = efreet_ini_string_get(ini, "Type");
420     if (tmp)
421     {
422         if (!strcasecmp(tmp, "Fixed"))
423             dir->type = EFREET_ICON_SIZE_TYPE_FIXED;
424
425         else if (!strcasecmp(tmp, "Scalable"))
426             dir->type = EFREET_ICON_SIZE_TYPE_SCALABLE;
427     }
428
429     dir->size.normal = efreet_ini_int_get(ini, "Size");
430
431     if (dir->type == EFREET_ICON_SIZE_TYPE_THRESHOLD)
432     {
433         val = efreet_ini_int_get(ini, "Threshold");
434         if (val < 0) val = 2;
435         dir->size.max = dir->size.normal + val;
436         dir->size.min = dir->size.normal - val;
437     }
438     else if (dir->type == EFREET_ICON_SIZE_TYPE_SCALABLE)
439     {
440         val = efreet_ini_int_get(ini, "MinSize");
441         if (val < 0) dir->size.min = dir->size.normal;
442         else dir->size.min = val;
443
444         val = efreet_ini_int_get(ini, "MaxSize");
445         if (val < 0) dir->size.max = dir->size.normal;
446         else dir->size.max = val;
447     }
448
449     return dir;
450 }
451
452 static Eina_Bool
453 icon_theme_index_read(Efreet_Cache_Icon_Theme *theme, const char *path)
454 {
455     Efreet_Ini *ini;
456     Efreet_Icon_Theme_Directory *dir;
457     const char *tmp;
458     struct stat st;
459     char rp[PATH_MAX];
460
461     if (!theme || !path) return EINA_FALSE;
462
463     if (!realpath(path, rp)) return EINA_FALSE;
464
465     if (stat(rp, &st) < 0) return EINA_FALSE;
466     if (theme->path && !strcmp(theme->path, rp) && theme->last_cache_check >= (long long) st.st_mtime)
467     {
468         /* no change */
469         theme->valid = 1;
470         return EINA_TRUE;
471     }
472     if (!theme->path || strcmp(theme->path, rp))
473     {
474         theme->path = eina_stringshare_add(rp);
475         eina_array_push(strs, theme->path);
476     }
477     if ((long long) st.st_mtime > theme->last_cache_check)
478         theme->last_cache_check = (long long) st.st_mtime;
479     theme->changed = 1;
480
481     ini = efreet_ini_new(path);
482     if (!ini) return EINA_FALSE;
483     if (!ini->data)
484     {
485         efreet_ini_free(ini);
486         return EINA_FALSE;
487     }
488
489     efreet_ini_section_set(ini, "Icon Theme");
490     tmp = efreet_ini_localestring_get(ini, "Name");
491     if (tmp)
492     {
493         theme->theme.name.name = eina_stringshare_add(tmp);
494         eina_array_push(strs, theme->theme.name.name);
495     }
496
497     tmp = efreet_ini_localestring_get(ini, "Comment");
498     if (tmp)
499     {
500         theme->theme.comment = eina_stringshare_add(tmp);
501         eina_array_push(strs, theme->theme.comment);
502     }
503
504     tmp = efreet_ini_string_get(ini, "Example");
505     if (tmp)
506     {
507         theme->theme.example_icon = eina_stringshare_add(tmp);
508         eina_array_push(strs, theme->theme.example_icon);
509     }
510
511     theme->hidden = efreet_ini_boolean_get(ini, "Hidden");
512
513     theme->valid = 1;
514
515     /* Check the inheritance. If there is none we inherit from the hicolor theme */
516     tmp = efreet_ini_string_get(ini, "Inherits");
517     if (tmp)
518     {
519         char *t, *s, *p;
520         const char *i;
521         size_t len;
522
523         len = strlen(tmp) + 1;
524         t = alloca(len);
525         memcpy(t, tmp, len);
526         s = t;
527         p = strchr(s, ',');
528
529         while (p)
530         {
531             *p = '\0';
532
533             i = eina_stringshare_add(s);
534             theme->theme.inherits = eina_list_append(theme->theme.inherits, i);
535             eina_array_push(strs, i);
536             s = ++p;
537             p = strchr(s, ',');
538         }
539         i = eina_stringshare_add(s);
540         theme->theme.inherits = eina_list_append(theme->theme.inherits, i);
541         eina_array_push(strs, i);
542     }
543
544     /* make sure this one is done last as setting the directory will change
545      * the ini section ... */
546     tmp = efreet_ini_string_get(ini, "Directories");
547     if (tmp)
548     {
549         char *t, *s, *p;
550         size_t len;
551
552         len = strlen(tmp) + 1;
553         t = alloca(len);
554         memcpy(t, tmp, len);
555         s = t;
556         p = s;
557
558         while (p)
559         {
560             p = strchr(s, ',');
561
562             if (p) *p = '\0';
563
564             dir = icon_theme_directory_new(ini, s);
565             if (!dir) goto error;
566             theme->theme.directories = eina_list_append(theme->theme.directories, dir);
567
568             if (p) s = ++p;
569         }
570     }
571
572 error:
573     efreet_ini_free(ini);
574
575     return EINA_TRUE;
576 }
577
578 static Eina_Bool
579 cache_theme_scan(const char *dir)
580 {
581     Eina_Iterator *it;
582     Eina_File_Direct_Info *entry;
583
584     it = eina_file_stat_ls(dir);
585     if (!it) return EINA_TRUE;
586
587     EINA_ITERATOR_FOREACH(it, entry)
588     {
589         Efreet_Cache_Icon_Theme *theme;
590         const char *name;
591         const char *path;
592         char buf[PATH_MAX];
593         struct stat st;
594
595         if (stat(entry->path, &st) < 0) continue;
596
597         if ((entry->type != EINA_FILE_DIR) &&
598             (entry->type != EINA_FILE_LNK))
599             continue;
600
601         name = entry->path + entry->name_start;
602         theme = eina_hash_find(icon_themes, name);
603
604         if (!theme)
605         {
606             theme = NEW(Efreet_Cache_Icon_Theme, 1);
607             theme->theme.name.internal = eina_stringshare_add(name);
608             eina_array_push(strs, theme->theme.name.internal);
609             eina_hash_direct_add(icon_themes,
610                           (void *)theme->theme.name.internal, theme);
611             theme->changed = 1;
612         }
613         if ((long long) st.st_mtime > theme->last_cache_check)
614         {
615             theme->last_cache_check = (long long) st.st_mtime;
616             theme->changed = 1;
617         }
618
619         /* TODO: We need to handle change in order of included paths */
620         if (!eina_list_search_unsorted(theme->theme.paths, EINA_COMPARE_CB(strcmp), entry->path))
621         {
622             path = eina_stringshare_add(entry->path);
623             theme->theme.paths = eina_list_append(theme->theme.paths, path);
624             eina_array_push(strs, path);
625             theme->changed = 1;
626         }
627
628         /* we're already valid so no reason to check for an index.theme file */
629         if (theme->valid) continue;
630
631         /* if the index.theme file exists we parse it into the theme */
632         memcpy(buf, entry->path, entry->path_length);
633         memcpy(buf + entry->path_length, "/index.theme", sizeof("/index.theme"));
634         if (ecore_file_exists(buf))
635         {
636             if (!icon_theme_index_read(theme, buf))
637                 theme->valid = 0;
638         }
639     }
640     eina_iterator_free(it);
641     return EINA_TRUE;
642 }
643
644 static int
645 cache_lock_file(void)
646 {
647     char file[PATH_MAX];
648     struct flock fl;
649     int lockfd;
650
651     snprintf(file, sizeof(file), "%s/efreet/icon_data.lock", efreet_cache_home_get());
652     lockfd = open(file, O_CREAT | O_RDWR, S_IRUSR | S_IWUSR);
653     if (lockfd < 0) return -1;
654     efreet_fsetowner(lockfd);
655
656     memset(&fl, 0, sizeof(struct flock));
657     fl.l_type = F_WRLCK;
658     fl.l_whence = SEEK_SET;
659     if (fcntl(lockfd, F_SETLK, &fl) < 0)
660     {
661         WRN("LOCKED! You may want to delete %s if this persists", file);
662         close(lockfd);
663         return -1;
664     }
665
666     return lockfd;
667 }
668
669 static void
670 icon_theme_free(Efreet_Cache_Icon_Theme *theme)
671 {
672     void *data;
673
674     eina_list_free(theme->theme.paths);
675     eina_list_free(theme->theme.inherits);
676     EINA_LIST_FREE(theme->theme.directories, data)
677         free(data);
678     if (theme->dirs) efreet_hash_free(theme->dirs, free);
679     free(theme);
680 }
681
682 /**
683  * @internal
684  * @return EINA_TRUE if data adds new
685  */
686 static Eina_Bool
687 add_data(Eet_File *ef, Eina_Array *data, const char *key)
688 {
689     Efreet_Cache_Array_String *add;
690     unsigned int i, j;
691     Eina_Bool added = EINA_FALSE;
692
693     add = eet_data_read(ef, efreet_array_string_edd(), key);
694     if (!add) return EINA_TRUE;
695     /* loop once to check added */
696     for (i = 0; i < data->count; i++)
697     {
698         int found = 0;
699         for (j = 0; j < add->array_count; ++j)
700         {
701             if (!strcmp(add->array[j], data->data[i]))
702             {
703                 found = 1;
704                 break;
705             }
706         }
707         if (!found)
708         {
709             added = EINA_TRUE;
710             break;
711         }
712     }
713     /* loop again to add all data */
714     for (i = 0; i < add->array_count; i++)
715     {
716         int found = 0;
717         for (j = 0; j < data->count; ++j)
718         {
719             if (!strcmp(add->array[i], data->data[j]))
720             {
721                 found = 1;
722                 break;
723             }
724         }
725         if (!found)
726             eina_array_push(data, add->array[i]);
727     }
728     IF_FREE(add->array);
729     free(add);
730
731     return added;
732 }
733
734
735 static void
736 save_data(Eet_File *ef, Eina_Array *data, const char *key)
737 {
738     Efreet_Cache_Array_String *save;
739     unsigned int i;
740
741     if (!data || !data->count) return;
742
743     save = NEW(Efreet_Cache_Array_String, 1);
744     save->array = NEW(char *, data->count);
745     save->array_count = 0;
746     for (i = 0; i < data->count; ++i)
747         save->array[save->array_count++] = data->data[i];
748     eet_data_write(ef, efreet_array_string_edd(), key, save, 1);
749     IF_FREE(save->array);
750     free(save);
751 }
752
753 int
754 main(int argc, char **argv)
755 {
756     /* TODO:
757      * - Add file monitor on files, so that we catch changes on files
758      *   during whilst this program runs.
759      * - Maybe linger for a while to reduce number of cache re-creates.
760      */
761     Eina_Iterator *it;
762     Efreet_Cache_Version *icon_version;
763     Efreet_Cache_Version *theme_version;
764     Efreet_Cache_Icon_Theme *theme;
765     Eet_Data_Descriptor *theme_edd;
766     Eet_Data_Descriptor *icon_edd;
767     Eet_Data_Descriptor *fallback_edd;
768     Eet_File *icon_ef;
769     Eet_File *theme_ef;
770     Eina_List *xdg_dirs = NULL;
771     Eina_List *l = NULL;
772     char file[PATH_MAX];
773     const char *path;
774     char *dir = NULL;
775     Eina_Bool changed = EINA_FALSE;
776     Eina_Bool flush = EINA_FALSE;
777     int lockfd = -1;
778     int tmpfd = -1;
779     char **keys;
780     int num, i;
781
782     /* init external subsystems */
783     if (!eina_init()) return -1;
784     _efreet_icon_cache_log_dom =
785         eina_log_domain_register("efreet_icon_cache", EFREET_DEFAULT_LOG_COLOR);
786     if (_efreet_icon_cache_log_dom < 0)
787     {
788         EINA_LOG_ERR("Efreet: Could not create a log domain for efreet_icon_cache.");
789         return -1;
790     }
791
792     eina_log_domain_level_set("efreet_icon_cache", EINA_LOG_LEVEL_ERR);
793
794     exts = eina_array_new(10);
795     extra_dirs = eina_array_new(10);
796
797     for (i = 1; i < argc; i++)
798     {
799         if (!strcmp(argv[i], "-v"))
800             eina_log_domain_level_set("efreet_icon_cache", EINA_LOG_LEVEL_DBG);
801         else if ((!strcmp(argv[i], "-h")) ||
802                  (!strcmp(argv[i], "-help")) ||
803                  (!strcmp(argv[i], "--h")) ||
804                  (!strcmp(argv[i], "--help")))
805         {
806             printf("Options:\n");
807             printf("  -v              Verbose mode\n");
808             printf("  -e .ext1 .ext2  Extensions\n");
809             printf("  -d dir1 dir2    Extra dirs\n");
810             exit(0);
811         }
812         else if (!strcmp(argv[i], "-e"))
813         {
814             while ((i < (argc - 1)) && (argv[(i + 1)][0] != '-'))
815                 eina_array_push(exts, argv[++i]);
816         }
817         else if (!strcmp(argv[i], "-d"))
818         {
819             while ((i < (argc - 1)) && (argv[(i + 1)][0] != '-'))
820                 eina_array_push(extra_dirs, argv[++i]);
821         }
822     }
823
824     if (!eet_init()) return -1;
825     if (!ecore_init()) return -1;
826
827     efreet_cache_update = 0;
828     /* finish efreet init */
829     if (!efreet_init()) goto on_error;
830
831     strs = eina_array_new(32);
832
833     /* create homedir */
834     snprintf(file, sizeof(file), "%s/efreet", efreet_cache_home_get());
835     if (!ecore_file_exists(file))
836     {
837         if (!ecore_file_mkpath(file)) return -1;
838         efreet_setowner(file);
839     }
840
841     /* lock process, so that we only run one copy of this program */
842     lockfd = cache_lock_file();
843     if (lockfd == -1) goto on_error;
844
845     /* Need to init edd's, so they are like we want, not like userspace wants */
846     icon_edd = efreet_icon_edd();
847     fallback_edd = efreet_icon_fallback_edd();
848     theme_edd = efreet_icon_theme_edd(EINA_TRUE);
849
850     icon_themes = eina_hash_string_superfast_new(EINA_FREE_CB(icon_theme_free));
851
852     INF("opening theme cache");
853     /* open theme file */
854     theme_ef = eet_open(efreet_icon_theme_cache_file(), EET_FILE_MODE_READ_WRITE);
855     if (!theme_ef) goto on_error_efreet;
856     theme_version = eet_data_read(theme_ef, efreet_version_edd(), EFREET_CACHE_VERSION);
857     if (theme_version &&
858         ((theme_version->major != EFREET_ICON_CACHE_MAJOR) ||
859          (theme_version->minor != EFREET_ICON_CACHE_MINOR)))
860     {
861         // delete old cache
862         eet_close(theme_ef);
863         if (unlink(efreet_icon_theme_cache_file()) < 0)
864         {
865             if (errno != ENOENT) goto on_error_efreet;
866         }
867         theme_ef = eet_open(efreet_icon_theme_cache_file(), EET_FILE_MODE_READ_WRITE);
868         if (!theme_ef) goto on_error_efreet;
869     }
870     if (!theme_version)
871         theme_version = NEW(Efreet_Cache_Version, 1);
872
873     theme_version->major = EFREET_ICON_CACHE_MAJOR;
874     theme_version->minor = EFREET_ICON_CACHE_MINOR;
875
876     if (add_data(theme_ef, exts, EFREET_CACHE_ICON_EXTENSIONS))
877         flush = EINA_TRUE;
878     if (add_data(theme_ef, extra_dirs, EFREET_CACHE_ICON_EXTRA_DIRS))
879         flush = EINA_TRUE;
880     if (flush)
881         changed = EINA_TRUE;
882
883     if (exts->count == 0)
884     {
885         ERR("Need to pass extensions to icon cache create process");
886         goto on_error_efreet;
887     }
888
889     keys = eet_list(theme_ef, "*", &num);
890     if (keys)
891     {
892         for (i = 0; i < num; i++)
893         {
894             if (!strncmp(keys[i], "__efreet", 8)) continue;
895             theme = eet_data_read(theme_ef, theme_edd, keys[i]);
896             if (theme)
897             {
898                 theme->valid = 0;
899                 eina_hash_direct_add(icon_themes, theme->theme.name.internal, theme);
900             }
901         }
902         free(keys);
903     }
904
905     INF("scan for themes");
906     /* scan themes */
907     cache_theme_scan(efreet_icon_deprecated_user_dir_get());
908     cache_theme_scan(efreet_icon_user_dir_get());
909
910     xdg_dirs = efreet_data_dirs_get();
911     EINA_LIST_FOREACH(xdg_dirs, l, dir)
912     {
913         snprintf(file, sizeof(file), "%s/icons", dir);
914         cache_theme_scan(file);
915     }
916
917 #ifndef STRICT_SPEC
918     EINA_LIST_FOREACH(xdg_dirs, l, dir)
919     {
920         snprintf(file, sizeof(file), "%s/pixmaps", dir);
921         cache_theme_scan(file);
922     }
923 #endif
924
925     cache_theme_scan("/usr/share/pixmaps");
926
927     /* scan icons */
928     it = eina_hash_iterator_data_new(icon_themes);
929     EINA_ITERATOR_FOREACH(it, theme)
930     {
931         if (!theme->valid) continue;
932 #ifndef STRICT_SPEC
933         if (!theme->theme.name.name) continue;
934 #endif
935         INF("scan theme %s", theme->theme.name.name);
936
937         theme->changed = check_changed(theme);
938         if (flush)
939             theme->changed = EINA_TRUE;
940
941         INF("open icon file");
942         /* open icon file */
943         icon_ef = eet_open(efreet_icon_cache_file(theme->theme.name.internal), EET_FILE_MODE_READ_WRITE);
944         if (!icon_ef) goto on_error_efreet;
945         icon_version = eet_data_read(icon_ef, efreet_version_edd(), EFREET_CACHE_VERSION);
946         if (theme->changed || (icon_version &&
947             ((icon_version->major != EFREET_ICON_CACHE_MAJOR) ||
948              (icon_version->minor != EFREET_ICON_CACHE_MINOR))))
949         {
950             // delete old cache
951             eet_close(icon_ef);
952             if (unlink(efreet_icon_cache_file(theme->theme.name.internal)) < 0)
953             {
954                 if (errno != ENOENT) goto on_error_efreet;
955             }
956             icon_ef = eet_open(efreet_icon_cache_file(theme->theme.name.internal), EET_FILE_MODE_READ_WRITE);
957             if (!icon_ef) goto on_error_efreet;
958             theme->changed = EINA_TRUE;
959         }
960
961         if (theme->changed)
962             changed = EINA_TRUE;
963
964         if (theme->changed)
965         {
966             Eina_Hash *themes;
967             Eina_Hash *icons;
968
969             if (!icon_version)
970                 icon_version = NEW(Efreet_Cache_Version, 1);
971
972             icon_version->major = EFREET_ICON_CACHE_MAJOR;
973             icon_version->minor = EFREET_ICON_CACHE_MINOR;
974
975             themes = eina_hash_string_superfast_new(NULL);
976             icons = eina_hash_string_superfast_new(NULL);
977
978             INF("scan icons\n");
979             if (cache_scan(&(theme->theme), themes, icons))
980             {
981                 Eina_Iterator *icons_it;
982                 Eina_Hash_Tuple *tuple;
983
984                 INF("generated: '%s' %i (%i)",
985                     theme->theme.name.internal,
986                     changed,
987                     eina_hash_population(icons));
988
989                 icons_it = eina_hash_iterator_tuple_new(icons);
990                 EINA_ITERATOR_FOREACH(icons_it, tuple)
991                     eet_data_write(icon_ef, icon_edd, tuple->key, tuple->data, 1);
992                 eina_iterator_free(icons_it);
993
994                 INF("theme change: %s %lld", theme->theme.name.internal, theme->last_cache_check);
995                 eet_data_write(theme_ef, theme_edd, theme->theme.name.internal, theme, 1);
996             }
997             eina_hash_free(themes);
998             eina_hash_free(icons);
999         }
1000
1001         eet_data_write(icon_ef, efreet_version_edd(), EFREET_CACHE_VERSION, icon_version, 1);
1002         eet_close(icon_ef);
1003         efreet_setowner(efreet_icon_cache_file(theme->theme.name.internal));
1004         free(icon_version);
1005     }
1006     eina_iterator_free(it);
1007
1008     INF("scan fallback icons");
1009     theme = eet_data_read(theme_ef, theme_edd, EFREET_CACHE_ICON_FALLBACK);
1010     if (!theme)
1011     {
1012         theme = NEW(Efreet_Cache_Icon_Theme, 1);
1013         theme->changed = EINA_TRUE;
1014     }
1015     if (flush)
1016         theme->changed = EINA_TRUE;
1017
1018     INF("open fallback file");
1019     /* open icon file */
1020     icon_ef = eet_open(efreet_icon_cache_file(EFREET_CACHE_ICON_FALLBACK), EET_FILE_MODE_READ_WRITE);
1021     if (!icon_ef) goto on_error_efreet;
1022     icon_version = eet_data_read(icon_ef, efreet_version_edd(), EFREET_CACHE_VERSION);
1023     if (theme->changed || (icon_version &&
1024         ((icon_version->major != EFREET_ICON_CACHE_MAJOR) ||
1025          (icon_version->minor != EFREET_ICON_CACHE_MINOR))))
1026     {
1027         // delete old cache
1028         eet_close(icon_ef);
1029         if (unlink(efreet_icon_cache_file(EFREET_CACHE_ICON_FALLBACK)) < 0)
1030         {
1031             if (errno != ENOENT) goto on_error_efreet;
1032         }
1033         icon_ef = eet_open(efreet_icon_cache_file(EFREET_CACHE_ICON_FALLBACK), EET_FILE_MODE_READ_WRITE);
1034         if (!icon_ef) goto on_error_efreet;
1035         theme->changed = EINA_TRUE;
1036     }
1037     if (!theme->changed)
1038         theme->changed = check_fallback_changed(theme);
1039     if (theme->changed && theme->dirs)
1040     {
1041         efreet_hash_free(theme->dirs, free);
1042         theme->dirs = NULL;
1043     }
1044     if (!theme->dirs)
1045         theme->dirs = eina_hash_string_superfast_new(NULL);
1046
1047     if (theme->changed)
1048         changed = EINA_TRUE;
1049
1050     if (theme->changed)
1051     {
1052         Eina_Hash *icons;
1053
1054         if (!icon_version)
1055             icon_version = NEW(Efreet_Cache_Version, 1);
1056
1057         icon_version->major = EFREET_ICON_CACHE_MAJOR;
1058         icon_version->minor = EFREET_ICON_CACHE_MINOR;
1059
1060         icons = eina_hash_string_superfast_new(NULL);
1061
1062         INF("scan fallback icons");
1063         /* Save fallback in the right part */
1064         if (cache_fallback_scan(icons, theme->dirs))
1065         {
1066             Eina_Iterator *icons_it;
1067             Eina_Hash_Tuple *tuple;
1068
1069             INF("generated: fallback %i (%i)", theme->changed, eina_hash_population(icons));
1070
1071             icons_it = eina_hash_iterator_tuple_new(icons);
1072             EINA_ITERATOR_FOREACH(icons_it, tuple)
1073                 eet_data_write(icon_ef, fallback_edd, tuple->key, tuple->data, 1);
1074             eina_iterator_free(icons_it);
1075         }
1076         eina_hash_free(icons);
1077
1078         eet_data_write(theme_ef, theme_edd, EFREET_CACHE_ICON_FALLBACK, theme, 1);
1079     }
1080
1081     icon_theme_free(theme);
1082
1083     eet_data_write(icon_ef, efreet_version_edd(), EFREET_CACHE_VERSION, icon_version, 1);
1084     eet_close(icon_ef);
1085     efreet_setowner(efreet_icon_cache_file(EFREET_CACHE_ICON_FALLBACK));
1086     free(icon_version);
1087
1088     eina_hash_free(icon_themes);
1089
1090     /* save data */
1091     eet_data_write(theme_ef, efreet_version_edd(), EFREET_CACHE_VERSION, theme_version, 1);
1092     save_data(theme_ef, exts, EFREET_CACHE_ICON_EXTENSIONS);
1093     save_data(theme_ef, extra_dirs, EFREET_CACHE_ICON_EXTRA_DIRS);
1094
1095     eet_close(theme_ef);
1096     efreet_setowner(efreet_icon_theme_cache_file());
1097     free(theme_version);
1098
1099     /* touch update file */
1100     snprintf(file, sizeof(file), "%s/efreet/icon_data.update", efreet_cache_home_get());
1101     tmpfd = open(file, O_CREAT | O_WRONLY, S_IRUSR | S_IWUSR);
1102     if (tmpfd >= 0)
1103     {
1104         char c = 'n';
1105
1106         efreet_fsetowner(tmpfd);
1107         if (changed) c = 'c';
1108         if (write(tmpfd, &c, 1) != 1) perror("write");
1109         close(tmpfd);
1110     }
1111
1112     INF("done");
1113 on_error_efreet:
1114     efreet_shutdown();
1115
1116 on_error:
1117     if (lockfd >= 0) close(lockfd);
1118
1119     while ((path = eina_array_pop(strs)))
1120         eina_stringshare_del(path);
1121     eina_array_free(strs);
1122     eina_array_free(exts);
1123     eina_array_free(extra_dirs);
1124
1125     ecore_shutdown();
1126     eet_shutdown();
1127     eina_log_domain_unregister(_efreet_icon_cache_log_dom);
1128     eina_shutdown();
1129
1130     return 0;
1131 }