/*****************************************************************
 * gmerlin - a general purpose multimedia framework and applications
 *
 * Copyright (c) 2001 - 2012 Members of the Gmerlin project
 * gmerlin-general@lists.sourceforge.net
 * http://gmerlin.sourceforge.net
 *
 * This program 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.
 *
 * This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.
 * *****************************************************************/

#include <ctype.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <dirent.h>
#include <glob.h>
#include <errno.h>

#include <config.h>

#include <gmerlin/translation.h>
#include <gmerlin/log.h>
#define LOG_DOMAIN "mdb.filesystem"

#include <gmerlin/mdb.h>
#include <gmerlin/backend.h>

#include <mdb_private.h>

#include <gmerlin/utils.h>

#include <gavl/metatags.h>

#define EXTFS_ID_PREFIX "/extfs"

/* ID From the volume manager */

#define VOLUME_ID        "volume_id"
#define VOLUME_CONTAINER "volume_container"

// #define BACKEND_NAME     "fs"

typedef struct
  {
  //  gavl_dictionary_t * dict;

  gavl_dictionary_t * state;
  gavl_array_t * dirs;

  gavl_array_t extfs;
  int64_t extfs_counter;
  
  gavl_dictionary_t * container;
  
  const char * root_id;
  int have_params;

  int mount_removable;
  int mount_audiocd;
  int mount_videodisk;
  
  } fs_t;

static void destroy_filesystem(bg_mdb_backend_t * b)
  {
  fs_t * fs = b->priv;
  gavl_array_free(&fs->extfs);
  free(fs);
  }


/*
 *   For fixed directories, the DB id
 *   /dirs/<dir-label>/<rel_path>
 *
 *   becomes
 *   <dir-path>/<rel_path>
 *
 *   For removable directories,
 *   /extfs-1/<rel_path>
 *
 *   becomes
 *   <extfs-mountpoint>/<rel_path>
 *
 *
 *   Multitrack files are reported as containers
 *   where the ID of the tracks is:
 *   <parent_id>/?track=1
 *   
 */

static char * id_to_path(const bg_mdb_backend_t * be, const char * id)
  {
  int i;
  const gavl_dictionary_t * d;
  
  fs_t * p = be->priv;

  /* configured directories */
  if(gavl_string_starts_with(id, p->root_id))
    {
    id += strlen(p->root_id);

    if(*id != '/')
      return NULL;

    id++;
  
    for(i = 0; i < p->dirs->num_entries; i++)
      {
      int label_len;
      const char * label;
      
      if((d = gavl_value_get_dictionary(&p->dirs->entries[i])) &&
         (label = gavl_dictionary_get_string(d, GAVL_META_LABEL)) &&
         (label_len = strlen(label)) &&
         !strncmp(label, id, label_len) &&
         ((id[label_len] == '/') || (id[label_len] == '\0')))
        {
        if(id[label_len] == '/')
          return bg_sprintf("%s/%s",
                            gavl_dictionary_get_string(d, GAVL_META_URI),
                            id + label_len + 1);
        else if(id[label_len] == '\0')
          return gavl_strdup(gavl_dictionary_get_string(d, GAVL_META_URI));
        }
      }
    }
  
  /* Removable drive or disk */
  else if(gavl_string_starts_with(id, EXTFS_ID_PREFIX))
    {
    int volume_id_len;
    const char * volume_id;
    const char * uri;
    
    //    fprintf(stderr, "id_to_path %s\n", id);
    for(i = 0; i < p->extfs.num_entries; i++)
      {
      if((d = gavl_value_get_dictionary(&p->extfs.entries[i])) &&
         (volume_id = gavl_dictionary_get_string(d, GAVL_META_ID)) &&
         (volume_id_len = strlen(volume_id)) &&
         !strncmp(volume_id, id, volume_id_len) &&
         (uri = gavl_dictionary_get_string(d, GAVL_META_URI)) &&
         ((id[volume_id_len] == '/') || (id[volume_id_len] == '\0')))
        {
        if(id[volume_id_len] == '/')
          {
          if(strstr(uri, "://")) // Removable
            {
            int track;
            char * ret;

            if(isdigit(id[volume_id_len+1]) && ((track = atoi(id + volume_id_len + 1)) > 0))
              {
              gavl_dictionary_t vars;
              gavl_dictionary_init(&vars);

              gavl_dictionary_set_int(&vars, BG_URL_VAR_TRACK, track);
              ret = bg_url_append_vars(gavl_strdup(uri), &vars);
              gavl_dictionary_free(&vars);
              return ret;
              }
            }
          else
            return bg_sprintf("%s/%s",
                            gavl_dictionary_get_string(d, GAVL_META_URI),
                            id + volume_id_len + 1);
          }
        else if(id[volume_id_len] == '\0')
          return gavl_strdup(uri);
        }
      }
    }
  return NULL;
  }

