/* Jungle Monkey Lite
 * Copyright (C) 1999, 2000  The Regents of the University of Michigan
 *
 * 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, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */


#include "config.h"

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <errno.h>
#include <unistd.h>
#include <dirent.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <signal.h>


#ifdef JM_ENABLE_GNOME
#include <popt-gnome.h>
#else
#include <popt.h>
#endif

#include <pwd.h>
#include <grp.h>
#include <sys/types.h>

#include <gnet/gnet.h>


#include "jmintl.h"
#include "jmutil.h"

#include "util/util.h"
#include "util/poll.h"
#include "btp/b_conn.h"
#include "btp/btp_debug.h"
#include "jmchannel/jmchannel.h"
#include "jmchannel/jmchannel_debug.h"
#include "jmchat/jmchat.h"
#include "jmchat/jmchat_debug.h"
#include "jmmsearch/jmmsearch.h"
#include "jmmsearch/jmmsp_debug.h"


#ifndef MAXLINELEN
#define MAXLINELEN 16000
#endif

#define MAX_TOKENS 10
#define MAX_INCLUDES 16


/* ******************** */
/* Globals		*/

static const gchar* 	script_name = NULL;
static int		fail_on_error = 0;
static int		clfl = 0;	/* command line is first line */
static int		no_prompt = 0;
static gboolean		shutting_down = FALSE;

static GIOChannel*	includes[MAX_INCLUDES] = {NULL, };
static guint		include_level = 0;
static GNetIOChannelReadAsyncID	readasync_id = NULL;
static gchar		line_buffer[MAXLINELEN];

static GHashTable*	name_to_channel = NULL;	
static GHashTable*	name_to_chat    = NULL;	
static GHashTable*	name_to_search  = NULL;	
static GHashTable*	url_to_obj      = NULL;

#define HAVE_NAME(N)    (g_hash_table_lookup(name_to_channel, (N)) || \
			 g_hash_table_lookup(name_to_chat,    (N)) || \
			 g_hash_table_lookup(name_to_search,  (N)))


static void start_backend (int argc, char* argv[]);

static gboolean readline_cb (GIOChannel* iochannel, 
			     GNetIOChannelReadAsyncStatus status, 
			     gchar* buffer, guint length, 
			     gpointer user_data);

static void	create_channel (const gchar* path, gboolean channels_only);
static void	remove_channel (const gchar* path);
static void	open_channel (const gchar* urlstr);
static void	list (const gchar* path);
static void	list_hfunc (gpointer key, gpointer value, gpointer user_data);
static void	add_file (const gchar* path, const gchar* name);
static void	remove_file (const gchar* path);
static void	add_dir (const gchar* path, const gchar* name, gboolean swallow);
static void	create_dir (const gchar* path);
static void	remove_dir (const gchar* path);
static void	add_url (const gchar* path, const gchar* urlstr);
static void	remove_url (const gchar* path);
static void	create_chat (const gchar* path);
static void	remove_chat (const gchar* name);
static void	create_search (const gchar* path);
static void	remove_search (const gchar* name);

static void     parse_path (const gchar* path, JMChannel** pchannel, JMAnn** pann);

static void	channel_info (JMChannel* channel, JMChannelType type, 
			      JMAnn* ann, gpointer user_data);
static void     channel_error (JMChannel* jmchannel, gpointer user_data);

static void     error (const gchar *format, ...);
static void 	sig_int_cb (int ignore);
static void 	sig_pipe_cb (int ignore);
static void 	try_exit (void);
static void	delete_name_to_whatever_hfunc (gpointer key, gpointer value, gpointer user_data);
static void	delete_url_to_obj_hfunc (gpointer key, gpointer value, gpointer user_data);
static void 	conn_zero_cb (void);
static gboolean exit_timeout (gpointer p);



int
main (int argc, char* argv[])
{
  GMainLoop* main_loop;

  /* Workaround strict poll */
# ifdef HAVE_STRICT_POLL
    g_main_set_poll_func (gnu_poll);
# endif

  /* Initialize the RNG */
  srand(time(NULL));

  /* Create name to channel map */
  name_to_channel = g_hash_table_new (g_str_hash, g_str_equal);
  name_to_chat    = g_hash_table_new (g_str_hash, g_str_equal);
  name_to_search  = g_hash_table_new (g_str_hash, g_str_equal);
  url_to_obj = g_hash_table_new (gnet_url_hash, gnet_url_equal);

  /* If command line is the next line, then don't start the backend yet */
  if (argc >= 2 && !strcmp(argv[1], "-c"))
    {
      /* Arg 2 may be the script name */
      if (argc == 3)
	script_name = argv[2];

      clfl = TRUE;
    }

  /* Otherwise, parse CL and start backend now */
  else
    {
      start_backend (argc, argv);
    }
    
  /* Load the script maybe */
  if (script_name)
    {
      int fd;

      no_prompt = TRUE;

      fd = open (script_name, O_RDONLY);
      if (fd == -1)
	my_error (_("Could not open file %s\n"), script_name);

      includes[0] = g_io_channel_unix_new (fd);
    }
  else
    {      
      g_print (_("jmlite console mode\n"));
      g_print (_("type ? for help\n"));
      g_print (_("> "));

      includes[0] = g_io_channel_unix_new (STDIN_FILENO);
    }

  g_assert (includes[0]);

  readasync_id = 
    gnet_io_channel_readline_async (includes[0], 
				    line_buffer, sizeof(line_buffer), 
				    0, readline_cb, NULL);

  signal (SIGINT, sig_int_cb);
  signal (SIGPIPE, sig_pipe_cb);

  main_loop = g_main_new(FALSE);
  g_main_run(main_loop);

  exit (EXIT_SUCCESS);
}


