implement view switching from world tree
[ladish.git] / daemon / appdb.c
blob377ea24367b4fcb12f31912cfeec164aeefd3c8a
1 /* -*- Mode: C ; c-basic-offset: 2 -*- */
2 /*
3 * LADI Session Handler (ladish)
5 * Copyright (C) 2008, 2009 Nedko Arnaudov <nedko@arnaudov.name>
7 **************************************************************************
8 * This file contains code of the application database
9 **************************************************************************
11 * LADI Session Handler is free software; you can redistribute it and/or modify
12 * it under the terms of the GNU General Public License as published by
13 * the Free Software Foundation; either version 2 of the License, or
14 * (at your option) any later version.
16 * LADI Session Handler is distributed in the hope that it will be useful,
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 * GNU General Public License for more details.
21 * You should have received a copy of the GNU General Public License
22 * along with LADI Session Handler. If not, see <http://www.gnu.org/licenses/>
23 * or write to the Free Software Foundation, Inc.,
24 * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
27 #include <stdbool.h>
28 #include <stdlib.h>
29 #include <string.h>
30 #include <sys/types.h>
31 #include <dirent.h>
32 #include <assert.h>
34 #include "appdb.h"
35 #include "../common/debug.h"
36 #include "catdup.h"
38 void
39 lash_appdb_free_entry(
40 struct lash_appdb_entry * entry_ptr);
42 #define MAP_TYPE_STRING 0
43 #define MAP_TYPE_BOOL 1
45 struct map
47 const char * key;
48 unsigned int type;
49 size_t offset;
52 struct map g_appdb_entry_map[] =
55 .key = "Name",
56 .type = MAP_TYPE_STRING,
57 .offset = offsetof(struct lash_appdb_entry, name)
60 .key = "GenericName",
61 .type = MAP_TYPE_STRING,
62 .offset = offsetof(struct lash_appdb_entry, generic_name)
65 .key = "Comment",
66 .type = MAP_TYPE_STRING,
67 .offset = offsetof(struct lash_appdb_entry, comment)
70 .key = "Icon",
71 .type = MAP_TYPE_STRING,
72 .offset = offsetof(struct lash_appdb_entry, icon)
75 .key = "Exec",
76 .type = MAP_TYPE_STRING,
77 .offset = offsetof(struct lash_appdb_entry, exec)
80 .key = "Path",
81 .type = MAP_TYPE_STRING,
82 .offset = offsetof(struct lash_appdb_entry, path)
85 .key = "Terminal",
86 .type = MAP_TYPE_BOOL,
87 .offset = offsetof(struct lash_appdb_entry, terminal)
90 .key = NULL,
94 struct entry
96 const char * key;
97 const char * value;
100 #define MAX_ENTRIES 1000
102 static
103 const char *
104 get_xdg_var(
105 const char * var_name,
106 const char * default_value)
108 const char * value;
110 value = getenv(var_name);
112 /* Spec says that if variable is "either not set or empty", default should be used */
113 if (value == NULL || strlen(value) == 0)
115 return default_value;
118 return value;
121 static
122 bool
123 suffix_match(
124 const char * string,
125 const char * suffix)
127 size_t len;
128 size_t len_suffix;
130 len = strlen(string);
131 len_suffix = strlen(suffix);
133 if (len <= len_suffix)
135 return false;
138 if (memcmp(string + (len - len_suffix), ".desktop", len_suffix) != 0)
140 return false;
143 return true;
146 static
147 bool
148 load_file_data(
149 const char * file_path,
150 char ** data_ptr_ptr)
152 FILE * file;
153 long size;
154 bool ret;
155 char * data_ptr;
157 ret = true;
158 *data_ptr_ptr = NULL;
160 file = fopen(file_path, "r");
161 if (file == NULL)
163 lash_error("Failed to open '%s' for reading", file_path);
164 goto exit;
167 if (fseek(file, 0, SEEK_END) == -1)
169 lash_error("fseek('%s') failed", file_path);
170 goto exit_close;
173 size = ftell(file);
174 if (size == -1)
176 lash_error("ftell('%s') failed", file_path);
177 goto exit_close;
180 data_ptr = malloc(size + 1);
181 if (data_ptr == NULL)
183 lash_error("Failed to allocate %ld bytes for data of file '%s'", size + 1, file_path);
184 ret = false;
185 goto exit_close;
188 if (fseek(file, 0, SEEK_SET) == -1)
190 lash_error("fseek('%s') failed", file_path);
191 goto exit_close;
194 if (fread(data_ptr, size, 1, file) != 1)
196 lash_error("Failed to read %ld bytes of data from file '%s'", size, file_path);
197 goto exit_free_data;
200 data_ptr[size] = 0;
202 *data_ptr_ptr = data_ptr;
203 goto exit_close;
205 exit_free_data:
206 free(data_ptr);
208 exit_close:
209 fclose(file);
211 exit:
212 return ret;
215 char *
216 strlstrip(char * string)
218 while (*string == ' ' || *string == '\t')
220 string++;
223 return string;
226 void
227 strrstrip(char * string)
229 char * temp;
231 temp = string + strlen(string);
233 while (temp > string)
235 temp--;
237 if (*temp == ' ' || *temp == '\t')
239 *temp = 0;
244 bool
245 lash_appdb_parse_file_data(
246 char * data,
247 struct entry * entries_array,
248 size_t max_count,
249 size_t * count_ptr)
251 char * line;
252 char * next_line;
253 char * value;
254 bool group_found;
255 size_t count;
257 group_found = false;
258 line = data;
259 count = 0;
263 next_line = strchr(line, '\n');
264 if (next_line != NULL)
266 //lash_info("there is next line");
267 *next_line = 0;
268 next_line++;
271 //lash_info("Line '%s'", line);
273 /* skip comments (and empty lines) */
274 if (*line == 0 || *line == '#')
276 continue;
279 if (!group_found)
281 /* first real line should be begining of "Desktop Entry" group */
282 if (strcmp(line, "[Desktop Entry]") != 0)
284 return false;
287 group_found = true;
288 continue;
291 value = strchr(line, '=');
292 if (value == NULL)
294 goto exit;
297 *value = 0;
298 value++;
300 /* strip spaces */
301 strrstrip(line);
302 value = strlstrip(value);
304 //lash_info("Key=%s", line);
305 //lash_info("Value=%s", value);
307 if (count + 1 == max_count)
309 lash_error("failed to parse desktop entry with more than %u keys", (unsigned int)max_count);
310 return false;
313 entries_array->key = line;
314 entries_array->value = value;
315 entries_array++;
316 count++;
318 while ((line = next_line) != NULL);
320 exit:
321 *count_ptr = count;
323 return group_found;
326 const char *
327 lash_appdb_find_key(
328 struct entry * entries,
329 size_t count,
330 const char * key)
332 size_t i;
334 for (i = 0 ; i < count ; i++)
336 if (strcmp(entries[i].key, key) == 0)
338 return entries[i].value;
342 return NULL;
345 bool
346 lash_appdb_load_file(
347 struct list_head * appdb,
348 const char * file_path)
350 char * data;
351 bool ret;
352 struct entry entries[MAX_ENTRIES];
353 size_t entries_count;
354 const char * value;
355 const char * name;
356 const char * xlash;
357 struct list_head * node_ptr;
358 struct lash_appdb_entry * entry_ptr;
359 struct map * map_ptr;
360 char ** str_ptr_ptr;
361 bool * bool_ptr;
363 //lash_info("Desktop entry '%s'", file_path);
365 ret = true;
367 if (!load_file_data(file_path, &data))
369 ret = false;
370 goto exit;
373 if (data == NULL)
375 goto exit;
378 if (!lash_appdb_parse_file_data(data, entries, MAX_ENTRIES, &entries_count))
380 goto exit_free_data;
383 //lash_info("%llu entries", (unsigned long long)entries_count);
385 /* check whether entry is of "Application" type */
386 value = lash_appdb_find_key(entries, entries_count, "Type");
387 if (value == NULL || strcmp(value, "Application") != 0)
389 goto exit_free_data;
392 /* check whether "Name" is preset, it is required */
393 name = lash_appdb_find_key(entries, entries_count, "Name");
394 if (name == NULL)
396 goto exit_free_data;
399 /* check whether entry has LIBLASH or LASHCLASS key */
400 xlash = lash_appdb_find_key(entries, entries_count, "X-LASH");
401 if (xlash == NULL)
403 goto exit_free_data;
406 /* check whether entry already exists (first found entries have priority according to XDG Base Directory Specification) */
407 list_for_each(node_ptr, appdb)
409 entry_ptr = list_entry(node_ptr, struct lash_appdb_entry, siblings);
411 if (strcmp(entry_ptr->name, name) == 0)
413 goto exit_free_data;
417 //lash_info("Application '%s' found", name);
419 /* allocate new entry */
420 entry_ptr = malloc(sizeof(struct lash_appdb_entry));
421 if (entry_ptr == NULL)
423 lash_error("malloc() failed");
424 goto fail_free_data;
427 memset(entry_ptr, 0, sizeof(struct lash_appdb_entry));
429 /* fill the entry */
430 map_ptr = g_appdb_entry_map;
431 while (map_ptr->key != NULL)
433 value = lash_appdb_find_key(entries, entries_count, map_ptr->key);
434 if (value == NULL)
436 assert(strcmp(map_ptr->key, "Name") != 0); /* name is required and we already checked this */
437 map_ptr++;
438 continue;
441 //lash_info("mapping key '%s' to '%s'", map_ptr->key, value);
443 if (map_ptr->type == MAP_TYPE_STRING)
445 str_ptr_ptr = (char **)((char *)entry_ptr + map_ptr->offset);
446 *str_ptr_ptr = strdup(value);
447 if (*str_ptr_ptr == NULL)
449 lash_error("strdup() failed");
450 goto fail_free_entry;
453 else if (map_ptr->type == MAP_TYPE_BOOL)
455 bool_ptr = (bool *)((char *)entry_ptr + map_ptr->offset);
456 if (strcmp(value, "true") == 0)
458 *bool_ptr = true;
460 else if (strcmp(value, "false") == 0)
462 *bool_ptr = false;
464 else
466 lash_error("Ignoring %s:%s bool with wrong value '%s'", name, map_ptr->key, value);
469 else
471 assert(false);
472 goto fail_free_entry;
475 map_ptr++;
478 /* add entry to appdb list */
479 list_add_tail(&entry_ptr->siblings, appdb);
481 goto exit_free_data;
483 fail_free_entry:
484 lash_appdb_free_entry(entry_ptr);
486 fail_free_data:
487 ret = false;
489 exit_free_data:
490 free(data);
492 exit:
493 return ret;
496 bool
497 lash_appdb_load_dir(
498 struct list_head * appdb,
499 const char * base_directory)
501 char * directory_path;
502 bool ret;
503 DIR * dir;
504 struct dirent * dentry_ptr;
505 char * file_path;
507 //lash_info("lash_appdb_load_dir() called for '%s'.", base_directory);
509 ret = false;
511 directory_path = catdup(base_directory, "/applications/");
512 if (directory_path == NULL)
514 lash_error("catdup() failed to compose the appdb dir path");
515 goto fail;
518 //lash_info("Scanning directory '%s'", directory_path);
520 dir = opendir(directory_path);
521 if (dir != NULL)
523 while ((dentry_ptr = readdir(dir)) != NULL)
525 if (dentry_ptr->d_type != DT_REG)
527 continue;
530 if (!suffix_match(dentry_ptr->d_name, ".desktop"))
532 continue;
535 file_path = catdup(directory_path, dentry_ptr->d_name);
536 if (file_path == NULL)
538 lash_error("catdup() failed to compose the appdb dir file");
540 else
542 if (!lash_appdb_load_file(appdb, file_path))
544 free(file_path);
545 goto fail_free_path;
548 free(file_path);
552 closedir(dir);
554 else
556 //lash_info("failed to open directory '%s'", directory_path);
559 ret = true;
561 fail_free_path:
562 free(directory_path);
564 fail:
565 return ret;
568 bool
569 lash_appdb_load_dirs(
570 struct list_head * appdb,
571 const char * base_directories)
573 char * limiter;
574 char * directory;
575 char * directories;
577 directories = strdup(base_directories);
578 if (directories == NULL)
580 lash_error("strdup() failed");
581 return false;
584 directory = directories;
588 limiter = strchr(directory, ':');
589 if (limiter != NULL)
591 *limiter = 0;
594 if (!lash_appdb_load_dir(appdb, directory))
596 free(directories);
597 return false;
600 directory = limiter + 1;
602 while (limiter != NULL);
604 free(directories);
606 return true;
609 bool
610 lash_appdb_load(
611 struct list_head * appdb)
613 const char * data_home;
614 char * data_home_default;
615 const char * data_dirs;
616 const char * home_dir;
617 bool ret;
619 ret = false;
621 INIT_LIST_HEAD(appdb);
623 //lash_info("lash_appdb_load() called.");
625 home_dir = getenv("HOME");
626 if (home_dir == NULL)
628 lash_error("HOME environment variable is not set.");
629 goto fail;
632 data_home_default = catdup(home_dir, "/.local/share");
633 if (data_home_default == NULL)
635 lash_error("catdup failed to compose data_home_default");
636 goto fail;
639 data_home = get_xdg_var("XDG_DATA_HOME", data_home_default);
641 if (!lash_appdb_load_dir(appdb, data_home))
643 goto fail_free_data_home_default;
646 data_dirs = get_xdg_var("XDG_DATA_DIRS", "/usr/local/share/:/usr/share/");
648 if (!lash_appdb_load_dirs(appdb, data_dirs))
650 goto fail_free_data_home_default;
653 ret = true;
655 fail_free_data_home_default:
656 free(data_home_default);
658 fail:
659 if (!ret)
661 lash_appdb_free(appdb);
664 return ret;
667 void
668 lash_appdb_free_entry(
669 struct lash_appdb_entry * entry_ptr)
671 //lash_info("lash_appdb_free_entry() called.");
673 if (entry_ptr->name != NULL)
675 free(entry_ptr->name);
678 if (entry_ptr->generic_name != NULL)
680 free(entry_ptr->generic_name);
683 if (entry_ptr->comment != NULL)
685 free(entry_ptr->comment);
688 if (entry_ptr->icon != NULL)
690 free(entry_ptr->icon);
693 if (entry_ptr->exec != NULL)
695 free(entry_ptr->exec);
698 if (entry_ptr->path != NULL)
700 free(entry_ptr->path);
703 free(entry_ptr);
706 void
707 lash_appdb_free(
708 struct list_head * appdb)
710 struct list_head * node_ptr;
711 struct lash_appdb_entry * entry_ptr;
713 //lash_info("lash_appdb_free() called.");
715 while (!list_empty(appdb))
717 node_ptr = appdb->next;
718 entry_ptr = list_entry(node_ptr, struct lash_appdb_entry, siblings);
720 list_del(node_ptr);
722 //lash_info("Destroying appdb entry '%s'", entry_ptr->name);
724 lash_appdb_free_entry(entry_ptr);