 /* GIO - GLib Input, Output and Streaming Library
 * 
 * Copyright (C) Carl-Anton Ingmarsson 2011 <ca.ingmarsson@gmail.com>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General
 * Public License along with this library; if not, write to the
 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 *
 * Author: Carl-Anton Ingmarsson <ca.ingmarsson@gmail.com>
 */

#include <glib/gi18n.h>

#include "gvfsafpserver.h"

#include "gvfsafpvolume.h"


struct _GVfsAfpVolumePrivate
{
  GVfsAfpServer *server;
  GVfsAfpConnection *conn;
  gboolean mounted;

  guint16 attributes;
  guint16 volume_id;
};

G_DEFINE_TYPE_WITH_PRIVATE (GVfsAfpVolume, g_vfs_afp_volume, G_TYPE_OBJECT);

static void
attention_cb (GVfsAfpConnection *conn, guint attention, GVfsAfpVolume *volume);

static void
g_vfs_afp_volume_init (GVfsAfpVolume *volume)
{
  GVfsAfpVolumePrivate *priv;

  volume->priv = priv = g_vfs_afp_volume_get_instance_private (volume);
  priv->mounted = FALSE;
}

static void
g_vfs_afp_volume_finalize (GObject *object)
{
  GVfsAfpVolume *volume;
  GVfsAfpVolumePrivate *priv;

  volume = G_VFS_AFP_VOLUME (object);
  priv = volume->priv;
  g_signal_handlers_disconnect_by_func (priv->conn, attention_cb, volume);

  G_OBJECT_CLASS (g_vfs_afp_volume_parent_class)->finalize (object);
}

static void
g_vfs_afp_volume_class_init (GVfsAfpVolumeClass *klass)
{
  GObjectClass* object_class = G_OBJECT_CLASS (klass);

  object_class->finalize = g_vfs_afp_volume_finalize;
}

GVfsAfpVolume *
g_vfs_afp_volume_new (GVfsAfpServer *server, GVfsAfpConnection *conn)
{
  GVfsAfpVolume *volume;
  GVfsAfpVolumePrivate *priv;

  g_return_val_if_fail (G_VFS_IS_AFP_SERVER (server), NULL);
  g_return_val_if_fail (G_VFS_IS_AFP_CONNECTION (conn), NULL);
  
  volume = g_object_new (G_VFS_TYPE_AFP_VOLUME, NULL);
  priv = volume->priv;

  priv->server = server;
  priv->conn = conn;
  g_signal_connect (priv->conn, "attention", G_CALLBACK (attention_cb), volume);

  return volume;
}

gboolean
g_vfs_afp_volume_mount_sync (GVfsAfpVolume *volume,
                             const char    *volume_name,
                             GCancellable  *cancellable,
                             GError       **error)
{
  GVfsAfpVolumePrivate *priv;
  GVfsAfpCommand *comm;
  GVfsAfpReply *reply;
  AfpResultCode res_code;

  g_return_val_if_fail (G_VFS_IS_AFP_VOLUME (volume), FALSE);
  g_return_val_if_fail (volume_name != NULL, FALSE);

  priv = volume->priv;
  
  /* Open Volume */
  comm = g_vfs_afp_command_new (AFP_COMMAND_OPEN_VOL);
  /* pad byte */
  g_vfs_afp_command_put_byte (comm, 0);
  /* Volume Bitmap */
  g_vfs_afp_command_put_uint16 (comm, AFP_VOLUME_BITMAP_VOL_ID_BIT | AFP_VOLUME_BITMAP_ATTRIBUTE_BIT);

  /* VolumeName */
  g_vfs_afp_command_put_pascal (comm, volume_name);

  /* TODO: password? */

  reply = g_vfs_afp_connection_send_command_sync (priv->conn, comm, cancellable,
                                                  error);
  g_object_unref (comm);
  if (!reply)
    return FALSE;

  res_code = g_vfs_afp_reply_get_result_code (reply);
  if (res_code != AFP_RESULT_NO_ERROR)
  {
    g_object_unref (reply);

    if (res_code == AFP_RESULT_OBJECT_NOT_FOUND)
    {
      g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND,
                           _("Volume doesn’t exist"));
      return FALSE;
    }

    goto generic_error;
  }
  
  /* Volume Bitmap */
  g_vfs_afp_reply_read_uint16 (reply, NULL);
  /* Volume Attributes Bitmap */
  g_vfs_afp_reply_read_uint16 (reply, &priv->attributes);
  /* Volume ID */
  g_vfs_afp_reply_read_uint16 (reply, &priv->volume_id);
  
  g_object_unref (reply);

  priv->mounted = TRUE;
  return TRUE;

generic_error:
  g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
               /* Translators: first %s is volumename and second servername */
               _("Couldn’t load %s on %s"), volume_name,
               g_vfs_afp_server_get_info(priv->server)->server_name);
  return FALSE;
}

guint16
g_vfs_afp_volume_get_attributes (GVfsAfpVolume *volume)
{
  GVfsAfpVolumePrivate *priv = volume->priv;

  g_return_val_if_fail (priv->mounted, 0);
  
  return priv->attributes; 
}

guint16
g_vfs_afp_volume_get_id (GVfsAfpVolume *volume)
{
  GVfsAfpVolumePrivate *priv = volume->priv;

  g_return_val_if_fail (priv->mounted, 0);
  
  return priv->volume_id; 
}

static void
get_vol_parms_cb (GObject *source_object, GAsyncResult *res, gpointer user_data)
{
  GVfsAfpConnection *connection = G_VFS_AFP_CONNECTION (source_object);
  GTask *task = G_TASK (user_data);
  GVfsAfpVolume *volume = G_VFS_AFP_VOLUME (g_task_get_source_object (task));
  GVfsAfpVolumePrivate *priv = volume->priv;

  GVfsAfpReply *reply;
  GError *err = NULL;
  AfpResultCode res_code;
  
  guint16 vol_bitmap;
  GFileInfo *info;

  guint64 bytes_free, bytes_total;

  reply = g_vfs_afp_connection_send_command_finish (connection, res, &err);
  if (!reply)
  {
    g_task_return_error (task, err);
    g_object_unref (task);
    return;
  }

  res_code = g_vfs_afp_reply_get_result_code (reply);
  if (res_code != AFP_RESULT_NO_ERROR)
  {
    g_object_unref (reply);

    g_task_return_error (task, afp_result_code_to_gerror (res_code));
    g_object_unref (task);
    return;
  }

  g_vfs_afp_reply_read_uint16 (reply, &vol_bitmap);

  info = g_file_info_new ();

  if (vol_bitmap & AFP_VOLUME_BITMAP_ATTRIBUTE_BIT)
  {
    guint16 vol_attrs_bitmap;
    
    g_vfs_afp_reply_read_uint16 (reply, &vol_attrs_bitmap);
    
    g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_FILESYSTEM_READONLY,
                                       vol_attrs_bitmap & AFP_VOLUME_ATTRIBUTES_BITMAP_READ_ONLY);
  }

  if (vol_bitmap & AFP_VOLUME_BITMAP_CREATE_DATE_BIT)
  {
    gint32 create_date;
    gint64 create_date_local;

    g_vfs_afp_reply_read_int32 (reply, &create_date);

    create_date_local = g_vfs_afp_server_time_to_local_time (priv->server, create_date);
    g_file_info_set_attribute_uint64 (info, G_FILE_ATTRIBUTE_TIME_CREATED,
                                      create_date_local);
  }

  if (vol_bitmap & AFP_VOLUME_BITMAP_MOD_DATE_BIT)
  {
    gint32 mod_date;
    gint64 mod_date_local;

    g_vfs_afp_reply_read_int32 (reply, &mod_date);

    mod_date_local = g_vfs_afp_server_time_to_local_time (priv->server, mod_date);
    g_file_info_set_attribute_uint64 (info, G_FILE_ATTRIBUTE_TIME_MODIFIED,
                                      mod_date_local);
  }

  if (vol_bitmap & AFP_VOLUME_BITMAP_EXT_BYTES_FREE_BIT)
  {
    g_vfs_afp_reply_read_uint64 (reply, &bytes_free);
    g_file_info_set_attribute_uint64 (info, G_FILE_ATTRIBUTE_FILESYSTEM_FREE,
                                      bytes_free);
  }

  if (vol_bitmap & AFP_VOLUME_BITMAP_EXT_BYTES_TOTAL_BIT)
  {
    g_vfs_afp_reply_read_uint64 (reply, &bytes_total);
    g_file_info_set_attribute_uint64 (info, G_FILE_ATTRIBUTE_FILESYSTEM_SIZE,
                                      bytes_total);
  }

  if (vol_bitmap & AFP_VOLUME_BITMAP_EXT_BYTES_FREE_BIT &&
      vol_bitmap & AFP_VOLUME_BITMAP_EXT_BYTES_TOTAL_BIT)
    g_file_info_set_attribute_uint64 (info, G_FILE_ATTRIBUTE_FILESYSTEM_USED,
                                      bytes_total - bytes_free);

  g_object_unref (reply);

  g_task_return_pointer (task, info, g_object_unref);
  g_object_unref (task);
}

/*
 * g_vfs_afp_volume_get_parms:
 * 
 * @volume: a #GVfsAfpVolume
 * @vol_bitmap: bitmap describing the parameters that should be received.
 * @cancellable: optional #GCancellable object, %NULL to ignore.
 * @callback: callback to call when the request is satisfied.
 * @user_data: the data to pass to callback function.
 * 
 * Asynchronously retrieves the parameters specified by @vol_bitmap from @volume
 */
void
g_vfs_afp_volume_get_parms (GVfsAfpVolume       *volume,
                            guint16              vol_bitmap,
                            GCancellable        *cancellable,
                            GAsyncReadyCallback  callback,
                            gpointer             user_data)
{
  GVfsAfpVolumePrivate *priv;
  GVfsAfpCommand *comm;
  GTask *task;

  priv = volume->priv;
  
  comm = g_vfs_afp_command_new (AFP_COMMAND_GET_VOL_PARMS);
  /* pad byte */
  g_vfs_afp_command_put_byte (comm, 0);
  /* Volume ID */
  g_vfs_afp_command_put_uint16 (comm, priv->volume_id);
  /* Volume Bitmap */
  g_vfs_afp_command_put_uint16 (comm, vol_bitmap);

  task = g_task_new (volume, cancellable, callback, user_data);
  g_task_set_source_tag (task, g_vfs_afp_volume_get_parms);

  g_vfs_afp_connection_send_command (priv->conn, comm, NULL, 0, get_vol_parms_cb,
                                     cancellable, task);
  g_object_unref (comm);
}

/*
 * g_vfs_afp_volume_get_parms_finish:
 * 
 * @volume: a #GVfsAfpVolume.
 * @result: a #GAsyncResult.
 * @error: a #GError, %NULL to ignore.
 * 
 * Finalizes the asynchronous operation started by
 * g_vfs_afp_volume_get_parms.
 * 
 * Returns: (transfer full): A #GFileInfo with the requested parameters or %NULL
 * on error.
 */
GFileInfo *
g_vfs_afp_volume_get_parms_finish (GVfsAfpVolume  *volume,
                                   GAsyncResult   *result,
                                   GError         **error)
{
  g_return_val_if_fail (g_task_is_valid (result, volume), NULL);
  g_return_val_if_fail (g_async_result_is_tagged (result, g_vfs_afp_volume_get_parms), NULL);

  return g_task_propagate_pointer (G_TASK (result), error);
}