static void
start_backend (int argc, char* argv[])
{
  JMUTIL_POPT_VARS

  struct poptOption jm_options[] = 
  {
    {"help", 'h', POPT_ARG_NONE, NULL, 1, 
     "Show this help message", NULL},
    {"usage", '\0', POPT_ARG_NONE, NULL, 2, 
     "Display brief usage message", NULL},
    {"version", 0, POPT_ARG_NONE, NULL, 3,
     "Output verson information and exit", NULL},

    JMUTIL_POPT_ARGS

    {"exit-on-error", 'e', POPT_ARG_NONE, &fail_on_error, 0,
     "Exit if an error occurs", NULL},
    {"first-line-is-command-line", 'c', POPT_ARG_NONE, NULL, 0,
     "First line read is command line (hack for scripts)", NULL},

    {"no-prompt", 'n', POPT_ARG_NONE, &no_prompt, 0,
     "Don't print the command prompt (on by default for scripts).", NULL},
    {"noop", '\0', POPT_ARG_NONE, NULL, 0,
     "No-operation.  Does nothing.", NULL},

    {NULL, '\0', 0, NULL, 0, NULL, NULL}
  };

  poptContext ctx;
  int rc;
  int exit_status = EXIT_SUCCESS;

/*    btp_debug_flags = 0x8c0c; */
/*    btp_debug_flags = 0xffff; */
/*    jmchat_debug_flags = 0xffff; */
/*    jmmsp_debug_flags = 0xffff; */
/*    jmchannel_debug_flags = 0xffff; */

  /* ******************** */
  /* Initialize NLS 	  */
#ifdef ENABLE_NLS
  {
    bindtextdomain (PACKAGE, LOCALEDIR);
    textdomain (PACKAGE);
  }
#endif

  /* ******************** */
  /* Parse command line	  */

  ctx = poptGetContext(NULL, argc, (const char**) argv, jm_options, 0);
  poptSetOtherOptionHelp(ctx, _("[OPTION]... [SCRIPT]"));

  while ((rc = poptGetNextOpt(ctx)) > 0)
    {
      switch (rc)
	{
	case 1:	  goto help;	  break;
	case 2:	  goto usage;	  break;
	case 3:   goto version;	  break;
	}
    }

  /* Get the script name (if we aren't doing clfl - it's too late if
     this is the first line). */
  if (!clfl)
    script_name = poptGetArg(ctx);

  poptFreeContext(ctx);

  /* Start backend  */
  if (JMUTIL_INIT)
    my_error (_("Could not start backend\n"));

  return;

 usage:
  poptPrintUsage (ctx, stdout, 0);
  exit (exit_status);

 help:
  g_print (_("`jmlite' - Command-line interface version of Jungle Monkey, a distributed\n"
	     "file sharing program.\n"));
  poptPrintHelp (ctx, stdout, 0);
  exit (exit_status);

 version:
  g_print (_("jmlite %s\n"
	     "Written by David A. Helder\n"
	     "\n"
	     "Copyright (C) 2000 The Regents of the University of Michigan\n"
	     "This is free software; see the source for copying conditions.  There is NO\n"
	     "warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"), 
	   VERSION);
  exit(exit_status);
}





