#include "gskcontrolserver.h"
#include "../http/gskhttpcontent.h"
#include "../gskmemory.h"
#include "../gsklog.h"
#include <string.h>


typedef struct _DirNode DirNode;
typedef struct _FileNode FileNode;
typedef struct _Command Command;



struct _FileNode
{
  char *name;
  gboolean is_virtual;
  union
  {
    struct {
      gsize size;
      gpointer data;
    } raw;
    struct {
      GskControlServerVFileContentsFunc vfile_func;
      gpointer          vfile_data;
      GDestroyNotify    vfile_data_destroy;
    } virtual;
  } info;
};

struct _DirNode
{
  char *name;
  GPtrArray *dirs, *files;
};

struct _Command
{
  char *name;
  GskControlServerCommandFunc func;
  gpointer func_data;
};

struct _GskControlServer
{
  GskHttpContent *content;
  DirNode *root;
  GHashTable *commands_by_name;
  Command *default_command;
};

static void
generic_failure (GskHttpServer *server,
                 GskHttpRequest *request,
                 GskHttpStatus status,
                 const char *status_name,
                 const char *error_message)
{
  GskHttpResponse *response;
  GskStream *content;
  response = gsk_http_response_from_request (request, status, -1);
  gsk_http_header_set_content_type (response, "text");
  gsk_http_header_set_content_subtype (response, "plain");
  content = gsk_memory_source_new_printf ("ERROR!!! (%s)\n\n"
                                          "%s\n",
                                          status_name, error_message);
  gsk_http_server_respond (server, request, response, content);
  g_object_unref (content);
  g_object_unref (response);
}

static void
bad_request_respond (GskHttpServer *server,
                     GskHttpRequest *request,
                     const char     *error_message)
{
  generic_failure (server, request, GSK_HTTP_STATUS_BAD_REQUEST, "Bad Request", error_message);
}

static void
error_processing_request (GskHttpServer *server,
                     GskHttpRequest *request,
                     const char     *error_message)
{
  generic_failure (server, request, GSK_HTTP_STATUS_BAD_REQUEST, "Error Processing Request", error_message);
}


static GskHttpContentResult
handle_run_txt (GskHttpContent   *content,
                GskHttpContentHandler *handler,
                GskHttpServer  *server,
                GskHttpRequest *request,
                GskStream      *post_data,
                gpointer        data)
{
  GError *error = NULL;
  Command *command;
  guint i;
  GskControlServer *cserver = data;
  GskStream *output;

  /* parse path down to an argument list */
  char **cmd = gsk_http_parse_cgi_query_string (request->path, &error);
  char **argv;
  if (cmd == NULL)
    {
      /* serve error page */
      bad_request_respond (server, request, error->message);
      g_error_free (error);
      g_strfreev (cmd);
      return GSK_HTTP_CONTENT_OK;
    }
  if (cmd[0] == NULL)
    {
      /* serve error page */
      bad_request_respond (server, request, "no command found");
      g_error_free (error);
      g_strfreev (cmd);
      return GSK_HTTP_CONTENT_OK;
    }
  if (strcmp (cmd[0], "command") != 0)
    {
      bad_request_respond (server, request, "first CGI variable should be command");
      g_error_free (error);
      g_strfreev (cmd);
      return GSK_HTTP_CONTENT_OK;
    }
  for (i = 1; cmd[2*i] != NULL; i++)
    {
      char buf[32];
      g_snprintf(buf,sizeof(buf),"arg%u", i);
      if (strcmp (cmd[2*i],buf) != 0)
        {
          bad_request_respond (server, request, "argument key was not argN (for N a natural number)");
          g_error_free (error);
          g_strfreev (cmd);
          return GSK_HTTP_CONTENT_OK;
        }
    }
  argv = g_new (char *, i + 1);
  for (i = 0; cmd[2*i] != NULL; i++)
    {
      g_free (cmd[2*i]);
      argv[i] = cmd[2*i+1];
    }
  g_free (cmd);
  argv[i] = NULL;

  /* Now handle argv[] */
  command = g_hash_table_lookup (cserver->commands_by_name, argv[0]);
  if (command == NULL)
    {
      if (cserver->default_command == NULL)
        {
          error_processing_request (server, request,
                               "no command handler for given commands nor a default handler");
          g_strfreev (argv);
          return GSK_HTTP_CONTENT_OK;
        }
    }
  output = NULL;
  if (!command->func (argv, post_data, &output, command->func_data, &error))
    {
      if (error)
        {
          error_processing_request (server, request, error->message);
          g_error_free (error);
        }
      else
          error_processing_request (server, request,
                               "command failed but got no specific error message");
      g_strfreev (argv);
      return GSK_HTTP_CONTENT_OK;
    }
  {
    GskHttpResponse *response;
    response = gsk_http_response_from_request (request, GSK_HTTP_STATUS_OK, -1);
    gsk_http_header_set_content_type (response, "text");
    gsk_http_header_set_content_subtype (response, "plain");
    if (output == NULL)
      output = gsk_memory_source_static_string ("");
    gsk_http_server_respond (server, request, response, output);
    g_object_unref (response);
    g_object_unref (output);
  }
  g_strfreev (argv);
  return GSK_HTTP_CONTENT_OK;
}

