/* GIO - GLib Input, Output and Streaming Library
 * 
 * Copyright (C) 2006-2007 Red Hat, Inc.
 *
 * 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: Alexander Larsson <alexl@redhat.com>
 */


#include <config.h>

#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <errno.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <stdlib.h>

#include <glib.h>
#include <glib/gstdio.h>
#include <glib/gi18n.h>
#include <gio/gio.h>
#include <gio/gunixmounts.h>

#include "gvfsbackendburn.h"
#include "gvfsmonitor.h"
#include "gvfsjobopenforread.h"
#include "gvfsjobread.h"
#include "gvfsjobseekread.h"
#include "gvfsjobopenforwrite.h"
#include "gvfsjobwrite.h"
#include "gvfsjobclosewrite.h"
#include "gvfsjobseekwrite.h"
#include "gvfsjobsetdisplayname.h"
#include "gvfsjobmountmountable.h"
#include "gvfsjobqueryinfo.h"
#include "gvfsjobdelete.h"
#include "gvfsjobqueryfsinfo.h"
#include "gvfsjobqueryattributes.h"
#include "gvfsjobenumerate.h"
#include "gvfsjobcreatemonitor.h"
#include "gvfsdaemonprotocol.h"

/* TODO:
 * Change notification
 * 
 */

typedef enum {
	VIRTUAL_NODE_FILE,
	VIRTUAL_NODE_DIRECTORY
} VirtualNodeType;

typedef struct {
	char           *filename;
	VirtualNodeType type;

	/* for files: */
	char           *backing_file; /* local filename */
	gboolean        owned_file;

	GList          *subscriptions;

	/* for directories: */
	GList          *children;
	volatile gint   ref_count;
} VirtualNode;

struct _GVfsBackendBurn
{
  GVfsBackend parent_instance;

  char        *tempdir;
  
  VirtualNode *root_node;
  
  GMountSpec *mount_spec;
};

G_DEFINE_TYPE (GVfsBackendBurn, g_vfs_backend_burn, G_VFS_TYPE_BACKEND)


static void virtual_node_unref (VirtualNode *node);

static VirtualNode *
virtual_node_new (const char     *filename,
                  VirtualNodeType type)
{
  VirtualNode *node;
  
  node = g_slice_new0 (VirtualNode);
  node->filename = g_strdup (filename);
  node->type = type;
  node->ref_count = 1;
  
  return node;
}

static void
virtual_node_free (VirtualNode *node,
                   gboolean     deep)
{
  GList *l;
  
  g_free (node->filename);
  
  switch (node->type)
    {
    case VIRTUAL_NODE_FILE:
      if (node->backing_file != NULL)
        {
          if (node->owned_file)
            g_unlink (node->backing_file);
          g_free (node->backing_file);
        }
      break;
    case VIRTUAL_NODE_DIRECTORY:
      if (deep)
        {
          for (l = node->children; l != NULL; l = l->next)
            virtual_node_unref ((VirtualNode *)l->data);
        }
      break;
    default:
      g_assert_not_reached ();
      break;
    }

  /* 
  while ((l = g_list_first (node->subscriptions)) != NULL) {
    MappingSubscription *sub = l->data;
    virtual_node_remove_subscription (node, sub);
  }
  */
  
  g_slice_free (VirtualNode, node);
}

static void
virtual_node_unref (VirtualNode *node)
{
  g_return_if_fail (node != NULL);
  g_return_if_fail (node->ref_count > 0);
  
  if (g_atomic_int_dec_and_test (&node->ref_count))
    virtual_node_free (node, TRUE);
}

static G_GNUC_UNUSED VirtualNode *
virtual_node_ref (VirtualNode *node)
{
  g_return_val_if_fail (node != NULL, NULL);
  g_return_val_if_fail (node->ref_count > 0, NULL);
  
  g_atomic_int_inc (&node->ref_count);
  return node;
}


static VirtualNode *
virtual_dir_lookup (VirtualNode *dir,
                    const char  *filename)
{
  GList       *l;
  VirtualNode *node;
  
  g_assert (dir->type == VIRTUAL_NODE_DIRECTORY);
  
  for (l = dir->children; l != NULL; l = l->next)
    {
      node = l->data;
      
      if (strcmp (node->filename, filename) == 0)
        return node;
    }
  
  return NULL;
}