gboolean
readline_cb (GIOChannel* iochannel, GNetIOChannelReadAsyncStatus status, 
	     gchar* buffer, guint length, gpointer user_data)
{
  gchar* tokens[MAX_TOKENS] = {NULL, };
  gint toknum;
  gchar* p, *end;

  g_assert (status == GNET_IOCHANNEL_READ_ASYNC_STATUS_OK);

  if (!length)
    {
      readasync_id = NULL;

      includes[include_level] = NULL;
      if (include_level)
	{
	  --include_level;
	  readasync_id = 
	    gnet_io_channel_readline_async (includes[include_level], 
					    line_buffer, sizeof(line_buffer), 
					    0, readline_cb, NULL);
	}

      return FALSE;
    }

  /* If the command line is the first line, then just read the command
     line */
  if (clfl && buffer[0] != '#')
    {
      gchar* buffer2;
      int argc = 0;
      char** argv = NULL;

      clfl = FALSE;

      /* Convert line to argv format */
      buffer2 = g_strconcat ("jmlite ", buffer, NULL);
      poptParseArgvString (buffer2, &argc, (const char***) &argv);
      g_free (buffer2);

      /* Start the backend using this format */
      start_backend (argc, argv);

      free (argv);

      return TRUE;
    }

  /* Tokenize */
  toknum = 0;
  p = buffer;
  end = &buffer[length];

  while (toknum < MAX_TOKENS)
    {
      gboolean quoted = FALSE;

      /* Skip initial whitespace */
      while (p < end && (isspace((int) *p) || !*p))
	++p;
      if (p == end)
	break;

      /* Skip if comment */
      if (*p == '#')
	break;

      /* Skip initial quote */
      if (*p == '\"')
	{
	  quoted = TRUE;
	  ++p;
	  if (p == end)
	    break;
	}

      /* Save token beginning */
      tokens[toknum++] = p;

      /* Read until next quote or white space */
      if (quoted)
	{
	  while (p < end && (*p != '\"'))
	    ++p;
	}
      else
	{
	  while (p < end && !(isspace((int) *p) || !*p))
	    ++p;
	}

      *p = '\0';
      if (p == end)	/* assume end < buffer[sizeof(buffer)] */
	break;

      ++p;
    }

  if (toknum == 0)
    {
      if (!no_prompt)
	g_print ("> ");
      return TRUE;
    }

  /* TODO: MOVE ALL THIS STUFF INTO FUNCTIONS! */
  if (!g_strcasecmp ("help", tokens[0]) || 
	   !g_strcasecmp ("?", tokens[0]))
    {
      g_print (_("jmlite console help\n"
		 "  COMMAND                     ARGUMENTS (<required> [optional])\n"
		 "  create_channel              <path> [channels_only]\n"
		 "  remove_channel              <name>\n"
		 "  open_channel                <url>\n"
		 "  add_file                    <path> <filename>\n"
		 "  remove_file, rm             <path>\n"
		 "  add_directory               <parent path> <dirname> [swallow]\n"
		 "  create_directory, mkdir     <path>\n"
		 "  remove_directory, rmdir     <path>\n"
		 "  list, ls                    [path]\n"
		 "  add_url                     <path> <url>\n"
		 "  remove_url                  <path>\n"
		 "  create_chat                 <path>\n"
		 "  remove_chat                 <name>\n"
		 "  create_search               <path>\n"
		 "  remove_search               <name>\n"
		 "  include                     <path>\n"
		 "  help\n"
		 "  quit\n"
		 ));
    }

  else if (!g_strcasecmp ("quit", tokens[0]) || 
	   !g_strcasecmp ("exit", tokens[0]))
    {
      try_exit ();
      readasync_id = NULL;
      return FALSE;
    }

  else if (!g_strcasecmp ("include", tokens[0]) && tokens[1])
    {
      int fd;

      if ((include_level + 1) < MAX_INCLUDES)
	{
	  fd = open (tokens[1], O_RDONLY);
	  if (fd != -1)
	    {
	      ++include_level;
	      includes[include_level] = g_io_channel_unix_new (fd);
	      readasync_id = 
		gnet_io_channel_readline_async (includes[include_level], 
						line_buffer, sizeof(line_buffer), 
						0, readline_cb, NULL);
	      return FALSE;
	    }
	  else
	    error (_("Bad include filename: %s\n"), tokens[1]);
	}
      else
	error (_("Maximum include depth of %d exceeded\n"), MAX_INCLUDES);
    }

  else if (!g_strcasecmp ("create_channel", tokens[0]) && tokens[1])
    create_channel (tokens[1], tokens[2] != NULL);

  else if (!g_strcasecmp ("remove_channel", tokens[0]) && tokens[1])
    remove_channel (tokens[1]);

  else if (!g_strcasecmp ("open_channel", tokens[0]) && tokens[1])
    open_channel (tokens[1]);

  else if ((!g_strcasecmp ("list", tokens[0]) ||
	    !g_strcasecmp ("ls", tokens[0])))
    list (tokens[1]);

  else if (!g_strcasecmp ("add_file", tokens[0]) && tokens[1] && tokens[2])
    add_file (tokens[1], tokens[2]);

  else if ((!g_strcasecmp ("remove_file", tokens[0]) ||
	    !g_strcasecmp ("rm", tokens[0])) && tokens[1])
    remove_file (tokens[1]);

  else if (!g_strcasecmp ("add_directory", tokens[0]) && tokens[1] && tokens[2])
    add_dir (tokens[1], tokens[2], tokens[3] != NULL);

  else if ((!g_strcasecmp ("create_directory", tokens[0]) ||
	    !g_strcasecmp ("mkdir", tokens[0])) && tokens[1])
    create_dir (tokens[1]);

  else if ((!g_strcasecmp ("remove_directory", tokens[0]) ||
	    !g_strcasecmp ("rmdir", tokens[0])) && tokens[1])
    remove_dir (tokens[1]);

  else if (!g_strcasecmp ("add_url", tokens[0]) && tokens[1] && tokens[2])
    add_url (tokens[1], tokens[2]);

  else if (!g_strcasecmp ("remove_url", tokens[0]) && tokens[1])
    remove_url (tokens[1]);

  else if (!g_strcasecmp ("create_chat", tokens[0]) && tokens[1])
    create_chat (tokens[1]);

  else if (!g_strcasecmp ("remove_chat", tokens[0]) && tokens[1])
    remove_chat (tokens[1]);

  else if (!g_strcasecmp ("create_search", tokens[0]) && tokens[1])
    create_search (tokens[1]);

  else if (!g_strcasecmp ("remove_search", tokens[0]) && tokens[1])
    remove_search (tokens[1]);

  else
    {
      error (_("Bad command: %s\n"), tokens[0]);
    }

  if (shutting_down)
    return FALSE;

  if (!no_prompt)
    g_print ("> ");

  return TRUE;
}


