/* GVFS plugin for Tux Commander
 * Copyright (C) 2008 Tomas Bzatek <tbzatek@users.sourceforge.net>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library 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
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, write to the
 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA 02111-1307, USA.
 */

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <stdint.h>
#include <sys/stat.h>
#include <errno.h>

#include <gio/gio.h>
#include <glib/gtypes.h>

#include "vfs_types.h"



#define VERSION "0.1.10"
#define BUILD_DATE "2009-10-25"
#define DEFAULT_BLOCK_SIZE 0x10000   /* 64kB */

#define CONST_DEFAULT_QUERY_INFO_ATTRIBUTES     G_FILE_ATTRIBUTE_STANDARD_TYPE "," G_FILE_ATTRIBUTE_STANDARD_NAME "," \
                                                G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME "," G_FILE_ATTRIBUTE_STANDARD_SIZE "," \
                                                G_FILE_ATTRIBUTE_STANDARD_SYMLINK_TARGET "," G_FILE_ATTRIBUTE_TIME_MODIFIED "," \
                                                G_FILE_ATTRIBUTE_TIME_ACCESS "," G_FILE_ATTRIBUTE_TIME_CHANGED "," \
                                                G_FILE_ATTRIBUTE_UNIX_MODE "," G_FILE_ATTRIBUTE_UNIX_UID "," \
                                                G_FILE_ATTRIBUTE_UNIX_GID

struct TVFSGlobs {
  TVFSLogFunc log_func;
  GFile *file;
  GFileEnumerator *enumerator;
  GCancellable *cancellable;

  GMainLoop *mount_main_loop;
  TVFSResult mount_result;
  int mount_try;
  gboolean ftp_anonymous;

  gboolean break_get_dir_size;
  guint32 block_size;

  TVFSAskQuestionCallback callback_ask_question;
  TVFSAskPasswordCallback callback_ask_password;
  TVFSProgressCallback callback_progress;
  void *callback_data;
};



static TVFSResult
g_error_to_TVFSResult (GError *error)
{
  g_print ("g_error_to_TVFSResult: code = %d\n", error->code);
  switch (error->code) {
    case G_IO_ERROR_FAILED:
    case G_IO_ERROR_NOT_FOUND:
      return cVFS_Failed;
      break;
    case G_IO_ERROR_PERMISSION_DENIED:
      return cVFS_PermissionDenied;
      break;
    case G_IO_ERROR_CANCELLED:
      return cVFS_Cancelled;
      break;
    case G_IO_ERROR_NOT_SUPPORTED:
    case G_IO_ERROR_FILENAME_TOO_LONG:
      return cVFS_Not_Supported;
      break;
    case G_IO_ERROR_NO_SPACE:
      return cVFS_NoSpaceLeft;
      break;
    case G_IO_ERROR_IS_DIRECTORY:
    case G_IO_ERROR_NOT_REGULAR_FILE:
    case G_IO_ERROR_NOT_SYMBOLIC_LINK:
    case G_IO_ERROR_NOT_MOUNTABLE_FILE:
    case G_IO_ERROR_INVALID_FILENAME:
    case G_IO_ERROR_TOO_MANY_LINKS:
    case G_IO_ERROR_INVALID_ARGUMENT:
    case G_IO_ERROR_NOT_DIRECTORY:
    case G_IO_ERROR_NOT_MOUNTED:
    case G_IO_ERROR_ALREADY_MOUNTED:
    case G_IO_ERROR_WRONG_ETAG:
    case G_IO_ERROR_TIMED_OUT:
    case G_IO_ERROR_WOULD_RECURSE:
    case G_IO_ERROR_HOST_NOT_FOUND:
      return cVFS_ReadErr;
      break;
    case G_IO_ERROR_EXISTS:
    case G_IO_ERROR_NOT_EMPTY:
    case G_IO_ERROR_CLOSED:
    case G_IO_ERROR_PENDING:
    case G_IO_ERROR_READ_ONLY:
    case G_IO_ERROR_CANT_CREATE_BACKUP:
    case G_IO_ERROR_BUSY:
    case G_IO_ERROR_WOULD_BLOCK:
    case G_IO_ERROR_WOULD_MERGE:
      return cVFS_WriteErr;
      break;
    case G_IO_ERROR_FAILED_HANDLED:
    default:
      return cVFS_Failed;
  }
}

static void
ask_password_cb (GMountOperation *op,
                 const char      *message,
                 const char      *default_user,
                 const char      *default_domain,
                 GAskPasswordFlags flags,
                 gpointer          user_data)
{
  struct TVFSGlobs *globs;
  char *username;
  char *password;
  int   anonymous;
  char  *domain;
  TVFSPasswordSave password_save;
  int result;


  globs = (struct TVFSGlobs*) user_data;
  g_assert (globs != NULL);
  globs->mount_try++;

  /*  First pass, look if we have a password to supply  */
  if (globs->mount_try == 1) {
    if ((flags & G_ASK_PASSWORD_ANONYMOUS_SUPPORTED) && globs->ftp_anonymous) {
      printf ("(WW) ask_password_cb: mount_try = %d, trying FTP anonymous login...\n", globs->mount_try);
      g_mount_operation_set_anonymous (op, TRUE);
      g_mount_operation_reply (op, G_MOUNT_OPERATION_HANDLED);
      return;
    }
  }

  /*  Ask user for password  */
  g_print ("(WW) ask_password_cb: mount_try = %d, message = '%s'\n", globs->mount_try, message);

  /*  Handle abort message from certain backends properly  */
  /*   - e.g. SMB backends use this to mask multiple auth callbacks from smbclient  */
  if (default_user && strcmp (default_user, "ABORT") == 0) {
    g_print ("(WW) default_user == \"ABORT\", aborting\n");
    g_mount_operation_reply (op, G_MOUNT_OPERATION_ABORTED);
    return;
  }

  username = NULL;
  domain = NULL;
  password = NULL;
  anonymous = FALSE;
  password_save = VFS_PASSWORD_SAVE_NEVER;

  if (globs->callback_ask_password) {
    fprintf (stderr, "  (II) Spawning callback_ask_password (%p)...\n", globs->callback_ask_password);
    result = globs->callback_ask_password (message, default_user, default_domain, NULL, flags,
                                           &username, &password, &anonymous, &domain, &password_save,
                                           globs->callback_data);
    fprintf (stderr, "    (II) Received result = %d\n", result);
    if (result) {
      if (flags & G_ASK_PASSWORD_NEED_USERNAME)
        g_mount_operation_set_username (op, username);
      if (flags & G_ASK_PASSWORD_NEED_DOMAIN)
        g_mount_operation_set_domain (op, domain);
      if (flags & G_ASK_PASSWORD_NEED_PASSWORD)
        g_mount_operation_set_password (op, password);
      if (flags & G_ASK_PASSWORD_ANONYMOUS_SUPPORTED)
        g_mount_operation_set_anonymous (op, anonymous);
      if (flags & G_ASK_PASSWORD_SAVING_SUPPORTED)
        g_mount_operation_set_password_save (op, password_save);
      g_mount_operation_reply (op, G_MOUNT_OPERATION_HANDLED);
    }
    else
      g_mount_operation_reply (op, G_MOUNT_OPERATION_ABORTED);
    return;
  }
  /*  Unhandled, abort  */
  g_mount_operation_reply (op, G_MOUNT_OPERATION_ABORTED);
}

