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