static void	
add_url (const gchar* path, const gchar* urlstr)
{
  gchar*     basename;
  gchar*     dirname  = NULL;
  JMChannel* pchannel = NULL;
  JMAnn*     pann     = NULL;
  GURL*      url;
  JMAnn*     ann;

  g_return_if_fail (path);

  basename = g_basename (path);
  dirname  = g_dirname (path);

  g_return_if_fail (basename);
  g_return_if_fail (dirname);

  /* Check URL */
  url = gnet_url_new (urlstr);
  if (!url)
    {
      error (_("Malformed URL: %s\n"), urlstr);
      goto error;
    }
     
  /* Check path */
  parse_path (dirname, &pchannel, &pann);
  if (!pchannel || !pann)
    {
      error (_("Bad path: %s\n"), path);
      goto error;
    }

  /* Check if already exists */
  if (pchannel && jmchannel_get (pchannel, basename))
    {
      error (_("%s already exists on channel %s\n"), 
	     path, jmchannel_get_name(pchannel));
      goto error;
    }

  g_print (_("URL %s add\n"), urlstr);

  /* Add to hashtable */
  g_hash_table_insert (url_to_obj, url, NULL);

  /* Advertise on channel */
  ann = jmchannel_add (pchannel, pann, basename, url);
  if (!ann)
    error (_("Could not advertise URL %s on channel %s\n"),
	   urlstr, jmchannel_get_name(pchannel));

 error:
  g_free (dirname);
}



static void
remove_url (const gchar* path)
{
  JMChannel* pchannel;
  JMAnn*     ann;
  GURL*	     url;
  GURL*	     orig_url;
  

  parse_path (path, &pchannel, &ann);
  if (!pchannel || !ann)
    {
      error (_("URL %s does not exist\n"), path);
      return;
    }

  if (!ann->is_local)
    {
      error (_("%s is not a local URL\n"), path);
      return;
    }

  url = jmann_get_url (ann);
  if (!url || g_hash_table_lookup (url_to_obj, url))
    {
      error (_("%s is not a URL\n"), path);
      return;
    }

  g_assert (g_hash_table_lookup_extended (url_to_obj, url, (gpointer) &orig_url, NULL));
  g_hash_table_remove (url_to_obj, url);
  gnet_url_delete (orig_url);

  jmchannel_remove (pchannel, ann);
}



static void
create_channel (const gchar* path, gboolean channels_only)
{
  gchar*     basename;
  gchar*     dirname  = NULL;
  JMChannel* pchannel = NULL;
  JMAnn*     pann     = NULL;
  JMChannel* channel;
  gchar*     url_str;

  g_return_if_fail (path);

  basename = g_basename (path);
  dirname  = g_dirname (path);

  g_return_if_fail (basename);
  g_return_if_fail (dirname);

  /* Make sure it doesn't exist */
  if (g_hash_table_lookup (name_to_channel, basename))
    {
      error (_("Channel %s already exists\n"), basename);
      goto error;
    }

  /* Check path */
  parse_path (dirname, &pchannel, &pann);
  if (strcmp(dirname, ".") && !(pchannel && pann))
    {
      error (_("Bad path: %s\n"), path);
      goto error;
    }

  /* Check if already exists */
  if (pchannel && jmchannel_get (pchannel, basename))
    {
      error (_("%s already exists on channel %s\n"), 
	     path, jmchannel_get_name(pchannel));
      goto error;
    }

  /* Check if name exists */
  if (HAVE_NAME(basename))
    {
      error (_("Something named %s already exists\n"), basename);
      goto error;
    }

  /* Create channel */
  channel = jmchannel_create (jm_btp_bpeer, basename);
  if (!channel)
    {
      error (_("Could not create channel %s\n"), path);
      goto error;
    }

  /* Initialize */
  channel->info_func  = channel_info;
  channel->error_func = channel_error;

  url_str = gnet_url_get_nice_string (channel->url);
  g_print (_("Channel %s created (URL is %s)\n"),
	   jmchannel_get_name(channel), url_str);
  g_free (url_str);

  /* Add to hashtable */
  g_hash_table_insert (name_to_channel, g_strdup(basename), channel);
  g_hash_table_insert (url_to_obj, channel->url, channel);

  /* Advertise channel */
  if (pchannel && pann)
    {
      JMAnn* ann;

      ann = jmchannel_add (pchannel, pann, basename, channel->url);
      if (!ann)
	error (_("Could not advertise channel %s on channel %s\n"),
	       jmchannel_get_name(channel), jmchannel_get_name(pchannel));
    }

 error:
  g_free (dirname);
}


