From 4ee61049ca2de478b189296ed34ce7fc2fea5dc8 Mon Sep 17 00:00:00 2001 From: englebass Date: Thu, 15 Apr 2010 18:24:48 +0000 Subject: [PATCH] efreet: and add file git-svn-id: svn+ssh://svn.enlightenment.org/var/svn/e/trunk/efreet@48026 7cbeb6ba-43b4-40fd-8cce-4c39aea84d33 --- src/lib/efreet_desktop_command.c | 862 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 862 insertions(+) create mode 100644 src/lib/efreet_desktop_command.c diff --git a/src/lib/efreet_desktop_command.c b/src/lib/efreet_desktop_command.c new file mode 100644 index 0000000..d71f78f --- /dev/null +++ b/src/lib/efreet_desktop_command.c @@ -0,0 +1,862 @@ +/* vim: set sw=4 ts=4 sts=4 et: */ + +#ifdef HAVE_CONFIG_H +# include +#endif + +#include +#include +#include +#include + +#include +#include + +#include "Efreet.h" +#include "efreet_private.h" + +#ifdef EFREET_MODULE_LOG_DOM +#undef EFREET_MODULE_LOG_DOM +#endif + +#define EFREET_MODULE_LOG_DOM _efreet_desktop_log_dom +extern int _efreet_desktop_log_dom; + +/** + * @internal + * The different types of commands in an Exec entry + */ +typedef enum Efreet_Desktop_Command_Flag +{ + EFREET_DESKTOP_EXEC_FLAG_FULLPATH = 0x0001, + EFREET_DESKTOP_EXEC_FLAG_URI = 0x0002 +} Efreet_Desktop_Command_Flag; + +/** + * @internal + * Efreet_Desktop_Command + */ +typedef struct Efreet_Desktop_Command Efreet_Desktop_Command; + +/** + * @internal + * Holds information on a desktop Exec command entry + */ +struct Efreet_Desktop_Command +{ + Efreet_Desktop *desktop; + int num_pending; + + Efreet_Desktop_Command_Flag flags; + + Efreet_Desktop_Command_Cb cb_command; + Efreet_Desktop_Progress_Cb cb_progress; + void *data; + + Eina_List *files; /**< list of Efreet_Desktop_Command_File */ +}; + +/** + * @internal + * Efreet_Desktop_Command_File + */ +typedef struct Efreet_Desktop_Command_File Efreet_Desktop_Command_File; + +/** + * @internal + * Stores information on a file passed to the desktop Exec command + */ +struct Efreet_Desktop_Command_File +{ + Efreet_Desktop_Command *command; + char *dir; + char *file; + char *fullpath; + char *uri; + + int pending; +}; + +/** + * A unique id for each tmp file created while building a command + */ +static int efreet_desktop_command_file_id = 0; + +static void *efreet_desktop_exec_cb(void *data, Efreet_Desktop *desktop, + char *exec, int remaining); +static int efreet_desktop_command_flags_get(Efreet_Desktop *desktop); +static void *efreet_desktop_command_execs_process(Efreet_Desktop_Command *command, Eina_List *execs); + +static Eina_List *efreet_desktop_command_build(Efreet_Desktop_Command *command); +static void efreet_desktop_command_free(Efreet_Desktop_Command *command); +static char *efreet_desktop_command_append_quoted(char *dest, int *size, + int *len, char *src); +static char *efreet_desktop_command_append_multiple(char *dest, int *size, int *len, + Efreet_Desktop_Command *command, + char type); +static char *efreet_desktop_command_append_single(char *dest, int *size, int *len, + Efreet_Desktop_Command_File *file, + char type); +static char *efreet_desktop_command_append_icon(char *dest, int *size, int *len, + Efreet_Desktop *desktop); + +static Efreet_Desktop_Command_File *efreet_desktop_command_file_process( + Efreet_Desktop_Command *command, + const char *file); +static const char *efreet_desktop_command_file_uri_process(const char *uri); +static void efreet_desktop_command_file_free(Efreet_Desktop_Command_File *file); + +static void efreet_desktop_cb_download_complete(void *data, const char *file, + int status); +static int efreet_desktop_cb_download_progress(void *data, const char *file, + long int dltotal, long int dlnow, + long int ultotal, long int ulnow); + +static char *efreet_desktop_command_path_absolute(const char *path); + +static char *efreet_string_append(char *dest, int *size, + int *len, const char *src); +static char *efreet_string_append_char(char *dest, int *size, + int *len, char c); + + +/** + * @param desktop: The desktop file to work with + * @param files: The files to be substituted into the exec line + * @param data: The data pointer to pass + * @return Returns the Ecore_Exce for @a desktop + * @brief Parses the @a desktop exec line and returns an Ecore_Exe. + */ +EAPI void +efreet_desktop_exec(Efreet_Desktop *desktop, Eina_List *files, void *data) +{ + efreet_desktop_command_get(desktop, files, efreet_desktop_exec_cb, data); +} + +/** + * @param desktop: the desktop entry + * @param files: an eina list of file names to execute, as either absolute paths, + * relative paths, or uris + * @param func: a callback to call for each prepared command line + * @param data: user data passed to the callback + * @return Returns the return value of @p func on success or NULL on failure + * @brief Get a command to use to execute a desktop entry. + */ +EAPI void * +efreet_desktop_command_get(Efreet_Desktop *desktop, Eina_List *files, + Efreet_Desktop_Command_Cb func, void *data) +{ + return efreet_desktop_command_progress_get(desktop, files, func, NULL, data); +} + +/** + * @param desktop: the desktop entry + * @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) + * @return Returns an eina list of exec strings + * @brief Get the command to use to execute a desktop entry + * + * The returned list and each of its elements must be freed. + */ +EAPI Eina_List * +efreet_desktop_command_local_get(Efreet_Desktop *desktop, Eina_List *files) +{ + Efreet_Desktop_Command *command; + char *file; + Eina_List *execs, *l; + + if (!desktop || !desktop->exec) return NULL; + + command = NEW(Efreet_Desktop_Command, 1); + if (!command) return 0; + + command->desktop = desktop; + + command->flags = efreet_desktop_command_flags_get(desktop); + /* get the required info for each file passed in */ + if (files) + { + EINA_LIST_FOREACH(files, l, file) + { + Efreet_Desktop_Command_File *dcf; + + dcf = efreet_desktop_command_file_process(command, file); + if (!dcf) continue; + if (dcf->pending) + { + efreet_desktop_command_file_free(dcf); + continue; + } + command->files = eina_list_append(command->files, dcf); + } + } + + execs = efreet_desktop_command_build(command); + efreet_desktop_command_free(command); + + return execs; +} + +/** + * @param desktop: the desktop entry + * @param files: an eina list of file names to execute, as either absolute paths, + * relative paths, or uris + * @param cb_command: a callback to call for each prepared command line + * @param cb_progress: a callback to get progress for the downloads + * @param data: user data passed to the callback + * @return Returns 1 on success or 0 on failure + * @brief Get a command to use to execute a desktop entry, and receive progress + * updates for downloading of remote URI's passed in. + */ +EAPI void * +efreet_desktop_command_progress_get(Efreet_Desktop *desktop, Eina_List *files, + Efreet_Desktop_Command_Cb cb_command, + Efreet_Desktop_Progress_Cb cb_progress, + void *data) +{ + Efreet_Desktop_Command *command; + Eina_List *l; + char *file; + void *ret = NULL; + + if (!desktop || !cb_command || !desktop->exec) return NULL; + + command = NEW(Efreet_Desktop_Command, 1); + if (!command) return NULL; + + command->cb_command = cb_command; + command->cb_progress = cb_progress; + command->data = data; + command->desktop = desktop; + + command->flags = efreet_desktop_command_flags_get(desktop); + /* get the required info for each file passed in */ + if (files) + { + EINA_LIST_FOREACH(files, l, file) + { + Efreet_Desktop_Command_File *dcf; + + dcf = efreet_desktop_command_file_process(command, file); + if (!dcf) continue; + command->files = eina_list_append(command->files, dcf); + command->num_pending += dcf->pending; + } + } + + if (command->num_pending == 0) + { + Eina_List *execs; + + execs = efreet_desktop_command_build(command); + ret = efreet_desktop_command_execs_process(command, execs); + eina_list_free(execs); + efreet_desktop_command_free(command); + } + + return ret; +} + +static void * +efreet_desktop_exec_cb(void *data, Efreet_Desktop *desktop __UNUSED__, + char *exec, int remaining __UNUSED__) +{ +#ifndef _WIN32 + ecore_exe_run(exec, data); + free(exec); +#endif + return NULL; +} + +/** + * @internal + * + * @brief Determine which file related field codes are present in the Exec string of a .desktop + * @params desktop and Efreet Desktop + * @return a bitmask of file field codes present in exec string + */ +static int +efreet_desktop_command_flags_get(Efreet_Desktop *desktop) +{ + int flags = 0; + const char *p; + /* first, determine which fields are present in the Exec string */ + p = strchr(desktop->exec, '%'); + while (p) + { + p++; + switch(*p) + { + case 'f': + case 'F': + flags |= EFREET_DESKTOP_EXEC_FLAG_FULLPATH; + break; + case 'u': + case 'U': + flags |= EFREET_DESKTOP_EXEC_FLAG_URI; + break; + case '%': + p++; + break; + default: + break; + } + + p = strchr(p, '%'); + } +#ifdef SLOPPY_SPEC + /* NON-SPEC!!! this is to work around LOTS of 'broken' .desktop files that + * do not specify %U/%u, %F/F etc. etc. at all. just a command. this is + * unlikely to be fixed in distributions etc. in the long run as gnome/kde + * seem to have workarounds too so no one notices. + */ + if (!flags) flags |= EFREET_DESKTOP_EXEC_FLAG_FULLPATH; +#endif + + return flags; +} + + +/** + * @internal + * + * @brief Call the command callback for each exec in the list + * @param command + * @param execs + */ +static void * +efreet_desktop_command_execs_process(Efreet_Desktop_Command *command, Eina_List *execs) +{ + Eina_List *l; + char *exec; + int num; + void *ret = NULL; + + num = eina_list_count(execs); + EINA_LIST_FOREACH(execs, l, exec) + { + ret = command->cb_command(command->data, command->desktop, exec, --num); + } + return ret; +} + + +/** + * @brief Builds the actual exec string from the raw string and a list of + * processed filename information. The callback passed in to + * efreet_desktop_command_get is called for each exec string created. + * + * @param command: the command to build + * @return a list of executable strings + */ +static Eina_List * +efreet_desktop_command_build(Efreet_Desktop_Command *command) +{ + Eina_List *execs = NULL; + const Eina_List *l; + char *exec; + + /* if the Exec field appends multiple, that will run the list to the end, + * causing this loop to only run once. otherwise, this loop will generate a + * command for each file in the list. if the list is empty, this + * will run once, removing any file field codes */ + l = command->files; + do + { + const char *p; + int len = 0; + int size = PATH_MAX; + int file_added = 0; + Efreet_Desktop_Command_File *file = eina_list_data_get(l); + + exec = malloc(size); + p = command->desktop->exec; + len = 0; + + while (*p) + { + if (len >= size - 1) + { + size = len + 1024; + exec = realloc(exec, size); + } + + /* XXX handle fields inside quotes? */ + if (*p == '%') + { + p++; + switch (*p) + { + case 'f': + case 'u': + case 'd': + case 'n': + if (file) + { + exec = efreet_desktop_command_append_single(exec, &size, + &len, file, *p); + file_added = 1; + } + break; + case 'F': + case 'U': + case 'D': + case 'N': + if (file) + { + exec = efreet_desktop_command_append_multiple(exec, &size, + &len, command, *p); + file_added = 1; + } + break; + case 'i': + exec = efreet_desktop_command_append_icon(exec, &size, &len, + command->desktop); + break; + case 'c': + exec = efreet_desktop_command_append_quoted(exec, &size, &len, + command->desktop->name); + break; + case 'k': + exec = efreet_desktop_command_append_quoted(exec, &size, &len, + command->desktop->orig_path); + break; + case 'v': + case 'm': + WRN("[Efreet]: Deprecated conversion char: '%c' in file '%s'", + *p, command->desktop->orig_path); + break; + case '%': + exec[len++] = *p; + break; + default: +#ifdef STRICT_SPEC + WRN("[Efreet_desktop]: Unknown conversion character: '%c'", *p); +#endif + break; + } + } + else exec[len++] = *p; + p++; + } + +#ifdef SLOPPY_SPEC + /* NON-SPEC!!! this is to work around LOTS of 'broken' .desktop files that + * do not specify %U/%u, %F/F etc. etc. at all. just a command. this is + * unlikely to be fixed in distributions etc. in the long run as gnome/kde + * seem to have workarounds too so no one notices. + */ + if ((file) && (!file_added)) + { + WRN("Efreet_desktop: %s\n" + " command: %s\n" + " has no file path/uri spec info for executing this app WITH a\n" + " file/uri as a parameter. This is unlikely to be the intent.\n" + " please check the .desktop file and fix it by adding a %%U or %%F\n" + " or something appropriate.", + command->desktop->orig_path, command->desktop->exec); + if (len >= size - 1) + { + size = len + 1024; + exec = realloc(exec, size); + } + exec[len++] = ' '; + exec = efreet_desktop_command_append_multiple(exec, &size, + &len, command, 'F'); + file_added = 1; + } +#endif + exec[len++] = '\0'; + + execs = eina_list_append(execs, exec); + + /* If no file was added, then the Exec field doesn't contain any file + * fields (fFuUdDnN). We only want to run the app once in this case. */ + if (!file_added) break; + } + while ((l = eina_list_next(l)) != NULL); + + return execs; +} + +static void +efreet_desktop_command_free(Efreet_Desktop_Command *command) +{ + Efreet_Desktop_Command_File *dcf; + + if (!command) return; + + while (command->files) + { + dcf = eina_list_data_get(command->files); + efreet_desktop_command_file_free(dcf); + command->files = eina_list_remove_list(command->files, + command->files); + } + FREE(command); +} + +static char * +efreet_desktop_command_append_quoted(char *dest, int *size, int *len, char *src) +{ + if (!src) return dest; + dest = efreet_string_append(dest, size, len, "'"); + + /* single quotes in src need to be escaped */ + if (strchr(src, '\'')) + { + char *p; + p = src; + while (*p) + { + if (*p == '\'') + dest = efreet_string_append(dest, size, len, "\'\\\'"); + + dest = efreet_string_append_char(dest, size, len, *p); + p++; + } + } + else + dest = efreet_string_append(dest, size, len, src); + + dest = efreet_string_append(dest, size, len, "'"); + + return dest; +} + +static char * +efreet_desktop_command_append_multiple(char *dest, int *size, int *len, + Efreet_Desktop_Command *command, + char type) +{ + Efreet_Desktop_Command_File *file; + Eina_List *l; + int first = 1; + + if (!command->files) return dest; + + EINA_LIST_FOREACH(command->files, l, file) + { + if (first) + first = 0; + else + dest = efreet_string_append_char(dest, size, len, ' '); + + dest = efreet_desktop_command_append_single(dest, size, len, + file, tolower(type)); + } + + return dest; +} + +static char * +efreet_desktop_command_append_single(char *dest, int *size, int *len, + Efreet_Desktop_Command_File *file, + char type) +{ + char *str; + switch(type) + { + case 'f': + str = file->fullpath; + break; + case 'u': + str = file->uri; + break; + case 'd': + str = file->dir; + break; + case 'n': + str = file->file; + break; + default: + ERR("Invalid type passed to efreet_desktop_command_append_single:" + " '%c'", type); + return dest; + } + + if (!str) return dest; + + dest = efreet_desktop_command_append_quoted(dest, size, len, str); + + return dest; +} + +static char * +efreet_desktop_command_append_icon(char *dest, int *size, int *len, + Efreet_Desktop *desktop) +{ + if (!desktop->icon || !desktop->icon[0]) return dest; + + dest = efreet_string_append(dest, size, len, "--icon "); + dest = efreet_desktop_command_append_quoted(dest, size, len, desktop->icon); + + return dest; +} + +/** + * @param command: the Efreet_Desktop_Comand that this file is for + * @param file: the filname as either an absolute path, relative path, or URI + */ +static Efreet_Desktop_Command_File * +efreet_desktop_command_file_process(Efreet_Desktop_Command *command, const char *file) +{ + Efreet_Desktop_Command_File *f; + const char *uri, *base; + int nonlocal = 0; +/* + DBG("FLAGS: %d, %d, %d, %d\n", + command->flags & EFREET_DESKTOP_EXEC_FLAG_FULLPATH ? 1 : 0, + command->flags & EFREET_DESKTOP_EXEC_FLAG_URI ? 1 : 0); +*/ + f = NEW(Efreet_Desktop_Command_File, 1); + if (!f) return NULL; + + f->command = command; + + /* handle uris */ + if (!strncmp(file, "http://", 7) || !strncmp(file, "ftp://", 6)) + { + uri = file; + base = ecore_file_file_get(file); + + nonlocal = 1; + } + else if (!strncmp(file, "file:", 5)) + { + file = efreet_desktop_command_file_uri_process(file); + if (!file) + { + efreet_desktop_command_file_free(f); + return NULL; + } + } + + if (nonlocal) + { + /* process non-local uri */ + if (command->flags & EFREET_DESKTOP_EXEC_FLAG_FULLPATH) + { + char buf[PATH_MAX]; + + snprintf(buf, sizeof(buf), "/tmp/%d-%d-%s", getpid(), + efreet_desktop_command_file_id++, base); + f->fullpath = strdup(buf); + f->pending = 1; + + ecore_file_download(uri, f->fullpath, efreet_desktop_cb_download_complete, + efreet_desktop_cb_download_progress, f, NULL); + } + + if (command->flags & EFREET_DESKTOP_EXEC_FLAG_URI) + f->uri = strdup(uri); + } + else + { + char *absol = efreet_desktop_command_path_absolute(file); + /* process local uri/path */ + if (command->flags & EFREET_DESKTOP_EXEC_FLAG_FULLPATH) + f->fullpath = strdup(absol); + + if (command->flags & EFREET_DESKTOP_EXEC_FLAG_URI) + { + char buf[PATH_MAX]; + snprintf(buf, sizeof(buf), "file://%s", absol); + f->uri = strdup(buf); + } + + free(absol); + } +#if 0 + INF(" fullpath: %s", f->fullpath); + INF(" uri: %s", f->uri); + INF(" dir: %s", f->dir); + INF(" file: %s", f->file); +#endif + return f; +} + +/** + * @brief Find the local path portion of a file uri. + * @param uri: a uri beginning with "file:" + * @return the location of the path portion of the uri, + * or NULL if the file is not on this machine + */ +static const char * +efreet_desktop_command_file_uri_process(const char *uri) +{ + const char *path = NULL; + int len = strlen(uri); + + /* uri:foo/bar => relative path foo/bar*/ + if (len >= 4 && uri[5] != '/') + path = uri + strlen("file:"); + + /* uri:/foo/bar => absolute path /foo/bar */ + else if (len >= 5 && uri[6] != '/') + path = uri + strlen("file:"); + + /* uri://foo/bar => absolute path /bar on machine foo */ + else if (len >= 6 && uri[7] != '/') + { + char *tmp, *p; + char hostname[PATH_MAX]; + size_t len2; + + len2 = strlen(uri + 7) + 1; + tmp = alloca(len2); + memcpy(tmp, uri + 7, len2); + p = strchr(tmp, '/'); + if (p) + { + *p = '\0'; + if (!strcmp(tmp, "localhost")) + path = uri + strlen("file://localhost"); + else + { + int ret; + + ret = gethostname(hostname, PATH_MAX); + if ((ret == 0) && !strcmp(tmp, hostname)) + path = uri + strlen("file://") + strlen(hostname); + } + } + } + + /* uri:///foo/bar => absolute path /foo/bar on local machine */ + else if (len >= 7) + path = uri + strlen("file://"); + + return path; +} + +static void +efreet_desktop_command_file_free(Efreet_Desktop_Command_File *file) +{ + if (!file) return; + + IF_FREE(file->fullpath); + IF_FREE(file->uri); + IF_FREE(file->dir); + IF_FREE(file->file); + + FREE(file); +} + + +static void +efreet_desktop_cb_download_complete(void *data, const char *file __UNUSED__, + int status __UNUSED__) +{ + Efreet_Desktop_Command_File *f; + + f = data; + + /* XXX check status... error handling, etc */ + f->pending = 0; + f->command->num_pending--; + + if (f->command->num_pending <= 0) + { + Eina_List *execs; + + execs = efreet_desktop_command_build(f->command); + /* TODO: Need to handle the return value from efreet_desktop_command_execs_process */ + efreet_desktop_command_execs_process(f->command, execs); + eina_list_free(execs); + efreet_desktop_command_free(f->command); + } +} + +static int +efreet_desktop_cb_download_progress(void *data, + const char *file __UNUSED__, + long int dltotal, long int dlnow, + long int ultotal __UNUSED__, + long int ulnow __UNUSED__) +{ + Efreet_Desktop_Command_File *dcf; + + dcf = data; + if (dcf->command->cb_progress) + return dcf->command->cb_progress(dcf->command->data, + dcf->command->desktop, + dcf->uri, dltotal, dlnow); + + return 0; +} + +/** + * @brief Build an absolute path from an absolute or relative one. + * @param path: an absolute or relative path + * @return an allocated absolute path (must be freed) + */ +static char * +efreet_desktop_command_path_absolute(const char *path) +{ + char *buf; + int size = PATH_MAX; + int len = 0; + + /* relative url */ + if (path[0] != '/') + { + if (!(buf = malloc(size))) return NULL; + if (!getcwd(buf, size)) + { + FREE(buf); + return NULL; + } + len = strlen(buf); + + if (buf[len-1] != '/') buf = efreet_string_append(buf, &size, &len, "/"); + buf = efreet_string_append(buf, &size, &len, path); + + return buf; + } + + /* just dup an already absolute buffer */ + return strdup(path); +} + +/** + * Append a string to a buffer, reallocating as necessary. + */ +static char * +efreet_string_append(char *dest, int *size, int *len, const char *src) +{ + int l; + int off = 0; + + l = eina_strlcpy(dest + *len, src, *size - *len); + + while (l > *size - *len) + { + /* we successfully appended this much */ + off += *size - *len - 1; + *len = *size - 1; + *size += 1024; + dest = realloc(dest, *size); + *(dest + *len) = '\0'; + + l = eina_strlcpy(dest + *len, src + off, *size - *len); + } + *len += l; + + return dest; +} + +static char * +efreet_string_append_char(char *dest, int *size, int *len, char c) +{ + if (*len >= *size - 1) + { + *size += 1024; + dest = realloc(dest, *size); + } + + dest[(*len)++] = c; + dest[*len] = '\0'; + + return dest; +} + -- 2.7.4