typedef struct
{
  gint16 fork_refnum;
  GFileInfo *info;
} OpenForkData;

static void
open_fork_data_free (OpenForkData *data)
{
  g_object_unref (data->info);

  g_slice_free (OpenForkData, data);
}

static void
open_fork_cb (GObject *source_object, GAsyncResult *result, gpointer user_data)
{
  GVfsAfpConnection *conn = G_VFS_AFP_CONNECTION (source_object);
  GTask *task = G_TASK (user_data);
  GVfsAfpVolume *volume;
  GVfsAfpVolumePrivate *priv;
  GVfsAfpReply *reply;
  GError *err = NULL;
  AfpResultCode res_code;
  gboolean res;

  OpenForkData *data;
  guint16 file_bitmap;

  volume = G_VFS_AFP_VOLUME (g_task_get_source_object (task));
  priv = volume->priv;
  
  reply = g_vfs_afp_connection_send_command_finish (conn, result, &err);
  if (!reply)
  {
    g_task_return_error (task, err);
    g_object_unref (task);
    return;
  }

  res_code = g_vfs_afp_reply_get_result_code (reply);
  if (res_code != AFP_RESULT_NO_ERROR)
  {
    g_object_unref (reply);

    switch (res_code)
    {
      case AFP_RESULT_ACCESS_DENIED:
        g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_PERMISSION_DENIED,
                                 _("Permission denied"));
        break;
      case AFP_RESULT_OBJECT_NOT_FOUND:
        g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_NOT_FOUND,
                                 _("File doesn’t exist"));
        break;
      case AFP_RESULT_OBJECT_TYPE_ERR:
        g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_IS_DIRECTORY,
                                 _("File is directory"));
        break;
      case AFP_RESULT_TOO_MANY_FILES_OPEN:
        g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_TOO_MANY_OPEN_FILES,
                                 _("Too many files open"));
        break;
      default:
        g_task_return_error (task, afp_result_code_to_gerror (res_code));
        break;
    }

    g_object_unref (task);
    return;
  }

  data = g_slice_new (OpenForkData);
  
  g_vfs_afp_reply_read_uint16 (reply, &file_bitmap);
  g_vfs_afp_reply_read_int16  (reply, &data->fork_refnum);

  data->info = g_file_info_new ();
  res = g_vfs_afp_server_fill_info (priv->server, data->info, reply, FALSE, file_bitmap, &err);
  g_object_unref (reply);
  if (!res)
  {
    g_task_return_error (task, err);
    g_object_unref (task);
    return;
  }

  g_task_set_task_data (task, data, (GDestroyNotify)open_fork_data_free);
  g_task_return_boolean (task, TRUE);
  g_object_unref (task);
}

/*
 * g_vfs_afp_volume_open_fork:
 * 
 * @volume: a #GVfsAfpVolume
 * @filename: file to open fork for.
 * @access_mode:
 * @bitmap:
 * @cancellable: optional #GCancellable object, %NULL to ignore.
 * @callback: callback to call when the request is satisfied.
 * @user_data: the data to pass to callback function.
 * 
 * Asynchronously opens a fork corresponding to @filename with the requested
 * access rights.
 */
void
g_vfs_afp_volume_open_fork (GVfsAfpVolume      *volume,
                            const char         *filename,
                            guint16             access_mode,
                            guint16             bitmap,
                            GCancellable       *cancellable,
                            GAsyncReadyCallback callback,
                            gpointer            user_data)
{
  GVfsAfpVolumePrivate *priv;
  GVfsAfpCommand *comm;
  GTask *task;

  g_return_if_fail (G_VFS_IS_AFP_VOLUME (volume));

  priv = volume->priv;

  comm = g_vfs_afp_command_new (AFP_COMMAND_OPEN_FORK);
  /* data fork */
  g_vfs_afp_command_put_byte (comm, 0);

  /* Volume ID */
  g_vfs_afp_command_put_uint16 (comm, g_vfs_afp_volume_get_id (volume));
  /* Directory ID */
  g_vfs_afp_command_put_uint32 (comm, 2);

  /* Bitmap */
  g_vfs_afp_command_put_uint16 (comm, bitmap);

  /* AccessMode */
  g_vfs_afp_command_put_uint16 (comm, access_mode);

  /* Pathname */
  g_vfs_afp_command_put_pathname (comm, filename);

  task = g_task_new (volume, cancellable, callback, user_data);
  g_task_set_source_tag (task, g_vfs_afp_volume_open_fork);

  g_vfs_afp_connection_send_command (priv->conn, comm, NULL, 0,
                                     open_fork_cb, cancellable, task);
  g_object_unref (comm);
}

/*
 * g_vfs_afp_volume_open_fork_finish:
 * 
 * @volume: a #GVfsAfpVolume.
 * @result: a #GAsyncResult.
 * @fork_refnum: (out) the reference id of the newly opened fork.
 * @info: (out callee-allocates) a #GFileInfo containing the requested parameters.
 * @error: a #GError, %NULL to ignore.
 * 
 * Finalizes the asynchronous operation started by
 * g_vfs_afp_volume_open_fork.
 * 
 * Returns: %TRUE on success, %FALSE on error.
 */
gboolean
g_vfs_afp_volume_open_fork_finish (GVfsAfpVolume  *volume,
                                   GAsyncResult   *res,
                                   gint16         *fork_refnum,
                                   GFileInfo      **info,
                                   GError         **error)
{
  OpenForkData *data;

  g_return_val_if_fail (g_task_is_valid (res, volume), FALSE);
  g_return_val_if_fail (g_async_result_is_tagged (res, g_vfs_afp_volume_open_fork), FALSE);

  if (!g_task_propagate_boolean (G_TASK (res), error))
    return FALSE;

  data = g_task_get_task_data (G_TASK (res));
  if (fork_refnum)
    *fork_refnum = data->fork_refnum;
  if (info)
    *info = g_object_ref (data->info);

  return TRUE;
}

static void
close_fork_cb (GObject *source_object, GAsyncResult *res, gpointer user_data)
{
  GVfsAfpConnection *conn = G_VFS_AFP_CONNECTION (source_object);
  GTask *task = G_TASK (user_data);

  GVfsAfpReply *reply;
  GError *err = NULL;
  AfpResultCode res_code;

  reply = g_vfs_afp_connection_send_command_finish (conn, res, &err);
  if (!reply)
  {
    g_task_return_error (task, err);
    g_object_unref (task);
    return;
  }

  res_code = g_vfs_afp_reply_get_result_code (reply);
  g_object_unref (reply);
  
  if (res_code != AFP_RESULT_NO_ERROR)
  {
    g_task_return_error (task, afp_result_code_to_gerror (res_code));
    g_object_unref (task);
    return;
  }

  g_task_return_boolean (task, TRUE);
  g_object_unref (task);
}

/*
 * g_vfs_afp_volume_close_fork:
 * 
 * @volume: a #GVfsAfpVolume.
 * @fork_refnum: the reference id of the fork which is to be closed.
 * @cancellable: optional #GCancellable object, %NULL to ignore.
 * @callback: callback to call when the request is satisfied.
 * @user_data: the data to pass to callback function.
 * 
 * Asynchronously closes an open fork specified by @fork_refnum.
 */
void
g_vfs_afp_volume_close_fork (GVfsAfpVolume       *volume,
                             gint16               fork_refnum,
                             GCancellable        *cancellable,
                             GAsyncReadyCallback  callback,
                             gpointer             user_data)
{
  GVfsAfpVolumePrivate *priv;
  GVfsAfpCommand *comm;
  GTask *task;

  g_return_if_fail (G_VFS_IS_AFP_VOLUME (volume));

  priv = volume->priv;
  
  comm = g_vfs_afp_command_new (AFP_COMMAND_CLOSE_FORK);
  /* pad byte */
  g_vfs_afp_command_put_byte (comm, 0);

  /* OForkRefNum */
  g_vfs_afp_command_put_int16 (comm, fork_refnum);

  task = g_task_new (volume, cancellable, callback, user_data);
  g_task_set_source_tag (task, g_vfs_afp_volume_close_fork);

  g_vfs_afp_connection_send_command (priv->conn, comm, NULL, 0,
                                     close_fork_cb, cancellable, task);
  g_object_unref (comm);
}

/*
 * g_vfs_afp_volume_close_fork_finish:
 * 
 * @volume: a #GVfsAfpVolume.
 * @result: a #GAsyncResult.
 * @error: a #GError, %NULL to ignore.
 * 
 * Finalizes the asynchronous operation started by
 * g_vfs_afp_volume_close_fork.
 * 
 * Returns: %TRUE on success, %FALSE on error.
 */
gboolean
g_vfs_afp_volume_close_fork_finish (GVfsAfpVolume  *volume,
                                    GAsyncResult   *result,
                                    GError         **error)
{
  g_return_val_if_fail (g_task_is_valid (result, volume), FALSE);
  g_return_val_if_fail (g_async_result_is_tagged (result, g_vfs_afp_volume_close_fork), FALSE);

  return g_task_propagate_boolean (G_TASK (result), error);
}

static void
delete_cb (GObject *source_object, GAsyncResult *res, gpointer user_data)
{
  GVfsAfpConnection *conn = G_VFS_AFP_CONNECTION (source_object);
  GTask *task = G_TASK (user_data);

  GVfsAfpReply *reply;
  GError *err = NULL;
  AfpResultCode res_code;

  reply = g_vfs_afp_connection_send_command_finish (conn, res, &err);
  if (!reply)
  {
    g_task_return_error (task, err);
    g_object_unref (task);
    return;
  }

  res_code = g_vfs_afp_reply_get_result_code (reply);
  g_object_unref (reply);
  
  if (res_code != AFP_RESULT_NO_ERROR)
  {
    switch (res_code)
    {
      case AFP_RESULT_ACCESS_DENIED:
        g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_PERMISSION_DENIED,
                                 _("Permission denied"));
        break;
      case AFP_RESULT_FILE_BUSY:
        g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_BUSY,
                                 _("Target file is open"));
        break;                           
      case AFP_RESULT_DIR_NOT_EMPTY:
        g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_NOT_EMPTY,
                                 _("Directory not empty"));
        break;
      case AFP_RESULT_OBJECT_LOCKED:
        g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_FAILED,
                                 _("Target object is marked as not deletable (DeleteInhibit)"));
        break;
      case AFP_RESULT_OBJECT_NOT_FOUND:
        g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_NOT_FOUND,
                                 _("Target object doesn’t exist"));
        break;
      case AFP_RESULT_VOL_LOCKED:
        g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_PERMISSION_DENIED,
                                 _("Volume is read-only"));
        break;
      default:
        g_task_return_error (task, afp_result_code_to_gerror (res_code));
        break;
    }

    g_object_unref (task);
    return;
  }

  g_task_return_boolean (task, TRUE);
  g_object_unref (task);
}

/*
 * g_vfs_afp_volume_delete:
 * 
 * @volume: a #GVfsAfpVolume.
 * @filename: file to delete.
 * @cancellable: optional #GCancellable object, %NULL to ignore.
 * @callback: callback to call when the request is satisfied.
 * @user_data: the data to pass to callback function.
 * 
 * Asynchronously deletes the file @filename.
 */
