/*   -*- c -*-
 * 
 *  ----------------------------------------------------------------------
 *  CcXstream test client for XBOX Media Server
 *  ----------------------------------------------------------------------
 *
 *  Copyright (c) 2002-2003 by PuhPuh
 *  
 *  This code is copyrighted property of the author.  It can still
 *  be used for any non-commercial purpose following conditions:
 *  
 *      1) This copyright notice is not removed.
 *      2) Source code follows any distribution of the software
 *         if possible.
 *      3) Copyright notice above is found in the documentation
 *         of the distributed software.
 *  
 *  Any express or implied warranties are disclaimed.  Author is
 *  not liable for any direct or indirect damages caused by the use
 *  of this software.
 *
 *  ----------------------------------------------------------------------
 *
 */

#include "ccincludes.h"
#include "ccbuffer.h"
#include "ccxclient.h"
#include "ccxencode.h"

#ifdef HAVE_READLINE
#include <readline/readline.h>
#endif /* HAVE_READLINE */

const char *av0;

char *discovered_server = NULL;
int discovered_port = 0;

void usage(int exitval);
char *cc_readline(const char *prompt);

char *cc_readline(const char *prompt)
{
  if (prompt == NULL)
    prompt = "";
  
#ifdef HAVE_READLINE
  {
    char *l, *r;

    l = readline(prompt);
    if (l == NULL)
      return NULL;
    r = xx_xstrdup(l);
    free(l); /* Not cc_xfree here. */
    return r;
  }
#else /* HAVE_READLINE */
  {
    char buf[1024];
    size_t len;

    fputs(prompt, stdout);
    fflush(stdout);
    if (fgets(buf, sizeof (buf), stdin) == NULL)
      return NULL;
    len = strlen(buf);
    while ((len > 0) && ((buf[len - 1] == '\n') || (buf[len - 1] == '\r')))
      {
	len--;
	buf[len] = '\0';
      }
    return cc_xstrdup(buf);
  }
#endif /* HAVE_READLINE */
}

CcXstreamClientError multi_get_file(CcXstreamServerConnection s, const char *path, int n)
{
  unsigned long *handle;
  unsigned char *opened, something_done;
  CcXstreamClientError rv;
  unsigned char *data;
  size_t data_len;
  int i;

  handle = cc_xcalloc(n, sizeof (handle[0]));
  opened = cc_xcalloc(n, sizeof (opened[0]));
  something_done = 0;
  for (i = 0; i < n; i++)
    {
      rv = cc_xstream_client_file_open(s, path, &(handle[i]));
      if (rv == CC_XSTREAM_CLIENT_OK)
	{
	  opened[i] = 1;
	  something_done = 1;
	  fprintf(stderr, "+");
	}
      else if (rv == CC_XSTREAM_CLIENT_FATAL_ERROR)
	{
	  cc_xfree(handle);
	  cc_xfree(opened);
	  return CC_XSTREAM_CLIENT_FATAL_ERROR;
	}
      else
	{
	  fprintf(stderr, "-");
	}
    }
  if (! something_done)
    {
      cc_xfree(handle);
      cc_xfree(opened);
      return CC_XSTREAM_CLIENT_COMMAND_FAILED;
    }
  while (1)
    {
      something_done = 0;
      for (i = 0; i < n; i++)
	{
	  if (opened[i])
	    {
	      switch (rv = cc_xstream_client_file_read(s, handle[i], 2048, &data, &data_len))
		{
		case CC_XSTREAM_CLIENT_OK:
		  if (data_len == 0)
		    {
		      fprintf(stderr, "*");
		    }
		  else
		    {
		      something_done = 1;
		    }
		  cc_xfree(data);
		  break;
		  
		case CC_XSTREAM_CLIENT_COMMAND_FAILED:
		  fprintf(stderr, "!");
		  break;
		  
		case CC_XSTREAM_CLIENT_FATAL_ERROR:
		  cc_xfree(handle);
		  cc_xfree(opened);
		  return CC_XSTREAM_CLIENT_FATAL_ERROR;
		}
	    }
	}
      if (! something_done)
	goto close_and_return;
    }
 close_and_return:
  for (i = 0; i < n; i++)
    {
      if (opened[i])
	switch (cc_xstream_client_close(s, handle[i]))
	  {
	  case CC_XSTREAM_CLIENT_OK:
	    break;
	    
	  case CC_XSTREAM_CLIENT_COMMAND_FAILED:
	    /* Failing here means we are out of sync somehow and it's fatal. */
	    cc_xfree(handle);
	    cc_xfree(opened);
	    return CC_XSTREAM_CLIENT_FATAL_ERROR;
	    
	  case CC_XSTREAM_CLIENT_FATAL_ERROR:
	    cc_xfree(handle);
	    cc_xfree(opened);
	    return CC_XSTREAM_CLIENT_FATAL_ERROR;
	  }
    }
  cc_xfree(handle);
  cc_xfree(opened);
  return CC_XSTREAM_CLIENT_OK;
  /*NOTREACHED*/
}