static void
remove_channel (const gchar* name)
{
  gchar*     orig_name;
  JMChannel* channel;

  /* TODO: Send removal announcements */

  /* Lookup channel */
  if (!g_hash_table_lookup_extended (name_to_channel, name, 
				     (gpointer) &orig_name, (gpointer) &channel))
    {
      error (_("%s does not exist\n"), name);
      return;
    }

  /* Delete it */
  g_hash_table_remove (name_to_channel, name);
  g_hash_table_remove (url_to_obj, channel->url);
  g_free (orig_name);
  jmchannel_delete (channel);
}


static void
open_channel (const gchar* urlstr)
{
  GURL*      url;
  JMChannel* channel;
  gchar*     name;

  url = gnet_url_new (urlstr);
  if (!url || strcmp (url->protocol, "jm"))
    {
      error (_("Malformed URL: %s\n"), urlstr);
      gnet_url_delete (url);
      return;
    }

  channel = (JMChannel*) g_hash_table_lookup (url_to_obj, url);
  if (channel)
    {
      error (_("Channel %s is already open\n"), urlstr);
      gnet_url_delete (url);
      return;
    }

  channel = jmchannel_join (jm_btp_bpeer, url);
  if (!channel)
    {
      error (_("Could not open channels %s\n"), url->resource);
      gnet_url_delete (url);
      return;
    }

  /* Initialize */
  channel->info_func  = channel_info;
  channel->error_func = channel_error;

  /* Add to hashtable */
  name = strrchr(url->resource, '/');
  g_return_if_fail (name);
  ++name;
  g_return_if_fail (*name);

  g_hash_table_insert (name_to_channel, g_strdup(name), channel);
  g_hash_table_insert (url_to_obj, channel->url, channel);
  gnet_url_delete (url);
}


static void
list (const gchar* path)
{
  if (path)
    {
      JMChannel* channel;
      JMAnn* ann;
      GList* i;

      parse_path (path, &channel, &ann);
      if (!channel || !ann)
	{
	  error (_("Bad path: %s\n"), path);
	  return;
	}

      g_print (_("%s%s/:\n"), jmchannel_get_name(channel), ann->path);
      for (i = ann->kids; i != NULL; i = i->next)
	{
	  JMAnn* kid = (JMAnn*) i->data;
	  g_print (_("\t%s\n"), kid->name);
	}
    }
  else
    {
      g_print (_("/:\n"));
      g_hash_table_foreach (name_to_channel, list_hfunc, NULL);
    }
}


static void
list_hfunc (gpointer key, gpointer value, gpointer user_data)
{
  gchar* name = (gchar*) key;

  g_print (_("\t%s\n"), name);
}


static void
add_file (const gchar* path, const gchar* name)
{
  gchar*     filename;
  JMChannel* pchannel = NULL;
  JMAnn*     pann     = NULL;
  GURL*      url;
  JMAnn*     ann;
  MtpLocalMirror* mirror;
  gchar*     resource;
  gchar*     urlstr;

  g_return_if_fail (path);
  g_return_if_fail (name);

  /* Check if file exists */
  filename = tilde_expand (name);
  if (!file_exists (filename))
    {
      error (_("Bad path: %s\n"), name);
      goto error;
    }

  /* Check path */
  parse_path (path, &pchannel, &pann);
  if (!pchannel || !pann)
    {
      error (_("Bad path: %s\n"), path);
      goto error;
    }

  /* Check if already exists */
  if (jmann_get_child (pann, g_basename(name)))
    {
      error (_("%s already exists on channel %s\n"), 
	     g_basename(name), jmchannel_get_name(pchannel));
      goto error;
    }

  /* Create MTP URL */
  url = gnet_url_clone (jm_mtp_rvous_url);
  resource = g_strconcat(pann->path, "/", g_basename(name), NULL);
  gnet_url_set_resource (url, resource);
  g_free (resource);
  g_return_if_fail (!g_hash_table_lookup (url_to_obj, url));

  /* Create MTP client */
  mirror = mtp_mirror_path (jm_mtp_server, url, filename);
  if (!mirror)
    {
      error (_("MTP Error: %s\n"), path);
      gnet_url_delete (url);
      goto error;
    }

  urlstr = gnet_url_get_nice_string (url);
  g_print (_("File name added (URL is %s\n"), urlstr);
  g_free (urlstr);

  /* Add to hashtable */
  g_hash_table_insert (url_to_obj, url, mirror);

  /* Advertise on channel */
  ann = jmchannel_add (pchannel, pann, g_basename(name), url);
  if (!ann)
    error (_("Could not advertise file %s on channel %s\n"),
	   g_basename(name), jmchannel_get_name(pchannel));

 error:
  g_free (filename);
}


