 /* 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., 59 Temple Place, Suite 330,
 * Boston, MA 02111-1307, USA.
 *
 * Author: Carl-Anton Ingmarsson <ca.ingmarsson@gmail.com>
 */

#include <config.h>

#include <stdlib.h>
#include <sys/stat.h>
#include <glib/gstdio.h>
#include <glib/gi18n.h>
#include <gio/gio.h>

#ifdef HAVE_GCRYPT
#include <gcrypt.h>
#endif

#include "gvfsjobmount.h"
#include "gvfsjobenumerate.h"
#include "gvfsjobqueryinfo.h"
#include "gvfsjobqueryfsinfo.h"
#include "gvfsjobsetattribute.h"
#include "gvfsjobqueryattributes.h"
#include "gvfsjobopenforread.h"
#include "gvfsjobcloseread.h"
#include "gvfsjobread.h"
#include "gvfsjobseekread.h"
#include "gvfsjobseekwrite.h"
#include "gvfsjobopenforwrite.h"
#include "gvfsjobwrite.h"
#include "gvfsjobclosewrite.h"
#include "gvfsjobdelete.h"
#include "gvfsjobmakedirectory.h"
#include "gvfsjobsetdisplayname.h"
#include "gvfsjobmove.h"
#include "gvfsjobcopy.h"

#include "gvfsafpserver.h"

#include "gvfsbackendafp.h"

struct _GVfsBackendAfpClass
{
  GVfsBackendClass parent_class;
};

struct _GVfsBackendAfp
{
  GVfsBackend parent_instance;

  GNetworkAddress    *addr;
  char               *volume_name;
  char               *user;

  GVfsAfpServer      *server;
  GVfsAfpVolume      *volume;

  guint32             user_id;
  guint32             group_id;
};


G_DEFINE_TYPE (GVfsBackendAfp, g_vfs_backend_afp, G_VFS_TYPE_BACKEND);


/*
 * Utility functions
 */

static void
copy_file_info_into (GFileInfo *src, GFileInfo *dest)
{
  char **attrs;
  gint i;

  attrs = g_file_info_list_attributes (src, NULL);

  for (i = 0; attrs[i]; i++)
  {
    GFileAttributeType type;
    gpointer value;
    
    g_file_info_get_attribute_data (src, attrs[i], &type, &value, NULL);
    g_file_info_set_attribute (dest, attrs[i], type, value);
  }

  g_strfreev (attrs);
}

typedef enum
{
  AFP_HANDLE_TYPE_READ_FILE,
  AFP_HANDLE_TYPE_CREATE_FILE,
  AFP_HANDLE_TYPE_REPLACE_FILE_TEMP,
  AFP_HANDLE_TYPE_REPLACE_FILE_DIRECT,
  AFP_HANDLE_TYPE_APPEND_TO_FILE
} AfpHandleType;

typedef struct
{
  GVfsBackendAfp *backend;
  
  AfpHandleType type;
  gint16 fork_refnum;
  gint64 offset;
  
  /* Used if type == AFP_HANDLE_TYPE_REPLACE_FILE_DIRECT */
  gint64 size;

  /* Used if type == AFP_HANDLE_TYPE_REPLACE_FILE_TEMP */
  char *filename;
  char *tmp_filename;
  gboolean make_backup;
} AfpHandle;

static AfpHandle *
afp_handle_new (GVfsBackendAfp *backend, gint16 fork_refnum)
{
  AfpHandle *afp_handle;

  afp_handle = g_slice_new0 (AfpHandle);
  afp_handle->backend = backend;
  afp_handle->fork_refnum = fork_refnum;

  return afp_handle;
}

static void
afp_handle_free (AfpHandle *afp_handle)
{
  g_free (afp_handle->filename);
  g_free (afp_handle->tmp_filename);
  
  g_slice_free (AfpHandle, afp_handle);
}

/*
 * Backend code
 */

typedef struct
{
  GVfsJobCopy  *job;
  GAsyncResult *source_parms_res;
  GAsyncResult *dest_parms_res;
} CopyData;

static void
copy_data_free (CopyData *copy_data)
{
  g_object_unref (copy_data->source_parms_res);
  g_object_unref (copy_data->dest_parms_res);

  g_slice_free (CopyData, copy_data);
}

static void
copy_copy_file_cb (GObject *source_object, GAsyncResult *res, gpointer user_data)
{
  GVfsAfpVolume *volume = G_VFS_AFP_VOLUME (source_object);
  GVfsJobCopy *job = G_VFS_JOB_COPY (user_data);

  GError *err = NULL;
  
  if (!g_vfs_afp_volume_copy_file_finish (volume, res, &err))
  {
    g_vfs_job_failed_from_error (G_VFS_JOB (job), err);
    g_error_free (err);
    return;
  }

  g_vfs_job_succeeded (G_VFS_JOB (job));
}

static void
copy_delete_cb (GObject *source_object, GAsyncResult *res, gpointer user_data)
{
  GVfsAfpVolume *volume = G_VFS_AFP_VOLUME (source_object);
  GVfsJobCopy *job = G_VFS_JOB_COPY (user_data);
  GVfsBackendAfp *afp_backend = G_VFS_BACKEND_AFP (job->backend);

  GError *err = NULL;
  
  if (!g_vfs_afp_volume_delete_finish (volume, res, &err))
  {
    g_vfs_job_failed_from_error (G_VFS_JOB (job), err);
    g_error_free (err);
    return;
  }

  g_vfs_afp_volume_copy_file (afp_backend->volume, job->source, job->destination,
                              G_VFS_JOB (job)->cancellable, copy_copy_file_cb, job);
}

static void
do_copy (CopyData *copy_data)
{
  GVfsJobCopy *job = copy_data->job;
  GVfsBackendAfp *afp_backend = G_VFS_BACKEND_AFP (job->backend);
  
  GFileInfo *info;
  GError *err = NULL;

  gboolean source_is_dir;
  gboolean dest_exists;
  gboolean dest_is_dir;
  
  info = g_vfs_afp_volume_get_filedir_parms_finish (afp_backend->volume, copy_data->source_parms_res, &err);
  if (!info)
  {
    g_vfs_job_failed_from_error (G_VFS_JOB (job), err);
    g_error_free (err);
    goto done;
  }

  /* If the source is a directory, don't fail with WOULD_RECURSE immediately,
   * as that is less useful to the app. Better check for errors on the
   * target instead.
   */
  source_is_dir = g_file_info_get_file_type (info) == G_FILE_TYPE_DIRECTORY ? TRUE : FALSE;
  g_object_unref (info);

  info = g_vfs_afp_volume_get_filedir_parms_finish (afp_backend->volume, copy_data->dest_parms_res, &err);
  if (!info)
  {
    if (g_error_matches (err, G_IO_ERROR, G_IO_ERROR_NOT_FOUND))
    {
      g_clear_error (&err);
      dest_exists = FALSE;
    }
    else
    {
      g_vfs_job_failed_from_error (G_VFS_JOB (job), err);
      g_error_free (err);
      goto done;
    }
  }
  else
  {
    dest_exists = TRUE;
    dest_is_dir = g_file_info_get_file_type (info) == G_FILE_TYPE_DIRECTORY ? TRUE : FALSE;
    g_object_unref (info);
  }

  /* Check target errors */
  if (dest_exists)
  {
    if ((job->flags & G_FILE_COPY_OVERWRITE))
    {
      /* Always fail on dirs, even with overwrite */
      if (dest_is_dir)
      {
        if (source_is_dir)
          g_vfs_job_failed_literal (G_VFS_JOB (job), G_IO_ERROR, G_IO_ERROR_WOULD_MERGE,
                                    _("Can't copy directory over directory"));
        else
          g_vfs_job_failed_literal (G_VFS_JOB (job), G_IO_ERROR, G_IO_ERROR_IS_DIRECTORY,
                                    _("File is directory"));
        goto done;
      }
    }
    else
    {
      g_vfs_job_failed (G_VFS_JOB (job), G_IO_ERROR, G_IO_ERROR_EXISTS,
                        _("Target file already exists"));
      goto done;
    }
  }

  /* Now we fail if the source is a directory */
  if (source_is_dir)
  {
    g_vfs_job_failed (G_VFS_JOB (job), G_IO_ERROR, G_IO_ERROR_WOULD_RECURSE,
                      _("Can't recursively copy directory"));
    goto done;
  }

  if (dest_exists)
  {
    g_vfs_afp_volume_delete (afp_backend->volume, job->destination,
                             G_VFS_JOB (job)->cancellable, copy_delete_cb, job);
  }
  else
  {
    g_vfs_afp_volume_copy_file (afp_backend->volume, job->source, job->destination,
                                G_VFS_JOB (job)->cancellable, copy_copy_file_cb, job);
  }
  
done:
  copy_data_free (copy_data);
  return;
}

static void
copy_get_dest_parms_cb (GObject *source_object, GAsyncResult *res, gpointer user_data)
{
  CopyData *copy_data = (CopyData *)user_data;
  
  copy_data->dest_parms_res = g_object_ref (res);
  if (copy_data->source_parms_res)
    do_copy (copy_data);
}

static void
copy_get_source_parms_cb (GObject *source_object, GAsyncResult *res, gpointer user_data)
{
  CopyData *copy_data = (CopyData *)user_data;

  copy_data->source_parms_res = g_object_ref (res);
  if (copy_data->dest_parms_res)
    do_copy (copy_data);
}