static VirtualNode *
virtual_node_lookup (VirtualNode  *root_dir,
                     const char   *path,
                     VirtualNode **parent)
{
  char        *copy, *next, *copy_orig;
  VirtualNode *node;
  
  copy_orig = g_strdup (path);
  copy = copy_orig;
  
  if (parent != NULL) 
    *parent = NULL;
  
  node = root_dir;
  
  while (copy != NULL)
    {
      /* Skip initial/multiple slashes */
      while (G_IS_DIR_SEPARATOR (*copy))
        ++copy;
      
      if (*copy == 0)
        break;
      
      next = strchr (copy, G_DIR_SEPARATOR);
      if (next)
        {
          *next = 0;
          next++;
        }
      
      if (node->type != VIRTUAL_NODE_DIRECTORY)
        {
          /* Found a file in the middle of the path */
          node = NULL;
          break;
        }
      
      if (parent != NULL) 
        *parent = node;
      
      node = virtual_dir_lookup (node, copy);
      if (node == NULL) 
        break;
      
      copy = next;
    }
  
  g_free (copy_orig);
  
  return node;
}

static VirtualNode *
virtual_mkdir (VirtualNode *node,
               const char  *name)
{
  VirtualNode *subdir;
  
  g_assert (node->type == VIRTUAL_NODE_DIRECTORY);
  
  if (virtual_dir_lookup (node, name) != NULL)
    return NULL;
  
  subdir = virtual_node_new (name, VIRTUAL_NODE_DIRECTORY);
  
  /* list takes ownership of ref */
  node->children = g_list_append (node->children, subdir);
  
  return subdir;
}

static void
virtual_unlink (VirtualNode *dir,
                VirtualNode *node)
{
  g_assert (dir->type == VIRTUAL_NODE_DIRECTORY);
  
  dir->children = g_list_remove (dir->children, node);
  virtual_node_unref (node);
}

static VirtualNode *
virtual_create (GVfsBackendBurn *backend,
                VirtualNode *dir,
                const char  *name,
                const char  *backing_file)
{
  VirtualNode *file;
  char        *template;
  int          fd;
  
  g_assert (dir->type == VIRTUAL_NODE_DIRECTORY);
  
  if (virtual_dir_lookup (dir, name) != NULL)
    return NULL;
  
  file = virtual_node_new (name, VIRTUAL_NODE_FILE);
  
  if (backing_file != NULL)
    {
      file->backing_file = g_strdup (backing_file);
      file->owned_file = FALSE;
    }
  else
    {
      template = g_build_filename (backend->tempdir, "file.XXXXXX", NULL);
      
      fd = g_mkstemp (template);
      if (fd < 0)
        {
          g_free (template);
          virtual_node_unref (file);
          return NULL;
          
        }
      close (fd);
      g_unlink (template);
      
      file->backing_file = template;
      file->owned_file = TRUE;
    }
  
  /* list takes ownership of ref */
  dir->children = g_list_append (dir->children, file);
  
  return file;
}

static void
g_vfs_backend_burn_finalize (GObject *object)
{
  GVfsBackendBurn *backend;

  backend = G_VFS_BACKEND_BURN (object);

  g_mount_spec_unref (backend->mount_spec);

  if (G_OBJECT_CLASS (g_vfs_backend_burn_parent_class)->finalize)
    (*G_OBJECT_CLASS (g_vfs_backend_burn_parent_class)->finalize) (object);
}

static void
g_vfs_backend_burn_init (GVfsBackendBurn *burn_backend)
{
  GVfsBackend *backend = G_VFS_BACKEND (burn_backend);
  GMountSpec *mount_spec;
  
  /* Translators: This is the name of the backend */
  g_vfs_backend_set_display_name (backend, _("Burn"));
  g_vfs_backend_set_icon_name (backend, "computer");
  g_vfs_backend_set_symbolic_icon_name (backend, "computer-symbolic");
  g_vfs_backend_set_user_visible (backend, FALSE);

  mount_spec = g_mount_spec_new ("burn");
  g_vfs_backend_set_mount_spec (backend, mount_spec);
  burn_backend->mount_spec = mount_spec;
}

