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