void
g_vfs_afp_volume_delete (GVfsAfpVolume       *volume,
                         const char          *filename,
                         GCancellable        *cancellable,
                         GAsyncReadyCallback  callback,
                         gpointer             user_data)
{
  GVfsAfpVolumePrivate *priv;
  GVfsAfpCommand *comm;
  GTask *task;

  g_return_if_fail (G_VFS_IS_AFP_VOLUME (volume));

  priv = volume->priv;
  
  comm = g_vfs_afp_command_new (AFP_COMMAND_DELETE);
  /* pad byte */
  g_vfs_afp_command_put_byte (comm, 0);
  /* Volume ID */
  g_vfs_afp_command_put_uint16 (comm, g_vfs_afp_volume_get_id (volume));
  /* Directory ID 2 == / */
  g_vfs_afp_command_put_uint32 (comm, 2);

  /* Pathname */
  g_vfs_afp_command_put_pathname (comm, filename);

  task = g_task_new (volume, cancellable, callback, user_data);
  g_task_set_source_tag (task, g_vfs_afp_volume_delete);

  g_vfs_afp_connection_send_command (priv->conn, comm, NULL, 0,
                                     delete_cb, cancellable, task);
  g_object_unref (comm);
}

/*
 * g_vfs_afp_volume_delete_finish:
 * 
 * @volume: a #GVfsAfpVolume.
 * @result: a #GAsyncResult.
 * @error: a #GError, %NULL to ignore.
 * 
 * Finalizes the asynchronous operation started by
 * g_vfs_afp_volume_delete.
 * 
 * Returns: %TRUE on success, %FALSE on error.
 */
gboolean
g_vfs_afp_volume_delete_finish (GVfsAfpVolume  *volume,
                                GAsyncResult   *result,
                                GError         **error)
{
  g_return_val_if_fail (g_task_is_valid (result, volume), FALSE);
  g_return_val_if_fail (g_async_result_is_tagged (result, g_vfs_afp_volume_delete), FALSE);

  return g_task_propagate_boolean (G_TASK (result), error);
}

typedef struct
{
  char *filename;
  gboolean hard_create;
} CreateFileData;

static void
create_file_data_free (CreateFileData *cfd)
{
  g_free (cfd->filename);

  g_slice_free (CreateFileData, cfd);
}

static void
create_file_cb (GObject *object, GAsyncResult *res, gpointer user_data)
{
  GVfsAfpConnection *conn = G_VFS_AFP_CONNECTION (object);
  GTask *task = G_TASK (user_data);

  GVfsAfpReply *reply;
  GError *err = NULL;
  AfpResultCode res_code;

  
  reply = g_vfs_afp_connection_send_command_finish (conn, res, &err);
  if (!reply)
  {
    g_task_return_error (task, err);
    g_object_unref (task);
    return;
  }

  res_code = g_vfs_afp_reply_get_result_code (reply);
  g_object_unref (reply);
  if (res_code != AFP_RESULT_NO_ERROR)
  {
    switch (res_code)
    {
      case AFP_RESULT_ACCESS_DENIED:
        g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_PERMISSION_DENIED,
                                 _("Permission denied"));
        break;
      case AFP_RESULT_DISK_FULL:
        g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_NO_SPACE,
                                 _("Not enough space on volume"));
        break;
      case AFP_RESULT_FILE_BUSY:
        g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_EXISTS,
                                 _("Target file is open"));
        break;
      case AFP_RESULT_OBJECT_EXISTS:
      case AFP_RESULT_OBJECT_TYPE_ERR:
        g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_EXISTS,
                                 _("Target file already exists"));
        break;
      case AFP_RESULT_OBJECT_NOT_FOUND:
        g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_NOT_FOUND,
                                 _("Ancestor directory doesn’t exist"));
        break;
      case AFP_RESULT_VOL_LOCKED:
        g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_PERMISSION_DENIED,
                                 _("Volume is read-only"));
        break;
      default:
        g_task_return_error (task, afp_result_code_to_gerror (res_code));
        break;
    }

    g_object_unref (task);
    return;
  }

  g_task_return_boolean (task, TRUE);
  g_object_unref (task);
}

static void
create_file_get_filedir_parms_cb (GObject *source_object, GAsyncResult *res, gpointer user_data)
{
  GVfsAfpVolume *volume = G_VFS_AFP_VOLUME (source_object);
  GVfsAfpVolumePrivate *priv = volume->priv;
  GTask *task = G_TASK (user_data);
  CreateFileData *cfd = g_task_get_task_data (task);

  GFileInfo *info;
  GError *err = NULL;

  guint32 dir_id;
  char *basename;
  GVfsAfpCommand *comm;

  info = g_vfs_afp_volume_get_filedir_parms_finish (volume, res, &err);
  if (!info)
  {
    g_task_return_error (task, err);
    g_object_unref (task);
    return;
  }

  dir_id = g_file_info_get_attribute_uint32 (info, G_FILE_ATTRIBUTE_AFP_NODE_ID);
  g_object_unref (info);

  comm = g_vfs_afp_command_new (AFP_COMMAND_CREATE_FILE);
  /* soft/hard create */
  g_vfs_afp_command_put_byte (comm, cfd->hard_create ? 0x80 : 0x00);
  /* Volume ID */
  g_vfs_afp_command_put_uint16 (comm, g_vfs_afp_volume_get_id (volume));
  /* Directory ID */
  g_vfs_afp_command_put_uint32 (comm, dir_id);

  /* Pathname */
  basename = g_path_get_basename (cfd->filename);
  g_vfs_afp_command_put_pathname (comm, basename);
  g_free (basename);

  g_vfs_afp_connection_send_command (priv->conn, comm, NULL, 0, create_file_cb,
                                     g_task_get_cancellable (task), task);
  g_object_unref (comm);
}

/*
 * g_vfs_afp_volume_create_file:
 * 
 * @volume: a #GVfsAfpVolume.
 * @filename: path to the new file to create.
 * @hard_create: if %TRUE this call will overwrite an already existing file.
 * If %FALSE it will error out instead.
 * @cancellable: optional #GCancellable object, %NULL to ignore.
 * @callback: callback to call when the request is satisfied.
 * @user_data: the data to pass to callback function.
 * 
 * Asynchronously creates a file at @filename.
 */
void
g_vfs_afp_volume_create_file (GVfsAfpVolume      *volume,
                              const char         *filename,
                              gboolean            hard_create,
                              GCancellable       *cancellable,
                              GAsyncReadyCallback callback,
                              gpointer            user_data)
{
  CreateFileData *cfd;
  GTask *task;
  char *dirname;

  cfd = g_slice_new0 (CreateFileData);
  cfd->filename = g_strdup (filename);
  cfd->hard_create = hard_create;

  task = g_task_new (volume, cancellable, callback, user_data);
  g_task_set_source_tag (task, g_vfs_afp_volume_create_file);
  g_task_set_task_data (task, cfd, (GDestroyNotify)create_file_data_free);

  dirname = g_path_get_dirname (filename);
  g_vfs_afp_volume_get_filedir_parms (volume, dirname, 0, AFP_DIR_BITMAP_NODE_ID_BIT,
                                      cancellable, create_file_get_filedir_parms_cb, task);
  g_free (dirname);
}

/*
 * g_vfs_afp_volume_create_file_finish:
 * 
 * @volume: a #GVfsAfpVolume.
 * @result: a #GAsyncResult.
 * @error: a #GError, %NULL to ignore.
 * 
 * Finalizes the asynchronous operation started by
 * g_vfs_afp_volume_create_file.
 * 
 * Returns: %TRUE on success, %FALSE on error.
 */
gboolean
g_vfs_afp_volume_create_file_finish (GVfsAfpVolume  *volume,
                                     GAsyncResult   *result,
                                     GError         **error)
{
  g_return_val_if_fail (g_task_is_valid (result, volume), FALSE);
  g_return_val_if_fail (g_async_result_is_tagged (result, g_vfs_afp_volume_create_file), FALSE);

  return g_task_propagate_boolean (G_TASK (result), error);
}

typedef struct
{
  char *basename;
} CreateDirData;

static void
create_dir_data_free (CreateDirData *cdd)
{
  g_free (cdd->basename);

  g_slice_free (CreateDirData, cdd);
}

static void
make_directory_cb (GObject *source_object, GAsyncResult *res, gpointer user_data)
{
  GVfsAfpConnection *conn = G_VFS_AFP_CONNECTION (source_object);
  GTask *task = G_TASK (user_data);

  GVfsAfpReply *reply;
  GError *err = NULL;
  AfpResultCode res_code;

  reply = g_vfs_afp_connection_send_command_finish (conn, res, &err);
  if (!reply)
  {
    g_task_return_error (task, err);
    g_object_unref (task);
    return;
  }

  res_code = g_vfs_afp_reply_get_result_code (reply);
  g_object_unref (reply);

  if (res_code != AFP_RESULT_NO_ERROR)
  {
    switch (res_code)
    {
      case AFP_RESULT_ACCESS_DENIED:
        g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_PERMISSION_DENIED,
                                 _("Permission denied"));
        break;
      case AFP_RESULT_DISK_FULL:
        g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_NO_SPACE,
                                 _("Not enough space on volume"));
        break;
      case AFP_RESULT_FLAT_VOL:
        g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
                                 /* Translators: flat means volume doesn't support directories
                                    (all files are in the volume root) */
                                 _("Volume is flat and doesn’t support directories"));
        break;
      case AFP_RESULT_OBJECT_NOT_FOUND:
        g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_NOT_FOUND,
                                 _("Ancestor directory doesn’t exist"));
        break;
      case AFP_RESULT_OBJECT_EXISTS:
        g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_EXISTS,
                                 _("Target directory already exists"));
        break;
      case AFP_RESULT_VOL_LOCKED:
        g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_PERMISSION_DENIED,
                                 _("Volume is read-only"));
        break;
      default:
        g_task_return_error (task, afp_result_code_to_gerror (res_code));
        break;
    }

    g_object_unref (task);
    return;
  }

  g_task_return_boolean (task, TRUE);
  g_object_unref (task);
}

static void
create_directory_get_filedir_parms_cb (GObject *source_object, GAsyncResult *res, gpointer user_data)
{
  GVfsAfpVolume *volume = G_VFS_AFP_VOLUME (source_object);
  GTask *task = G_TASK (user_data);
  CreateDirData *cdd = g_task_get_task_data (task);

  GFileInfo *info = NULL;
  GError *err = NULL;

  guint32 dir_id;
  GVfsAfpCommand *comm;
  
  info = g_vfs_afp_volume_get_filedir_parms_finish (volume, res, &err);
  if (!info)
  {
    g_task_return_error (task, err);
    g_object_unref (task);
    return;
  }

  dir_id = g_file_info_get_attribute_uint32 (info, G_FILE_ATTRIBUTE_AFP_NODE_ID);
  g_object_unref (info);

  comm = g_vfs_afp_command_new (AFP_COMMAND_CREATE_DIR);
  /* pad byte */
  g_vfs_afp_command_put_byte (comm, 0);
  /* Volume ID */
  g_vfs_afp_command_put_uint16 (comm, g_vfs_afp_volume_get_id (volume));
  /* Directory ID */
  g_vfs_afp_command_put_uint32 (comm, dir_id);

  /* Pathname */
  g_vfs_afp_command_put_pathname (comm, cdd->basename);
  
  g_vfs_afp_connection_send_command (volume->priv->conn, comm, NULL, 0, make_directory_cb,
                                     g_task_get_cancellable (task), task);
  g_object_unref (comm);
}

/*
 * g_vfs_afp_volume_create_directory:
 *
 * @volume: a #GVfsAfpVolume.
 * @directory: path to the new directory to create.
 * @hard_create: if %TRUE this call will overwrite an already existing file.
 * If %FALSE it will error out instead.
 * @cancellable: optional #GCancellable object, %NULL to ignore.
 * @callback: callback to call when the request is satisfied.
 * @user_data: the data to pass to callback function.
 * 
 * Asynchronously creates a directory at @directory.
 */