CcXstreamClientError get_file(CcXstreamServerConnection s, const char *path, int print)
{
  unsigned long handle;
  CcXstreamClientError rv;
  unsigned char *data;
  size_t data_len;

  rv = cc_xstream_client_file_open(s, path, &handle);
  if (rv != CC_XSTREAM_CLIENT_OK)
    return rv;
  while (1)
    {
      switch (rv = cc_xstream_client_file_read(s, handle, print ? 100 : 2048, &data, &data_len))
	{
	case CC_XSTREAM_CLIENT_OK:
	  if (data_len == 0)
	    {
	      goto close_and_return;
	    }
	  if (print)
	    {
	      fwrite(data, 1, data_len, stdout);
	    }
	  cc_xfree(data);
	  break;

	case CC_XSTREAM_CLIENT_COMMAND_FAILED:
	  goto close_and_return;

	case CC_XSTREAM_CLIENT_FATAL_ERROR:
	  return CC_XSTREAM_CLIENT_FATAL_ERROR;
	}
      
    }
 close_and_return:
  switch (cc_xstream_client_close(s, handle))
    {
    case CC_XSTREAM_CLIENT_OK:
      return rv;

    case CC_XSTREAM_CLIENT_COMMAND_FAILED:
      /* Failing here means we are out of sync somehow and it's fatal. */
      return CC_XSTREAM_CLIENT_FATAL_ERROR;

    case CC_XSTREAM_CLIENT_FATAL_ERROR:
      return CC_XSTREAM_CLIENT_FATAL_ERROR;
    }
  /*NOTREACHED*/
}

CcXstreamClientError list_dir(CcXstreamServerConnection s, int full)
{
  unsigned long handle;
  CcXstreamClientError rv;
  char *name, *info;
  int i = 0;

  rv = cc_xstream_client_dir_open(s, &handle);
  if (rv != CC_XSTREAM_CLIENT_OK)
    return rv;
  while (1)
    {
      switch (rv = cc_xstream_client_dir_read(s, handle, &name, &info))
	{
	case CC_XSTREAM_CLIENT_OK:
	  if (strlen(name) == 0)
	    {
	      cc_xfree(name);
	      cc_xfree(info);
	      return CC_XSTREAM_CLIENT_OK;
	    }
	  if (full)
	    fprintf(stderr, "%2d) name = \"%s\" info = \"%s\"\n", ++i, name, info);
	  else
	    fprintf(stderr, "%2d) %s\n", ++i, name);
	  cc_xfree(name);
	  cc_xfree(info);
	  break;

	case CC_XSTREAM_CLIENT_COMMAND_FAILED:
	  goto close_and_return;

	case CC_XSTREAM_CLIENT_FATAL_ERROR:
	  return CC_XSTREAM_CLIENT_FATAL_ERROR;
	}
      
    }
 close_and_return:
  switch (cc_xstream_client_close(s, handle))
    {
    case CC_XSTREAM_CLIENT_OK:
      return rv;

    case CC_XSTREAM_CLIENT_COMMAND_FAILED:
      /* Failing here means we are out of sync somehow and it's fatal. */
      return CC_XSTREAM_CLIENT_FATAL_ERROR;

    case CC_XSTREAM_CLIENT_FATAL_ERROR:
      return CC_XSTREAM_CLIENT_FATAL_ERROR;
    }
  /*NOTREACHED*/
}