static void
add_command_internal (GskControlServer *server,
                      const char       *command_name,
                      GskControlServerCommandFunc func,
                      gpointer          data)
{
  Command *command;
  g_return_if_fail (g_hash_table_lookup (server->commands_by_name, command_name) == NULL);
  command = g_new (Command, 1);
  command->name = g_strdup (command_name);
  command->func = func;
  command->func_data = data;
  g_hash_table_insert (server->commands_by_name, command->name, command);
}

static char **
path_split (const char *path)
{
  char **split = g_strsplit (path, "/", 0);
  char **out = split;
  char **split_at = split;
  while (*split_at)
    {
      if (**split_at)
        *out++ = *split_at;
      else
        g_free (*split_at);
      split_at++;
    }
  *out = NULL;
  return split;
}

static DirNode *
maybe_get_dir_node (GskControlServer *server,
                    char            **path)
{
  char **at;
  DirNode *node = server->root;
  guint i;
  for (at = path; *at; at++)
    {
      DirNode *sub = NULL;
      if (node->dirs != NULL)
        for (i = 0; i < node->dirs->len; i++)
          {
            DirNode *dn = node->dirs->pdata[i];
            if (strcmp (dn->name, at[0]) == 0)
              {
                sub = dn;
                break;
              }
          }
      if (sub == NULL)
        return NULL;
      node = sub;
    }
  return node;
}

static void
append_command_star_to_str (gpointer key, gpointer value,
                            gpointer data)
{
  g_string_append_printf ((GString *) data, "%s*\n", (char*)key);
}

static gboolean
command_handler__ls (char **argv,
                     GskStream *input,
                     GskStream **output,
                     gpointer data,
                     GError **error)
{
  GskControlServer *cserver = data;
  char **path_pieces;
  DirNode *node;
  GString *rs;
  guint i;
  if (argv[1] == NULL)
    path_pieces = g_new0 (char *, 1);
  else if (argv[2] == NULL)
    path_pieces = path_split (argv[1]);
  else
    {
      g_set_error (error, GSK_G_ERROR_DOMAIN,
                   GSK_ERROR_INVALID_ARGUMENT,
                   "'ls' command takes just one argument");
      g_strfreev (path_pieces);
      return FALSE;
    }
  if (path_pieces[0] && path_pieces[1] == NULL
      && strcmp (path_pieces[0], "bin") == 0)
    {
      /* return list of commands */
      rs = g_string_new ("");
      g_hash_table_foreach (cserver->commands_by_name,
                            append_command_star_to_str,
                            rs);
      goto return_string;
    }

  node = maybe_get_dir_node (cserver, path_pieces);
  if (node == NULL)
    {
      g_set_error (error, GSK_G_ERROR_DOMAIN,
                   GSK_ERROR_FILE_NOT_FOUND,
                   "directory %s not found",
                   argv[1] ? argv[1] : "/");
      g_strfreev (path_pieces);
      return FALSE;
    }

  /* return list of files and directories */
  rs = g_string_new ("");
  if (node->dirs)
    for (i = 0; i < node->dirs->len; i++)
      g_string_append_printf (rs, "%s/\n",
                              ((DirNode *)(node->dirs->pdata[i]))->name);
  if (node->files)
    for (i = 0; i < node->files->len; i++)
      g_string_append_printf (rs, "%s\n",
                              ((FileNode *)(node->files->pdata[i]))->name);

  if (path_pieces[0] == NULL)
    {
      /* add a few hack directories in */
      g_string_append_printf (rs, "bin/\n");
    }

return_string:
  {
    guint len = rs->len;
    char *mem = g_string_free (rs, FALSE);
    *output = gsk_memory_slab_source_new (mem, len, g_free, mem);
    g_strfreev (path_pieces);
    return TRUE;
  }
}