void
g_vfs_afp_volume_create_directory (GVfsAfpVolume      *volume,
                                   const char         *directory,
                                   GCancellable       *cancellable,
                                   GAsyncReadyCallback callback,
                                   gpointer            user_data)
{
  GTask *task;
  CreateDirData *cdd;
  char *dirname;

  g_return_if_fail (G_VFS_IS_AFP_VOLUME (volume));

  task = g_task_new (volume, cancellable, callback, user_data);
  g_task_set_source_tag (task, g_vfs_afp_volume_create_directory);

  cdd = g_slice_new (CreateDirData);
  cdd->basename = g_path_get_basename (directory);

  g_task_set_task_data (task, cdd, (GDestroyNotify)create_dir_data_free);

  dirname = g_path_get_dirname (directory);
  g_vfs_afp_volume_get_filedir_parms (volume, dirname, 0,
                                      AFP_DIR_BITMAP_NODE_ID_BIT,
                                      cancellable,
                                      create_directory_get_filedir_parms_cb,
                                      task);
  g_free (dirname);
}

/*
 * g_vfs_afp_volume_create_directory_finish:
 * 
 * @volume: a #GVfsAfpVolume.
 * @result: a #GAsyncResult.
 * @error: a #GError, %NULL to ignore.
 * 
 * Finalizes the asynchronous operation started by
 * g_vfs_afp_volume_create_directory.
 * 
 * Returns: %TRUE on success, %FALSE on error.
 */
gboolean
g_vfs_afp_volume_create_directory_finish (GVfsAfpVolume  *volume,
                                          GAsyncResult   *result,
                                          GError         **error)
{
  g_return_val_if_fail (g_task_is_valid (result, volume), FALSE);
  g_return_val_if_fail (g_async_result_is_tagged (result, g_vfs_afp_volume_create_directory), FALSE);

  return g_task_propagate_boolean (G_TASK (result), error);
}

typedef struct
{
  char *filename;
  char *new_name;
} RenameData;

static void
rename_data_free (RenameData *rd)
{
  g_free (rd->filename);
  g_free (rd->new_name);
  g_slice_free (RenameData, rd);
}

static void
rename_cb (GObject *source_object, GAsyncResult *res, gpointer user_data)
{
  GVfsAfpConnection *conn = G_VFS_AFP_CONNECTION (source_object);
  GTask *task = G_TASK (user_data);

  GVfsAfpReply *reply;
  GError *err = NULL;
  AfpResultCode res_code;

  reply = g_vfs_afp_connection_send_command_finish (conn, res, &err);
  if (!reply)
  {
    g_task_return_error (task, err);
    g_object_unref (task);
    return;
  }

  res_code = g_vfs_afp_reply_get_result_code (reply);
  g_object_unref (reply);

  if (res_code != AFP_RESULT_NO_ERROR)
  {
    switch (res_code)
    {
      case AFP_RESULT_ACCESS_DENIED:
        g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_PERMISSION_DENIED,
                                 _("Permission denied"));
        break;
      case AFP_RESULT_CANT_RENAME:
        g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_INVALID_FILENAME,
                                 _("Can’t rename volume"));
        break;
      case AFP_RESULT_OBJECT_EXISTS:
        g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_EXISTS,
                                 _("Object with that name already exists"));
        break;
      case AFP_RESULT_OBJECT_LOCKED:
        g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_FAILED,
                                 _("Target object is marked as not renameable (RenameInhibit)"));
        break;
      case AFP_RESULT_OBJECT_NOT_FOUND:
        g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_NOT_FOUND,
                                 _("Target object doesn’t exist"));
        break;
      case AFP_RESULT_VOL_LOCKED:
        g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_PERMISSION_DENIED,
                                 _("Volume is read-only"));
        break;
      default:
        g_task_return_error (task, afp_result_code_to_gerror (res_code));
        break;
    }

    g_object_unref (task);
    return;
  }

  g_task_return_boolean (task, TRUE);
  g_object_unref (task);
}

static void
rename_get_filedir_parms_cb (GObject      *source_object,
                             GAsyncResult *res,
                             gpointer      user_data)
{
  GVfsAfpVolume *volume = G_VFS_AFP_VOLUME (source_object);
  GTask *task = G_TASK (user_data);
  RenameData *rd = g_task_get_task_data (task);

  GFileInfo *info;
  GError *err = NULL;

  guint32 dir_id;
  GVfsAfpCommand *comm;
  char *basename;

  info = g_vfs_afp_volume_get_filedir_parms_finish (volume, res, &err);
  if (!info)
  {
    g_task_return_error (task, err);
    g_object_unref (task);
    return;
  }

  dir_id = g_file_info_get_attribute_uint32 (info, G_FILE_ATTRIBUTE_AFP_PARENT_DIR_ID);
  g_object_unref (info);

  comm = g_vfs_afp_command_new (AFP_COMMAND_RENAME);
  /* pad byte */
  g_vfs_afp_command_put_byte (comm, 0);
  /* Volume ID */
  g_vfs_afp_command_put_uint16 (comm, g_vfs_afp_volume_get_id (volume));
  /* Directory ID */
  g_vfs_afp_command_put_uint32 (comm, dir_id);

  /* Pathname */
  basename = g_path_get_basename (rd->filename);
  g_vfs_afp_command_put_pathname (comm, basename);
  g_free (basename);

  /* NewName */
  g_vfs_afp_command_put_pathname (comm, rd->new_name);

  g_vfs_afp_connection_send_command (volume->priv->conn, comm, NULL, 0, rename_cb,
                                     g_task_get_cancellable (task), task);
  g_object_unref (comm);
}

/*
 * g_vfs_afp_volume_rename:
 * 
 * @volume: a #GVfsAfpVolume.
 * @filename: path to file to rename.
 * @new_name: the new name of the file.
 * @cancellable: optional #GCancellable object, %NULL to ignore.
 * @callback: callback to call when the request is satisfied.
 * @user_data: the data to pass to callback function.
 * 
 * Asynchronously renames the file at @filename to @new_name.
 */
void
g_vfs_afp_volume_rename (GVfsAfpVolume      *volume,
                         const char         *filename,
                         const char         *new_name,
                         GCancellable       *cancellable,
                         GAsyncReadyCallback callback,
                         gpointer            user_data)
{
  GTask *task;
  RenameData *rd;

  g_return_if_fail (G_VFS_IS_AFP_VOLUME (volume));

  task = g_task_new (volume, cancellable, callback, user_data);
  g_task_set_source_tag (task, g_vfs_afp_volume_rename);

  rd = g_slice_new (RenameData);
  rd->filename = g_strdup (filename);
  rd->new_name = g_strdup (new_name);

  g_task_set_task_data (task, rd, (GDestroyNotify)rename_data_free);

  g_vfs_afp_volume_get_filedir_parms (volume, filename,
                                      AFP_FILEDIR_BITMAP_PARENT_DIR_ID_BIT,
                                      AFP_FILEDIR_BITMAP_PARENT_DIR_ID_BIT,
                                      cancellable, rename_get_filedir_parms_cb,
                                      task);
}

/*
 * g_vfs_afp_volume_rename_finish:
 * 
 * @volume: a #GVfsAfpVolume.
 * @result: a #GAsyncResult.
 * @error: a #GError, %NULL to ignore.
 * 
 * Finalizes the asynchronous operation started by
 * g_vfs_afp_volume_move_and_rename.
 * 
 * Returns: %TRUE on success, %FALSE on error.
 */
gboolean
g_vfs_afp_volume_rename_finish (GVfsAfpVolume  *volume,
                                GAsyncResult   *res,
                                GError        **error)
{
  g_return_val_if_fail (g_task_is_valid (res, volume), FALSE);
  g_return_val_if_fail (g_async_result_is_tagged (res, g_vfs_afp_volume_rename), FALSE);

  return g_task_propagate_boolean (G_TASK (res), error);
}

static void
move_and_rename_cb (GObject *source_object, GAsyncResult *res, gpointer user_data)
{
  GVfsAfpConnection *conn = G_VFS_AFP_CONNECTION (source_object);
  GTask *task = G_TASK (user_data);

  GVfsAfpReply *reply;
  GError *err = NULL;

  AfpResultCode res_code;

  reply = g_vfs_afp_connection_send_command_finish (conn, res, &err);
  if (!reply)
  {
    g_task_return_error (task, err);
    g_object_unref (task);
    return;
  }

  res_code = g_vfs_afp_reply_get_result_code (reply);
  g_object_unref (reply);
  
  if (res_code != AFP_RESULT_NO_ERROR)
  {
    switch (res_code)
    {
      case AFP_RESULT_ACCESS_DENIED:
        g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_PERMISSION_DENIED,
                                 _("Permission denied"));
        break;
      case AFP_RESULT_CANT_MOVE:
        g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_WOULD_RECURSE,
                                 _("Can’t move directory into one of its descendants"));
        break;
      case AFP_RESULT_INSIDE_SHARE_ERR:
        g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_FAILED,
                                 _("Can’t move sharepoint into a shared directory"));
        break;
      case AFP_RESULT_INSIDE_TRASH_ERR:
        g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_FAILED,
                                 _("Can’t move a shared directory into the Trash"));
        break;
      case AFP_RESULT_OBJECT_EXISTS:
        g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_EXISTS,
                                 _("Target file already exists"));
        break;
      case AFP_RESULT_OBJECT_LOCKED:
        g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_NOT_FOUND,
                                 _("Object being moved is marked as not renameable (RenameInhibit)"));
        break;
      case AFP_RESULT_OBJECT_NOT_FOUND:
        g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_NOT_FOUND,
                                 _("Object being moved doesn’t exist"));
        break;
      default:
        g_task_return_error (task, afp_result_code_to_gerror (res_code));
        break;
    }

    g_object_unref (task);
    return;
  }

  g_task_return_boolean (task, TRUE);
  g_object_unref (task);
}

/*
 * g_vfs_afp_volume_move_and_rename:
 * 
 * @volume: a #GVfsAfpVolume.
 * @source: the source path of the file to move.
 * @destination: destination path.
 * @cancellable: optional #GCancellable object, %NULL to ignore.
 * @callback: callback to call when the request is satisfied.
 * @user_data: the data to pass to callback function.
 * 
 * Asynchronously moves (and renames) the file at @source to @destination.
 */
void
g_vfs_afp_volume_move_and_rename (GVfsAfpVolume      *volume,
                                  const char         *source,
                                  const char         *destination,
                                  GCancellable       *cancellable,
                                  GAsyncReadyCallback callback,
                                  gpointer            user_data)
{
  GVfsAfpVolumePrivate *priv;
  GVfsAfpCommand *comm;
  char *dirname, *basename;
  GTask *task;

  g_return_if_fail (G_VFS_IS_AFP_VOLUME (volume));

  priv = volume->priv;
  
  comm = g_vfs_afp_command_new (AFP_COMMAND_MOVE_AND_RENAME);
  /* pad byte */
  g_vfs_afp_command_put_byte (comm, 0);

  /* VolumeID */
  g_vfs_afp_command_put_uint16 (comm, g_vfs_afp_volume_get_id (volume));

  /* SourceDirectoryID 2 == / */
  g_vfs_afp_command_put_uint32 (comm, 2);
  /* DestDirectoryID 2 == / */
  g_vfs_afp_command_put_uint32 (comm, 2);

  /* SourcePathname */
  g_vfs_afp_command_put_pathname (comm, source);

  /* DestPathname */
  dirname = g_path_get_dirname (destination);
  g_vfs_afp_command_put_pathname (comm, dirname);
  g_free (dirname);

  /* NewName */
  basename = g_path_get_basename (destination);
  g_vfs_afp_command_put_pathname (comm, basename);
  g_free (basename);

  task = g_task_new (volume, cancellable, callback, user_data);
  g_task_set_source_tag (task, g_vfs_afp_volume_move_and_rename);

  g_vfs_afp_connection_send_command (priv->conn, comm, NULL, 0,
                                     move_and_rename_cb, cancellable, task);
  g_object_unref (comm);
}

