Logo Search packages:      
Sourcecode: ladish version File versions  Download package

appdb.c

/* -*- Mode: C ; c-basic-offset: 2 -*- */
/*
 * LADI Session Handler (ladish)
 *
 * Copyright (C) 2008,2009,2010 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 "appdb.h"
#include "../log.h"
#include "../common/catdup.h"
#include "../assert.h"

void
lash_appdb_free_entry(
  struct lash_appdb_entry * entry_ptr);

#define MAP_TYPE_STRING  0
#define MAP_TYPE_BOOL    1

00045 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,
  }
};

00094 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)
  {
    log_error("Failed to open '%s' for reading", file_path);
    goto exit;
  }

  if (fseek(file, 0, SEEK_END) == -1)
  {
    log_error("fseek('%s') failed", file_path);
    goto exit_close;
  }

  size = ftell(file);
  if (size == -1)
  {
    log_error("ftell('%s') failed", file_path);
    goto exit_close;
  }

  data_ptr = malloc(size + 1);
  if (data_ptr == NULL)
  {
    log_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)
  {
    log_error("fseek('%s') failed", file_path);
    goto exit_close;
  }

  if (fread(data_ptr, size, 1, file) != 1)
  {
    log_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)
    {
      //log_info("there is next line");
      *next_line = 0;
      next_line++;
    }

    //log_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);

    //log_info("Key=%s", line);
    //log_info("Value=%s", value);

    if (count + 1 == max_count)
    {
      log_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;

  //log_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;
  }

  //log_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;
    }
  }

  //log_info("Application '%s' found", name);

  /* allocate new entry */
  entry_ptr = malloc(sizeof(struct lash_appdb_entry));
  if (entry_ptr == NULL)
  {
    log_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;
    }

    //log_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)
      {
        log_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
      {
        log_error("Ignoring %s:%s bool with wrong value '%s'", name, map_ptr->key, value);
      }
    }
    else
    {
      ASSERT_NO_PASS;
      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;

  //log_info("lash_appdb_load_dir() called for '%s'.", base_directory);

  ret = false;

  directory_path = catdup(base_directory, "/applications/");
  if (directory_path == NULL)
  {
    log_error("catdup() failed to compose the appdb dir path");
    goto fail;
  }

  //log_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 = catdup(directory_path, dentry_ptr->d_name);
      if (file_path == NULL)
      {
        log_error("catdup() failed to compose the appdb dir file");
      }
      else
      {
        if (!lash_appdb_load_file(appdb, file_path))
        {
          free(file_path);
          goto fail_free_path;
        }

        free(file_path);
      }
    }

    closedir(dir);
  }
  else
  {
    //log_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)
  {
    log_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);

  //log_info("lash_appdb_load() called.");

  home_dir = getenv("HOME");
  if (home_dir == NULL)
  {
    log_error("HOME environment variable is not set.");
    goto fail;
  }

  data_home_default = catdup(home_dir, "/.local/share");
  if (data_home_default == NULL)
  {
    log_error("catdup failed to compose data_home_default");
    goto fail;
  }

  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)
{
  //log_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;

  //log_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);

    //log_info("Destroying appdb entry '%s'", entry_ptr->name);

    lash_appdb_free_entry(entry_ptr);
  }
}

Generated by  Doxygen 1.6.0   Back to index