static char * path_to_id(const bg_mdb_backend_t * be, const char * path)
  {
  int i;
  const gavl_dictionary_t * d;
  int dir_uri_len;
  const char * dir_uri;
  
  fs_t * p = be->priv;
  
  for(i = 0; i < p->dirs->num_entries; i++)
    {
    if((d = gavl_value_get_dictionary(&p->dirs->entries[i])) &&
       (dir_uri = gavl_dictionary_get_string(d, GAVL_META_URI)) &&
       (dir_uri_len = strlen(dir_uri)) &&
       !strncmp(dir_uri, path, dir_uri_len))
      {
      if(path[dir_uri_len] == '/')
        return bg_sprintf("%s/%s/%s",
                          p->root_id,
                          gavl_dictionary_get_string(d, GAVL_META_LABEL),
                          path + (dir_uri_len + 1));
      else if(path[dir_uri_len] == '\0')
        return bg_sprintf("%s/%s", p->root_id,
                          gavl_dictionary_get_string(d, GAVL_META_LABEL));
      }
    }

  for(i = 0; i < p->extfs.num_entries; i++)
    {
    if((d = gavl_value_get_dictionary(&p->extfs.entries[i])) &&
       (dir_uri = gavl_dictionary_get_string(d, GAVL_META_URI)) &&
       (dir_uri_len = strlen(dir_uri)) &&
       !strncmp(dir_uri, path, dir_uri_len))
      {
      if(strstr(dir_uri, "://"))
        {
        int track = 0;
        char * ret = NULL;
        gavl_dictionary_t url_vars;

        gavl_dictionary_init(&url_vars);

        bg_url_get_vars_c(path, &url_vars);
        if(gavl_dictionary_get_int(&url_vars, BG_URL_VAR_TRACK, &track) && (track > 0))
          ret = bg_sprintf("%s/%d", gavl_dictionary_get_string(d, GAVL_META_ID), track);
        else
          ret = gavl_strdup(gavl_dictionary_get_string(d, GAVL_META_ID));
        
        gavl_dictionary_free(&url_vars);
        return ret;
        }
      
      if(path[dir_uri_len] == '/')
        return bg_sprintf("%s/%s",
                          gavl_dictionary_get_string(d, GAVL_META_ID),
                          path + (dir_uri_len + 1));
      else if(path[dir_uri_len] == '\0')
        return gavl_strdup(gavl_dictionary_get_string(d, GAVL_META_ID));
      }
    }
  return NULL;
  }

static gavl_dictionary_t * get_volume_container(bg_mdb_backend_t * b, gavl_dictionary_t * volume)
  {
  int i, num;
  gavl_dictionary_t * ret;
  gavl_dictionary_t * ret_m;
  gavl_dictionary_t * dict;
  gavl_dictionary_t * m;
  const char * uri;
  char * id;
  
  ret = gavl_dictionary_get_dictionary_nc(volume, VOLUME_CONTAINER);

  if(ret)
    return ret;

  uri = gavl_dictionary_get_string(volume, GAVL_META_URI);
  
  ret = bg_plugin_registry_load_media_info(bg_plugin_reg, uri, 0);

  if(!ret)
    return NULL;
  
  id = path_to_id(b, uri);

  ret_m = gavl_dictionary_get_dictionary_create(ret, GAVL_META_METADATA);
  gavl_dictionary_set_string(ret_m, GAVL_META_ID, id);
  
  num = gavl_get_num_tracks(ret);

  for(i = 0; i < num; i++)
    {
    if((dict = gavl_get_track_nc(ret, i)) &&
       (m = gavl_track_get_metadata_nc(dict)))
      {
      gavl_dictionary_set_string_nocopy(m, GAVL_META_ID, bg_sprintf("%s/?track=%d", id, i+1));
      bg_mdb_add_http_uris(b->db, dict);
      }
    }
#if 0  
  fprintf(stderr, "Loaded disk\n");
  gavl_dictionary_dump(ret, 2);
  fprintf(stderr, "\n");
#endif
  gavl_dictionary_set_dictionary_nocopy(volume, VOLUME_CONTAINER, ret);

  free(id);
  
  return gavl_dictionary_get_dictionary_nc(volume, VOLUME_CONTAINER);
  }