static gboolean
try_copy (GVfsBackend *backend,
          GVfsJobCopy *job,
          const char *source,
          const char *destination,
          GFileCopyFlags flags,
          GFileProgressCallback progress_callback,
          gpointer progress_callback_data)
{
  GVfsBackendAfp *afp_backend = G_VFS_BACKEND_AFP (backend);

  CopyData *copy_data;

  copy_data = g_slice_new0 (CopyData);
  copy_data->job = job;

  g_vfs_afp_volume_get_filedir_parms (afp_backend->volume, source,
                                      AFP_FILEDIR_BITMAP_ATTRIBUTE_BIT,
                                      AFP_FILEDIR_BITMAP_ATTRIBUTE_BIT,
                                      G_VFS_JOB (job)->cancellable, copy_get_source_parms_cb,
                                      copy_data);

  g_vfs_afp_volume_get_filedir_parms (afp_backend->volume, destination,
                                      AFP_FILEDIR_BITMAP_ATTRIBUTE_BIT,
                                      AFP_FILEDIR_BITMAP_ATTRIBUTE_BIT,
                                      G_VFS_JOB (job)->cancellable, copy_get_dest_parms_cb,
                                      copy_data);

  return TRUE;
}

static void
move_move_and_rename_cb (GObject *source_object, GAsyncResult *res, gpointer user_data)
{
  GVfsAfpVolume *volume = G_VFS_AFP_VOLUME (source_object);
  GVfsJobMove *job = G_VFS_JOB_MOVE (user_data);

  GError *err = NULL;
  
  if (!g_vfs_afp_volume_move_and_rename_finish (volume, res, &err))
  {
    g_vfs_job_failed_from_error (G_VFS_JOB (job), err);
    g_error_free (err);
    return;
  }

  g_vfs_job_succeeded (G_VFS_JOB (job));
}

static void
move_delete_cb (GObject *source_object, GAsyncResult *res, gpointer user_data)
{
  GVfsAfpVolume *volume = G_VFS_AFP_VOLUME (source_object);
  GVfsJobMove *job = G_VFS_JOB_MOVE (user_data);

  GError *err = NULL;
  
  if (!g_vfs_afp_volume_delete_finish (volume, res, &err))
  {
    g_vfs_job_failed_from_error (G_VFS_JOB (job), err);
    g_error_free (err);
    return;
  }

  g_vfs_afp_volume_move_and_rename (volume, job->source, job->destination,
                                    G_VFS_JOB (job)->cancellable, move_move_and_rename_cb,
                                    job);
}

typedef struct
{
  GVfsJobMove *job;
  GAsyncResult *source_parms_res;
  GAsyncResult *dest_parms_res;
} MoveData;

static void
free_move_data (MoveData *move_data)
{
  g_object_unref (move_data->source_parms_res);
  g_object_unref (move_data->dest_parms_res);

  g_slice_free (MoveData, move_data);
}

static void
do_move (MoveData *move_data)
{
  GVfsJobMove *job = move_data->job;
  GVfsBackendAfp *afp_backend = G_VFS_BACKEND_AFP (job->backend);
  
  GFileInfo *info;
  GError *err = NULL;

  gboolean source_is_dir;
  gboolean dest_exists;
  gboolean dest_is_dir;
  
  info = g_vfs_afp_volume_get_filedir_parms_finish (afp_backend->volume,
                                                    move_data->source_parms_res, &err);
  if (!info)
  {
    g_vfs_job_failed_from_error (G_VFS_JOB (job), err);
    g_error_free (err);
    goto done;
  }

  source_is_dir = g_file_info_get_file_type (info) == G_FILE_TYPE_DIRECTORY ? TRUE : FALSE;
  g_object_unref (info);

  info = g_vfs_afp_volume_get_filedir_parms_finish (afp_backend->volume, 
                                                    move_data->dest_parms_res, &err);
  if (!info)
  {
    if (g_error_matches (err, G_IO_ERROR, G_IO_ERROR_NOT_FOUND))
    {
      g_clear_error (&err);
      dest_exists = FALSE;
    }
    else
    {
      g_vfs_job_failed_from_error (G_VFS_JOB (job), err);
      g_error_free (err);
      goto done;
    }
  }
  else
  {
    dest_exists = TRUE;
    dest_is_dir = g_file_info_get_file_type (info) == G_FILE_TYPE_DIRECTORY ? TRUE : FALSE;
    g_object_unref (info);
  }

  if (dest_exists)
  {
    if ((job->flags & G_FILE_COPY_OVERWRITE))
    {
      /* Always fail on dirs, even with overwrite */
      if (dest_is_dir)
      {
        if (source_is_dir)
          g_vfs_job_failed_literal (G_VFS_JOB (job), G_IO_ERROR, G_IO_ERROR_WOULD_MERGE,
                                    _("Can't move directory over directory"));
        else
          g_vfs_job_failed_literal (G_VFS_JOB (job), G_IO_ERROR, G_IO_ERROR_IS_DIRECTORY,
                                    _("File is directory"));
        goto done;
      }
    }
    else
    {
      g_vfs_job_failed (G_VFS_JOB (job), G_IO_ERROR, G_IO_ERROR_EXISTS,
                        _("Target file already exists"));
      goto done;
    }

    g_vfs_afp_volume_delete (afp_backend->volume, job->destination,
                             G_VFS_JOB (job)->cancellable, move_delete_cb, job);
  }
  else
    g_vfs_afp_volume_move_and_rename (afp_backend->volume, job->source, job->destination,
                                      G_VFS_JOB (job)->cancellable, move_move_and_rename_cb,
                                      job);

done:
  free_move_data (move_data);
  return;
}

static void
move_get_dest_parms_cb (GObject *source_object, GAsyncResult *res, gpointer user_data)
{
  MoveData *move_data = (MoveData *)user_data;
  
  move_data->dest_parms_res = g_object_ref (res);
  if (move_data->source_parms_res)
    do_move (move_data);
}

static void
move_get_source_parms_cb (GObject *source_object, GAsyncResult *res, gpointer user_data)
{
  MoveData *move_data = (MoveData *)user_data;

  move_data->source_parms_res = g_object_ref (res);
  if (move_data->dest_parms_res)
    do_move (move_data);
}

static gboolean
try_move (GVfsBackend *backend,
          GVfsJobMove *job,
          const char *source,
          const char *destination,
          GFileCopyFlags flags,
          GFileProgressCallback progress_callback,
          gpointer progress_callback_data)
{
  GVfsBackendAfp *afp_backend = G_VFS_BACKEND_AFP (backend);

  MoveData *move_data;
  
  move_data = g_slice_new0 (MoveData);
  move_data->job = job;
  
  g_vfs_afp_volume_get_filedir_parms (afp_backend->volume, source,
                                      AFP_FILEDIR_BITMAP_ATTRIBUTE_BIT,
                                      AFP_FILEDIR_BITMAP_ATTRIBUTE_BIT,
                                      G_VFS_JOB (job)->cancellable, move_get_source_parms_cb,
                                      move_data);

  g_vfs_afp_volume_get_filedir_parms (afp_backend->volume, destination,
                                      AFP_FILEDIR_BITMAP_ATTRIBUTE_BIT,
                                      AFP_FILEDIR_BITMAP_ATTRIBUTE_BIT,
                                      G_VFS_JOB (job)->cancellable, move_get_dest_parms_cb,
                                      move_data);  

  return TRUE;
}

static void
rename_cb (GObject *source_object, GAsyncResult *res, gpointer user_data)
{
  GVfsAfpVolume *volume = G_VFS_AFP_VOLUME (source_object);
  GVfsJobSetDisplayName *job = G_VFS_JOB_SET_DISPLAY_NAME (user_data);

  GError *err = NULL;
  char *dirname, *newpath;

  if (!g_vfs_afp_volume_rename_finish (volume, res, &err))
  {
    g_vfs_job_failed_from_error (G_VFS_JOB (job), err);
    g_error_free (err);
    return;
  }

  dirname = g_path_get_dirname (job->filename);
  newpath = g_build_filename (dirname, job->display_name, NULL);
  g_vfs_job_set_display_name_set_new_path (job, newpath);

  g_free (dirname);
  g_free (newpath);

  g_vfs_job_succeeded (G_VFS_JOB (job));
}
  
static gboolean
try_set_display_name (GVfsBackend *backend,
                      GVfsJobSetDisplayName *job,
                      const char *filename,
                      const char *display_name)
{
  GVfsBackendAfp *afp_backend = G_VFS_BACKEND_AFP (backend);
  
  if (is_root (filename))
  {
    g_vfs_job_failed_literal (G_VFS_JOB (job), G_IO_ERROR, G_IO_ERROR_INVALID_FILENAME,
                              _("Can't rename volume"));
    return TRUE;
  }

  g_vfs_afp_volume_rename (afp_backend->volume, filename, display_name,
                           G_VFS_JOB (job)->cancellable, rename_cb, job);
  return TRUE;
}

static void
create_directory_cb (GObject *source_object, GAsyncResult *res, gpointer user_data)
{
  GVfsAfpVolume *volume = G_VFS_AFP_VOLUME (source_object);
  GVfsJobMakeDirectory *job = G_VFS_JOB_MAKE_DIRECTORY (user_data);

  GError *err = NULL;

  if (!g_vfs_afp_volume_create_directory_finish (volume, res, &err))
  {
    g_vfs_job_failed_from_error (G_VFS_JOB (job), err);
    g_error_free (err);
    return;
  }

  g_vfs_job_succeeded (G_VFS_JOB (job));
}

static gboolean 
try_make_directory (GVfsBackend *backend,
                    GVfsJobMakeDirectory *job,
                    const char *filename)
{
  GVfsBackendAfp *afp_backend = G_VFS_BACKEND_AFP (backend);

  g_vfs_afp_volume_create_directory (afp_backend->volume, job->filename,
                                     G_VFS_JOB (job)->cancellable,
                                     create_directory_cb, job);
  return TRUE;
}