static void
remove_file (const gchar* path)
{
  JMChannel* pchannel;
  JMAnn*     ann;
  GURL*	     url;
  GURL*	     orig_url;
  MtpLocalMirror* mirror;

  parse_path (path, &pchannel, &ann);
  if (!pchannel || !ann)
    {
      error (_("File %s does not exist\n"), path);
      return;
    }

  if (!ann->is_local)
    {
      error (_("%s is not a local file\n"), path);
      return;
    }

  url = jmann_get_url (ann);
  if (!url || strcmp(url->protocol, "mtp"))
    {
      error (_("%s is not a file\n"), path);
      return;
    }

  g_assert (g_hash_table_lookup_extended (url_to_obj, url, (gpointer) &orig_url, (gpointer) &mirror));
  g_hash_table_remove (url_to_obj, url);
  gnet_url_delete (orig_url);

  jmchannel_remove (pchannel, ann);

  g_return_if_fail (mirror);
  mtp_unmirror (mirror);
}


static void
add_dir (const gchar* path, const gchar* name, gboolean swallow)
{
  error (_("Implement me.\n"));
}


static void
create_dir (const gchar* path)
{
  gchar*     basename;
  gchar*     dirname  = NULL;
  JMChannel* pchannel = NULL;
  JMAnn*     pann     = NULL;
  JMAnn*     ann;

  g_return_if_fail (path);

  basename = g_basename (path);
  dirname = g_dirname (path);

  /* Check path */
  parse_path (dirname, &pchannel, &pann);
  if (!pchannel || !pann)
    {
      error (_("Bad path: %s\n"), path);
      goto error;
    }

  /* Check if already exists */
  if (jmann_get_child (pann, basename))
    {
      error (_("%s already exists on channel %s\n"), 
	     basename, jmchannel_get_name(pchannel));
      goto error;
    }

  /* Advertise on channel */
  ann = jmchannel_add (pchannel, pann, basename, NULL);
  if (!ann)
    error (_("Could not advertise directory %s on channel %s\n"),
	   path, jmchannel_get_name(pchannel));

 error:
  g_free (dirname);

}


static void
remove_dir (const gchar* path)
{
  JMChannel* pchannel;
  JMAnn*     ann;

  parse_path (path, &pchannel, &ann);
  if (!pchannel || !ann)
    {
      error (_("Directory %s does not exist\n"), path);
      return;
    }

  if (!ann->is_local)
    {
      error (_("%s is not a local directory\n"), path);
      return;
    }

  if (jmann_get_url (ann))
    {
      error (_("%s is not a directory\n"), path);
      return;
    }

  jmchannel_remove (pchannel, ann);
}



static void
create_chat (const gchar* path)
{
  gchar*     basename;
  gchar*     dirname  = NULL;
  JMChannel* pchannel = NULL;
  JMAnn*     pann     = NULL;
  JMChat*    chat;
  gchar*     url_str;

  g_return_if_fail (path);

  basename = g_basename (path);
  dirname  = g_dirname (path);

  g_return_if_fail (basename);
  g_return_if_fail (dirname);

  /* Check path */
  parse_path (dirname, &pchannel, &pann);
  if (strcmp(dirname, ".") && !(pchannel && pann))
    {
      error (_("Bad path: %s\n"), path);
      goto error;
    }

  /* Check if already exists */
  if (pchannel && jmchannel_get (pchannel, basename))
    {
      error (_("%s already exists on channel %s\n"), 
	     path, jmchannel_get_name(pchannel));
      goto error;
    }

  /* Check if name exists */
  if (HAVE_NAME(basename))
    {
      error (_("Something named %s already exists\n"), basename);
      goto error;
    }

  /* Create chat */
  chat = jmchat_create (jm_btp_bpeer, basename, NULL);
  if (!chat)
    {
      error (_("Could not create chat %s\n"), path);
      goto error;
    }

  /* TODO: Initialize */

  url_str = gnet_url_get_nice_string (chat->url);
  g_print (_("Chat %s created (URL is %s)\n"),
	   basename, url_str);
  g_free (url_str);

  /* Add to hashtable */
  g_hash_table_insert (name_to_chat, g_strdup(basename), chat);
  g_hash_table_insert (url_to_obj, chat->url, chat);

  /* Advertise channel */
  if (pchannel && pann)
    {
      JMAnn* ann;

      ann = jmchannel_add (pchannel, pann, basename, chat->url);
      if (!ann)
	error (_("Could not advertise chat %s on channel %s\n"),
	       basename, jmchannel_get_name(pchannel));
    }

 error:
  g_free (dirname);
}