static void
ask_question_cb (GMountOperation *op,
                 const gchar     *message,
                 const gchar     *choices[],
                 gpointer         user_data)
{
  struct TVFSGlobs *globs;
  int len;
  int choice;

  globs = (struct TVFSGlobs*) user_data;
  g_assert (globs != NULL);

  g_print ("(WW) ask_question_cb: message = '%s'\n", message);

  len = 0;
  while (choices[len] != NULL) {
    g_print ("(WW) ask_question_cb: choice[%d] = '%s'\n", len, choices[len]);
    len++;
  }

  choice = -1;
  if (globs->callback_ask_question) {
    fprintf (stderr, "  (II) Spawning callback_ask_question (%p)...\n", globs->callback_ask_question);
    /*  At this moment, only SFTP uses ask_question and the second button is cancellation  */
    globs->callback_ask_question (message, choices, &choice, 1, globs->callback_data);
    fprintf (stderr, "    (II) Received choice = %d\n", choice);

    if (choice >= 0) {
      g_mount_operation_set_choice (op, choice);
      g_mount_operation_reply (op, G_MOUNT_OPERATION_HANDLED);
    }
    else {
      g_mount_operation_reply (op, G_MOUNT_OPERATION_ABORTED);
    }
    return;
  }

  g_mount_operation_reply (op, G_MOUNT_OPERATION_UNHANDLED);
}

static void
mount_done_cb (GObject *object,
               GAsyncResult *res,
               gpointer user_data)
{
  struct TVFSGlobs *globs;
  gboolean succeeded;
  GError *error = NULL;

  globs = (struct TVFSGlobs*) user_data;
  g_assert (globs != NULL);

  succeeded = g_file_mount_enclosing_volume_finish (G_FILE (object), res, &error);

  if (! succeeded) {
    g_print ("(EE) Error mounting location: %s\n", error->message);
    globs->mount_result = g_error_to_TVFSResult (error);
    g_error_free (error);
  }
  else {
    globs->mount_result = cVFS_OK;
    g_print ("(II) Mount successful.\n");
  }

  g_main_loop_quit (globs->mount_main_loop);
}

static TVFSResult
vfs_handle_mount (struct TVFSGlobs *globs, GFile *file)
{
  GMountOperation *op;

  g_print ("(II) Mounting location...\n");

  op = g_mount_operation_new ();
  g_signal_connect (op, "ask-password", (GCallback)ask_password_cb, globs);
  g_signal_connect (op, "ask-question", (GCallback)ask_question_cb, globs);
  globs->mount_result = cVFS_Failed;
  globs->mount_try = 0;

  /*  Inspiration taken from Bastien Nocera's http://svn.gnome.org/viewvc/totem-pl-parser/trunk/plparse/totem-disc.c?view=markup  */
  globs->mount_main_loop = g_main_loop_new (NULL, FALSE);
  g_file_mount_enclosing_volume (file, G_MOUNT_MOUNT_NONE, op, NULL, mount_done_cb, globs);
  g_main_loop_run (globs->mount_main_loop);

  g_main_loop_unref (globs->mount_main_loop);
  globs->mount_main_loop = NULL;
  g_object_unref (op);

  return globs->mount_result;
}

struct TVFSGlobs *
VFSNew (TVFSLogFunc log_func)
{
  struct TVFSGlobs * globs;

  globs = (struct TVFSGlobs *) malloc (sizeof (struct TVFSGlobs));
  memset (globs, 0, sizeof (struct TVFSGlobs));

  globs->log_func = log_func;
  globs->log_func ("GVFS plugin: VFSInit");

  globs->file = NULL;
  globs->enumerator = NULL;
  globs->cancellable = NULL;

  globs->break_get_dir_size = FALSE;
  globs->block_size = DEFAULT_BLOCK_SIZE;

  globs->callback_data = NULL;
  globs->callback_ask_question = NULL;
  globs->callback_ask_password = NULL;
  globs->callback_progress = NULL;

  return globs;
}

void
VFSSetCallbacks (struct TVFSGlobs *globs,
                 TVFSAskQuestionCallback ask_question_callback,
                 TVFSAskPasswordCallback ask_password_callback,
                 TVFSProgressCallback progress_func,
                 void *data)
{
  globs->callback_ask_question = ask_question_callback;
  globs->callback_ask_password = ask_password_callback;
  globs->callback_progress = progress_func;
  globs->callback_data = data;
}

void
VFSFree (struct TVFSGlobs *globs)
{
  globs->log_func ("GVFS plugin: VFSDestroy");
  free (globs);
}

int
VFSVersion ()
{
  return cVFSVersion;
}