static gboolean
try_mount (GVfsBackend *backend,
           GVfsJobMount *job,
           GMountSpec *mount_spec,
           GMountSource *mount_source,
           gboolean is_automount)
{
  GVfsBackendBurn *burn_backend = G_VFS_BACKEND_BURN (backend);
  char        *filename;

  filename = g_build_filename (g_get_user_runtime_dir (), "gvfs-burn", NULL);
  if (g_mkdir_with_parents (filename, 0700) < 0)
    {
      g_free (filename);
      g_vfs_job_failed (G_VFS_JOB (job),
                        G_IO_ERROR, G_IO_ERROR_FAILED,
                        _("Unable to create temporary directory"));
      return TRUE;
    }

  burn_backend->tempdir = filename;
  burn_backend->root_node =
    virtual_node_new (NULL, VIRTUAL_NODE_DIRECTORY);
  
  g_vfs_job_succeeded (G_VFS_JOB (job));
  return TRUE;
}

static gboolean
try_delete (GVfsBackend *backend,
            GVfsJobDelete *job,
            const char *filename)
{
  char *dirname, *basename;
  VirtualNode *file, *dir;

  dirname = g_path_get_dirname (filename);
  dir = virtual_node_lookup (G_VFS_BACKEND_BURN (backend)->root_node, dirname, NULL);
  g_free (dirname);

  if (dir == NULL ||
      dir->type != VIRTUAL_NODE_DIRECTORY)
    {
      g_vfs_job_failed (G_VFS_JOB (job), G_IO_ERROR,
                        G_IO_ERROR_NOT_FOUND,
                        _("No such file or directory"));
      return TRUE;
    }

  basename = g_path_get_basename (filename);
  file = virtual_dir_lookup (dir, basename);
  g_free (basename);
  if (file == NULL)
    {
      g_vfs_job_failed (G_VFS_JOB (job), G_IO_ERROR,
                        G_IO_ERROR_NOT_FOUND,
                        _("No such file or directory"));
      return TRUE;
    }
  
  if (file->type == VIRTUAL_NODE_DIRECTORY &&
      file->children != NULL)
    {
      g_vfs_job_failed (G_VFS_JOB (job), G_IO_ERROR,
                        G_IO_ERROR_NOT_EMPTY,
                        _("Directory not empty"));
      return TRUE;
    }
  
  virtual_unlink (dir, file);
  
  g_vfs_job_succeeded (G_VFS_JOB (job));
  return TRUE;
}


static gboolean
try_open_for_read (GVfsBackend *backend,
                   GVfsJobOpenForRead *job,
                   const char *filename)
{
  VirtualNode *node;
  GFileInputStream *stream;
  GFile *file;
  GError *error;

  node = virtual_node_lookup (G_VFS_BACKEND_BURN (backend)->root_node, filename, NULL);
  if (node == NULL)
    {
      g_vfs_job_failed (G_VFS_JOB (job), G_IO_ERROR,
                        G_IO_ERROR_NOT_FOUND,
                        _("No such file or directory"));
      return TRUE;
    }

  if (node->type == VIRTUAL_NODE_DIRECTORY)
    {
      g_vfs_job_failed (G_VFS_JOB (job),
                        G_IO_ERROR, G_IO_ERROR_IS_DIRECTORY,
                        _("Can’t copy file over directory"));
      return TRUE;
    }

  file = g_file_new_for_path (node->backing_file);
  
  error = NULL;
  stream = g_file_read (file, G_VFS_JOB (job)->cancellable, &error);
  g_object_unref (file);
  
  if (stream)
    {
      g_vfs_job_open_for_read_set_can_seek (job, g_seekable_can_seek (G_SEEKABLE (stream)));
      g_vfs_job_open_for_read_set_handle (job, stream);
      g_vfs_job_succeeded (G_VFS_JOB (job));
    }
  else
    {
      g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
      g_error_free (error);
    }

  return TRUE;
}


static void
do_read (GVfsBackend *backend,
         GVfsJobRead *job,
         GVfsBackendHandle _handle,
         char *buffer,
         gsize bytes_requested)
{
  GError *error;
  GFileInputStream *stream = _handle;
  gssize s;
  
  error = NULL;
  s = g_input_stream_read (G_INPUT_STREAM(stream),
                           buffer, bytes_requested,
                           G_VFS_JOB (job)->cancellable, &error); 
  if (s >= 0)
    {
      g_vfs_job_read_set_size (job, s);
      g_vfs_job_succeeded (G_VFS_JOB (job));
    }
  else
    {
      g_vfs_job_failed_from_error (G_VFS_JOB (job), error); 
      g_error_free (error);
    }
}