static void
remove_chat (const gchar* name)
{
  gchar*     orig_name;
  JMChat*    chat;

  /* TODO: Send removal announcements */

  /* Lookup chat */
  if (!g_hash_table_lookup_extended (name_to_chat, name, 
				     (gpointer) &orig_name, (gpointer) &chat))
    {
      error (_("%s does not exist\n"), name);
      return;
    }

  /* Delete it */
  g_hash_table_remove (name_to_chat, name);
  g_hash_table_remove (url_to_obj, chat->url);
  g_free (orig_name);
  jmchat_delete (chat);
}


static void
create_search (const gchar* path)
{
  gchar*     basename;
  gchar*     dirname  = NULL;
  JMChannel* pchannel = NULL;
  JMAnn*     pann     = NULL;
  JMMSearch* search;
  gchar*     url_str;

  g_return_if_fail (path);

  basename = g_basename (path);
  dirname  = g_dirname (path);

  g_return_if_fail (basename);
  g_return_if_fail (dirname);

  /* Check path */
  parse_path (dirname, &pchannel, &pann);
  if (strcmp(dirname, ".") && !(pchannel && pann))
    {
      error (_("Bad path: %s\n"), path);
      goto error;
    }

  /* Check if already exists */
  if (pchannel && jmchannel_get (pchannel, basename))
    {
      error (_("%s already exists on channel %s\n"), 
	     path, jmchannel_get_name(pchannel));
      goto error;
    }

  /* Check if name exists */
  if (HAVE_NAME(basename))
    {
      error (_("Something named %s already exists\n"), basename);
      goto error;
    }

  /* Create search */
  search = jmmsearch_create (jm_btp_bpeer, basename);
  if (!search)
    {
      error (_("Could not create search %s\n"), path);
      goto error;
    }

  /* TODO: Initialize */

  url_str = gnet_url_get_nice_string (search->jmmsp->url);
  g_print (_("Search %s created (URL is %s)\n"),
	   basename, url_str);
  g_free (url_str);

  /* Add to hashtable */
  g_hash_table_insert (name_to_search, g_strdup(basename), search);
  g_hash_table_insert (url_to_obj, search->jmmsp->url, search);

  /* Advertise channel */
  if (pchannel && pann)
    {
      JMAnn* ann;

      ann = jmchannel_add (pchannel, pann, basename, search->jmmsp->url);
      if (!ann)
	error (_("Could not advertise search %s on channel %s\n"),
	       basename, jmchannel_get_name(pchannel));
    }

 error:
  g_free (dirname);
}


static void
remove_search (const gchar* name)
{
  gchar*       orig_name;
  JMMSearch*   search;

  /* TODO: Send removal announcements */

  /* Lookup search */
  if (!g_hash_table_lookup_extended (name_to_search, name, 
				     (gpointer) &orig_name, (gpointer) &search))
    {
      error (_("%s does not exist\n"), name);
      return;
    }

  /* Delete it */
  g_hash_table_remove (name_to_search, name);
  g_hash_table_remove (url_to_obj, search->jmmsp->url);
  g_free (orig_name);
  jmmsearch_delete (search);
}




/* ******************** */


static void
parse_path (const gchar* path, JMChannel** pchannel, JMAnn** pann)
{
  const gchar* p;
  const gchar* end;
  gchar* name = NULL;

  g_return_if_fail (path);
  g_return_if_fail (pchannel);
  g_return_if_fail (pann);

  *pchannel = NULL;
  *pann     = NULL;

  if (!strcmp(path, "."))
    return;

  /* Skip the initial /, if there is one */
  p = path;
  if (p[0] == '/')
    p = &p[1];

  /* Get the channel name */
  end = strchr (p, '/');
  if (end)
    name = g_strndup (p, end - p);
  else
    name = g_strdup (p);

  /* Get the channel */
  (*pchannel) = (JMChannel*) g_hash_table_lookup (name_to_channel, name);
  g_free (name);
  if (!*pchannel)
    return;

  /* If there is no path, then it refers to the root */
  if (!end)
    {
      *pann = (*pchannel)->root_ann;
      return;
    }

  /* Get the announcement */
  *pann = jmchannel_get (*pchannel, end);
}