struct TVFSInfo *
VFSGetInfo ()
{
  struct TVFSInfo *module_info = g_malloc0 (sizeof (struct TVFSInfo));

  module_info->ID = g_strdup ("gvfs_plugin");
  module_info->Name = g_strdup ("GVFS plugin");
  module_info->About = g_strdup_printf ("version %s, build date: %s", VERSION, BUILD_DATE);
  module_info->Copyright = g_strdup ("Copyright (C) 2008-2009 Tomáš Bžatek");

  return module_info;
}

#if 0
char *
VFSGetArchiveExts ()
{
  return NULL;
}
#endif

char *
VFSGetNetworkServices ()
{
  GVfs *gvfs;
  const gchar* const * schemes;
  char *l = NULL;
  char *s;

  gvfs = g_vfs_get_default ();
  g_print ("(II) GVFS: is_active = %d\n", g_vfs_is_active (gvfs));

  schemes = g_vfs_get_supported_uri_schemes (gvfs);
  for (; *schemes; schemes++) {
    if (l) {
      s = g_strdup_printf ("%s;%s", l, *schemes);
      g_free (l);
      l = s;
    }
    else
      l = g_strdup (*schemes);
  }

  g_print ("(II) GVFS: supported schemes: %s\n", l);
  return l;
}

TVFSResult
VFSOpenURI (struct TVFSGlobs *globs, char *sURI)
{
  GFile *f, *f2;
  GFileInfo *info;
  GError *error;
  TVFSResult res;

  globs->file = NULL;
  globs->ftp_anonymous = FALSE;

  if (strstr (sURI, "@") == NULL) {
    /*  test for FTP protocol (we only enable anonymous here)  */
    globs->ftp_anonymous = strcasestr (sURI, "ftp://") == sURI;
  }

  g_print ("(II) VFSOpenURI: opening URI '%s'\n", sURI);
  f = g_file_new_for_commandline_arg (sURI);

  while (1) {
    error = NULL;
    info = g_file_query_info (f, CONST_DEFAULT_QUERY_INFO_ATTRIBUTES,
                                 G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, NULL, &error);

    /*  Fallback to parent directory if specified path doesn't exist  */
    if (error && g_error_matches (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND)) {
      f2 = g_file_get_parent (f);
      if (f2) {
        g_object_unref (f);
        f = f2;
        g_error_free (error);
        continue;
      }
    }
    /*  Mount the target  */
    if (error && g_error_matches (error, G_IO_ERROR, G_IO_ERROR_NOT_MOUNTED)) {
      g_error_free (error);
      error = NULL;
      res = vfs_handle_mount (globs, f);
      if (res != cVFS_OK)
        return res;
      else
        continue;
    }
    /*  Any other errors --> report  */
    if (error) {
      g_print ("(EE) VFSOpenURI: g_file_query_info() error: %s\n", error->message);
      res = g_error_to_TVFSResult (error);
      g_error_free (error);
      g_object_unref (f);
      return res;
    }
    /*  everything ok?  */
    break;
  }

  globs->file = f;
  return cVFS_OK;
}

TVFSResult
VFSClose (struct TVFSGlobs *globs)
{
  g_print ("(II) VFSClose\n");

  if (globs->file)
    g_object_unref (globs->file);
  globs->file = NULL;

  return cVFS_OK;
}

char *
VFSGetPath (struct TVFSGlobs *globs)
{
  GFile *root;
  char *path, *s;

  if (globs->file) {
    root = g_file_resolve_relative_path (globs->file, "/");
    if (root == NULL)
      return NULL;
    path = g_file_get_relative_path (root, globs->file);
    if (path == NULL) {
      g_object_unref (root);
      return NULL;
    }
    if (! g_path_is_absolute (path))
      s = g_strdup_printf ("/%s", path);
    else
      s = g_strdup (path);
    g_print ("(II) VFSGetPath: '%s'\n", s);
    g_free (path);
    g_object_unref (root);
    return s;
  }
  else
    return NULL;
}

char *
VFSGetPathURI (struct TVFSGlobs *globs)
{
  if (globs->file)
    return g_file_get_uri (globs->file);
  else
    return NULL;
}

guint64
VFSGetFileSystemFree (struct TVFSGlobs *globs, char *APath)
{
  GFileInfo *info;
  GError *error;
  guint64 res;

  if (globs->file == NULL)
    return 0;

  error = NULL;
  info = g_file_query_filesystem_info (globs->file, G_FILE_ATTRIBUTE_FILESYSTEM_FREE, NULL, &error);
  if (error) {
    g_print ("(EE) VFSGetFileSystemFree: %s\n", error->message);
    g_error_free (error);
    return 0;
  }
  res = g_file_info_get_attribute_uint64 (info, G_FILE_ATTRIBUTE_FILESYSTEM_FREE);
  g_object_unref (info);
  return res;
}

guint64
VFSGetFileSystemSize (struct TVFSGlobs *globs, char *APath)
{
  GFileInfo *info;
  GError *error;
  guint64 res;

  if (globs->file == NULL)
    return 0;

  error = NULL;
  info = g_file_query_filesystem_info (globs->file, G_FILE_ATTRIBUTE_FILESYSTEM_SIZE, NULL, &error);
  if (error) {
    g_print ("(EE) VFSGetFileSystemSize: %s\n", error->message);
    g_error_free (error);
    return 0;
  }
  res = g_file_info_get_attribute_uint64 (info, G_FILE_ATTRIBUTE_FILESYSTEM_SIZE);
  g_object_unref (info);
  return res;
}


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

