/* -*- Mode: C; indent-tabs-mode: nil; tab-width: 2 -*-
 *
 * Copyright (C) 2009,2010 Canonical Ltd.
 * Authors:
 *  Gustavo Sverzut Barbieri <gustavo.barbieri@canonical.com>
 *  Michael Terry <michael.terry@canonical.com>
 *
 * This file is part of Netbook Launcher EFL.
 *
 * Netbook Launcher EFL 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, version 3 of the License.
 *
 * Netbook Launcher EFL 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 Netbook Launcher EFL.  If not, see <http://www.gnu.org/licenses/>.
 */

#include "netbook-launcher.h"

#include <Elementary.h>
#include <glib/gi18n.h>

#ifdef HAVE_GOOGLE_GADGETS
#include <libgadget/libgadget.h>
#endif

#define LINK_WATCH_DIR "/tmp/webfav"

static const char _EDJE_GROUP_LAYOUT[] = "e/netbook/launcher/main/apps/layout";
static const char _EDJE_GROUP_GRID[] = "e/netbook/launcher/main/apps/grid";
static const char _EDJE_GROUP_ITEM[] = "e/netbook/launcher/main/apps/item";
static const char _EDJE_PART_CONTENTS[] = "e.box.contents";
static const char _EDJE_PART_SCROLLER[] = "e.swallow.scroller";
static const char _EDJE_PART_QUIT[] = "e.swallow.quit";

static const char _EDJE_SIG_SRC[] = "e";
static const char _EDJE_SIG_FOCUS_IN[] = "gui,action,focus,in";
static const char _EDJE_SIG_ENTER[] = "gui,action,enter";
static const char _EDJE_SIG_EXIT[] = "gui,action,exit";
static const char _EDJE_SIG_QUIT_EXECUTED[] = "gui,action,quit,executed";
static const char _EDJE_SIG_ENTER_END[] = "gui,action,enter,end";
static const char _EDJE_SIG_EXIT_END[] = "gui,action,exit,end";
static const char _EDJE_SIG_FAV_ADDED[] = "gui,state,favorite,added";
static const char _EDJE_SIG_QUIT_FAV_ENTER[] = "gui,action,quit,favorites,enter";
static const char _EDJE_SIG_QUIT_FAV_EXIT[] = "gui,action,quit,favorites,exit";
static const char _EDJE_SIG_QUIT_CAT_ENTER[] = "gui,action,quit,categories,enter";
static const char _EDJE_SIG_QUIT_CAT_EXIT[] = "gui,action,quit,categories,exit";
static const char _EDJE_SIG_ADD_ITEM_EXECUTED[] = "gui,action,add_item,executed";
static const char _EDJE_SIG_EDIT_MODE_ON[] = "edit_mode,on";
static const char _EDJE_SIG_EDIT_MODE_OFF[] = "edit_mode,off";

static const char _favorites_id[] = "_nl_apps_favorites";
static const char _gadgets_id[] = "Widgets";
static const char _apps_key[] = "_nl_apps";

static const char _ICON_QUIT[] = "gnome-logout";


int FAVORITES_EVENT_CHANGED = 0;

struct apps_context
{
  Evas_Object *apps;
  Evas_Object *edje;
  Evas_Object *scroller;
  Evas_Object *grid;
  Evas_Object *quit;
  const char *favorites_id;
  const char *gadgets_id;
  const char *category;
  const char *pending_category;
  const char *quit_icon;
  void (*pending_end_callback)(void *data, Evas_Object *apps);
  void *pending_end_callback_data;

  GFile *watch_dir;
  GFileMonitor *dir_monitor;

  struct
  {
    Ecore_Event_Handler *favorites_changed;
  } handler;

  struct app_item_common item;
};

static inline void
_apps_context_set(Evas_Object *obj, struct apps_context *ctxt)
{
  evas_object_data_set(obj, _apps_key, ctxt);
}

static inline struct apps_context *
_apps_context_get(Evas_Object *obj)
{
  return evas_object_data_get(obj, _apps_key);
}

static void
_apps_quit_executed(void *data, Evas_Object *o, const char *signal __UNUSED__, const char *source __UNUSED__)
{
  Evas_Object *apps = data;
  evas_object_smart_callback_call(apps, CALLBACK_APPS_QUIT_SELECTED, NULL);
  sound_play(o, "button-pressed");
  sound_play(o, "button-released");
}