static gavl_dictionary_t * volume_by_uri(bg_mdb_backend_t * be, const char * uri)
  {
  int i;
  const char * volume_uri;
  gavl_dictionary_t * d;
  fs_t * p = be->priv;
  
  for(i = 0; i < p->extfs.num_entries; i++)
    {
    if((d = gavl_value_get_dictionary_nc(&p->extfs.entries[i])) &&
       (volume_uri = gavl_dictionary_get_string(d, GAVL_META_URI)) &&
       !strcmp(volume_uri, uri))
      {
      return d;
      }
    }
  return NULL;
  }

static int browse_object(gavl_dictionary_t * ret,
                         bg_mdb_backend_t * be, const char * path)
  {
  const char * pos;
  int result = 0;
  gavl_dictionary_t * m;
  time_t mtime;
  int num_items = 0;
  int num_containers = 0;
  
  struct stat st;
  struct stat st1;
  char * path_priv = NULL;
  int track = -1;
  
  //  fprintf(stderr, "mdb_filesystem: browse_object %s\n", path);
  
  if(strstr(path, "://")) /* Device */
    {
    int track = 0;
    char * base_path = NULL;
    gavl_dictionary_t * volume;
    gavl_dictionary_t * container;
    gavl_dictionary_t url_vars;

    gavl_dictionary_init(&url_vars);
    base_path = gavl_strdup(path);
    bg_url_get_vars(base_path, &url_vars);

    if(!(volume = volume_by_uri(be, base_path)) ||
       !(container = get_volume_container(be, volume)))
      goto end;
    
    /* Find volume */
    
    if(gavl_dictionary_get_int(&url_vars, BG_URL_VAR_TRACK, &track))
      {
      track--;
      gavl_dictionary_copy(ret, gavl_get_track(container, track));
      }
    else
      {
      gavl_dictionary_copy(ret, container);
      gavl_dictionary_set(ret, GAVL_META_CHILDREN, NULL);
      }
    free(base_path);
    gavl_dictionary_free(&url_vars);
    // result = 1;
    goto end;
    }

  /* Multitrack file */
  
  if((pos = strrchr(path, '/')) &&
     gavl_string_starts_with(pos, "/?track=") &&
     ((track = atoi(pos + 8)) > 0))
    {
    /* Multi track file */
    path_priv = gavl_strdup(path);
    path_priv[pos - path] = '\0';
    path = path_priv;
    }

  // fprintf(stderr, "Browse object %s %d\n", path, track);
  
  if(stat(path, &st))
    goto end;
  
  if(S_ISDIR(st.st_mode))
    {
    glob_t g;
    char * pattern;
    int i;
    
    mtime = st.st_mtime;

    pattern = bg_sprintf("%s/*", path);

    pattern = gavl_escape_string(pattern, "[]?");
    
    glob(pattern, 0, NULL /* errfunc */, &g);

    
    for(i = 0; i < g.gl_pathc; i++)
      {
      if(stat(g.gl_pathv[i], &st1))
        break;
      
      if(st1.st_mtime > mtime)
        mtime = st1.st_mtime;

      if(S_ISDIR(st1.st_mode))
        {
        num_containers++;
        }
      else if(S_ISREG(st1.st_mode))
        {
        gavl_dictionary_t * mi;
        int num_tracks;
        
        mi = bg_plugin_registry_load_media_info(bg_plugin_reg, g.gl_pathv[i], 0);
        
        if(!mi)
          num_items++;
        else
          {
          num_tracks = gavl_get_num_tracks(mi);

          if(num_tracks > 1)
            num_containers++;
          else if(num_tracks == 1)
            num_items++;
          
          gavl_dictionary_destroy(mi);
          }
        }
      }
        
    globfree(&g);
    
    /* Create return value */
  
    m = gavl_dictionary_get_dictionary_create(ret, GAVL_META_METADATA);
    
    gavl_dictionary_set_string(m, GAVL_META_MEDIA_CLASS,
                               GAVL_META_MEDIA_CLASS_DIRECTORY);
    
    if((pos = strrchr(path, '/')))
      gavl_dictionary_set_string(m, GAVL_META_LABEL, pos + 1);
    
    gavl_dictionary_set_string_nocopy(m, GAVL_META_ID, path_to_id(be, path));
    
    gavl_track_set_num_children(ret, num_containers, num_items);
    gavl_dictionary_set_long(m, GAVL_META_MTIME, mtime);
    }
  else if(S_ISREG(st.st_mode))
    {
    gavl_dictionary_t * mi;
    int num_tracks;
    mi = bg_plugin_registry_load_media_info(bg_plugin_reg, path, 0);
    
    // gavl_get_track(const gavl_dictionary_t * dict, int idx);
    
    if(!mi)
      {
      /* File couldn't be opened */
      m = gavl_dictionary_get_dictionary_create(ret, GAVL_META_METADATA);
      gavl_dictionary_set_string(m, GAVL_META_MEDIA_CLASS,
                                 GAVL_META_MEDIA_CLASS_FILE);
      
      if((pos = strrchr(path, '/')))
        gavl_dictionary_set_string(m, GAVL_META_LABEL, pos + 1);
      
      gavl_dictionary_set_string_nocopy(m, GAVL_META_ID, path_to_id(be, path));
      result = 1;
      goto end;
      }
    
    num_tracks = gavl_get_num_tracks(mi);
    
    if(num_tracks == 1)
      {
      gavl_dictionary_copy(ret, gavl_get_track(mi, 0));
      bg_mdb_add_http_uris(be->db, ret);
      }
    else if(num_tracks > 1)
      {
      if(track > 0)
        {
        gavl_dictionary_copy(ret, gavl_get_track(mi, track-1));
        }
      else
        {
        gavl_dictionary_copy(ret, mi);
        gavl_dictionary_set(ret, GAVL_META_CHILDREN, NULL);
        }
      }
    
    m = gavl_track_get_metadata_nc(ret);
    gavl_dictionary_set_string_nocopy(m, GAVL_META_ID, path_to_id(be, path));
    
    if(mi)
      gavl_dictionary_destroy(mi);
    }
  
  result = 1;
  end:
  
  if(path_priv)
    free(path_priv);
  
  return result;
  }