TVFSResult
VFSChangeDir (struct TVFSGlobs *globs, char *NewPath)
{
  GFile *f;
  GFileEnumerator *en;
  GError *error, *error_shortcut;
  TVFSResult res;
  GFileInfo *info;
  gchar *target_uri;

  if (globs->file == NULL) {
    g_print ("(EE) VFSChangeDir: globs->file == NULL !\n");
    return cVFS_Failed;
  }

  g_print ("(II) VFSChangeDir: changing dir to '%s'\n", NewPath);

  f = g_file_resolve_relative_path (globs->file, NewPath);
  if (f == NULL) {
    g_print ("(EE) VFSChangeDir: g_file_resolve_relative_path() failed.\n");
    return cVFS_Failed;
  }

  res = cVFS_OK;
  while (1) {
    error = NULL;
    en = g_file_enumerate_children (f, CONST_DEFAULT_QUERY_INFO_ATTRIBUTES,
                                       G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, NULL, &error);

    /*  if the target is shortcut, change the URI  */
    if (error && g_error_matches (error, G_IO_ERROR, G_IO_ERROR_NOT_DIRECTORY)) {
      error_shortcut = NULL;
      info = g_file_query_info (f, G_FILE_ATTRIBUTE_STANDARD_TARGET_URI,
                                   G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, NULL, &error_shortcut);
      if (info) {
        target_uri = g_strdup (g_file_info_get_attribute_string (info, G_FILE_ATTRIBUTE_STANDARD_TARGET_URI));
        g_object_unref (info);
        if (target_uri) {
          g_print ("(WW) VFSChangeDir: following shortcut, changing URI to '%s'\n", target_uri);
          g_object_unref (f);
          f = g_file_new_for_uri (target_uri);
          g_free (target_uri);
          g_error_free (error);
          continue;
        }
      }
      if (error_shortcut)
        g_error_free (error_shortcut);
    }
    /*  Mount the target  */
    if (error && g_error_matches (error, G_IO_ERROR, G_IO_ERROR_NOT_MOUNTED)) {
      g_error_free (error);
      res = vfs_handle_mount (globs, f);
      if (res != cVFS_OK) {
        g_object_unref (f);
        return res;
      }
      else
        continue;
    }
    /*  Any other errors --> report  */
    if (error) {
      g_print ("(EE) VFSChangeDir: g_file_enumerate_children() error: %s\n", error->message);
      res = g_error_to_TVFSResult (error);
      g_error_free (error);
      g_object_unref (f);
      return res;
    }
    /*  everything ok?  */
    break;
  }

  globs->enumerator = en;
  g_object_unref (globs->file);
  globs->file = f;

  return res;
}


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

static void
g_file_info_to_TVFSItem (GFileInfo *info, struct TVFSItem *Item)
{
  g_assert (info != NULL);
  g_assert (Item != NULL);

  Item->FName = g_strdup (g_file_info_get_name (info));
  Item->FDisplayName = g_strdup (g_file_info_get_display_name (info));
  Item->sLinkTo = g_file_info_get_symlink_target (info) == NULL ? NULL : g_strdup (g_file_info_get_symlink_target (info));
  Item->iSize = g_file_info_get_size (info);
  Item->iPackedSize = -1;
  Item->iMode = g_file_info_get_attribute_uint32 (info, G_FILE_ATTRIBUTE_UNIX_MODE);

  Item->m_time = g_file_info_get_attribute_uint64 (info, G_FILE_ATTRIBUTE_TIME_MODIFIED);
  Item->a_time = g_file_info_get_attribute_uint64 (info, G_FILE_ATTRIBUTE_TIME_ACCESS);
  Item->c_time = g_file_info_get_attribute_uint64 (info, G_FILE_ATTRIBUTE_TIME_CHANGED);
  Item->iUID = g_file_info_get_attribute_uint32 (info, G_FILE_ATTRIBUTE_UNIX_UID);
  Item->iGID = g_file_info_get_attribute_uint32 (info, G_FILE_ATTRIBUTE_UNIX_GID);

//  g_print ("(II) g_file_info_to_TVFSItem: type = %d\n", g_file_info_get_file_type (info));
//  g_print ("(II) g_file_info_to_TVFSItem: UNIX_MODE = %d\n", Item->iMode);

  if (g_file_info_get_is_symlink (info)) {
    Item->ItemType = vSymlink;
  }
  else
  {
    switch (g_file_info_get_file_type (info)) {
      case G_FILE_TYPE_REGULAR:
        Item->ItemType = vRegular;
        break;
      case G_FILE_TYPE_DIRECTORY:
      case G_FILE_TYPE_SHORTCUT:   /*  Used in network:/// */
      case G_FILE_TYPE_MOUNTABLE:
        Item->ItemType = vDirectory;
        break;
      case G_FILE_TYPE_SYMBOLIC_LINK:
        Item->ItemType = vSymlink;
        break;
      case G_FILE_TYPE_SPECIAL: /* socket, fifo, blockdev, chardev */
        Item->ItemType = vBlockdev;
        break;
      case G_FILE_TYPE_UNKNOWN:
      default: Item->ItemType = vRegular;
    }
  }

  /*  fallback to default file mode if read fails  */
  if (Item->iMode == 0) {
    if (Item->ItemType == vDirectory)
      Item->iMode = S_IFDIR + S_IRWXU + S_IRGRP + S_IXGRP + S_IROTH + S_IXOTH;
    else
      Item->iMode = S_IRUSR + S_IWUSR + S_IRGRP + S_IROTH;
  }
}

TVFSResult
VFSListNext (struct TVFSGlobs *globs, char *sDir, struct TVFSItem *Item)
{
  GError *error;
  GFileInfo *info;
  TVFSResult res;

  if (globs->file == NULL) {
    g_print ("(EE) VFSListNext: globs->file == NULL !\n");
    return cVFS_Failed;
  }
  if (globs->enumerator == NULL) {
    g_print ("(EE) VFSListNext: globs->enumerator == NULL !\n");
    return cVFS_Failed;
  }

  error = NULL;
  info = g_file_enumerator_next_file (globs->enumerator, NULL, &error);
  if (error) {
    g_print ("(EE) VFSListNext: g_file_enumerator_next_file() error: %s\n", error->message);
    res = g_error_to_TVFSResult (error);
    g_error_free (error);
    return res;
  }
  if (! error && ! info)
    return cVFS_No_More_Files;
  g_file_info_to_TVFSItem (info, Item);
  g_object_unref (info);
  return cVFS_OK;
}