static void
delete_delete_cb (GObject *source_object, GAsyncResult *res, gpointer user_data)
{
  GVfsAfpVolume *volume = G_VFS_AFP_VOLUME (source_object);
  GVfsJobDelete *job = G_VFS_JOB_DELETE (user_data);

  GError *err = NULL;

  if (!g_vfs_afp_volume_delete_finish (volume, res, &err))
  {
    g_vfs_job_failed_from_error (G_VFS_JOB (job), err);
    g_error_free (err);
    return;
  }

  g_vfs_job_succeeded (G_VFS_JOB (job));
}

static gboolean 
try_delete (GVfsBackend *backend,
            GVfsJobDelete *job,
            const char *filename)
{
  GVfsBackendAfp *afp_backend = G_VFS_BACKEND_AFP (backend);

  g_vfs_afp_volume_delete (afp_backend->volume, filename,
                           G_VFS_JOB (job)->cancellable, delete_delete_cb, job);
  
  return TRUE;
}

static void
write_cb (GObject *source_object, GAsyncResult *res, gpointer user_data)
{
  GVfsAfpVolume *volume = G_VFS_AFP_VOLUME (source_object);
  GVfsJobWrite *job = G_VFS_JOB_WRITE (user_data);
  AfpHandle *afp_handle = (AfpHandle *)job->handle;

  GError *err = NULL;
  gint64 last_written;
  gsize written_size;

  if (!g_vfs_afp_volume_write_to_fork_finish (volume, res, &last_written, &err))
  {
    g_vfs_job_failed_from_error (G_VFS_JOB (job), err);
    g_error_free (err);
    return;
  }

  written_size = last_written - afp_handle->offset;
  afp_handle->offset = last_written;

  if (afp_handle->type == AFP_HANDLE_TYPE_REPLACE_FILE_DIRECT)
    afp_handle->size = MAX (last_written, afp_handle->size);
  
  g_vfs_job_write_set_written_size (job, written_size); 
  g_vfs_job_succeeded (G_VFS_JOB (job));
}

static gboolean
try_write (GVfsBackend *backend,
           GVfsJobWrite *job,
           GVfsBackendHandle handle,
           char *buffer,
           gsize buffer_size)
{
  GVfsBackendAfp *afp_backend = G_VFS_BACKEND_AFP (backend);
  AfpHandle *afp_handle = (AfpHandle *)handle;

  g_vfs_afp_volume_write_to_fork (afp_backend->volume, afp_handle->fork_refnum,
                                  buffer, buffer_size, afp_handle->offset,
                                  G_VFS_JOB (job)->cancellable, write_cb, job);

  return TRUE;
}

static void
seek_on_write_cb (GObject *source_object, GAsyncResult *res, gpointer user_data)
{
  GVfsAfpVolume *volume = G_VFS_AFP_VOLUME (source_object);
  GVfsJobSeekWrite *job = G_VFS_JOB_SEEK_WRITE (user_data);
  AfpHandle *afp_handle = (AfpHandle *)job->handle;

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

  info = g_vfs_afp_volume_get_fork_parms_finish (volume, res, &err);
  if (!info)
  {
    g_vfs_job_failed_from_error (G_VFS_JOB (job), err);
    g_error_free (err);
    return;
  }
  
  size = g_file_info_get_size (info);
  g_object_unref (info);

  switch (job->seek_type)
  {
    case G_SEEK_CUR:
      afp_handle->offset += job->requested_offset;
      break;
    case G_SEEK_SET:
      afp_handle->offset = job->requested_offset;
      break;
    case G_SEEK_END:
      afp_handle->offset = size + job->requested_offset;
      break;
  }

  if (afp_handle->offset < 0)
    afp_handle->offset = 0;
  else if (afp_handle->offset > size)
    afp_handle->offset = size;

  g_vfs_job_seek_write_set_offset (job, afp_handle->offset);
  g_vfs_job_succeeded (G_VFS_JOB (job));
}

static gboolean
try_seek_on_write (GVfsBackend *backend,
                   GVfsJobSeekWrite *job,
                   GVfsBackendHandle handle,
                   goffset    offset,
                   GSeekType  type)
{
  GVfsBackendAfp *afp_backend = G_VFS_BACKEND_AFP (backend);
  AfpHandle *afp_handle = (AfpHandle *)handle;

  if (afp_handle->type == AFP_HANDLE_TYPE_REPLACE_FILE_DIRECT)
  {
    switch (job->seek_type)
    {
      case G_SEEK_CUR:
        afp_handle->offset += job->requested_offset;
        break;
      case G_SEEK_SET:
        afp_handle->offset = job->requested_offset;
        break;
      case G_SEEK_END:
        afp_handle->offset = afp_handle->size + job->requested_offset;
        break;
    }

    if (afp_handle->offset < 0)
      afp_handle->offset = 0;
    else if (afp_handle->offset > afp_handle->size)
      afp_handle->offset = afp_handle->size;

    g_vfs_job_seek_write_set_offset (job, afp_handle->offset);
    g_vfs_job_succeeded (G_VFS_JOB (job));
  }
  
  else
  {
    g_vfs_afp_volume_get_fork_parms (afp_backend->volume, afp_handle->fork_refnum,
                                     AFP_FILE_BITMAP_EXT_DATA_FORK_LEN_BIT,
                                     G_VFS_JOB (job)->cancellable, seek_on_write_cb, job);
  }
    
  return TRUE;
}

static void
seek_on_read_cb (GObject *source_object, GAsyncResult *res, gpointer user_data)
{
  GVfsAfpVolume *volume = G_VFS_AFP_VOLUME (source_object);
  GVfsJobSeekRead *job = G_VFS_JOB_SEEK_READ (user_data);
  AfpHandle *afp_handle = (AfpHandle *)job->handle;

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

  info = g_vfs_afp_volume_get_fork_parms_finish (volume, res, &err);
  if (!info)
  {
    g_vfs_job_failed_from_error (G_VFS_JOB (job), err);
    g_error_free (err);
    return;
  }
  
  size = g_file_info_get_size (info);
  g_object_unref (info);

  switch (job->seek_type)
  {
    case G_SEEK_CUR:
      afp_handle->offset += job->requested_offset;
      break;
    case G_SEEK_SET:
      afp_handle->offset = job->requested_offset;
      break;
    case G_SEEK_END:
      afp_handle->offset = size + job->requested_offset;
      break;
  }

  if (afp_handle->offset < 0)
    afp_handle->offset = 0;
  else if (afp_handle->offset > size)
    afp_handle->offset = size;

  g_vfs_job_seek_read_set_offset (job, afp_handle->offset);
  g_vfs_job_succeeded (G_VFS_JOB (job));
}

static gboolean
try_seek_on_read (GVfsBackend *backend,
                  GVfsJobSeekRead *job,
                  GVfsBackendHandle handle,
                  goffset    offset,
                  GSeekType  type)
{
  GVfsBackendAfp *afp_backend = G_VFS_BACKEND_AFP (backend);
  AfpHandle *afp_handle = (AfpHandle *)handle;

  g_vfs_afp_volume_get_fork_parms (afp_backend->volume, afp_handle->fork_refnum,
                                   AFP_FILE_BITMAP_EXT_DATA_FORK_LEN_BIT,
                                   G_VFS_JOB (job)->cancellable, seek_on_read_cb, job);
  
  return TRUE;
}

static void
read_cb (GObject *source_object, GAsyncResult *res, gpointer user_data)
{
  GVfsAfpVolume *volume = G_VFS_AFP_VOLUME (source_object);
  GVfsJobRead *job = G_VFS_JOB_READ (user_data);
  AfpHandle *afp_handle = (AfpHandle *)job->handle;

  GError *err = NULL;
  gsize bytes_read;

  if (!g_vfs_afp_volume_read_from_fork_finish (volume, res, &bytes_read, &err))
  {
    g_vfs_job_failed_from_error (G_VFS_JOB (job), err);
    g_error_free (err);
    return;
  }

  afp_handle->offset += bytes_read;
  g_vfs_job_read_set_size (job, bytes_read);
  
  g_vfs_job_succeeded (G_VFS_JOB (job));
}
  
static gboolean 
try_read (GVfsBackend *backend,
          GVfsJobRead *job,
          GVfsBackendHandle handle,
          char *buffer,
          gsize bytes_requested)
{
  GVfsBackendAfp *afp_backend = G_VFS_BACKEND_AFP (backend);
  AfpHandle *afp_handle = (AfpHandle *)handle;

  g_vfs_afp_volume_read_from_fork (afp_backend->volume, afp_handle->fork_refnum,
                                   buffer, bytes_requested, afp_handle->offset,
                                   G_VFS_JOB (job)->cancellable, read_cb, job);
  return TRUE;
}

static void
close_replace_get_filedir_parms_cb (GObject *source_object, GAsyncResult *res, gpointer user_data)
{
  GVfsAfpVolume *volume = G_VFS_AFP_VOLUME (source_object);
  GVfsJobCloseWrite *job = G_VFS_JOB_CLOSE_WRITE (user_data);

  GFileInfo *info;
  GError *err = NULL;

  info = g_vfs_afp_volume_get_filedir_parms_finish (volume, res, &err);
  if (!info)
  {
    g_vfs_job_failed_from_error (G_VFS_JOB (job), err);
    g_error_free (err);
    return;
  }

  g_vfs_job_close_write_set_etag (job, g_file_info_get_etag (info));
  g_vfs_job_succeeded (G_VFS_JOB (job));
  
  g_object_unref (info);
}