/* **************************************** */


static void
channel_info (JMChannel* channel, JMChannelType type, 
		 JMAnn* ann, gpointer user_data)
{
  GURL* url = NULL;
  gchar* url_str = NULL;

  switch (type)
    {
    case JMCHANNEL_UPDATE: 
      {
	if (url_str)
	  g_print (_("[Update %s:%s (%s)]\n"), 
		   jmchannel_get_name(channel), ann->path, url_str);
	else
	  g_print (_("[Update %s:%s]\n"), 
		   jmchannel_get_name(channel), ann->path);
	break;
      }

    case JMCHANNEL_REMOVE: 
    case JMCHANNEL_REMOVE_TIMEOUT: 
      {
	g_print (_("[Remove %s:%s]\n"), jmchannel_get_name(channel), ann->path);
	break;
      }

    default: g_assert_not_reached();
    }

  gnet_url_delete (url);
  g_free (url_str);
}


static void
channel_error (JMChannel* jmchannel, gpointer user_data)
{
  g_print ("JMChannel Error: %s\n", jmchannel_get_name(jmchannel));
}


/* **************************************** */



static void
error (const gchar *format, ...)
{
  va_list args;
  va_start (args, format);
  g_print (_("Error: "));
  vfprintf(stderr, format, args);
  va_end (args);

  if (fail_on_error)
    {
      g_return_if_fail (shutting_down == FALSE);

      shutting_down = TRUE;
      try_exit ();
    }
}


static void
sig_int_cb (int ignore)
{
  /* Reset signal handler */
  signal (SIGINT, SIG_DFL);

  /* Set shutting down flag */
  g_return_if_fail (shutting_down == FALSE);
  shutting_down = TRUE;

  /* Read no more input */
  if (readasync_id)
    {
      gnet_io_channel_read_async_cancel (readasync_id);
      readasync_id = NULL;
    }

  try_exit ();
}


static void
sig_pipe_cb (int ignore)
{
  /* Reset both signal handlers */
  signal (SIGPIPE, SIG_DFL);
  signal (SIGINT, SIG_DFL);

  g_print (_("\nERROR: Caught SIGPIPE signal.  This is a bug.  Dumping core.\n"));
  abort ();
}


static void
try_exit (void)
{
  /* Delete objects */
  g_hash_table_foreach (name_to_channel, delete_name_to_whatever_hfunc, NULL);
  g_hash_table_destroy (name_to_channel);
  name_to_channel = NULL;
  g_hash_table_foreach (name_to_chat, delete_name_to_whatever_hfunc, NULL);
  g_hash_table_destroy (name_to_chat);
  name_to_chat = NULL;
  g_hash_table_foreach (name_to_search, delete_name_to_whatever_hfunc, NULL);
  g_hash_table_destroy (name_to_search);
  name_to_search = NULL;
  g_hash_table_foreach (url_to_obj, delete_url_to_obj_hfunc, NULL);
  g_hash_table_destroy (url_to_obj);
  url_to_obj = NULL;

  /* Shutdown */
  b_conn_num_conns_zero = conn_zero_cb;
  if (!jm_running || jmutil_shutdown())
    {
      exit (EXIT_SUCCESS);
    }
  else
    {
      g_print (_("Exiting... (waiting for %d connections)..."), b_conn_num_conns);
      g_timeout_add (5000, (GSourceFunc) exit_timeout, (void*) 0);
    }
}


static void
delete_name_to_whatever_hfunc (gpointer key, gpointer value, gpointer user_data)
{
  g_free ((gchar*) key);
}


static void
delete_url_to_obj_hfunc (gpointer key, gpointer value, gpointer user_data)
{
  GURL* url;

  url = (GURL*) key;

  if (!strcmp(url->protocol, "jm"))
    {
      JMChannel* channel = (JMChannel*) value;
      jmchannel_delete (channel);
    }
  else if (!strcmp(url->protocol, "jmchat"))
    {
      JMChat* chat = (JMChat*) value;
      jmchat_delete (chat);
    }
  else if (!strcmp(url->protocol, "jmmsp"))
    {
      JMMSearch* search = (JMMSearch*) value;
      jmmsearch_delete (search);
    }
  else if (!strcmp(url->protocol, "mtp"))
    {
      MtpLocalMirror* mirror = (MtpLocalMirror*) value;
      mtp_unmirror (mirror);
    }
  else if (!value)
    {
      gnet_url_delete (url);
    }
  else
    g_assert_not_reached();
}


static void
conn_zero_cb (void)
{
  fprintf (stderr, " done\n");
  exit (EXIT_SUCCESS);
}


static gboolean
exit_timeout (gpointer p)
{
  exit (EXIT_SUCCESS);
}


syntax highlighted by Code2HTML, v. 0.9.1