TVFSResult
VFSListFirst (struct TVFSGlobs *globs, char *sDir, struct TVFSItem *Item)
{
  return VFSListNext (globs, sDir, Item);
}

TVFSResult
VFSListClose (struct TVFSGlobs *globs)
{
  GError *error;
  TVFSResult res;

  if (globs->file == NULL) {
    g_print ("(EE) VFSListClose: globs->file == NULL !\n");
    return cVFS_Failed;
  }
  if (globs->enumerator == NULL) {
    g_print ("(EE) VFSListClose: globs->enumerator == NULL !\n");
    return cVFS_Failed;
  }
  g_print ("(II) VFSListClose\n");

  error = NULL;
  g_file_enumerator_close (globs->enumerator, NULL, &error);
  g_object_unref (globs->enumerator);
  globs->enumerator = NULL;
  if (error) {
    g_print ("(EE) VFSListClose: g_file_enumerator_close() error: %s\n", error->message);
    res = g_error_to_TVFSResult (error);
    g_error_free (error);
    return res;
  }
  return cVFS_OK;
}


/**************************************************************************************************************************************/
/**************************************************************************************************************************************/
long
VFSFileExists (struct TVFSGlobs *globs, const char *FileName, const long Use_lstat)
{
  GFile *f;
  GError *error;
  GFileInfo *info;

  if (globs->file == NULL) {
    g_print ("(EE) VFSFileExists: globs->file == NULL !\n");
    return cVFS_Failed;
  }

  f = g_file_resolve_relative_path (globs->file, FileName);
  if (f == NULL) {
    g_print ("(EE) VFSMkDir: g_file_resolve_relative_path() failed.\n");
    return cVFS_Failed;
  }

  error = NULL;
  info = g_file_query_info (f, G_FILE_ATTRIBUTE_STANDARD_NAME,
                            Use_lstat ? G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS : G_FILE_QUERY_INFO_NONE, NULL, &error);
  g_object_unref (f);
  if (error) {
//    g_print ("(EE) VFSFileExists: g_file_query_info() error: %s\n", error->message);
    g_error_free (error);
    return FALSE;
  }
  g_object_unref (info);
  return TRUE;
}

TVFSResult
VFSFileInfo (struct TVFSGlobs *globs, char *AFileName, struct TVFSItem *Item)
{
  GFile *f;
  GError *error;
  GFileInfo *info;
  TVFSResult res;

  if (globs->file == NULL) {
    g_print ("(EE) VFSFileInfo: globs->file == NULL !\n");
    return cVFS_Failed;
  }

  f = g_file_resolve_relative_path (globs->file, AFileName);
  if (f == NULL) {
    g_print ("(EE) VFSMkDir: g_file_resolve_relative_path() failed.\n");
    return cVFS_Failed;
  }

  error = NULL;
  info = g_file_query_info (f, CONST_DEFAULT_QUERY_INFO_ATTRIBUTES,
                               G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, NULL, &error);
  g_object_unref (f);
  if (error) {
    g_print ("(EE) VFSFileInfo: g_file_query_info() error: %s\n", error->message);
    res = g_error_to_TVFSResult (error);
    g_error_free (error);
    return res;
  }
  g_file_info_to_TVFSItem (info, Item);
  g_object_unref (info);
  return cVFS_OK;
}

TVFSResult
VFSMkDir (struct TVFSGlobs *globs, const char *sDirName)
{
  GFile *f;
  GError *error;
  TVFSResult res;

  if (globs->file == NULL) {
    g_print ("(EE) VFSMkDir: globs->file == NULL !\n");
    return cVFS_Failed;
  }

  f = g_file_resolve_relative_path (globs->file, sDirName);
  if (f == NULL) {
    g_print ("(EE) VFSMkDir: g_file_resolve_relative_path() failed.\n");
    return cVFS_Failed;
  }

  error = NULL;
  g_file_make_directory (f, NULL, &error);
  g_object_unref (f);
  if (error) {
    g_print ("(EE) VFSMkDir: g_file_make_directory() error: %s\n", error->message);
    res = g_error_to_TVFSResult (error);
    g_error_free (error);
    return res;
  }
  return cVFS_OK;
}

TVFSResult
VFSRemove (struct TVFSGlobs *globs, const char *APath)
{
  GFile *f;
  GError *error;
  TVFSResult res;

  if (globs->file == NULL) {
    g_print ("(EE) VFSRemove: globs->file == NULL !\n");
    return cVFS_Failed;
  }

  f = g_file_resolve_relative_path (globs->file, APath);
  if (f == NULL) {
    g_print ("(EE) VFSRemove: g_file_resolve_relative_path() failed.\n");
    return cVFS_Failed;
  }

  error = NULL;
  g_file_delete (f, NULL, &error);
  g_object_unref (f);
  if (error) {
    g_print ("(EE) VFSRemove: g_file_delete() error: %s\n", error->message);
    res = g_error_to_TVFSResult (error);
    g_error_free (error);
    return res;
  }
  return cVFS_OK;
}

TVFSResult
VFSRename (struct TVFSGlobs *globs, const char *sSrcName, const char *sDstName)
{
  GFile *src, *dst;
  GError *error;
  TVFSResult res;

  if (globs->file == NULL) {
    g_print ("(EE) VFSRename: globs->file == NULL !\n");
    return cVFS_Failed;
  }

  src = g_file_resolve_relative_path (globs->file, sSrcName);
  if (src == NULL) {
    g_print ("(EE) VFSRename: g_file_resolve_relative_path() failed.\n");
    return cVFS_Failed;
  }

  g_print ("VFSRename: '%s' --> '%s'\n", sSrcName, sDstName);

  error = NULL;
  g_file_set_display_name (src, sDstName, NULL, &error);
  if (error) {
    g_print ("(WW) VFSRename: g_file_set_display_name() failed (\"%s\"), using fallback g_file_move()\n", error->message);
    g_error_free (error);

    dst = g_file_resolve_relative_path (src, sDstName);
    if (dst == NULL) {
      g_print ("(EE) VFSRename: g_file_resolve_relative_path() failed.\n");
      g_object_unref (src);
      return cVFS_Failed;
    }
    error = NULL;
    g_file_move (src, dst, G_FILE_COPY_NO_FALLBACK_FOR_MOVE, NULL, NULL, NULL, &error);
    if (error) {
      g_print ("(EE) VFSRename: g_file_move() error: %s\n", error->message);
      res = g_error_to_TVFSResult (error);
      g_error_free (error);
      g_object_unref (src);
      g_object_unref (dst);
      return res;
    }
    g_object_unref (dst);
  }

  g_object_unref (src);
  return cVFS_OK;
}

