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