static void
_apps_add_item_executed(void *data, Evas_Object *o, const char *signal __UNUSED__, const char *source __UNUSED__)
{
  Evas_Object *apps = data;
  evas_object_smart_callback_call(apps, CALLBACK_APPS_ADD_ITEM_SELECTED, NULL);
}

/* Emits @signal with @source on each item _EDJE_GROUP_ITEM of the grid _EDJE_GROUP_GRID */
static void
_app_items_emit_signal(void *data, Evas_Object *obj, const char *signal, const char *source)
{
  struct apps_context *ctxt = data;

  Evas_Object* edje_grid = elm_layout_edje_get(ctxt->grid);
  Evas_Object* box = (Evas_Object *)edje_object_part_object_get(edje_grid, _EDJE_PART_CONTENTS);
  Eina_Iterator* iter = evas_object_box_iterator_new(box);
  Evas_Object *item;

  while(eina_iterator_next(iter, (void**)&item))
    edje_object_signal_emit(elm_layout_edje_get(item), signal, source);

  eina_iterator_free(iter);
}



static void
_apps_ecore_quit_show(struct apps_context *ctxt, Eina_Bool is_favorite)
{
#if USE_QUIT
  const char *sig_enter, *sig_exit;
  Evas_Object *ed;

  if (!ctxt->quit_icon)
    return;

  if (!ctxt->quit)
  {
    ctxt->quit = _nl_icon_load_sized
      (ctxt->apps, ctxt->quit_icon, ctxt->item.item.icon_size);
    if (!ctxt->quit)
      return;
    elm_layout_content_set(ctxt->apps, _EDJE_PART_QUIT, ctxt->quit);
  }

  if (is_favorite)
  {
    sig_enter = _EDJE_SIG_QUIT_FAV_ENTER;
    sig_exit = _EDJE_SIG_QUIT_CAT_EXIT;
  }
  else
  {
    sig_enter = _EDJE_SIG_QUIT_CAT_ENTER;
    sig_exit = _EDJE_SIG_QUIT_FAV_EXIT;
  }

  ed = elm_layout_edje_get(ctxt->apps);
  edje_object_signal_emit(ed, sig_exit, _EDJE_SIG_SRC);
  edje_object_signal_emit(ed, sig_enter, _EDJE_SIG_SRC);
#endif
}

static LauncherCategory *
_apps_find_category(const struct apps_context *ctxt, const char *category)
{
  const GSList *n = launcher_menu_get_categories(ctxt->item.menu);
  for (; n != NULL; n = n->next)
  {
    LauncherCategory *cat = n->data;
    const char *cur = launcher_category_get_name(cat);
    if (cur && strcmp(cur, category) == 0)
      return cat;
  }
  return NULL;
}

static void
_apps_glib_load_category(void *data, const char *category)
{
  struct apps_context *ctxt = data; /* do not use ctxt->category here! */
  LauncherCategory *cat;
  GSList *lst, *n;
  unsigned int i, len;
  Evas_Object *ed;

  cat = _apps_find_category(ctxt, category);
  if (!cat)
  {
    g_warning("could not find category '%s' in menu.", category);
    goto error;
  }

  ed = elm_layout_edje_get(ctxt->grid);
  if (!edje_object_part_box_remove_all(ed, _EDJE_PART_CONTENTS, 1))
  {
      ERR("could not clear box\n");
      return;
  }
  evas_object_size_hint_min_set(ctxt->grid, 0, 0);

  lst = launcher_category_get_applications(cat);
  len = g_slist_length(lst);

  Eina_Bool is_favorite = EINA_FALSE;
  for (i = 1, n = lst; n != NULL; n = n->next, i += 5)
  {
    Evas_Object *item;
    LauncherApplication *app = n->data;
    const char *icon_name, *name, *comment, *app_icon, *match_name, *dfile;

    name = launcher_application_get_name(app);
    name = !name ? "" : name;
    comment = launcher_application_get_comment(app);
    comment = !comment ? "" : comment;
    app_icon = launcher_application_get_icon_name(app);
    match_name = launcher_application_get_unique_string(app);
    match_name = !match_name ? "" : match_name;
    dfile = launcher_application_get_desktop_file(app);
    dfile = !dfile ? "" : dfile;

    icon_name = icon_path_get(app_icon, ctxt->item.item.icon_size);
    if (icon_name)
        app_icon = icon_name;
    app_icon = !app_icon ? "" : app_icon;

    item = app_item_add
        (&ctxt->item, ctxt->category, name, comment, app_icon, match_name, dfile,
         is_favorite);
    if (!item)
        continue;

    edje_object_part_box_append(ed, _EDJE_PART_CONTENTS, item);
  }
  _apps_ecore_quit_show(ctxt, is_favorite);
  return;

error:
    _apps_ecore_quit_show(ctxt, ctxt->category == ctxt->favorites_id);
}