static gboolean
command_handler__cat(char **argv,
                     GskStream *input,
                     GskStream **output,
                     gpointer data,
                     GError **error)
{
  const guint8 *content;
  guint content_length;
  gpointer copy;
  GskControlServer *server = data;
  if (argv[1] == NULL || argv[2] != NULL)
    {
      g_set_error (error, GSK_G_ERROR_DOMAIN,
                   GSK_ERROR_INVALID_ARGUMENT,
                   "'cat' command takes just one argument");
      return FALSE;
    }
  switch (gsk_control_server_stat (server, argv[1]))
    {
      case GSK_CONTROL_SERVER_FILE_RAW:
        if (!gsk_control_server_peek_raw_file (data, argv[1], &content, &content_length))
          {
            g_set_error (error, GSK_G_ERROR_DOMAIN,
                         GSK_ERROR_FILE_NOT_FOUND,
                         "cat: file %s not found", argv[1]);
            return FALSE;
          }
        copy = g_memdup (content, content_length);
        *output = gsk_memory_slab_source_new (copy, content_length, g_free, copy);
        return TRUE;
      case GSK_CONTROL_SERVER_FILE_VIRTUAL:
        {
          guint8 *content;
          guint length;
          GDestroyNotify release;
          gpointer release_data;
          if (!gsk_control_server_get_vfile_contents (server, argv[1],
                                                      &content, &length,
                                                      &release, &release_data))
            {
              g_set_error (error, GSK_G_ERROR_DOMAIN,
                           GSK_ERROR_FILE_NOT_FOUND,
                           "cat: error getting virtual file %s", argv[1]);
              return FALSE;
            }
          *output = gsk_memory_slab_source_new (content, length, release, release_data);
          return TRUE;
        }
      case GSK_CONTROL_SERVER_FILE_DIR:
        g_set_error (error, GSK_G_ERROR_DOMAIN,
                     GSK_ERROR_FILE_NOT_FOUND,
                     "cat: %s was a directory", argv[1]);
        return FALSE;
      case GSK_CONTROL_SERVER_FILE_NOT_EXIST:
        g_set_error (error, GSK_G_ERROR_DOMAIN,
                     GSK_ERROR_FILE_NOT_FOUND,
                     "cat: %s was not found", argv[1]);
        return FALSE;
      default:
        g_set_error (error, GSK_G_ERROR_DOMAIN,
                     GSK_ERROR_FILE_NOT_FOUND,
                     "cat: should not be reached (%s)", argv[1]);
        g_return_val_if_reached (FALSE);
    }
  return TRUE;
}

/**
 * gsk_control_server_new:
 * returns: a new GskControlServer.
 *
 * Allocate a new GskControlServer.
 *
 * It has a few builtin commands: 'ls', 'cat'.
 */
GskControlServer *
gsk_control_server_new (void)
{
  GskControlServer *server = g_new (GskControlServer, 1);
  GskHttpContentId id = GSK_HTTP_CONTENT_ID_INIT;
  GskHttpContentHandler *handler;
  server->content = gsk_http_content_new ();
  id.path_prefix = "/run.txt?";
  handler = gsk_http_content_handler_new (handle_run_txt, server, NULL);
  gsk_http_content_add_handler (server->content, &id, handler, GSK_HTTP_CONTENT_REPLACE);
  gsk_http_content_handler_unref (handler);
  server->root = g_new0 (DirNode, 1);
  server->default_command = NULL;
  server->commands_by_name = g_hash_table_new (g_str_hash, g_str_equal);

  add_command_internal (server, "ls", command_handler__ls, server);
  add_command_internal (server, "cat", command_handler__cat, server);
  return server;
}