static void
do_seek_on_read (GVfsBackend *backend,
                  GVfsJobSeekRead *job,
                  GVfsBackendHandle _handle,
                  goffset    offset,
                  GSeekType  type)
{
  GError *error;
  GFileInputStream *stream = _handle;
  
  error = NULL;
  if (g_seekable_seek (G_SEEKABLE (stream), offset, type,
                       G_VFS_JOB (job)->cancellable, &error))
    {
      g_vfs_job_seek_read_set_offset (job, g_seekable_tell (G_SEEKABLE (stream)));
      g_vfs_job_succeeded (G_VFS_JOB (job));
    }
  else
    {
      g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
      g_error_free (error);
    }
}


static void
do_close_read (GVfsBackend *backend,
               GVfsJobCloseRead *job,
               GVfsBackendHandle _handle)
{
  GError *error;
  GFileInputStream *stream = _handle;
  
  error = NULL;
  if (g_input_stream_close (G_INPUT_STREAM(stream),
                            G_VFS_JOB (job)->cancellable, &error))
    {
      g_vfs_job_succeeded (G_VFS_JOB (job));
    }
  else
    {
      g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
      g_error_free (error);
    }
  
  g_object_unref (stream);
}

static char *
make_valid_utf8 (const char *name)
{
  GString *string;
  const gchar *remainder, *invalid;
  gint remaining_bytes, valid_bytes;
  
  string = NULL;
  remainder = name;
  remaining_bytes = strlen (name);
  
  while (remaining_bytes != 0) 
    {
      if (g_utf8_validate (remainder, remaining_bytes, &invalid)) 
        break;
      valid_bytes = invalid - remainder;
      
      if (string == NULL) 
        string = g_string_sized_new (remaining_bytes);
      
      g_string_append_len (string, remainder, valid_bytes);
      /* append U+FFFD REPLACEMENT CHARACTER */
      g_string_append (string, "\357\277\275");
      
      remaining_bytes -= valid_bytes + 1;
      remainder = invalid + 1;
    }
  
  if (string == NULL)
    return g_strdup (name);
  
  g_string_append (string, remainder);
  
  g_warn_if_fail (g_utf8_validate (string->str, -1, NULL));
  
  return g_string_free (string, FALSE);
}


static void
file_info_from_node (VirtualNode *node,
                     GFileInfo *info,
                     const char *attributes)
{
  GIcon *icon;
  GFile *file;
  GFileInfo *file_info;

  if (node->type == VIRTUAL_NODE_DIRECTORY)
    {
      g_file_info_set_file_type (info, G_FILE_TYPE_DIRECTORY);
      icon = g_themed_icon_new ("folder");
      g_file_info_set_icon (info, icon);
      g_object_unref (icon);
      icon = g_themed_icon_new ("folder-symbolic");
      g_file_info_set_symbolic_icon (info, icon);
      g_object_unref (icon);
      g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE, TRUE);
      g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_DELETE, TRUE);
      g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_TRASH, FALSE);
      g_file_info_set_content_type (info, "inode/directory"); 
   }
  else
    {
      file = g_file_new_for_path (node->backing_file);
      file_info = g_file_query_info (file,
                                     attributes,
                                     0, /* Always follow symlinks */
                                     NULL, NULL);
      if (file_info)
        {
          g_file_info_copy_into (file_info, info);
          g_object_unref (file_info);
        }
      
      g_file_info_set_attribute_byte_string (info,
                                             "burn::backing-file",
                                             node->backing_file);
    }

  if (node->filename != NULL)
    {
      char *utf8;
      
      g_file_info_set_name (info, node->filename);
      /* Ensure display name is utf8 */
      utf8 = make_valid_utf8 (node->filename);
      g_file_info_set_display_name (info, utf8);
      g_free (utf8);
    }
  else
    {
      g_file_info_set_name (info, "/");
      /* Translators: this is the display name of the backend */
      g_file_info_set_display_name (info, _("CD/DVD Creator"));
    }
}