static void browse_children(bg_mdb_backend_t * be, const char * path,
                            gavl_msg_t * msg, const char * ctx_id, int start, int num)
  {
  struct stat st;
  
  //  fprintf(stderr, "browse_children %s\n", path);

  if(strstr(path, "://"))
    {
    gavl_dictionary_t * volume;
    gavl_dictionary_t * container;
    char * base_path = NULL;

    gavl_dictionary_t url_vars;

    gavl_array_t * arr;
    gavl_msg_t * res;

    gavl_dictionary_init(&url_vars);
    base_path = gavl_strdup(path);
    bg_url_get_vars(base_path, &url_vars);
    
    /* Find volume */
    if(!(volume = volume_by_uri(be, base_path)) ||
       !(container = get_volume_container(be, volume)) ||
       !(arr = gavl_get_tracks_nc(container)))
      return;

    if(!bg_mdb_adjust_num(start, &num, arr->num_entries))
      return;
    
    res = bg_msg_sink_get(be->ctrl.evt_sink);

    if(num < arr->num_entries)
      {
      int i;
      gavl_array_t tmp_arr;
      gavl_array_init(&tmp_arr);
      
      /* Range */

      for(i = 0; i < num; i++)
        gavl_array_splice_val(&tmp_arr, i, 0, &arr->entries[i+start]);
      
      bg_mdb_set_browse_children_response(res, &tmp_arr, msg, &start, 1, arr->num_entries);
      gavl_array_free(&tmp_arr);
      }
    else
      bg_mdb_set_browse_children_response(res, arr, msg, &start, 1, arr->num_entries);
    
    bg_msg_sink_put(be->ctrl.evt_sink, res);
    
    gavl_dictionary_free(&url_vars);
    free(base_path);
    return;
    }
  
  if(stat(path, &st))
    return;

  if(S_ISDIR(st.st_mode))
    {
    /* Directory */
    int i;
    char * pattern;
    glob_t g;
    gavl_dictionary_t * obj;
    gavl_array_t * arr = NULL;
    
    gavl_time_t time_msg = 0;
    gavl_time_t current_time = 0;
    gavl_time_t start_time = gavl_timer_get(be->db->timer);
    int do_flush = 0;

    int idx = start;
    
    memset(&g, 0, sizeof(g));

    pattern = bg_sprintf("%s/*", path);

    pattern = gavl_escape_string(pattern, "[]?");
    
    glob(pattern, 0, NULL /* errfunc */, &g);

    if(num < 0)
      do_flush = 1;
    
    if(!bg_mdb_adjust_num(start, &num, g.gl_pathc))
      return;

    for(i = 0; i < num; i++)
      {
      gavl_value_t val;
      
      gavl_value_init(&val);
      obj = gavl_value_set_dictionary(&val);
      
      if((browse_object(obj, be, g.gl_pathv[i + start])))
        {
        if(!arr)
          arr = gavl_array_create();
        
        obj = NULL;
        
        gavl_array_splice_val_nocopy(arr, -1, 0, &val);

        if(do_flush)
          {
          current_time = gavl_timer_get(be->db->timer) - start_time;
        
          if(current_time - time_msg > GAVL_TIME_SCALE)
            {
            int last = 0;
            /* Flush messages */
            gavl_msg_t * res = bg_msg_sink_get(be->ctrl.evt_sink);

            if(idx + arr->num_entries == num)
              last = 1;
            
            bg_mdb_set_browse_children_response(res, arr, msg, &idx, last, g.gl_pathc);

            bg_msg_sink_put(be->ctrl.evt_sink, res);

            time_msg = current_time;
          
            gavl_array_destroy(arr);
            arr = NULL;
            }
          }
        
        }
      else
        gavl_value_free(&val);
      }

    if(arr)
      {
      /* Flush messages */
      gavl_msg_t * res = bg_msg_sink_get(be->ctrl.evt_sink);
      bg_mdb_set_browse_children_response(res, arr, msg, &idx, 1, g.gl_pathc);
      bg_msg_sink_put(be->ctrl.evt_sink, res);
      gavl_array_destroy(arr);
      }
    globfree(&g);
    }
  else if(S_ISREG(st.st_mode))     /* Multitrack file */
    {
    gavl_dictionary_t * mi;
    gavl_msg_t * res;
    int i;
    char * parent_id;

    gavl_array_t arr;
    
    
    const gavl_array_t * children;
    
    mi = bg_plugin_registry_load_media_info(bg_plugin_reg, path, 0);

    children = gavl_get_tracks(mi);
    
    parent_id = path_to_id(be, path);

    if(!bg_mdb_adjust_num(start, &num, children->num_entries))
      {
      gavl_dictionary_destroy(mi);
      return;
      }
    
    gavl_array_init(&arr);
    
    for(i = 0; i < num; i++)
      {
      gavl_dictionary_t * dict = gavl_value_get_dictionary_nc(&children->entries[i + start]);
      
      gavl_track_set_id_nocopy(dict,
                               bg_sprintf("%s/?track=%d", parent_id, i+1+start));
      
      bg_mdb_add_http_uris(be->db, dict);
      gavl_array_splice_val(&arr, -1, 0, &children->entries[i + start]);
      }
    free(parent_id);
    
    res = bg_msg_sink_get(be->ctrl.evt_sink);
    bg_mdb_set_browse_children_response(res, &arr, msg, &start, 1, children->num_entries);
    bg_msg_sink_put(be->ctrl.evt_sink, res);
    gavl_dictionary_destroy(mi);
    gavl_array_free(&arr);
    }
  
  }