TVFSResult
VFSMakeSymLink (struct TVFSGlobs *globs, const char *NewFileName, const char *PointTo)
{
  GFile *f;
  GError *error;
  TVFSResult res;

  if (globs->file == NULL) {
    g_print ("(EE) VFSMakeSymLink: globs->file == NULL !\n");
    return cVFS_Failed;
  }

  f = g_file_resolve_relative_path (globs->file, NewFileName);
  if (f == NULL) {
    g_print ("(EE) VFSMakeSymLink: g_file_resolve_relative_path() failed.\n");
    return cVFS_Failed;
  }

  error = NULL;
  g_file_make_symbolic_link (f, PointTo, NULL, &error);
  g_object_unref (f);
  if (error) {
    g_print ("(EE) VFSMakeSymLink: g_file_make_symbolic_link() error: %s\n", error->message);
    res = g_error_to_TVFSResult (error);
    g_error_free (error);
    return res;
  }
  return cVFS_OK;
}

TVFSResult
VFSChmod (struct TVFSGlobs *globs, const char *FileName, const uint Mode)
{
  GFile *f;
  GError *error;
  TVFSResult res;

  if (globs->file == NULL) {
    g_print ("(EE) VFSChmod: globs->file == NULL !\n");
    return cVFS_Failed;
  }

  f = g_file_resolve_relative_path (globs->file, FileName);
  if (f == NULL) {
    g_print ("(EE) VFSChmod: g_file_resolve_relative_path() failed.\n");
    return cVFS_Failed;
  }
//  g_print ("(II) VFSChmod (%s, %d): Going to set permissions on '%s'\n", FileName, Mode, g_file_get_uri (f));

  error = NULL;
  g_file_set_attribute_uint32 (f, G_FILE_ATTRIBUTE_UNIX_MODE, Mode, G_FILE_QUERY_INFO_NONE, NULL, &error);
  g_object_unref (f);
  if (error) {
    g_print ("(EE) VFSChmod: g_file_set_attribute_uint32() error: %s\n", error->message);
    res = g_error_to_TVFSResult (error);
    g_error_free (error);
    return res;
  }
  return cVFS_OK;
}

TVFSResult
VFSChown (struct TVFSGlobs *globs, const char *FileName, const uint UID, const uint GID)
{
  GFile *f;
  GError *error;
  TVFSResult res;

  if (globs->file == NULL) {
    g_print ("(EE) VFSChown: globs->file == NULL !\n");
    return cVFS_Failed;
  }

  f = g_file_resolve_relative_path (globs->file, FileName);
  if (f == NULL) {
    g_print ("(EE) VFSChown: g_file_resolve_relative_path() failed.\n");
    return cVFS_Failed;
  }

  error = NULL;
  g_file_set_attribute_uint32 (f, G_FILE_ATTRIBUTE_UNIX_UID, UID, G_FILE_QUERY_INFO_NONE, NULL, &error);
  if (error) {
    g_print ("(EE) VFSChown: g_file_set_attribute_uint32() error: %s\n", error->message);
    res = g_error_to_TVFSResult (error);
    g_error_free (error);
    g_object_unref (f);
    return res;
  }
  error = NULL;
  g_file_set_attribute_uint32 (f, G_FILE_ATTRIBUTE_UNIX_GID, GID, G_FILE_QUERY_INFO_NONE, NULL, &error);
  if (error) {
    g_print ("(EE) VFSChown: g_file_set_attribute_uint32() error: %s\n", error->message);
    res = g_error_to_TVFSResult (error);
    g_error_free (error);
    g_object_unref (f);
    return res;
  }
  g_object_unref (f);
  return cVFS_OK;
}

TVFSResult
VFSChangeTimes (struct TVFSGlobs *globs, char *APath, long mtime, long atime)
{
  GFile *f;
  GError *error;
  TVFSResult res;

  if (globs->file == NULL) {
    g_print ("(EE) VFSChangeTimes: globs->file == NULL !\n");
    return cVFS_Failed;
  }

  f = g_file_resolve_relative_path (globs->file, APath);
  if (f == NULL) {
    g_print ("(EE) VFSChangeTimes: g_file_resolve_relative_path() failed.\n");
    return cVFS_Failed;
  }

  error = NULL;
  g_file_set_attribute_uint64 (f, G_FILE_ATTRIBUTE_TIME_MODIFIED, mtime, G_FILE_QUERY_INFO_NONE, NULL, &error);
  if (error) {
    g_print ("(EE) VFSChangeTimes: g_file_set_attribute_uint64() error: %s\n", error->message);
    res = g_error_to_TVFSResult (error);
    g_error_free (error);
    g_object_unref (f);
    return res;
  }
  error = NULL;
  g_file_set_attribute_uint64 (f, G_FILE_ATTRIBUTE_TIME_ACCESS, atime, G_FILE_QUERY_INFO_NONE, NULL, &error);
  if (error) {
    g_print ("(EE) VFSChangeTimes: g_file_set_attribute_uint64() error: %s\n", error->message);
    g_error_free (error);
    /*  Silently drop the error, atime is not commonly supported on most systems  */
  }
  g_object_unref (f);
  return cVFS_OK;
}


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

