02c3fac1c3aa0386a9f064f38b181bdd760f6338
[framework/uifw/efreet.git] / src / lib / efreet_icon.c
1 /* vim: set sw=4 ts=4 sts=4 et: */
2 #include "Efreet.h"
3 #include "efreet_private.h"
4
5 static char *efreet_icon_deprecated_user_dir = NULL;
6 static char *efreet_icon_user_dir = NULL;
7 static Eina_Hash *efreet_icon_themes = NULL;
8 static Eina_List *efreet_icon_extensions = NULL;
9 static Eina_List *efreet_extra_icon_dirs = NULL;
10 static Eina_Hash *efreet_icon_cache = NULL;
11
12 static int efreet_icon_init_count = 0;
13
14 typedef struct Efreet_Icon_Cache Efreet_Icon_Cache;
15 struct Efreet_Icon_Cache
16 {
17     char *key;
18     char *path;
19     time_t lasttime;
20 };
21
22 static char *efreet_icon_remove_extension(const char *icon);
23 static Efreet_Icon_Theme *efreet_icon_find_theme_check(const char *theme_name);
24
25
26 static char *efreet_icon_find_fallback(Efreet_Icon_Theme *theme,
27                                        const char *icon,
28                                        unsigned int size);
29 static char *efreet_icon_list_find_fallback(Efreet_Icon_Theme *theme,
30                                             Eina_List *icons,
31                                             unsigned int size);
32 static char *efreet_icon_find_helper(Efreet_Icon_Theme *theme,
33                                      const char *icon, unsigned int size);
34 static char *efreet_icon_list_find_helper(Efreet_Icon_Theme *theme,
35                                           Eina_List *icons, unsigned int size);
36 static char *efreet_icon_lookup_icon(Efreet_Icon_Theme *theme,
37                                      const char *icon_name, unsigned int size);
38 static char *efreet_icon_fallback_icon(const char *icon_name);
39 static char *efreet_icon_fallback_dir_scan(const char *dir,
40                                            const char *icon_name);
41
42 static char *efreet_icon_lookup_directory(Efreet_Icon_Theme *theme,
43                                           Efreet_Icon_Theme_Directory *dir,
44                                           const char *icon_name);
45 static int efreet_icon_directory_size_distance(Efreet_Icon_Theme_Directory *dir,
46                                                     unsigned int size);
47 static int efreet_icon_directory_size_match(Efreet_Icon_Theme_Directory *dir,
48                                                   unsigned int size);
49
50 static Efreet_Icon *efreet_icon_new(const char *path);
51 static void efreet_icon_populate(Efreet_Icon *icon, const char *file);
52
53 static char *efreet_icon_lookup_directory_helper(Efreet_Icon_Theme_Directory *dir,
54                                     const char *path, const char *icon_name);
55
56 static Efreet_Icon_Theme *efreet_icon_theme_new(void);
57 static void efreet_icon_theme_free(Efreet_Icon_Theme *theme);
58 static void efreet_icon_theme_dir_scan_all(const char *theme_name);
59 static void efreet_icon_theme_dir_scan(const char *dir,
60                                         const char *theme_name);
61 static void efreet_icon_theme_dir_validity_check(void);
62 static void efreet_icon_theme_path_add(Efreet_Icon_Theme *theme,
63                                                 const char *path);
64 static void efreet_icon_theme_index_read(Efreet_Icon_Theme *theme,
65                                                 const char *path);
66
67 static Efreet_Icon_Theme_Directory *efreet_icon_theme_directory_new(Efreet_Ini *ini,
68                                                                 const char *name);
69 static void efreet_icon_theme_directory_free(Efreet_Icon_Theme_Directory *dir);
70
71 static void efreet_icon_theme_cache_check(Efreet_Icon_Theme *theme);
72 static int efreet_icon_theme_cache_check_dir(Efreet_Icon_Theme *theme,
73                                                         const char *dir);
74
75 static int efreet_icon_cache_find(Efreet_Icon_Cache *value, const char *key);
76 static void efreet_icon_cache_flush(Efreet_Icon_Theme *theme, Eina_List *list);
77 static void efreet_icon_cache_free(Efreet_Icon_Cache *value);
78 static char *efreet_icon_cache_check(Efreet_Icon_Theme *theme, const char *icon, unsigned int size);
79 static void efreet_icon_cache_add(Efreet_Icon_Theme *theme, const char *icon, unsigned int size, const char *value);
80
81 static Efreet_Icon_Theme *fake_null = NULL;
82
83 static void
84 _efreet_icon_cache_list_destroy(Eina_List *list)
85 {
86    Efreet_Icon_Cache *cache;
87
88    EINA_LIST_FREE(list, cache)
89      efreet_icon_cache_free(cache);
90 }
91
92 /**
93  * @internal
94  * @return Returns 1 on success or 0 on failure
95  * @brief Initializes the icon system
96  */
97 int
98 efreet_icon_init(void)
99 {
100     if (efreet_icon_init_count++ > 0)
101         return efreet_icon_init_count;
102
103     if (!efreet_icon_themes)
104     {
105         const char *default_exts[] = {".png", ".xpm", NULL};
106         int i;
107
108         if (!ecore_init())
109         {
110             efreet_icon_init_count--;
111             return 0;
112         }
113
114         /* setup the default extension list */
115         for (i = 0; default_exts[i] != NULL; i++)
116             efreet_icon_extensions = eina_list_append(efreet_icon_extensions, strdup(default_exts[i]));
117
118         efreet_icon_themes = eina_hash_string_superfast_new(EINA_FREE_CB(efreet_icon_theme_free));
119
120         efreet_extra_icon_dirs = NULL;
121         efreet_icon_cache = eina_hash_pointer_new(EINA_FREE_CB(_efreet_icon_cache_list_destroy));
122     }
123
124     return 1;
125 }
126
127 /**
128  * @internal
129  * @return Returns no value
130  * @brief Shuts down the icon system
131  */
132 void
133 efreet_icon_shutdown(void)
134 {
135     void *d;
136
137     if (--efreet_icon_init_count)
138         return;
139
140     IF_FREE(efreet_icon_user_dir);
141     IF_FREE(efreet_icon_deprecated_user_dir);
142
143     IF_FREE_LIST(efreet_icon_extensions, free);
144     IF_FREE_HASH(efreet_icon_themes);
145     efreet_extra_icon_dirs = eina_list_free(efreet_extra_icon_dirs);
146
147     IF_FREE_HASH(efreet_icon_cache);
148
149    if (fake_null)
150    {
151        efreet_icon_theme_free(fake_null);
152        fake_null = NULL;
153    }
154
155     ecore_shutdown();
156     efreet_icon_init_count = 0;
157 }
158
159 /**
160  * @return Returns the user icon directory
161  * @brief Returns the user icon directory
162  */
163 const char *
164 efreet_icon_deprecated_user_dir_get(void)
165 {
166     const char *user;
167     int len;
168
169     if (efreet_icon_deprecated_user_dir) return efreet_icon_deprecated_user_dir;
170
171     user = efreet_home_dir_get();
172     len = strlen(user) + strlen("/.icons") + 1;
173     efreet_icon_deprecated_user_dir = malloc(sizeof(char) * len);
174     snprintf(efreet_icon_deprecated_user_dir, len, "%s/.icons", user);
175
176     return efreet_icon_deprecated_user_dir;
177 }
178
179 EAPI const char *
180 efreet_icon_user_dir_get(void)
181 {
182     const char *user;
183     int len;
184
185     if (efreet_icon_user_dir) return efreet_icon_user_dir;
186
187     user = efreet_data_home_get();
188     len = strlen(user) + strlen("/icons") + 1;
189     efreet_icon_user_dir = malloc(sizeof(char) * len);
190     snprintf(efreet_icon_user_dir, len, "%s/icons", user);
191
192     return efreet_icon_user_dir;
193 }
194
195 /**
196  * @param ext: The extension to add to the list of checked extensions
197  * @return Returns no value.
198  * @brief Adds the given extension to the list of possible icon extensions
199  */
200 EAPI void
201 efreet_icon_extension_add(const char *ext)
202 {
203     efreet_icon_extensions  = eina_list_prepend(efreet_icon_extensions, strdup(ext));
204 }
205
206 /**
207  * @return Returns a list of strings that are paths to other icon directories
208  * @brief Gets the list of all extra directories to look for icons. These
209  * directories are used to look for icons after looking in the user icon dir
210  * and before looking in standard system directories. The order of search is
211  * from first to last directory in this list. the strings in the list should
212  * be created with eina_stringshare_add().
213  */
214 EAPI Eina_List **
215 efreet_icon_extra_list_get(void)
216 {
217     return &efreet_extra_icon_dirs;
218 }
219
220 static Eina_Bool
221 _hash_keys(Eina_Hash *hash __UNUSED__, const void *key, void *list)
222 {
223   *(Eina_List**)list = eina_list_append(*(Eina_List**)list, key);
224   return EINA_TRUE;
225 }
226 /**
227  * @return Returns a list of Efreet_Icon structs for all the non-hidden icon
228  * themes
229  * @brief Retrieves all of the non-hidden icon themes available on the system.
230  * The returned list must be freed. Do not free the list data.
231  */
232 EAPI Eina_List *
233 efreet_icon_theme_list_get(void)
234 {
235     Eina_List *list = NULL;
236     Eina_List *theme_list = NULL;
237     char *dir;
238     Eina_Iterator *it;
239
240     /* update the list to include all icon themes */
241     efreet_icon_theme_dir_scan_all(NULL);
242     efreet_icon_theme_dir_validity_check();
243
244     /* create the list for the user */
245     it = eina_hash_iterator_key_new(efreet_icon_themes);
246     eina_iterator_foreach(it, EINA_EACH(_hash_keys), &theme_list);
247     eina_iterator_free(it);
248
249     EINA_LIST_FREE(theme_list, dir)
250     {
251         Efreet_Icon_Theme *theme;
252
253         theme = eina_hash_find(efreet_icon_themes, dir);
254         if (theme->hidden || theme->fake) continue;
255 #ifndef STRICT_SPEC
256         if (!theme->name.name) continue;
257 #endif
258
259         list = eina_list_append(list, theme);
260     }
261
262     return list;
263 }
264
265 /**
266  * @param theme_name: The theme to look for
267  * @return Returns the icon theme related to the given theme name or NULL if
268  * none exists.
269  * @brief Tries to get the icon theme structure for the given theme name
270  */
271 EAPI Efreet_Icon_Theme *
272 efreet_icon_theme_find(const char *theme_name)
273 {
274     Efreet_Icon_Theme *theme;
275
276     if (!theme_name) return fake_null;
277
278     theme = eina_hash_find(efreet_icon_themes, theme_name);
279     if (!theme)
280     {
281         efreet_icon_theme_dir_scan_all(theme_name);
282         theme = eina_hash_find(efreet_icon_themes, theme_name);
283     }
284
285     return theme;
286 }
287
288 /**
289  * @internal
290  * @param icon: The icon name to strip extension
291  * @return Extension removed if in list of extensions, else untouched.
292  * @brief Removes extension from icon name if in list of extensions.
293  */
294 static char *
295 efreet_icon_remove_extension(const char *icon)
296 {
297     Eina_List *l;
298     char *tmp = NULL, *ext = NULL;
299
300     tmp = strdup(icon);
301     ext = strrchr(tmp, '.');
302     if (ext)
303     {
304         const char *ext2;
305         EINA_LIST_FOREACH(efreet_icon_extensions, l, ext2)
306         {
307             if (!strcmp(ext, ext2))
308             {
309 #ifdef STRICT_SPEC
310                 printf("[Efreet]: Requesting an icon with an extension: %s\n",
311                                                                         icon);
312 #endif
313                 *ext = '\0';
314                 break;
315             }
316         }
317     }
318
319     return tmp;
320 }
321
322 /**
323  * @internal
324  * @param theme_name: The icon theme to look for
325  * @return Returns the Efreet_Icon_Theme structure representing this theme
326  * or a new blank theme if not found
327  * @brief Retrieves a theme, or creates a blank one if not found.
328  */
329 static Efreet_Icon_Theme *
330 efreet_icon_find_theme_check(const char *theme_name)
331 {
332     Efreet_Icon_Theme *theme = NULL;
333     if (theme_name) theme = efreet_icon_theme_find(theme_name);
334     if (!theme)
335     {
336         if ((fake_null) && (!theme_name)) return fake_null;
337         theme = efreet_icon_theme_new();
338         theme->fake = 1;
339         if (theme_name)
340           {
341              theme->name.internal = eina_stringshare_add(theme_name);
342              eina_hash_del(efreet_icon_themes, (void *)theme->name.internal, NULL);
343              eina_hash_add(efreet_icon_themes, (void *)theme->name.internal, theme);
344           }
345         else
346           {
347              theme->name.internal = NULL;
348              fake_null = theme;
349           }
350     }
351
352     return theme;
353 }
354
355 /**
356  * @param theme_name: The icon theme to look for
357  * @param icon: The icon to look for
358  * @param size; The icon size to look for
359  * @return Returns the path to the given icon or NULL if none found
360  * @brief Retrives the path to the given icon.
361  */
362 EAPI char *
363 efreet_icon_path_find(const char *theme_name, const char *icon, unsigned int size)
364 {
365     const char *cached;
366     char *value;
367     Efreet_Icon_Theme *theme;
368
369     if ((cached = efreet_icon_hash_get(theme_name, icon, size)) != NULL)
370     {
371        if (cached == NON_EXISTING) return NULL;
372        return strdup(cached);
373     }
374     theme = efreet_icon_find_theme_check(theme_name);
375
376 #ifdef SLOPPY_SPEC
377     {
378         char *tmp;
379
380         tmp = efreet_icon_remove_extension(icon);
381         value = efreet_icon_find_helper(theme, tmp, size);
382         FREE(tmp);
383     }
384 #else
385     value = efreet_icon_find_helper(theme, icon, size);
386 #endif
387
388     /* we didn't find the icon in the theme or in the inherited directories
389      * then just look for a non theme icon
390      */
391     if (!value || (value == NON_EXISTING)) value = efreet_icon_fallback_icon(icon);
392
393     efreet_icon_hash_put(theme_name, icon, size, value);
394    
395     if (value == NON_EXISTING) value = NULL;
396     return value;
397 }
398
399 /**
400  * @param theme_name: The icon theme to look for
401  * @param icons: List of icons to look for
402  * @param size; The icon size to look for
403  * @return Returns the path representing first found icon or
404  * NULL if none of the icons are found
405  * @brief Retrieves all of the information about the first found icon in
406  * the list.
407  * @note This function will search the given theme for all icons before falling
408  * back. This is useful when searching for mimetype icons.
409  */
410 EAPI char *
411 efreet_icon_list_find(const char *theme_name, Eina_List *icons,
412                                                             unsigned int size)
413 {
414     Eina_List *l;
415     const char *icon = NULL;
416     char *value = NULL;
417     char *data;
418     Efreet_Icon_Theme *theme;
419
420     theme = efreet_icon_find_theme_check(theme_name);
421
422 #ifdef SLOPPY_SPEC
423     {
424         Eina_List *tmps = NULL;
425
426         EINA_LIST_FOREACH(icons, l, icon)
427             tmps = eina_list_append(tmps, efreet_icon_remove_extension(icon));
428
429         value = efreet_icon_list_find_helper(theme, tmps, size);
430         EINA_LIST_FREE(tmps, data)
431             free(data);
432     }
433 #else
434     value = efreet_icon_list_find_helper(theme, icons, size);
435 #endif
436
437     /* we didn't find the icons in the theme or in the inherited directories
438      * then just look for a non theme icon
439      */
440     if (!value || (value == NON_EXISTING))
441     {
442         EINA_LIST_FOREACH(icons, l, icon)
443         {
444             value = efreet_icon_fallback_icon(icon);
445             if (value && (value != NON_EXISTING))
446                 break;
447         }
448     }
449
450     if (value == NON_EXISTING) value = NULL;
451     return value;
452 }
453
454 /**
455  * @param theme: The icon theme to look for
456  * @param icon: The icon to look for
457  * @param size: The icon size to look for
458  * @return Returns the Efreet_Icon structure representing this icon or NULL
459  * if the icon is not found
460  * @brief Retrieves all of the information about the given icon.
461  */
462 EAPI Efreet_Icon *
463 efreet_icon_find(const char *theme_name, const char *icon, unsigned int size)
464 {
465     char *path;
466
467     path = efreet_icon_path_find(theme_name, icon, size);
468     if (path)
469     {
470         Efreet_Icon *icon;
471
472         icon = efreet_icon_new(path);
473         free(path);
474         return icon;
475     }
476
477     return NULL;
478 }
479
480 /**
481  * @internal
482  * @param theme: The theme to search in
483  * @param icon: The icon to search for
484  * @param size: The size to search for
485  * @return Returns the icon matching the given information or NULL if no
486  * icon found
487  * @brief Scans inheriting themes for the given icon
488  */
489 static char *
490 efreet_icon_find_fallback(Efreet_Icon_Theme *theme,
491                           const char *icon, unsigned int size)
492 {
493     Eina_List *l;
494     char *parent = NULL;
495     char *value = NULL;
496
497     if (theme->inherits)
498     {
499         EINA_LIST_FOREACH(theme->inherits, l, parent)
500         {
501             Efreet_Icon_Theme *parent_theme;
502
503             parent_theme = efreet_icon_theme_find(parent);
504             if ((!parent_theme) || (parent_theme == theme)) continue;
505
506             value = efreet_icon_find_helper(parent_theme, icon, size);
507             if (value && (value != NON_EXISTING)) break;
508         }
509     }
510     /* if this isn't the hicolor theme, and we have no other fallbacks
511      * check hicolor */
512     else if (strcmp(theme->name.internal, "hicolor"))
513     {
514         Efreet_Icon_Theme *parent_theme;
515
516         parent_theme = efreet_icon_theme_find("hicolor");
517         if (parent_theme)
518             value = efreet_icon_find_helper(parent_theme, icon, size);
519     }
520
521     return value;
522 }
523
524 /**
525  * @internal
526  * @param theme: The theme to search in
527  * @param icon: The icon to search for
528  * @param size: The size to search for
529  * @return Returns the icon matching the given information or NULL if no
530  * icon found
531  * @brief Scans the theme and any inheriting themes for the given icon
532  */
533 static char *
534 efreet_icon_find_helper(Efreet_Icon_Theme *theme,
535                         const char *icon, unsigned int size)
536 {
537     char *value;
538     static int recurse = 0;
539
540     efreet_icon_theme_cache_check(theme);
541
542     /* go no further if this theme is fake */
543     if (theme->fake || !theme->valid) return NULL;
544
545     /* limit recursion in finding themes and inherited themes to 256 levels */
546     if (recurse > 256) return NULL;
547     recurse++;
548
549     value = efreet_icon_lookup_icon(theme, icon, size);
550
551     /* we didin't find the image check the inherited themes */
552     if (!value || (value == NON_EXISTING))
553         value = efreet_icon_find_fallback(theme, icon, size);
554
555     recurse--;
556     return value;
557 }
558
559 /**
560  * @internal
561  * @param theme: The theme to search in
562  * @param icons: The icons to search for
563  * @param size: The size to search for
564  * @return Returns the icon matching the given information or NULL if no
565  * icon found
566  * @brief Scans inheriting themes for the given icons
567  */
568 static char *
569 efreet_icon_list_find_fallback(Efreet_Icon_Theme *theme,
570                                Eina_List *icons, unsigned int size)
571 {
572     Eina_List *l;
573     char *parent = NULL;
574     char *value = NULL;
575
576     if (theme->inherits)
577     {
578         EINA_LIST_FOREACH(theme->inherits, l, parent)
579         {
580             Efreet_Icon_Theme *parent_theme;
581
582             parent_theme = efreet_icon_theme_find(parent);
583             if ((!parent_theme) || (parent_theme == theme)) continue;
584
585             value = efreet_icon_list_find_helper(parent_theme,
586                                                         icons, size);
587             if (value && (value != NON_EXISTING)) break;
588         }
589     }
590
591     /* if this isn't the hicolor theme, and we have no other fallbacks
592      * check hicolor
593      */
594     else if (strcmp(theme->name.internal, "hicolor"))
595     {
596         Efreet_Icon_Theme *parent_theme;
597
598         parent_theme = efreet_icon_theme_find("hicolor");
599         if (parent_theme)
600             value = efreet_icon_list_find_helper(parent_theme,
601                                                         icons, size);
602     }
603
604     return value;
605 }
606
607 /**
608  * @internal
609  * @param theme: The theme to search in
610  * @param icons: The icons to search for
611  * @param size: The size to search for
612  * @return Returns the icon matching the given information or NULL if no
613  * icon found
614  * @brief Scans the theme and any inheriting themes for the given icons
615  */
616 static char *
617 efreet_icon_list_find_helper(Efreet_Icon_Theme *theme,
618                              Eina_List *icons, unsigned int size)
619 {
620     Eina_List *l;
621     char *value = NULL;
622     const char *icon = NULL;
623     static int recurse = 0;
624
625     efreet_icon_theme_cache_check(theme);
626
627     /* go no further if this theme is fake */
628     if (theme->fake || !theme->valid) return NULL;
629
630     /* limit recursion in finding themes and inherited themes to 256 levels */
631     if (recurse > 256) return NULL;
632     recurse++;
633
634     EINA_LIST_FOREACH(icons, l, icon)
635     {
636         value = efreet_icon_lookup_icon(theme, icon, size);
637         if (value && (value != NON_EXISTING))
638             break;
639     }
640
641     /* we didn't find the image check the inherited themes */
642     if (!value || (value == NON_EXISTING))
643         value = efreet_icon_list_find_fallback(theme, icons, size);
644
645     recurse--;
646     return value;
647 }
648
649 /**
650  * @internal
651  * @param theme: The icon theme to look in
652  * @param icon_name: The icon name to look for
653  * @param size: The icon size to look for
654  * @return Returns the path for the theme/icon/size combo or NULL if
655  * none found
656  * @brief Looks for the @a icon in the @a theme for the @a size given.
657  */
658 static char *
659 efreet_icon_lookup_icon(Efreet_Icon_Theme *theme, const char *icon_name,
660                                                     unsigned int size)
661 {
662     Eina_List *l;
663     char *icon = NULL, *tmp = NULL;
664     Efreet_Icon_Theme_Directory *dir;
665     int minimal_size = INT_MAX;
666
667     if (!theme || (theme->paths == NULL) || !icon_name || !size)
668         return NULL;
669
670     icon = efreet_icon_cache_check(theme, icon_name, size);
671     if (icon) return icon;
672
673     /* search for allowed size == requested size */
674     EINA_LIST_FOREACH(theme->directories, l, dir)
675     {
676         if (!efreet_icon_directory_size_match(dir, size)) continue;
677         icon = efreet_icon_lookup_directory(theme, dir,
678                                             icon_name);
679         if (icon)
680         {
681             efreet_icon_cache_add(theme, icon_name, size, icon);
682             return icon;
683         }
684     }
685
686     /* search for any icon that matches */
687     EINA_LIST_FOREACH(theme->directories, l, dir)
688     {
689         int distance;
690
691         distance = efreet_icon_directory_size_distance(dir, size);
692         if (distance >= minimal_size) continue;
693
694         tmp = efreet_icon_lookup_directory(theme, dir,
695                                            icon_name);
696         if (tmp)
697         {
698             FREE(icon);
699             icon = tmp;
700             minimal_size = distance;
701         }
702     }
703
704     efreet_icon_cache_add(theme, icon_name, size, icon);
705     return icon;
706 }
707
708
709 /**
710  * @internal
711  * @param theme: The theme to use
712  * @param dir: The theme directory to look in
713  * @param icon_name: The icon name to look for
714  * @return Returns the icon cloest matching the given information or NULL if
715  * none found
716  * @brief Tries to find the file closest matching the given icon
717  */
718 static char *
719 efreet_icon_lookup_directory(Efreet_Icon_Theme *theme,
720                              Efreet_Icon_Theme_Directory *dir,
721                              const char *icon_name)
722 {
723     Eina_List *l;
724     char *icon;
725     const char *path;
726
727     EINA_LIST_FOREACH(theme->paths, l, path)
728     {
729         icon = efreet_icon_lookup_directory_helper(dir, path, icon_name);
730         if (icon) return icon;
731     }
732
733     return NULL;
734 }
735
736 /**
737  * @internal
738  * @param dir: The theme directory to work with
739  * @param size: The size to check
740  * @return Returns true if the size matches for the given directory, 0
741  * otherwise
742  * @brief Checks if the size matches for the given directory or not
743  */
744 static int
745 efreet_icon_directory_size_match(Efreet_Icon_Theme_Directory *dir,
746                                 unsigned int size)
747 {
748     if (dir->type == EFREET_ICON_SIZE_TYPE_FIXED)
749         return (dir->size.normal == size);
750
751     if (dir->type == EFREET_ICON_SIZE_TYPE_SCALABLE)
752         return ((dir->size.min < size) && (size < dir->size.max));
753
754     if (dir->type == EFREET_ICON_SIZE_TYPE_THRESHOLD)
755         return (((dir->size.normal - dir->size.threshold) < size)
756                 && (size < (dir->size.normal + dir->size.threshold)));
757
758     return 0;
759 }
760
761 /**
762  * @internal
763  * @param dir: The directory to work with
764  * @param size: The size to check for
765  * @return Returns the distance this size is away from the desired size
766  * @brief Returns the distance the given size is away from the desired size
767  */
768 static int
769 efreet_icon_directory_size_distance(Efreet_Icon_Theme_Directory *dir,
770                                     unsigned int size)
771 {
772     if (dir->type == EFREET_ICON_SIZE_TYPE_FIXED)
773         return (abs(dir->size.normal - size));
774
775     if (dir->type == EFREET_ICON_SIZE_TYPE_SCALABLE)
776     {
777         if (size < dir->size.min)
778             return dir->size.min - size;
779         if (dir->size.max < size)
780             return size - dir->size.max;
781
782         return 0;
783     }
784
785     if (dir->type == EFREET_ICON_SIZE_TYPE_THRESHOLD)
786     {
787         if (size < (dir->size.normal - dir->size.threshold))
788             return (dir->size.min - size);
789         if ((dir->size.normal + dir->size.threshold) < size)
790             return (size - dir->size.max);
791
792         return 0;
793     }
794
795     return 0;
796 }
797
798 /**
799  * @internal
800  * @param icon_name: The icon name to look for
801  * @return Returns the Efreet_Icon for the given name or NULL if none found
802  * @brief Looks for the un-themed icon in the base directories
803  */
804 static char *
805 efreet_icon_fallback_icon(const char *icon_name)
806 {
807     char *icon;
808
809     if (!icon_name) return NULL;
810     icon = efreet_icon_cache_check(efreet_icon_find_theme_check(NULL), icon_name, 0);
811     if (icon) return icon;
812
813     icon = efreet_icon_fallback_dir_scan(efreet_icon_deprecated_user_dir_get(), icon_name);
814     if (!icon)
815         icon = efreet_icon_fallback_dir_scan(efreet_icon_user_dir_get(), icon_name);
816     if (!icon)
817     {
818         Eina_List *xdg_dirs, *l;
819         const char *dir;
820         char path[PATH_MAX];
821
822         EINA_LIST_FOREACH(efreet_extra_icon_dirs, l, dir)
823         {
824             icon = efreet_icon_fallback_dir_scan(dir, icon_name);
825             if (icon)
826             {
827                efreet_icon_cache_add(efreet_icon_find_theme_check(NULL), icon_name, 0, icon);
828                 return icon;
829             }
830         }
831
832         xdg_dirs = efreet_data_dirs_get();
833
834         EINA_LIST_FOREACH(xdg_dirs, l, dir)
835         {
836             snprintf(path, PATH_MAX, "%s/icons", dir);
837             icon = efreet_icon_fallback_dir_scan(path, icon_name);
838             if (icon)
839             {
840                 efreet_icon_cache_add(efreet_icon_find_theme_check(NULL), icon_name, 0, icon);
841                 return icon;
842             }
843         }
844
845         icon = efreet_icon_fallback_dir_scan("/usr/share/pixmaps", icon_name);
846     }
847
848     efreet_icon_cache_add(efreet_icon_find_theme_check(NULL), icon_name, 0, icon);
849     return icon;
850 }
851
852 /**
853  * @internal
854  * @param dir: The directory to scan
855  * @param icon_name: The icon to look for
856  * @return Returns the icon for the given name or NULL on failure
857  * @brief Scans the given @a dir for the given @a icon_name returning the
858  * Efreet_icon if found, NULL otherwise.
859  */
860 static char *
861 efreet_icon_fallback_dir_scan(const char *dir, const char *icon_name)
862 {
863     Eina_List *l;
864     char *icon = NULL;
865     char path[PATH_MAX], *ext;
866     const char *icon_path[] = { dir, "/", icon_name, NULL };
867     size_t size;
868
869     if (!dir || !icon_name) return NULL;
870
871     size = efreet_array_cat(path, sizeof(path), icon_path);
872     EINA_LIST_FOREACH(efreet_icon_extensions, l, ext)
873     {
874         ecore_strlcpy(path + size, ext, sizeof(path) - size);
875
876         if (ecore_file_exists(path))
877         {
878             icon = strdup(path);
879             if (icon) break;
880         }
881         *(path + size) = '\0';
882     }
883     /* This is to catch non-conforming .desktop files */
884 #ifdef SLOPPY_SPEC
885     if (!icon)
886     {
887         if ((ecore_file_exists(path))  && (!ecore_file_is_dir(path)))
888         {
889             icon = strdup(path);
890 #ifdef STRICT_SPEC
891             if (icon)
892                 printf("[Efreet]: Found an icon that already has an extension: %s\n", path);
893 #endif
894         }
895     }
896 #endif
897
898     return icon;
899 }
900
901 /**
902  * @internal
903  * @param theme: The theme to work with
904  * @param dir: The theme directory to work with
905  * @param path: The partial path to use
906  * @return Returns no value
907  * @brief Caches the icons in the given theme directory path at the given
908  * size
909  */
910 static char *
911 efreet_icon_lookup_directory_helper(Efreet_Icon_Theme_Directory *dir,
912                                     const char *path, const char *icon_name)
913 {
914     Eina_List *l;
915     char *icon = NULL;
916     char file_path[PATH_MAX];
917     const char *ext, *path_strs[] = { path, "/", dir->name, "/", icon_name, NULL };
918     size_t len;
919
920     len = efreet_array_cat(file_path, sizeof(file_path), path_strs);
921
922     EINA_LIST_FOREACH(efreet_icon_extensions, l, ext)
923     {
924         ecore_strlcpy(file_path + len, ext, sizeof(file_path) - len);
925
926         if (ecore_file_exists(file_path))
927         {
928             icon = strdup(file_path);
929             break;
930         }
931     }
932     return icon;
933 }
934
935 /**
936  * @internal
937  * @return Returns a new Efreet_Icon struct on success or NULL on failure
938  * @brief Creates a new Efreet_Icon struct
939  */
940 static Efreet_Icon *
941 efreet_icon_new(const char *path)
942 {
943     Efreet_Icon *icon;
944     char *p;
945
946     icon = NEW(Efreet_Icon, 1);
947     icon->path = strdup(path);
948
949     /* load the .icon file if it's available */
950     p = strrchr(icon->path, '.');
951     if (p)
952     {
953         char ico_path[PATH_MAX];
954
955         *p = '\0';
956
957         snprintf(ico_path, sizeof(ico_path), "%s.icon", icon->path);
958         *p = '.';
959
960         if (ecore_file_exists(ico_path))
961             efreet_icon_populate(icon, ico_path);
962     }
963
964     if (!icon->name)
965     {
966         const char *file;
967
968         file = ecore_file_file_get(icon->path);
969         p = strrchr(icon->path, '.');
970         if (p) *p = '\0';
971         icon->name = strdup(file);
972         if (p) *p = '.';
973     }
974
975     return icon;
976 }
977
978 /**
979  * @param icon: The Efreet_Icon to cleanup
980  * @return Returns no value.
981  * @brief Free's the given icon and all its internal data.
982  */
983 EAPI void
984 efreet_icon_free(Efreet_Icon *icon)
985 {
986     if (!icon) return;
987
988     icon->ref_count --;
989     if (icon->ref_count > 0) return;
990
991     IF_FREE(icon->path);
992     IF_FREE(icon->name);
993     IF_FREE_LIST(icon->attach_points, free);
994
995     FREE(icon);
996 }
997
998 /**
999  * @internal
1000  * @param icon: The icon to populate
1001  * @param file: The file to populate from
1002  * @return Returns no value
1003  * @brief Tries to populate the icon information from the given file
1004  */
1005 static void
1006 efreet_icon_populate(Efreet_Icon *icon, const char *file)
1007 {
1008     Efreet_Ini *ini;
1009     const char *tmp;
1010
1011     ini = efreet_ini_new(file);
1012     if (!ini->data)
1013     {
1014         efreet_ini_free(ini);
1015         return;
1016     }
1017
1018     efreet_ini_section_set(ini, "Icon Data");
1019     tmp = efreet_ini_localestring_get(ini, "DisplayName");
1020     if (tmp) icon->name = strdup(tmp);
1021
1022     tmp = efreet_ini_string_get(ini, "EmbeddedTextRectangle");
1023     if (tmp)
1024     {
1025         int points[4];
1026         char *t, *s, *p;
1027         int i;
1028
1029         t = strdup(tmp);
1030         s = t;
1031         for (i = 0; i < 4; i++)
1032         {
1033             if (s)
1034             {
1035                 p = strchr(s, ',');
1036
1037                 if (p) *p = '\0';
1038                 points[i] = atoi(s);
1039
1040                 if (p) s = ++p;
1041                 else s = NULL;
1042             }
1043             else
1044             {
1045                 points[i] = 0;
1046             }
1047         }
1048
1049         icon->has_embedded_text_rectangle = 1;
1050         icon->embedded_text_rectangle.x0 = points[0];
1051         icon->embedded_text_rectangle.y0 = points[1];
1052         icon->embedded_text_rectangle.x1 = points[2];
1053         icon->embedded_text_rectangle.y1 = points[3];
1054
1055         FREE(t);
1056     }
1057
1058     tmp = efreet_ini_string_get(ini, "AttachPoints");
1059     if (tmp)
1060     {
1061         char *t, *s, *p;
1062
1063         t = strdup(tmp);
1064         s = t;
1065         while (s)
1066         {
1067             Efreet_Icon_Point *point;
1068
1069             p = strchr(s, ',');
1070             /* If this happens there is something wrong with the .icon file */
1071             if (!p) break;
1072
1073             point = NEW(Efreet_Icon_Point, 1);
1074
1075             *p = '\0';
1076             point->x = atoi(s);
1077
1078             s = ++p;
1079             p = strchr(s, '|');
1080             if (p) *p = '\0';
1081
1082             point->y = atoi(s);
1083
1084             icon->attach_points = eina_list_append(icon->attach_points, point);
1085
1086             if (p) s = ++p;
1087             else s = NULL;
1088         }
1089         FREE(t);
1090     }
1091
1092     efreet_ini_free(ini);
1093 }
1094
1095 /**
1096  * @internal
1097  * @return Returns a new Efreet_Icon_Theme on success or NULL on failure
1098  * @brief Creates a new Efreet_Icon_Theme structure
1099  */
1100 static Efreet_Icon_Theme *
1101 efreet_icon_theme_new(void)
1102 {
1103     Efreet_Icon_Theme *theme;
1104
1105     theme = NEW(Efreet_Icon_Theme, 1);
1106
1107     return theme;
1108 }
1109
1110 /**
1111  * @internal
1112  * @param theme: The theme to free
1113  * @return Returns no value
1114  * @brief Frees up the @a theme structure.
1115  */
1116 static void
1117 efreet_icon_theme_free(Efreet_Icon_Theme *theme)
1118 {
1119     void *d;
1120     if (!theme) return;
1121
1122     IF_RELEASE(theme->name.internal);
1123     IF_RELEASE(theme->name.name);
1124
1125     IF_FREE(theme->comment);
1126     IF_FREE(theme->example_icon);
1127
1128     IF_FREE_LIST(theme->paths, free);
1129     IF_FREE_LIST(theme->inherits, free);
1130     IF_FREE_LIST(theme->directories, efreet_icon_theme_directory_free);
1131
1132     FREE(theme);
1133 }
1134
1135 /**
1136  * @internal
1137  * @param theme: The theme to work with
1138  * @param path: The path to add
1139  * @return Returns no value
1140  * @brief This will correctly add the given path to the list of theme paths.
1141  * @Note Assumes you've already verified that @a path is a valid directory.
1142  */
1143 static void
1144 efreet_icon_theme_path_add(Efreet_Icon_Theme *theme, const char *path)
1145 {
1146     if (!theme || !path) return;
1147
1148     theme->paths = eina_list_append(theme->paths, strdup(path));
1149 }
1150
1151 /**
1152  * @internal
1153  * @return Returns no value
1154  * @brief This validates that our cache is still valid.
1155  *
1156  * This is checked by the following algorithm:
1157  *   - if we've check less then 5 seconds ago we're good
1158  *   - if the mtime on the dir is less then our last check time we're good
1159  *   - otherwise, invalidate the caches
1160  */
1161 static void
1162 efreet_icon_theme_cache_check(Efreet_Icon_Theme *theme)
1163 {
1164     Eina_List *l;
1165     double new_check;
1166
1167     new_check = ecore_time_get();
1168
1169     /* we're within 5 seconds of the last time we checked the cache */
1170     if ((new_check - 5) <= theme->last_cache_check) return;
1171
1172     if (theme->fake)
1173         efreet_icon_theme_dir_scan_all(theme->name.internal);
1174
1175     else
1176     {
1177         char *path;
1178
1179         EINA_LIST_FOREACH(theme->paths, l, path)
1180         {
1181             if (!efreet_icon_theme_cache_check_dir(theme, path))
1182                 break;
1183         }
1184     }
1185     theme->last_cache_check = new_check;
1186 }
1187
1188 /**
1189  * @internal
1190  * @param theme: The icon theme to check
1191  * @param dir: The directory to check
1192  * @return Returns 1 if the cache is still valid, 0 otherwise
1193  * @brief This will check if the theme cache is still valid. If it isn't the
1194  * cache will be invalided and 0 returned.
1195  */
1196 static int
1197 efreet_icon_theme_cache_check_dir(Efreet_Icon_Theme *theme, const char *dir)
1198 {
1199     struct stat buf;
1200
1201     /* have we modified this directory since our last cache check? */
1202     if (stat(dir, &buf) || (buf.st_mtime > theme->last_cache_check))
1203     {
1204         eina_hash_del(efreet_icon_cache, theme, NULL);
1205         return 0;
1206     }
1207
1208     return 1;
1209 }
1210
1211 /**
1212  * @internal
1213  * @param theme_name: The theme to scan for
1214  * @return Returns no value
1215  * @brief Scans the theme directories. If @a theme_name is NULL it will load
1216  * up all theme data. If @a theme_name is not NULL it will look for that
1217  * specific theme data
1218  */
1219 static void
1220 efreet_icon_theme_dir_scan_all(const char *theme_name)
1221 {
1222     Eina_List *xdg_dirs, *l;
1223     char path[PATH_MAX], *dir;
1224
1225     efreet_icon_theme_dir_scan(efreet_icon_deprecated_user_dir_get(), theme_name);
1226     efreet_icon_theme_dir_scan(efreet_icon_user_dir_get(), theme_name);
1227
1228     xdg_dirs = efreet_data_dirs_get();
1229     EINA_LIST_FOREACH(xdg_dirs, l, dir)
1230     {
1231         snprintf(path, sizeof(path), "%s/icons", dir);
1232         efreet_icon_theme_dir_scan(path, theme_name);
1233     }
1234
1235     efreet_icon_theme_dir_scan("/usr/share/pixmaps", theme_name);
1236 }
1237
1238 /**
1239  * @internal
1240  * @param search_dir: The directory to scan
1241  * @param theme_name: Scan for this specific theme, set to NULL to find all
1242  * themes.
1243  * @return Returns no value
1244  * @brief Scans the given directory and adds non-hidden icon themes to the
1245  * given list. If the theme isnt' in our cache then load the index.theme and
1246  * add to the cache.
1247  */
1248 static void
1249 efreet_icon_theme_dir_scan(const char *search_dir, const char *theme_name)
1250 {
1251     DIR *dirs;
1252     struct dirent *dir;
1253
1254     if (!search_dir) return;
1255
1256     dirs = opendir(search_dir);
1257     if (!dirs) return;
1258
1259     while ((dir = readdir(dirs)))
1260     {
1261         Efreet_Icon_Theme *theme;
1262         char path[PATH_MAX];
1263         const char *key;
1264
1265         if (!strcmp(dir->d_name, ".") || !strcmp(dir->d_name, "..")) continue;
1266
1267         /* only care if this is a directory or the theme name matches the
1268          * given name */
1269         snprintf(path, sizeof(path), "%s/%s", search_dir, dir->d_name);
1270         if (((theme_name != NULL) && (strcmp(theme_name, dir->d_name)))
1271                 || !ecore_file_is_dir(path))
1272             continue;
1273
1274         key = eina_stringshare_add(dir->d_name);
1275         theme = eina_hash_find(efreet_icon_themes, key);
1276
1277         if (!theme)
1278         {
1279             theme = efreet_icon_theme_new();
1280             theme->name.internal = key;
1281             eina_hash_add(efreet_icon_themes,
1282                           (void *)theme->name.internal, theme);
1283         }
1284         else
1285         {
1286             if (theme->fake)
1287                 theme->fake = 0;
1288             eina_stringshare_del(key);
1289         }
1290
1291         efreet_icon_theme_path_add(theme, path);
1292
1293         /* we're already valid so no reason to check for an index.theme file */
1294         if (theme->valid) continue;
1295
1296         /* if the index.theme file exists we parse it into the theme */
1297         strncat(path, "/index.theme", sizeof(path));
1298         if (ecore_file_exists(path))
1299             efreet_icon_theme_index_read(theme, path);
1300     }
1301     closedir(dirs);
1302
1303     /* if we were given a theme name we want to make sure that that given
1304      * theme is valid before finishing, unless it's a fake theme */
1305     if (theme_name)
1306     {
1307         Efreet_Icon_Theme *theme;
1308
1309         theme = eina_hash_find(efreet_icon_themes, theme_name);
1310         if (theme && !theme->valid && !theme->fake)
1311             eina_hash_del(efreet_icon_themes, theme_name, theme);
1312     }
1313 }
1314
1315 /**
1316  * @internal
1317  * @param theme: The theme to set the values into
1318  * @param path: The path to the index.theme file for this theme
1319  * @return Returns no value
1320  * @brief This will load up the theme with the data in the index.theme file
1321  */
1322 static void
1323 efreet_icon_theme_index_read(Efreet_Icon_Theme *theme, const char *path)
1324 {
1325     Efreet_Ini *ini;
1326     const char *tmp;
1327
1328     if (!theme || !path) return;
1329
1330     ini = efreet_ini_new(path);
1331     if (!ini->data)
1332     {
1333         efreet_ini_free(ini);
1334         return;
1335     }
1336
1337     efreet_ini_section_set(ini, "Icon Theme");
1338     tmp = efreet_ini_localestring_get(ini, "Name");
1339     if (tmp) theme->name.name = eina_stringshare_add(tmp);
1340
1341     tmp = efreet_ini_localestring_get(ini, "Comment");
1342     if (tmp) theme->comment = strdup(tmp);
1343
1344     tmp = efreet_ini_string_get(ini, "Example");
1345     if (tmp) theme->example_icon = strdup(tmp);
1346
1347     theme->hidden = efreet_ini_boolean_get(ini, "Hidden");
1348
1349     theme->valid = 1;
1350
1351     /* Check the inheritance. If there is none we inherit from the hicolor theme */
1352     tmp = efreet_ini_string_get(ini, "Inherits");
1353     if (tmp)
1354     {
1355         char *t, *s, *p;
1356
1357         t = strdup(tmp);
1358         s = t;
1359         p = strchr(s, ',');
1360
1361         while (p)
1362         {
1363             *p = '\0';
1364
1365             theme->inherits = eina_list_append(theme->inherits, strdup(s));
1366             s = ++p;
1367             p = strchr(s, ',');
1368         }
1369         theme->inherits = eina_list_append(theme->inherits, strdup(s));
1370
1371         FREE(t);
1372     }
1373
1374     /* make sure this one is done last as setting the directory will change
1375      * the ini section ... */
1376     tmp = efreet_ini_string_get(ini, "Directories");
1377     if (tmp)
1378     {
1379         char *t, *s, *p;
1380
1381         t = strdup(tmp);
1382         s = t;
1383         p = s;
1384
1385         while (p)
1386         {
1387             p = strchr(s, ',');
1388
1389             if (p) *p = '\0';
1390
1391             theme->directories = eina_list_append(theme->directories,
1392                             efreet_icon_theme_directory_new(ini, s));
1393
1394             if (p) s = ++p;
1395         }
1396
1397         FREE(t);
1398     }
1399
1400     efreet_ini_free(ini);
1401 }
1402
1403 /**
1404  * @internal
1405  * @return Returns no value
1406  * @brief Because the theme icon directories can be spread over multiple
1407  * base directories we may need to create the icon theme strucutre before
1408  * finding the index.theme file. It may also be that we never find an
1409  * index.theme file as this isn't a valid theme. This function makes sure
1410  * that everything we've got in our hash has a valid key to it.
1411  */
1412 static void
1413 efreet_icon_theme_dir_validity_check(void)
1414 {
1415     Eina_List *keys;
1416     const char *name;
1417     Eina_Iterator *it;
1418
1419     keys = NULL;
1420     it = eina_hash_iterator_key_new(efreet_icon_themes);
1421     eina_iterator_foreach(it, EINA_EACH(_hash_keys), &keys);
1422     eina_iterator_free(it);
1423
1424     EINA_LIST_FREE(keys, name)
1425     {
1426         Efreet_Icon_Theme *theme;
1427
1428         theme = eina_hash_find(efreet_icon_themes, name);
1429         if (theme && !theme->valid && !theme->fake)
1430             eina_hash_del(efreet_icon_themes, name, theme);
1431     }
1432 }
1433
1434 /**
1435  * @internal
1436  * @param ini: The ini file with information on this directory
1437  * @param name: The name of the directory
1438  * @return Returns a new Efreet_Icon_Theme_Directory based on the
1439  * information in @a ini.
1440  * @brief Creates and initialises an icon theme directory from the given ini
1441  * information
1442  */
1443 static Efreet_Icon_Theme_Directory *
1444 efreet_icon_theme_directory_new(Efreet_Ini *ini, const char *name)
1445 {
1446     Efreet_Icon_Theme_Directory *dir;
1447     int val;
1448     const char *tmp;
1449
1450     if (!ini) return NULL;
1451
1452     dir = NEW(Efreet_Icon_Theme_Directory, 1);
1453     dir->name = strdup(name);
1454
1455     efreet_ini_section_set(ini, name);
1456
1457     tmp = efreet_ini_string_get(ini, "Context");
1458     if (tmp)
1459     {
1460         if (!strcasecmp(tmp, "Actions"))
1461             dir->context = EFREET_ICON_THEME_CONTEXT_ACTIONS;
1462
1463         else if (!strcasecmp(tmp, "Devices"))
1464             dir->context = EFREET_ICON_THEME_CONTEXT_DEVICES;
1465
1466         else if (!strcasecmp(tmp, "FileSystems"))
1467             dir->context = EFREET_ICON_THEME_CONTEXT_FILESYSTEMS;
1468
1469         else if (!strcasecmp(tmp, "MimeTypes"))
1470             dir->context = EFREET_ICON_THEME_CONTEXT_MIMETYPES;
1471     }
1472
1473     tmp = efreet_ini_string_get(ini, "Type");
1474     if (tmp)
1475     {
1476         if (!strcasecmp(tmp, "Fixed"))
1477             dir->type = EFREET_ICON_SIZE_TYPE_FIXED;
1478
1479         else if (!strcasecmp(tmp, "Scalable"))
1480             dir->type = EFREET_ICON_SIZE_TYPE_SCALABLE;
1481
1482         else if (!strcasecmp(tmp, "Threshold"))
1483             dir->type = EFREET_ICON_SIZE_TYPE_THRESHOLD;
1484     }
1485
1486     dir->size.normal = efreet_ini_int_get(ini, "Size");
1487
1488     val = efreet_ini_int_get(ini, "MinSize");
1489     if (val < 0) dir->size.min = dir->size.normal;
1490     else dir->size.min = val;
1491
1492     val = efreet_ini_int_get(ini, "MaxSize");
1493     if (val < 0) dir->size.max = dir->size.normal;
1494     else dir->size.max = val;
1495
1496     val = efreet_ini_int_get(ini, "Threshold");
1497     if (val < 0) dir->size.threshold = 2;
1498     else dir->size.threshold = val;
1499
1500     return dir;
1501 }
1502
1503 /**
1504  * @internal
1505  * @param dir: The Efreet_Icon_Theme_Directory to free
1506  * @return Returns no value
1507  * @brief Frees the given directory @a dir
1508  */
1509 static void
1510 efreet_icon_theme_directory_free(Efreet_Icon_Theme_Directory *dir)
1511 {
1512     if (!dir) return;
1513
1514     IF_FREE(dir->name);
1515     FREE(dir);
1516 }
1517
1518 static int
1519 efreet_icon_cache_find(Efreet_Icon_Cache *value, const char *key)
1520 {
1521     if (!value || !key) return -1;
1522     return strcmp(value->key, key);
1523 }
1524
1525 static void
1526 efreet_icon_cache_flush(Efreet_Icon_Theme *theme, Eina_List *list)
1527 {
1528     /* TODO:
1529      * * Dynamic cache size
1530      * * Maybe add references to cache, so that we sort on how often a value is used
1531      */
1532     while (eina_list_count(list) > 100)
1533     {
1534         Efreet_Icon_Cache *cache;
1535         Eina_List *last;
1536
1537         last = eina_list_last(list);
1538         cache = eina_list_data_get(last);
1539         efreet_icon_cache_free(cache);
1540         list = eina_list_remove_list(list, last);
1541     }
1542
1543     eina_hash_modify(efreet_icon_cache, theme, list);
1544 }
1545
1546 static void
1547 efreet_icon_cache_free(Efreet_Icon_Cache *value)
1548 {
1549     if (!value) return;
1550
1551     IF_FREE(value->key);
1552     IF_FREE(value->path);
1553     free(value);
1554 }
1555
1556 static char *
1557 efreet_icon_cache_check(Efreet_Icon_Theme *theme, const char *icon, unsigned int size)
1558 {
1559     Eina_List *list;
1560     Efreet_Icon_Cache *cache;
1561     char key[4096];
1562     struct stat st;
1563
1564     list = eina_hash_find(efreet_icon_cache, theme);
1565     if (!list) return NULL;
1566
1567     snprintf(key, sizeof(key), "%s %d", icon, size);
1568     cache = eina_list_search_unsorted(list, EINA_COMPARE_CB(efreet_icon_cache_find), key);
1569     if (cache)
1570     {
1571         if (!cache->path)
1572         {
1573             list = eina_list_promote_list(list, eina_list_data_find_list(list, cache));
1574             eina_hash_modify(efreet_icon_cache, theme, list);
1575             return NON_EXISTING;
1576         }
1577         else if (!stat(cache->path, &st) && st.st_mtime == cache->lasttime)
1578         {
1579             list = eina_list_promote_list(list, eina_list_data_find_list(list, cache));
1580             eina_hash_modify(efreet_icon_cache, theme, list);
1581             return strdup(cache->path);
1582         }
1583         efreet_icon_cache_free(cache);
1584         list = eina_list_remove(list, cache);
1585         if (list != NULL) eina_hash_modify(efreet_icon_cache, theme, list);
1586         else eina_hash_del(efreet_icon_cache, theme, NULL);
1587     }
1588     return NULL;
1589 }
1590
1591 static void
1592 efreet_icon_cache_add(Efreet_Icon_Theme *theme, const char *icon, unsigned int size, const char *value)
1593 {
1594     Eina_List *list, *l;
1595     Efreet_Icon_Cache *cache;
1596     char key[4096];
1597     struct stat st;
1598
1599     list = eina_hash_find(efreet_icon_cache, theme);
1600
1601     snprintf(key, sizeof(key), "%s %d", icon, size);
1602     cache = NEW(Efreet_Icon_Cache, 1);
1603     cache->key = strdup(key);
1604     if ((value) && !stat(value, &st))
1605     {
1606         cache->path = strdup(value);
1607         cache->lasttime = st.st_mtime;
1608     }
1609     else
1610         cache->lasttime = ecore_time_get();
1611
1612     l = list;
1613     list = eina_list_prepend(list, cache);
1614
1615     if (!l) eina_hash_add(efreet_icon_cache, theme, list);
1616     else eina_hash_modify(efreet_icon_cache, theme, list);
1617
1618     efreet_icon_cache_flush(theme, list);
1619 }