/*
 * g_vfs_afp_volume_move_and_rename_finish:
 * 
 * @volume: a #GVfsAfpVolume.
 * @result: a #GAsyncResult.
 * @error: a #GError, %NULL to ignore.
 * 
 * Finalizes the asynchronous operation started by
 * g_vfs_afp_volume_move_and_rename.
 * 
 * Returns: %TRUE on success, %FALSE on error.
 */
gboolean
g_vfs_afp_volume_move_and_rename_finish (GVfsAfpVolume  *volume,
                                         GAsyncResult   *res,
                                         GError        **error)
{
  g_return_val_if_fail (g_task_is_valid (res, volume), FALSE);
  g_return_val_if_fail (g_async_result_is_tagged (res, g_vfs_afp_volume_move_and_rename), FALSE);

  return g_task_propagate_boolean (G_TASK (res), error);
}

static void
copy_file_cb (GObject *source_object, GAsyncResult *res, gpointer user_data)
{
  GVfsAfpConnection *conn = G_VFS_AFP_CONNECTION (source_object);
  GTask *task = G_TASK (user_data);

  GVfsAfpReply *reply;
  GError *err = NULL;

  AfpResultCode res_code;

  reply = g_vfs_afp_connection_send_command_finish (conn, res, &err);
  if (!reply)
  {
    g_task_return_error (task, err);
    g_object_unref (task);
    return;
  }

  res_code = g_vfs_afp_reply_get_result_code (reply);
  g_object_unref (reply);

  if (res_code != AFP_RESULT_NO_ERROR)
  {
    switch (res_code)
    {
      case AFP_RESULT_ACCESS_DENIED:
        g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_PERMISSION_DENIED,
                                 _("Permission denied"));
        break;
      case AFP_RESULT_CALL_NOT_SUPPORTED:
        g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
                                 _("Server doesn’t support the FPCopyFile operation"));
        break;
      case AFP_RESULT_DENY_CONFLICT:
        g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_FAILED,
                                 _("Unable to open source file for reading"));
        break;
      case AFP_RESULT_DISK_FULL:
        g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_NO_SPACE,
                                 _("Not enough space on volume"));
        break;
      case AFP_RESULT_OBJECT_EXISTS:
        g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_EXISTS,
                                 _("Target file already exists"));
        break;
      case AFP_RESULT_OBJECT_NOT_FOUND:
        g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_NOT_FOUND,
                                 _("Source file and/or destination directory doesn’t exist"));
        break;
      case AFP_RESULT_OBJECT_TYPE_ERR:
        g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_IS_DIRECTORY,
                                 _("Source file is a directory"));
        break;
      default:
        g_task_return_error (task, afp_result_code_to_gerror (res_code));
        break;
    }

    g_object_unref (task);
    return;
  }

  g_task_return_boolean (task, TRUE);
  g_object_unref (task);
}

/*
 * g_vfs_afp_volume_copy_file:
 * 
 * @volume: a #GVfsAfpVolume.
 * @source: the source path of the file to copy.
 * @destination: destination path.
 * @cancellable: optional #GCancellable object, %NULL to ignore.
 * @callback: callback to call when the request is satisfied.
 * @user_data: the data to pass to callback function.
 * 
 * Asynchronously copies the file at @source to @destination.
 */
void
g_vfs_afp_volume_copy_file (GVfsAfpVolume      *volume,
                            const char         *source,
                            const char         *destination,
                            GCancellable       *cancellable,
                            GAsyncReadyCallback callback,
                            gpointer            user_data)
{
  GVfsAfpVolumePrivate *priv;
  
  GVfsAfpCommand *comm;
  char *dirname, *basename;
  GTask *task;

  g_return_if_fail (G_VFS_IS_AFP_VOLUME (volume));

  priv = volume->priv;
  
  comm = g_vfs_afp_command_new (AFP_COMMAND_COPY_FILE);
  /* pad byte */
  g_vfs_afp_command_put_byte (comm, 0);

  /* SourceVolumeID */
  g_vfs_afp_command_put_uint16 (comm, g_vfs_afp_volume_get_id (volume));
  /* SourceDirectoryID 2 == / */
  g_vfs_afp_command_put_uint32 (comm, 2);

  /* DestVolumeID */
  g_vfs_afp_command_put_uint16 (comm, g_vfs_afp_volume_get_id (volume));
  /* DestDirectoryID 2 == / */
  g_vfs_afp_command_put_uint32 (comm, 2);

  /* SourcePathname */
  g_vfs_afp_command_put_pathname (comm, source);

  /* DestPathname */
  dirname = g_path_get_dirname (destination);
  g_vfs_afp_command_put_pathname (comm, dirname);
  g_free (dirname);

  /* NewName */
  basename = g_path_get_basename (destination);
  g_vfs_afp_command_put_pathname (comm, basename);
  g_free (basename);

  task = g_task_new (volume, cancellable, callback, user_data);
  g_task_set_source_tag (task, g_vfs_afp_volume_copy_file);

  g_vfs_afp_connection_send_command (priv->conn, comm, NULL, 0,
                                     copy_file_cb, cancellable, task);
  g_object_unref (comm);
}

/*
 * g_vfs_afp_volume_copy_file_finish:
 * 
 * @volume: a #GVfsAfpVolume.
 * @result: a #GAsyncResult.
 * @error: a #GError, %NULL to ignore.
 * 
 * Finalizes the asynchronous operation started by
 * g_vfs_afp_volume_copy_file.
 * 
 * Returns: %TRUE on success, %FALSE on error.
 */
gboolean
g_vfs_afp_volume_copy_file_finish (GVfsAfpVolume *volume,
                                   GAsyncResult  *res,
                                   GError       **error)
{
  g_return_val_if_fail (g_task_is_valid (res, volume), FALSE);
  g_return_val_if_fail (g_async_result_is_tagged (res, g_vfs_afp_volume_copy_file), FALSE);

  return g_task_propagate_boolean (G_TASK (res), error);
}

static void
get_filedir_parms_cb (GObject *source_object, GAsyncResult *result, gpointer user_data)
{
  GVfsAfpConnection *conn = G_VFS_AFP_CONNECTION (source_object);
  GTask *task = G_TASK (user_data);
  GVfsAfpVolume *volume = G_VFS_AFP_VOLUME (g_task_get_source_object (task));

  GVfsAfpReply *reply;
  GError *err = NULL;
  AfpResultCode res_code;
  gboolean res;

  guint16 file_bitmap, dir_bitmap, bitmap;
  guint8 FileDir;
  gboolean directory;
  GFileInfo *info;

  reply = g_vfs_afp_connection_send_command_finish (conn, result, &err);
  if (!reply)
  {
    g_task_return_error (task, err);
    g_object_unref (task);
    return;
  }

  res_code = g_vfs_afp_reply_get_result_code (reply);
  if (res_code != AFP_RESULT_NO_ERROR)
  {
    g_object_unref (reply);
    
    switch (res_code)
    {
      case AFP_RESULT_OBJECT_NOT_FOUND:
        g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_NOT_FOUND,
                                 _("File doesn’t exist"));
        break;
      default:
        g_task_return_error (task, afp_result_code_to_gerror (res_code));
        break;
    }

    g_object_unref (task);
    return;
  }

  g_vfs_afp_reply_read_uint16 (reply, &file_bitmap);
  g_vfs_afp_reply_read_uint16 (reply, &dir_bitmap);

  g_vfs_afp_reply_read_byte (reply, &FileDir);
  /* Pad Byte */
  g_vfs_afp_reply_read_byte (reply, NULL);
  
  directory = (FileDir & 0x80); 
  bitmap =  directory ? dir_bitmap : file_bitmap;

  info = g_file_info_new ();
  res = g_vfs_afp_server_fill_info (volume->priv->server, info, reply, directory, bitmap, &err);
  g_object_unref (reply);
  if (!res)
  {
    g_task_return_error (task, err);
    g_object_unref (task);
    return;
  }

  g_task_return_pointer (task, info, g_object_unref);
  g_object_unref (task);
}

/*
 * g_vfs_afp_volume_get_filedir_parms:
 * 
 * @volume: a #GVfsAfpVolume.
 * @filename: file or directory whose parameters should be retreived.
 * @file_bitmap: bitmap describing the parameters to retrieve if @filename is a
 * file.
 * @dir_bitmap: bitmap describing the parameters to retrieve if @filename is a
 * directory.
 * @cancellable: optional #GCancellable object, %NULL to ignore.
 * @callback: callback to call when the request is satisfied.
 * @user_data: the data to pass to callback function.
 * 
 * Asynchronously retrieves the parameters described by @file_bitmap or
 * @dir_bitmap of the file/directory at @filename.
 */
void
g_vfs_afp_volume_get_filedir_parms (GVfsAfpVolume       *volume,
                                    const char          *filename,
                                    guint16              file_bitmap,
                                    guint16              dir_bitmap,
                                    GCancellable        *cancellable,
                                    GAsyncReadyCallback  callback,
                                    gpointer             user_data)
{
  GVfsAfpVolumePrivate *priv;
  GVfsAfpCommand *comm;
  GTask *task;

  g_return_if_fail (G_VFS_IS_AFP_VOLUME (volume));

  priv = volume->priv;
  
  comm = g_vfs_afp_command_new (AFP_COMMAND_GET_FILE_DIR_PARMS);
  /* pad byte */
  g_vfs_afp_command_put_byte (comm, 0);
  /* VolumeID */
  g_vfs_afp_command_put_uint16 (comm, g_vfs_afp_volume_get_id (volume));
  /* Directory ID 2 == / */
  g_vfs_afp_command_put_uint32 (comm, 2);
  /* FileBitmap */  
  g_vfs_afp_command_put_uint16 (comm, file_bitmap);
  /* DirectoryBitmap */  
  g_vfs_afp_command_put_uint16 (comm, dir_bitmap);
  /* PathName */
  g_vfs_afp_command_put_pathname (comm, filename);

  task = g_task_new (volume, cancellable, callback, user_data);
  g_task_set_source_tag (task, g_vfs_afp_volume_get_filedir_parms);

  g_vfs_afp_connection_send_command (priv->conn, comm, NULL, 0,
                                     get_filedir_parms_cb, cancellable,
                                     task);
  g_object_unref (comm);
}

/*
 * g_vfs_afp_volume_get_filedir_parms_finish:
 * 
 * @volume: a #GVfsAfpVolume.
 * @result: a #GAsyncResult.
 * @error: a #GError, %NULL to ignore.
 * 
 * Finalizes the asynchronous operation started by
 * g_vfs_afp_volume_get_fork_parms.
 * 
 * Returns: (transfer full): A #GFileInfo with the requested parameters or %NULL
 * on error.
 */
