/* -*- Mode: C ; c-basic-offset: 2 -*- */ /* * LADI Session Handler (ladish) * * Copyright (C) 2008 Nedko Arnaudov * ************************************************************************** * This file contains code of the application database ************************************************************************** * * LADI Session Handler is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * LADI Session Handler is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with LADI Session Handler. If not, see * or write to the Free Software Foundation, Inc., * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. */ #include #include #include #include #include #include #include "appdb.h" #include "../common/debug.h" #include "../common/safety.h" void lash_appdb_free_entry( struct lash_appdb_entry * entry_ptr); #define MAP_TYPE_STRING 0 #define MAP_TYPE_BOOL 1 struct map { const char * key; unsigned int type; size_t offset; }; struct map g_appdb_entry_map[] = { { .key = "Name", .type = MAP_TYPE_STRING, .offset = offsetof(struct lash_appdb_entry, name) }, { .key = "GenericName", .type = MAP_TYPE_STRING, .offset = offsetof(struct lash_appdb_entry, generic_name) }, { .key = "Comment", .type = MAP_TYPE_STRING, .offset = offsetof(struct lash_appdb_entry, comment) }, { .key = "Icon", .type = MAP_TYPE_STRING, .offset = offsetof(struct lash_appdb_entry, icon) }, { .key = "Exec", .type = MAP_TYPE_STRING, .offset = offsetof(struct lash_appdb_entry, exec) }, { .key = "Path", .type = MAP_TYPE_STRING, .offset = offsetof(struct lash_appdb_entry, path) }, { .key = "Terminal", .type = MAP_TYPE_BOOL, .offset = offsetof(struct lash_appdb_entry, terminal) }, { .key = NULL, } }; struct entry { const char * key; const char * value; }; #define MAX_ENTRIES 1000 static const char * get_xdg_var( const char * var_name, const char * default_value) { const char * value; value = getenv(var_name); /* Spec says that if variable is "either not set or empty", default should be used */ if (value == NULL || strlen(value) == 0) { return default_value; } return value; } static bool suffix_match( const char * string, const char * suffix) { size_t len; size_t len_suffix; len = strlen(string); len_suffix = strlen(suffix); if (len <= len_suffix) { return false; } if (memcmp(string + (len - len_suffix), ".desktop", len_suffix) != 0) { return false; } return true; } static bool load_file_data( const char * file_path, char ** data_ptr_ptr) { FILE * file; long size; bool ret; char * data_ptr; ret = true; *data_ptr_ptr = NULL; file = fopen(file_path, "r"); if (file == NULL) { lash_error("Failed to open '%s' for reading", file_path); goto exit; } if (fseek(file, 0, SEEK_END) == -1) { lash_error("fseek('%s') failed", file_path); goto exit_close; } size = ftell(file); if (size == -1) { lash_error("ftell('%s') failed", file_path); goto exit_close; } data_ptr = malloc(size + 1); if (data_ptr == NULL) { lash_error("Failed to allocate %ld bytes for data of file '%s'", size + 1, file_path); ret = false; goto exit_close; } if (fseek(file, 0, SEEK_SET) == -1) { lash_error("fseek('%s') failed", file_path); goto exit_close; } if (fread(data_ptr, size, 1, file) != 1) { lash_error("Failed to read %ld bytes of data from file '%s'", size, file_path); goto exit_free_data; } data_ptr[size] = 0; *data_ptr_ptr = data_ptr; goto exit_close; exit_free_data: free(data_ptr); exit_close: fclose(file); exit: return ret; } char * strlstrip(char * string) { while (*string == ' ' || *string == '\t') { string++; } return string; } void strrstrip(char * string) { char * temp; temp = string + strlen(string); while (temp > string) { temp--; if (*temp == ' ' || *temp == '\t') { *temp = 0; } } } bool lash_appdb_parse_file_data( char * data, struct entry * entries_array, size_t max_count, size_t * count_ptr) { char * line; char * next_line; char * value; bool group_found; size_t count; group_found = false; line = data; count = 0; do { next_line = strchr(line, '\n'); if (next_line != NULL) { //lash_info("there is next line"); *next_line = 0; next_line++; } //lash_info("Line '%s'", line); /* skip comments (and empty lines) */ if (*line == 0 || *line == '#') { continue; } if (!group_found) { /* first real line should be begining of "Desktop Entry" group */ if (strcmp(line, "[Desktop Entry]") != 0) { return false; } group_found = true; continue; } value = strchr(line, '='); if (value == NULL) { goto exit; } *value = 0; value++; /* strip spaces */ strrstrip(line); value = strlstrip(value); //lash_info("Key=%s", line); //lash_info("Value=%s", value); if (count + 1 == max_count) { lash_error("failed to parse desktop entry with more than %u keys", (unsigned int)max_count); return false; } entries_array->key = line; entries_array->value = value; entries_array++; count++; } while ((line = next_line) != NULL); exit: *count_ptr = count; return group_found; } const char * lash_appdb_find_key( struct entry * entries, size_t count, const char * key) { size_t i; for (i = 0 ; i < count ; i++) { if (strcmp(entries[i].key, key) == 0) { return entries[i].value; } } return NULL; } bool lash_appdb_load_file( struct list_head * appdb, const char * file_path) { char * data; bool ret; struct entry entries[MAX_ENTRIES]; size_t entries_count; const char * value; const char * name; const char * xlash; struct list_head * node_ptr; struct lash_appdb_entry * entry_ptr; struct map * map_ptr; char ** str_ptr_ptr; bool * bool_ptr; //lash_info("Desktop entry '%s'", file_path); ret = true; if (!load_file_data(file_path, &data)) { ret = false; goto exit; } if (data == NULL) { goto exit; } if (!lash_appdb_parse_file_data(data, entries, MAX_ENTRIES, &entries_count)) { goto exit_free_data; } //lash_info("%llu entries", (unsigned long long)entries_count); /* check whether entry is of "Application" type */ value = lash_appdb_find_key(entries, entries_count, "Type"); if (value == NULL || strcmp(value, "Application") != 0) { goto exit_free_data; } /* check whether "Name" is preset, it is required */ name = lash_appdb_find_key(entries, entries_count, "Name"); if (name == NULL) { goto exit_free_data; } /* check whether entry has LIBLASH or LASHCLASS key */ xlash = lash_appdb_find_key(entries, entries_count, "X-LASH"); if (xlash == NULL) { goto exit_free_data; } /* check whether entry already exists (first found entries have priority according to XDG Base Directory Specification) */ list_for_each(node_ptr, appdb) { entry_ptr = list_entry(node_ptr, struct lash_appdb_entry, siblings); if (strcmp(entry_ptr->name, name) == 0) { goto exit_free_data; } } //lash_info("Application '%s' found", name); /* allocate new entry */ entry_ptr = malloc(sizeof(struct lash_appdb_entry)); if (entry_ptr == NULL) { lash_error("malloc() failed"); goto fail_free_data; } memset(entry_ptr, 0, sizeof(struct lash_appdb_entry)); /* fill the entry */ map_ptr = g_appdb_entry_map; while (map_ptr->key != NULL) { value = lash_appdb_find_key(entries, entries_count, map_ptr->key); if (value == NULL) { assert(strcmp(map_ptr->key, "Name") != 0); /* name is required and we already checked this */ map_ptr++; continue; } //lash_info("mapping key '%s' to '%s'", map_ptr->key, value); if (map_ptr->type == MAP_TYPE_STRING) { str_ptr_ptr = (char **)((char *)entry_ptr + map_ptr->offset); *str_ptr_ptr = strdup(value); if (*str_ptr_ptr == NULL) { lash_error("strdup() failed"); goto fail_free_entry; } } else if (map_ptr->type == MAP_TYPE_BOOL) { bool_ptr = (bool *)((char *)entry_ptr + map_ptr->offset); if (strcmp(value, "true") == 0) { *bool_ptr = true; } else if (strcmp(value, "false") == 0) { *bool_ptr = false; } else { lash_error("Ignoring %s:%s bool with wrong value '%s'", name, map_ptr->key, value); } } else { assert(false); goto fail_free_entry; } map_ptr++; } /* add entry to appdb list */ list_add_tail(&entry_ptr->siblings, appdb); goto exit_free_data; fail_free_entry: lash_appdb_free_entry(entry_ptr); fail_free_data: ret = false; exit_free_data: free(data); exit: return ret; } bool lash_appdb_load_dir( struct list_head * appdb, const char * base_directory) { char * directory_path; bool ret; DIR * dir; struct dirent * dentry_ptr; char * file_path; //lash_info("lash_appdb_load_dir() called for '%s'.", base_directory); ret = false; directory_path = lash_catdup(base_directory, "/applications/"); //lash_info("Scanning directory '%s'", directory_path); dir = opendir(directory_path); if (dir != NULL) { while ((dentry_ptr = readdir(dir)) != NULL) { if (dentry_ptr->d_type != DT_REG) { continue; } if (!suffix_match(dentry_ptr->d_name, ".desktop")) { continue; } file_path = lash_catdup(directory_path, dentry_ptr->d_name); if (!lash_appdb_load_file(appdb, file_path)) { free(file_path); goto fail_free_path; } free(file_path); } closedir(dir); } else { //lash_info("failed to open directory '%s'", directory_path); } ret = true; fail_free_path: free(directory_path); //fail: return ret; } bool lash_appdb_load_dirs( struct list_head * appdb, const char * base_directories) { char * limiter; char * directory; char * directories; directories = strdup(base_directories); if (directories == NULL) { lash_error("strdup() failed"); return false; } directory = directories; do { limiter = strchr(directory, ':'); if (limiter != NULL) { *limiter = 0; } if (!lash_appdb_load_dir(appdb, directory)) { free(directories); return false; } directory = limiter + 1; } while (limiter != NULL); free(directories); return true; } bool lash_appdb_load( struct list_head * appdb) { const char * data_home; char * data_home_default; const char * data_dirs; const char * home_dir; bool ret; ret = false; INIT_LIST_HEAD(appdb); //lash_info("lash_appdb_load() called."); home_dir = getenv("HOME"); if (home_dir == NULL) { lash_error("HOME environment variable is not set."); goto fail; } data_home_default = lash_catdup(home_dir, "/.local/share"); data_home = get_xdg_var("XDG_DATA_HOME", data_home_default); if (!lash_appdb_load_dir(appdb, data_home)) { goto fail_free_data_home_default; } data_dirs = get_xdg_var("XDG_DATA_DIRS", "/usr/local/share/:/usr/share/"); if (!lash_appdb_load_dirs(appdb, data_dirs)) { goto fail_free_data_home_default; } ret = true; fail_free_data_home_default: free(data_home_default); fail: if (!ret) { lash_appdb_free(appdb); } return ret; } void lash_appdb_free_entry( struct lash_appdb_entry * entry_ptr) { //lash_info("lash_appdb_free_entry() called."); if (entry_ptr->name != NULL) { free(entry_ptr->name); } if (entry_ptr->generic_name != NULL) { free(entry_ptr->generic_name); } if (entry_ptr->comment != NULL) { free(entry_ptr->comment); } if (entry_ptr->icon != NULL) { free(entry_ptr->icon); } if (entry_ptr->exec != NULL) { free(entry_ptr->exec); } if (entry_ptr->path != NULL) { free(entry_ptr->path); } free(entry_ptr); } void lash_appdb_free( struct list_head * appdb) { struct list_head * node_ptr; struct lash_appdb_entry * entry_ptr; //lash_info("lash_appdb_free() called."); while (!list_empty(appdb)) { node_ptr = appdb->next; entry_ptr = list_entry(node_ptr, struct lash_appdb_entry, siblings); list_del(node_ptr); //lash_info("Destroying appdb entry '%s'", entry_ptr->name); lash_appdb_free_entry(entry_ptr); } }