static LauncherApplication *
_apps_glib_favorite_app_find(const struct apps_context *ctxt, const char *uid)
{
  LauncherApplication *app;
  gchar *type, *desktop_file;

  type = launcher_favorites_get_string(ctxt->item.favs, uid, "type");
  if (!type)
  {
    g_warning("no type for favorite id '%s'", uid);
    return NULL;
  }

  if (g_strcmp0(type, "application") != 0)
  {
    g_debug("favorite type '%s' not supported for id '%s'", type, uid);
    g_free(type);
    return NULL;
  }
  g_free(type);

  desktop_file = launcher_favorites_get_string
    (ctxt->item.favs, uid, "desktop_file");
  if (!desktop_file || !g_file_test(desktop_file, G_FILE_TEST_EXISTS))
  {
    g_warning("no desktop file for favorite id '%s'", uid);
    g_free(desktop_file);
    return NULL;
  }

  app = launcher_application_new_from_desktop_file(desktop_file);
  if (!app) {
    g_warning("could not create launcher app for desktop file '%s', favorite id '%s'",
              desktop_file, uid);
    g_free(desktop_file);
    return NULL;
  }

  /* If loading desktop failed, sometimes we'll get a valid, but 
     empty LauncherApplication.  Check for that here. */
  if (!launcher_application_get_desktop_file(app)) {
    g_warning("could not create launcher app for desktop file '%s', favorite id '%s'",
              desktop_file, uid);
    g_free(desktop_file);
    return NULL;
  }

  g_free(desktop_file);
  return app;
}

static gboolean
_apps_glib_gadget_uri_find(const struct apps_context *ctxt, const char *uid)
{
    gchar *type;

    type = launcher_favorites_get_string(ctxt->item.favs, uid, "type");
    if (!type)
    {
        g_warning("no type for favorite id '%s'", uid);
        return FALSE;
    }

    if (g_strcmp0(type, "libgadget-ggadget") != 0)
    {
        g_debug("favorite type '%s' not supported for id '%s'", type, uid);
        g_free(type);
        return FALSE;
    }
    g_free(type);

    return TRUE;
}

static gboolean
_apps_glib_favorite_uri_find(const struct apps_context *ctxt, const char *uid)
{
  gchar *type;

  type = launcher_favorites_get_string(ctxt->item.favs, uid, "type");
  if (!type)
  {
    g_warning("no type for favorite id '%s'", uid);
    return FALSE;
  }

  if (g_strcmp0(type, "uri") != 0)
  {
    g_debug("favorite type '%s' not supported for id '%s'", type, uid);
    g_free(type);
    return FALSE;
  }
  g_free(type);

  return TRUE;
}

#ifdef HAVE_GOOGLE_GADGETS
static gint
_sort_gadget(GadgetInfo *a, GadgetInfo *b)
{
  if (!a)
      return -1;
  if (!b)
      return 1;
  if (G_UNLIKELY(a == b))
      return 0;
  return g_strcmp0(a->name, b->name);
}

static gchar *
_get_icon(const char *icon_name)
{
  gchar *icon, *escaped;

  if (!icon_name)
    return NULL;

  escaped = g_uri_escape_string(icon_name, NULL, TRUE);

  icon = g_build_filename(DATADIR, PACKAGE, "icons", escaped, NULL);
  if (!g_file_test(icon, G_FILE_TEST_EXISTS))
  {
    g_free(icon);
    icon = g_build_filename(DATADIR, "netbook-launcher", "icons", escaped, NULL);
    if (!g_file_test(icon, G_FILE_TEST_EXISTS))
    {
      g_free(icon);
      icon = g_build_filename(g_get_user_cache_dir(), PACKAGE,
          "icons", escaped, NULL);

      if (!g_file_test(icon, G_FILE_TEST_EXISTS))
      {
        // download icon
        GFile *remote = g_file_new_for_uri(icon_name);
        GFile *local = g_file_new_for_path(icon);
        GFile *dir = g_file_get_parent(local);
        g_file_make_directory_with_parents(dir, NULL, NULL);
        if (!g_file_copy(remote, local, G_FILE_COPY_NONE, NULL, NULL, NULL, NULL))
        {
          g_free(icon);
          icon = NULL;
        }
        g_object_unref(remote);
        g_object_unref(local);
        g_object_unref(dir);
      }
    }
  }
  g_free(escaped);
  return icon;
}