/**
 * gsk_control_server_add_command:
 * @server: the server which should learn the command.
 * @command_name: the name of the new command.
 * @func: the function that implements the new command.
 * @data: opaque data to pass to 'func'.
 *
 * Add a command to the command server.
 * The command will show up when users 'ls /bin'
 * and they may also type it directly.
 *
 * The command must not conflict with any of the
 * reserved commands: ls, cat, set, get.
 */
static const char *reserved_commands[] =
{
  "cat",
  "get",
  "set",
  "ls",
  NULL
};

void
gsk_control_server_add_command (GskControlServer *server,
                                const char       *command_name,
                                GskControlServerCommandFunc func,
                                gpointer          data)
{
  guint i;
  for (i = 0; reserved_commands[i]; i++)
    if (strcmp (command_name, reserved_commands[i]) == 0)
      {
        g_warning ("command %s is reserved: you cannot add it",
                   command_name);
        return;
      }
  add_command_internal (server, command_name, func, data);
}

void
gsk_control_server_set_default_command
                               (GskControlServer *server,
                                GskControlServerCommandFunc func,
                                gpointer          data)
{
  Command *command;
  g_return_if_fail (server->default_command == NULL);
  command = g_new (Command, 1);
  command->name = NULL;
  command->func = func;
  command->func_data = data;
  server->default_command = command;
}

static DirNode *
server_mkdir (GskControlServer *server,
              char **split,
              GError    **error)
{
  DirNode *node = server->root;
  guint i, j;
  for (i = 0; split[i]; i++)
    {
      if (node->files)
        for (j = 0; j < node->files->len; j++)
          {
            FileNode *fn = node->files->pdata[j];
            if (strcmp (fn->name, split[i]) == 0)
              {
                g_set_error (error, GSK_G_ERROR_DOMAIN,
                             GSK_ERROR_IS_A_DIRECTORY,
                             "node %s already exists as a file",
                             split[i]);
                return NULL;
              }
          }
      if (node->dirs)
        {
          for (j = 0; j < node->dirs->len; j++)
            {
              DirNode *dn = node->dirs->pdata[j];
              if (strcmp (dn->name, split[i]) == 0)
                break;
            }
          if (j < node->dirs->len)
            {
              node = node->dirs->pdata[j];
              continue;
            }
        }
      else
        node->dirs = g_ptr_array_new ();
      {
        DirNode *new = g_new (DirNode, 1);
        new->name = g_strdup (split[i]);
        new->dirs = new->files = NULL;
        g_ptr_array_add (node->dirs, new);
        node = new;
      }
    }
  return node;
}

static void
destruct_file_node (FileNode *fn)
{
  g_free (fn->name);
  if (fn->is_virtual)
    fn->info.virtual.vfile_data_destroy (fn->info.virtual.vfile_data);
  else
    g_free (fn->info.raw.data);
}

static FileNode *
set_file_generic (GskControlServer *server,
                  const char       *path,
                  GError          **error)
{
  char **p = path_split (path);
  char **end;
  char *basename;
  DirNode *node;
  guint i;
  if (p[0] == NULL)
    {
      g_set_error (error, GSK_G_ERROR_DOMAIN,
                   GSK_ERROR_INVALID_ARGUMENT,
                   "file name must have at least one component");
      g_strfreev (p);
      return NULL;
    }
  for (end = p; end[1]; end++)
    ;
  basename = *end;
  *end = NULL;
  node = server_mkdir (server, p, error);
  if (node == NULL)
    {
      g_strfreev (p);
      return NULL;
    }
  
  *end = basename;
  if (node->dirs)
    for (i = 0; i < node->dirs->len; i++)
      {
        DirNode *dn = node->dirs->pdata[i];
        if (strcmp (dn->name, basename) == 0)
          {
            g_set_error (error, GSK_G_ERROR_DOMAIN,
                         GSK_ERROR_IS_A_DIRECTORY,
                         "node %s already exists as a directory",
                         path);
            g_strfreev (p);
            return FALSE;
          }
      }
  if (node->files == NULL)
    node->files = g_ptr_array_new ();
  for (i = 0; i < node->files->len; i++)
    {
      FileNode *fn = node->files->pdata[i];
      if (strcmp (fn->name, basename) == 0)
        break;
    }
  if (i == node->files->len)
    {
      /* new file */
      FileNode *fn = g_new (FileNode, 1);
      fn->name = g_strdup (basename);
      g_ptr_array_add (node->files, fn);
      g_strfreev (p);
      return fn;
    }
  else
    {
      FileNode *fn = node->files->pdata[i];
      destruct_file_node (fn);
      g_strfreev (p);
      return fn;
    }
}