static void
close_replace_delete_backup_cb (GObject *source_object, GAsyncResult *res, gpointer user_data)
{
  GVfsAfpVolume *volume = G_VFS_AFP_VOLUME (source_object);
  AfpHandle *afp_handle = (AfpHandle *)user_data;

  char *backup_name;

  /* We ignore all errors and just try to rename the temporary file anyway */
  backup_name = g_strconcat (afp_handle->filename, "~", NULL);

  g_vfs_afp_volume_move_and_rename (volume, afp_handle->tmp_filename, backup_name,
                                    NULL, NULL, NULL);
  afp_handle_free (afp_handle);
}

static void
close_replace_close_fork_cb (GObject *source_object, GAsyncResult *res, gpointer user_data)
{
  GVfsAfpVolume *volume = G_VFS_AFP_VOLUME (source_object);
  AfpHandle *afp_handle = (AfpHandle *)user_data;

  if (afp_handle->make_backup)
  {
    char *backup_name = g_strconcat (afp_handle->filename, "~", NULL);
    
    /* Delete old backup */
    g_vfs_afp_volume_delete (volume, backup_name, NULL,
                             close_replace_delete_backup_cb, afp_handle);
    g_free (backup_name);
  }

  else
  {
    /* Delete temporary file */
    g_vfs_afp_volume_delete (volume, afp_handle->tmp_filename, NULL, NULL, NULL);
    
    afp_handle_free (afp_handle);
  }
}

static void
close_replace_exchange_files_cb (GObject *source_object, GAsyncResult *res, gpointer user_data)
{
  GVfsAfpVolume *volume = G_VFS_AFP_VOLUME (source_object);
  GVfsJobCloseWrite *job = G_VFS_JOB_CLOSE_WRITE (user_data);
  AfpHandle *afp_handle = (AfpHandle *)job->handle;

  GError *err = NULL;

  if (!g_vfs_afp_volume_exchange_files_finish (volume, res, &err))
  {
    g_vfs_job_failed_from_error (G_VFS_JOB (job), err);
    g_error_free (err);
    afp_handle_free (afp_handle);
    return;
  }

  /* Close fork and remove/rename the temporary file even if the exchange failed */
  g_vfs_afp_volume_close_fork (volume, afp_handle->fork_refnum,
                               G_VFS_JOB (job)->cancellable,
                               close_replace_close_fork_cb, job->handle);
  
  /* Get ETAG */
  g_vfs_afp_volume_get_filedir_parms (volume, afp_handle->filename,
                                      AFP_FILE_BITMAP_MOD_DATE_BIT, 0,
                                      G_VFS_JOB (job)->cancellable,
                                      close_replace_get_filedir_parms_cb, job);
}

static void
close_fork_cb (GObject *source_object, GAsyncResult *res, gpointer user_data)
{
  GVfsAfpVolume *volume = G_VFS_AFP_VOLUME (source_object);
  GVfsJob *job = G_VFS_JOB (user_data);

  GError *err = NULL;

  if (!g_vfs_afp_volume_close_fork_finish (volume, res, &err))
  {
    g_vfs_job_failed_from_error (G_VFS_JOB (job), err);
    g_error_free (err);
    return;
  }

  g_vfs_job_succeeded (G_VFS_JOB (job));
}

static void
close_fork (GVfsAfpVolume  *volume,
            GVfsJob        *job,
            AfpHandle      *afp_handle)
{
  g_vfs_afp_volume_close_fork (volume, afp_handle->fork_refnum,
                               G_VFS_JOB (job)->cancellable,
                               close_fork_cb, job);
  afp_handle_free (afp_handle);
}

static void
close_write_get_fork_parms_cb (GObject *source_object, GAsyncResult *res, gpointer user_data)
{
  GVfsAfpVolume *volume = G_VFS_AFP_VOLUME (source_object);
  GVfsJobCloseWrite *job = G_VFS_JOB_CLOSE_WRITE (user_data);

  AfpHandle *afp_handle = (AfpHandle *)job->handle;

  GError *err = NULL;
  GFileInfo *info;

  info = g_vfs_afp_volume_get_fork_parms_finish (volume, res, &err);
  if (!info)
  {
    g_vfs_job_failed_from_error (G_VFS_JOB (job), err);
    g_error_free (err);

    afp_handle_free (afp_handle);
    return;
  }

  g_vfs_job_close_write_set_etag (job, g_file_info_get_etag (info));
  close_fork (volume, G_VFS_JOB (job), afp_handle);
}
  
static void
close_replace_set_fork_size_cb (GObject *source_object, GAsyncResult *res, gpointer user_data)
{
  GVfsAfpVolume *volume = G_VFS_AFP_VOLUME (source_object);
  GVfsJobCloseWrite *job = G_VFS_JOB_CLOSE_WRITE (user_data);
  GVfsBackendAfp *afp_backend = G_VFS_BACKEND_AFP (job->backend);
  AfpHandle *afp_handle = (AfpHandle *)job->handle;

  GError *err = NULL;

  if (!g_vfs_afp_volume_set_fork_size_finish (volume, res, &err))
  {
    g_vfs_job_failed_from_error (G_VFS_JOB (job), err);
    g_error_free (err);
    afp_handle_free (afp_handle);
    return;
  }

  /* Get ETAG */
  g_vfs_afp_volume_get_fork_parms (afp_backend->volume, afp_handle->fork_refnum,
                                   AFP_FILE_BITMAP_MOD_DATE_BIT,
                                   G_VFS_JOB (job)->cancellable,
                                   close_write_get_fork_parms_cb, job);
}

static gboolean
try_close_write (GVfsBackend *backend,
                 GVfsJobCloseWrite *job,
                 GVfsBackendHandle handle)
{
  GVfsBackendAfp *afp_backend = G_VFS_BACKEND_AFP (backend);
  AfpHandle *afp_handle = (AfpHandle *)handle;
  
  if (afp_handle->type == AFP_HANDLE_TYPE_REPLACE_FILE_TEMP)
  {
    g_vfs_afp_volume_exchange_files (afp_backend->volume, afp_handle->filename,
                                     afp_handle->tmp_filename, 
                                     G_VFS_JOB (job)->cancellable,
                                     close_replace_exchange_files_cb, job);
  }
  else if (afp_handle->type == AFP_HANDLE_TYPE_REPLACE_FILE_DIRECT)
  {
    g_vfs_afp_volume_set_fork_size (afp_backend->volume, afp_handle->fork_refnum,
                                    afp_handle->size, G_VFS_JOB (job)->cancellable,
                                    close_replace_set_fork_size_cb, job);
  }
  else
  {
    /* Get ETAG */
    g_vfs_afp_volume_get_fork_parms (afp_backend->volume, afp_handle->fork_refnum,
                                     AFP_FILE_BITMAP_MOD_DATE_BIT,
                                     G_VFS_JOB (job)->cancellable,
                                     close_write_get_fork_parms_cb, job);
  }
  
  return TRUE;
}

static gboolean
try_close_read (GVfsBackend *backend,
                GVfsJobCloseRead *job,
                GVfsBackendHandle handle)
{
  GVfsBackendAfp *afp_backend = G_VFS_BACKEND_AFP (backend);
  AfpHandle *afp_handle = (AfpHandle *)handle;

  close_fork (afp_backend->volume, G_VFS_JOB (job), afp_handle);
  
  return TRUE;
}

static void
create_open_fork_cb (GObject *source_object, GAsyncResult *res, gpointer user_data)
{
  GVfsAfpVolume *volume = G_VFS_AFP_VOLUME (source_object);
  GVfsJobOpenForWrite *job = G_VFS_JOB_OPEN_FOR_WRITE (user_data);
  GVfsBackendAfp *afp_backend = G_VFS_BACKEND_AFP (job->backend);

  gint16 fork_refnum;
  GError *err = NULL;
  AfpHandle *afp_handle;
  
  if (!g_vfs_afp_volume_open_fork_finish (volume, res, &fork_refnum, NULL, &err))
  {
    g_vfs_job_failed_from_error (G_VFS_JOB (job), err);
    g_error_free (err);
    return;
  }

  afp_handle = afp_handle_new (afp_backend, fork_refnum);
  afp_handle->type = AFP_HANDLE_TYPE_CREATE_FILE;
  
  g_vfs_job_open_for_write_set_handle (job, (GVfsBackendHandle) afp_handle);
  g_vfs_job_open_for_write_set_can_seek (job, TRUE);
  g_vfs_job_open_for_write_set_initial_offset (job, 0);

  g_vfs_job_succeeded (G_VFS_JOB (job));
}

static void
create_cb (GObject *source_object, GAsyncResult *res, gpointer user_data)
{
  GVfsAfpVolume *volume = G_VFS_AFP_VOLUME (source_object);
  GVfsJobOpenForWrite *job = G_VFS_JOB_OPEN_FOR_WRITE (user_data);

  GError *err = NULL;

  if (!g_vfs_afp_volume_create_file_finish (volume, res, &err))
  {
    g_vfs_job_failed_from_error (G_VFS_JOB (job), err);
    g_error_free (err);
    return;
  }

  g_vfs_afp_volume_open_fork (volume, job->filename, AFP_ACCESS_MODE_WRITE_BIT, 0,
                              G_VFS_JOB (job)->cancellable, create_open_fork_cb, job);
}

static gboolean
try_create (GVfsBackend *backend,
            GVfsJobOpenForWrite *job,
            const char *filename,
            GFileCreateFlags flags)
{
  GVfsBackendAfp *afp_backend = G_VFS_BACKEND_AFP (backend);

  g_vfs_afp_volume_create_file (afp_backend->volume, filename, FALSE,
                                G_VFS_JOB (job)->cancellable, create_cb, job);

  return TRUE;
}