static void
_apps_glib_load_gadgets(void *data)
{
  struct apps_context *ctxt = data;
  GList *gadgets, *g;
  Evas_Object *ed;
  GadgetManager *manager;

  ed = elm_layout_edje_get(ctxt->grid);
  if (!edje_object_part_box_remove_all(ed, _EDJE_PART_CONTENTS, 1))
  {
    ERR("could not clear box\n");
    return;
  }
  evas_object_size_hint_min_set(ctxt->grid, 0, 0);

  manager = gadget_manager_get_default();
  if (!manager)
  {
    ERR("No gadget manager\n");
    return;
  }
  /* Just in case the sources haven`t been loaded before */
  gadget_manager_load_sources(manager);

  gadgets = gadget_manager_get_available_gadgets(manager);
  gadgets = g_list_sort(gadgets, (GCompareFunc)_sort_gadget);
  for (g = gadgets; g; g = g->next)
  {
    GadgetInfo *info = g->data;
    Evas_Object *item;
    char *gadget_name = NULL;
    char *icon = NULL;
    char *uid;

    if (!info)
      continue;

    uid = efl_gadget_uid_make();

    /* First check if there is an icon based on the name of the gadget file.
        This might happen if we pre-ship some icons. */
    gadget_name = g_path_get_basename(info->path);
    icon = _get_icon(gadget_name);
    if (!icon && info->icon_name)
      icon = _get_icon(info->icon_name);
    if (!icon && info->icon)
    {
      char *escaped = g_uri_escape_string(gadget_name, NULL, TRUE);
      icon = gstuff_pixbuf_to_file(info->icon, escaped);
      g_free(escaped);
    }
    g_free(gadget_name);

    item = app_gadget_add
      (&ctxt->item, ctxt->category, info->name, info->description,
       icon, info->path, uid, EINA_FALSE);
    g_free(uid);
    g_free(icon);

    if (!item)
      continue;

    edje_object_part_box_append(ed, _EDJE_PART_CONTENTS, item);
  }

  g_list_foreach(gadgets, (GFunc)gadget_info_free, NULL);
  g_list_free(gadgets);
  g_object_unref(manager);
}
#endif

static void
_apps_glib_load_favorites(void *data)
{
  struct apps_context *ctxt = data; /* do not use ctxt->category here! */
  GSList *lst, *n;
  unsigned int len;
  gboolean fav_uri, is_gadget;
  Evas_Object *ed;
  Eina_Bool is_favorite;

  ed = elm_layout_edje_get(ctxt->grid);
  if (!edje_object_part_box_remove_all(ed, _EDJE_PART_CONTENTS, 1))
  {
    ERR("could not clear box\n");
    return;
  }
  evas_object_size_hint_min_set(ctxt->grid, 0, 0);

  lst = launcher_favorites_get_favorites(ctxt->item.favs);
  len = g_slist_length(lst);

  is_favorite = EINA_TRUE;
  for (n = lst; n != NULL; n = n->next)
  {
    Evas_Object *item;
    gchar *uid = n->data;

    is_gadget = _apps_glib_gadget_uri_find(ctxt, uid);
    if (is_gadget) {
#ifdef HAVE_GOOGLE_GADGETS
      gchar *path = launcher_favorites_get_string(ctxt->item.favs,
          uid, "path");
      g_debug("load gadget '%s' '%s'", uid, path);
      item = app_gadget_add(&ctxt->item, NULL, NULL, NULL, NULL, path, uid,
          is_favorite);
      evas_object_show(item);
#else
      item = NULL;
#endif
    } else {
      LauncherApplication *app;
      const char *icon_name, *name, *comment, *app_icon_name, *match_name;

      fav_uri = FALSE;
      app = _apps_glib_favorite_app_find(ctxt, uid);
      if (!app)
      {
        fav_uri = _apps_glib_favorite_uri_find(ctxt, uid);
        if (!fav_uri)
          continue;
      }

      name = fav_uri ? launcher_favorites_get_string(ctxt->item.favs, uid,
          "name") : launcher_application_get_name(app);
      name = !name ? "" : name;
      comment = fav_uri ? launcher_favorites_get_string(ctxt->item.favs, uid,
          "comment") : launcher_application_get_comment(app);
      comment = !comment ? "" : comment;
      app_icon_name = fav_uri ? launcher_favorites_get_string(ctxt->item.favs,
          uid, "icon") : launcher_application_get_icon_name(app);
      match_name = fav_uri ? "firefox" :
        launcher_application_get_unique_string(app);
      match_name = !match_name ? "" : match_name;

      icon_name = icon_path_get(app_icon_name, ctxt->item.item.icon_size);
      if (icon_name)
        app_icon_name = icon_name;

      if (!g_file_test(app_icon_name, G_FILE_TEST_EXISTS)) {
        /* Provide some sort of backup icon */
        const char *backup_name;
        if (strcmp(launcher_favorites_get_string(ctxt->item.favs, uid, "type"), "uri") == 0)
          backup_name = "applications-internet";
        else
          backup_name = "applications-other";
        app_icon_name = icon_path_get(backup_name, ctxt->item.item.icon_size);
      }

      g_debug("load favorite '%s' '%s' '%s' '%s' '%s'",
          name, comment, app_icon_name, match_name, uid);

      item = app_item_add
        (&ctxt->item, ctxt->category, name, comment, app_icon_name, match_name,
         uid, is_favorite);
    }
    if (!item)
        continue;

    edje_object_part_box_append(ed, _EDJE_PART_CONTENTS, item);
  }

  while (lst)
  {
    g_free(lst->data);
    lst = g_slist_delete_link(lst, lst);
  }

  /* Connect each grid item to the edit_mode status.
   * This is achieved by 'forwarding' the _EDJE_SIG_EDIT_MODE_ON and
   * _EDJE_SIG_EDIT_MODE_OFF signals from _EDJE_GROUP_LAYOUT to
   * each _EDJE_GROUP_ITEM.
   */
  edje_object_signal_callback_add(ctxt->edje, _EDJE_SIG_EDIT_MODE_ON,
                                  _EDJE_SIG_SRC, _app_items_emit_signal, ctxt);
  edje_object_signal_callback_add(ctxt->edje, _EDJE_SIG_EDIT_MODE_OFF,
                                  _EDJE_SIG_SRC, _app_items_emit_signal, ctxt);

  _apps_ecore_quit_show(ctxt, is_favorite);
}