static int handle_msg(void * priv, gavl_msg_t * msg)
  {
  bg_mdb_backend_t * be = priv;
  fs_t * fs = be->priv;
  
  switch(msg->NS)
    {
    case BG_MSG_NS_DB:
      {
      switch(msg->ID)
        {
        case BG_FUNC_DB_BROWSE_OBJECT:
          {
          gavl_msg_t * res;
          gavl_value_t val;
          gavl_dictionary_t * obj;

          const char * ctx_id = gavl_dictionary_get_string(&msg->header,
                                                           GAVL_MSG_CONTEXT_ID);
          char * path = id_to_path(be, ctx_id);
          
          obj = gavl_value_set_dictionary(&val);
          
          browse_object(obj, be, path);

          res = bg_msg_sink_get(be->ctrl.evt_sink);
          
          bg_mdb_set_browse_obj_response(res, obj, msg);
          
          bg_msg_sink_put(be->ctrl.evt_sink, res);

          if(path)
            free(path);
          
          }
          break;
        case BG_FUNC_DB_BROWSE_CHILDREN:
          {
          const char * ctx_id;
          int start, num;
          
          //          fprintf(stderr, "Browse children:\n");
          //          gavl_msg_dump(msg, 2);

          bg_mdb_get_browse_children_request(msg, &ctx_id, &start, &num);
          
          /* /dirs */
          if(!strcmp(ctx_id, fs->root_id))
            {
            const gavl_dictionary_t * dir;
            gavl_array_t arr;
            int i;
            
            gavl_array_init(&arr);

            if(!bg_mdb_adjust_num(start, &num, fs->dirs->num_entries))
              return 1;
            
            for(i = 0; i < num; i++)
              {
              if((dir = gavl_value_get_dictionary(&fs->dirs->entries[start + i])))
                {
                gavl_value_t val;
                gavl_dictionary_t * track;
                gavl_dictionary_t * m;
                char * id;
                char * path;
                
                id = bg_sprintf("%s/%s", fs->root_id,
                                gavl_dictionary_get_string(dir, GAVL_META_LABEL));
                path = id_to_path(be, id);
                
                gavl_value_init(&val);
                track = gavl_value_set_dictionary(&val);

                // fprintf(stderr, "Path: %s\n", path);
                
                browse_object(track, be, gavl_dictionary_get_string(dir, GAVL_META_URI));
                
                m = gavl_dictionary_get_dictionary_create(track, GAVL_META_METADATA);
                
                gavl_dictionary_set_string_nocopy(m, GAVL_META_ID, id);
                
                gavl_dictionary_set_string(m, GAVL_META_LABEL, gavl_dictionary_get_string(dir, GAVL_META_LABEL));

                gavl_array_splice_val_nocopy(&arr, i, 0, &val);
                free(path);
                path = NULL;
                }
              }

            if(arr.num_entries)
              {
              gavl_msg_t * res;
              res = bg_msg_sink_get(be->ctrl.evt_sink);

              bg_mdb_set_browse_children_response(res, &arr, msg, &start, 1, fs->dirs->num_entries);

              //              fprintf(stderr, "Browse children response:\n");
              //              gavl_msg_dump(res, 2);

              bg_msg_sink_put(be->ctrl.evt_sink, res);
              }
            gavl_array_free(&arr);
            }
          else
            {
            char * path = id_to_path(be, ctx_id);
            //            gavl_log(GAVL_LOG_INFO, LOG_DOMAIN, "BG_CMD_DB_BROWSE_CHILDREN %s %s", ctx_id, path);
            browse_children(be, path, msg, ctx_id, start, num);
            free(path);
            }
          }
          break;
        }
      break;
      }
    case BG_MSG_NS_VOLUMEMANAGER:
      switch(msg->ID)
        {
        case BG_MSG_ID_VOLUME_ADDED:
          {
          gavl_value_t vol_store_val;
          gavl_dictionary_t * vol_store;
          
          gavl_dictionary_t obj;
          gavl_dictionary_t vol;
          gavl_dictionary_t * m;
          const char * id;
          const char * uri;
          const char * klass;
          
          gavl_dictionary_init(&vol);
          gavl_dictionary_init(&obj);
          
          id = gavl_msg_get_arg_string_c(msg, 0);
          gavl_msg_get_arg_dictionary(msg, 1, &vol);

          if(!(klass = gavl_dictionary_get_string(&vol, GAVL_META_MEDIA_CLASS)))
            return 1;
          
          if(gavl_string_starts_with(klass, GAVL_META_MEDIA_CLASS_ROOT_REMOVABLE_FILESYSTEM) &&
             !fs->mount_removable)
            {
            return 1;
            }
          if(!strcmp(klass, GAVL_META_MEDIA_CLASS_ROOT_REMOVABLE_AUDIOCD) &&
             !fs->mount_audiocd)
            {
            //  fprintf(stderr, "Not adding audio CD\n");
            return 1;
            }
          if((!strcmp(klass, GAVL_META_MEDIA_CLASS_ROOT_REMOVABLE_VCD) ||
              !strcmp(klass, GAVL_META_MEDIA_CLASS_ROOT_REMOVABLE_SVCD) ||
              !strcmp(klass, GAVL_META_MEDIA_CLASS_ROOT_REMOVABLE_VIDEODVD)) &&
             !fs->mount_videodisk)
            return 1;
          
          uri = gavl_dictionary_get_string(&vol, GAVL_META_URI);
          
          /* Store locally */
          gavl_value_init(&vol_store_val);
          vol_store = gavl_value_set_dictionary(&vol_store_val);
          
          gavl_dictionary_set_string_nocopy(vol_store, GAVL_META_ID, bg_sprintf(EXTFS_ID_PREFIX"-%"PRId64,
                                                                                ++fs->extfs_counter));
          
          gavl_dictionary_set_string(vol_store, GAVL_META_URI, uri);
          gavl_dictionary_set_string(vol_store, VOLUME_ID, id);
          
          gavl_array_splice_val_nocopy(&fs->extfs, -1, 0, &vol_store_val);
          /* */
          
          browse_object(&obj, be, gavl_dictionary_get_string(&vol, GAVL_META_URI));
          m = gavl_dictionary_get_dictionary_create(&obj, GAVL_META_METADATA);

          /* Keep the label */
          gavl_dictionary_set(&vol, GAVL_META_LABEL, NULL);
          gavl_dictionary_update_fields(m, &vol);
          
          bg_mdb_container_set_backend(&obj, MDB_BACKEND_FILESYSTEM);
          
          //          gavl_log(GAVL_LOG_INFO, LOG_DOMAIN, "Volume added %s", id); 

          bg_mdb_add_root_container(be->ctrl.evt_sink, &obj);


          if(*uri == '/')
            bg_mdb_export_media_directory(be->ctrl.evt_sink, gavl_dictionary_get_string(&vol, GAVL_META_URI));
          
          gavl_dictionary_free(&obj);
          gavl_dictionary_free(&vol);
          }
          break;
        case BG_MSG_ID_VOLUME_REMOVED:
          {
          int i;
          const char * test_id;
          const char * id;
          const gavl_dictionary_t * d;
          
          id = gavl_msg_get_arg_string_c(msg, 0);
          
          for(i = 0; i < fs->extfs.num_entries; i++)
            {
            if((d = gavl_value_get_dictionary(&fs->extfs.entries[i])) &&
               (test_id = gavl_dictionary_get_string(d, VOLUME_ID)) &&
               !strcmp(test_id, id))
              {
              gavl_log(GAVL_LOG_INFO, LOG_DOMAIN, "Volume removed %s", id); 
              
              /* Send message */
              bg_mdb_delete_root_container(be->ctrl.evt_sink, gavl_dictionary_get_string(d, GAVL_META_ID));
              
              bg_mdb_unexport_media_directory(be->ctrl.evt_sink, gavl_dictionary_get_string(d, GAVL_META_URI));
              
              /* Remove locally */
              gavl_array_splice_val(&fs->extfs, i, 1, NULL);
              break;
              }
            }
          break;
          }
        }
      break;
    case GAVL_MSG_NS_GENERIC:
      switch(msg->ID)
        {
        case GAVL_CMD_QUIT:
          return 0;
          break;
        }
      break;
    case BG_MSG_NS_PARAMETER:
      switch(msg->ID)
        {
        case BG_MSG_SET_PARAMETER:
          {
          gavl_msg_t * resp;
          const char * name = NULL;
          gavl_value_t val;
          

          gavl_value_init(&val);

          bg_msg_get_parameter(msg, &name, &val);
          
          if(!name)
            {
            fs->have_params = 1;
            return 1;
            }
          
          if(!strcmp(name, "dirs"))
            {
            int num_added = 0;
            int num_deleted = 0;
            
            const char * dir;
            int i;
            const gavl_array_t * arr = gavl_value_get_array(&val);

            /* Check whether to add dirs */
            
            for(i = 0; i < arr->num_entries; i++)
              {
              dir = gavl_string_array_get(arr, i);

              if(bg_mdb_has_dir_array(fs->dirs, dir))
                {
                if(!fs->have_params)
                  bg_mdb_export_media_directory(be->ctrl.evt_sink, dir);
                continue;
                }
              else if(bg_is_directory(dir, 0))
                {
                gavl_dictionary_t cont;
                gavl_dictionary_init(&cont);
                
                gavl_log(GAVL_LOG_INFO, LOG_DOMAIN, "Added %s", dir);
                
                browse_object(&cont, be, dir);
                
                bg_mdb_add_dir_array(fs->dirs, dir, &cont, gavl_track_get_id(fs->container), be->ctrl.evt_sink);
                
                gavl_dictionary_free(&cont);
                
                bg_mdb_export_media_directory(be->ctrl.evt_sink, dir);
                num_added++;
                }
              else
                {
                gavl_log(GAVL_LOG_ERROR, LOG_DOMAIN, "Cannot add %s as directory", dir);
                }
              
              }
            
            /* Check whether to remove dirs */
            
            i = 0;

            while(i < fs->dirs->num_entries)
              {
              dir = bg_mdb_array_get_dir(fs->dirs, i, NULL);

              if(gavl_string_array_indexof(arr, dir) < 0)
                {
                gavl_log(GAVL_LOG_INFO, LOG_DOMAIN, "Removing %s", dir);
                bg_mdb_unexport_media_directory(be->ctrl.evt_sink, dir);

                bg_mdb_del_dir_array(fs->dirs, dir, gavl_track_get_id(fs->container), be->ctrl.evt_sink);
                num_deleted++;
                }
              else
                i++;
              }

            if(!num_added && !num_deleted)
              {
              gavl_value_free(&val);
              return 1;
              }

            else
              {
              /* Update Parent */
              resp = bg_msg_sink_get(be->ctrl.evt_sink);
              
              gavl_msg_set_id_ns(resp, BG_MSG_DB_OBJECT_CHANGED, BG_MSG_NS_DB);
              
              gavl_dictionary_set_string(&resp->header, GAVL_MSG_CONTEXT_ID, gavl_track_get_id(fs->container));

              gavl_track_set_num_children(fs->container, fs->dirs->num_entries, 0);
              
              gavl_msg_set_arg_dictionary(resp, 0, fs->container);
              
              bg_msg_sink_put(be->ctrl.evt_sink, resp);
              }
            }
          else if(!strcmp(name, "mount_removable"))
            {
            fs->mount_removable = val.v.i;
            //   fprintf(stderr, "mount removable: %d\n", fs->mount_removable);
            }
          else if(!strcmp(name, "mount_audiocd"))
            {
            fs->mount_audiocd = val.v.i;
            }
          else if(!strcmp(name, "mount_videodisk"))
            {
            fs->mount_videodisk = val.v.i;
            }
          
          
          /* Pass to core to store it in the config registry */
          if(fs->have_params)
            {
            resp = bg_msg_sink_get(be->ctrl.evt_sink);
            bg_msg_set_parameter_ctx(resp, BG_MSG_PARAMETER_CHANGED_CTX, MDB_BACKEND_FILESYSTEM, name, &val);
            bg_msg_sink_put(be->ctrl.evt_sink, resp);
          
            gavl_value_free(&val);
            }
          }
          break;
        }
    }  
  
  return 1;
  }