static void
replace_open_fork_cb (GObject *source_object, GAsyncResult *res, gpointer user_data)
{
  GVfsAfpVolume *volume = G_VFS_AFP_VOLUME (source_object);
  GVfsJobOpenForWrite *job = G_VFS_JOB_OPEN_FOR_WRITE (user_data);
  GVfsBackendAfp *afp_backend = G_VFS_BACKEND_AFP (job->backend);

  gint16 fork_refnum;
  GError *err = NULL;
  AfpHandle *afp_handle;
  char *tmp_filename;
  
  if (!g_vfs_afp_volume_open_fork_finish (volume, res, &fork_refnum, NULL, &err))
  {
    g_vfs_job_failed_from_error (G_VFS_JOB (job), err);
    g_error_free (err);
    return;
  }

  afp_handle = afp_handle_new (afp_backend, fork_refnum);
  tmp_filename = g_object_get_data (G_OBJECT (job), "TempFilename");
  /* Replace using temporary file */
  if (tmp_filename)
  {
    afp_handle->type = AFP_HANDLE_TYPE_REPLACE_FILE_TEMP;
    afp_handle->filename = g_strdup (job->filename);
    afp_handle->tmp_filename = g_strdup (tmp_filename);
    afp_handle->make_backup = job->make_backup;
  }
  else
    afp_handle->type = AFP_HANDLE_TYPE_REPLACE_FILE_DIRECT;
    
  g_vfs_job_open_for_write_set_handle (job, (GVfsBackendHandle) afp_handle);
  g_vfs_job_open_for_write_set_can_seek (job, TRUE);
  g_vfs_job_open_for_write_set_initial_offset (job, 0);

  g_vfs_job_succeeded (G_VFS_JOB (job));
}

static void replace_create_tmp_file (GVfsAfpVolume *volume, GVfsJobOpenForWrite *job);

static void
replace_create_tmp_file_cb (GObject *source_object, GAsyncResult *res, gpointer user_data)
{
  GVfsAfpVolume *volume = G_VFS_AFP_VOLUME (source_object);
  GVfsJobOpenForWrite *job = G_VFS_JOB_OPEN_FOR_WRITE (user_data);

  GError *err = NULL;
  char *tmp_filename;

  if (!g_vfs_afp_volume_create_file_finish (volume, res, &err))
  {
    if (g_error_matches (err, G_IO_ERROR, G_IO_ERROR_EXISTS))
      replace_create_tmp_file (volume, job);

    /* We don't have the necessary permissions to create a temporary file
     * so we try to write directly to the file */
    else if (g_error_matches (err, G_IO_ERROR, G_IO_ERROR_PERMISSION_DENIED))
    {
      /* FIXME: We don't support making backups when we can't use FPExchangeFiles */
      if (job->make_backup)
      {
        g_vfs_job_failed_literal (G_VFS_JOB (job), G_IO_ERROR,
                                  G_IO_ERROR_CANT_CREATE_BACKUP,
                                  _("backups not supported"));
      }
      else
      {
        g_object_set_data (G_OBJECT (job), "TempFilename", NULL);
        g_vfs_afp_volume_open_fork (volume, job->filename,
                                    AFP_ACCESS_MODE_WRITE_BIT, 0,
                                    G_VFS_JOB (job)->cancellable, replace_open_fork_cb, job);
      }
    }
                              
    else
    {
      g_vfs_job_failed (G_VFS_JOB (job), err->domain, err->code,
                        _("Unable to create temporary file (%s)"), err->message);
    }
    g_error_free (err);
    return;
  }

  tmp_filename = g_object_get_data (G_OBJECT (job), "TempFilename");
  g_vfs_afp_volume_open_fork (volume, tmp_filename,
                              AFP_ACCESS_MODE_WRITE_BIT, 0,
                              G_VFS_JOB (job)->cancellable, replace_open_fork_cb, job);
}