static void
VFSGetDirSize_recurse (struct TVFSGlobs *globs, GFile *file, guint64 *size)
{
  GFile *f;
  GFileEnumerator *en;
  GError *error;
  GFileInfo *info;

  if (globs->break_get_dir_size)
    return;

  error = NULL;
  en = g_file_enumerate_children (file, G_FILE_ATTRIBUTE_STANDARD_TYPE "," G_FILE_ATTRIBUTE_STANDARD_NAME "," G_FILE_ATTRIBUTE_STANDARD_SIZE,
                                  G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, NULL, &error);
  if (error) {
    g_print ("(EE) VFSGetDirSize_recurse: g_file_enumerate_children() error: %s\n", error->message);
    g_error_free (error);
    return;
  }

  error = NULL;
  while ((info = g_file_enumerator_next_file (en, NULL, &error))) {
    if (globs->break_get_dir_size)
      break;

    if (g_file_info_get_file_type (info) == G_FILE_TYPE_DIRECTORY) {
      f = g_file_resolve_relative_path (file, g_file_info_get_name (info));
      if (f == NULL) {
        g_print ("(EE) VFSGetDirSize_recurse: g_file_resolve_relative_path() failed.\n");
        return;
      }
      VFSGetDirSize_recurse (globs, f, size);
      g_object_unref (f);
    }
    else
      *size += g_file_info_get_size (info);
    g_object_unref (info);
  }
  if (error) {
    g_print ("(EE) VFSGetDirSize_recurse: g_file_enumerator_next_file() error: %s\n", error->message);
    g_error_free (error);
  }
  g_file_enumerator_close (en, NULL, &error);
  g_object_unref (en);
  if (error) {
    g_print ("(EE) VFSGetDirSize_recurse: g_file_enumerator_close() error: %s\n", error->message);
    g_error_free (error);
  }
}

guint64
VFSGetDirSize (struct TVFSGlobs *globs, char *APath)
{
  GFile *f;
  guint64 size;

  if (globs == NULL) return 0;
  if (globs->file == NULL) {
    g_print ("(EE) VFSGetDirSize: globs->file == NULL !\n");
    return 0;
  }
  f = g_file_resolve_relative_path (globs->file, APath);
  if (f == NULL) {
    g_print ("(EE) VFSGetDirSize: g_file_resolve_relative_path() failed.\n");
    return 0;
  }

  size = 0;
  globs->break_get_dir_size = FALSE;
  VFSGetDirSize_recurse (globs, f, &size);
  globs->break_get_dir_size = FALSE;
  g_object_unref (f);

  return size;
}

void
VFSBreakGetDirSize (struct TVFSGlobs *globs)
{
  g_print ("(WW) VFSBreakGetDirSize: ################################### calling Break\n");
  if (globs)
    globs->break_get_dir_size = TRUE;
}


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

TVFSFileDes
VFSOpenFile (struct TVFSGlobs *globs, const char *APath, int Mode, int *Error)
{
  printf("(WW) VFSOpenFile: Not supported in GVFS plugin.\n");
  return NULL;
}

TVFSResult
VFSCloseFile (struct TVFSGlobs *globs, TVFSFileDes FileDescriptor)
{
  printf("(WW) VFSCloseFile: Not supported in GVFS plugin.\n");
  return cVFS_Not_Supported;
}

guint64
VFSFileSeek (struct TVFSGlobs *globs, TVFSFileDes FileDescriptor, guint64 AbsoluteOffset, int *Error)
{
  printf("(WW) VFSFileSeek: Not supported in GVFS plugin.\n");
  return -1;
}

int
VFSReadFile (struct TVFSGlobs *globs, TVFSFileDes FileDescriptor, gpointer Buffer, int ABlockSize, int *Error)
{
  printf("(WW) VFSReadFile: Not supported in GVFS plugin.\n");
  return cVFS_Not_Supported;
}

int
VFSWriteFile (struct TVFSGlobs *globs, TVFSFileDes FileDescriptor, gpointer Buffer, int BytesCount, int *Error)
{
  printf("(WW) VFSWriteFile: Not supported in GVFS plugin.\n");
  return cVFS_Not_Supported;
}

void
VFSSetBlockSize (struct TVFSGlobs *globs, int Value)
{
  if (globs)
    globs->block_size = Value;
}

gboolean
VFSIsOnSameFS (struct TVFSGlobs *globs, const char *Path1, const char *Path2)
{
  GFile *file1, *file2;
  GFileInfo *info1, *info2;
  GError *error;
  gboolean res;

  if (globs->file == NULL) {
    g_print ("(EE) VFSIsOnSameFS: globs->file == NULL !\n");
    return FALSE;
  }

  file1 = g_file_resolve_relative_path (globs->file, Path1);
  file2 = g_file_resolve_relative_path (globs->file, Path2);
  if (file1 == NULL) {
    g_print ("(EE) VFSIsOnSameFS: g_file_resolve_relative_path() failed.\n");
    return FALSE;
  }
  if (file2 == NULL) {
    g_print ("(EE) VFSIsOnSameFS: g_file_resolve_relative_path() failed.\n");
    return FALSE;
  }

  error = NULL;
  info1 = g_file_query_info (file1, G_FILE_ATTRIBUTE_ID_FILESYSTEM,
                                    G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, NULL, &error);
  if (error) {
    g_print ("(EE) VFSIsOnSameFS: g_file_query_info() error: %s\n", error->message);
    g_error_free (error);
    g_object_unref (file1);
    g_object_unref (file2);
    return FALSE;
  }
  info2 = g_file_query_info (file2, G_FILE_ATTRIBUTE_ID_FILESYSTEM,
                                    G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, NULL, &error);
  if (error) {
    g_print ("(EE) VFSIsOnSameFS: g_file_query_info() error: %s\n", error->message);
    g_error_free (error);
    g_object_unref (info1);
    g_object_unref (file1);
    g_object_unref (file2);
    return FALSE;
  }

  g_print ("(II) VFSIsOnSameFS: '%s' vs. '%s'\n", g_file_info_get_attribute_string (info1, G_FILE_ATTRIBUTE_ID_FILESYSTEM),
                                                  g_file_info_get_attribute_string (info2, G_FILE_ATTRIBUTE_ID_FILESYSTEM));

  res = strcmp (g_file_info_get_attribute_string (info1, G_FILE_ATTRIBUTE_ID_FILESYSTEM),
                g_file_info_get_attribute_string (info2, G_FILE_ATTRIBUTE_ID_FILESYSTEM)) == 0;
  g_object_unref (file1);
  g_object_unref (file2);
  g_object_unref (info1);
  g_object_unref (info2);
  return res;
}

