/*
 * minimal_client.c  - a minimal Subversion client application ("hello world")
 *
 * ====================================================================
 * Copyright (c) 2000-2004 CollabNet.  All rights reserved.
 *
 * This software is licensed as described in the file COPYING, which
 * you should have received as part of this distribution.  The terms
 * are also available at http://subversion.tigris.org/license-1.html.
 * If newer versions of this license are posted there, you may use a
 * newer version instead, at your option.
 *
 * This software consists of voluntary contributions made by many
 * individuals.  For exact contribution history, see the revision
 * history and logs, available at http://subversion.tigris.org/.
 * ====================================================================
 *
 *  This app demonstrates how to use the svn_client.h API.
 *
 *  It reads a directory URL from the commandline, runs
 *  svn_client_list() and prints the list of directory-entries.  It
 *  also knows how to deal with basic username/password authentication
 *  challenges.
 *
 *  For a much more complex example, the svn cmdline client might be
 *  considered the 'reference implementation'.
 *
 *  From a Linux system, a typical commandline compile might look like:
 *
 *  cc minimal_client.c -o minimal_client \
 *  -I/usr/local/include/subversion-1 -I/usr/local/apache2/include \
 *  -L/usr/local/apache2/lib -L/usr/local/lib \
 *  -lsvn_client-1 -lapr-0 -laprutil-0
 *
 */


#include "svn_client.h"
#include "svn_cmdline.h"
#include "svn_pools.h"
#include "svn_config.h"
#include "svn_fs.h"


/* Display a prompt and read a one-line response into the provided buffer,
   removing a trailing newline if present. */
static svn_error_t *
prompt_and_read_line(const char *prompt,
                     char *buffer,
                     size_t max)
{
  int len;
  printf("%s: ", prompt);
  if (fgets(buffer, max, stdin) == NULL)
    return svn_error_create(0, NULL, "error reading stdin");
  len = strlen(buffer);
  if (len > 0 && buffer[len-1] == '\n')
    buffer[len-1] = 0;
  return SVN_NO_ERROR;
}

/* A tiny callback function of type 'svn_auth_simple_prompt_func_t'. For
   a much better example, see svn_cl__auth_simple_prompt in the official
   svn cmdline client. */
static svn_error_t *
my_simple_prompt_callback (svn_auth_cred_simple_t **cred,
                           void *baton,
                           const char *realm,
                           const char *username,
                           svn_boolean_t may_save,
                           apr_pool_t *pool)
{
  svn_auth_cred_simple_t *ret = apr_pcalloc (pool, sizeof (*ret));
  char answerbuf[100];

  if (realm)
    {
      printf ("Authentication realm: %s\n", realm);
    }

  if (username)
    ret->username = apr_pstrdup (pool, username);
  else
    {
      SVN_ERR (prompt_and_read_line("Username", answerbuf, sizeof(answerbuf)));
      ret->username = apr_pstrdup (pool, answerbuf);
    }

  SVN_ERR (prompt_and_read_line("Password", answerbuf, sizeof(answerbuf)));
  ret->password = apr_pstrdup (pool, answerbuf);

  *cred = ret;
  return SVN_NO_ERROR;
}


/* A tiny callback function of type 'svn_auth_username_prompt_func_t'. For
   a much better example, see svn_cl__auth_username_prompt in the official
   svn cmdline client. */
static svn_error_t *
my_username_prompt_callback (svn_auth_cred_username_t **cred,
                             void *baton,
                             const char *realm,
                             svn_boolean_t may_save,
                             apr_pool_t *pool)
{
  svn_auth_cred_username_t *ret = apr_pcalloc (pool, sizeof (*ret));
  char answerbuf[100];

  if (realm)
    {
      printf ("Authentication realm: %s\n", realm);
    }

  SVN_ERR (prompt_and_read_line("Username", answerbuf, sizeof(answerbuf)));
  ret->username = apr_pstrdup (pool, answerbuf);

  *cred = ret;
  return SVN_NO_ERROR;
}