static void
random_chars (char *str, int len)
{
  int i;
  const char chars[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";

  for (i = 0; i < len; i++)
    str[i] = chars[g_random_int_range (0, strlen(chars))];
}

static void
replace_create_tmp_file (GVfsAfpVolume *volume, GVfsJobOpenForWrite *job)
{
  char basename[] = "~gvfXXXX.tmp";
  char *dir, *tmp_filename;

  random_chars (basename + 4, 4);
  dir = g_path_get_dirname (job->filename);

  tmp_filename = g_build_filename (dir, basename, NULL);
  g_free (dir);

  g_object_set_data_full (G_OBJECT (job), "TempFilename", tmp_filename, g_free);
  g_vfs_afp_volume_create_file (volume, tmp_filename, FALSE,
                                G_VFS_JOB (job)->cancellable,
                                replace_create_tmp_file_cb, job);
}

static void
replace_get_filedir_parms_cb (GObject *source_object, GAsyncResult *res, gpointer user_data)
{
  GVfsAfpVolume *volume = G_VFS_AFP_VOLUME (source_object);
  GVfsJobOpenForWrite *job = G_VFS_JOB_OPEN_FOR_WRITE (user_data);
  GVfsBackendAfp *afp_backend = G_VFS_BACKEND_AFP (job->backend);

  GError *err = NULL;
  GFileInfo *info;

  info = g_vfs_afp_volume_get_filedir_parms_finish (volume, res, &err);
  if (!info)
  {
    /* Create file if it doesn't exist */
    if (g_error_matches (err, G_IO_ERROR, G_IO_ERROR_NOT_FOUND))
      try_create (G_VFS_BACKEND (afp_backend), job, job->filename, job->flags);

    else
      g_vfs_job_failed_from_error (G_VFS_JOB (job), err);

    g_error_free (err);
    return;
  }

  if (g_file_info_get_file_type (info) == G_FILE_TYPE_DIRECTORY)
  {
    g_vfs_job_failed_literal (G_VFS_JOB (job), G_IO_ERROR, G_IO_ERROR_IS_DIRECTORY,
                              _("File is directory"));
  }
  
  else if (job->etag && g_strcmp0 (g_file_info_get_etag (info), job->etag) != 0)
  {
    g_vfs_job_failed_literal (G_VFS_JOB (job), 
                              G_IO_ERROR, G_IO_ERROR_WRONG_ETAG,
                              _("The file was externally modified"));
  }
  else
  {
    if (g_vfs_afp_volume_get_attributes (volume) & AFP_VOLUME_ATTRIBUTES_BITMAP_NO_EXCHANGE_FILES)
    {
      /* FIXME: We don't support making backups when we can't use FPExchangeFiles */
      if (job->make_backup)
      {
        g_vfs_job_failed_literal (G_VFS_JOB (job), G_IO_ERROR,
                                  G_IO_ERROR_CANT_CREATE_BACKUP,
                                  _("backups not supported"));
      }
      else
      {
        g_vfs_afp_volume_open_fork (volume, job->filename,
                                    AFP_ACCESS_MODE_WRITE_BIT, 0,
                                    G_VFS_JOB (job)->cancellable, replace_open_fork_cb, job);
      }
    }
    else
      replace_create_tmp_file (volume, job);
  }

  g_object_unref (info);
}
  
static gboolean
try_replace (GVfsBackend *backend,
             GVfsJobOpenForWrite *job,
             const char *filename,
             const char *etag,
             gboolean make_backup,
             GFileCreateFlags flags)
{
  GVfsBackendAfp *afp_backend = G_VFS_BACKEND_AFP (backend);

  g_vfs_afp_volume_get_filedir_parms (afp_backend->volume, filename, 
                                      AFP_FILE_BITMAP_MOD_DATE_BIT, 0,
                                      G_VFS_JOB (job)->cancellable,
                                      replace_get_filedir_parms_cb, job);
  return TRUE;
}

static void
append_to_get_fork_parms_cb (GObject *source_object, GAsyncResult *res, gpointer user_data)
{
  GVfsAfpVolume *volume = G_VFS_AFP_VOLUME (source_object);
  GVfsJobOpenForWrite *job = G_VFS_JOB_OPEN_FOR_WRITE (user_data);
  AfpHandle *afp_handle = (AfpHandle *)job->backend_handle;

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

  info = g_vfs_afp_volume_get_fork_parms_finish (volume, res, &err);
  if (!info)
  {
    g_vfs_job_failed_from_error (G_VFS_JOB (job), err); 
    g_error_free (err);
    afp_handle_free (afp_handle);
    return;
  }

  size = g_file_info_get_size (info);
  g_object_unref (info);

  afp_handle->offset = size;
  g_vfs_job_open_for_write_set_initial_offset (job, size);
  g_vfs_job_open_for_write_set_can_seek (job, TRUE);

  g_vfs_job_succeeded (G_VFS_JOB (job));
}

static void
append_to_open_fork_cb (GObject *source_object, GAsyncResult *res, gpointer user_data)
{
  GVfsAfpVolume *volume = G_VFS_AFP_VOLUME (source_object);
  GVfsJobOpenForWrite *job = G_VFS_JOB_OPEN_FOR_WRITE (user_data);
  GVfsBackendAfp *afp_backend = G_VFS_BACKEND_AFP (job->backend);

  gint16 fork_refnum;
  GError *err = NULL;
  AfpHandle *afp_handle;

  if (!g_vfs_afp_volume_open_fork_finish (volume, res, &fork_refnum, NULL, &err))
  {
    /* Create file if it doesn't exist */
    if (g_error_matches (err, G_IO_ERROR, G_IO_ERROR_NOT_FOUND))
      try_create (G_VFS_BACKEND (afp_backend), job, job->filename, job->flags);
    
    else
      g_vfs_job_failed_from_error (G_VFS_JOB (job), err);
    
    g_error_free (err);
    return;
  }

  afp_handle = afp_handle_new (afp_backend, fork_refnum);
  afp_handle->type = AFP_HANDLE_TYPE_APPEND_TO_FILE;
  g_vfs_job_open_for_write_set_handle (job, (GVfsBackendHandle) afp_handle);
  
  g_vfs_afp_volume_get_fork_parms (afp_backend->volume, afp_handle->fork_refnum,
                                   AFP_FILE_BITMAP_EXT_DATA_FORK_LEN_BIT,
                                   G_VFS_JOB (job)->cancellable,
                                   append_to_get_fork_parms_cb, job);
}

static gboolean
try_append_to (GVfsBackend *backend,
               GVfsJobOpenForWrite *job,
               const char *filename,
               GFileCreateFlags flags)
{
  GVfsBackendAfp *afp_backend = G_VFS_BACKEND_AFP (backend);

  g_vfs_afp_volume_open_fork (afp_backend->volume, job->filename,
                              AFP_ACCESS_MODE_WRITE_BIT, 0,
                              G_VFS_JOB (job)->cancellable, append_to_open_fork_cb, job);
  return TRUE;
}

static void
read_open_fork_cb (GObject *source_object, GAsyncResult *res, gpointer user_data)
{
  GVfsAfpVolume *volume = G_VFS_AFP_VOLUME (source_object);
  GVfsJobOpenForRead *job = G_VFS_JOB_OPEN_FOR_READ (user_data);
  GVfsBackendAfp *afp_backend = G_VFS_BACKEND_AFP (job->backend);

  GError *err = NULL;
  gint16 fork_refnum;
  AfpHandle *afp_handle;
  
  if (!g_vfs_afp_volume_open_fork_finish (volume, res, &fork_refnum, NULL, &err))
  {
    g_vfs_job_failed_from_error (G_VFS_JOB (job), err);
    g_error_free (err);
    return;
  }

  afp_handle = afp_handle_new (afp_backend, fork_refnum);
  afp_handle->type = AFP_HANDLE_TYPE_READ_FILE;
  
  g_vfs_job_open_for_read_set_handle (job, (GVfsBackendHandle) afp_handle);
  g_vfs_job_open_for_read_set_can_seek (job, TRUE);

  g_vfs_job_succeeded (G_VFS_JOB (job));
}

static gboolean
try_open_for_read (GVfsBackend *backend,
                   GVfsJobOpenForRead *job,
                   const char *filename)
{
  GVfsBackendAfp *afp_backend = G_VFS_BACKEND_AFP (backend);

  g_vfs_afp_volume_open_fork (afp_backend->volume, filename,
                              AFP_ACCESS_MODE_READ_BIT, 0,
                              G_VFS_JOB (job)->cancellable, read_open_fork_cb, job);
  return TRUE;
}

static guint16
create_filedir_bitmap (GVfsBackendAfp *afp_backend, GFileAttributeMatcher *matcher)
{
  guint16 bitmap;

  bitmap = AFP_FILEDIR_BITMAP_ATTRIBUTE_BIT | AFP_FILEDIR_BITMAP_UTF8_NAME_BIT;

  if (g_file_attribute_matcher_matches (matcher, G_FILE_ATTRIBUTE_TIME_CREATED))
    bitmap |= AFP_FILEDIR_BITMAP_CREATE_DATE_BIT;
  
  if (g_file_attribute_matcher_matches (matcher, G_FILE_ATTRIBUTE_TIME_MODIFIED) ||
      g_file_attribute_matcher_matches (matcher, G_FILE_ATTRIBUTE_ETAG_VALUE))
    bitmap |= AFP_FILEDIR_BITMAP_MOD_DATE_BIT;

  if (g_file_attribute_matcher_matches (matcher, G_FILE_ATTRIBUTE_UNIX_MODE) ||
      g_file_attribute_matcher_matches (matcher, G_FILE_ATTRIBUTE_UNIX_UID) ||
      g_file_attribute_matcher_matches (matcher, G_FILE_ATTRIBUTE_UNIX_GID) ||
      g_file_attribute_matcher_matches (matcher, G_FILE_ATTRIBUTE_ACCESS_CAN_READ) ||
      g_file_attribute_matcher_matches (matcher, G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE) ||
      g_file_attribute_matcher_matches (matcher, G_FILE_ATTRIBUTE_ACCESS_CAN_EXECUTE)||
      g_file_attribute_matcher_matches (matcher, G_FILE_ATTRIBUTE_OWNER_USER) ||
      g_file_attribute_matcher_matches (matcher, G_FILE_ATTRIBUTE_OWNER_USER_REAL) ||
      g_file_attribute_matcher_matches (matcher, G_FILE_ATTRIBUTE_OWNER_GROUP))
      
  {
    if (g_vfs_afp_volume_get_attributes (afp_backend->volume) & AFP_VOLUME_ATTRIBUTES_BITMAP_SUPPORTS_UNIX_PRIVS)
      bitmap |= AFP_FILEDIR_BITMAP_UNIX_PRIVS_BIT;
  }
      
  return bitmap;
}

static guint16
create_file_bitmap (GVfsBackendAfp *afp_backend, GFileAttributeMatcher *matcher)
{
  guint16 file_bitmap;
  
  file_bitmap = create_filedir_bitmap (afp_backend, matcher);

  if (g_file_attribute_matcher_matches (matcher, G_FILE_ATTRIBUTE_STANDARD_SIZE))
    file_bitmap |= AFP_FILE_BITMAP_EXT_DATA_FORK_LEN_BIT;
  
  return file_bitmap;
}

static guint16
create_dir_bitmap (GVfsBackendAfp *afp_backend, GFileAttributeMatcher *matcher)
{
  guint16 dir_bitmap;
  
  dir_bitmap = create_filedir_bitmap (afp_backend, matcher);

  if (g_file_attribute_matcher_matches (matcher, G_FILE_ATTRIBUTE_AFP_CHILDREN_COUNT))
    dir_bitmap |= AFP_DIR_BITMAP_OFFSPRING_COUNT_BIT;
  
  return dir_bitmap;
}

static void
enumerate (GVfsBackendAfp *afp_backend,
           GVfsJobEnumerate *job,
           gint32 start_index);

static void
enumerate_cb (GObject *source_object, GAsyncResult *res, gpointer user_data)
{
  GVfsAfpVolume *volume = G_VFS_AFP_VOLUME (source_object);
  GVfsJobEnumerate *job = G_VFS_JOB_ENUMERATE (user_data);
  GVfsBackendAfp *afp_backend = G_VFS_BACKEND_AFP (job->backend);

  GPtrArray *infos;
  GError *err = NULL;

  guint i;
  gint64 start_index;

  
  if (!g_vfs_afp_volume_enumerate_finish (volume, res, &infos, &err))
  {
    g_vfs_job_failed_from_error (G_VFS_JOB (job), err);
    g_error_free (err);
    return;
  }

  /* No more files */
  if (!infos)
  {
    g_vfs_job_succeeded (G_VFS_JOB (job));
    g_vfs_job_enumerate_done (job);
    return;
  }

  for (i = 0; i < infos->len; i++)
    g_vfs_job_enumerate_add_info (job, g_ptr_array_index (infos, i));
  
  start_index = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (job),
                                                    "start-index"));
  start_index += infos->len;
  g_ptr_array_unref (infos);

  enumerate (afp_backend, job, start_index);
}

static void
enumerate (GVfsBackendAfp *afp_backend,
           GVfsJobEnumerate *job,
           gint32 start_index)
{
  const char *filename = job->filename;
  GFileAttributeMatcher *matcher = job->attribute_matcher;
  
  guint16 file_bitmap, dir_bitmap;
  
  g_object_set_data (G_OBJECT (job), "start-index",
                     GINT_TO_POINTER (start_index));

  file_bitmap = create_file_bitmap (afp_backend, matcher);
  dir_bitmap = create_dir_bitmap (afp_backend, matcher);
  
  g_vfs_afp_volume_enumerate (afp_backend->volume, filename, start_index,
                              file_bitmap, dir_bitmap,
                              G_VFS_JOB (job)->cancellable, enumerate_cb, job);
}

static gboolean
try_enumerate (GVfsBackend *backend,
               GVfsJobEnumerate *job,
               const char *filename,
               GFileAttributeMatcher *matcher,
               GFileQueryInfoFlags flags)
{
  GVfsBackendAfp *afp_backend = G_VFS_BACKEND_AFP (backend);

  enumerate (afp_backend, job, 1);
  
  return TRUE;
}

static gboolean
try_query_settable_attributes (GVfsBackend *backend,
                               GVfsJobQueryAttributes *job,
                               const char *filename)
{
  GVfsBackendAfp *afp_backend = G_VFS_BACKEND_AFP (backend);
  GFileAttributeInfoList *list;

  list = g_file_attribute_info_list_new ();

  if (g_vfs_afp_volume_get_attributes (afp_backend->volume) & AFP_VOLUME_ATTRIBUTES_BITMAP_SUPPORTS_UNIX_PRIVS)
  {
    g_file_attribute_info_list_add (list,
                                    G_FILE_ATTRIBUTE_UNIX_MODE,
                                    G_FILE_ATTRIBUTE_TYPE_UINT32,
                                    G_FILE_ATTRIBUTE_INFO_COPY_WITH_FILE |
                                    G_FILE_ATTRIBUTE_INFO_COPY_WHEN_MOVED);
    g_file_attribute_info_list_add (list,
                                    G_FILE_ATTRIBUTE_UNIX_UID,
                                    G_FILE_ATTRIBUTE_TYPE_UINT32,
                                    G_FILE_ATTRIBUTE_INFO_COPY_WITH_FILE |
                                    G_FILE_ATTRIBUTE_INFO_COPY_WHEN_MOVED);
    g_file_attribute_info_list_add (list,
                                    G_FILE_ATTRIBUTE_UNIX_GID,
                                    G_FILE_ATTRIBUTE_TYPE_UINT32,
                                    G_FILE_ATTRIBUTE_INFO_COPY_WITH_FILE |
                                    G_FILE_ATTRIBUTE_INFO_COPY_WHEN_MOVED);
  }
    
  g_vfs_job_query_attributes_set_list (job, list);
  g_vfs_job_succeeded (G_VFS_JOB (job));
  g_file_attribute_info_list_unref (list);
  
  return TRUE;
}