/**
 * gsk_control_server_set_file:
 * @server: the server which have the virtual file added.
 * @path: the virtual path for the file's location.
 * @content: the data for the file.
 * @content_length: the contents of the file.
 * @error: place to store the error if failure occurs.
 * 
 * Try to create a file in the virtual file-system
 * that clients will have access to.
 *
 * The clients can find the file using 'ls'
 * and read the file using 'cat'.
 *
 * This may replace an old copy of the file.
 *
 * returns: whether this command succeeded.
 * This can fail if it cannot make a virtual-directory.
 */
gboolean
gsk_control_server_set_file    (GskControlServer *server,
                                const char       *path,
                                const guint8     *content,
                                guint             content_length,
                                GError          **error)
{
  FileNode *fn;
  gpointer ccopy = g_memdup (content, content_length);
  fn = set_file_generic (server, path, error);
  if (fn == NULL)
    return FALSE;

  fn->is_virtual = FALSE;
  fn->info.raw.size = content_length;
  fn->info.raw.data = ccopy;
  return TRUE;
}

/**
 * gsk_control_server_set_vfile:
 * @server: the server which have the virtual file added.
 * @path: the virtual path for the file's location.
 * @content: the data for the file.
 * @content_length: the contents of the file.
 * @error: place to store the error if failure occurs.
 * 
 * Try to create a virtual-file in the virtual file-system
 * that clients will have access to.
 *
 * The clients can find the file using 'ls'
 * and read the file using 'cat'.
 *
 * This may replace an old copy of the file.
 */
gboolean
gsk_control_server_set_vfile   (GskControlServer *server,
                                const char       *path,
                                GskControlServerVFileContentsFunc vfile_func,
                                gpointer          vfile_data,
                                GDestroyNotify    vfile_data_destroy,
                                GError           **error)
{
  FileNode *fn;
  fn = set_file_generic (server, path, error);
  if (fn == NULL)
    return FALSE;

  fn->is_virtual = TRUE;
  fn->info.virtual.vfile_func = vfile_func;
  fn->info.virtual.vfile_data = vfile_data;
  fn->info.virtual.vfile_data_destroy = vfile_data_destroy;
  return TRUE;
}

static void
get_logfile_contents (gpointer  vfile_data,
                      guint    *len_out,
                      guint8   **contents_out,
                      GDestroyNotify *done_with_contents_out,
                      gpointer *done_with_contents_data_out)
{
  char *contents = gsk_log_ring_buffer_get (vfile_data);
  *len_out = strlen (contents);
  *contents_out = (guint8*) contents;
  *done_with_contents_data_out = contents;
  *done_with_contents_out = g_free;
}

/**
 * gsk_control_server_set_logfile_v:
 * @server: the server to which to add the virtual file.
 * @path: the virtual path for the file's location.
 * @ring_buffer_size: size in bytes of the log's ring-buffer.
 * @n_log_domains: number of log-domains to include in this ring-buffer.
 * @domains: domain/loglevel pairs.
 *
 * Make a virtual file in the control-server that will be a ring-buffer
 * of the most recent data all the log domains.
 */
void
gsk_control_server_set_logfile_v (GskControlServer *server,
                                  const char       *path,
                                  guint             ring_buffer_size,
                                  guint             n_log_domains,
                                  const GskControlServerLogDomain *domains)
{
  GskLogRingBuffer *ring_buffer = gsk_log_ring_buffer_new (ring_buffer_size);
  guint i;
  gsk_control_server_set_vfile (server, path,
                                get_logfile_contents, ring_buffer, NULL,
                                NULL);
  for (i = 0; i < n_log_domains; i++)
    gsk_log_trap_ring_buffer (domains[i].domain, domains[i].levels,
                              ring_buffer, NULL);
}