int
main (int argc, const char **argv)
{
  apr_pool_t *pool;
  svn_error_t *err;
  svn_opt_revision_t revision;
  apr_hash_t *dirents;
  apr_hash_index_t *hi;
  svn_client_ctx_t *ctx;
  const char *URL;

  if (argc <= 1)
    {
      printf ("Usage:  %s URL\n", argv[0]);
      return EXIT_FAILURE;
    }
  else
    URL = argv[1];

  /* Initialize the app.  Send all error messages to 'stderr'.  */
  if (svn_cmdline_init ("minimal_client", stderr) != EXIT_SUCCESS)
    return EXIT_FAILURE;

  /* Create top-level memory pool. Be sure to read the HACKING file to
     understand how to properly use/free subpools. */
  pool = svn_pool_create (NULL);

  /* Initialize the FS library. */
  err = svn_fs_initialize (pool);
  if (err)
    {
      /* For functions deeper in the stack, we usually use the
         SVN_ERR() exception-throwing macro (see svn_error.h).  At the
         top level, we catch & print the error with svn_handle_error2(). */
      svn_handle_error2 (err, stderr, FALSE, "minimal_client: ");
      return EXIT_FAILURE;
    }

  /* Make sure the ~/.subversion run-time config files exist */
  err = svn_config_ensure (NULL, pool);
  if (err)
    {
      svn_handle_error2 (err, stderr, FALSE, "minimal_client: ");
      return EXIT_FAILURE;
    }

  /* All clients need to fill out a client_ctx object. */
  {
    /* Initialize and allocate the client_ctx object. */
    if ((err = svn_client_create_context (&ctx, pool)))
      {
        svn_handle_error2 (err, stderr, FALSE, "minimal_client: ");
        return EXIT_FAILURE;
      }

    /* Load the run-time config file into a hash */
    if ((err = svn_config_get_config (&(ctx->config), NULL, pool)))
      {
        svn_handle_error2 (err, stderr, FALSE, "minimal_client: ");
        return EXIT_FAILURE;
      }

#ifdef WIN32
    /* Set the working copy administrative directory name. */
    if (getenv ("SVN_ASP_DOT_NET_HACK"))
      {
        err = svn_wc_set_adm_dir ("_svn", pool);
        if (err)
          {
            svn_handle_error2 (err, stderr, FALSE, "minimal_client: ");
            return EXIT_FAILURE;
          }
      }
#endif

    /* Depending on what your client does, you'll want to read about
       (and implement) the various callback function types below.  */

    /* A func (& context) which receives event signals during
       checkouts, updates, commits, etc.  */
    /* ctx->notify_func = my_notification_func;
       ctx->notify_baton = NULL; */

    /* A func (& context) which can receive log messages */
    /* ctx->log_msg_func = my_log_msg_receiver_func;
       ctx->log_msg_baton = NULL; */

    /* A func (& context) which checks whether the user cancelled */
    /* ctx->cancel_func = my_cancel_checking_func;
       ctx->cancel_baton = NULL; */

    /* Make the client_ctx capable of authenticating users */
    {
      /* There are many different kinds of authentication back-end
         "providers".  See svn_auth.h for a full overview.

         If you want to get the auth behavior of the 'svn' program,
         you can use svn_cmdline_setup_auth_baton, which will give
         you the exact set of auth providers it uses.  This program
         doesn't use it because it's only appropriate for a command
         line program, and this is supposed to be a general purpose
         example. */

      svn_auth_provider_object_t *provider;
      apr_array_header_t *providers
        = apr_array_make (pool, 4, sizeof (svn_auth_provider_object_t *));

      svn_auth_get_simple_prompt_provider (&provider,
                                           my_simple_prompt_callback,
                                           NULL, /* baton */
                                           2, /* retry limit */ pool);
      APR_ARRAY_PUSH (providers, svn_auth_provider_object_t *) = provider;

      svn_auth_get_username_prompt_provider (&provider,
                                             my_username_prompt_callback,
                                             NULL, /* baton */
                                             2, /* retry limit */ pool);
      APR_ARRAY_PUSH (providers, svn_auth_provider_object_t *) = provider;

      /* Register the auth-providers into the context's auth_baton. */
      svn_auth_open (&ctx->auth_baton, providers, pool);
    }
  } /* end of client_ctx setup */


  /* Now do the real work. */

  /* Set revision to always be the HEAD revision.  It could, however,
     be set to a specific revision number, date, or other values. */
  revision.kind = svn_opt_revision_head;

  /* Main call into libsvn_client does all the work. */
  err = svn_client_ls (&dirents,
                       URL, &revision,
                       FALSE, /* no recursion */
                       ctx, pool);
  if (err)
    {
      svn_handle_error2 (err, stderr, FALSE, "minimal_client: ");
      return EXIT_FAILURE;
    }

  /* Print the dir entries in the hash. */
  for (hi = apr_hash_first (pool, dirents); hi; hi = apr_hash_next (hi))
    {
      const char *entryname;
      svn_dirent_t *val;

      apr_hash_this (hi, (void *) &entryname, NULL, (void *) &val);
      printf ("   %s\n", entryname);

      /* 'val' is actually an svn_dirent_t structure; a more complex
          program would mine it for extra printable information. */
    }

  return EXIT_SUCCESS;
}