static void
set_attribute_set_unix_privs_cb (GObject *source_object, GAsyncResult *res, gpointer user_data)
{
  GVfsAfpVolume *volume = G_VFS_AFP_VOLUME (source_object);
  GVfsJobSetAttribute *job = G_VFS_JOB_SET_ATTRIBUTE (user_data);

  GError *err = NULL;

  if (!g_vfs_afp_volume_set_unix_privs_finish (volume, res, &err))
  {
    g_vfs_job_failed_from_error (G_VFS_JOB (job), err);
    g_error_free (err);
    return;
  }

  g_vfs_job_succeeded (G_VFS_JOB (job));
}

static void
set_attribute_get_filedir_parms_cb (GObject *source_object, GAsyncResult *res, gpointer user_data)
{
  GVfsAfpVolume *volume = G_VFS_AFP_VOLUME (source_object);
  GVfsJobSetAttribute *job = G_VFS_JOB_SET_ATTRIBUTE (user_data);

  GFileInfo *info;
  GError *err = NULL;

  guint32 uid, gid, permissions, ua_permissions;

  info = g_vfs_afp_volume_get_filedir_parms_finish (volume, res, &err);
  if (!info)
  {
    g_vfs_job_failed_from_error (G_VFS_JOB (job), err);
    g_error_free (err);
    return;
  }

  uid = g_file_info_get_attribute_uint32 (info, G_FILE_ATTRIBUTE_UNIX_UID);
  gid = g_file_info_get_attribute_uint32 (info, G_FILE_ATTRIBUTE_UNIX_GID);
  permissions = g_file_info_get_attribute_uint32 (info, G_FILE_ATTRIBUTE_UNIX_MODE);
  ua_permissions = g_file_info_get_attribute_uint32 (info, G_FILE_ATTRIBUTE_AFP_UA_PERMISSIONS);

  g_object_unref (info);

  
  if (strcmp (job->attribute, G_FILE_ATTRIBUTE_UNIX_UID) == 0)
    uid = job->value.uint32;
  else if (strcmp (job->attribute, G_FILE_ATTRIBUTE_UNIX_GID) == 0)
    gid = job->value.uint32;
  else if (strcmp (job->attribute, G_FILE_ATTRIBUTE_UNIX_MODE) == 0)
    permissions = job->value.uint32;

  g_vfs_afp_volume_set_unix_privs (volume, job->filename,
                                   uid, gid, permissions, ua_permissions,
                                   G_VFS_JOB (job)->cancellable,
                                   set_attribute_set_unix_privs_cb, job);
}

static gboolean
try_set_attribute (GVfsBackend *backend,
                   GVfsJobSetAttribute *job,
                   const char *filename,
                   const char *attribute,
                   GFileAttributeType type,
                   gpointer value_p,
                   GFileQueryInfoFlags flags)
{
  GVfsBackendAfp *afp_backend = G_VFS_BACKEND_AFP (backend);

  if ((strcmp (attribute, G_FILE_ATTRIBUTE_UNIX_MODE) == 0 ||
       strcmp (attribute, G_FILE_ATTRIBUTE_UNIX_UID) == 0 ||
       strcmp (attribute, G_FILE_ATTRIBUTE_UNIX_GID) == 0)
      && g_vfs_afp_volume_get_attributes (afp_backend->volume) & AFP_VOLUME_ATTRIBUTES_BITMAP_SUPPORTS_UNIX_PRIVS)
    {
      if (type != G_FILE_ATTRIBUTE_TYPE_UINT32) 
      {
        g_vfs_job_failed (G_VFS_JOB (job),
                          G_IO_ERROR,
                          G_IO_ERROR_INVALID_ARGUMENT,
                          "%s",
                          _("Invalid attribute type (uint32 expected)"));
        return TRUE;
      }

      g_vfs_afp_volume_get_filedir_parms (afp_backend->volume, filename,
                                          AFP_FILEDIR_BITMAP_UNIX_PRIVS_BIT,
                                          AFP_FILEDIR_BITMAP_UNIX_PRIVS_BIT,
                                          G_VFS_JOB (job)->cancellable,
                                          set_attribute_get_filedir_parms_cb,
                                          job);
      return TRUE;
    }

  else {
    g_vfs_job_failed (G_VFS_JOB (job),
                      G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
                      _("Operation unsupported"));
    return TRUE;
  }
}


static void
query_fs_info_get_vol_parms_cb (GObject *source_object, GAsyncResult *res, gpointer user_data)
{
  GVfsAfpVolume *volume = G_VFS_AFP_VOLUME (source_object);
  GVfsJobQueryFsInfo *job = G_VFS_JOB_QUERY_FS_INFO (user_data);

  GError *err = NULL;
  GFileInfo *info;

  info = g_vfs_afp_volume_get_parms_finish (volume, res, &err);
  if (!info)
  {
    g_vfs_job_failed_from_error (G_VFS_JOB (job), err);
    g_error_free (err);
    return;
  }

  copy_file_info_into (info, job->file_info);
  g_object_unref (info);


  g_vfs_job_succeeded (G_VFS_JOB (job));
}

static gboolean
try_query_fs_info (GVfsBackend *backend,
                   GVfsJobQueryFsInfo *job,
                   const char *filename,
                   GFileInfo *info,
                   GFileAttributeMatcher *matcher)
{
  GVfsBackendAfp *afp_backend = G_VFS_BACKEND_AFP (backend);
  
  guint16 vol_bitmap = 0;

  g_file_info_set_attribute_string (info, G_FILE_ATTRIBUTE_FILESYSTEM_TYPE, "afp");

  
  if (g_file_attribute_matcher_matches (matcher, G_FILE_ATTRIBUTE_FILESYSTEM_SIZE))
    vol_bitmap |= AFP_VOLUME_BITMAP_EXT_BYTES_TOTAL_BIT;
  
  if (g_file_attribute_matcher_matches (matcher, G_FILE_ATTRIBUTE_FILESYSTEM_FREE))
    vol_bitmap |= AFP_VOLUME_BITMAP_EXT_BYTES_FREE_BIT;

  if (g_file_attribute_matcher_matches (matcher, G_FILE_ATTRIBUTE_FILESYSTEM_READONLY))
    vol_bitmap |= AFP_VOLUME_BITMAP_ATTRIBUTE_BIT;

  if (vol_bitmap != 0)
  {
    g_vfs_afp_volume_get_parms (afp_backend->volume, vol_bitmap,
                                G_VFS_JOB (job)->cancellable,
                                query_fs_info_get_vol_parms_cb, job);
  }
  else
    g_vfs_job_succeeded (G_VFS_JOB (job));

  return TRUE;
}

static void
get_name_cb (GObject *source_object, GAsyncResult *res, gpointer user_data)
{
  GVfsAfpVolume *volume = G_VFS_AFP_VOLUME (source_object);
  GVfsJobQueryInfo *job = G_VFS_JOB_QUERY_INFO (user_data);

  char *name;
  AfpMapIDFunction map_function;
  guint outstanding_requests;

  name = g_vfs_afp_volume_map_id_finish (volume, res, &map_function, NULL);
  if (name)
  {
    switch (map_function)
    {
      case AFP_MAP_ID_FUNCTION_USER_ID_TO_NAME:
        g_file_info_set_attribute_string (job->file_info, G_FILE_ATTRIBUTE_OWNER_USER,
                                          name);
        break;
      case AFP_MAP_ID_FUNCTION_USER_ID_TO_UTF8_NAME:
        g_file_info_set_attribute_string (job->file_info, G_FILE_ATTRIBUTE_OWNER_USER_REAL,
                                          name);
        break;
      case AFP_MAP_ID_FUNCTION_GROUP_ID_TO_NAME:
        g_file_info_set_attribute_string (job->file_info, G_FILE_ATTRIBUTE_OWNER_GROUP,
                                          name);
        break;

      default:
        g_assert_not_reached ();
    }

    g_free (name);
  }

  outstanding_requests = GPOINTER_TO_UINT (G_VFS_JOB (job)->backend_data);
  if (--outstanding_requests == 0)
    g_vfs_job_succeeded (G_VFS_JOB (job));
  else
    G_VFS_JOB (job)->backend_data = GUINT_TO_POINTER (outstanding_requests);
}

static void
set_root_info (GVfsBackendAfp *afp_backend, GFileInfo *info)
{
  GIcon *icon;
  
  g_file_info_set_file_type (info, G_FILE_TYPE_DIRECTORY);
  g_file_info_set_name (info, "/");
  g_file_info_set_display_name (info,
                                g_vfs_backend_get_display_name (G_VFS_BACKEND (afp_backend)));
  g_file_info_set_content_type (info, "inode/directory");
  icon = g_vfs_backend_get_icon (G_VFS_BACKEND (afp_backend));
  if (icon != NULL)
    g_file_info_set_icon (info, icon);
}