static Eina_Bool
_apps_load(struct apps_context *ctxt)
{
  /* use given parameter (copy) and not the ctxt->category from inside glib! */
  app_item_common_navigation_id_set(&ctxt->item, NULL);
#ifdef HAVE_GOOGLE_GADGETS
  if (ctxt->category == ctxt->gadgets_id)
    return gstuff_glib_run_simple(_apps_glib_load_gadgets, ctxt);
  else if (ctxt->category != ctxt->favorites_id)
#else
  if (ctxt->category != ctxt->favorites_id)
#endif
    return gstuff_glib_run_ptr_string
      (_apps_glib_load_category, ctxt, ctxt->category);
  else
    return gstuff_glib_run_simple(_apps_glib_load_favorites, ctxt);
}

static Eina_Bool
_apps_load_internal(struct apps_context *ctxt, const char *category, Eina_Bool animate, void (*end_callback)(void *data, Evas_Object *apps), const void *end_callback_data)
{
  category = eina_stringshare_ref(category);

  if (!animate)
  {
    if (category == ctxt->category)
    {
      eina_stringshare_del(category);
      return 1;
    }

    DBG("immediate replace category '%s' with '%s'\n",
        ctxt->category, category);

    eina_stringshare_del(ctxt->category);
    ctxt->category = category;
    return _apps_load(ctxt);
  }

  if (category == ctxt->pending_category)
  {
    eina_stringshare_del(category);
    return 1;
  } else if (ctxt->pending_category)
    WRN("loading apps category '%s' while other '%s' was pending.\n",
        category, ctxt->pending_category);

  if (ctxt->pending_end_callback)
    ctxt->pending_end_callback(ctxt->pending_end_callback_data, ctxt->apps);
  ctxt->pending_end_callback = end_callback;
  ctxt->pending_end_callback_data = (void *)end_callback_data;

  eina_stringshare_del(ctxt->pending_category);
  ctxt->pending_category = category;

  DBG("animate replace category '%s' with '%s'\n", ctxt->category, category);
  edje_object_signal_emit(ctxt->edje, _EDJE_SIG_EXIT, _EDJE_SIG_SRC);
  return 1;
}