gboolean
VFSTwoSameFiles (struct TVFSGlobs *globs, const char *Path1, const char *Path2)
{
  GFile *file1, *file2;
  gboolean res;

  if (globs->file == NULL) {
    g_print ("(EE) VFSTwoSameFiles: globs->file == NULL !\n");
    return FALSE;
  }

  file1 = g_file_resolve_relative_path (globs->file, Path1);
  file2 = g_file_resolve_relative_path (globs->file, Path2);
  if (file1 == NULL) {
    g_print ("(EE) VFSTwoSameFiles: g_file_resolve_relative_path() failed.\n");
    return FALSE;
  }
  if (file2 == NULL) {
    g_print ("(EE) VFSTwoSameFiles: g_file_resolve_relative_path() failed.\n");
    return FALSE;
  }

  /* FIXME: we should do some I/O test, we're esentially comparing strings at the moment */
  res = g_file_equal (file1, file2);
  g_object_unref (file1);
  g_object_unref (file2);
  return res;
}


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

static void
vfs_copy_progress_callback (goffset current_num_bytes,
                            goffset total_num_bytes,
                            gpointer user_data)
{
  struct TVFSGlobs *globs;

//  g_print ("(II) vfs_copy_progress_callback spawned: current_num_bytes = %lu, total_num_bytes = %lu\n", current_num_bytes, total_num_bytes);

  if (! user_data)
    return;
  globs = (struct TVFSGlobs *)user_data;

  if (globs->callback_progress) {
    if (! globs->callback_progress (current_num_bytes, total_num_bytes, globs->callback_data))
      g_cancellable_cancel (globs->cancellable);
  }
}

#define TUXCMD_DEFAULT_COPY_FLAGS G_FILE_COPY_OVERWRITE | G_FILE_COPY_NOFOLLOW_SYMLINKS | G_FILE_COPY_ALL_METADATA

TVFSResult
VFSCopyToLocal (struct TVFSGlobs *globs, const char *sSrcName, const char *sDstName, gboolean Append)
{
  GFile *src, *dst;
  GError *error;
  TVFSResult res;


  if (globs->file == NULL) {
    g_print ("(EE) VFSCopyToLocal: globs->file == NULL !\n");
    return cVFS_Failed;
  }

  g_print ("(II) VFSCopyToLocal: '%s' --> '%s'\n", sSrcName, sDstName);

  src = g_file_resolve_relative_path (globs->file, sSrcName);
  if (src == NULL) {
    g_print ("(EE) VFSCopyToLocal: g_file_resolve_relative_path() failed.\n");
    return cVFS_Failed;
  }
  dst = g_file_new_for_path (sDstName);
  if (dst == NULL) {
    g_print ("(EE) VFSCopyToLocal: g_file_resolve_relative_path() failed.\n");
    return cVFS_Failed;
  }

  globs->cancellable = g_cancellable_new ();

  res = cVFS_OK;
  error = NULL;
  g_file_copy (src, dst, TUXCMD_DEFAULT_COPY_FLAGS, globs->cancellable, vfs_copy_progress_callback, globs, &error);
  if (error) {
    g_print ("(EE) VFSCopyToLocal: g_file_copy() error: %s\n", error->message);
//    res = g_error_to_TVFSResult (error);
    if (error->code == G_IO_ERROR_CANCELLED)
      res = cVFS_Cancelled;
    else res = cVFS_ReadErr;
    g_error_free (error);
  }

  g_object_unref (globs->cancellable);
  g_object_unref (src);
  g_object_unref (dst);
  return res;
}

TVFSResult
VFSCopyFromLocal (struct TVFSGlobs *globs, const char *sSrcName, const char *sDstName, gboolean Append)
{
  GFile *src, *dst;
  GError *error;
  TVFSResult res;


  if (globs->file == NULL) {
    g_print ("(EE) VFSCopyFromLocal: globs->file == NULL !\n");
    return cVFS_Failed;
  }

  g_print ("(II) VFSCopyFromLocal: '%s' --> '%s'\n", sSrcName, sDstName);

  src = g_file_new_for_path (sSrcName);
  if (src == NULL) {
    g_print ("(EE) VFSCopyFromLocal: g_file_resolve_relative_path() failed.\n");
    return cVFS_Failed;
  }
  dst = g_file_resolve_relative_path (globs->file, sDstName);
  if (dst == NULL) {
    g_print ("(EE) VFSCopyFromLocal: g_file_resolve_relative_path() failed.\n");
    return cVFS_Failed;
  }

  globs->cancellable = g_cancellable_new ();

  res = cVFS_OK;
  error = NULL;
  /* FIXME: Appending not supported */
  g_file_copy (src, dst, TUXCMD_DEFAULT_COPY_FLAGS, globs->cancellable, vfs_copy_progress_callback, globs, &error);
  if (error) {
    g_print ("(EE) VFSCopyFromLocal: g_file_copy() error: %s\n", error->message);
//    res = g_error_to_TVFSResult (error);
    if (error->code == G_IO_ERROR_CANCELLED)
      res = cVFS_Cancelled;
    else res = cVFS_WriteErr;
    g_error_free (error);
  }

  g_object_unref (globs->cancellable);
  g_object_unref (src);
  g_object_unref (dst);
  return res;
}



/**********
 * TODO:
 *        NOT NEEDED - block size settings for GIO subsystem
 *        NOT NEEDED - variable block size for different protocols?
 *        - support for appending in VFSCopyFromLocal
 *        DONE- authentication improvements (needs new VFS API)
 *
 ***/