static const bg_parameter_info_t parameters[] =
  {
    {
      .name = "mount_removable",
      .long_name = TRS("Mount removable storage media"),
      .help_string = TRS("Mount removable storage media like pendrives, mobile phones."),
      .type = BG_PARAMETER_CHECKBUTTON,
    },
    {
      .name = "mount_audiocd",
      .long_name = TRS("Mount audio CDs"),
      .type = BG_PARAMETER_CHECKBUTTON,
    },
    {
      .name = "mount_videodisk",
      .long_name = TRS("Mount video disks"),
      .type = BG_PARAMETER_CHECKBUTTON,
    },
    {
      .name = "dirs",
      .long_name = TRS("Directories"),
      .type = BG_PARAMETER_DIRLIST,
    },
    { /* End */ },
  };

void bg_mdb_create_filesystem(bg_mdb_backend_t * b)
  {
  fs_t * priv;
  priv = calloc(1, sizeof(*priv));
  b->priv = priv;

  b->parameters = parameters;
  
  b->flags |= BE_FLAG_VOLUMES;
  
  priv->root_id = bg_mdb_get_klass_id(GAVL_META_MEDIA_CLASS_ROOT_DIRECTORIES);
  
  b->destroy = destroy_filesystem;

  bg_controllable_init(&b->ctrl,
                       bg_msg_sink_create(handle_msg, b, 0),
                       bg_msg_hub_create(1));
  
  priv->state = gavl_dictionary_get_dictionary_create(&b->db->state, "filesystem");

  if(!(priv->dirs = gavl_dictionary_get_array_nc(priv->state, "dirs")))
    {
    gavl_value_t val;
    gavl_value_init(&val);
    gavl_value_set_array(&val);
    gavl_dictionary_set_nocopy(priv->state, "dirs", &val);
    
    priv->dirs  = gavl_dictionary_get_array_nc(priv->state, "dirs");
    }

  //  fprintf(stderr, "bg_mdb_create_filesystem\n");
  //  gavl_array_dump(priv->dirs, 2);
  
  priv->container = bg_mdb_get_root_container(b->db, GAVL_META_MEDIA_CLASS_ROOT_DIRECTORIES);
  
  bg_mdb_container_set_backend(priv->container, MDB_BACKEND_FILESYSTEM);

  /* Add children */
  gavl_track_set_num_children(priv->container, priv->dirs->num_entries, 0);
  
  }