/**
 * gsk_control_server_set_logfile_v:
 * @server: the server to which to add the virtual file.
 * @path: the virtual path for the file's location.
 * @ring_buffer_size: size in bytes of the log's ring-buffer.
 * @first_log_domain: name of first log domain to put in ring-buffer.
 * @first_log_level_flags: first log domain's accepted log-levels.
 * @next_log_domain: name of second log domain to put in ring-buffer,
 * or NULL if there is only one log domain.
 * @...: continue listing domain/level-mask pairs.
 *
 * Make a virtual file in the control-server that will be a ring-buffer
 * of the most recent data from all the log domains.
 */
void
gsk_control_server_set_logfile   (GskControlServer *server,
                                  const char       *path,
                                  guint             ring_buffer_size,
                                  const char       *first_log_domain,
                                  GLogLevelFlags    first_log_level_flags,
                                  const char       *next_log_domain,
                                  ...)
{
  guint n_domains = 1;
  const char *d = next_log_domain;
  va_list args;
  GskControlServerLogDomain *domains;
  va_start (args, next_log_domain);
  while (d)
    {
      n_domains++;
      va_arg (args, GLogLevelFlags);
      d = va_arg (args, const char *);
    }
  va_end (args);

  domains = g_newa (GskControlServerLogDomain, n_domains);
  domains[0].domain = first_log_domain;
  domains[0].levels = first_log_level_flags;
  va_start (args, next_log_domain);
  d = next_log_domain;
  n_domains = 1;
  while (d)
    {
      domains[n_domains].domain = d;
      domains[n_domains].levels = va_arg (args, GLogLevelFlags);
      n_domains++;
      d = va_arg (args, const char *);
    }

  gsk_control_server_set_logfile_v (server, path, ring_buffer_size,
                                    n_domains, domains);
}



/**
 * gsk_control_server_delete_file:
 * @server: the server which have the virtual file removed from.
 * @path: the virtual path for the file's location.
 * @error: place to store the error if failure occurs.
 *
 * Remove a file from the virtual filesystem.
 *
 * Returns an error if the file does not exist or is a directory.
 */
gboolean
gsk_control_server_delete_file (GskControlServer *server,
                                const char       *path,
                                GError          **error)
{
  DirNode *node = server->root;
  FileNode *file_node;
  char **p = path_split (path);
  char **at;
  guint i;
  if (p[0] == NULL)
    {
      g_set_error (error, GSK_G_ERROR_DOMAIN,
                   GSK_ERROR_INVALID_ARGUMENT,
                   "file name must have at least one component");
      g_strfreev (p);
      return FALSE;
    }
  for (at = p; at[1]; at++)
    {
      DirNode *sub = NULL;
      if (node->dirs != NULL)
        for (i = 0; i < node->dirs->len; i++)
          {
            DirNode *dn = node->dirs->pdata[i];
            if (strcmp (dn->name, at[0]) == 0)
              {
                sub = dn;
                break;
              }
          }
      if (sub == NULL)
        {
          g_set_error (error, GSK_G_ERROR_DOMAIN,
                       GSK_ERROR_FILE_NOT_FOUND,
                       "directory to %s did not exist", path);
          g_strfreev (p);
          return FALSE;
        }
      node = sub;
    }
  if (node->dirs)
    for (i = 0; i < node->dirs->len; i++)
      {
        DirNode *dn = node->dirs->pdata[i];
        if (strcmp (dn->name, *at) == 0)
          {
            g_set_error (error, GSK_G_ERROR_DOMAIN,
                         GSK_ERROR_IS_A_DIRECTORY,
                         "%s was a directory", path);
            g_strfreev (p);
            return FALSE;
          }
      }
  file_node = NULL;
  if (node->files != NULL)
    for (i = 0; i < node->files->len; i++)
      {
        FileNode *fn = node->files->pdata[i];
        if (strcmp (fn->name, *at) == 0)
          {
            file_node = fn;
            break;
          }
      }
  if (file_node == NULL)
    {
      g_set_error (error, GSK_G_ERROR_DOMAIN,
                   GSK_ERROR_FILE_NOT_FOUND,
                   "%s was not found", path);
      g_strfreev (p);
      return FALSE;
    }
  g_ptr_array_remove_index_fast (node->files, i);
  destruct_file_node (file_node);
  g_free (file_node);
  g_strfreev (p);
  return TRUE;
}
/**
 * gsk_control_server_delete_directory:
 * @server: the server which have the virtual file removed from.
 * @path: the virtual path for the file's location.
 * @error: place to store the error if failure occurs.
 *
 * Remove a file from the virtual filesystem.
 *
 * Returns an error if the file does not exist or is a directory.
 */