static gboolean
try_enumerate (GVfsBackend *backend,
               GVfsJobEnumerate *job,
               const char *filename,
               GFileAttributeMatcher *attribute_matcher,
               GFileQueryInfoFlags flags)
{
  VirtualNode *node, *child;
  GFileInfo *info;
  GList *l;
  
  node = virtual_node_lookup (G_VFS_BACKEND_BURN (backend)->root_node, filename, NULL);
 
  if (node == NULL)
    {
      g_vfs_job_failed (G_VFS_JOB (job), G_IO_ERROR,
                        G_IO_ERROR_NOT_FOUND,
                        _("No such file or directory"));
      return TRUE;
    }
  
  if (node->type != VIRTUAL_NODE_DIRECTORY)
    {
      g_vfs_job_failed (G_VFS_JOB (job), G_IO_ERROR,
                        G_IO_ERROR_NOT_DIRECTORY,
                        _("The file is not a directory"));
      return TRUE;
    }

  g_vfs_job_succeeded (G_VFS_JOB (job));
  
  for (l = node->children; l != NULL; l = l->next)
    {
      child = l->data;
      
      info = g_file_info_new ();
      file_info_from_node (child, info, job->attributes);
      g_vfs_job_enumerate_add_info (job, info);
      g_object_unref (info);
    }

  g_vfs_job_enumerate_done (job);
 
  return TRUE;
}

static gboolean
try_query_info (GVfsBackend *backend,
                GVfsJobQueryInfo *job,
                const char *filename,
                GFileQueryInfoFlags flags,
                GFileInfo *info,
                GFileAttributeMatcher *matcher)
{
  VirtualNode *node;

  node = virtual_node_lookup (G_VFS_BACKEND_BURN (backend)->root_node, filename, NULL);
 
  if (node == NULL)
    {
      g_vfs_job_failed (G_VFS_JOB (job), G_IO_ERROR,
                        G_IO_ERROR_NOT_FOUND,
                        _("No such file or directory"));
      return TRUE;
    }

  file_info_from_node (node, info, job->attributes);
  g_vfs_job_succeeded (G_VFS_JOB (job));
  
  return TRUE;
}

static gboolean
try_make_directory (GVfsBackend *backend,
                    GVfsJobMakeDirectory *job,
                    const char *filename)
{
  char *dirname, *basename;
  VirtualNode *file, *dir;
  
  dirname = g_path_get_dirname (filename);
  dir = virtual_node_lookup (G_VFS_BACKEND_BURN (backend)->root_node, dirname, NULL);
  g_free (dirname);
  
  if (dir == NULL)
    {
      g_vfs_job_failed (G_VFS_JOB (job), G_IO_ERROR,
                        G_IO_ERROR_NOT_FOUND,
                        _("No such file or directory"));
      return TRUE;
    }

  basename = g_path_get_basename (filename);
  file = virtual_dir_lookup (dir, basename);
  if (file != NULL)
    {
      g_vfs_job_failed (G_VFS_JOB (job), G_IO_ERROR,
                        G_IO_ERROR_EXISTS,
                        _("File exists"));
      g_free (basename);
      return TRUE;
    }

  file = virtual_mkdir (dir, basename);
  g_free (basename);
  
  g_vfs_job_succeeded (G_VFS_JOB (job));

  return TRUE;
}

static gboolean
try_set_display_name (GVfsBackend *backend,
                      GVfsJobSetDisplayName *job,
                      const char *filename,
                      const char *display_name)
{
  VirtualNode *node, *dir;
  char *target_path;
  char *dirname;
  
  node = virtual_node_lookup (G_VFS_BACKEND_BURN (backend)->root_node, filename, &dir);
  if (node == NULL)
    {
      g_vfs_job_failed (G_VFS_JOB (job), G_IO_ERROR,
                        G_IO_ERROR_NOT_FOUND,
                        _("No such file or directory"));
      return TRUE;
    }

  if (virtual_dir_lookup (dir, display_name) != NULL)
    {
      g_vfs_job_failed (G_VFS_JOB (job), G_IO_ERROR,
                        G_IO_ERROR_EXISTS,
                        _("File exists"));
      return TRUE;
    }

  /* We use UTF8 for filenames */
  g_free (node->filename);
  node->filename = g_strdup (display_name);

  dirname = g_path_get_dirname (filename);
  target_path = g_build_filename (dirname, display_name, NULL);
  g_vfs_job_set_display_name_set_new_path (job, target_path);
  g_free (dirname);
  g_free (target_path);
  
  g_vfs_job_succeeded (G_VFS_JOB (job));
  
  return TRUE;
  
}

