more leak fixes
[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         p = t;
1066         while (s)
1067         {
1068             Efreet_Icon_Point *point;
1069
1070             p = strchr(s, ',');
1071             /* If this happens there is something wrong with the .icon file */
1072             if (!p) break;
1073
1074             point = NEW(Efreet_Icon_Point, 1);
1075
1076             *p = '\0';
1077             point->x = atoi(s);
1078
1079             s = ++p;
1080             p = strchr(s, '|');
1081             if (p) *p = '\0';
1082
1083             point->y = atoi(s);
1084
1085             icon->attach_points = eina_list_append(icon->attach_points, point);
1086
1087             if (p) s = ++p;
1088             else s = NULL;
1089         }
1090         FREE(t);
1091     }
1092
1093     efreet_ini_free(ini);
1094 }
1095
1096 /**
1097  * @internal
1098  * @return Returns a new Efreet_Icon_Theme on success or NULL on failure
1099  * @brief Creates a new Efreet_Icon_Theme structure
1100  */
1101 static Efreet_Icon_Theme *
1102 efreet_icon_theme_new(void)
1103 {
1104     Efreet_Icon_Theme *theme;
1105
1106     theme = NEW(Efreet_Icon_Theme, 1);
1107
1108     return theme;
1109 }
1110
1111 /**
1112  * @internal
1113  * @param theme: The theme to free
1114  * @return Returns no value
1115  * @brief Frees up the @a theme structure.
1116  */
1117 static void
1118 efreet_icon_theme_free(Efreet_Icon_Theme *theme)
1119 {
1120     void *d;
1121     if (!theme) return;
1122
1123     IF_RELEASE(theme->name.internal);
1124     IF_RELEASE(theme->name.name);
1125
1126     IF_FREE(theme->comment);
1127     IF_FREE(theme->example_icon);
1128
1129     IF_FREE_LIST(theme->paths, free);
1130     IF_FREE_LIST(theme->inherits, free);
1131     IF_FREE_LIST(theme->directories, efreet_icon_theme_directory_free);
1132
1133     FREE(theme);
1134 }
1135
1136 /**
1137  * @internal
1138  * @param theme: The theme to work with
1139  * @param path: The path to add
1140  * @return Returns no value
1141  * @brief This will correctly add the given path to the list of theme paths.
1142  * @Note Assumes you've already verified that @a path is a valid directory.
1143  */
1144 static void
1145 efreet_icon_theme_path_add(Efreet_Icon_Theme *theme, const char *path)
1146 {
1147     if (!theme || !path) return;
1148
1149     theme->paths = eina_list_append(theme->paths, strdup(path));
1150 }
1151
1152 /**
1153  * @internal
1154  * @return Returns no value
1155  * @brief This validates that our cache is still valid.
1156  *
1157  * This is checked by the following algorithm:
1158  *   - if we've check less then 5 seconds ago we're good
1159  *   - if the mtime on the dir is less then our last check time we're good
1160  *   - otherwise, invalidate the caches
1161  */
1162 static void
1163 efreet_icon_theme_cache_check(Efreet_Icon_Theme *theme)
1164 {
1165     Eina_List *l;
1166     double new_check;
1167
1168     new_check = ecore_time_get();
1169
1170     /* we're within 5 seconds of the last time we checked the cache */
1171     if ((new_check - 5) <= theme->last_cache_check) return;
1172
1173     if (theme->fake)
1174         efreet_icon_theme_dir_scan_all(theme->name.internal);
1175
1176     else
1177     {
1178         char *path;
1179
1180         EINA_LIST_FOREACH(theme->paths, l, path)
1181         {
1182             if (!efreet_icon_theme_cache_check_dir(theme, path))
1183                 break;
1184         }
1185     }
1186     theme->last_cache_check = new_check;
1187 }
1188
1189 /**
1190  * @internal
1191  * @param theme: The icon theme to check
1192  * @param dir: The directory to check
1193  * @return Returns 1 if the cache is still valid, 0 otherwise
1194  * @brief This will check if the theme cache is still valid. If it isn't the
1195  * cache will be invalided and 0 returned.
1196  */
1197 static int
1198 efreet_icon_theme_cache_check_dir(Efreet_Icon_Theme *theme, const char *dir)
1199 {
1200     struct stat buf;
1201
1202     /* have we modified this directory since our last cache check? */
1203     if (stat(dir, &buf) || (buf.st_mtime > theme->last_cache_check))
1204     {
1205         eina_hash_del(efreet_icon_cache, theme, NULL);
1206         return 0;
1207     }
1208
1209     return 1;
1210 }
1211
1212 /**
1213  * @internal
1214  * @param theme_name: The theme to scan for
1215  * @return Returns no value
1216  * @brief Scans the theme directories. If @a theme_name is NULL it will load
1217  * up all theme data. If @a theme_name is not NULL it will look for that
1218  * specific theme data
1219  */
1220 static void
1221 efreet_icon_theme_dir_scan_all(const char *theme_name)
1222 {
1223     Eina_List *xdg_dirs, *l;
1224     char path[PATH_MAX], *dir;
1225
1226     efreet_icon_theme_dir_scan(efreet_icon_deprecated_user_dir_get(), theme_name);
1227     efreet_icon_theme_dir_scan(efreet_icon_user_dir_get(), theme_name);
1228
1229     xdg_dirs = efreet_data_dirs_get();
1230     EINA_LIST_FOREACH(xdg_dirs, l, dir)
1231     {
1232         snprintf(path, sizeof(path), "%s/icons", dir);
1233         efreet_icon_theme_dir_scan(path, theme_name);
1234     }
1235
1236     efreet_icon_theme_dir_scan("/usr/share/pixmaps", theme_name);
1237 }
1238
1239 /**
1240  * @internal
1241  * @param search_dir: The directory to scan
1242  * @param theme_name: Scan for this specific theme, set to NULL to find all
1243  * themes.
1244  * @return Returns no value
1245  * @brief Scans the given directory and adds non-hidden icon themes to the
1246  * given list. If the theme isnt' in our cache then load the index.theme and
1247  * add to the cache.
1248  */
1249 static void
1250 efreet_icon_theme_dir_scan(const char *search_dir, const char *theme_name)
1251 {
1252     DIR *dirs;
1253     struct dirent *dir;
1254
1255     if (!search_dir) return;
1256
1257     dirs = opendir(search_dir);
1258     if (!dirs) return;
1259
1260     while ((dir = readdir(dirs)))
1261     {
1262         Efreet_Icon_Theme *theme;
1263         char path[PATH_MAX];
1264         const char *key;
1265
1266         if (!strcmp(dir->d_name, ".") || !strcmp(dir->d_name, "..")) continue;
1267
1268         /* only care if this is a directory or the theme name matches the
1269          * given name */
1270         snprintf(path, sizeof(path), "%s/%s", search_dir, dir->d_name);
1271         if (((theme_name != NULL) && (strcmp(theme_name, dir->d_name)))
1272                 || !ecore_file_is_dir(path))
1273             continue;
1274
1275         key = eina_stringshare_add(dir->d_name);
1276         theme = eina_hash_find(efreet_icon_themes, key);
1277
1278         if (!theme)
1279         {
1280             theme = efreet_icon_theme_new();
1281             theme->name.internal = key;
1282             eina_hash_add(efreet_icon_themes,
1283                           (void *)theme->name.internal, theme);
1284         }
1285         else
1286         {
1287             if (theme->fake)
1288                 theme->fake = 0;
1289             eina_stringshare_del(key);
1290         }
1291
1292         efreet_icon_theme_path_add(theme, path);
1293
1294         /* we're already valid so no reason to check for an index.theme file */
1295         if (theme->valid) continue;
1296
1297         /* if the index.theme file exists we parse it into the theme */
1298         strncat(path, "/index.theme", sizeof(path));
1299         if (ecore_file_exists(path))
1300             efreet_icon_theme_index_read(theme, path);
1301     }
1302     closedir(dirs);
1303
1304     /* if we were given a theme name we want to make sure that that given
1305      * theme is valid before finishing, unless it's a fake theme */
1306     if (theme_name)
1307     {
1308         Efreet_Icon_Theme *theme;
1309
1310         theme = eina_hash_find(efreet_icon_themes, theme_name);
1311         if (theme && !theme->valid && !theme->fake)
1312             eina_hash_del(efreet_icon_themes, theme_name, theme);
1313     }
1314 }
1315
1316 /**
1317  * @internal
1318  * @param theme: The theme to set the values into
1319  * @param path: The path to the index.theme file for this theme
1320  * @return Returns no value
1321  * @brief This will load up the theme with the data in the index.theme file
1322  */
1323 static void
1324 efreet_icon_theme_index_read(Efreet_Icon_Theme *theme, const char *path)
1325 {
1326     Efreet_Ini *ini;
1327     const char *tmp;
1328
1329     if (!theme || !path) return;
1330
1331     ini = efreet_ini_new(path);
1332     if (!ini->data)
1333     {
1334         efreet_ini_free(ini);
1335         return;
1336     }
1337
1338     efreet_ini_section_set(ini, "Icon Theme");
1339     tmp = efreet_ini_localestring_get(ini, "Name");
1340     if (tmp) theme->name.name = eina_stringshare_add(tmp);
1341
1342     tmp = efreet_ini_localestring_get(ini, "Comment");
1343     if (tmp) theme->comment = strdup(tmp);
1344
1345     tmp = efreet_ini_string_get(ini, "Example");
1346     if (tmp) theme->example_icon = strdup(tmp);
1347
1348     theme->hidden = efreet_ini_boolean_get(ini, "Hidden");
1349
1350     theme->valid = 1;
1351
1352     /* Check the inheritance. If there is none we inherit from the hicolor theme */
1353     tmp = efreet_ini_string_get(ini, "Inherits");
1354     if (tmp)
1355     {
1356         char *t, *s, *p;
1357
1358         t = strdup(tmp);
1359         s = t;
1360         p = strchr(s, ',');
1361
1362         while (p)
1363         {
1364             *p = '\0';
1365
1366             theme->inherits = eina_list_append(theme->inherits, strdup(s));
1367             s = ++p;
1368             p = strchr(s, ',');
1369         }
1370         theme->inherits = eina_list_append(theme->inherits, strdup(s));
1371
1372         FREE(t);
1373     }
1374
1375     /* make sure this one is done last as setting the directory will change
1376      * the ini section ... */
1377     tmp = efreet_ini_string_get(ini, "Directories");
1378     if (tmp)
1379     {
1380         char *t, *s, *p;
1381
1382         t = strdup(tmp);
1383         s = t;
1384         p = s;
1385
1386         while (p)
1387         {
1388             p = strchr(s, ',');
1389
1390             if (p) *p = '\0';
1391
1392             theme->directories = eina_list_append(theme->directories,
1393                             efreet_icon_theme_directory_new(ini, s));
1394
1395             if (p) s = ++p;
1396         }
1397
1398         FREE(t);
1399     }
1400
1401     efreet_ini_free(ini);
1402 }
1403
1404 /**
1405  * @internal
1406  * @return Returns no value
1407  * @brief Because the theme icon directories can be spread over multiple
1408  * base directories we may need to create the icon theme strucutre before
1409  * finding the index.theme file. It may also be that we never find an
1410  * index.theme file as this isn't a valid theme. This function makes sure
1411  * that everything we've got in our hash has a valid key to it.
1412  */
1413 static void
1414 efreet_icon_theme_dir_validity_check(void)
1415 {
1416     Eina_List *keys;
1417     const char *name;
1418     Eina_Iterator *it;
1419
1420     keys = NULL;
1421     it = eina_hash_iterator_key_new(efreet_icon_themes);
1422     eina_iterator_foreach(it, EINA_EACH(_hash_keys), &keys);
1423     eina_iterator_free(it);
1424
1425     EINA_LIST_FREE(keys, name)
1426     {
1427         Efreet_Icon_Theme *theme;
1428
1429         theme = eina_hash_find(efreet_icon_themes, name);
1430         if (theme && !theme->valid && !theme->fake)
1431             eina_hash_del(efreet_icon_themes, name, theme);
1432     }
1433 }
1434
1435 /**
1436  * @internal
1437  * @param ini: The ini file with information on this directory
1438  * @param name: The name of the directory
1439  * @return Returns a new Efreet_Icon_Theme_Directory based on the
1440  * information in @a ini.
1441  * @brief Creates and initialises an icon theme directory from the given ini
1442  * information
1443  */
1444 static Efreet_Icon_Theme_Directory *
1445 efreet_icon_theme_directory_new(Efreet_Ini *ini, const char *name)
1446 {
1447     Efreet_Icon_Theme_Directory *dir;
1448     int val;
1449     const char *tmp;
1450
1451     if (!ini) return NULL;
1452
1453     dir = NEW(Efreet_Icon_Theme_Directory, 1);
1454     dir->name = strdup(name);
1455
1456     efreet_ini_section_set(ini, name);
1457
1458     tmp = efreet_ini_string_get(ini, "Context");
1459     if (tmp)
1460     {
1461         if (!strcasecmp(tmp, "Actions"))
1462             dir->context = EFREET_ICON_THEME_CONTEXT_ACTIONS;
1463
1464         else if (!strcasecmp(tmp, "Devices"))
1465             dir->context = EFREET_ICON_THEME_CONTEXT_DEVICES;
1466
1467         else if (!strcasecmp(tmp, "FileSystems"))
1468             dir->context = EFREET_ICON_THEME_CONTEXT_FILESYSTEMS;
1469
1470         else if (!strcasecmp(tmp, "MimeTypes"))
1471             dir->context = EFREET_ICON_THEME_CONTEXT_MIMETYPES;
1472     }
1473
1474     tmp = efreet_ini_string_get(ini, "Type");
1475     if (tmp)
1476     {
1477         if (!strcasecmp(tmp, "Fixed"))
1478             dir->type = EFREET_ICON_SIZE_TYPE_FIXED;
1479
1480         else if (!strcasecmp(tmp, "Scalable"))
1481             dir->type = EFREET_ICON_SIZE_TYPE_SCALABLE;
1482
1483         else if (!strcasecmp(tmp, "Threshold"))
1484             dir->type = EFREET_ICON_SIZE_TYPE_THRESHOLD;
1485     }
1486
1487     dir->size.normal = efreet_ini_int_get(ini, "Size");
1488
1489     val = efreet_ini_int_get(ini, "MinSize");
1490     if (val < 0) dir->size.min = dir->size.normal;
1491     else dir->size.min = val;
1492
1493     val = efreet_ini_int_get(ini, "MaxSize");
1494     if (val < 0) dir->size.max = dir->size.normal;
1495     else dir->size.max = val;
1496
1497     val = efreet_ini_int_get(ini, "Threshold");
1498     if (val < 0) dir->size.threshold = 2;
1499     else dir->size.threshold = val;
1500
1501     return dir;
1502 }
1503
1504 /**
1505  * @internal
1506  * @param dir: The Efreet_Icon_Theme_Directory to free
1507  * @return Returns no value
1508  * @brief Frees the given directory @a dir
1509  */
1510 static void
1511 efreet_icon_theme_directory_free(Efreet_Icon_Theme_Directory *dir)
1512 {
1513     if (!dir) return;
1514
1515     IF_FREE(dir->name);
1516     FREE(dir);
1517 }
1518
1519 static int
1520 efreet_icon_cache_find(Efreet_Icon_Cache *value, const char *key)
1521 {
1522     if (!value || !key) return -1;
1523     return strcmp(value->key, key);
1524 }
1525
1526 static void
1527 efreet_icon_cache_flush(Efreet_Icon_Theme *theme, Eina_List *list)
1528 {
1529     /* TODO:
1530      * * Dynamic cache size
1531      * * Maybe add references to cache, so that we sort on how often a value is used
1532      */
1533     while (eina_list_count(list) > 100)
1534     {
1535         Efreet_Icon_Cache *cache;
1536         Eina_List *last;
1537
1538         last = eina_list_last(list);
1539         cache = eina_list_data_get(last);
1540         efreet_icon_cache_free(cache);
1541         list = eina_list_remove_list(list, last);
1542     }
1543
1544     eina_hash_modify(efreet_icon_cache, theme, list);
1545 }
1546
1547 static void
1548 efreet_icon_cache_free(Efreet_Icon_Cache *value)
1549 {
1550     if (!value) return;
1551
1552     IF_FREE(value->key);
1553     IF_FREE(value->path);
1554     free(value);
1555 }
1556
1557 static char *
1558 efreet_icon_cache_check(Efreet_Icon_Theme *theme, const char *icon, unsigned int size)
1559 {
1560     Eina_List *list;
1561     Efreet_Icon_Cache *cache;
1562     char key[4096];
1563     struct stat st;
1564
1565     list = eina_hash_find(efreet_icon_cache, theme);
1566     if (!list) return NULL;
1567
1568     snprintf(key, sizeof(key), "%s %d", icon, size);
1569     cache = eina_list_search_unsorted(list, (Eina_Compare_Cb)efreet_icon_cache_find, key);
1570     if (cache)
1571     {
1572         if (!cache->path)
1573         {
1574             list = eina_list_promote_list(list, eina_list_data_find_list(list, cache));
1575             eina_hash_modify(efreet_icon_cache, theme, list);
1576             return NON_EXISTING;
1577         }
1578         else if (!stat(cache->path, &st) && st.st_mtime == cache->lasttime)
1579         {
1580             list = eina_list_promote_list(list, eina_list_data_find_list(list, cache));
1581             eina_hash_modify(efreet_icon_cache, theme, list);
1582             return strdup(cache->path);
1583         }
1584         efreet_icon_cache_free(cache);
1585         list = eina_list_remove(list, cache);
1586         if (list != NULL) eina_hash_modify(efreet_icon_cache, theme, list);
1587         else eina_hash_del(efreet_icon_cache, theme, NULL);
1588     }
1589     return NULL;
1590 }
1591
1592 static void
1593 efreet_icon_cache_add(Efreet_Icon_Theme *theme, const char *icon, unsigned int size, const char *value)
1594 {
1595     Eina_List *list, *l;
1596     Efreet_Icon_Cache *cache;
1597     char key[4096];
1598     struct stat st;
1599
1600     list = eina_hash_find(efreet_icon_cache, theme);
1601
1602     snprintf(key, sizeof(key), "%s %d", icon, size);
1603     cache = NEW(Efreet_Icon_Cache, 1);
1604     cache->key = strdup(key);
1605     if ((value) && !stat(value, &st))
1606     {
1607         cache->path = strdup(value);
1608         cache->lasttime = st.st_mtime;
1609     }
1610     else
1611         cache->lasttime = ecore_time_get();
1612
1613     l = list;
1614     list = eina_list_prepend(list, cache);
1615
1616     if (!l) eina_hash_add(efreet_icon_cache, theme, list);
1617     else eina_hash_modify(efreet_icon_cache, theme, list);
1618
1619     efreet_icon_cache_flush(theme, list);
1620 }