static void
query_info_get_filedir_parms_cb (GObject *source_object, GAsyncResult *res, gpointer user_data)
{
  GVfsAfpVolume *volume = G_VFS_AFP_VOLUME (source_object);
  GVfsJobQueryInfo *job = G_VFS_JOB_QUERY_INFO (user_data);
  GVfsBackendAfp *afp_backend = G_VFS_BACKEND_AFP (job->backend);
  
  GFileInfo *info;
  GError *err = NULL;

  GFileAttributeMatcher *matcher;
  guint outstanding_requests;
  
  info = g_vfs_afp_volume_get_filedir_parms_finish (volume, res, &err);
  if (!info)
  {
    g_vfs_job_failed_from_error (G_VFS_JOB (job), err);
    g_error_free (err);
    return;
  }

  outstanding_requests = 0;
  matcher = job->attribute_matcher;
  
  if (g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_UNIX_UID))  
  {
    guint32 uid;

    uid = g_file_info_get_attribute_uint32 (info, G_FILE_ATTRIBUTE_UNIX_UID);

    if (g_file_attribute_matcher_matches (matcher, G_FILE_ATTRIBUTE_OWNER_USER))
    {
      g_vfs_afp_volume_map_id (volume,
                               AFP_MAP_ID_FUNCTION_USER_ID_TO_NAME, uid,
                               G_VFS_JOB (job)->cancellable, get_name_cb, job);
      outstanding_requests++;
    }
    
    if (g_file_attribute_matcher_matches (matcher, G_FILE_ATTRIBUTE_OWNER_USER_REAL))
    {
      g_vfs_afp_volume_map_id (volume,
                               AFP_MAP_ID_FUNCTION_USER_ID_TO_UTF8_NAME, uid,
                               G_VFS_JOB (job)->cancellable, get_name_cb, job);
      outstanding_requests++;
    }
  }

  if (g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_UNIX_GID) &&
      g_file_attribute_matcher_matches (matcher, G_FILE_ATTRIBUTE_OWNER_GROUP))  
  {
    guint32 gid;

    gid = g_file_info_get_attribute_uint32 (info, G_FILE_ATTRIBUTE_UNIX_GID);

    g_vfs_afp_volume_map_id (volume,
                             AFP_MAP_ID_FUNCTION_GROUP_ID_TO_NAME, gid,
                             G_VFS_JOB (job)->cancellable, get_name_cb, job);
    outstanding_requests++;
  }
  
  G_VFS_JOB (job)->backend_data = GUINT_TO_POINTER (outstanding_requests);
  
  copy_file_info_into (info, job->file_info);
  g_object_unref (info);

  if (is_root (job->filename))
    set_root_info (afp_backend, job->file_info);
  
  if (outstanding_requests == 0)
    g_vfs_job_succeeded (G_VFS_JOB (job));
}

static gboolean
try_query_info (GVfsBackend *backend,
                GVfsJobQueryInfo *job,
                const char *filename,
                GFileQueryInfoFlags flags,
                GFileInfo *info,
                GFileAttributeMatcher *matcher)
{
  GVfsBackendAfp *afp_backend = G_VFS_BACKEND_AFP (backend);

  g_debug ("Filename: %s\n", filename);
  
  if (is_root (filename))
  {
    guint16 dir_bitmap = 0;

    dir_bitmap = create_dir_bitmap (afp_backend, matcher);
    dir_bitmap &= ~AFP_DIR_BITMAP_UTF8_NAME_BIT;
    
    if (dir_bitmap != 0)
    {
      g_vfs_afp_volume_get_filedir_parms (afp_backend->volume, filename,
                                          0, dir_bitmap,
                                          G_VFS_JOB (job)->cancellable,
                                          query_info_get_filedir_parms_cb, job);
    }
    else
    {
      set_root_info (afp_backend, info);
      g_vfs_job_succeeded (G_VFS_JOB (job));
    }
  }
  
  else {
    guint16 file_bitmap, dir_bitmap;
    
    file_bitmap = create_file_bitmap (afp_backend, matcher);
    dir_bitmap = create_dir_bitmap (afp_backend, matcher);

    g_vfs_afp_volume_get_filedir_parms (afp_backend->volume, filename,
                                        file_bitmap, dir_bitmap,
                                        G_VFS_JOB (job)->cancellable,
                                        query_info_get_filedir_parms_cb, job);
  }

  return TRUE;
}

static void
do_mount (GVfsBackend *backend,
          GVfsJobMount *job,
          GMountSpec *mount_spec,
          GMountSource *mount_source,
          gboolean is_automount)
{
  GVfsBackendAfp *afp_backend = G_VFS_BACKEND_AFP (backend);

  gboolean res;
  GError *err = NULL;
  
  GMountSpec *afp_mount_spec;
  char       *server_name;
  char       *display_name;

  afp_backend->server = g_vfs_afp_server_new (afp_backend->addr);

  res = g_vfs_afp_server_login (afp_backend->server, afp_backend->user, mount_source,
                                NULL, G_VFS_JOB (job)->cancellable, &err);
  if (!res)
    goto error;

  afp_backend->volume =
    g_vfs_afp_server_mount_volume_sync (afp_backend->server, afp_backend->volume_name,
                                        G_VFS_JOB (job)->cancellable, &err);
  if (!afp_backend->volume)
    goto error;
  
  /* set mount info */
  afp_mount_spec = g_mount_spec_new ("afp-volume");
  g_mount_spec_set (afp_mount_spec, "host",
                    g_network_address_get_hostname (G_NETWORK_ADDRESS (afp_backend->addr)));
  g_mount_spec_set (afp_mount_spec, "volume", afp_backend->volume_name);
  if (afp_backend->user)
    g_mount_spec_set (afp_mount_spec, "user", afp_backend->user);

  g_vfs_backend_set_mount_spec (backend, afp_mount_spec);
  g_mount_spec_unref (afp_mount_spec);

  if (afp_backend->server->utf8_server_name)
    server_name = afp_backend->server->utf8_server_name;
  else
    server_name = afp_backend->server->server_name;
  
  if (afp_backend->user)
    /* Translators: first %s is volumename, second username and third servername */ 
    display_name = g_strdup_printf (_("AFP volume %s for %s on %s"), 
                                    afp_backend->volume_name, afp_backend->user,
                                    server_name);
  else
    /* Translators: first %s is volumename and second servername */
    display_name = g_strdup_printf (_("AFP volume %s on %s"),
                                    afp_backend->volume_name, server_name);
  
  g_vfs_backend_set_display_name (backend, display_name);
  g_free (display_name);

  g_vfs_backend_set_icon_name (backend, "folder-remote-afp");
  g_vfs_backend_set_user_visible (backend, TRUE);

  g_vfs_job_succeeded (G_VFS_JOB (job));
  return;

error:
  g_vfs_job_failed_from_error (G_VFS_JOB (job), err);
  return;
}
  
static gboolean
try_mount (GVfsBackend *backend,
           GVfsJobMount *job,
           GMountSpec *mount_spec,
           GMountSource *mount_source,
           gboolean is_automount)
{
  GVfsBackendAfp *afp_backend = G_VFS_BACKEND_AFP (backend);

  const char *host, *volume, *portstr, *user;
  guint16 port = 548;

  host = g_mount_spec_get (mount_spec, "host");
  if (host == NULL)
  {
    g_vfs_job_failed (G_VFS_JOB (job),
                      G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT,
                      _("No hostname specified"));
    return TRUE;
  }

  volume = g_mount_spec_get (mount_spec, "volume");
  if (volume == NULL)
  {
    g_vfs_job_failed (G_VFS_JOB (job),
                      G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT,
                      _("No volume specified"));
    return TRUE;
  }
  afp_backend->volume_name = g_strdup (volume);

  portstr = g_mount_spec_get (mount_spec, "port");
  if (portstr != NULL)
  {
    port = atoi (portstr);
  }

  afp_backend->addr = G_NETWORK_ADDRESS (g_network_address_new (host, port));

  user = g_mount_spec_get (mount_spec, "user");
  afp_backend->user = g_strdup (user);

  return FALSE;
}

static void
g_vfs_backend_afp_init (GVfsBackendAfp *object)
{
  GVfsBackendAfp *afp_backend = G_VFS_BACKEND_AFP (object);
  
  afp_backend->volume_name = NULL;
  afp_backend->user = NULL;

  afp_backend->addr = NULL;
}

static void
g_vfs_backend_afp_finalize (GObject *object)
{
  GVfsBackendAfp *afp_backend = G_VFS_BACKEND_AFP (object);

  g_free (afp_backend->user);

  if (afp_backend->volume)
    g_object_unref (afp_backend->volume);
    
  if (afp_backend->addr)
    g_object_unref (afp_backend->addr);

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

static void
g_vfs_backend_afp_class_init (GVfsBackendAfpClass *klass)
{
  GObjectClass *object_class = G_OBJECT_CLASS (klass);
  GVfsBackendClass *backend_class = G_VFS_BACKEND_CLASS (klass);

  object_class->finalize = g_vfs_backend_afp_finalize;

  backend_class->try_mount = try_mount;
  backend_class->mount = do_mount;
  backend_class->try_query_info = try_query_info;
  backend_class->try_query_fs_info = try_query_fs_info;
  backend_class->try_set_attribute = try_set_attribute;
  backend_class->try_query_settable_attributes = try_query_settable_attributes;
  backend_class->try_enumerate = try_enumerate;
  backend_class->try_open_for_read = try_open_for_read;
  backend_class->try_close_read = try_close_read;
  backend_class->try_read = try_read;
  backend_class->try_seek_on_read = try_seek_on_read;
  backend_class->try_append_to = try_append_to;
  backend_class->try_create = try_create;
  backend_class->try_replace = try_replace;
  backend_class->try_write = try_write;
  backend_class->try_seek_on_write = try_seek_on_write;
  backend_class->try_close_write = try_close_write;
  backend_class->try_delete = try_delete;
  backend_class->try_make_directory = try_make_directory;
  backend_class->try_set_display_name = try_set_display_name;
  backend_class->try_move = try_move;
  backend_class->try_copy = try_copy;
}

void
g_vfs_afp_daemon_init (void)
{
  g_set_application_name (_("Apple Filing Protocol Service"));

#ifdef HAVE_GCRYPT
  gcry_check_version (NULL);
  gcry_control (GCRYCTL_INITIALIZATION_FINISHED, 0);
#endif
}
