ladish/daemon/appdb.c

711 lines
13 KiB
C

/* -*- Mode: C ; c-basic-offset: 2 -*- */
/*
* LADI Session Handler (ladish)
*
* Copyright (C) 2008 Nedko Arnaudov <nedko@arnaudov.name>
*
**************************************************************************
* 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 <http://www.gnu.org/licenses/>
* or write to the Free Software Foundation, Inc.,
* 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <dirent.h>
#include <assert.h>
#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);
}
}