GFileInfo *
g_vfs_afp_volume_get_filedir_parms_finish (GVfsAfpVolume  *volume,
                                           GAsyncResult   *result,
                                           GError         **error)
{
  g_return_val_if_fail (g_task_is_valid (result, volume), NULL);
  g_return_val_if_fail (g_async_result_is_tagged (result, g_vfs_afp_volume_get_filedir_parms), NULL);

  return g_task_propagate_pointer (G_TASK (result), error);
}

static void
get_fork_parms_cb (GObject *source_object, GAsyncResult *result, gpointer user_data)
{
  GVfsAfpConnection *conn = G_VFS_AFP_CONNECTION (source_object);
  GTask *task = G_TASK (user_data);
  GVfsAfpVolume *volume = G_VFS_AFP_VOLUME (g_task_get_source_object (task));
  GVfsAfpVolumePrivate *priv = volume->priv;

  GVfsAfpReply *reply;
  GError *err = NULL;
  AfpResultCode res_code;
  gboolean res;

  guint16 file_bitmap;
  GFileInfo *info;

  reply = g_vfs_afp_connection_send_command_finish (conn, result, &err);
  if (!reply)
  {
    g_task_return_error (task, err);
    g_object_unref (task);
    return;
  }

  res_code = g_vfs_afp_reply_get_result_code (reply);
  if (res_code != AFP_RESULT_NO_ERROR)
  {
    g_object_unref (reply);

    g_task_return_error (task, afp_result_code_to_gerror (res_code));
    g_object_unref (task);
    return;
  }

  g_vfs_afp_reply_read_uint16 (reply, &file_bitmap);

  info = g_file_info_new ();
  res = g_vfs_afp_server_fill_info (priv->server, info, reply, FALSE, file_bitmap, &err);
  g_object_unref (reply);
  if (!res)
  {
    g_task_return_error (task, err);
    g_object_unref (task);
    return;
  }

  g_task_return_pointer (task, info, g_object_unref);
  g_object_unref (task);
}

/*
 * g_vfs_afp_volume_get_fork_parms:
 * 
 * @volume: a #GVfsAfpVolume.
 * @fork_refnume: the reference id of the fork.
 * @file_bitmap: bitmap describing the parameters to retrieve.
 * @cancellable: optional #GCancellable object, %NULL to ignore.
 * @callback: callback to call when the request is satisfied.
 * @user_data: the data to pass to callback function.
 * 
 * Asynchronously retrieves the parameters described by @file_bitmap of the fork
 * with reference id @fork_refnum.
 */
void
g_vfs_afp_volume_get_fork_parms (GVfsAfpVolume       *volume,
                                 gint16               fork_refnum,
                                 guint16              file_bitmap,
                                 GCancellable        *cancellable,
                                 GAsyncReadyCallback  callback,
                                 gpointer             user_data)
{
  GVfsAfpVolumePrivate *priv;
  GVfsAfpCommand *comm;
  GTask *task;

  g_return_if_fail (G_VFS_IS_AFP_VOLUME (volume));

  priv = volume->priv;
  
  comm = g_vfs_afp_command_new (AFP_COMMAND_GET_FORK_PARMS);
  /* pad byte */
  g_vfs_afp_command_put_byte (comm, 0);
  /* OForkRefNum */
  g_vfs_afp_command_put_int16 (comm, fork_refnum);
  /* Bitmap */  
  g_vfs_afp_command_put_uint16 (comm, file_bitmap);

  task = g_task_new (volume, cancellable, callback, user_data);
  g_task_set_source_tag (task, g_vfs_afp_volume_get_fork_parms);

  g_vfs_afp_connection_send_command (priv->conn, comm, NULL, 0,
                                     get_fork_parms_cb, cancellable,
                                     task);
  g_object_unref (comm);
}

/*
 * g_vfs_afp_volume_get_fork_parms_finish:
 * 
 * @volume: a #GVfsAfpVolume.
 * @result: a #GAsyncResult.
 * @error: a #GError, %NULL to ignore.
 * 
 * Finalizes the asynchronous operation started by
 * g_vfs_afp_volume_get_fork_parms.
 * 
 * Returns: (transfer full): A #GFileInfo with the requested parameters or %NULL
 * on error.
 */
GFileInfo *
g_vfs_afp_volume_get_fork_parms_finish (GVfsAfpVolume  *volume,
                                        GAsyncResult   *result,
                                        GError         **error)
{
  g_return_val_if_fail (g_task_is_valid (result, volume), NULL);
  g_return_val_if_fail (g_async_result_is_tagged (result, g_vfs_afp_volume_get_fork_parms), NULL);

  return g_task_propagate_pointer (G_TASK (result), error);
}

static void
set_fork_parms_cb (GObject *source_object, GAsyncResult *res, gpointer user_data)
{
  GVfsAfpConnection *conn = G_VFS_AFP_CONNECTION (source_object);
  GTask *task = G_TASK (user_data);

  GVfsAfpReply *reply;
  GError *err = NULL;
  AfpResultCode res_code;

  reply = g_vfs_afp_connection_send_command_finish (conn, res, &err);
  if (!reply)
  {
    g_task_return_error (task, err);
    g_object_unref (task);
    return;
  }

  res_code = g_vfs_afp_reply_get_result_code (reply);
  g_object_unref (reply);
  if (res_code != AFP_RESULT_NO_ERROR)
  {
    switch (res_code)
    {
      case AFP_RESULT_ACCESS_DENIED:
        g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_FAILED,
                                 _("Permission denied"));
        break;
      case AFP_RESULT_DISK_FULL:
        g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_NO_SPACE,
                                 _("Not enough space on volume"));
        break;
      case AFP_RESULT_LOCK_ERR:
        g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_FAILED,
                                 /* Translators: range conflict means
                                    requested data are locked by another user */
                                 _("Range lock conflict exists"));
        break;
      default:
        g_task_return_error (task, afp_result_code_to_gerror (res_code));
        break;
    }

    g_object_unref (task);
    return;
  }

  g_task_return_boolean (task, TRUE);
  g_object_unref (task);
}

/*
 * g_vfs_afp_volume_set_fork_size:
 * 
 * @volume: a #GVfsAfpVolume.
 * @fork_refnume: the reference id of the fork.
 * @size: the new size of the fork,
 * @cancellable: optional #GCancellable object, %NULL to ignore.
 * @callback: callback to call when the request is satisfied.
 * @user_data: the data to pass to callback function.
 * 
 * Asynchronously sets the size of the fork referenced by @fork_refnum.
 */
void
g_vfs_afp_volume_set_fork_size (GVfsAfpVolume       *volume,
                                gint16               fork_refnum,
                                gint64               size,
                                GCancellable        *cancellable,
                                GAsyncReadyCallback  callback,
                                gpointer             user_data)
{
  GVfsAfpVolumePrivate *priv;
  GVfsAfpCommand *comm;
  GTask *task;

  g_return_if_fail (G_VFS_IS_AFP_VOLUME (volume));

  priv = volume->priv;
  
  comm = g_vfs_afp_command_new (AFP_COMMAND_SET_FORK_PARMS);
  /* pad byte */
  g_vfs_afp_command_put_byte (comm, 0);

  /* OForkRefNum */
  g_vfs_afp_command_put_int16 (comm, fork_refnum);
  /* Bitmap */
  g_vfs_afp_command_put_uint16 (comm, AFP_FILE_BITMAP_EXT_DATA_FORK_LEN_BIT);
  /* ForkLen */
  g_vfs_afp_command_put_int64 (comm, size);

  task = g_task_new (volume, cancellable, callback, user_data);
  g_task_set_source_tag (task, g_vfs_afp_volume_set_fork_size);

  g_vfs_afp_connection_send_command (priv->conn, comm, NULL, 0,
                                     set_fork_parms_cb, cancellable, task);
  g_object_unref (comm);
}

/*
 * g_vfs_afp_volume_set_fork_size_finish:
 * 
 * @volume: a #GVfsAfpVolume.
 * @result: a #GAsyncResult.
 * @error: a #GError, %NULL to ignore.
 * 
 * Finalizes the asynchronous operation started by
 * g_vfs_afp_volume_set_fork_size.
 * 
 * Returns: (transfer full): %TRUE on success, %FALSE otherwise.
 */
gboolean
g_vfs_afp_volume_set_fork_size_finish (GVfsAfpVolume  *volume,
                                       GAsyncResult   *result,
                                       GError         **error)
{
  g_return_val_if_fail (g_task_is_valid (result, volume), FALSE);
  g_return_val_if_fail (g_async_result_is_tagged (result, g_vfs_afp_volume_set_fork_size), FALSE);

  return g_task_propagate_boolean (G_TASK (result), error);
}

static void
set_unix_privs_cb (GObject *source_object, GAsyncResult *res, gpointer user_data)
{
  GVfsAfpConnection *conn = G_VFS_AFP_CONNECTION (source_object);
  GTask *task = G_TASK (user_data);

  GVfsAfpReply *reply;
  GError *err = NULL;
  AfpResultCode res_code;

  reply = g_vfs_afp_connection_send_command_finish (conn, res, &err);
  if (!reply)
  {
    g_task_return_error (task, err);
    g_object_unref (task);
    return;
  }

  res_code = g_vfs_afp_reply_get_result_code (reply);
  g_object_unref (reply);
  if (res_code != AFP_RESULT_NO_ERROR)
  {
    switch (res_code)
    {
      case AFP_RESULT_ACCESS_DENIED:
        g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_PERMISSION_DENIED,
                                 _("Permission denied"));
        break;
      case AFP_RESULT_OBJECT_NOT_FOUND:
        g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_NOT_FOUND,
                                 _("Target object doesn’t exist"));
        break;
      case AFP_RESULT_VOL_LOCKED:
        g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_PERMISSION_DENIED,
                                 _("Volume is read-only"));
        break;
      default:
        g_task_return_error (task, afp_result_code_to_gerror (res_code));
        break;
    }

    g_object_unref (task);
    return;
  }

  g_task_return_boolean (task, TRUE);
  g_object_unref (task);
}

/*
 * g_vfs_afp_volume_set_unix_privs:
 * 
 * @volume: a #GVfsAfpVolume.
 * @filename: file or directory whose unix privileges should be set.
 * @uid: the new user id of the file.
 * @gid: the new group id of the file.
 * @permissions: the new unix permissions of the file.
 * @ua_permissions: the new AFP access right of the file.
 * @cancellable: optional #GCancellable object, %NULL to ignore.
 * @callback: callback to call when the request is satisfied.
 * @user_data: the data to pass to callback function.
 * 
 * Asynchronously sets new unix permissions on the file/directory pointed to by
 * @filename.
 */
void
g_vfs_afp_volume_set_unix_privs (GVfsAfpVolume       *volume,
                                 const char          *filename,
                                 guint32              uid,
                                 guint32              gid,
                                 guint32              permissions,
                                 guint32              ua_permissions,
                                 GCancellable        *cancellable,
                                 GAsyncReadyCallback  callback,
                                 gpointer             user_data)
{
  GVfsAfpVolumePrivate *priv;
  GVfsAfpCommand *comm;
  GTask *task;

  g_return_if_fail (G_VFS_IS_AFP_VOLUME (volume));

  priv = volume->priv;
  
  comm = g_vfs_afp_command_new (AFP_COMMAND_SET_FILEDIR_PARMS);
  /* pad byte */
  g_vfs_afp_command_put_byte (comm, 0);

  /* VolumeID */
  g_vfs_afp_command_put_uint16 (comm, g_vfs_afp_volume_get_id (volume));
  /* DirectoryID 2 == / */
  g_vfs_afp_command_put_uint32 (comm, 2);
  /* Bitmap */
  g_vfs_afp_command_put_uint16 (comm, AFP_FILEDIR_BITMAP_UNIX_PRIVS_BIT);
  /* Pathname */
  g_vfs_afp_command_put_pathname (comm, filename);
  /* pad to even */
  g_vfs_afp_command_pad_to_even (comm);

  /* UID */
  g_vfs_afp_command_put_uint32 (comm, uid);
  /* GID */
  g_vfs_afp_command_put_uint32 (comm, gid);
  /* Permissions */
  g_vfs_afp_command_put_uint32 (comm, permissions);
  /* UAPermissions */
  g_vfs_afp_command_put_uint32 (comm, ua_permissions);

  task = g_task_new (volume, cancellable, callback, user_data);
  g_task_set_source_tag (task, g_vfs_afp_volume_set_unix_privs);

  g_vfs_afp_connection_send_command (priv->conn, comm, NULL, 0,
                                     set_unix_privs_cb, cancellable, task);
  g_object_unref (comm);
}