Eina_Bool
apps_load_category(Evas_Object *apps, const char *category, Eina_Bool animate, void (*end_callback)(void *data, Evas_Object *apps), const void *end_callback_data)
{
  struct apps_context *ctxt = _apps_context_get(apps);
  Eina_Bool ret;

  category = eina_stringshare_add(category);
  ret = _apps_load_internal
    (ctxt, category, animate, end_callback, end_callback_data);
  eina_stringshare_del(category);
  return ret;
}

Eina_Bool
apps_load_favorites(Evas_Object *apps, Eina_Bool animate, void (*end_callback)(void *data, Evas_Object *apps), const void *end_callback_data)
{
  struct apps_context *ctxt = _apps_context_get(apps);
  return _apps_load_internal
    (ctxt, ctxt->favorites_id, animate, end_callback, end_callback_data);
}

static void
_apps_box_size_hints_changed(void *data, Evas *e __UNUSED__, Evas_Object *o, void *event_info __UNUSED__)
{
  Evas_Object *grid = data;
  Evas_Coord w, h;
  evas_object_size_hint_min_get(o, &w, &h);
  evas_object_size_hint_min_set(grid, w, h);
  evas_object_resize(grid, w, h);
}

static void
_apps_glib_stop(void *data)
{
  struct apps_context *ctxt = data;

  if (ctxt->watch_dir)
    {
       g_object_unref(ctxt->watch_dir);
       ctxt->watch_dir = NULL;
    }
  if (ctxt->dir_monitor)
    {
       g_object_unref(ctxt->dir_monitor);
       ctxt->dir_monitor = NULL;
    }
  app_item_common_glib_stop(&ctxt->item);
  free(ctxt);
}

static void
_app_ecore_favs_changed(void)
{
  ecore_event_add(FAVORITES_EVENT_CHANGED, NULL, NULL, NULL);
}

static void
_app_ecore_favs_added(void *data)
{
  struct apps_context *ctxt = data;
  edje_object_signal_emit(ctxt->edje, _EDJE_SIG_FAV_ADDED, _EDJE_SIG_SRC);
  _app_ecore_favs_changed();
}

static gchar *
_make_thumbnail_hash(const gchar *name)
{
  gchar *temp;
  gchar *ret;

  temp = g_compute_checksum_for_string(G_CHECKSUM_MD5, name, -1);
  ret = g_strconcat(temp, ".png", NULL);

  g_free (temp);
  return ret;
}

static gchar *
_apps_glib_create_uri_icon(struct apps_context *ctxt, gchar *uid, gchar *name, gchar *uri)
{
  gchar *thumbhash;
  gchar *thumbfile;
  gchar *escaped;
  gchar *cmd;
  gchar *ret;
  gchar *dirpath;
  LauncherFavorites *favs = launcher_favorites_get_default();

  if (!g_str_has_prefix(uri, "http"))
  {
    g_warning("Unable to create thumbnail for a non-http page.\n");
    return NULL;
  }

  escaped = g_markup_escape_text(name, -1);
  thumbhash = _make_thumbnail_hash(escaped);
  dirpath = g_build_filename(g_get_user_cache_dir(), PACKAGE, "webicons", NULL);
  g_mkdir_with_parents(dirpath, 0700);
  thumbfile = g_build_filename(dirpath, thumbhash, NULL);
  cmd = g_strdup_printf("gnome-web-photo --mode=thumbnail \"%s\" %s",
                        uri, thumbfile);
  g_print("cmd: %s\n", cmd);
  if (g_spawn_command_line_sync(cmd, NULL, NULL, NULL, NULL))
    ret = g_strdup(thumbfile);
  else
    ret = NULL;

  g_free(thumbhash);
  g_free(escaped);
  g_free(cmd);
  g_free(dirpath);
  g_free(thumbfile);

  return ret;
}