static void delete_dirnode_recursively (DirNode *dir_node);
gboolean
gsk_control_server_delete_directory (GskControlServer *server,
                                     const char       *path,
                                     GError          **error)
{
  DirNode *node = server->root;
  char **p = path_split (path);
  char **at;
  guint i;
  if (p[0] == NULL)
    {
      g_set_error (error, GSK_G_ERROR_DOMAIN,
                   GSK_ERROR_INVALID_ARGUMENT,
                   "file name must have at least one component");
      g_strfreev (p);
      return FALSE;
    }
  for (at = p; at[1]; at++)
    {
      DirNode *sub = NULL;
      if (node->dirs != NULL)
        for (i = 0; i < node->dirs->len; i++)
          {
            DirNode *dn = node->dirs->pdata[i];
            if (strcmp (dn->name, at[0]) == 0)
              {
                sub = dn;
                break;
              }
          }
      if (sub == NULL)
        {
          g_set_error (error, GSK_G_ERROR_DOMAIN,
                       GSK_ERROR_FILE_NOT_FOUND,
                       "directory to %s did not exist", path);
          g_strfreev (p);
          return FALSE;
        }
      node = sub;
    }
  if (node->dirs)
    for (i = 0; i < node->dirs->len; i++)
      {
        DirNode *dn = node->dirs->pdata[i];
        if (strcmp (dn->name, *at) == 0)
          break;
      }
  if (node->dirs == NULL
   || i == node->dirs->len)
    {
      g_set_error (error, GSK_G_ERROR_DOMAIN,
                   GSK_ERROR_INVALID_ARGUMENT,
                   "%s was a not directory", path);
      g_strfreev (p);
      return FALSE;
    }

  {
    DirNode *to_kill = node->dirs->pdata[i];
    g_ptr_array_remove_index_fast (node->dirs, i);
    delete_dirnode_recursively (to_kill);
  }
  g_strfreev (p);

  return TRUE;
}

static void
delete_dirnode_recursively (DirNode *dir_node)
{
  guint i;
  if (dir_node->dirs)
    {
      for (i = 0; i < dir_node->dirs->len; i++)
        delete_dirnode_recursively (dir_node->dirs->pdata[i]);
      g_ptr_array_free (dir_node->dirs, TRUE);
    }
  if (dir_node->files)
    {
      for (i = 0; i < dir_node->files->len; i++)
        {
          FileNode *file_node;
          file_node = dir_node->files->pdata[i];
          destruct_file_node (file_node);
          g_free (file_node);
        }
      g_ptr_array_free (dir_node->files, TRUE);
    }
  g_free (dir_node->name);
  g_free (dir_node);
}

GskControlServerFileStat
gsk_control_server_stat        (GskControlServer *server,
                                const char       *path)
{
  char **p = path_split (path);
  char **at;
  DirNode *node = server->root;
  guint i;
  if (p[0] == NULL)
    {
      g_free (p);
      return GSK_CONTROL_SERVER_FILE_DIR;
    }
  /* terminate at the penultimate path component--
     the last path component is the base filename,
     which we handle separately */
  for (at = p; at[1]; at++)
    {
      DirNode *sub = NULL;
      if (node->dirs != NULL)
        for (i = 0; i < node->dirs->len; i++)
          {
            DirNode *dn = node->dirs->pdata[i];
            if (strcmp (dn->name, at[0]) == 0)
              {
                sub = dn;
                break;
              }
          }
      if (sub == NULL)
        {
          g_strfreev (p);
          return GSK_CONTROL_SERVER_FILE_NOT_EXIST;
        }
      node = sub;
    }
  if (node->files == NULL)
    {
      g_strfreev (p);
      return GSK_CONTROL_SERVER_FILE_NOT_EXIST;
    }
  if (node->files)
    for (i = 0; i < node->files->len; i++)
      {
        FileNode *fn = node->files->pdata[i];
        if (strcmp (fn->name, *at) == 0)
          {
            g_strfreev (p);
            return fn->is_virtual ? GSK_CONTROL_SERVER_FILE_VIRTUAL
                                  : GSK_CONTROL_SERVER_FILE_RAW;
          }
      }
  if (node->dirs)
    for (i = 0; i < node->dirs->len; i++)
      {
        DirNode *dn = node->dirs->pdata[i];
        if (strcmp (dn->name, *at) == 0)
          {
            g_strfreev (p);
            return GSK_CONTROL_SERVER_FILE_DIR;
          }
      }
  g_strfreev (p);
  return GSK_CONTROL_SERVER_FILE_NOT_EXIST;
}