/*
 * g_vfs_afp_volume_set_unix_privs_finish:
 * 
 * @volume: a #GVfsAfpVolume.
 * @result: a #GAsyncResult.
 * @error: a #GError, %NULL to ignore.
 * 
 * Finalizes the asynchronous operation started by
 * g_vfs_afp_volume_set_unix_privs.
 * 
 * Returns: %TRUE on success, %FALSE on error.
 */
gboolean
g_vfs_afp_volume_set_unix_privs_finish (GVfsAfpVolume  *volume,
                                        GAsyncResult   *res,
                                        GError        **error)
{
  g_return_val_if_fail (g_task_is_valid (res, volume), FALSE);
  g_return_val_if_fail (g_async_result_is_tagged (res, g_vfs_afp_volume_set_unix_privs), FALSE);

  return g_task_propagate_boolean (G_TASK (res), error);
}

static const gint16 ENUMERATE_REQ_COUNT           = G_MAXINT16;
static const gint16 ENUMERATE_EXT_MAX_REPLY_SIZE  = G_MAXINT16;
static const gint32 ENUMERATE_EXT2_MAX_REPLY_SIZE = G_MAXINT32;

static void
enumerate_cb (GObject *source_object, GAsyncResult *res, gpointer user_data)
{
  GVfsAfpConnection *conn = G_VFS_AFP_CONNECTION (source_object);
  GTask *task = G_TASK (user_data);
  GVfsAfpVolume *volume = G_VFS_AFP_VOLUME (g_task_get_source_object (task));
  GVfsAfpVolumePrivate *priv = volume->priv;

  GVfsAfpReply *reply;
  GError *err = NULL;
  AfpResultCode res_code;
  
  guint16 file_bitmap;
  guint16  dir_bitmap;
  gint16 count, i;
  GPtrArray *infos;

  reply = g_vfs_afp_connection_send_command_finish (conn, res, &err);
  if (!reply)
  {
    g_task_return_error (task, err);
    g_object_unref (task);
    return;
  }

  res_code = g_vfs_afp_reply_get_result_code (reply);
  if (res_code != AFP_RESULT_NO_ERROR)
  {
    g_object_unref (reply);
    
    switch (res_code)
    {
      case AFP_RESULT_OBJECT_NOT_FOUND:
        g_task_return_pointer (task, NULL, NULL);
        break;
        
      case AFP_RESULT_ACCESS_DENIED:
        g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_PERMISSION_DENIED,
                                 _("Permission denied"));
        break;
      case AFP_RESULT_DIR_NOT_FOUND:
        g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_NOT_FOUND,
                                 _("Directory doesn’t exist"));
        break;
      case AFP_RESULT_OBJECT_TYPE_ERR:
        g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_NOT_DIRECTORY,
                                 _("Target object is not a directory"));
        break;
      default:
        g_task_return_error (task, afp_result_code_to_gerror (res_code));
        break;
    }

    g_object_unref (task);
    return;
  }

  g_vfs_afp_reply_read_uint16 (reply, &file_bitmap);
  g_vfs_afp_reply_read_uint16 (reply, &dir_bitmap);

  g_vfs_afp_reply_read_int16 (reply, &count);
  infos = g_ptr_array_new_full (count, g_object_unref);
  
  for (i = 0; i < count; i++)
  {
    goffset start_pos;
    guint16 struct_length;
    guint8 FileDir;
    
    gboolean directory;
    guint16 bitmap;
    GFileInfo *info;

    start_pos = g_vfs_afp_reply_get_pos (reply);
    
    g_vfs_afp_reply_read_uint16 (reply, &struct_length);
    g_vfs_afp_reply_read_byte (reply, &FileDir);
    /* pad byte */
    g_vfs_afp_reply_read_byte (reply, NULL);

    directory = (FileDir & 0x80); 
    bitmap =  directory ? dir_bitmap : file_bitmap;
    
    info = g_file_info_new ();
    if (!g_vfs_afp_server_fill_info (priv->server, info, reply, directory, bitmap, &err))
    {
      g_object_unref (reply);
      g_task_return_error (task, err);
      g_object_unref (task);
      return;
    }
    
    g_ptr_array_add (infos, info);

    g_vfs_afp_reply_seek (reply, start_pos + struct_length, G_SEEK_SET);
  }
  g_object_unref (reply);

  g_task_return_pointer (task, infos, (GDestroyNotify)g_ptr_array_unref);
  g_object_unref (task);
}

/*
 * g_vfs_afp_volume_enumerate:
 * 
 * @volume: a #GVfsAfpVolume.
 * @cancellable: optional #GCancellable object, %NULL to ignore.
 * @callback: callback to call when the request is satisfied.
 * @user_data: the data to pass to callback function.
 * 
 * Asynchronously enumerates the files in @directory starting at index
 * @start_index.
 */
void
g_vfs_afp_volume_enumerate (GVfsAfpVolume       *volume,
                            const char          *directory,
                            gint64               start_index,
                            guint16              file_bitmap,
                            guint16              dir_bitmap,
                            GCancellable        *cancellable,
                            GAsyncReadyCallback  callback,
                            gpointer             user_data)
{
  GVfsAfpVolumePrivate *priv;

  const GVfsAfpServerInfo *info;
  gint32 max;
  
  GVfsAfpCommand *comm;
  GTask *task;

  g_return_if_fail (G_VFS_IS_AFP_VOLUME (volume));

  priv = volume->priv;

  task = g_task_new (volume, cancellable, callback, user_data);
  g_task_set_source_tag (task, g_vfs_afp_volume_enumerate);

  info = g_vfs_afp_server_get_info (priv->server);
  
  max = (info->version >= AFP_VERSION_3_1) ? G_MAXINT32 : G_MAXINT16;
  /* Can't enumerate any more files */
  if (start_index > max)
  {
    g_task_return_pointer (task, NULL, NULL);
    g_object_unref (task);
    return;
  }
  
  if (info->version >= AFP_VERSION_3_1)
    comm = g_vfs_afp_command_new (AFP_COMMAND_ENUMERATE_EXT2);
  else
    comm = g_vfs_afp_command_new (AFP_COMMAND_ENUMERATE_EXT);
  
  /* pad byte */
  g_vfs_afp_command_put_byte (comm, 0);

  /* Volume ID */
  g_vfs_afp_command_put_uint16 (comm, g_vfs_afp_volume_get_id (volume));
  /* Directory ID 2 == / */
  g_vfs_afp_command_put_uint32 (comm, 2);

  /* File Bitmap */
  g_vfs_afp_command_put_uint16 (comm, file_bitmap);
  
  /* Dir Bitmap */
  g_vfs_afp_command_put_uint16 (comm, dir_bitmap);

  /* Req Count */
  g_vfs_afp_command_put_int16 (comm, ENUMERATE_REQ_COUNT);

  
  /* StartIndex and MaxReplySize */
  if (info->version >= AFP_VERSION_3_1)
  {
    g_vfs_afp_command_put_int32 (comm, start_index);
    g_vfs_afp_command_put_int32 (comm, ENUMERATE_EXT2_MAX_REPLY_SIZE);
  }
  else
  {
    g_vfs_afp_command_put_int16 (comm, start_index);
    g_vfs_afp_command_put_int16 (comm, ENUMERATE_EXT_MAX_REPLY_SIZE);
  }
  
  /* Pathname */
  g_vfs_afp_command_put_pathname (comm, directory);
  
  g_vfs_afp_connection_send_command (priv->conn, comm, NULL, 0,
                                     enumerate_cb, cancellable, task);
  g_object_unref (comm);
}

/*
 * g_vfs_afp_volume_enumerate_finish:
 * 
 * @volume: a #GVfsAfpVolume.
 * @result: a #GAsyncResult.
 * @infos: (out) (element-type G.FileInfo): array of #GFileInfo objects or %NULL
 * when no more files could be found.
 * @error: a #GError, %NULL to ignore.
 * 
 * Finalizes the asynchronous operation started by
 * g_vfs_afp_volume_enumerate.
 * 
 * Returns: %TRUE on success, %FALSE on error.
 */
gboolean
g_vfs_afp_volume_enumerate_finish (GVfsAfpVolume  *volume,
                                   GAsyncResult   *res,
                                   GPtrArray      **infos,
                                   GError        **error)
{
  g_return_val_if_fail (g_task_is_valid (res, volume), FALSE);
  g_return_val_if_fail (g_async_result_is_tagged (res, g_vfs_afp_volume_enumerate), FALSE);

  if (g_async_result_legacy_propagate_error (res, error))
    return FALSE;

  if (infos)
    *infos = g_task_propagate_pointer (G_TASK (res), NULL);

  return TRUE;
}

static void
close_replace_exchange_files_cb (GObject *source_object, GAsyncResult *res, gpointer user_data)
{
  GVfsAfpConnection *conn = G_VFS_AFP_CONNECTION (source_object);
  GTask *task = G_TASK (user_data);

  GVfsAfpReply *reply;
  GError *err = NULL;
  AfpResultCode res_code;

  reply = g_vfs_afp_connection_send_command_finish (conn, res, &err);
  if (!reply)
  {
    g_task_return_error (task, err);
    g_object_unref (task);
    return;
  }
  
  res_code = g_vfs_afp_reply_get_result_code (reply);
  g_object_unref (reply);
  
  if (res_code != AFP_RESULT_NO_ERROR)
  {
    switch (res_code)
    {
      case AFP_RESULT_ACCESS_DENIED:
        g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_FAILED,
                                 _("Permission denied"));
        break;
      case AFP_RESULT_ID_NOT_FOUND:
        g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_NOT_FOUND,
                                 _("File doesn’t exist"));
        break;
      case AFP_RESULT_OBJECT_TYPE_ERR:
        g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_IS_DIRECTORY,
                                 _("File is directory"));
        break;   
      default:
        g_task_return_error (task, afp_result_code_to_gerror (res_code));
        break;
    }

    g_object_unref (task);
    return;
  }

  g_task_return_boolean (task, TRUE);
  g_object_unref (task);
}
  
/*
 * g_vfs_afp_volume_exchange_files:
 * 
 * @volume: a #GVfsAfpVolume.
 * @source: path to source file to exchange.
 * @destination: path to destination file to exchange.
 * @cancellable: optional #GCancellable object, %NULL to ignore.
 * @callback: callback to call when the request is satisfied.
 * @user_data: the data to pass to callback function.
 * 
 * Asynchronously exchanges the file system metadata of the two files @source
 * and @destination.
 */
