0927ba9999fff4c52f07c89b753179c80142e590
[framework/uifw/efreet.git] / src / lib / efreet_desktop.c
1 /* vim: set sw=4 ts=4 sts=4 et: */
2 #include "Efreet.h"
3 #include "efreet_private.h"
4
5 #define DESKTOP_VERSION 1.0
6
7 /**
8  * The current desktop environment (e.g. "Enlightenment" or "Gnome")
9  */
10 static const char *desktop_environment = NULL;
11
12 /**
13  * A cache of all loaded desktops, hashed by file name.
14  * Values are Efreet_Desktop structures
15  */
16 static Eina_Hash *efreet_desktop_cache = NULL;
17
18 /**
19  * A list of the desktop types available
20  */
21 static Eina_List *efreet_desktop_types = NULL;
22
23 /**
24  * A unique id for each tmp file created while building a command
25  */
26 static int efreet_desktop_command_file_id = 0;
27
28 static int init = 0;
29 static int cache_flush = 0;
30
31 EAPI int EFREET_DESKTOP_TYPE_APPLICATION = 0;
32 EAPI int EFREET_DESKTOP_TYPE_LINK = 0;
33 EAPI int EFREET_DESKTOP_TYPE_DIRECTORY = 0;
34
35 /**
36  * @internal
37  * Information about custom types
38  */
39 typedef struct Efreet_Desktop_Type_Info Efreet_Desktop_Type_Info;
40 struct Efreet_Desktop_Type_Info
41 {
42     int id;
43     char *type;
44     Efreet_Desktop_Type_Parse_Cb parse_func;
45     Efreet_Desktop_Type_Save_Cb save_func;
46     Efreet_Desktop_Type_Free_Cb free_func;
47 };
48
49 static int efreet_desktop_read(Efreet_Desktop *desktop);
50 static void efreet_desktop_clear(Efreet_Desktop *desktop);
51 static Efreet_Desktop_Type_Info *efreet_desktop_type_parse(const char *type_str);
52 static void *efreet_desktop_application_fields_parse(Efreet_Desktop *desktop,
53                                                     Efreet_Ini *ini);
54 static void efreet_desktop_application_fields_save(Efreet_Desktop *desktop,
55                                                     Efreet_Ini *ini);
56 static void *efreet_desktop_link_fields_parse(Efreet_Desktop *desktop,
57                                                 Efreet_Ini *ini);
58 static void efreet_desktop_link_fields_save(Efreet_Desktop *desktop,
59                                                 Efreet_Ini *ini);
60 static int efreet_desktop_generic_fields_parse(Efreet_Desktop *desktop,
61                                                 Efreet_Ini *ini);
62 static void efreet_desktop_generic_fields_save(Efreet_Desktop *desktop,
63                                                 Efreet_Ini *ini);
64 static Eina_Bool efreet_desktop_x_fields_parse(const Eina_Hash *hash,
65                                                const void *key,
66                                                void *data,
67                                                void *fdata);
68 static Eina_Bool efreet_desktop_x_fields_save(const Eina_Hash *hash,
69                                               const void *key,
70                                               void *value,
71                                               void *fdata);
72 static int efreet_desktop_environment_check(Efreet_Ini *ini);
73 static char *efreet_string_append(char *dest, int *size,
74                                     int *len, const char *src);
75 static char *efreet_string_append_char(char *dest, int *size,
76                                         int *len, char c);
77 static Eina_List *efreet_desktop_command_build(Efreet_Desktop_Command *command);
78 static void efreet_desktop_command_free(Efreet_Desktop_Command *command);
79 static char *efreet_desktop_command_append_quoted(char *dest, int *size,
80                                                     int *len, char *src);
81 static char *efreet_desktop_command_append_icon(char *dest, int *size, int *len,
82                                                 Efreet_Desktop *desktop);
83 static char *efreet_desktop_command_append_single(char *dest, int *size, int *len,
84                                                 Efreet_Desktop_Command_File *file,
85                                                 char type);
86 static char *efreet_desktop_command_append_multiple(char *dest, int *size, int *len,
87                                                     Efreet_Desktop_Command *command,
88                                                     char type);
89
90 static char *efreet_desktop_command_path_absolute(const char *path);
91 static Efreet_Desktop_Command_File *efreet_desktop_command_file_process(
92                                                     Efreet_Desktop_Command *command,
93                                                     const char *file);
94 static const char *efreet_desktop_command_file_uri_process(const char *uri);
95 static void efreet_desktop_command_file_free(Efreet_Desktop_Command_File *file);
96
97 static void efreet_desktop_cb_download_complete(void *data, const char *file,
98                                                                 int status);
99 static int efreet_desktop_cb_download_progress(void *data, const char *file,
100                                            long int dltotal, long int dlnow,
101                                            long int ultotal, long int ulnow);
102
103
104 static void *efreet_desktop_exec_cb(void *data, Efreet_Desktop *desktop,
105                                             char *exec, int remaining);
106
107 static void efreet_desktop_type_info_free(Efreet_Desktop_Type_Info *info);
108 static int efreet_desktop_command_flags_get(Efreet_Desktop *desktop);
109 static void *efreet_desktop_command_execs_process(Efreet_Desktop_Command *command, Eina_List *execs);
110
111 /**
112  * @internal
113  * @return Returns > 0 on success or 0 on failure
114  * @brief Initialize the Desktop parser subsystem
115  */
116 int
117 efreet_desktop_init(void)
118 {
119     if (init++) return init;
120     if (!eina_stringshare_init()) return --init;
121     if (!ecore_file_init()) return --init;
122
123     efreet_desktop_cache = eina_hash_string_superfast_new(NULL);
124     efreet_desktop_types = NULL;
125
126     EFREET_DESKTOP_TYPE_APPLICATION = efreet_desktop_type_add("Application",
127                                         efreet_desktop_application_fields_parse,
128                                         efreet_desktop_application_fields_save,
129                                         NULL);
130     EFREET_DESKTOP_TYPE_LINK = efreet_desktop_type_add("Link",
131                                     efreet_desktop_link_fields_parse,
132                                     efreet_desktop_link_fields_save, NULL);
133     EFREET_DESKTOP_TYPE_DIRECTORY = efreet_desktop_type_add("Directory", NULL,
134                                                                 NULL, NULL);
135
136     return init;
137 }
138
139 /**
140  * @internal
141  * @returns the number of initializations left for this system
142  * @brief Attempts to shut down the subsystem if nothing else is using it
143  */
144 int
145 efreet_desktop_shutdown(void)
146 {
147     Efreet_Desktop_Type_Info *info;
148
149     if (--init) return init;
150     ecore_file_shutdown();
151     eina_stringshare_shutdown();
152
153     IF_RELEASE(desktop_environment);
154     IF_FREE_HASH(efreet_desktop_cache);
155     while (efreet_desktop_types)
156     {
157         info = eina_list_data_get(efreet_desktop_types);
158         efreet_desktop_type_info_free(info);
159         efreet_desktop_types = eina_list_remove_list(efreet_desktop_types,
160                                                      efreet_desktop_types);
161     }
162
163     return init;
164 }
165
166 /**
167  * @internal
168  * @param desktop: The desktop to check
169  * @return Returns 1 if the cache is still valid, 0 otherwise
170  * @brief This will check if the desktop cache is still valid.
171  */
172 static int
173 efreet_desktop_cache_check(Efreet_Desktop *desktop)
174 {
175     if (!desktop) return 0;
176
177     /* have we modified this file since we last read it in? */
178     if ((desktop->cache_flush != cache_flush) ||
179         (ecore_file_mod_time(desktop->orig_path) != desktop->load_time))
180      return 0;
181
182     return 1;
183 }
184
185 /**
186  * @param file: The file to get the Efreet_Desktop from
187  * @return Returns a reference to a cached Efreet_Desktop on success, NULL
188  * on failure. This reference should not be freed.
189  * @brief Gets a reference to an Efreet_Desktop structure representing the
190  * contents of @a file or NULL if @a file is not a valid .desktop file.
191  */
192 EAPI Efreet_Desktop *
193 efreet_desktop_get(const char *file)
194 {
195     Efreet_Desktop *desktop;
196
197     if (efreet_desktop_cache)
198     {
199         desktop = eina_hash_find(efreet_desktop_cache, file);
200         if (desktop)
201         {
202             if (efreet_desktop_cache_check(desktop))
203             {
204                 desktop->ref++;
205                 return desktop;
206             }
207
208             efreet_desktop_clear(desktop);
209             if (efreet_desktop_read(desktop))
210             {
211                 desktop->ref++;
212                 desktop->cache_flush = cache_flush;
213                 return desktop;
214             }
215
216             desktop->cached = 0;
217             eina_hash_del(efreet_desktop_cache, file, NULL);
218         }
219     }
220
221     desktop = efreet_desktop_new(file);
222     if (!desktop) return NULL;
223
224     eina_hash_add(efreet_desktop_cache, file, desktop);
225     desktop->cached = 1;
226     return desktop;
227 }
228
229 /**
230  * @param desktop: The Efreet_Desktop to ref
231  * @return Returns the new reference count
232  */
233 EAPI int
234 efreet_desktop_ref(Efreet_Desktop *desktop)
235 {
236     if (!desktop) return 0;
237     desktop->ref++;
238     return desktop->ref;
239 }
240
241 /**
242  * @param file: The file to create the Efreet_Desktop from
243  * @return Returns a new empty_Efreet_Desktop on success, NULL on failure
244  * @brief Creates a new empty Efreet_Desktop structure or NULL on failure
245  */
246 EAPI Efreet_Desktop *
247 efreet_desktop_empty_new(const char *file)
248 {
249     Efreet_Desktop *desktop;
250
251     desktop = NEW(Efreet_Desktop, 1);
252     if (!desktop) return NULL;
253
254     desktop->orig_path = strdup(file);
255     desktop->load_time = ecore_file_mod_time(file);
256
257     desktop->ref = 1;
258
259     return desktop;
260 }
261
262 /**
263  * @internal
264  * @param file: The file to create the Efreet_Desktop from
265  * @return Returns a new Efreet_Desktop on success, NULL on failure
266  * @brief Creates a new Efreet_Desktop structure initialized from the
267  * contents of @a file or NULL on failure
268  */
269 EAPI Efreet_Desktop *
270 efreet_desktop_new(const char *file)
271 {
272     Efreet_Desktop *desktop;
273
274     if (!ecore_file_exists(file)) return NULL;
275
276     desktop = NEW(Efreet_Desktop, 1);
277     if (!desktop) return NULL;
278
279     desktop->orig_path = strdup(file);
280
281     if (!efreet_desktop_read(desktop))
282     {
283         efreet_desktop_free(desktop);
284         return NULL;
285     }
286
287     desktop->ref = 1;
288     desktop->cache_flush = cache_flush;
289
290     return desktop;
291 }
292
293 /**
294  * @internal
295  * @param desktop: The desktop to fill
296  * @return Returns 1 on success, 0 on failure
297  * @brief initialize an Efreet_Desktop from the contents of @a file
298  */
299 static int
300 efreet_desktop_read(Efreet_Desktop *desktop)
301 {
302     Efreet_Ini *ini;
303     int error = 0;
304     int ok;
305
306     ini = efreet_ini_new(desktop->orig_path);
307     if (!ini->data)
308     {
309         efreet_ini_free(ini);
310         return 0;
311     }
312
313     ok = efreet_ini_section_set(ini, "Desktop Entry");
314     if (!ok) ok = efreet_ini_section_set(ini, "KDE Desktop Entry");
315     if (!ok)
316     {
317         printf("efreet_desktop_new error: no Desktop Entry section\n");
318         error = 1;
319     }
320
321     if (!error)
322     {
323         Efreet_Desktop_Type_Info *info;
324
325         info = efreet_desktop_type_parse(efreet_ini_string_get(ini, "Type"));
326         if (info)
327         {
328             desktop->type = info->id;
329             desktop->version = efreet_ini_double_get(ini, "Version");
330
331             if (info->parse_func)
332                 desktop->type_data = info->parse_func(desktop, ini);
333         }
334         else
335             error = 1;
336     }
337
338     if (!error && !efreet_desktop_environment_check(ini)) error = 1;
339     if (!error && !efreet_desktop_generic_fields_parse(desktop, ini)) error = 1;
340     if (!error)
341        eina_hash_foreach(ini->section, efreet_desktop_x_fields_parse, desktop);
342
343     efreet_ini_free(ini);
344
345     desktop->load_time = ecore_file_mod_time(desktop->orig_path);
346
347     if (error) return 0;
348
349     return 1;
350 }
351
352 /**
353  * @internal
354  * @param desktop: The Efreet_Desktop to work with
355  * @return Returns no value
356  * @brief Frees the Efreet_Desktop's data
357  */
358 static void
359 efreet_desktop_clear(Efreet_Desktop *desktop)
360 {
361     char *data;
362
363     IF_FREE(desktop->name);
364     IF_FREE(desktop->generic_name);
365     IF_FREE(desktop->comment);
366     IF_FREE(desktop->icon);
367     IF_FREE(desktop->url);
368
369     IF_FREE(desktop->try_exec);
370     IF_FREE(desktop->exec);
371     IF_FREE(desktop->path);
372     IF_FREE(desktop->startup_wm_class);
373
374     IF_FREE_LIST(desktop->only_show_in);
375     IF_FREE_LIST(desktop->not_show_in);
376     while (desktop->categories)
377     {
378         data = eina_list_data_get(desktop->categories);
379         eina_stringshare_del(data);
380         desktop->categories = eina_list_remove_list(desktop->categories, desktop->categories);
381     }
382     while (desktop->mime_types)
383     {
384         data = eina_list_data_get(desktop->mime_types);
385         eina_stringshare_del(data);
386         desktop->mime_types = eina_list_remove_list(desktop->mime_types, desktop->mime_types);
387     }
388
389     IF_FREE_HASH(desktop->x);
390
391     if (desktop->type_data)
392     {
393         Efreet_Desktop_Type_Info *info;
394         info = eina_list_nth(efreet_desktop_types, desktop->type);
395         if (info->free_func)
396             info->free_func(desktop->type_data);
397     }
398 }
399
400 /**
401  * @param desktop: The desktop file to save
402  * @return Returns 1 on success or 0 on failure
403  * @brief Saves any changes made to @a desktop back to the file on the
404  * filesystem
405  */
406 EAPI int
407 efreet_desktop_save(Efreet_Desktop *desktop)
408 {
409     Efreet_Desktop_Type_Info *info;
410     Efreet_Ini *ini;
411     int ok = 1;
412
413     ini = efreet_ini_new(desktop->orig_path);
414     efreet_ini_section_add(ini, "Desktop Entry");
415     efreet_ini_section_set(ini, "Desktop Entry");
416
417     info = eina_list_nth(efreet_desktop_types, desktop->type);
418     if (info)
419     {
420         efreet_ini_string_set(ini, "Type", info->type);
421         if (info->save_func) info->save_func(desktop, ini);
422     }
423     else
424         ok = 0;
425
426     if (ok)
427     {
428         char *val;
429
430         if (desktop->only_show_in)
431         {
432             val = efreet_desktop_string_list_join(desktop->only_show_in);
433             efreet_ini_string_set(ini, "OnlyShowIn", val);
434             FREE(val);
435         }
436         if (desktop->not_show_in)
437         {
438             val = efreet_desktop_string_list_join(desktop->not_show_in);
439             efreet_ini_string_set(ini, "NotShowIn", val);
440             FREE(val);
441         }
442         efreet_desktop_generic_fields_save(desktop, ini);
443         /* When we save the file, it should be updated to the
444          * latest version that we support! */
445         efreet_ini_double_set(ini, "Version", DESKTOP_VERSION);
446
447         if (!efreet_ini_save(ini, desktop->orig_path)) ok = 0;
448         else
449         {
450             if (desktop != eina_hash_find(efreet_desktop_cache, desktop->orig_path))
451             {
452                 desktop->cached = 1;
453                 eina_hash_del(efreet_desktop_cache, desktop->orig_path, NULL);
454                 eina_hash_add(efreet_desktop_cache, desktop->orig_path,
455                               desktop);
456             }
457         }
458     }
459     efreet_ini_free(ini);
460     return ok;
461 }
462
463 /**
464  * @param desktop: The desktop file to save
465  * @param file: The filename to save as
466  * @return Returns 1 on success or 0 on failure
467  * @brief Saves @a desktop to @a file
468  */
469 EAPI int
470 efreet_desktop_save_as(Efreet_Desktop *desktop, const char *file)
471 {
472     if (desktop == eina_hash_find(efreet_desktop_cache, desktop->orig_path))
473     {
474         desktop->cached = 0;
475         eina_hash_del(efreet_desktop_cache, desktop->orig_path, NULL);
476     }
477     FREE(desktop->orig_path);
478     desktop->orig_path = strdup(file);
479     return efreet_desktop_save(desktop);
480 }
481
482 /**
483  * @internal
484  * @param desktop: The Efreet_Desktop to work with
485  * @return Returns no value
486  * @brief Frees the Efreet_Desktop structure and all of it's data
487  */
488 EAPI void
489 efreet_desktop_free(Efreet_Desktop *desktop)
490 {
491     char *str;
492
493     if (!desktop) return;
494
495     desktop->ref--;
496     if (desktop->ref > 0) return;
497
498     if (desktop->cached && efreet_desktop_cache)
499       eina_hash_del(efreet_desktop_cache, desktop->orig_path, NULL);
500
501     IF_FREE(desktop->orig_path);
502
503     IF_FREE(desktop->name);
504     IF_FREE(desktop->generic_name);
505     IF_FREE(desktop->comment);
506     IF_FREE(desktop->icon);
507     IF_FREE(desktop->url);
508
509     IF_FREE(desktop->try_exec);
510     IF_FREE(desktop->exec);
511     IF_FREE(desktop->path);
512     IF_FREE(desktop->startup_wm_class);
513
514     IF_FREE_LIST(desktop->only_show_in);
515     IF_FREE_LIST(desktop->not_show_in);
516
517     EINA_LIST_FREE(desktop->categories, str)
518         eina_stringshare_del(str);
519     EINA_LIST_FREE(desktop->mime_types, str)
520         eina_stringshare_del(str);
521
522     IF_FREE_HASH(desktop->x);
523
524     if (desktop->type_data)
525     {
526         Efreet_Desktop_Type_Info *info;
527         info = eina_list_nth(efreet_desktop_types, desktop->type);
528         if (info->free_func)
529             info->free_func(desktop->type_data);
530     }
531
532     FREE(desktop);
533 }
534
535 /**
536  * @param desktop: The desktop file to work with
537  * @param files: The files to be substituted into the exec line
538  * @param data: The data pointer to pass
539  * @return Returns the Ecore_Exce for @a desktop
540  * @brief Parses the @a desktop exec line and returns an Ecore_Exe.
541  */
542 EAPI void
543 efreet_desktop_exec(Efreet_Desktop *desktop, Eina_List *files, void *data)
544 {
545     efreet_desktop_command_get(desktop, files, efreet_desktop_exec_cb, data);
546 }
547
548 static void *
549 efreet_desktop_exec_cb(void *data, Efreet_Desktop *desktop __UNUSED__,
550                                 char *exec, int remaining __UNUSED__)
551 {
552     ecore_exe_run(exec, data);
553     free(exec);
554     return NULL;
555 }
556
557 /**
558  * @param environment: the environment name
559  * @brief sets the global desktop environment name
560  */
561 EAPI void
562 efreet_desktop_environment_set(const char *environment)
563 {
564     if (desktop_environment) eina_stringshare_del(desktop_environment);
565     if (environment) desktop_environment = eina_stringshare_add(environment);
566     else desktop_environment = NULL;
567 }
568
569 /**
570  * @return environment: the environment name
571  * @brief sets the global desktop environment name
572  */
573 EAPI const char *
574 efreet_desktop_environment_get(void)
575 {
576     return desktop_environment;
577 }
578
579 /**
580  * @param desktop: The desktop to work with
581  * @return Returns the number of categories assigned to this desktop
582  * @brief Retrieves the number of categories the given @a desktop belongs
583  * too
584  */
585 EAPI unsigned int
586 efreet_desktop_category_count_get(Efreet_Desktop *desktop)
587 {
588     if (!desktop || !desktop->categories) return 0;
589     return eina_list_count(desktop->categories);
590 }
591
592 /**
593  * @param desktop: the desktop
594  * @param category: the category name
595  * @brief add a category to a desktop
596  */
597 EAPI void
598 efreet_desktop_category_add(Efreet_Desktop *desktop, const char *category)
599 {
600     if (!desktop) return;
601
602     if (eina_list_search_unsorted(desktop->categories,
603                                   (Eina_Compare_Cb)strcmp, category)) return;
604
605     desktop->categories = eina_list_append(desktop->categories,
606                         (void *)eina_stringshare_add(category));
607 }
608
609 /**
610  * @param desktop: the desktop
611  * @param category: the category name
612  * @brief removes a category from a desktop
613  * @return 1 if the desktop had his category listed, 0 otherwise
614  */
615 EAPI int
616 efreet_desktop_category_del(Efreet_Desktop *desktop, const char *category)
617 {
618     char *found = NULL;
619
620     if (!desktop || !desktop->categories) return 0;
621
622     if ((found = eina_list_search_unsorted(desktop->categories,
623                                            (Eina_Compare_Cb)strcmp, category)))
624     {
625         eina_stringshare_del(found);
626         desktop->categories = eina_list_remove(desktop->categories, found);
627
628         return 1;
629     }
630
631     return 0;
632 }
633
634 /**
635  * @param type: The type to add to the list of matching types
636  * @param parse_func: a function to parse out custom fields
637  * @param save_func: a function to save data returned from @a parse_func
638  * @param free_func: a function to free data returned from @a parse_func
639  * @return Returns the id of the new type
640  * @brief Adds the given type to the list of types in the system
641  */
642 EAPI int
643 efreet_desktop_type_add(const char *type, Efreet_Desktop_Type_Parse_Cb parse_func,
644                         Efreet_Desktop_Type_Save_Cb save_func,
645                         Efreet_Desktop_Type_Free_Cb free_func)
646 {
647     int id;
648     Efreet_Desktop_Type_Info *info;
649
650     info = NEW(Efreet_Desktop_Type_Info, 1);
651     if (!info) return 0;
652
653     id = eina_list_count(efreet_desktop_types);
654
655     info->id = id;
656     info->type = strdup(type);
657     info->parse_func = parse_func;
658     info->save_func = save_func;
659     info->free_func = free_func;
660
661     efreet_desktop_types = eina_list_append(efreet_desktop_types, info);
662
663     return id;
664 }
665
666 /**
667  * @brief Add an alias for an existing desktop type.
668  * @param from_type the type to alias (e.g. EFREE_DESKTOP_TYPE_APPLICATION)
669  * @param alias the alias
670  * @return the new type id, or -1 if @p from_type was not valid
671  *
672  * This allows applications to add non-standard types that behave exactly as standard types.
673  */
674 EAPI int
675 efreet_desktop_type_alias(int from_type, const char *alias)
676 {
677     Efreet_Desktop_Type_Info *info;
678     info = eina_list_nth(efreet_desktop_types, from_type);
679     if (!info) return -1;
680
681     return efreet_desktop_type_add(alias, info->parse_func, info->save_func, info->free_func);
682 }
683
684 /**
685  * @internal
686  * @brief Free an Efreet Desktop_Type_Info struct
687  */
688 static void
689 efreet_desktop_type_info_free(Efreet_Desktop_Type_Info *info)
690 {
691     if (!info) return;
692     IF_FREE(info->type);
693     free(info);
694 }
695
696 /**
697  * @brief get type specific data for custom desktop types
698  * @param desktop the desktop
699  * @return type specific data, or NULL if there is none
700  */
701 EAPI void *
702 efreet_desktop_type_data_get(Efreet_Desktop *desktop)
703 {
704     return desktop->type_data;
705 }
706
707 /**
708  * @internal
709  * @param type_str: the type as a string
710  * @return the parsed type
711  * @brief parse the type string into an Efreet_Desktop_Type
712  */
713 static Efreet_Desktop_Type_Info *
714 efreet_desktop_type_parse(const char *type_str)
715 {
716     Efreet_Desktop_Type_Info *info;
717     Eina_List *l;
718
719     if (!type_str) return NULL;
720
721     EINA_LIST_FOREACH(efreet_desktop_types, l, info)
722     {
723         if (!strcmp(info->type, type_str))
724             return info;
725     }
726
727     return NULL;
728 }
729
730 /**
731  * @param string: the raw string list
732  * @return an Eina_List of ecore string's
733  * @brief Parse ';' separate list of strings according to the desktop spec
734  */
735 EAPI Eina_List *
736 efreet_desktop_string_list_parse(const char *string)
737 {
738     Eina_List *list = NULL;
739     char *tmp;
740     char *s, *p;
741
742     if (!string) return NULL;
743
744     tmp = strdup(string);
745     s = tmp;
746
747     while ((p = strchr(s, ';')))
748     {
749         if (p > tmp && *(p-1) == '\\') continue;
750         *p = '\0';
751         list = eina_list_append(list, (void *)eina_stringshare_add(s));
752         s = p + 1;
753     }
754     /* If this is true, the .desktop file does not follow the standard */
755     if (*s)
756     {
757 #ifdef STRICT_SPEC
758         printf("[Efreet]: Found a string list without ';' "
759                 "at the end: %s\n", string);
760 #endif
761         list = eina_list_append(list, (void *)eina_stringshare_add(s));
762     }
763
764     free(tmp);
765
766     return list;
767 }
768
769 /**
770  * @param list: Eina_List with strings
771  * @return a raw string list
772  * @brief Create a ';' separate list of strings according to the desktop spec
773  */
774 EAPI char *
775 efreet_desktop_string_list_join(Eina_List *list)
776 {
777     Eina_List *l;
778     const char *tmp;
779     char *string;
780     size_t size, pos, len;
781
782     if (!list) return strdup("");
783
784     size = 1024;
785     string = malloc(size);
786     pos = 0;
787
788     EINA_LIST_FOREACH(list, l, tmp)
789     {
790         len = strlen(tmp);
791         /* +1 for ';' */
792         if ((len + pos + 1) >= size)
793         {
794             size = len + pos + 1024;
795             string = realloc(string, size);
796         }
797         strcpy(string + pos, tmp);
798         pos += len;
799         strcpy(string + pos, ";");
800         pos += 1;
801     }
802     return string;
803 }
804
805 /**
806  * @brief Tell Efreet to flush any cached desktop entries so it reloads on get.
807  *
808  * This flags the cache to be invalid, so next time a desktop file is fetched
809  * it will force it to be re-read off disk next time efreet_desktop_get() is
810  * called.
811  */
812 EAPI void
813 efreet_desktop_cache_flush(void)
814 {
815     cache_flush++;
816 }
817
818 /**
819  * @internal
820  * @param desktop: the Efreet_Desktop to store parsed fields in
821  * @param ini: the Efreet_Ini to parse fields from
822  * @return No value
823  * @brief Parse application specific desktop fields
824  */
825 static void *
826 efreet_desktop_application_fields_parse(Efreet_Desktop *desktop, Efreet_Ini *ini)
827 {
828     const char *val;
829
830     val = efreet_ini_string_get(ini, "TryExec");
831     if (val) desktop->try_exec = strdup(val);
832
833     val = efreet_ini_string_get(ini, "Exec");
834     if (val) desktop->exec = strdup(val);
835
836     val = efreet_ini_string_get(ini, "Path");
837     if (val) desktop->path = strdup(val);
838
839     val = efreet_ini_string_get(ini, "StartupWMClass");
840     if (val) desktop->startup_wm_class = strdup(val);
841
842     desktop->categories = efreet_desktop_string_list_parse(
843                                 efreet_ini_string_get(ini, "Categories"));
844     desktop->mime_types = efreet_desktop_string_list_parse(
845                                 efreet_ini_string_get(ini, "MimeType"));
846
847     desktop->terminal = efreet_ini_boolean_get(ini, "Terminal");
848     desktop->startup_notify = efreet_ini_boolean_get(ini, "StartupNotify");
849
850     return NULL;
851 }
852
853 /**
854  * @internal
855  * @param desktop: the Efreet_Desktop to save fields from
856  * @param ini: the Efreet_Ini to save fields to
857  * @return Returns no value
858  * @brief Save application specific desktop fields
859  */
860 static void
861 efreet_desktop_application_fields_save(Efreet_Desktop *desktop, Efreet_Ini *ini)
862 {
863     char *val;
864
865     if (desktop->try_exec)
866         efreet_ini_string_set(ini, "TryExec", desktop->try_exec);
867
868     if (desktop->exec)
869         efreet_ini_string_set(ini, "Exec", desktop->exec);
870
871     if (desktop->path)
872         efreet_ini_string_set(ini, "Path", desktop->path);
873
874     if (desktop->startup_wm_class)
875         efreet_ini_string_set(ini, "StartupWMClass", desktop->startup_wm_class);
876
877     if (desktop->categories)
878     {
879         val = efreet_desktop_string_list_join(desktop->categories);
880         efreet_ini_string_set(ini, "Categories", val);
881         FREE(val);
882     }
883
884     if (desktop->mime_types)
885     {
886         val = efreet_desktop_string_list_join(desktop->mime_types);
887         efreet_ini_string_set(ini, "MimeType", val);
888         FREE(val);
889     }
890
891     efreet_ini_boolean_set(ini, "Terminal", desktop->terminal);
892     efreet_ini_boolean_set(ini, "StartupNotify", desktop->startup_notify);
893 }
894
895 /**
896  * @internal
897  * @param desktop: the Efreet_Desktop to store parsed fields in
898  * @param ini: the Efreet_Ini to parse fields from
899  * @return Returns no value
900  * @brief Parse link specific desktop fields
901  */
902 static void *
903 efreet_desktop_link_fields_parse(Efreet_Desktop *desktop, Efreet_Ini *ini)
904 {
905     const char *val;
906
907     val = efreet_ini_string_get(ini, "URL");
908     if (val) desktop->url = strdup(val);
909     return NULL;
910 }
911
912 /**
913  * @internal
914  * @param desktop: the Efreet_Desktop to save fields from
915  * @param ini: the Efreet_Ini to save fields in
916  * @return Returns no value
917  * @brief Save link specific desktop fields
918  */
919 static void
920 efreet_desktop_link_fields_save(Efreet_Desktop *desktop, Efreet_Ini *ini)
921 {
922     if (desktop->url) efreet_ini_string_set(ini, "URL", desktop->url);
923 }
924
925 /**
926  * @internal
927  * @param desktop: the Efreet_Desktop to store parsed fields in
928  * @param ini: the Efreet_Ini to parse fields from
929  * @return 1 if parsed succesfully, 0 otherwise
930  * @brief Parse desktop fields that all types can include
931  */
932 static int
933 efreet_desktop_generic_fields_parse(Efreet_Desktop *desktop, Efreet_Ini *ini)
934 {
935     const char *val;
936
937     val = efreet_ini_localestring_get(ini, "Name");
938     if (val) desktop->name = strdup(val);
939     else
940     {
941         printf("efreet_desktop_generic_fields_parse error: no Name\n");
942         return 0;
943     }
944
945     val = efreet_ini_localestring_get(ini, "GenericName");
946     if (val) desktop->generic_name = strdup(val);
947
948     val = efreet_ini_localestring_get(ini, "Comment");
949     if (val) desktop->comment = strdup(val);
950
951     val = efreet_ini_localestring_get(ini, "Icon");
952     if (val) desktop->icon = strdup(val);
953
954     desktop->no_display = efreet_ini_boolean_get(ini, "NoDisplay");
955     desktop->hidden = efreet_ini_boolean_get(ini, "Hidden");
956
957     return 1;
958 }
959
960 /**
961  * @internal
962  * @param desktop: the Efreet_Desktop to save fields from
963  * @param ini: the Efreet_Ini to save fields to
964  * @return Returns nothing
965  * @brief Save desktop fields that all types can include
966  */
967 static void
968 efreet_desktop_generic_fields_save(Efreet_Desktop *desktop, Efreet_Ini *ini)
969 {
970     const char *val;
971
972     if (desktop->name)
973     {
974         efreet_ini_localestring_set(ini, "Name", desktop->name);
975         val = efreet_ini_string_get(ini, "Name");
976         if (!val)
977             efreet_ini_string_set(ini, "Name", desktop->name);
978     }
979     if (desktop->generic_name)
980     {
981         efreet_ini_localestring_set(ini, "GenericName", desktop->generic_name);
982         val = efreet_ini_string_get(ini, "GenericName");
983         if (!val)
984             efreet_ini_string_set(ini, "GenericName", desktop->generic_name);
985     }
986     if (desktop->comment)
987     {
988         efreet_ini_localestring_set(ini, "Comment", desktop->comment);
989         val = efreet_ini_string_get(ini, "Comment");
990         if (!val)
991             efreet_ini_string_set(ini, "Comment", desktop->comment);
992     }
993     if (desktop->icon)
994     {
995         efreet_ini_localestring_set(ini, "Icon", desktop->icon);
996         val = efreet_ini_string_get(ini, "Icon");
997         if (!val)
998             efreet_ini_string_set(ini, "Icon", desktop->icon);
999     }
1000
1001     efreet_ini_boolean_set(ini, "NoDisplay", desktop->no_display);
1002     efreet_ini_boolean_set(ini, "Hidden", desktop->hidden);
1003
1004     if (desktop->x) eina_hash_foreach(desktop->x, efreet_desktop_x_fields_save,
1005                                       ini);
1006 }
1007
1008 /**
1009  * @internal
1010  * @param node: The node to work with
1011  * @param desktop: The desktop file to work with
1012  * @return Returns always true, to be used in eina_hash_foreach()
1013  * @brief Parses out an X- key from @a node and stores in @a desktop
1014  */
1015 static Eina_Bool
1016 efreet_desktop_x_fields_parse(const Eina_Hash *hash __UNUSED__, const void *key, void *value, void *fdata)
1017 {
1018     Efreet_Desktop * desktop = fdata;
1019
1020     if (!desktop) return EINA_TRUE;
1021     if (strncmp(key, "X-", 2)) return EINA_TRUE;
1022
1023     if (!desktop->x)
1024       desktop->x = eina_hash_string_superfast_new(EINA_FREE_CB(eina_stringshare_del));
1025     eina_hash_del(desktop->x, key, NULL);
1026     eina_hash_add(desktop->x, key, (void *)eina_stringshare_add(value));
1027
1028     return EINA_TRUE;
1029 }
1030
1031 /**
1032  * @internal
1033  * @param node: The node to work with
1034  * @param ini: The ini file to work with
1035  * @return Returns no value
1036  * @brief Stores an X- key from @a node and stores in @a ini
1037  */
1038 static Eina_Bool
1039 efreet_desktop_x_fields_save(const Eina_Hash *hash __UNUSED__, const void *key, void *value, void *fdata)
1040 {
1041     Efreet_Ini *ini = fdata;
1042     efreet_ini_string_set(ini, key, value);
1043
1044     return EINA_TRUE;
1045 }
1046
1047
1048 /**
1049  * @internal
1050  * @param ini: The Efreet_Ini to parse values from
1051  * @return 1 if desktop should be included in current environement, 0 otherwise
1052  * @brief Determines if a desktop should be included in the current environment,
1053  * based on the values of the OnlyShowIn and NotShowIn fields
1054  */
1055 static int
1056 efreet_desktop_environment_check(Efreet_Ini *ini)
1057 {
1058     Eina_List *list;
1059     int found = 0;
1060     char *val;
1061
1062     if (!desktop_environment)
1063       return 1;
1064
1065     list = efreet_desktop_string_list_parse(efreet_ini_string_get(ini, "OnlyShowIn"));
1066     if (list)
1067     {
1068        EINA_LIST_FREE(list, val)
1069             {
1070                 if (!strcmp(val, desktop_environment))
1071                     found = 1;
1072             eina_stringshare_del(val);
1073         }
1074
1075         return found;
1076     }
1077
1078         list = efreet_desktop_string_list_parse(efreet_ini_string_get(ini, "NotShowIn"));
1079     EINA_LIST_FREE(list, val)
1080             {
1081                 if (!strcmp(val, desktop_environment))
1082                     found = 1;
1083          eina_stringshare_del(val);
1084         }
1085
1086         return !found;
1087 }
1088
1089
1090 /**
1091  * @param desktop: the desktop entry
1092  * @param files: an ecore list of file names to execute, as either absolute paths,
1093  * relative paths, or uris
1094  * @param func: a callback to call for each prepared command line
1095  * @param data: user data passed to the callback
1096  * @return Returns the return value of @p func on success or NULL on failure
1097  * @brief Get a command to use to execute a desktop entry.
1098  */
1099 EAPI void *
1100 efreet_desktop_command_get(Efreet_Desktop *desktop, Eina_List *files,
1101                             Efreet_Desktop_Command_Cb func, void *data)
1102 {
1103     return efreet_desktop_command_progress_get(desktop, files, func, NULL, data);
1104 }
1105
1106 /**
1107  * @param desktop: the desktop entry
1108  * @param files an ecore list of local files, as absolute paths, local paths, or file:// uris (or NULL to get exec string with no files appended)
1109  * @return Returns an ecore list of exec strings
1110  * @brief Get the command to use to execute a desktop entry
1111  *
1112  * The returned list and each of its elements must be freed.
1113  */
1114 EAPI Eina_List *
1115 efreet_desktop_command_local_get(Efreet_Desktop *desktop, Eina_List *files)
1116 {
1117     Efreet_Desktop_Command *command;
1118     char *file;
1119     Eina_List *execs, *l;
1120
1121     if (!desktop || !desktop->exec) return NULL;
1122
1123     command = NEW(Efreet_Desktop_Command, 1);
1124     if (!command) return 0;
1125
1126     command->desktop = desktop;
1127
1128     command->flags = efreet_desktop_command_flags_get(desktop);
1129     /* get the required info for each file passed in */
1130     if (files)
1131     {
1132         EINA_LIST_FOREACH(files, l, file)
1133         {
1134             Efreet_Desktop_Command_File *dcf;
1135
1136             dcf = efreet_desktop_command_file_process(command, file);
1137             if (!dcf) continue;
1138             if (dcf->pending)
1139             {
1140                 efreet_desktop_command_file_free(dcf);
1141                 continue;
1142             }
1143             command->files = eina_list_append(command->files, dcf);
1144         }
1145     }
1146
1147     execs = efreet_desktop_command_build(command);
1148     efreet_desktop_command_free(command);
1149
1150     return execs;
1151 }
1152
1153
1154 /**
1155  * @param desktop: the desktop entry
1156  * @param files: an ecore list of file names to execute, as either absolute paths,
1157  * relative paths, or uris
1158  * @param cb_command: a callback to call for each prepared command line
1159  * @param cb_progress: a callback to get progress for the downloads
1160  * @param data: user data passed to the callback
1161  * @return Returns 1 on success or 0 on failure
1162  * @brief Get a command to use to execute a desktop entry, and receive progress
1163  * updates for downloading of remote URI's passed in.
1164  */
1165 EAPI void *
1166 efreet_desktop_command_progress_get(Efreet_Desktop *desktop, Eina_List *files,
1167                                     Efreet_Desktop_Command_Cb cb_command,
1168                                     Efreet_Desktop_Progress_Cb cb_progress,
1169                                     void *data)
1170 {
1171     Efreet_Desktop_Command *command;
1172     Eina_List *l;
1173     char *file;
1174     void *ret = NULL;
1175
1176     if (!desktop || !cb_command || !desktop->exec) return NULL;
1177
1178     command = NEW(Efreet_Desktop_Command, 1);
1179     if (!command) return NULL;
1180
1181     command->cb_command = cb_command;
1182     command->cb_progress = cb_progress;
1183     command->data = data;
1184     command->desktop = desktop;
1185
1186     command->flags = efreet_desktop_command_flags_get(desktop);
1187     /* get the required info for each file passed in */
1188     if (files)
1189     {
1190         EINA_LIST_FOREACH(files, l, file)
1191         {
1192             Efreet_Desktop_Command_File *dcf;
1193
1194             dcf = efreet_desktop_command_file_process(command, file);
1195             if (!dcf) continue;
1196             command->files = eina_list_append(command->files, dcf);
1197             command->num_pending += dcf->pending;
1198         }
1199     }
1200
1201     if (command->num_pending == 0)
1202     {
1203         Eina_List *execs;
1204         execs = efreet_desktop_command_build(command);
1205         ret = efreet_desktop_command_execs_process(command, execs);
1206         eina_list_free(execs);
1207         efreet_desktop_command_free(command);
1208     }
1209
1210     return ret;
1211 }
1212
1213 /**
1214  * @internal
1215  *
1216  * @brief Determine which file related field codes are present in the Exec string of a .desktop
1217  * @params desktop and Efreet Desktop
1218  * @return a bitmask of file field codes present in exec string
1219  */
1220 static int
1221 efreet_desktop_command_flags_get(Efreet_Desktop *desktop)
1222 {
1223     int flags = 0;
1224     const char *p;
1225     /* first, determine which fields are present in the Exec string */
1226     p = strchr(desktop->exec, '%');
1227     while (p)
1228     {
1229         p++;
1230         switch(*p)
1231         {
1232             case 'f':
1233             case 'F':
1234                 flags |= EFREET_DESKTOP_EXEC_FLAG_FULLPATH;
1235                 break;
1236             case 'u':
1237             case 'U':
1238                 flags |= EFREET_DESKTOP_EXEC_FLAG_URI;
1239                 break;
1240             case 'd':
1241             case 'D':
1242                 flags |= EFREET_DESKTOP_EXEC_FLAG_DIR;
1243                 break;
1244             case 'n':
1245             case 'N':
1246                 flags |= EFREET_DESKTOP_EXEC_FLAG_FILE;
1247                 break;
1248             case '%':
1249                 p++;
1250                 break;
1251             default:
1252                 break;
1253         }
1254
1255         p = strchr(p, '%');
1256     }
1257 #ifdef SLOPPY_SPEC   
1258    /* NON-SPEC!!! this is to work around LOTS of 'broken' .desktop files that
1259     * do not specify %U/%u, %F/F etc. etc. at all. just a command. this is
1260     * unlikely to be fixed in distributions etc. in the long run as gnome/kde
1261     * seem to have workarounds too so no one notices.
1262     */
1263    if (!flags) flags |= EFREET_DESKTOP_EXEC_FLAG_FULLPATH;
1264 #endif
1265    
1266     return flags;
1267 }
1268
1269
1270 /**
1271  * @internal
1272  *
1273  * @brief Call the command callback for each exec in the list
1274  * @param command
1275  * @param execs
1276  */
1277 static void *
1278 efreet_desktop_command_execs_process(Efreet_Desktop_Command *command, Eina_List *execs)
1279 {
1280     Eina_List *l;
1281     char *exec;
1282     int num;
1283     void *ret = NULL;
1284    
1285     num = eina_list_count(execs);
1286     EINA_LIST_FOREACH(execs, l, exec)
1287     {
1288         ret = command->cb_command(command->data, command->desktop, exec, --num);
1289     }
1290     return ret;
1291 }
1292
1293
1294 /**
1295  * @brief Builds the actual exec string from the raw string and a list of
1296  * processed filename information. The callback passed in to
1297  * efreet_desktop_command_get is called for each exec string created.
1298  *
1299  * @param command: the command to build
1300  * @return a list of executable strings
1301  */
1302 static Eina_List *
1303 efreet_desktop_command_build(Efreet_Desktop_Command *command)
1304 {
1305     Eina_List *execs = NULL;
1306     const Eina_List *l;
1307     char *exec;
1308
1309     /* if the Exec field appends multiple, that will run the list to the end,
1310      * causing this loop to only run once. otherwise, this loop will generate a
1311      * command for each file in the list. if the list is empty, this
1312      * will run once, removing any file field codes */
1313     l = command->files;
1314     do
1315       {
1316         const char *p;
1317         int len = 0;
1318         int size = PATH_MAX;
1319         int file_added = 0;
1320         Efreet_Desktop_Command_File *file = eina_list_data_get(l);
1321
1322         exec = malloc(size);
1323         p = command->desktop->exec;
1324         len = 0;
1325
1326         while (*p)
1327         {
1328             if (len >= size - 1)
1329             {
1330                 size = len + 1024;
1331                 exec = realloc(exec, size);
1332             }
1333
1334             /* XXX handle fields inside quotes? */
1335             if (*p == '%')
1336             {
1337                 p++;
1338                 switch (*p)
1339                 {
1340                     case 'f':
1341                     case 'u':
1342                     case 'd':
1343                     case 'n':
1344                         if (file)
1345                         {
1346                             exec = efreet_desktop_command_append_single(exec, &size,
1347                                                                     &len, file, *p);
1348                             file_added = 1;
1349                         }
1350                         break;
1351                     case 'F':
1352                     case 'U':
1353                     case 'D':
1354                     case 'N':
1355                         if (file)
1356                         {
1357                             exec = efreet_desktop_command_append_multiple(exec, &size,
1358                                                                     &len, command, *p);
1359                             file_added = 1;
1360                         }
1361                         break;
1362                     case 'i':
1363                         exec = efreet_desktop_command_append_icon(exec, &size, &len,
1364                                                                     command->desktop);
1365                         break;
1366                     case 'c':
1367                         exec = efreet_desktop_command_append_quoted(exec, &size, &len,
1368                                                                 command->desktop->name);
1369                         break;
1370                     case 'k':
1371                         exec = efreet_desktop_command_append_quoted(exec, &size, &len,
1372                                                             command->desktop->orig_path);
1373                         break;
1374                     case 'v':
1375                     case 'm':
1376                         printf("[Efreet]: Deprecated conversion char: '%c' in file '%s'\n",
1377                                                             *p, command->desktop->orig_path);
1378                         break;
1379                     case '%':
1380                         exec[len++] = *p;
1381                         break;
1382                     default:
1383 #ifdef STRICT_SPEC
1384                         printf("[Efreet]: Unknown conversion character: '%c'\n", *p);
1385 #endif
1386                         break;
1387                 }
1388             }
1389             else exec[len++] = *p;
1390             p++;
1391         }
1392
1393 #ifdef SLOPPY_SPEC       
1394        /* NON-SPEC!!! this is to work around LOTS of 'broken' .desktop files that
1395         * do not specify %U/%u, %F/F etc. etc. at all. just a command. this is
1396         * unlikely to be fixed in distributions etc. in the long run as gnome/kde
1397         * seem to have workarounds too so no one notices.
1398         */
1399        if ((file) && (!file_added))
1400          {
1401             printf("[Efreet]: %s\n"
1402                    "  command: %s\n"
1403                    "  has no file path/uri spec info for executing this app WITH a\n"
1404                    "  file/uri as a parameter. This is unlikely to be the intent.\n"
1405                    "  please check the .desktop file and fix it by adding a %%U or %%F\n"
1406                    "  or something appropriate.",
1407                    command->desktop->orig_path, command->desktop->exec);
1408             if (len >= size - 1)
1409               {
1410                  size = len + 1024;
1411                  exec = realloc(exec, size);
1412               }
1413             exec[len++] = ' ';
1414             exec = efreet_desktop_command_append_multiple(exec, &size,
1415                                                           &len, command, 'F');
1416             file_added = 1;
1417          }
1418 #endif
1419         exec[len++] = '\0';
1420
1421         execs = eina_list_append(execs, exec);
1422
1423         /* If no file was added, then the Exec field doesn't contain any file
1424          * fields (fFuUdDnN). We only want to run the app once in this case. */
1425         if (!file_added) break;
1426       }
1427     while ((l = eina_list_next(l)) != NULL);
1428
1429     return execs;
1430 }
1431
1432 static void
1433 efreet_desktop_command_free(Efreet_Desktop_Command *command)
1434 {
1435     Efreet_Desktop_Command_File *dcf;
1436
1437     if (!command) return;
1438
1439     while (command->files)
1440     {
1441         dcf = eina_list_data_get(command->files);
1442         efreet_desktop_command_file_free(dcf);
1443         command->files = eina_list_remove_list(command->files,
1444                                                command->files);
1445     }
1446     FREE(command);
1447 }
1448
1449 static char *
1450 efreet_desktop_command_append_quoted(char *dest, int *size, int *len, char *src)
1451 {
1452     if (!src) return dest;
1453     dest = efreet_string_append(dest, size, len, "'");
1454
1455     /* single quotes in src need to be escaped */
1456     if (strchr(src, '\''))
1457     {
1458         char *p;
1459         p = src;
1460         while (*p)
1461         {
1462             if (*p == '\'')
1463                 dest = efreet_string_append(dest, size, len, "\'\\\'");
1464
1465             dest = efreet_string_append_char(dest, size, len, *p);
1466             p++;
1467         }
1468     }
1469     else
1470         dest = efreet_string_append(dest, size, len, src);
1471
1472     dest = efreet_string_append(dest, size, len, "'");
1473
1474     return dest;
1475 }
1476
1477 static char *
1478 efreet_desktop_command_append_multiple(char *dest, int *size, int *len,
1479                                         Efreet_Desktop_Command *command,
1480                                         char type)
1481 {
1482     Efreet_Desktop_Command_File *file;
1483     Eina_List *l;
1484     int first = 1;
1485
1486     if (!command->files) return dest;
1487
1488     EINA_LIST_FOREACH(command->files, l, file)
1489     {
1490         if (first)
1491             first = 0;
1492         else
1493             dest = efreet_string_append_char(dest, size, len, ' ');
1494
1495         dest = efreet_desktop_command_append_single(dest, size, len,
1496                                                     file, tolower(type));
1497     }
1498
1499     return dest;
1500 }
1501
1502 static char *
1503 efreet_desktop_command_append_single(char *dest, int *size, int *len,
1504                                         Efreet_Desktop_Command_File *file,
1505                                         char type)
1506 {
1507     char *str;
1508     switch(type)
1509     {
1510         case 'f':
1511             str = file->fullpath;
1512             break;
1513         case 'u':
1514             str = file->uri;
1515             break;
1516         case 'd':
1517             str = file->dir;
1518             break;
1519         case 'n':
1520             str = file->file;
1521             break;
1522         default:
1523             printf("Invalid type passed to efreet_desktop_command_append_single:"
1524                                                                 " '%c'\n", type);
1525             return dest;
1526     }
1527
1528     if (!str) return dest;
1529
1530     dest = efreet_desktop_command_append_quoted(dest, size, len, str);
1531
1532     return dest;
1533 }
1534
1535 static char *
1536 efreet_desktop_command_append_icon(char *dest, int *size, int *len,
1537                                             Efreet_Desktop *desktop)
1538 {
1539     if (!desktop->icon || !desktop->icon[0]) return dest;
1540
1541     dest = efreet_string_append(dest, size, len, "--icon ");
1542     dest = efreet_desktop_command_append_quoted(dest, size, len, desktop->icon);
1543
1544     return dest;
1545 }
1546
1547
1548 /**
1549  * Append a string to a buffer, reallocating as necessary.
1550  */
1551 static char *
1552 efreet_string_append(char *dest, int *size, int *len, const char *src)
1553 {
1554     int l;
1555     int off = 0;
1556
1557     l = ecore_strlcpy(dest + *len, src, *size - *len);
1558
1559     while (l > *size - *len)
1560     {
1561         /* we successfully appended this much */
1562         off += *size - *len - 1;
1563         *len = *size - 1;
1564         *size += 1024;
1565         dest = realloc(dest, *size);
1566         *(dest + *len) = '\0';
1567
1568         l = ecore_strlcpy(dest + *len, src + off, *size - *len);
1569     }
1570     *len += l;
1571
1572     return dest;
1573 }
1574
1575 static char *
1576 efreet_string_append_char(char *dest, int *size, int *len, char c)
1577 {
1578     if (*len >= *size - 1)
1579     {
1580         *size += 1024;
1581         dest = realloc(dest, *size);
1582     }
1583
1584     dest[(*len)++] = c;
1585     dest[*len] = '\0';
1586
1587     return dest;
1588 }
1589
1590 /**
1591  * @param command: the Efreet_Desktop_Comand that this file is for
1592  * @param file: the filname as either an absolute path, relative path, or URI
1593  */
1594 static Efreet_Desktop_Command_File *
1595 efreet_desktop_command_file_process(Efreet_Desktop_Command *command, const char *file)
1596 {
1597     Efreet_Desktop_Command_File *f;
1598     const char *uri, *base;
1599     int nonlocal = 0;
1600 /*
1601     printf("FLAGS: %d, %d, %d, %d\n",
1602         command->flags & EFREET_DESKTOP_EXEC_FLAG_FULLPATH ? 1 : 0,
1603         command->flags & EFREET_DESKTOP_EXEC_FLAG_URI ? 1 : 0,
1604         command->flags & EFREET_DESKTOP_EXEC_FLAG_DIR ? 1 : 0,
1605         command->flags & EFREET_DESKTOP_EXEC_FLAG_FILE ? 1 : 0);
1606 */
1607     f = NEW(Efreet_Desktop_Command_File, 1);
1608     if (!f) return NULL;
1609
1610     f->command = command;
1611
1612     /* handle uris */
1613     if(!strncmp(file, "http://", 7) || !strncmp(file, "ftp://", 6))
1614     {
1615         uri = file;
1616         base = ecore_file_file_get(file);
1617
1618         nonlocal = 1;
1619     }
1620     else if (!strncmp(file, "file:", 5))
1621     {
1622         file = efreet_desktop_command_file_uri_process(file);
1623         if (!file)
1624         {
1625             efreet_desktop_command_file_free(f);
1626             return NULL;
1627         }
1628     }
1629
1630     if (nonlocal)
1631     {
1632         /* process non-local uri */
1633         if (command->flags & EFREET_DESKTOP_EXEC_FLAG_FULLPATH)
1634         {
1635             char buf[PATH_MAX];
1636
1637             snprintf(buf, PATH_MAX, "/tmp/%d-%d-%s", getpid(),
1638                             efreet_desktop_command_file_id++, base);
1639             f->fullpath = strdup(buf);
1640             f->pending = 1;
1641
1642             ecore_file_download(uri, f->fullpath, efreet_desktop_cb_download_complete,
1643                                             efreet_desktop_cb_download_progress, f);
1644         }
1645
1646         if (command->flags & EFREET_DESKTOP_EXEC_FLAG_URI)
1647             f->uri = strdup(uri);
1648         if (command->flags & EFREET_DESKTOP_EXEC_FLAG_DIR)
1649             f->dir = strdup("/tmp");
1650         if (command->flags & EFREET_DESKTOP_EXEC_FLAG_FILE)
1651             f->file = strdup(base);
1652     }
1653     else
1654     {
1655         char *abs = efreet_desktop_command_path_absolute(file);
1656         /* process local uri/path */
1657         if (command->flags & EFREET_DESKTOP_EXEC_FLAG_FULLPATH)
1658             f->fullpath = strdup(abs);
1659
1660         if (command->flags & EFREET_DESKTOP_EXEC_FLAG_URI)
1661         {
1662             char buf[PATH_MAX];
1663             snprintf(buf, sizeof(buf), "file://%s", abs);
1664             f->uri = strdup(buf);
1665         }
1666         if (command->flags & EFREET_DESKTOP_EXEC_FLAG_DIR)
1667             f->dir = ecore_file_dir_get(abs);
1668         if (command->flags & EFREET_DESKTOP_EXEC_FLAG_FILE)
1669             f->file = strdup(ecore_file_file_get(file));
1670
1671         free(abs);
1672     }
1673 #if 0
1674     printf("  fullpath: %s\n", f->fullpath);
1675     printf("  uri: %s\n", f->uri);
1676     printf("  dir: %s\n", f->dir);
1677     printf("  file: %s\n", f->file);
1678 #endif
1679     return f;
1680 }
1681
1682 /**
1683  * @brief Find the local path portion of a file uri.
1684  * @param uri: a uri beginning with "file:"
1685  * @return the location of the path portion of the uri,
1686  * or NULL if the file is not on this machine
1687  */
1688 static const char *
1689 efreet_desktop_command_file_uri_process(const char *uri)
1690 {
1691     const char *path = NULL;
1692     int len = strlen(uri);
1693
1694     /* uri:foo/bar => relative path foo/bar*/
1695     if (len >= 4 && uri[5] != '/')
1696         path = uri + strlen("file:");
1697
1698     /* uri:/foo/bar => absolute path /foo/bar */
1699     else if (len >= 5 && uri[6] != '/')
1700         path = uri + strlen("file:");
1701
1702     /* uri://foo/bar => absolute path /bar on machine foo */
1703     else if (len >= 6 && uri[7] != '/')
1704     {
1705         char *tmp, *p;
1706         char hostname[PATH_MAX];
1707         tmp = strdup(uri + 7);
1708         p = strchr(tmp, '/');
1709         if (p)
1710         {
1711             *p = '\0';
1712             if (!strcmp(tmp, "localhost"))
1713                 path = uri + strlen("file://localhost");
1714             else
1715             {
1716                 int ret;
1717
1718                 ret = gethostname(hostname, PATH_MAX);
1719                 if ((ret == 0) && !strcmp(tmp, hostname))
1720                     path = uri + strlen("file://") + strlen(hostname);
1721             }
1722         }
1723         free(tmp);
1724     }
1725
1726     /* uri:///foo/bar => absolute path /foo/bar on local machine */
1727     else if (len >= 7)
1728         path = uri + strlen("file://");
1729
1730     return path;
1731 }
1732
1733 static void
1734 efreet_desktop_command_file_free(Efreet_Desktop_Command_File *file)
1735 {
1736     if (!file) return;
1737
1738     IF_FREE(file->fullpath);
1739     IF_FREE(file->uri);
1740     IF_FREE(file->dir);
1741     IF_FREE(file->file);
1742
1743     FREE(file);
1744 }
1745
1746
1747 static void
1748 efreet_desktop_cb_download_complete(void *data, const char *file __UNUSED__,
1749                                                         int status __UNUSED__)
1750 {
1751     Efreet_Desktop_Command_File *f;
1752
1753     f = data;
1754
1755     /* XXX check status... error handling, etc */
1756     f->pending = 0;
1757     f->command->num_pending--;
1758
1759     if (f->command->num_pending <= 0)
1760     {
1761         Eina_List *execs;
1762         execs = efreet_desktop_command_build(f->command);
1763         /* TODO: Need to handle the return value from efreet_desktop_command_execs_process */
1764         efreet_desktop_command_execs_process(f->command, execs);
1765         eina_list_free(execs);
1766         efreet_desktop_command_free(f->command);
1767     }
1768 }
1769
1770 static int
1771 efreet_desktop_cb_download_progress(void *data,
1772                                     const char *file __UNUSED__,
1773                                     long int dltotal, long int dlnow,
1774                                     long int ultotal __UNUSED__,
1775                                     long int ulnow __UNUSED__)
1776 {
1777     Efreet_Desktop_Command_File *dcf;
1778
1779     dcf = data;
1780     if (dcf->command->cb_progress)
1781         return dcf->command->cb_progress(dcf->command->data,
1782                                         dcf->command->desktop,
1783                                         dcf->uri, dltotal, dlnow);
1784
1785     return 0;
1786 }
1787
1788 /**
1789  * @brief Build an absolute path from an absolute or relative one.
1790  * @param path: an absolute or relative path
1791  * @return an allocated absolute path (must be freed)
1792  */
1793 static char *
1794 efreet_desktop_command_path_absolute(const char *path)
1795 {
1796     char *buf;
1797     int size = PATH_MAX;
1798     int len = 0;
1799
1800     /* relative url */
1801     if (path[0] != '/')
1802     {
1803         if (!(buf = malloc(size))) return NULL;
1804         if (!getcwd(buf, size))
1805         {
1806             FREE(buf);
1807             return NULL;
1808         }
1809         len = strlen(buf);
1810
1811         if (buf[len-1] != '/') buf = efreet_string_append(buf, &size, &len, "/");
1812         buf = efreet_string_append(buf, &size, &len, path);
1813
1814         return buf;
1815     }
1816
1817     /* just dup an already absolute buffer */
1818     return strdup(path);
1819 }