static FileNode *
find_file_node (GskControlServer *server,
                const char       *path)
{
  char **p = path_split (path);
  char **at;
  DirNode *node = server->root;
  guint i;
  if (p[0] == NULL)
    {
      g_free (p);
      return FALSE;
    }
  /* terminate at the penultimate path component--
     the last path component is the base filename,
     which we handle separately */
  for (at = p; at[1]; at++)
    {
      DirNode *sub = NULL;
      if (node->dirs != NULL)
        for (i = 0; i < node->dirs->len; i++)
          {
            DirNode *dn = node->dirs->pdata[i];
            if (strcmp (dn->name, at[0]) == 0)
              {
                sub = dn;
                break;
              }
          }
      if (sub == NULL)
        {
          g_strfreev (p);
          return FALSE;
        }
      node = sub;
    }
  if (node->files == NULL)
    {
      g_strfreev (p);
      return FALSE;
    }
  for (i = 0; i < node->files->len; i++)
    {
      FileNode *fn = node->files->pdata[i];
      if (strcmp (fn->name, *at) == 0)
        {
          g_strfreev (p);
          return fn;
        }
    }
  g_strfreev (p);
  return NULL;
}

/**
 * gsk_control_server_peek_raw_file:
 * @server: the server to query.
 * @path: the path in the virtual file-system.
 * @content_out: where to put a pointer to the data.
 * @content_length_out: where to put the length of the data.
 * returns: whether we were able to find the file.
 *
 * Get the contents of a file that is in the virtual
 * file-system.  These data will go away if the file
 * is deleted or replaced: make a copy if you plan on holding
 * the data around.
 */
gboolean
gsk_control_server_peek_raw_file (GskControlServer *server,
                                  const char       *path,
                                  const guint8    **content_out,
                                  guint            *content_length_out)
{
  FileNode *fn = find_file_node (server, path);
  if (fn == NULL || fn->is_virtual)
    return FALSE;
  *content_out = fn->info.raw.data;
  *content_length_out = fn->info.raw.size;
  return TRUE;
}

/**
 * gsk_control_server_get_vfile_contents:
 * @server: the server to query.
 * @path: the file's path.
 * @content_out: the content that should be returned to the user.
 * @content_length_out: length of hte content that should be given to
 * the user.
 * @release_func_out:
 * @release_func_data_out:
 *
 * Obtain the data for a virtual function.
 * Callbacks to release the data are also provided.
 * 
 * This is rarely needed: it is mostly used by the control-server
 * itself.
 *
 * returns: whether we were able to find the file
 * and retrieve its contents.
 */
gboolean
gsk_control_server_get_vfile_contents (GskControlServer *server,
                                       const char       *path,
                                       guint8          **content_out,
                                       guint            *content_length_out,
                                       GDestroyNotify   *release_func_out,
                                       gpointer         *release_func_data_out)
{
  FileNode *fn = find_file_node (server, path);
  if (fn == NULL || !fn->is_virtual)
    return FALSE;
  fn->info.virtual.vfile_func (fn->info.virtual.vfile_data,
                               content_length_out, content_out,
                               release_func_out, release_func_data_out);
  return TRUE;
}

/**
 * gsk_control_server_listen:
 * @server: the server that should listen.
 * @address: the port to listen on.
 * @error: place to store the error if failure occurs.
 *
 * Bind to address and answer the requests using server.
 *
 * returns: whether the bind operation was successful.
 */
gboolean
gsk_control_server_listen (GskControlServer *server,
                           GskSocketAddress *address,
                           GError          **error)
{
  return gsk_http_content_listen (server->content, address, error);
}


syntax highlighted by Code2HTML, v. 0.9.1