EFL 1.7 svn doobies
[profile/ivi/efreet.git] / src / lib / efreet_mime.c
1 #ifdef HAVE_CONFIG_H
2 # include <config.h>
3 #endif
4
5 #ifdef HAVE_ALLOCA_H
6 # include <alloca.h>
7 #elif defined __GNUC__
8 # define alloca __builtin_alloca
9 #elif defined _AIX
10 # define alloca __alloca
11 #elif defined _MSC_VER
12 # include <malloc.h>
13 # define alloca _alloca
14 #else
15 # include <stddef.h>
16 # ifdef  __cplusplus
17 extern "C"
18 # endif
19 void *alloca (size_t);
20 #endif
21
22 #include <ctype.h>
23 #include <sys/stat.h>
24 #include <fcntl.h>
25 #include <unistd.h>
26 #include <sys/mman.h>
27 #include <fnmatch.h>
28
29 #ifdef _WIN32
30 # include <winsock2.h>
31 #endif
32
33 #ifdef HAVE_NETINET_IN_H
34 # include <netinet/in.h>
35 #endif
36
37 #ifdef HAVE_ARPA_INET_H
38 # include <arpa/inet.h>
39 #endif
40
41 #include <Ecore.h>
42 #include <Ecore_File.h>
43
44 /* define macros and variable for using the eina logging system  */
45 #define EFREET_MODULE_LOG_DOM _efreet_mime_log_dom
46 static int _efreet_mime_log_dom = -1;
47
48 #include "Efreet.h"
49 #include "Efreet_Mime.h"
50 #include "efreet_private.h"
51
52 static Eina_List *globs = NULL;     /* contains Efreet_Mime_Glob structs */
53 static Eina_List *magics = NULL;    /* contains Efreet_Mime_Magic structs */
54 static Eina_Hash *wild = NULL;      /* contains *.ext and mime.types globs*/
55 static Eina_Hash *monitors = NULL;  /* contains file monitors */
56 static Eina_Hash *mime_icons = NULL; /* contains cache with mime->icons */
57 static Eina_Inlist *mime_icons_lru = NULL;
58 static unsigned int _efreet_mime_init_count = 0;
59
60 static const char *_mime_inode_symlink = NULL;
61 static const char *_mime_inode_fifo = NULL;
62 static const char *_mime_inode_chardevice = NULL;
63 static const char *_mime_inode_blockdevice = NULL;
64 static const char *_mime_inode_socket = NULL;
65 static const char *_mime_inode_mountpoint = NULL;
66 static const char *_mime_inode_directory = NULL;
67 static const char *_mime_application_x_executable = NULL;
68 static const char *_mime_application_octet_stream = NULL;
69 static const char *_mime_text_plain = NULL;
70
71 /**
72  * @internal
73  * @brief Holds whether we are big/little endian
74  * @note This is set during efreet_mime_init based on
75  * a runtime check.
76  */
77 static enum
78 {
79     EFREET_ENDIAN_BIG = 0,
80     EFREET_ENDIAN_LITTLE = 1
81 } efreet_mime_endianess = EFREET_ENDIAN_BIG;
82
83 /*
84  * Buffer sized used for magic checks.  The default is good enough for the
85  * current set of magic rules.  This setting is only here for the future.
86  */
87 #define EFREET_MIME_MAGIC_BUFFER_SIZE 512
88
89 /*
90  * Minimum timeout in seconds between mime-icons cache flush.
91  */
92 #define EFREET_MIME_ICONS_FLUSH_TIMEOUT 60
93
94 /*
95  * Timeout in seconds, when older mime-icons items are expired.
96  */
97 #define EFREET_MIME_ICONS_EXPIRE_TIMEOUT 600
98
99 /*
100  * mime-icons maximum population.
101  */
102 #define EFREET_MIME_ICONS_MAX_POPULATION 512
103
104 /*
105  * If defined, dump mime-icons statistics after flush.
106  */
107 //#define EFREET_MIME_ICONS_DEBUG
108
109 typedef struct Efreet_Mime_Glob Efreet_Mime_Glob;
110 struct Efreet_Mime_Glob
111 {
112     const char *glob;
113     const char *mime;
114 };
115
116 typedef struct Efreet_Mime_Magic Efreet_Mime_Magic;
117 struct Efreet_Mime_Magic
118 {
119     unsigned int priority;
120     const char *mime;
121     Eina_List *entries;
122 };
123
124 typedef struct Efreet_Mime_Magic_Entry Efreet_Mime_Magic_Entry;
125 struct Efreet_Mime_Magic_Entry
126 {
127     unsigned int indent;
128     unsigned int offset;
129     unsigned int word_size;
130     unsigned int range_len;
131     unsigned short value_len;
132     char *mask;
133     char *value;
134 };
135
136 typedef struct Efreet_Mime_Icon_Entry_Head Efreet_Mime_Icon_Entry_Head;
137 struct Efreet_Mime_Icon_Entry_Head
138 {
139     EINA_INLIST; /* node of mime_icons_lru */
140     Eina_Inlist *list;
141     const char *mime;
142     double timestamp;
143 };
144
145 typedef struct Efreet_Mime_Icon_Entry Efreet_Mime_Icon_Entry;
146 struct Efreet_Mime_Icon_Entry
147 {
148     EINA_INLIST;
149     const char *icon;
150     const char *theme;
151     unsigned int size;
152 };
153
154 static int efreet_mime_glob_remove(const char *glob);
155 static void efreet_mime_mime_types_load(const char *file);
156 static void efreet_mime_shared_mimeinfo_globs_load(const char *file);
157 static void efreet_mime_shared_mimeinfo_magic_load(const char *file);
158 static void efreet_mime_shared_mimeinfo_magic_parse(char *data, int size);
159 static const char *efreet_mime_magic_check_priority(const char *file,
160                                                       unsigned int start,
161                                                       unsigned int end);
162 static int efreet_mime_init_files(void);
163 static const char *efreet_mime_special_check(const char *file);
164 static const char *efreet_mime_fallback_check(const char *file);
165 static void efreet_mime_glob_free(void *data);
166 static void efreet_mime_magic_free(void *data);
167 static void efreet_mime_magic_entry_free(void *data);
168 static int efreet_mime_glob_match(const char *str, const char *glob);
169 static int efreet_mime_glob_case_match(char *str, const char *glob);
170 static int efreet_mime_endian_check(void);
171
172 static void efreet_mime_monitor_add(const char *file);
173 static void efreet_mime_cb_update_file(void *data,
174                                         Ecore_File_Monitor *monitor,
175                                         Ecore_File_Event event,
176                                         const char *path);
177
178 static void efreet_mime_icons_flush(double now);
179 static void efreet_mime_icon_entry_head_free(Efreet_Mime_Icon_Entry_Head *entry);
180 static void efreet_mime_icon_entry_add(const char *mime,
181                                        const char *icon,
182                                        const char *theme,
183                                        unsigned int size);
184 static const char *efreet_mime_icon_entry_find(const char *mime,
185                                                const char *theme,
186                                                unsigned int size);
187 static void efreet_mime_icons_debug(void);
188
189 EAPI int
190 efreet_mime_init(void)
191 {
192     if (++_efreet_mime_init_count != 1)
193         return _efreet_mime_init_count;
194
195     if (!ecore_init())
196         return --_efreet_mime_init_count;
197
198     if (!ecore_file_init())
199         goto shutdown_ecore;
200
201     if (!efreet_init())
202         goto shutdown_ecore_file;
203
204     _efreet_mime_log_dom = eina_log_domain_register
205       ("efreet_mime", EFREET_DEFAULT_LOG_COLOR);
206
207     if (_efreet_mime_log_dom < 0)
208     {
209         EINA_LOG_ERR("Efreet: Could not create a log domain for efreet_mime.");
210         goto shutdown_efreet;
211     }
212
213     efreet_mime_endianess = efreet_mime_endian_check();
214
215     monitors = eina_hash_string_superfast_new(EINA_FREE_CB(ecore_file_monitor_del));
216
217     efreet_mime_type_cache_clear();
218
219     if (!efreet_mime_init_files())
220         goto unregister_log_domain;
221
222     return _efreet_mime_init_count;
223
224 unregister_log_domain:
225     eina_log_domain_unregister(_efreet_mime_log_dom);
226     _efreet_mime_log_dom = -1;
227 shutdown_efreet:
228     efreet_shutdown();
229 shutdown_ecore_file:
230     ecore_file_shutdown();
231 shutdown_ecore:
232     ecore_shutdown();
233
234     return --_efreet_mime_init_count;
235 }
236
237 EAPI int
238 efreet_mime_shutdown(void)
239 {
240     if (--_efreet_mime_init_count != 0)
241         return _efreet_mime_init_count;
242
243     efreet_mime_icons_debug();
244
245     IF_RELEASE(_mime_inode_symlink);
246     IF_RELEASE(_mime_inode_fifo);
247     IF_RELEASE(_mime_inode_chardevice);
248     IF_RELEASE(_mime_inode_blockdevice);
249     IF_RELEASE(_mime_inode_socket);
250     IF_RELEASE(_mime_inode_mountpoint);
251     IF_RELEASE(_mime_inode_directory);
252     IF_RELEASE(_mime_application_x_executable);
253     IF_RELEASE(_mime_application_octet_stream);
254     IF_RELEASE(_mime_text_plain);
255
256     IF_FREE_LIST(globs, efreet_mime_glob_free);
257     IF_FREE_LIST(magics, efreet_mime_magic_free);
258     IF_FREE_HASH(monitors);
259     IF_FREE_HASH(wild);
260     IF_FREE_HASH(mime_icons);
261     eina_log_domain_unregister(_efreet_mime_log_dom);
262     _efreet_mime_log_dom = -1;
263     efreet_shutdown();
264     ecore_file_shutdown();
265     ecore_shutdown();
266
267     return _efreet_mime_init_count;
268 }
269
270 EAPI const char *
271 efreet_mime_type_get(const char *file)
272 {
273     const char *type = NULL;
274
275     EINA_SAFETY_ON_NULL_RETURN_VAL(file, NULL);
276
277     if ((type = efreet_mime_special_check(file)))
278         return type;
279
280     /* Check magics with priority > 80 */
281     if ((type = efreet_mime_magic_check_priority(file, 0, 80)))
282         return type;
283
284     /* Check globs */
285     if ((type = efreet_mime_globs_type_get(file)))
286         return type;
287
288     /* Check rest of magics */
289     if ((type = efreet_mime_magic_check_priority(file, 80, 0)))
290         return type;
291
292     return efreet_mime_fallback_check(file);
293 }
294
295 EAPI const char *
296 efreet_mime_type_icon_get(const char *mime, const char *theme, unsigned int size)
297 {
298     const char *icon = NULL;
299     char *data;
300     Eina_List *icons  = NULL;
301     const char *env = NULL;
302     char *p = NULL, *pp = NULL, *ppp = NULL;
303     char buf[PATH_MAX];
304     const char *cache;
305
306     EINA_SAFETY_ON_NULL_RETURN_VAL(mime, NULL);
307     EINA_SAFETY_ON_NULL_RETURN_VAL(theme, NULL);
308
309     mime = eina_stringshare_add(mime);
310     theme = eina_stringshare_add(theme);
311     cache = efreet_mime_icon_entry_find(mime, theme, size);
312     if (cache)
313     {
314         eina_stringshare_del(mime);
315         eina_stringshare_del(theme);
316         return cache;
317     }
318
319     /* Standard icon name */
320     p = strdup(mime);
321     pp = p;
322     while (*pp)
323     {
324         if (*pp == '/') *pp = '-';
325         pp++;
326     }
327     icons = eina_list_append(icons, p);
328
329     /* Environment Based icon names */
330     if ((env = efreet_desktop_environment_get()))
331     {
332         snprintf(buf, sizeof(buf), "%s-mime-%s", env, p);
333         icons = eina_list_append(icons, strdup(buf));
334
335         snprintf(buf, sizeof(buf), "%s-%s", env, p);
336         icons = eina_list_append(icons, strdup(buf));
337     }
338
339     /* Mime prefixed icon names */
340     snprintf(buf, sizeof(buf), "mime-%s", p);
341     icons = eina_list_append(icons, strdup(buf));
342
343     /* Generic icons */
344     pp = strdup(p);
345     while ((ppp = strrchr(pp, '-')))
346     {
347         *ppp = '\0';
348
349         snprintf(buf, sizeof(buf), "%s-x-generic", pp);
350         icons = eina_list_append(icons, strdup(buf));
351
352         snprintf(buf, sizeof(buf), "%s-generic", pp);
353         icons = eina_list_append(icons, strdup(buf));
354
355         snprintf(buf, sizeof(buf), "%s", pp);
356         icons = eina_list_append(icons, strdup(buf));
357     }
358     FREE(pp);
359
360     /* Search for icons using list */
361     icon = efreet_icon_list_find(theme, icons, size);
362     while (icons)
363     {
364         data = eina_list_data_get(icons);
365         free(data);
366         icons = eina_list_remove_list(icons, icons);
367     }
368
369     efreet_mime_icon_entry_add(mime, eina_stringshare_add(icon), theme, size);
370
371     return icon;
372 }
373
374 EAPI void
375 efreet_mime_type_cache_clear(void)
376 {
377     if (mime_icons)
378     {
379         eina_hash_free(mime_icons);
380         mime_icons_lru = NULL;
381     }
382     mime_icons = eina_hash_stringshared_new(EINA_FREE_CB(efreet_mime_icon_entry_head_free));
383 }
384
385 EAPI void
386 efreet_mime_type_cache_flush(void)
387 {
388     efreet_mime_icons_flush(ecore_loop_time_get());
389 }
390
391
392 EAPI const char *
393 efreet_mime_magic_type_get(const char *file)
394 {
395     EINA_SAFETY_ON_NULL_RETURN_VAL(file, NULL);
396     return efreet_mime_magic_check_priority(file, 0, 0);
397 }
398
399 EAPI const char *
400 efreet_mime_globs_type_get(const char *file)
401 {
402     Eina_List *l;
403     Efreet_Mime_Glob *g;
404     char *sl, *p;
405     const char *s;
406     char *ext, *mime;
407
408     EINA_SAFETY_ON_NULL_RETURN_VAL(file, NULL);
409
410     /* Check in the extension hash for the type */
411     ext = strchr(file, '.');
412     if (ext)
413     {
414         sl = alloca(strlen(ext) + 1);
415         for (s = ext, p = sl; *s; s++, p++) *p = tolower(*s);
416         *p = 0;
417         p = sl;
418         while (p)
419         {
420             p++;
421             if (p && (mime = eina_hash_find(wild, p))) return mime;
422             p = strchr(p, '.');
423         }
424     }
425
426     /* Fallback to the other globs if not found */
427     EINA_LIST_FOREACH(globs, l, g)
428     {
429         if (efreet_mime_glob_match(file, g->glob))
430             return g->mime;
431     }
432
433     ext = alloca(strlen(file) + 1);
434     for (s = file, p = ext; *s; s++, p++) *p = tolower(*s);
435     *p = 0;
436     EINA_LIST_FOREACH(globs, l, g)
437     {
438         if (efreet_mime_glob_case_match(ext, g->glob))
439             return g->mime;
440     }
441     return NULL;
442 }
443
444 EAPI const char *
445 efreet_mime_special_type_get(const char *file)
446 {
447     EINA_SAFETY_ON_NULL_RETURN_VAL(file, NULL);
448     return efreet_mime_special_check(file);
449 }
450
451 EAPI const char *
452 efreet_mime_fallback_type_get(const char *file)
453 {
454     EINA_SAFETY_ON_NULL_RETURN_VAL(file, NULL);
455     return efreet_mime_fallback_check(file);
456 }
457
458 /**
459  * @internal
460  * @return Returns the endianess
461  * @brief Retreive the endianess of the machine
462  */
463 static int
464 efreet_mime_endian_check(void)
465 {
466     int test = 1;
467     return (*((char*)(&test)));
468 }
469
470 /**
471  * @internal
472  * @param file File to monitor
473  * @return Returns no value.
474  * @brief Creates a new file monitor if we aren't already monitoring the
475  * given file
476  */
477 static void
478 efreet_mime_monitor_add(const char *file)
479 {
480     Ecore_File_Monitor *fm = NULL;
481
482     /* if this is already in our hash then we're already monitoring so no
483      * reason to re-monitor */
484     if (eina_hash_find(monitors, file))
485         return;
486
487     if ((fm = ecore_file_monitor_add(file, efreet_mime_cb_update_file, NULL)))
488     {
489         eina_hash_del(monitors, file, NULL);
490         eina_hash_add(monitors, file, fm);
491     }
492 }
493
494 /**
495  * @internal
496  * @param datadirs List of XDG data dirs
497  * @param datahome Path to XDG data home directory
498  * @return Returns no value
499  * @brief Read all glob files in XDG data/home dirs.
500  * Also reads the /etc/mime.types file.
501  */
502 static void
503 efreet_mime_load_globs(Eina_List *datadirs, const char *datahome)
504 {
505     Eina_List *l;
506     char buf[4096];
507     const char *datadir = NULL;
508
509     IF_FREE_HASH(wild);
510     wild = eina_hash_string_superfast_new(EINA_FREE_CB(eina_stringshare_del));
511     while (globs)
512     {
513         efreet_mime_glob_free(eina_list_data_get(globs));
514         globs = eina_list_remove_list(globs, globs);
515     }
516
517     /*
518      * This is here for legacy reasons.  It is mentioned briefly
519      * in the spec and seems to still be quite valid.  It is
520      * loaded first so the globs files will override anything
521      * in here.
522     */
523     efreet_mime_mime_types_load("/etc/mime.types");
524
525     datadir = datahome;
526     snprintf(buf, sizeof(buf), "%s/mime/globs", datadir);
527     efreet_mime_shared_mimeinfo_globs_load(buf);
528
529     EINA_LIST_FOREACH(datadirs, l, datadir)
530     {
531         snprintf(buf, sizeof(buf), "%s/mime/globs", datadir);
532         efreet_mime_shared_mimeinfo_globs_load(buf);
533     }
534 }
535
536 /**
537  * @internal
538  * @param datadirs List of XDG data dirs
539  * @param datahome Path to XDG data home directory
540  * @return Returns no value
541  * @brief Read all magic files in XDG data/home dirs.
542  */
543 static void
544 efreet_mime_load_magics(Eina_List *datadirs, const char *datahome)
545 {
546     Eina_List *l;
547     char buf[4096];
548     const char *datadir = NULL;
549
550     while (magics)
551     {
552         efreet_mime_magic_free(eina_list_data_get(magics));
553         magics = eina_list_remove_list(magics, magics);
554     }
555
556     datadir = datahome;
557     snprintf(buf, sizeof(buf), "%s/mime/magic", datadir);
558     efreet_mime_shared_mimeinfo_magic_load(buf);
559
560     EINA_LIST_FOREACH(datadirs, l, datadir)
561     {
562         snprintf(buf, sizeof(buf), "%s/mime/magic", datadir);
563         efreet_mime_shared_mimeinfo_magic_load(buf);
564     }
565 }
566
567 /**
568  * @internal
569  * @param data Data pointer passed to monitor_add
570  * @param monitor Ecore_File_Monitor associated with this event
571  * @param event The type of event
572  * @param path Path to the file that was updated
573  * @return Returns no value
574  * @brief Callback for all file monitors.  Just reloads the appropriate
575  * list depending on which file changed.  If it was a magic file
576  * only the magic list is updated.  If it was a glob file or /etc/mime.types,
577  * the globs are updated.
578  */
579 static void
580 efreet_mime_cb_update_file(void *data __UNUSED__,
581                     Ecore_File_Monitor *monitor __UNUSED__,
582                     Ecore_File_Event event __UNUSED__,
583                     const char *path)
584 {
585     Eina_List *datadirs = NULL;
586     const char *datahome = NULL;
587
588     if (!(datahome = efreet_data_home_get()))
589         return;
590
591     if (!(datadirs = efreet_data_dirs_get()))
592         return;
593
594     if (strstr(path, "magic"))
595         efreet_mime_load_magics(datadirs, datahome);
596     else
597         efreet_mime_load_globs(datadirs, datahome);
598 }
599
600 /**
601  * @internal
602  * @param datadirs List of XDG data dirs
603  * @param datahome Path to XDG data home directory
604  * @return Returns 1 on success, 0 on failure
605  * @brief Initializes globs, magics, and monitors lists.
606  */
607 static int
608 efreet_mime_init_files(void)
609 {
610     Eina_List *l;
611     Eina_List *datadirs = NULL;
612     char buf[PATH_MAX];
613     const char *datahome, *datadir = NULL;
614
615     if (!(datahome = efreet_data_home_get()))
616         return 0;
617
618     if (!(datadirs = efreet_data_dirs_get()))
619         return 0;
620
621     /*
622      * Add our file monitors
623      * We watch the directories so we can watch for new files
624      */
625     datadir = datahome;
626     snprintf(buf, sizeof(buf), "%s/mime", datadir);
627     efreet_mime_monitor_add(buf);
628
629     EINA_LIST_FOREACH(datadirs, l, datadir)
630     {
631         snprintf(buf, sizeof(buf), "%s/mime", datadir);
632         efreet_mime_monitor_add(buf);
633     }
634     efreet_mime_monitor_add("/etc/mime.types");
635
636     /* Load our mime information */
637     efreet_mime_load_globs(datadirs, datahome);
638     efreet_mime_load_magics(datadirs, datahome);
639
640     _mime_inode_symlink            = eina_stringshare_add("inode/symlink");
641     _mime_inode_fifo               = eina_stringshare_add("inode/fifo");
642     _mime_inode_chardevice         = eina_stringshare_add("inode/chardevice");
643     _mime_inode_blockdevice        = eina_stringshare_add("inode/blockdevice");
644     _mime_inode_socket             = eina_stringshare_add("inode/socket");
645     _mime_inode_mountpoint         = eina_stringshare_add("inode/mountpoint");
646     _mime_inode_directory          = eina_stringshare_add("inode/directory");
647     _mime_application_x_executable = eina_stringshare_add("application/x-executable");
648     _mime_application_octet_stream = eina_stringshare_add("application/octet-stream");
649     _mime_text_plain               = eina_stringshare_add("text/plain");
650
651     return 1;
652 }
653
654 /**
655  * @internal
656  * @param file File to examine
657  * @return Returns mime type if special file, else NULL
658  * @brief Returns a mime type based on the stat of a file.
659  * This is used first to catch directories and other special
660  * files.  A NULL return doesn't necessarily mean failure, but
661  * can also mean the file is regular.
662  * @note Mapping of file types to mime types:
663  * Stat Macro   File Type           Mime Type
664  * S_IFREG      regular             NULL
665  * S_IFIFO      named pipe (fifo)   inode/fifo
666  * S_IFCHR      character special   inode/chardevice
667  * S_IFDIR      directory           inode/directory
668  * S_IFBLK      block special       inode/blockdevice
669  * S_IFLNK      symbolic link       inode/symlink
670  * S_IFSOCK     socket              inode/socket
671  *
672  * This function can also return inode/mount-point.
673  * This is calculated by comparing the st_dev of the directory
674  * against that of it's parent directory.  If they differ it
675  * is considered a mount point.
676  */
677 static const char *
678 efreet_mime_special_check(const char *file)
679 {
680     struct stat s;
681     int path_len = 0;
682
683     /* no link on Windows < Vista */
684 #ifdef _WIN32
685     if (!stat(file, &s))
686 #else
687     if (!lstat(file, &s))
688 #endif
689     {
690         if (S_ISREG(s.st_mode))
691             return NULL;
692
693 #ifndef _WIN32
694         if (S_ISLNK(s.st_mode))
695         return _mime_inode_symlink;
696 #endif
697
698         if (S_ISFIFO(s.st_mode))
699             return _mime_inode_fifo;
700
701         if (S_ISCHR(s.st_mode))
702             return _mime_inode_chardevice;
703
704         if (S_ISBLK(s.st_mode))
705             return _mime_inode_blockdevice;
706
707 #ifndef _WIN32
708         if (S_ISSOCK(s.st_mode))
709             return _mime_inode_socket;
710 #endif
711
712         if (S_ISDIR(s.st_mode))
713         {
714             struct stat s2;
715             char parent[PATH_MAX];
716             char path[PATH_MAX];
717
718             strncpy(path, file, PATH_MAX);
719
720             path_len = strlen(file);
721             strncpy(parent, path, PATH_MAX);
722
723             /* Kill any trailing slash */
724             parent[--path_len] = '\0';
725
726             /* Truncate to last slash */
727             while (parent[--path_len] != '/') parent[path_len] = '\0';
728
729 #ifdef _WIN32
730             if (!stat(file, &s2))
731 #else
732             if (!lstat(parent, &s2))
733 #endif
734             {
735                 if (s.st_dev != s2.st_dev)
736                     return _mime_inode_mountpoint;
737             }
738
739             return _mime_inode_directory;
740         }
741
742         return NULL;
743     }
744
745     return NULL;
746 }
747
748 /**
749  * @internal
750  * @param file File to examine
751  * @return Returns mime type or NULL if the file doesn't exist
752  * @brief Returns text/plain if the file appears to contain text and
753  * returns application/octet-stream if it appears to be binary.
754  */
755 static const char *
756 efreet_mime_fallback_check(const char *file)
757 {
758     FILE *f = NULL;
759     char buf[32];
760     int i;
761
762     if (ecore_file_can_exec(file))
763         return _mime_application_x_executable;
764
765     if (!(f = fopen(file, "r"))) return NULL;
766
767     i = fread(buf, 1, sizeof(buf), f);
768     fclose(f);
769
770     if (i == 0) return _mime_application_octet_stream;
771
772     /*
773      * Check for ASCII control characters in the first 32 bytes.
774      * Line Feeds, carriage returns, and tabs are ignored as they are
775      * quite common in text files in the first 32 chars.
776      */
777     for (i -= 1; i >= 0; --i)
778     {
779         if ((buf[i] < 0x20) &&
780             (buf[i] != '\n') &&     /* Line Feed */
781             (buf[i] != '\r') &&     /* Carriage Return */
782             (buf[i] != '\t'))       /* Tab */
783             return _mime_application_octet_stream;
784     }
785
786     return _mime_text_plain;
787 }
788
789 /**
790  * @internal
791  * @param glob Glob to search for
792  * @return Returns 1 on success, 0 on failure
793  * @brief Removes a glob from the list
794  */
795 static int
796 efreet_mime_glob_remove(const char *glob)
797 {
798     Efreet_Mime_Glob *mime = NULL;
799
800     if ((mime = eina_list_search_unsorted(globs, EINA_COMPARE_CB(strcmp), glob)))
801     {
802         globs = eina_list_remove(globs, mime);
803         IF_RELEASE(mime->glob);
804         IF_RELEASE(mime->mime);
805         FREE(mime);
806         return 1;
807     }
808
809     return 0;
810 }
811
812 static inline const char *
813 efreet_eat_space(const char *head, const Eina_File_Line *ln, Eina_Bool not)
814 {
815    if (not)
816      {
817         while (!isspace(*head) && (head < ln->end))
818           head++;
819      }
820    else
821      {
822         while (isspace(*head) && (head < ln->end))
823           head++;
824      }
825
826    return head;
827 }
828
829 /**
830  * @internal
831  * @param file mime.types file to load
832  * @return Returns no value
833  * @brief Loads values from a mime.types style file
834  * into the globs list.
835  * @note Format:
836  * application/msaccess     mdb
837  * application/msword       doc dot
838  */
839 static void
840 efreet_mime_mime_types_load(const char *file)
841 {
842    const Eina_File_Line *ln;
843    Eina_Iterator *it;
844    Eina_File *f;
845    const char *head_line;
846    const char *word_start;
847    const char *mimetype;
848
849    f = eina_file_open(file, 0);
850    if (!f) return ;
851
852    it = eina_file_map_lines(f);
853    if (it)
854      {
855         Eina_Strbuf *ext;
856
857         ext = eina_strbuf_new();
858
859         EINA_ITERATOR_FOREACH(it, ln)
860           {
861              head_line = efreet_eat_space(ln->start, ln, EINA_FALSE);
862              if (head_line == ln->end) continue ;
863
864              if (*head_line == '#') continue ;
865
866              word_start = head_line;
867              head_line = efreet_eat_space(head_line, ln, EINA_TRUE);
868
869              if (head_line == ln->end) continue ;
870              mimetype = eina_stringshare_add_length(word_start, head_line - word_start);
871              do
872                {
873                   head_line = efreet_eat_space(head_line, ln, EINA_FALSE);
874                   if (head_line == ln->end) break ;
875
876                   word_start = head_line;
877                   head_line = efreet_eat_space(head_line, ln, EINA_TRUE);
878
879                   eina_strbuf_append_length(ext, word_start, head_line - word_start);
880
881                   eina_hash_del(wild,
882                                 eina_strbuf_string_get(ext),
883                                 NULL);
884                   eina_hash_add(wild,
885                                 eina_strbuf_string_get(ext),
886                                 eina_stringshare_ref(mimetype));
887
888                   eina_strbuf_reset(ext);
889                }
890              while (head_line < ln->end);
891
892              eina_stringshare_del(mimetype);
893           }
894
895         eina_strbuf_free(ext);
896         eina_iterator_free(it);
897      }
898    eina_file_close(f);
899 }
900
901 /**
902  * @internal
903  * @param file globs file to load
904  * @return Returns no value
905  * @brief Loads values from a mime.types style file
906  * into the globs list.
907  * @note Format:
908  * text/vnd.wap.wml:*.wml
909  * application/x-7z-compressed:*.7z
910  * application/vnd.corel-draw:*.cdr
911  * text/spreadsheet:*.sylk
912  */
913 static void
914 efreet_mime_shared_mimeinfo_globs_load(const char *file)
915 {
916     FILE *f = NULL;
917     char buf[4096], mimetype[4096], ext[4096], *p, *pp;
918     Efreet_Mime_Glob *mime = NULL;
919
920     f = fopen(file, "rb");
921     if (!f) return;
922
923     while (fgets(buf, sizeof(buf), f))
924     {
925         p = buf;
926         while (isspace(*p) && (*p != 0) && (*p != '\n')) p++;
927
928         if (*p == '#') continue;
929         if ((*p == '\n') || (*p == 0)) continue;
930
931         pp = p;
932         while ((*p != ':') && (*p != 0) && (*p != '\n')) p++;
933
934         if ((*p == '\n') || (*p == 0)) continue;
935         strncpy(mimetype, pp, (p - pp));
936         mimetype[p - pp] = 0;
937         p++;
938         pp = ext;
939
940         while ((*p != 0) && (*p != '\n'))
941         {
942             *pp = *p;
943             pp++;
944             p++;
945         }
946
947         *pp = 0;
948
949         if (ext[0] == '*' && ext[1] == '.')
950         {
951             eina_hash_del(wild, &(ext[2]), NULL);
952             eina_hash_add(wild, &(ext[2]),
953                             (void*)eina_stringshare_add(mimetype));
954         }
955         else
956         {
957             mime = NEW(Efreet_Mime_Glob, 1);
958             if (mime)
959             {
960                 mime->mime = eina_stringshare_add(mimetype);
961                 mime->glob = eina_stringshare_add(ext);
962                 if ((!mime->mime) || (!mime->glob))
963                 {
964                     IF_RELEASE(mime->mime);
965                     IF_RELEASE(mime->glob);
966                     FREE(mime);
967                 }
968                 else
969                 {
970                     efreet_mime_glob_remove(ext);
971                     globs = eina_list_append(globs, mime);
972                 }
973             }
974         }
975     }
976
977     fclose(f);
978 }
979
980 /**
981  * @internal
982  * @param in Number to count the digits
983  * @return Returns number of digits
984  * @brief Calculates and returns the number of digits
985  * in a number.
986  */
987 static int
988 efreet_mime_count_digits(int in)
989 {
990     int i = 1, j = in;
991
992     if (j < 10) return 1;
993     while ((j /= 10) > 0) ++i;
994
995     return i;
996 }
997
998 /**
999  * @internal
1000  * @param file File to parse
1001  * @return Returns no value
1002  * @brief Loads a magic file and adds information to magics list
1003  */
1004 static void
1005 efreet_mime_shared_mimeinfo_magic_load(const char *file)
1006 {
1007     int fd = -1, size;
1008     char *data = (void *)-1;
1009
1010     if (!file) return;
1011
1012     size = ecore_file_size(file);
1013     if (size <= 0) return;
1014
1015     fd = open(file, O_RDONLY);
1016     if (fd == -1) return;
1017
1018     /* let's make mmap safe and just get 0 pages for IO erro */
1019     eina_mmap_safety_enabled_set(EINA_TRUE);
1020
1021     data = mmap(NULL, size, PROT_READ, MAP_SHARED, fd, 0);
1022     if (data == MAP_FAILED)
1023     {
1024         close(fd);
1025         return;
1026     }
1027
1028     efreet_mime_shared_mimeinfo_magic_parse(data, size);
1029
1030     munmap(data, size);
1031     close(fd);
1032 }
1033
1034 /**
1035  * @param data The data from the file
1036  * @return Returns no value
1037  * @brief Parses a magic file
1038  * @note Format:
1039  *
1040  * ----------------------------------------------------------------------
1041  * |                     HEX                         |    ASCII         |
1042  * ----------------------------------------------------------------------
1043  * |4D 49 4D 45 2D 4D 61 67 69 63 00 0A 5B 39 30 3A  | MIME-Magic..[90: |
1044  * |61 70 70 6C 69 63 61 74 69 6F 6E 2F 64 6F 63 62  | application/docb |
1045  * |6F 6F 6B 2B 78 6D 6C 5D 0A 3E 30 3D 00 05 3C 3F  | ook+xml].>0=..<? |
1046  * |78 6D 6C 0A 31 3E 30 3D 00 19 2D 2F 2F 4F 41 53  | xml.1>0=..-//OAS |
1047  * |49 53 2F 2F 44 54 44 20 44 6F 63 42 6F 6F 6B 20  | IS//DTD DocBook  |
1048  * |58 4D 4C 2B 31 30 31 0A 31 3E 30 3D 00 17 2D 2F  | XML+101.1>0=..-/ |
1049  * ----------------------------------------------------------------------
1050  *
1051  * indent
1052  *   The nesting depth of the rule, corresponding to the number of '>'
1053  *   characters in the traditional file format.
1054  * ">" start-offset
1055  *     The offset into the file to look for a match.
1056  * "=" value
1057  *     Two bytes giving the (big-endian) length of the value, followed by the
1058  *     value itself.
1059  * "&" mask
1060  *     The mask, which (if present) is exactly the same length as the value.
1061  * "~" word-size
1062  *     On little-endian machines, the size of each group to byte-swap.
1063  * "+" range-length
1064  *     The length of the region in the file to check.
1065  *
1066  * The indent, range-length, word-size and mask components are optional.
1067  * If missing, indent defaults to 0, range-length to 1, the word-size to 1,
1068  * and the mask to all 'one' bits.  In our case, mask is null as it is
1069  * quicker, uses less memory and will achieve the same exact effect.
1070  */
1071 static void
1072 efreet_mime_shared_mimeinfo_magic_parse(char *data, int size)
1073 {
1074     Efreet_Mime_Magic *mime = NULL;
1075     Efreet_Mime_Magic_Entry *entry = NULL;
1076     char *ptr;
1077
1078     ptr = data;
1079
1080     /* make sure we're a magic file */
1081     if (!ptr || (size < 12) || strncmp(ptr, "MIME-Magic\0\n", 12))
1082         return;
1083
1084     ptr += 12;
1085
1086     for (; (ptr - data) < size; )
1087     {
1088         if (*ptr == '[')
1089         {
1090             char *val, buf[512];
1091
1092             mime = NEW(Efreet_Mime_Magic, 1);
1093             magics = eina_list_append(magics, mime);
1094
1095             val = ++ptr;
1096             while ((*val != ':')) val++;
1097             memcpy(&buf, ptr, val - ptr);
1098             buf[val - ptr] = '\0';
1099
1100             mime->priority = atoi(buf);
1101             ptr = ++val;
1102
1103             while ((*val != ']')) val++;
1104             memcpy(&buf, ptr, val - ptr);
1105             buf[val - ptr] = '\0';
1106
1107             mime->mime = eina_stringshare_add(buf);
1108             ptr = ++val;
1109
1110             while (*ptr != '\n') ptr++;
1111             ptr++;
1112         }
1113         else
1114         {
1115             short tshort;
1116
1117             if (!mime) continue;
1118             if (!entry)
1119             {
1120                 if (!(entry = NEW(Efreet_Mime_Magic_Entry, 1)))
1121                 {
1122                     IF_FREE_LIST(magics, efreet_mime_magic_free);
1123                     return;
1124                 }
1125
1126                 entry->indent = 0;
1127                 entry->offset = 0;
1128                 entry->value_len = 0;
1129                 entry->word_size = 1;
1130                 entry->range_len = 1;
1131                 entry->mask = NULL;
1132                 entry->value = NULL;
1133
1134                 mime->entries = eina_list_append(mime->entries, entry);
1135             }
1136
1137             switch(*ptr)
1138             {
1139                 case '>':
1140                     ptr ++;
1141                     entry->offset = atoi(ptr);
1142                     ptr += efreet_mime_count_digits(entry->offset);
1143                     break;
1144
1145                 case '=':
1146                     ptr++;
1147
1148                     tshort = 0;
1149                     memcpy(&tshort, ptr, sizeof(short));
1150                     entry->value_len = ntohs(tshort);
1151                     ptr += 2;
1152
1153                     entry->value = NEW(1, entry->value_len);
1154                     memcpy(entry->value, ptr, entry->value_len);
1155                     ptr += entry->value_len;
1156                     break;
1157
1158                 case '&':
1159                     ptr++;
1160                     entry->mask = NEW(1, entry->value_len);
1161                     memcpy(entry->mask, ptr, entry->value_len);
1162                     ptr += entry->value_len;
1163                     break;
1164
1165                 case '~':
1166                     ptr++;
1167                     entry->word_size = atoi(ptr);
1168                     if ((entry->word_size != 0) && (((entry->word_size != 1)
1169                                     && (entry->word_size != 2)
1170                                     && (entry->word_size != 4))
1171                                 || (entry->value_len % entry->word_size)))
1172                     {
1173                         /* Invalid, Destroy */
1174                         FREE(entry->value);
1175                         FREE(entry->mask);
1176                         FREE(entry);
1177
1178                         while (*ptr != '\n') ptr++;
1179                         break;
1180                     }
1181
1182                     if (efreet_mime_endianess == EFREET_ENDIAN_LITTLE)
1183                     {
1184                         int j;
1185
1186                         for (j = 0; j < entry->value_len; j += entry->word_size)
1187                         {
1188                             if (entry->word_size == 2)
1189                             {
1190                                 ((short*)entry->value)[j] =
1191                                               ntohs(((short*)entry->value)[j]);
1192
1193                                 if (entry->mask)
1194                                     ((short*)entry->mask)[j] =
1195                                               ntohs(((short*)entry->mask)[j]);
1196                             }
1197                             else if (entry->word_size == 4)
1198                             {
1199                                 ((int*)entry->value)[j] =
1200                                               ntohl(((int*)entry->value)[j]);
1201
1202                                 if (entry->mask)
1203                                     ((int*)entry->mask)[j] =
1204                                               ntohl(((int*)entry->mask)[j]);
1205                             }
1206                         }
1207                     }
1208
1209                     ptr += efreet_mime_count_digits(entry->word_size);
1210                     break;
1211
1212                 case '+':
1213                     ptr++;
1214                     entry->range_len = atoi(ptr);
1215                     ptr += efreet_mime_count_digits(entry->range_len);
1216                     break;
1217
1218                 case '\n':
1219                     ptr++;
1220                     entry = NULL;
1221                     break;
1222
1223                 default:
1224                     if (isdigit(*ptr))
1225                     {
1226                         entry->indent = atoi(ptr);
1227                         ptr += efreet_mime_count_digits(entry->indent);
1228                     }
1229                     break;
1230             }
1231         }
1232     }
1233 /*
1234     if (entry)
1235     {
1236         IF_FREE(entry->value);
1237         IF_FREE(entry->mask);
1238         FREE(entry);
1239     }
1240  */
1241 }
1242
1243 /**
1244  * @internal
1245  * @param file File to check
1246  * @param start Start priority, if 0 start at beginning
1247  * @param end End priority, should be less then start
1248  * unless start
1249  * @return Returns mime type for file if found, NULL if not
1250  * @brief Applies magic rules to a file given a start and end priority
1251  */
1252 static const char *
1253 efreet_mime_magic_check_priority(const char *file,
1254                                   unsigned int start,
1255                                   unsigned int end)
1256 {
1257     Efreet_Mime_Magic *m = NULL;
1258     Efreet_Mime_Magic_Entry *e = NULL;
1259     Eina_List *l, *ll;
1260     FILE *f = NULL;
1261     unsigned int i = 0, offset = 0,level = 0, match = 0, bytes_read = 0;
1262     const char *last_mime = NULL;
1263     char c, v, buf[EFREET_MIME_MAGIC_BUFFER_SIZE];
1264
1265     f = fopen(file, "rb");
1266     if (!f) return NULL;
1267
1268     if (!magics)
1269     {
1270         fclose(f);
1271         return NULL;
1272     }
1273
1274     if ((bytes_read = fread(buf, 1, sizeof(buf), f)) == 0)
1275     {
1276         fclose(f);
1277         return NULL;
1278     }
1279
1280     EINA_LIST_FOREACH(magics, l, m)
1281     {
1282         if ((start != 0) && (m->priority > start))
1283             continue;
1284
1285         if (m->priority < end)
1286             break;
1287
1288         EINA_LIST_FOREACH(m->entries, ll, e)
1289         {
1290             if ((level < e->indent) && !match)
1291                 continue;
1292
1293             if ((level >= e->indent) && !match)
1294                 level = e->indent;
1295
1296             else if ((level > e->indent) && match)
1297             {
1298                 fclose(f);
1299                 return last_mime;
1300             }
1301
1302             for (offset = e->offset; offset < e->offset + e->range_len; offset++)
1303             {
1304                 if (((offset + e->value_len) > bytes_read) &&
1305                         (fseek(f, offset, SEEK_SET) == -1))
1306                     break;
1307
1308                 match = 1;
1309                 for (i = 0; i < e->value_len; ++i)
1310                 {
1311                     if (offset + e->value_len > bytes_read)
1312                         c = fgetc(f);
1313                     else
1314                         c = buf[offset + i];
1315
1316                     v = e->value[i];
1317                     if (e->mask) v &= e->mask[i];
1318
1319                     if (!(c == v))
1320                     {
1321                         match = 0;
1322                         break;
1323                     }
1324                 }
1325
1326                 if (match)
1327                 {
1328                     level += 1;
1329                     last_mime = m->mime;
1330                     break;
1331                 }
1332             }
1333         }
1334
1335         if (match)
1336         {
1337             fclose(f);
1338             return last_mime;
1339         }
1340     }
1341     fclose(f);
1342
1343     return NULL;
1344 }
1345
1346 /**
1347  * @internal
1348  * @param data Data pointer that is being destroyed
1349  * @return Returns no value
1350  * @brief Callback for globs destroy
1351  */
1352 static void
1353 efreet_mime_glob_free(void *data)
1354 {
1355     Efreet_Mime_Glob *m = data;
1356
1357     IF_RELEASE(m->mime);
1358     IF_RELEASE(m->glob);
1359     IF_FREE(m);
1360 }
1361
1362 /**
1363  * @internal
1364  * @param data Data pointer that is being destroyed
1365  * @return Returns no value
1366  * @brief Callback for magics destroy
1367  */
1368 static void
1369 efreet_mime_magic_free(void *data)
1370 {
1371     Efreet_Mime_Magic *m = data;
1372
1373     IF_RELEASE(m->mime);
1374     IF_FREE_LIST(m->entries, efreet_mime_magic_entry_free);
1375     IF_FREE(m);
1376 }
1377
1378 /**
1379  * @internal
1380  * @param data Data pointer that is being destroyed
1381  * @return Returns no value
1382  * @brief Callback for magic entry destroy
1383  */
1384 static void
1385 efreet_mime_magic_entry_free(void *data)
1386 {
1387     Efreet_Mime_Magic_Entry *e = data;
1388
1389     IF_FREE(e->mask);
1390     IF_FREE(e->value);
1391     IF_FREE(e);
1392 }
1393
1394
1395 /**
1396  * @internal
1397  * @param str String (filename) to match
1398  * @param glob Glob to match str to
1399  * @return Returns 1 on success, 0 on failure
1400  * @brief Compares str to glob, case sensitive
1401  */
1402 static int
1403 efreet_mime_glob_match(const char *str, const char *glob)
1404 {
1405     if (!str || !glob) return 0;
1406     if (glob[0] == 0)
1407     {
1408         if (str[0] == 0) return 1;
1409         return 0;
1410     }
1411     if (!fnmatch(glob, str, 0)) return 1;
1412     return 0;
1413 }
1414
1415 /**
1416  * @internal
1417  * @param str String (filename) to match
1418  * @param glob Glob to match str to
1419  * @return Returns 1 on success, 0 on failure
1420  * @brief Compares str to glob, case insensitive (expects str already in lower case)
1421  */
1422 static int
1423 efreet_mime_glob_case_match(char *str, const char *glob)
1424 {
1425     const char *p;
1426     char *tglob, *tp;
1427
1428     if (!str || !glob) return 0;
1429     if (glob[0] == 0)
1430     {
1431         if (str[0] == 0) return 1;
1432         return 0;
1433     }
1434     tglob = alloca(strlen(glob) + 1);
1435     for (tp = tglob, p = glob; *p; p++, tp++) *tp = tolower(*p);
1436     *tp = 0;
1437     if (!fnmatch(str, tglob, 0)) return 1;
1438     return 0;
1439 }
1440
1441 static void
1442 efreet_mime_icons_flush(double now)
1443 {
1444     Eina_Inlist *l;
1445     static double old = 0;
1446     int todo;
1447
1448     if (now - old < EFREET_MIME_ICONS_FLUSH_TIMEOUT)
1449         return;
1450     old = now;
1451
1452     todo = eina_hash_population(mime_icons) - EFREET_MIME_ICONS_MAX_POPULATION;
1453     if (todo <= 0)
1454         return;
1455
1456     l = mime_icons_lru->last; /* mime_icons_lru is not NULL, since todo > 0 */
1457     for (; todo > 0; todo--)
1458     {
1459         Efreet_Mime_Icon_Entry_Head *entry = (Efreet_Mime_Icon_Entry_Head *)l;
1460         Eina_Inlist *prev = l->prev;
1461
1462         mime_icons_lru = eina_inlist_remove(mime_icons_lru, l);
1463         eina_hash_del_by_key(mime_icons, entry->mime);
1464         l = prev;
1465     }
1466
1467     efreet_mime_icons_debug();
1468 }
1469
1470 static void
1471 efreet_mime_icon_entry_free(Efreet_Mime_Icon_Entry *node)
1472 {
1473     eina_stringshare_del(node->icon);
1474     eina_stringshare_del(node->theme);
1475     free(node);
1476 }
1477
1478 static void
1479 efreet_mime_icon_entry_head_free(Efreet_Mime_Icon_Entry_Head *entry)
1480 {
1481     while (entry->list)
1482     {
1483         Efreet_Mime_Icon_Entry *n = (Efreet_Mime_Icon_Entry *)entry->list;
1484         entry->list = eina_inlist_remove(entry->list, entry->list);
1485         efreet_mime_icon_entry_free(n);
1486     }
1487
1488     eina_stringshare_del(entry->mime);
1489     free(entry);
1490 }
1491
1492 static Efreet_Mime_Icon_Entry *
1493 efreet_mime_icon_entry_new(const char *icon,
1494                            const char *theme,
1495                            unsigned int size)
1496 {
1497     Efreet_Mime_Icon_Entry *entry;
1498
1499     entry = malloc(sizeof(*entry));
1500     if (!entry)
1501         return NULL;
1502
1503     entry->icon = icon;
1504     entry->theme = theme;
1505     entry->size = size;
1506
1507     return entry;
1508 }
1509
1510 static void
1511 efreet_mime_icon_entry_add(const char *mime,
1512                            const char *icon,
1513                            const char *theme,
1514                            unsigned int size)
1515 {
1516     Efreet_Mime_Icon_Entry_Head *entry;
1517     Efreet_Mime_Icon_Entry *n;
1518
1519     n = efreet_mime_icon_entry_new(icon, theme, size);
1520     if (!n)
1521         return;
1522     entry = eina_hash_find(mime_icons, mime);
1523
1524     if (entry)
1525     {
1526         Eina_Inlist *l;
1527
1528         l = EINA_INLIST_GET(n);
1529         entry->list = eina_inlist_prepend(entry->list, l);
1530
1531         l = EINA_INLIST_GET(entry);
1532         mime_icons_lru = eina_inlist_promote(mime_icons_lru, l);
1533     }
1534     else
1535     {
1536         Eina_Inlist *l;
1537
1538         entry = malloc(sizeof(*entry));
1539         if (!entry)
1540         {
1541             efreet_mime_icon_entry_free(n);
1542             return;
1543         }
1544
1545         l = EINA_INLIST_GET(n);
1546         entry->list = eina_inlist_prepend(NULL, l);
1547         entry->mime = mime;
1548         eina_hash_direct_add(mime_icons, mime, entry);
1549
1550         l = EINA_INLIST_GET(entry);
1551         mime_icons_lru = eina_inlist_prepend(mime_icons_lru, l);
1552     }
1553
1554     entry->timestamp = ecore_loop_time_get();
1555     efreet_mime_icons_flush(entry->timestamp);
1556 }
1557
1558 static const char *
1559 efreet_mime_icon_entry_find(const char *mime,
1560                             const char *theme,
1561                             unsigned int size)
1562 {
1563     Efreet_Mime_Icon_Entry_Head *entry;
1564     Efreet_Mime_Icon_Entry *n;
1565
1566     entry = eina_hash_find(mime_icons, mime);
1567     if (!entry)
1568         return NULL;
1569
1570     EINA_INLIST_FOREACH(entry->list, n)
1571     {
1572         if ((n->theme == theme) && (n->size == size))
1573         {
1574             Eina_Inlist *l;
1575
1576             l = EINA_INLIST_GET(n);
1577             if (entry->list != l)
1578                 entry->list = eina_inlist_promote(entry->list, l);
1579
1580             l = EINA_INLIST_GET(entry);
1581             if (mime_icons_lru != l)
1582                 mime_icons_lru = eina_inlist_promote(mime_icons_lru, l);
1583
1584             entry->timestamp = ecore_loop_time_get();
1585             return n->icon;
1586         }
1587     }
1588
1589     return NULL;
1590 }
1591
1592 #ifdef EFREET_MIME_ICONS_DEBUG
1593 static void
1594 efreet_mime_icons_debug(void)
1595 {
1596     double now = ecore_loop_time_get();
1597     Efreet_Mime_Icon_Entry_Head *entry;
1598     EINA_INLIST_FOREACH(mime_icons_lru, entry)
1599     {
1600         Efreet_Mime_Icon_Entry *n;
1601
1602         if ((now > 0) &&
1603             (now - entry->timestamp >= EFREET_MIME_ICONS_EXPIRE_TIMEOUT))
1604         {
1605             puts("*** FOLLOWING ENTRIES ARE AGED AND CAN BE EXPIRED ***");
1606             now = 0;
1607         }
1608
1609         DBG("mime-icon entry: '%s' last used: %s",
1610             entry->mime, ctime(&entry->timestamp));
1611
1612         EINA_INLIST_FOREACH(entry->list, n)
1613             DBG("\tsize: %3u theme: '%s' icon: '%s'",
1614                    n->theme, n->size, n->icon);
1615     }
1616 }
1617 #else
1618 static void
1619 efreet_mime_icons_debug(void)
1620 {
1621 }
1622 #endif