void
g_vfs_afp_volume_exchange_files (GVfsAfpVolume       *volume,
                                 const char          *source,
                                 const char          *destination,
                                 GCancellable        *cancellable,
                                 GAsyncReadyCallback  callback,
                                 gpointer             user_data)
{
  GVfsAfpVolumePrivate *priv;
  GVfsAfpCommand *comm;
  GTask *task;

  g_return_if_fail (G_VFS_IS_AFP_VOLUME (volume));

  priv = volume->priv;
  
  comm = g_vfs_afp_command_new (AFP_COMMAND_EXCHANGE_FILES);
  /* pad byte */
  g_vfs_afp_command_put_byte (comm, 0);

  /* Volume ID */
  g_vfs_afp_command_put_uint16 (comm, g_vfs_afp_volume_get_id (volume));
  /* SourceDirectory ID 2 == / */
  g_vfs_afp_command_put_uint32 (comm, 2);
  /* DestDirectory ID 2 == / */
  g_vfs_afp_command_put_uint32 (comm, 2);

  /* SourcePath */
  g_vfs_afp_command_put_pathname (comm, source);
  /* DestPath */
  g_vfs_afp_command_put_pathname (comm, destination);

  task = g_task_new (volume, cancellable, callback, user_data);
  g_task_set_source_tag (task, g_vfs_afp_volume_exchange_files);

  g_vfs_afp_connection_send_command (priv->conn, comm, NULL, 0,
                                     close_replace_exchange_files_cb,
                                     cancellable, task);
  g_object_unref (comm);
}

/*
 * g_vfs_afp_volume_exchange_files_finish:
 * 
 * @volume: a #GVfsAfpVolume.
 * @result: a #GAsyncResult.
 * @error: a #GError, %NULL to ignore.
 * 
 * Finalizes the asynchronous operation started by
 * g_vfs_afp_volume_exchange_files.
 * 
 * Returns: %TRUE on success, %FALSE on error.
 */
gboolean
g_vfs_afp_volume_exchange_files_finish (GVfsAfpVolume  *volume,
                                        GAsyncResult   *res,
                                        GError        **error)
{
  g_return_val_if_fail (g_task_is_valid (res, volume), FALSE);
  g_return_val_if_fail (g_async_result_is_tagged (res, g_vfs_afp_volume_exchange_files), FALSE);

  return g_task_propagate_boolean (G_TASK (res), error);
}

static void
write_ext_cb (GObject *source_object, GAsyncResult *res, gpointer user_data)
{
  GVfsAfpConnection *conn = G_VFS_AFP_CONNECTION (source_object);
  GTask *task = G_TASK (user_data);

  GVfsAfpReply *reply;
  GError *err = NULL;
  AfpResultCode res_code;
  gint64 last_written;

  reply = g_vfs_afp_connection_send_command_finish (conn, res, &err);
  if (!reply)
  {
    g_task_return_error (task, err);
    g_object_unref (task);
    return;
  }

  res_code = g_vfs_afp_reply_get_result_code (reply);
  if (res_code != AFP_RESULT_NO_ERROR)
  {
    g_object_unref (reply);

    switch (res_code)
    {
      case AFP_RESULT_ACCESS_DENIED:
        g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_FAILED,
                                 _("File is not open for write access"));
        break;
      case AFP_RESULT_DISK_FULL:
        g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_NO_SPACE,
                                 _("Not enough space on volume"));
        break;
      case AFP_RESULT_LOCK_ERR:
        g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_FAILED,
                                 _("File is locked by another user"));
        break;
      default:
        g_task_return_error (task, afp_result_code_to_gerror (res_code));
        break;
    }

    g_object_unref (task);
    return;
  }

  g_vfs_afp_reply_read_int64 (reply, &last_written);
  g_object_unref (reply);

  g_task_return_int (task, last_written);
  g_object_unref (task);
}

/*
 * g_vfs_afp_volume_write_to_fork:
 * 
 * @volume: a #GVfsAfpVolume.
 * @fork_refnume: reference id of the fork to write to.
 * @buffer: buffer containing the data to write. Must be valid during the whole
 * call.
 * @buffer_size: size of @buffer.
 * @offset: offset in file where the data should be written.
 * @cancellable: optional #GCancellable object, %NULL to ignore.
 * @callback: callback to call when the request is satisfied.
 * @user_data: the data to pass to callback function.
 * 
 * Asynchronously writes the data in @buffer to the fork referenced by
 * @fork_refnum.
 */
void
g_vfs_afp_volume_write_to_fork (GVfsAfpVolume       *volume,
                                guint16              fork_refnum,
                                char                *buffer,
                                gsize                buffer_size,
                                gint64               offset,
                                GCancellable        *cancellable,
                                GAsyncReadyCallback  callback,
                                gpointer             user_data)
{
  GVfsAfpCommand *comm;
  guint32 req_count;
  GTask *task;

  g_return_if_fail (G_VFS_IS_AFP_VOLUME (volume));
  
  comm = g_vfs_afp_command_new (AFP_COMMAND_WRITE_EXT);
  /* StartEndFlag = 0 */
  g_vfs_afp_command_put_byte (comm, 0);

  /* OForkRefNum */
  g_vfs_afp_command_put_int16 (comm, fork_refnum);
  /* Offset */
  g_vfs_afp_command_put_int64 (comm, offset);
  
  /* ReqCount */
  req_count = MIN (buffer_size, G_MAXUINT32);
  g_vfs_afp_command_put_int64 (comm, req_count);

  g_vfs_afp_command_set_buffer (comm, buffer, req_count);

  task = g_task_new (volume, cancellable, callback, user_data);
  g_task_set_source_tag (task, g_vfs_afp_volume_write_to_fork);

  g_vfs_afp_connection_send_command (volume->priv->conn, comm, NULL, 0,
                                     write_ext_cb, cancellable, task);
  g_object_unref (comm);
}

/*
 * g_vfs_afp_volume_write_to_fork_finish:
 * 
 * @volume: a #GVfsAfpVolume.
 * @result: a #GAsyncResult.
 * @last_written: (out) (allow-none): offset of the last written byte.
 * @error: a #GError, %NULL to ignore.
 * 
 * Finalizes the asynchronous operation started by
 * g_vfs_afp_volume_write_to_fork.
 * 
 * Returns: %TRUE on success, %FALSE on error.
 */
gboolean
g_vfs_afp_volume_write_to_fork_finish (GVfsAfpVolume  *volume,
                                       GAsyncResult   *res,
                                       gint64         *last_written,
                                       GError        **error)
{
  g_return_val_if_fail (g_task_is_valid (res, volume), FALSE);
  g_return_val_if_fail (g_async_result_is_tagged (res, g_vfs_afp_volume_write_to_fork), FALSE);

  if (g_async_result_legacy_propagate_error (res, error))
    return FALSE;

  if (last_written)
    *last_written = g_task_propagate_int (G_TASK (res), NULL);

  return TRUE;
}

static void
read_ext_cb (GObject *source_object, GAsyncResult *res, gpointer user_data)
{
  GVfsAfpConnection *conn = G_VFS_AFP_CONNECTION (source_object);
  GTask *task = G_TASK (user_data);

  GVfsAfpReply *reply;
  GError *err = NULL;
  AfpResultCode res_code;

  reply = g_vfs_afp_connection_send_command_finish (conn, res, &err);
  if (!reply)
  {
    g_task_return_error (task, err);
    g_object_unref (task);
    return;
  }

  res_code = g_vfs_afp_reply_get_result_code (reply);
  if (!(res_code == AFP_RESULT_NO_ERROR || res_code == AFP_RESULT_LOCK_ERR ||
        res_code == AFP_RESULT_EOF_ERR))
  {
    g_object_unref (reply);

    switch (res_code)
    {
      case AFP_RESULT_ACCESS_DENIED:
        g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_FAILED,
                                  _("File is not open for read access"));
        break;
      default:
        g_task_return_error (task, afp_result_code_to_gerror (res_code));
        break;
    }

    g_object_unref (task);
    return;
  }

  g_task_return_int (task, g_vfs_afp_reply_get_size (reply));
  g_object_unref (task);
  g_object_unref (reply);
}

/*
 * g_vfs_afp_volume_read_from_fork:
 * 
 * @volume: a #GVfsAfpVolume.
 * @fork_refnume: reference id of the fork to write to.
 * @buffer: buffer to read data into. Must be valid during the whole call. 
 * @bytes_requested: number of bytes that should be read.
 * @offset: offset in file from where the data should be read.
 * @cancellable: optional #GCancellable object, %NULL to ignore.
 * @callback: callback to call when the request is satisfied.
 * @user_data: the data to pass to callback function.
 * 
 * Asynchronously reads data from the fork referenced by @fork_refnum.
 */
void
g_vfs_afp_volume_read_from_fork (GVfsAfpVolume       *volume,
                                 guint16              fork_refnum,
                                 char                *buffer,
                                 gsize                bytes_requested,
                                 gint64               offset,
                                 GCancellable        *cancellable,
                                 GAsyncReadyCallback  callback,
                                 gpointer             user_data)
{
  GVfsAfpCommand *comm;
  guint32 req_count;
  GTask *task;

  g_return_if_fail (G_VFS_IS_AFP_VOLUME (volume));
  
  comm = g_vfs_afp_command_new (AFP_COMMAND_READ_EXT);
  /* pad byte */
  g_vfs_afp_command_put_byte (comm, 0);

  /* OForkRefNum */
  g_vfs_afp_command_put_int16 (comm, fork_refnum);
  /* Offset */
  g_vfs_afp_command_put_int64 (comm, offset);
  /* ReqCount */
  req_count = MIN (bytes_requested, G_MAXUINT32);
  g_vfs_afp_command_put_int64 (comm, req_count);

  task = g_task_new (volume, cancellable, callback, user_data);
  g_task_set_source_tag (task, g_vfs_afp_volume_read_from_fork);

  g_vfs_afp_connection_send_command (volume->priv->conn, comm, buffer,
                                     req_count, read_ext_cb, cancellable,
                                     task);
  g_object_unref (comm);
}

/*
 * g_vfs_afp_volume_read_from_fork_finish:
 * 
 * @volume: a #GVfsAfpVolume.
 * @result: a #GAsyncResult.
 * @bytes_read: (out) (allow-none): the number of bytes that were read.
 * @error: a #GError, %NULL to ignore.
 * 
 * Finalizes the asynchronous operation started by
 * g_vfs_afp_volume_read_from_fork.
 * 
 * Returns: %TRUE on success, %FALSE on error.
 */
gboolean
g_vfs_afp_volume_read_from_fork_finish (GVfsAfpVolume  *volume,
                                        GAsyncResult   *res,
                                        gsize          *bytes_read,
                                        GError        **error)
{
  g_return_val_if_fail (g_task_is_valid (res, volume), FALSE);
  g_return_val_if_fail (g_async_result_is_tagged (res, g_vfs_afp_volume_read_from_fork), FALSE);

  if (g_async_result_legacy_propagate_error (res, error))
    return FALSE;

  if (bytes_read)
    *bytes_read = g_task_propagate_int (G_TASK (res), NULL);

  return TRUE;
}

static void
attention_cb (GVfsAfpConnection *conn, guint attention, GVfsAfpVolume *volume)
{
  /* Respond to the server notification with FPGetVolParms as the spec
   * suggests.  Some servers disconnect us if we don't. */
  if (attention == AFP_ATTENTION_CODE_SERVER_NOTIFICATION)
    g_vfs_afp_volume_get_parms (volume,
                                AFP_VOLUME_BITMAP_VOL_ID_BIT,
                                NULL, NULL, NULL);
}