static void
_apps_glib_on_watch_dir_changed(GFileMonitor *monitor, GFile *file_changed, GFile *other_file, GFileMonitorEvent event, struct apps_context *ctxt)
{
  LauncherFavorites *favs;
  GKeyFile *file;
  gchar *link_path, *name, *url, *uid, *comment, *icon;

  if (event != G_FILE_MONITOR_EVENT_CREATED)
    return;

  link_path = g_file_get_path (file_changed);
  if (!(link_path && g_str_has_suffix(link_path, ".desktop")))
    return;

  file = g_key_file_new();
  if (!g_key_file_load_from_file(file, link_path, 0, NULL))
    {
       g_warning("Unable to open %s, cannot add web favorite", link_path);
       return;
    }

  name = g_key_file_get_string(file, G_KEY_FILE_DESKTOP_GROUP,
                               G_KEY_FILE_DESKTOP_KEY_NAME, NULL);
  url = g_key_file_get_string(file, G_KEY_FILE_DESKTOP_GROUP,
                              G_KEY_FILE_DESKTOP_KEY_URL, NULL);
  comment = g_key_file_get_string(file, G_KEY_FILE_DESKTOP_GROUP,
                                  G_KEY_FILE_DESKTOP_KEY_COMMENT, NULL);
  uid = g_strdup_printf("uri-%d", (gint)time(NULL));
  if (!name)
    name = g_strdup(url);

  icon = _apps_glib_create_uri_icon(ctxt, uid, name, url);

  favs = launcher_favorites_get_default();

  launcher_favorites_set_string(favs, uid, "type", "uri");
  launcher_favorites_set_string(favs, uid, "uri", url);
  launcher_favorites_set_string(favs, uid, "name", name);
  launcher_favorites_set_string(favs, uid, "comment", comment);
  launcher_favorites_set_string(favs, uid, "icon", icon);
  launcher_favorites_add_favorite(favs, uid);

  gstuff_ecore_run_simple(_app_ecore_favs_added, ctxt);


  g_object_unref(favs);
  g_free(link_path);
  g_free(name);
  g_free(url);
  g_free(comment);
  g_free(uid);
  g_free(icon);
}

static void
_apps_watch_links(struct apps_context *ctxt)
{
  GFile *dir;

  g_mkdir_with_parents(LINK_WATCH_DIR, 0700);

  dir = ctxt->watch_dir = g_file_new_for_path(LINK_WATCH_DIR);
  ctxt->dir_monitor = g_file_monitor_directory(dir, 0, NULL, NULL);

  g_signal_connect(ctxt->dir_monitor, "changed",
                   G_CALLBACK(_apps_glib_on_watch_dir_changed), ctxt);
}

static void
_apps_glib_start(void *data)
{
  struct apps_context *ctxt = data;
  app_item_common_glib_start(&ctxt->item);
  ctxt->quit_icon = icon_unr_path_get(_ICON_QUIT, ctxt->item.item.icon_size);
  _apps_watch_links(ctxt);
}

static void
_apps_del(void *data, Evas *e __UNUSED__, Evas_Object *o __UNUSED__, void *event_info __UNUSED__)
{
  struct apps_context *ctxt = data;
  eina_stringshare_del(ctxt->favorites_id);
  eina_stringshare_del(ctxt->gadgets_id);
  eina_stringshare_del(ctxt->category);
  eina_stringshare_del(ctxt->pending_category);

  if (ctxt->handler.favorites_changed)
    ecore_event_handler_del(ctxt->handler.favorites_changed);

  app_item_common_ecore_stop(&ctxt->item);
  gstuff_glib_run_simple(_apps_glib_stop, ctxt);
}

static void
_apps_enter_end(void *data, Evas_Object *o __UNUSED__, const char *emission __UNUSED__, const char *source __UNUSED__)
{
  struct apps_context *ctxt = data;
  DBG("apps finish enter animation, ready!\n");

  if (ctxt->pending_end_callback)
    ctxt->pending_end_callback(ctxt->pending_end_callback_data, ctxt->apps);
  ctxt->pending_end_callback = NULL;
  ctxt->pending_end_callback_data = NULL;

  eina_stringshare_del(ctxt->pending_category);
  ctxt->pending_category = NULL;
}

static void
_apps_exit_end(void *data, Evas_Object *o __UNUSED__, const char *emission __UNUSED__, const char *source __UNUSED__)
{
  struct apps_context *ctxt = data;
  DBG("apps finish exit animation, load '%s'!\n", ctxt->pending_category);
  eina_stringshare_del(ctxt->category);
  ctxt->category = eina_stringshare_ref(ctxt->pending_category);
  _apps_load(ctxt);
  DBG("apps enter animation...\n");
  edje_object_signal_emit(ctxt->edje, _EDJE_SIG_ENTER, _EDJE_SIG_SRC);
}

static int
_apps_favorites_changed(void *data, int type __UNUSED__, void *event __UNUSED__)
{
  struct apps_context *ctxt = data;
  if (ctxt->category == ctxt->favorites_id)
    gstuff_glib_run_simple(_apps_glib_load_favorites, ctxt);
  return 1;
}

static void
_apps_focus_key_down(void *data, Evas *e __UNUSED__, Evas_Object *o __UNUSED__, void *event_info __UNUSED__)
{
  struct apps_context *ctxt = data;
  edje_object_signal_emit(ctxt->edje, _EDJE_SIG_FOCUS_IN, _EDJE_SIG_SRC);
}