CcXstreamClientError fuzzy_get_file(CcXstreamServerConnection s, const char *path, int print)
{
  unsigned long handle;
  CcXstreamClientError rv;
  unsigned char *data;
  size_t data_len, read_len;
  int last_read_was_truncated;

  rv = cc_xstream_client_file_open(s, path, &handle);
  if (rv != CC_XSTREAM_CLIENT_OK)
    return rv;
  read_len = print ? 100 : 2048;
  while (1)
    {
      /* Read 100 (or 2048 bytes) */
      switch (rv = cc_xstream_client_file_read(s, handle, read_len, &data, &data_len))
	{
	case CC_XSTREAM_CLIENT_OK:
	  if (data_len == 0)
	    {
	      goto close_and_return;
	    }
	  if (print)
	    {
	      fwrite(data, 1, data_len, stdout);
	    }
	  last_read_was_truncated = (data_len != read_len);
	  cc_xfree(data);
	  break;

	case CC_XSTREAM_CLIENT_COMMAND_FAILED:
	  goto close_and_return;

	case CC_XSTREAM_CLIENT_FATAL_ERROR:
	  return CC_XSTREAM_CLIENT_FATAL_ERROR;
	}
      /* Seek back 50 (or 1024 bytes) */
      if (! last_read_was_truncated)
	{
	  switch (rv = cc_xstream_client_file_backwards(s, handle, read_len / 2, 0))
	    {
	    case CC_XSTREAM_CLIENT_OK:
	      break;
	      
	    case CC_XSTREAM_CLIENT_COMMAND_FAILED:
	      goto close_and_return;
	      
	    case CC_XSTREAM_CLIENT_FATAL_ERROR:
	      return CC_XSTREAM_CLIENT_FATAL_ERROR;
	    }
	}
    }
 close_and_return:
  switch (cc_xstream_client_close(s, handle))
    {
    case CC_XSTREAM_CLIENT_OK:
      return rv;

    case CC_XSTREAM_CLIENT_COMMAND_FAILED:
      /* Failing here means we are out of sync somehow and it's fatal. */
      return CC_XSTREAM_CLIENT_FATAL_ERROR;

    case CC_XSTREAM_CLIENT_FATAL_ERROR:
      return CC_XSTREAM_CLIENT_FATAL_ERROR;
    }
  /*NOTREACHED*/
}

void discovery_cb(const char *addr, const char *port, const char *version, const char *comment, void *context)
{
  printf("Server: address = \"%s\"\n", addr);
  printf("        port    = \"%s\"\n", port);
  printf("        version = \"%s\"\n", version);
  printf("        comment = \"%s\"\n", comment);
  if (discovered_server == NULL)
    {
      discovered_server = cc_xstrdup(addr);
      discovered_port = atoi(port);
    }
}

CcXstreamClientError discover()
{
  return ccx_client_discover_servers(discovery_cb, NULL);
}

void usage(int exitval)
{
  fprintf(stderr, "Usage: %s [options] [host [port]]\n", av0);
  fprintf(stderr, "    -h                Print this message\n");
  fprintf(stderr, "    -p port           Use this port as default instead of port %d.\n",
	  (int)CC_XSTREAM_DEFAULT_PORT);
  fprintf(stderr, "    -D                Execute only the server discovery.\n");
  fprintf(stderr, "    -DD               Execute the server discovery and connect to\n");
  fprintf(stderr, "                      the first server that responds.\n");
  exit(exitval);
}