static gboolean
try_push (GVfsBackend *backend,
          GVfsJobPush *job,
          const char *destination,
          const char *local_path,
          GFileCopyFlags flags,
          gboolean remove_source,
          GFileProgressCallback progress_callback,
          gpointer progress_callback_data)
{
  VirtualNode *file, *dir;
  struct stat stat_buf;
  char *dirname, *basename;

  if (remove_source)
    {
      /* Fallback to copy & delete for now, fix that up later */
      g_vfs_job_failed_literal (G_VFS_JOB (job), G_IO_ERROR,
				G_IO_ERROR_NOT_SUPPORTED,
                                _("Operation not supported by backend"));
      return TRUE;
    }

  if (g_stat (local_path, &stat_buf) == -1)
    {
      int errsv = errno;

      g_vfs_job_failed (G_VFS_JOB (job),
                        G_IO_ERROR,
                        g_io_error_from_errno (errsv),
                        "%s", g_strerror (errsv));
      return TRUE;
    }

  dirname = g_path_get_dirname (destination);
  dir = virtual_node_lookup (G_VFS_BACKEND_BURN (backend)->root_node, dirname, NULL);
  g_free (dirname);
  file = NULL;

  if (dir == NULL)
    {
      /* Parent of created file doesn't exist */
      g_vfs_job_failed (G_VFS_JOB (job), G_IO_ERROR,
                        G_IO_ERROR_NOT_FOUND,
                        _("No such file or directory in target path"));
      return TRUE;
    }
  
  basename = g_path_get_basename (destination);
  file = virtual_dir_lookup (dir, basename);
  g_free (basename);
  
  if (S_ISDIR (stat_buf.st_mode))
    {
      /* 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. 
       */
      
      if (file != NULL)
        {
          if (flags & G_FILE_COPY_OVERWRITE)
            {
              if (file->type == VIRTUAL_NODE_DIRECTORY)
                {
                  g_vfs_job_failed (G_VFS_JOB (job),
                                    G_IO_ERROR, G_IO_ERROR_WOULD_MERGE,
                                    _("Can’t copy directory over directory"));
                  return TRUE;
                }
              /* continue to would_recurse error */
            }
          else
            {
              g_vfs_job_failed (G_VFS_JOB (job),
                                G_IO_ERROR, G_IO_ERROR_EXISTS,
                                _("Target file exists"));
              return TRUE;
            }
        }
      
      g_vfs_job_failed (G_VFS_JOB (job),
                        G_IO_ERROR, G_IO_ERROR_WOULD_RECURSE,
                        _("Can’t recursively copy directory"));
      return TRUE;
    }

  if (file != NULL)
    {
      if (flags & G_FILE_COPY_OVERWRITE)
        {
          if (file->type == VIRTUAL_NODE_DIRECTORY)
            {
              g_vfs_job_failed (G_VFS_JOB (job),
                                G_IO_ERROR, G_IO_ERROR_IS_DIRECTORY,
                                _("Can’t copy file over directory"));
              return TRUE;
            }
          else
            {
              g_assert (file->type == VIRTUAL_NODE_FILE);
              if (file->owned_file)
                g_unlink (file->backing_file);
              g_free (file->backing_file);
              file->owned_file = FALSE;
              file->backing_file = g_strdup (local_path);
              
              g_vfs_job_succeeded (G_VFS_JOB (job));
              return TRUE;
            }
        }
      else
        {
          g_vfs_job_failed (G_VFS_JOB (job),
                            G_IO_ERROR, G_IO_ERROR_EXISTS,
                            _("File exists"));
          return TRUE;
        }
    }
  else
    {
      basename = g_path_get_basename (destination);
      file = virtual_create (G_VFS_BACKEND_BURN (backend),
                             dir,
                             basename,
                             local_path);
      g_free (basename);
      
      g_vfs_job_succeeded (G_VFS_JOB (job));
      return TRUE;
    }
}