Evas_Object *
apps_add(Evas_Object *parent)
{
  struct apps_context *ctxt;
  Evas_Object *apps, *scroller, *grid, *ed, *box;
  const char *s;
  Eina_Bool w = 1, h = 1;

  if (FAVORITES_EVENT_CHANGED == 0)
    FAVORITES_EVENT_CHANGED = ecore_event_type_new();

  apps = elm_layout_add(parent);
  if (!apps)
  {
    ERR("could not create apps layout widget.\n");
    return NULL;
  }

  ed = elm_layout_edje_get(apps);
  if (!elm_layout_file_set(apps, _nl_prefs.theme, _EDJE_GROUP_LAYOUT))
  {
    int err = edje_object_load_error_get(ed);
    const char *errmsg = edje_load_error_str(err);
    ERR("cannot load theme '%s', group '%s': %s\n",
        _nl_prefs.theme, _EDJE_GROUP_LAYOUT, errmsg);
    goto error;
  }

  edje_object_signal_callback_add
    (ed, _EDJE_SIG_QUIT_EXECUTED, _EDJE_SIG_SRC, _apps_quit_executed, apps);

  scroller = elm_scroller_add(apps);
  if (!scroller)
  {
    ERR("could not createapps scroller.\n");
    goto error;
  }
  elm_object_style_set(scroller,  "apps");
  elm_layout_content_set(apps, _EDJE_PART_SCROLLER, scroller);

  grid = elm_layout_add(apps);
  if (!grid)
  {
    ERR("could not create apps layout widget.\n");
    goto error;
  }

  ed = elm_layout_edje_get(grid);
  if (!elm_layout_file_set(grid, _nl_prefs.theme, _EDJE_GROUP_GRID))
  {
    int err = edje_object_load_error_get(ed);
    const char *errmsg = edje_load_error_str(err);
    ERR("cannot load theme '%s', group '%s': %s\n",
        _nl_prefs.theme, _EDJE_GROUP_GRID, errmsg);
    goto error;
  }

  box = (Evas_Object *)edje_object_part_object_get(ed, _EDJE_PART_CONTENTS);
  evas_object_event_callback_add
    (box, EVAS_CALLBACK_CHANGED_SIZE_HINTS, _apps_box_size_hints_changed, grid);

  elm_scroller_content_set(scroller, grid);

  ctxt = calloc(1, sizeof(*ctxt));
  if (!ctxt)
  {
    ERR("could not allocate apps context.\n");
    goto error;
  }
  ctxt->apps = apps;
  ctxt->edje = elm_layout_edje_get(apps);
  ctxt->scroller = scroller;
  ctxt->grid = grid;
  ctxt->favorites_id = eina_stringshare_add(_favorites_id);
  ctxt->gadgets_id = eina_stringshare_add(_(_gadgets_id));

  _apps_context_set(apps, ctxt);

  app_item_common_ecore_start(&ctxt->item, apps, scroller, grid);

  edje_object_signal_callback_add
    (ctxt->edje, _EDJE_SIG_ENTER_END, _EDJE_SIG_SRC, _apps_enter_end, ctxt);
  edje_object_signal_callback_add
    (ctxt->edje, _EDJE_SIG_EXIT_END, _EDJE_SIG_SRC, _apps_exit_end, ctxt);

  evas_object_event_callback_add(apps, EVAS_CALLBACK_FREE, _apps_del, ctxt);
  evas_object_event_callback_add
    (apps, EVAS_CALLBACK_KEY_DOWN, _apps_focus_key_down, ctxt);

  /* Add item callback connection */
  edje_object_signal_callback_add
    (ctxt->edje, _EDJE_SIG_ADD_ITEM_EXECUTED, _EDJE_SIG_SRC,
     _apps_add_item_executed, apps);

  s = edje_object_data_get(ed, "scroll_horizontal");
  if (s && (strcmp(s, "off") == 0))
      w = 0;
  s = edje_object_data_get(ed, "scroll_vertical");
  if (s && (strcmp(s, "off") == 0))
      h = 0;

  elm_scroller_bounce_set(scroller, w, h);

  ctxt->handler.favorites_changed = ecore_event_handler_add
    (FAVORITES_EVENT_CHANGED, _apps_favorites_changed, ctxt);

  gstuff_glib_run_simple(_apps_glib_start, ctxt);

  return apps;

error:
  evas_object_del(apps);
  return NULL;
}