int main(int argc, char **argv)
{
  int port, rv, c, discover_only;
  char *host;
  CcBufferRec buf[0];
  char *ln = NULL;
  CcXstreamServerConnection s;

  if (strchr(argv[0], '/'))
    av0 = strrchr(argv[0], '/') + 1;
  else
    av0 = argv[0];

  cc_buffer_init(buf);
  rv = 0;
  discover_only = 0;
  port = CC_XSTREAM_DEFAULT_PORT;
  while ((c = getopt(argc, argv, "hdDp:")) != EOF)
    switch(c)
      {
      case 'h': 
	usage(0);
	
      case 'd':
	cc_debug_set_level(cc_debug_level() + 1);
	break;

      case 'p':
	port = atoi(optarg);
	if ((port < 1) || (port > 65535))
	  {
	    fprintf(stderr, "%s: Illegal port number.\n", av0);
	    usage(-1);
	  }
	break;

      case 'D':
	discover_only++;
	break;

      default:
	fprintf(stderr, "%s: Bad command line option -%c.\n", av0, optopt);
	usage(-1);
      }
  
  argc -= optind;
  argv += optind;

  if (discover_only == 1)
    {
      discover();
      exit(0);
      /*NOTREACHED*/
    }
  else if (discover_only > 1)
    {
      if (argc > 0)
	{
	  fprintf(stderr, "%s: Explicit address not allowed with server discovery.\n", av0);
	  usage(-1);
	}
      else
	{
	  char *a = NULL, *p = NULL;

	  discover();
	  if ((discovered_server == NULL) || (discovered_port == 0))
	    {
	      fprintf(stderr, "%s: Server discovery failed.\n", av0);
	      exit(1);
	    }
	  host = discovered_server;
	  port = discovered_port;
	  cc_xfree(p);
	  if ((port < 1) || (port > 65535))
	    {
	      fprintf(stderr, "%s: Server discovery returned invalid port.\n", av0);
	      exit(1);
	    }
	}
    }
  else
    {
      if (argc > 2)
	{
	  usage(-1);
	}
      if (argc > 1)
	{
	  port = atoi(argv[1]);
	  if ((port < 1) || (port > 65535))
	    {
	      fprintf(stderr, "%s: Illegal port number.\n", av0);
	      usage(-1);
	    }
	}
      if (argc > 0)
	{
	  host = cc_xstrdup(argv[0]);
	}
      else
	{
	  host = cc_xstrdup("localhost");
	}
    }
  fprintf(stderr, "Connecting to xbmsp://%s:%d/\n", host, port);

  if (cc_xstream_client_connect(host, port, &s) != CC_XSTREAM_CLIENT_OK)
    {
      fprintf(stderr, "%s: Can't connect to host %s.\n", av0, host);
      exit(1);
    }

  if (cc_xstream_client_version_handshake(s) != CC_XSTREAM_CLIENT_OK)
    {
      fprintf(stderr, "%s: Version handshake failed.\n", av0);
      rv = 2;
      goto out;
    }
  switch (list_dir(s, 0))
    {
    case CC_XSTREAM_CLIENT_OK:
      break;

    case CC_XSTREAM_CLIENT_COMMAND_FAILED:
      fprintf(stderr, "%s: Unable to list directory.  May need authentication.\n", av0);
      break;

    default:
      rv = 2;
      goto out;
    }
  while (1) {
    ln = cc_readline("> ");
    if (ln == NULL)
      {
	fprintf(stderr, "%s: EOF.  Goodbye.\n", av0);
	rv = 0;
	goto out;
      }
    else if (strcasecmp(ln, "quit") == 0)
      {
	fprintf(stderr, "%s: Goodbye.\n", av0);
	rv = 0;
	goto out;
      }
    else if (strcasecmp(ln, "help") == 0)
      {
	fprintf(stderr, "%s: Commands.\n", av0);
	fprintf(stderr, "  quit              - Quit this program\n");
	fprintf(stderr, "  help              - Print help\n");
	fprintf(stderr, "  dir               - List directory\n");
	fprintf(stderr, "  longdir           - List directory and attributes\n");
	fprintf(stderr, "  cd dir            - Change directory (.. = up one level,\n");
	fprintf(stderr, "                      / = root dir)\n");
	fprintf(stderr, "  get file          - Silently get (and ignore) the file\n");
	fprintf(stderr, "  cat file          - Get the file and print it to the screen\n");\
	fprintf(stderr, "  mget # file       - Silently get (and ignore) the file # times\n");
	fprintf(stderr, "  cget #1 #2 file   - Silently get (and ignore) the file #2 times\n");
	fprintf(stderr, "                      concurrently and repeat it #1 times\n");
	fprintf(stderr, "  option name value - set given server option to given value\n");
	fprintf(stderr, "  user id password  - authenticate yourself to the server with password\n");
      }
    else if (strcasecmp(ln, "discover") == 0)
      {
	discover(port, NULL, NULL, NULL);
      }
    else if ((strcasecmp(ln, "dir") == 0) ||
	     (strcasecmp(ln, "longdir") == 0))
      {
	int longdir;

	longdir = (strcasecmp(ln, "longdir") == 0);
	switch (list_dir(s, longdir))
	  {
	  case CC_XSTREAM_CLIENT_OK:
	    fprintf(stderr, "%s: List dir ok.\n", av0);
	    break;
	    
	  case CC_XSTREAM_CLIENT_COMMAND_FAILED:
	    fprintf(stderr, "%s: List dir failed.\n", av0);
	    break;
		
	  case CC_XSTREAM_CLIENT_FATAL_ERROR:
	    fprintf(stderr, "%s: List dir fatally failed.\n", av0);
	    rv = 2;
	    goto out;
	  }
      }
    else if (strncasecmp(ln, "cd ", 3) == 0)
      {
	char *path;
	unsigned long levels;

	path = ln + 3;
	levels = 0;

	if (strcmp(path, ".") == 0)
	  path = "";
	if (strcmp(path, "/") == 0)
	  levels = 0xffffffff;
	else if (strcmp(path, "..") == 0)
	  levels = 1;
	else if (strcmp(path, "...") == 0)
	  levels = 2;
	else if (strcmp(path, "....") == 0)
	  levels = 3;
	else if (strcmp(path, ".....") == 0)
	  levels = 4;
	else if (strcmp(path, "......") == 0)
	  levels = 5;
	else if (strcmp(path, ".......") == 0)
	  levels = 6;
	else if (strcmp(path, "........") == 0)
	  levels = 7;
	else if (strcmp(path, ".........") == 0)
	  levels = 8;
	else if (strcmp(path, "..........") == 0)
	  levels = 9;
	else if (strcmp(path, "...........") == 0)
	  levels = 10;
	if (levels != 0)
	  {
	    switch (cc_xstream_client_upcwd(s, levels))
	      {
	      case CC_XSTREAM_CLIENT_OK:
		fprintf(stderr, "%s: Change dir ok.\n", av0);
		break;

	      case CC_XSTREAM_CLIENT_COMMAND_FAILED:
		fprintf(stderr, "%s: Change dir failed.\n", av0);
		break;

	      case CC_XSTREAM_CLIENT_FATAL_ERROR:
		fprintf(stderr, "%s: Change dir fatally failed.\n", av0);
		rv = 2;
		goto out;
	      }
	  }
	else
	  {
	    switch (cc_xstream_client_setcwd(s, path))
	      {
	      case CC_XSTREAM_CLIENT_OK:
		fprintf(stderr, "%s: Change dir ok.\n", av0);
		break;

	      case CC_XSTREAM_CLIENT_COMMAND_FAILED:
		fprintf(stderr, "%s: Change dir failed.\n", av0);
		break;

	      case CC_XSTREAM_CLIENT_FATAL_ERROR:
		fprintf(stderr, "%s: Change dir fatally failed.\n", av0);
		rv = 2;
		goto out;
	      }
	  }
      }
    else if (strncasecmp(ln, "info ", 5) == 0)
      {
	char *path, *info;

	path = ln + 5;
	switch (cc_xstream_client_file_info(s, path, &info))
	  {
	  case CC_XSTREAM_CLIENT_OK:
	    fprintf(stderr, "name=\"%s\"\n", path);
	    fprintf(stderr, "info=\"%s\"\n", info);
	    break;
	    
	  case CC_XSTREAM_CLIENT_COMMAND_FAILED:
	    fprintf(stderr, "%s: File info failed.\n", av0);
	    break;
		
	  case CC_XSTREAM_CLIENT_FATAL_ERROR:
	    fprintf(stderr, "%s: File info fatally failed.\n", av0);
	    rv = 2;
	    goto out;
	  }
      }
    else if (strncasecmp(ln, "option ", 7) == 0)
      {
	char *option, *value;

	option = cc_xstrdup(ln + 7);
	value = strchr(option, ' ');
	if (value != NULL)
	  {
	    *value = '\0';
	    value++;
	    switch (cc_xstream_client_set_configuration_option(s, option, value))
	      {
	      case CC_XSTREAM_CLIENT_OK:
		fprintf(stderr, "option=\"%s\"\n", option);
		fprintf(stderr, "value=\"%s\"\n", value);
		cc_xfree(option);
		break;
		
	      case CC_XSTREAM_CLIENT_COMMAND_FAILED:
		cc_xfree(option);
		fprintf(stderr, "%s: Set option failed.\n", av0);
		break;
		
	      case CC_XSTREAM_CLIENT_FATAL_ERROR:
		cc_xfree(option);
		fprintf(stderr, "%s: Set option fatally failed.\n", av0);
		rv = 2;
		goto out;
	      }
	  }
	else
	  {
	    fprintf(stderr, "%s: Bad command \"%s\".\n", av0, ln);
	    cc_xfree(option);
	  }
      }
    else if (strncasecmp(ln, "user ", 5) == 0)
      {
	char *user, *pass;

	user = cc_xstrdup(ln + 5);
	pass = strchr(user, ' ');
	if (pass != NULL)
	  {
	    *pass = '\0';
	    pass++;
	    switch (cc_xstream_client_password_authenticate(s, user, pass))
	      {
	      case CC_XSTREAM_CLIENT_OK:
		fprintf(stderr, "Password OK\n");
		cc_xfree(user);
		break;
		
	      case CC_XSTREAM_CLIENT_COMMAND_FAILED:
		cc_xfree(user);
		fprintf(stderr, "%s: Authentication failed.\n", av0);
		break;
		
	      case CC_XSTREAM_CLIENT_FATAL_ERROR:
		cc_xfree(user);
		fprintf(stderr, "%s: Authentication fatally failed.\n", av0);
		rv = 2;
		goto out;
	      }
	  }
	else
	  {
	    fprintf(stderr, "%s: Bad command \"%s\".\n", av0, ln);
	    cc_xfree(user);
	  }
      }
    else if ((strncasecmp(ln, "cat ", 4) == 0) ||
	     (strncasecmp(ln, "get ", 4) == 0))
      {
	int print;

	print = (strncasecmp(ln, "cat ", 4) == 0);
	switch (get_file(s, ln + 4, print))
	  {
	  case CC_XSTREAM_CLIENT_OK:
	    fprintf(stderr, "%s: %s file ok.\n", av0, (print ? "Cat" : "Get"));
	    break;
	    
	  case CC_XSTREAM_CLIENT_COMMAND_FAILED:
	    fprintf(stderr, "%s: %s file failed.\n", av0, (print ? "Cat" : "Get"));
	    break;
		
	  case CC_XSTREAM_CLIENT_FATAL_ERROR:
	    fprintf(stderr, "%s: %s file fatally failed.\n", av0, (print ? "Cat" : "Get"));
	    rv = 2;
	    goto out;
	  }
      }
    else if ((strncasecmp(ln, "fuzzycat ", 9) == 0) ||
	     (strncasecmp(ln, "fuzzyget ", 9) == 0))
      {
	int print;

	print = (strncasecmp(ln, "fuzzycat ", 9) == 0);
	switch (fuzzy_get_file(s, ln + 9, print))
	  {
	  case CC_XSTREAM_CLIENT_OK:
	    fprintf(stderr, "%s: %s file ok.\n", av0, (print ? "FuzzyCat" : "FuzzyGet"));
	    break;
	    
	  case CC_XSTREAM_CLIENT_COMMAND_FAILED:
	    fprintf(stderr, "%s: %s file failed.\n", av0, (print ? "FuzzyCat" : "FuzzyGet"));
	    break;
		
	  case CC_XSTREAM_CLIENT_FATAL_ERROR:
	    fprintf(stderr, "%s: %s file fatally failed.\n", av0, (print ? "FuzzyCat" : "FuzzyGet"));
	    rv = 2;
	    goto out;
	  }
      }
    else if (strncasecmp(ln, "mget ", 5) == 0)
      {
	unsigned long x, n;
	char *end;

	if (isdigit(ln[5]))
	  {
	    n = strtoul(ln + 5, &end, 10);
	    if ((n > 0) && (*end == ' '))
	      {
		end++;
		for (x = 0; x < n; x++)
		  {
		    switch (get_file(s, end, 0))
		      {
		      case CC_XSTREAM_CLIENT_OK:
			fprintf(stderr, ".");
			if (x + 1 >= n)
			  {
			    fprintf(stderr, "\n%s: Get file ok.\n", av0);
			  }
			break;
			
		      case CC_XSTREAM_CLIENT_COMMAND_FAILED:
			if (x > 0)
			  fprintf(stderr, "\n");
			fprintf(stderr, "%s: Get file failed.\n", av0);
			x = n - 1;
			break;
			
		      case CC_XSTREAM_CLIENT_FATAL_ERROR:
			if (x > 0)
			  fprintf(stderr, "\n");
			fprintf(stderr, "%s: Get file fatally failed.\n", av0);
			rv = 2;
			goto out;
		      }
		  }
	      }
	    else
	      {
		fprintf(stderr, "%s: Bad command \"%s\".\n", av0, ln);
	      }
	  }
      }
    else if (strncasecmp(ln, "cget ", 5) == 0)
      {
	unsigned long x, n, m;
	char *end;

	if (isdigit(ln[5]))
	  {
	    n = strtoul(ln + 5, &end, 10);
	    if ((n > 0) && (*end == ' '))
	      {
		end++;
		if (isdigit(*end))
		  {
		    m = strtoul(end, &end, 10);
		    if ((m > 0) && (*end == ' '))
		      {
			end++;
			for (x = 0; x < n; x++)
			  {
			    switch (multi_get_file(s, end, m))
			      {
			      case CC_XSTREAM_CLIENT_OK:
				if (x + 1 >= n)
				  {
				    fprintf(stderr, "\n%s: Get file ok.\n", av0);
				  }
				break;
				
			      case CC_XSTREAM_CLIENT_COMMAND_FAILED:
				fprintf(stderr, "\n");
				fprintf(stderr, "%s: Get file failed.\n", av0);
				x = n - 1;
				break;
				
			      case CC_XSTREAM_CLIENT_FATAL_ERROR:
				fprintf(stderr, "\n");
				fprintf(stderr, "%s: Get file fatally failed.\n", av0);
				rv = 2;
				goto out;
			      }
			  }
		      }
		    else
		      {
			fprintf(stderr, "%s: Bad command \"%s\".\n", av0, ln);
		      }
		  }
		else
		  {
		    fprintf(stderr, "%s: Bad command \"%s\".\n", av0, ln);
		  }
	      }
	    else
	      {
		fprintf(stderr, "%s: Bad command \"%s\".\n", av0, ln);
	      }
	  }
	else
	  {
	    fprintf(stderr, "%s: Bad command \"%s\".\n", av0, ln);
	  }
      }
    else
      {
	fprintf(stderr, "%s: Bad command \"%s\".\n", av0, ln);
      }
    free(ln);
    ln = NULL;
  }
 out:
  cc_xstream_client_disconnect(s);
  if (ln != NULL)
    cc_xfree(ln);
  exit(0);
}
/* eof (ccxtest.c) */