static gboolean
try_create_dir_monitor (GVfsBackend *backend,
                        GVfsJobCreateMonitor *job,
                        const char *filename,
                        GFileMonitorFlags flags)
{
  g_vfs_job_failed (G_VFS_JOB (job),
                    G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
                    _("Not supported"));
  return TRUE;
}

static gboolean
try_move (GVfsBackend *backend,
          GVfsJobMove *job,
          const char *source,
          const char *destination,
          GFileCopyFlags flags,
          GFileProgressCallback progress_callback,
          gpointer progress_callback_data)
{
  VirtualNode *source_node, *dest_node, *root_node, *source_dir, *dest_dir;

  root_node = G_VFS_BACKEND_BURN (backend)->root_node;
  
  source_node = virtual_node_lookup (root_node, source, &source_dir);
  if (source_node == NULL)
    {
      g_vfs_job_failed (G_VFS_JOB (job), G_IO_ERROR,
                        G_IO_ERROR_NOT_FOUND,
                        _("No such file or directory"));
      return TRUE;
    }

  dest_node = virtual_node_lookup (root_node, destination, &dest_dir);
  if (dest_node != NULL)
    {
      if (flags & G_FILE_COPY_OVERWRITE)
	{
	  if (dest_node->type == VIRTUAL_NODE_DIRECTORY)
	    {
	      if (source_node->type == VIRTUAL_NODE_DIRECTORY)
		g_vfs_job_failed (G_VFS_JOB (job), G_IO_ERROR,
				  G_IO_ERROR_WOULD_MERGE,
				  _("File exists"));
	      else
		g_vfs_job_failed (G_VFS_JOB (job), G_IO_ERROR,
				  G_IO_ERROR_IS_DIRECTORY,
				  _("File exists"));
	      return TRUE;
	    }
	  else
	    virtual_unlink (dest_dir, dest_node);
	}
      else
	{
	  g_vfs_job_failed (G_VFS_JOB (job), G_IO_ERROR,
			    G_IO_ERROR_EXISTS,
			    _("File exists"));
	  return TRUE;
	}
    }
  else if (dest_dir == NULL)
    {
      g_vfs_job_failed (G_VFS_JOB (job), G_IO_ERROR,
                        G_IO_ERROR_NOT_FOUND,
                        _("No such file or directory"));
      return TRUE;
    }
  
  g_free (source_node->filename);
  source_node->filename = g_path_get_basename (destination);
  
  if (source_dir != dest_dir)
    {
      source_dir->children = g_list_remove (source_dir->children, source_node);
      dest_dir->children = g_list_append (dest_dir->children, source_node);
    }

  g_vfs_job_succeeded (G_VFS_JOB (job));
  
  return TRUE;
}

static gboolean
try_query_fs_info (GVfsBackend *backend,
                   GVfsJobQueryFsInfo *job,
                   const char *filename,
                   GFileInfo *info,
                   GFileAttributeMatcher *matcher)
{
  g_file_info_set_attribute_string (info, G_FILE_ATTRIBUTE_FILESYSTEM_TYPE, "burn");
  g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_FILESYSTEM_REMOTE, FALSE);
  g_vfs_job_succeeded (G_VFS_JOB (job));
  return TRUE;
}

static void
g_vfs_backend_burn_class_init (GVfsBackendBurnClass *klass)
{
  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
  GVfsBackendClass *backend_class = G_VFS_BACKEND_CLASS (klass);
  
  gobject_class->finalize = g_vfs_backend_burn_finalize;

  backend_class->try_mount = try_mount;
  backend_class->try_open_for_read = try_open_for_read;
  backend_class->try_query_info = try_query_info;
  backend_class->try_query_fs_info = try_query_fs_info;
  backend_class->try_enumerate = try_enumerate;
  backend_class->try_create_dir_monitor = try_create_dir_monitor;
  backend_class->try_make_directory = try_make_directory;
  backend_class->try_set_display_name = try_set_display_name;
  backend_class->try_push = try_push;
  backend_class->try_delete = try_delete;
  backend_class->try_move = try_move;
  backend_class->read = do_read;
  backend_class->seek_on_read = do_seek_on_read;
  backend_class->close_read = do_close_read;
}
