[gvfs] Implement truncate support for output streams



commit c3b6615e95bd213beab32d90a8bf38f1b221a8b4
Author: Ross Lagerwall <rosslagerwall gmail com>
Date:   Thu Oct 17 07:26:05 2013 +0200

    Implement truncate support for output streams
    
    Backends receive a TRUNCATE message which contains a size parameter.
    Truncation is signaled with a TRUNCATED message (which contains no other
    useful information).
    
    In more detail:
    Add a new dbus method, OpenForWriteFlags, which has a flags parameter to
    implement can_seek and can_truncate.  These flags are used in
    GDaemonFileOutputStream.  Compatability with old clients is maintained.
    Implement the can_truncate and truncate_fn GDaemonFileOutputStream
    methods.
    Add two new message types to the daemon socket protocol:
        G_VFS_DAEMON_SOCKET_PROTOCOL_REQUEST_TRUNCATE
        G_VFS_DAEMON_SOCKET_PROTOCOL_REPLY_TRUNCATED
    Add a new job type, GVfsJobTruncate.
    Add two new methods to GVfsBackend which backend classes can implement:
    truncate and try_truncate
    
    https://bugzilla.gnome.org/show_bug.cgi?id=573837

 client/gdaemonfile.c             |   66 +++++++------
 client/gdaemonfileoutputstream.c |  201 +++++++++++++++++++++++++++++++++++++-
 client/gdaemonfileoutputstream.h |    2 +-
 common/gvfsdaemonprotocol.h      |    6 +
 common/org.gtk.vfs.xml           |   12 +++
 daemon/Makefile.am               |    1 +
 daemon/gvfsbackend.c             |    1 +
 daemon/gvfsbackend.h             |    9 ++
 daemon/gvfsjobopenforwrite.c     |  107 +++++++++++++++++----
 daemon/gvfsjobopenforwrite.h     |   22 ++++-
 daemon/gvfsjobtruncate.c         |  128 ++++++++++++++++++++++++
 daemon/gvfsjobtruncate.h         |   63 ++++++++++++
 daemon/gvfswritechannel.c        |   26 +++++-
 daemon/gvfswritechannel.h        |    1 +
 14 files changed, 591 insertions(+), 54 deletions(-)
---
diff --git a/client/gdaemonfile.c b/client/gdaemonfile.c
index 267ceaa..445c919 100644
--- a/client/gdaemonfile.c
+++ b/client/gdaemonfile.c
@@ -1238,7 +1238,7 @@ file_open_write (GFile *file,
   GVfsDBusMount *proxy;
   char *path;
   gboolean res;
-  gboolean can_seek;
+  guint32 ret_flags;
   GUnixFDList *fd_list;
   int fd;
   GVariant *fd_id_val = NULL;
@@ -1255,20 +1255,20 @@ file_open_write (GFile *file,
   if (proxy == NULL)
     return NULL;
 
-  res = gvfs_dbus_mount_call_open_for_write_sync (proxy,
-                                                  path,
-                                                  mode,
-                                                  etag,
-                                                  make_backup,
-                                                  flags,
-                                                  pid,
-                                                  NULL,
-                                                  &fd_id_val,
-                                                  &can_seek,
-                                                  &initial_offset,
-                                                  &fd_list,
-                                                  cancellable,
-                                                  &local_error);
+  res = gvfs_dbus_mount_call_open_for_write_flags_sync (proxy,
+                                                        path,
+                                                        mode,
+                                                        etag,
+                                                        make_backup,
+                                                        flags,
+                                                        pid,
+                                                        NULL,
+                                                        &fd_id_val,
+                                                        &ret_flags,
+                                                        &initial_offset,
+                                                        &fd_list,
+                                                        cancellable,
+                                                        &local_error);
 
   if (! res)
     {
@@ -1295,7 +1295,7 @@ file_open_write (GFile *file,
   g_variant_unref (fd_id_val);
   g_object_unref (fd_list);
   
-  return g_daemon_file_output_stream_new (fd, can_seek, initial_offset);
+  return g_daemon_file_output_stream_new (fd, ret_flags, initial_offset);
 }
 
 static GFileOutputStream *
@@ -3144,7 +3144,7 @@ file_open_write_async_cb (GVfsDBusMount *proxy,
   AsyncCallFileReadWrite *data = user_data;
   GError *error = NULL;
   GSimpleAsyncResult *orig_result;
-  gboolean can_seek;
+  guint32 flags;
   GUnixFDList *fd_list;
   int fd;
   GVariant *fd_id_val;
@@ -3154,7 +3154,13 @@ file_open_write_async_cb (GVfsDBusMount *proxy,
 
   orig_result = data->result;
   
-  if (! gvfs_dbus_mount_call_open_for_write_finish (proxy, &fd_id_val, &can_seek, &initial_offset, &fd_list, 
res, &error))
+  if (! gvfs_dbus_mount_call_open_for_write_flags_finish (proxy,
+                                                          &fd_id_val,
+                                                          &flags,
+                                                          &initial_offset,
+                                                          &fd_list,
+                                                          res,
+                                                          &error))
     {
       _g_simple_async_result_take_error_stripped (orig_result, error);
       goto out;
@@ -3172,7 +3178,7 @@ file_open_write_async_cb (GVfsDBusMount *proxy,
     }
   else
     {
-      output_stream = g_daemon_file_output_stream_new (fd, can_seek, initial_offset);
+      output_stream = g_daemon_file_output_stream_new (fd, flags, initial_offset);
       g_simple_async_result_set_op_res_gpointer (orig_result, output_stream, g_object_unref);
       g_object_unref (fd_list);
     }
@@ -3201,17 +3207,17 @@ file_open_write_async_get_proxy_cb (GVfsDBusMount *proxy,
   
   data->result = g_object_ref (result);
   
-  gvfs_dbus_mount_call_open_for_write (proxy,
-                                       path,
-                                       data->mode,
-                                       data->etag,
-                                       data->make_backup,
-                                       data->flags,
-                                       pid,
-                                       NULL,
-                                       cancellable,
-                                       (GAsyncReadyCallback) file_open_write_async_cb,
-                                       data);
+  gvfs_dbus_mount_call_open_for_write_flags (proxy,
+                                             path,
+                                             data->mode,
+                                             data->etag,
+                                             data->make_backup,
+                                             data->flags,
+                                             pid,
+                                             NULL,
+                                             cancellable,
+                                             (GAsyncReadyCallback) file_open_write_async_cb,
+                                             data);
   data->cancelled_tag = _g_dbus_async_subscribe_cancellable (connection, cancellable);
 }
 
diff --git a/client/gdaemonfileoutputstream.c b/client/gdaemonfileoutputstream.c
index e180486..4a5f0b7 100644
--- a/client/gdaemonfileoutputstream.c
+++ b/client/gdaemonfileoutputstream.c
@@ -98,6 +98,26 @@ typedef struct {
 } SeekOperation;
 
 typedef enum {
+  TRUNCATE_STATE_INIT = 0,
+  TRUNCATE_STATE_WROTE_REQUEST,
+  TRUNCATE_STATE_HANDLE_INPUT
+} TruncateState;
+
+typedef struct {
+  TruncateState state;
+
+  /* Output */
+  goffset size;
+  /* Input */
+  gboolean ret_val;
+  GError *ret_error;
+
+  gboolean sent_cancel;
+
+  guint32 seq_nr;
+} TruncateOperation;
+
+typedef enum {
   CLOSE_STATE_INIT = 0,
   CLOSE_STATE_WROTE_REQUEST,
   CLOSE_STATE_HANDLE_INPUT
@@ -157,7 +177,8 @@ struct _GDaemonFileOutputStream {
 
   GOutputStream *command_stream;
   GInputStream *data_stream;
-  guint can_seek : 1;
+  gboolean can_seek;
+  gboolean can_truncate;
   
   guint32 seq_nr;
   goffset current_offset;
@@ -191,6 +212,11 @@ static gboolean   g_daemon_file_output_stream_seek              (GFileOutputStre
                                                                 GSeekType             type,
                                                                 GCancellable         *cancellable,
                                                                 GError              **error);
+static gboolean   g_daemon_file_output_stream_can_truncate      (GFileOutputStream    *stream);
+static gboolean   g_daemon_file_output_stream_truncate          (GFileOutputStream    *stream,
+                                                                goffset               size,
+                                                                GCancellable         *cancellable,
+                                                                GError              **error);
 static void       g_daemon_file_output_stream_write_async       (GOutputStream        *stream,
                                                                 const void           *buffer,
                                                                 gsize                 count,
@@ -276,6 +302,8 @@ g_daemon_file_output_stream_class_init (GDaemonFileOutputStreamClass *klass)
   file_stream_class->tell = g_daemon_file_output_stream_tell;
   file_stream_class->can_seek = g_daemon_file_output_stream_can_seek;
   file_stream_class->seek = g_daemon_file_output_stream_seek;
+  file_stream_class->can_truncate = g_daemon_file_output_stream_can_truncate;
+  file_stream_class->truncate_fn = g_daemon_file_output_stream_truncate;
   file_stream_class->query_info = g_daemon_file_output_stream_query_info;
   file_stream_class->get_etag = g_daemon_file_output_stream_get_etag;
   file_stream_class->query_info_async = g_daemon_file_output_stream_query_info_async;
@@ -292,7 +320,7 @@ g_daemon_file_output_stream_init (GDaemonFileOutputStream *info)
 
 GFileOutputStream *
 g_daemon_file_output_stream_new (int fd,
-                                gboolean can_seek,
+                                guint32 flags,
                                 goffset initial_offset)
 {
   GDaemonFileOutputStream *stream;
@@ -301,7 +329,8 @@ g_daemon_file_output_stream_new (int fd,
 
   stream->command_stream = g_unix_output_stream_new (fd, FALSE);
   stream->data_stream = g_unix_input_stream_new (fd, TRUE);
-  stream->can_seek = can_seek;
+  stream->can_seek = flags & OPEN_FOR_WRITE_FLAG_CAN_SEEK;
+  stream->can_truncate = flags & OPEN_FOR_WRITE_FLAG_CAN_TRUNCATE;
   stream->current_offset = initial_offset;
   
   return G_FILE_OUTPUT_STREAM (stream);
@@ -1020,6 +1049,172 @@ g_daemon_file_output_stream_seek (GFileOutputStream *stream,
   return op.ret_val;
 }
 
+static StateOp
+iterate_truncate_state_machine (GDaemonFileOutputStream *file,
+                                IOOperationData *io_op,
+                                TruncateOperation *op)
+{
+  gsize len;
+
+  while (TRUE)
+    {
+      switch (op->state)
+        {
+        case TRUNCATE_STATE_INIT:
+          append_request (file,
+                          G_VFS_DAEMON_SOCKET_PROTOCOL_REQUEST_TRUNCATE,
+                          op->size & 0xffffffff,
+                          op->size >> 32,
+                          0,
+                          &op->seq_nr);
+          op->state = TRUNCATE_STATE_WROTE_REQUEST;
+          io_op->io_buffer = file->output_buffer->str;
+          io_op->io_size = file->output_buffer->len;
+          io_op->io_allow_cancel = TRUE;
+          return STATE_OP_WRITE;
+
+        case TRUNCATE_STATE_WROTE_REQUEST:
+          if (io_op->io_cancelled)
+            {
+              if (!op->sent_cancel)
+                unappend_request (file);
+              op->ret_val = FALSE;
+              g_set_error_literal (&op->ret_error,
+                                   G_IO_ERROR,
+                                   G_IO_ERROR_CANCELLED,
+                                   _("Operation was cancelled"));
+              return STATE_OP_DONE;
+            }
+
+          if (io_op->io_res < file->output_buffer->len)
+            {
+              g_string_remove_in_front (file->output_buffer, io_op->io_res);
+              io_op->io_buffer = file->output_buffer->str;
+              io_op->io_size = file->output_buffer->len;
+              io_op->io_allow_cancel = FALSE;
+              return STATE_OP_WRITE;
+            }
+          g_string_truncate (file->output_buffer, 0);
+
+          op->state = TRUNCATE_STATE_HANDLE_INPUT;
+          break;
+
+        case TRUNCATE_STATE_HANDLE_INPUT:
+          if (io_op->cancelled && !op->sent_cancel)
+            {
+              op->sent_cancel = TRUE;
+              append_request (file, G_VFS_DAEMON_SOCKET_PROTOCOL_REQUEST_CANCEL,
+                              op->seq_nr, 0, 0, NULL);
+              op->state = TRUNCATE_STATE_WROTE_REQUEST;
+              io_op->io_buffer = file->output_buffer->str;
+              io_op->io_size = file->output_buffer->len;
+              io_op->io_allow_cancel = FALSE;
+              return STATE_OP_WRITE;
+            }
+
+          if (io_op->io_res > 0)
+            {
+              gsize unread_size = io_op->io_size - io_op->io_res;
+              g_string_set_size (file->input_buffer,
+                                 file->input_buffer->len - unread_size);
+            }
+
+          len = get_reply_header_missing_bytes (file->input_buffer);
+          if (len > 0)
+            {
+              gsize current_len = file->input_buffer->len;
+              g_string_set_size (file->input_buffer, current_len + len);
+              io_op->io_buffer = file->input_buffer->str + current_len;
+              io_op->io_size = len;
+              io_op->io_allow_cancel = !op->sent_cancel;
+              return STATE_OP_READ;
+            }
+
+          {
+            GVfsDaemonSocketProtocolReply reply;
+            char *data;
+            data = decode_reply (file->input_buffer, &reply);
+
+            if (reply.type == G_VFS_DAEMON_SOCKET_PROTOCOL_REPLY_ERROR &&
+                reply.seq_nr == op->seq_nr)
+              {
+                op->ret_val = FALSE;
+                decode_error (&reply, data, &op->ret_error);
+                g_string_truncate (file->input_buffer, 0);
+                return STATE_OP_DONE;
+              }
+            else if (reply.type == G_VFS_DAEMON_SOCKET_PROTOCOL_REPLY_TRUNCATED &&
+                     reply.seq_nr == op->seq_nr)
+              {
+                op->ret_val = TRUE;
+                g_string_truncate (file->input_buffer, 0);
+                return STATE_OP_DONE;
+              }
+            /* Ignore other reply types */
+          }
+
+          g_string_truncate (file->input_buffer, 0);
+
+          /* This wasn't interesting, read next reply */
+          op->state = TRUNCATE_STATE_HANDLE_INPUT;
+          break;
+
+        default:
+          g_assert_not_reached ();
+        }
+
+      /* Clear io_op between non-op state switches */
+      io_op->io_size = 0;
+      io_op->io_res = 0;
+      io_op->io_cancelled = FALSE;
+    }
+}
+
+static gboolean
+g_daemon_file_output_stream_can_truncate (GFileOutputStream *stream)
+{
+  GDaemonFileOutputStream *file;
+
+  file = G_DAEMON_FILE_OUTPUT_STREAM (stream);
+
+  return file->can_truncate;
+}
+
+static gboolean
+g_daemon_file_output_stream_truncate (GFileOutputStream *stream,
+                                      goffset            size,
+                                      GCancellable      *cancellable,
+                                      GError           **error)
+{
+  GDaemonFileOutputStream *file;
+  TruncateOperation op;
+
+  file = G_DAEMON_FILE_OUTPUT_STREAM (stream);
+
+  if (!file->can_truncate)
+    {
+      g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
+                           _("Truncate not supported on stream"));
+      return FALSE;
+    }
+
+  if (g_cancellable_set_error_if_cancelled (cancellable, error))
+    return FALSE;
+
+  memset (&op, 0, sizeof (op));
+  op.state = TRUNCATE_STATE_INIT;
+  op.size = size;
+
+  if (!run_sync_state_machine (file, (state_machine_iterator)iterate_truncate_state_machine,
+                               &op, cancellable, error))
+    return FALSE; /* IO Error */
+
+  if (!op.ret_val)
+    g_propagate_error (error, op.ret_error);
+
+  return op.ret_val;
+}
+
 static char *
 g_daemon_file_output_stream_get_etag (GFileOutputStream     *stream)
 {
diff --git a/client/gdaemonfileoutputstream.h b/client/gdaemonfileoutputstream.h
index 0b48728..cee761b 100644
--- a/client/gdaemonfileoutputstream.h
+++ b/client/gdaemonfileoutputstream.h
@@ -45,7 +45,7 @@ struct _GDaemonFileOutputStreamClass
 GType g_daemon_file_output_stream_get_type (void) G_GNUC_CONST;
 
 GFileOutputStream *g_daemon_file_output_stream_new (int fd,
-                                                   gboolean can_seek,
+                                                   guint32 flags,
                                                    goffset initial_offset);
 
 G_END_DECLS
diff --git a/common/gvfsdaemonprotocol.h b/common/gvfsdaemonprotocol.h
index 440620b..e74a5d2 100644
--- a/common/gvfsdaemonprotocol.h
+++ b/common/gvfsdaemonprotocol.h
@@ -19,6 +19,10 @@ G_BEGIN_DECLS
 /* Normal ops are faster, one minute timeout */
 #define G_VFS_DBUS_TIMEOUT_MSECS (1000*60)
 
+/* Flags for the OpenForWriteFlags method */
+#define OPEN_FOR_WRITE_FLAG_CAN_SEEK     (1<<0)
+#define OPEN_FOR_WRITE_FLAG_CAN_TRUNCATE (1<<1)
+
 typedef struct {
   guint32 command;
   guint32 seq_nr;
@@ -36,6 +40,7 @@ typedef struct {
 #define G_VFS_DAEMON_SOCKET_PROTOCOL_REQUEST_SEEK_SET 4
 #define G_VFS_DAEMON_SOCKET_PROTOCOL_REQUEST_SEEK_END 5
 #define G_VFS_DAEMON_SOCKET_PROTOCOL_REQUEST_QUERY_INFO 6
+#define G_VFS_DAEMON_SOCKET_PROTOCOL_REQUEST_TRUNCATE 7
 
 /*
 read, readahead reply:
@@ -67,6 +72,7 @@ typedef struct {
 #define G_VFS_DAEMON_SOCKET_PROTOCOL_REPLY_WRITTEN  3
 #define G_VFS_DAEMON_SOCKET_PROTOCOL_REPLY_CLOSED   4
 #define G_VFS_DAEMON_SOCKET_PROTOCOL_REPLY_INFO     5
+#define G_VFS_DAEMON_SOCKET_PROTOCOL_REPLY_TRUNCATED 6
 
 
 typedef union {
diff --git a/common/org.gtk.vfs.xml b/common/org.gtk.vfs.xml
index b668352..63038b0 100644
--- a/common/org.gtk.vfs.xml
+++ b/common/org.gtk.vfs.xml
@@ -199,6 +199,18 @@
       <arg type='t' name='initial_offset' direction='out'/>
       <annotation name="org.gtk.GDBus.C.UnixFD" value="true"/>
     </method>
+    <method name="OpenForWriteFlags">
+      <arg type='ay' name='path_data' direction='in'/>
+      <arg type='q' name='mode' direction='in'/>
+      <arg type='s' name='etag' direction='in'/>
+      <arg type='b' name='make_backup' direction='in'/>
+      <arg type='u' name='flags' direction='in'/>
+      <arg type='u' name='pid' direction='in'/>
+      <arg type='h' name='fd_id' direction='out'/>
+      <arg type='u' name='flags' direction='out'/>
+      <arg type='t' name='initial_offset' direction='out'/>
+      <annotation name="org.gtk.GDBus.C.UnixFD" value="true"/>
+    </method>
     <method name="QueryInfo">
       <arg type='ay' name='path_data' direction='in'/>
       <arg type='s' name='attributes' direction='in'/>
diff --git a/daemon/Makefile.am b/daemon/Makefile.am
index 020943a..6a1b837 100644
--- a/daemon/Makefile.am
+++ b/daemon/Makefile.am
@@ -178,6 +178,7 @@ libdaemon_la_SOURCES = \
        gvfsjobopenforwrite.c gvfsjobopenforwrite.h \
        gvfsjobwrite.c gvfsjobwrite.h \
        gvfsjobseekwrite.c gvfsjobseekwrite.h \
+       gvfsjobtruncate.c gvfsjobtruncate.h \
        gvfsjobclosewrite.c gvfsjobclosewrite.h \
        gvfsjobqueryinfo.c gvfsjobqueryinfo.h \
        gvfsjobqueryinforead.c gvfsjobqueryinforead.h \
diff --git a/daemon/gvfsbackend.c b/daemon/gvfsbackend.c
index f6eb77b..26eba28 100644
--- a/daemon/gvfsbackend.c
+++ b/daemon/gvfsbackend.c
@@ -280,6 +280,7 @@ register_path_cb (GDBusConnection *conn,
   g_signal_connect (skeleton, "handle-unmount", G_CALLBACK (g_vfs_job_unmount_new_handle), data);
   g_signal_connect (skeleton, "handle-open-for-read", G_CALLBACK (g_vfs_job_open_for_read_new_handle), data);
   g_signal_connect (skeleton, "handle-open-for-write", G_CALLBACK (g_vfs_job_open_for_write_new_handle), 
data);
+  g_signal_connect (skeleton, "handle-open-for-write-flags", G_CALLBACK 
(g_vfs_job_open_for_write_new_handle_with_flags), data);
   g_signal_connect (skeleton, "handle-copy", G_CALLBACK (g_vfs_job_copy_new_handle), data);
   g_signal_connect (skeleton, "handle-move", G_CALLBACK (g_vfs_job_move_new_handle), data);
   g_signal_connect (skeleton, "handle-push", G_CALLBACK (g_vfs_job_push_new_handle), data);
diff --git a/daemon/gvfsbackend.h b/daemon/gvfsbackend.h
index 45ec4fb..8e7ba55 100644
--- a/daemon/gvfsbackend.h
+++ b/daemon/gvfsbackend.h
@@ -55,6 +55,7 @@ typedef struct _GVfsJobRead             GVfsJobRead;
 typedef struct _GVfsJobOpenForWrite     GVfsJobOpenForWrite;
 typedef struct _GVfsJobWrite            GVfsJobWrite;
 typedef struct _GVfsJobSeekWrite        GVfsJobSeekWrite;
+typedef struct _GVfsJobTruncate         GVfsJobTruncate;
 typedef struct _GVfsJobCloseWrite       GVfsJobCloseWrite;
 typedef struct _GVfsJobQueryInfo        GVfsJobQueryInfo;
 typedef struct _GVfsJobQueryInfoRead    GVfsJobQueryInfoRead;
@@ -236,6 +237,14 @@ struct _GVfsBackendClass
                                 GVfsBackendHandle handle,
                                 goffset    offset,
                                 GSeekType  type);
+  void     (*truncate)          (GVfsBackend *backend,
+                                GVfsJobTruncate *job,
+                                GVfsBackendHandle handle,
+                                goffset size);
+  gboolean (*try_truncate)      (GVfsBackend *backend,
+                                GVfsJobTruncate *job,
+                                GVfsBackendHandle handle,
+                                goffset size);
   void     (*query_info)        (GVfsBackend *backend,
                                 GVfsJobQueryInfo *job,
                                 const char *filename,
diff --git a/daemon/gvfsjobopenforwrite.c b/daemon/gvfsjobopenforwrite.c
index 429e267..1388dab 100644
--- a/daemon/gvfsjobopenforwrite.c
+++ b/daemon/gvfsjobopenforwrite.c
@@ -81,28 +81,29 @@ g_vfs_job_open_for_write_init (GVfsJobOpenForWrite *job)
 {
 }
 
-gboolean
-g_vfs_job_open_for_write_new_handle (GVfsDBusMount *object,
-                                     GDBusMethodInvocation *invocation,
-                                     GUnixFDList *fd_list,
-                                     const gchar *arg_path_data,
-                                     guint16 arg_mode,
-                                     const gchar *arg_etag,
-                                     gboolean arg_make_backup,
-                                     guint arg_flags,
-                                     guint arg_pid,
-                                     GVfsBackend *backend)
+static gboolean
+open_for_write_new_handle_common (GVfsDBusMount *object,
+                                  GDBusMethodInvocation *invocation,
+                                  GUnixFDList *fd_list,
+                                  const gchar *arg_path_data,
+                                  guint16 arg_mode,
+                                  const gchar *arg_etag,
+                                  gboolean arg_make_backup,
+                                  guint arg_flags,
+                                  guint arg_pid,
+                                  GVfsBackend *backend,
+                                  GVfsJobOpenForWriteVersion version)
 {
   GVfsJobOpenForWrite *job;
-  
+
   if (g_vfs_backend_invocation_first_handler (object, invocation, backend))
     return TRUE;
-  
+
   job = g_object_new (G_VFS_TYPE_JOB_OPEN_FOR_WRITE,
                       "object", object,
                       "invocation", invocation,
                       NULL);
-  
+
   job->filename = g_strdup (arg_path_data);
   job->mode = arg_mode;
   if (*arg_etag != 0)
@@ -111,6 +112,7 @@ g_vfs_job_open_for_write_new_handle (GVfsDBusMount *object,
   job->flags = arg_flags;
   job->backend = backend;
   job->pid = arg_pid;
+  job->version = version;
 
   g_vfs_job_source_new_job (G_VFS_JOB_SOURCE (backend), G_VFS_JOB (job));
   g_object_unref (job);
@@ -118,6 +120,56 @@ g_vfs_job_open_for_write_new_handle (GVfsDBusMount *object,
   return TRUE;
 }
 
+gboolean
+g_vfs_job_open_for_write_new_handle (GVfsDBusMount *object,
+                                     GDBusMethodInvocation *invocation,
+                                     GUnixFDList *fd_list,
+                                     const gchar *arg_path_data,
+                                     guint16 arg_mode,
+                                     const gchar *arg_etag,
+                                     gboolean arg_make_backup,
+                                     guint arg_flags,
+                                     guint arg_pid,
+                                     GVfsBackend *backend)
+{
+  return open_for_write_new_handle_common(object,
+                                          invocation,
+                                          fd_list,
+                                          arg_path_data,
+                                          arg_mode,
+                                          arg_etag,
+                                          arg_make_backup,
+                                          arg_flags,
+                                          arg_pid,
+                                          backend,
+                                          OPEN_FOR_WRITE_VERSION_ORIGINAL);
+}
+
+gboolean
+g_vfs_job_open_for_write_new_handle_with_flags (GVfsDBusMount *object,
+                                                GDBusMethodInvocation *invocation,
+                                                GUnixFDList *fd_list,
+                                                const gchar *arg_path_data,
+                                                guint16 arg_mode,
+                                                const gchar *arg_etag,
+                                                gboolean arg_make_backup,
+                                                guint arg_flags,
+                                                guint arg_pid,
+                                                GVfsBackend *backend)
+{
+  return open_for_write_new_handle_common(object,
+                                          invocation,
+                                          fd_list,
+                                          arg_path_data,
+                                          arg_mode,
+                                          arg_etag,
+                                          arg_make_backup,
+                                          arg_flags,
+                                          arg_pid,
+                                          backend,
+                                          OPEN_FOR_WRITE_VERSION_WITH_FLAGS);
+}
+
 static void
 run (GVfsJob *job)
 {
@@ -233,6 +285,13 @@ g_vfs_job_open_for_write_set_can_seek (GVfsJobOpenForWrite *job,
 }
 
 void
+g_vfs_job_open_for_write_set_can_truncate (GVfsJobOpenForWrite *job,
+                                           gboolean can_truncate)
+{
+  job->can_truncate = can_truncate;
+}
+
+void
 g_vfs_job_open_for_write_set_initial_offset (GVfsJobOpenForWrite *job,
                                             goffset              initial_offset)
 {
@@ -284,10 +343,22 @@ create_reply (GVfsJob *job,
 
   g_signal_emit_by_name (job, "new-source", open_job->write_channel);
 
-  gvfs_dbus_mount_complete_open_for_write (object, invocation,
-                                           fd_list, g_variant_new_handle (fd_id),
-                                           open_job->can_seek,
-                                           open_job->initial_offset);
+  switch (open_job->version)
+    {
+      case OPEN_FOR_WRITE_VERSION_ORIGINAL:
+        gvfs_dbus_mount_complete_open_for_write (object, invocation,
+                                                 fd_list, g_variant_new_handle (fd_id),
+                                                 open_job->can_seek ? OPEN_FOR_WRITE_FLAG_CAN_SEEK : 0,
+                                                 open_job->initial_offset);
+        break;
+      case OPEN_FOR_WRITE_VERSION_WITH_FLAGS:
+        gvfs_dbus_mount_complete_open_for_write_flags (object, invocation,
+                                                 fd_list, g_variant_new_handle (fd_id),
+                                                 (open_job->can_seek ? OPEN_FOR_WRITE_FLAG_CAN_SEEK : 0) |
+                                                 (open_job->can_truncate ? OPEN_FOR_WRITE_FLAG_CAN_TRUNCATE 
: 0),
+                                                 open_job->initial_offset);
+        break;
+    }
   
   close (remote_fd);
   g_object_unref (fd_list);
diff --git a/daemon/gvfsjobopenforwrite.h b/daemon/gvfsjobopenforwrite.h
index 79854d2..141189f 100644
--- a/daemon/gvfsjobopenforwrite.h
+++ b/daemon/gvfsjobopenforwrite.h
@@ -44,6 +44,11 @@ typedef enum {
   OPEN_FOR_WRITE_REPLACE = 2
 } GVfsJobOpenForWriteMode;
 
+typedef enum {
+  OPEN_FOR_WRITE_VERSION_ORIGINAL,
+  OPEN_FOR_WRITE_VERSION_WITH_FLAGS,
+} GVfsJobOpenForWriteVersion;
+
 struct _GVfsJobOpenForWrite
 {
   GVfsJobDBus parent_instance;
@@ -57,11 +62,14 @@ struct _GVfsJobOpenForWrite
   GVfsBackend *backend;
   GVfsBackendHandle backend_handle;
 
-  gboolean can_seek;
+  guint can_seek : 1;
+  guint can_truncate : 1;
   goffset initial_offset;
   GVfsWriteChannel *write_channel;
 
   GPid pid;
+
+  GVfsJobOpenForWriteVersion version;
 };
 
 struct _GVfsJobOpenForWriteClass
@@ -81,10 +89,22 @@ gboolean g_vfs_job_open_for_write_new_handle         (GVfsDBusMount         *obj
                                                       guint                  arg_flags,
                                                       guint                  arg_pid,
                                                       GVfsBackend           *backend);
+gboolean g_vfs_job_open_for_write_new_handle_with_flags (GVfsDBusMount         *object,
+                                                         GDBusMethodInvocation *invocation,
+                                                         GUnixFDList           *fd_list,
+                                                         const gchar           *arg_path_data,
+                                                         guint16                arg_mode,
+                                                         const gchar           *arg_etag,
+                                                         gboolean               arg_make_backup,
+                                                         guint                  arg_flags,
+                                                         guint                  arg_pid,
+                                                         GVfsBackend           *backend);
 void     g_vfs_job_open_for_write_set_handle         (GVfsJobOpenForWrite *job,
                                                      GVfsBackendHandle    handle);
 void     g_vfs_job_open_for_write_set_can_seek       (GVfsJobOpenForWrite *job,
                                                      gboolean             can_seek);
+void     g_vfs_job_open_for_write_set_can_truncate   (GVfsJobOpenForWrite *job,
+                                                      gboolean             can_truncate);
 void     g_vfs_job_open_for_write_set_initial_offset (GVfsJobOpenForWrite *job,
                                                      goffset              initial_offset);
 GPid     g_vfs_job_open_for_write_get_pid            (GVfsJobOpenForWrite *job);
diff --git a/daemon/gvfsjobtruncate.c b/daemon/gvfsjobtruncate.c
new file mode 100644
index 0000000..bac3248
--- /dev/null
+++ b/daemon/gvfsjobtruncate.c
@@ -0,0 +1,128 @@
+/* GIO - GLib Input, Output and Streaming Library
+ * 
+ * Copyright (C) 2013 Ross Lagerwall
+ *
+ * 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.
+ */
+
+#include <config.h>
+
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+
+#include <glib.h>
+#include <glib/gi18n.h>
+#include "gvfswritechannel.h"
+#include "gvfsjobtruncate.h"
+#include "gvfsdaemonutils.h"
+
+G_DEFINE_TYPE (GVfsJobTruncate, g_vfs_job_truncate, G_VFS_TYPE_JOB)
+
+static void     run        (GVfsJob *job);
+static gboolean try        (GVfsJob *job);
+static void     send_reply (GVfsJob *job);
+
+static void
+g_vfs_job_truncate_finalize (GObject *object)
+{
+  GVfsJobTruncate *job;
+
+  job = G_VFS_JOB_TRUNCATE (object);
+  g_object_unref (job->channel);
+
+  if (G_OBJECT_CLASS (g_vfs_job_truncate_parent_class)->finalize)
+    (*G_OBJECT_CLASS (g_vfs_job_truncate_parent_class)->finalize) (object);
+}
+
+static void
+g_vfs_job_truncate_class_init (GVfsJobTruncateClass *klass)
+{
+  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+  GVfsJobClass *job_class = G_VFS_JOB_CLASS (klass);
+
+  gobject_class->finalize = g_vfs_job_truncate_finalize;
+
+  job_class->run = run;
+  job_class->try = try;
+  job_class->send_reply = send_reply;
+}
+
+static void
+g_vfs_job_truncate_init (GVfsJobTruncate *job)
+{
+}
+
+GVfsJob *
+g_vfs_job_truncate_new (GVfsWriteChannel *channel,
+                        GVfsBackendHandle handle,
+                        goffset size,
+                        GVfsBackend *backend)
+{
+  GVfsJobTruncate *job;
+
+  job = g_object_new (G_VFS_TYPE_JOB_TRUNCATE, NULL);
+
+  job->backend = backend;
+  job->channel = g_object_ref (channel);
+  job->handle = handle;
+  job->size = size;
+
+  return G_VFS_JOB (job);
+}
+
+/* Might be called on an i/o thread */
+static void
+send_reply (GVfsJob *job)
+{
+  GVfsJobTruncate *op_job = G_VFS_JOB_TRUNCATE (job);
+
+  g_debug ("job_truncate send reply\n");
+
+  if (job->failed)
+    g_vfs_channel_send_error (G_VFS_CHANNEL (op_job->channel), job->error);
+  else
+    g_vfs_write_channel_send_truncated (op_job->channel);
+}
+
+static void
+run (GVfsJob *job)
+{
+  GVfsJobTruncate *op_job = G_VFS_JOB_TRUNCATE (job);
+  GVfsBackendClass *class = G_VFS_BACKEND_GET_CLASS (op_job->backend);
+
+  if (class->truncate)
+    class->truncate (op_job->backend, op_job, op_job->handle, op_job->size);
+  else
+    g_vfs_job_failed (job, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
+                      _("Operation not supported by backend"));
+}
+
+static gboolean
+try (GVfsJob *job)
+{
+  GVfsJobTruncate *op_job = G_VFS_JOB_TRUNCATE (job);
+  GVfsBackendClass *class = G_VFS_BACKEND_GET_CLASS (op_job->backend);
+
+  if (class->try_truncate)
+    return class->try_truncate (op_job->backend,
+                                op_job,
+                                op_job->handle,
+                                op_job->size);
+  else
+    return FALSE;
+}
diff --git a/daemon/gvfsjobtruncate.h b/daemon/gvfsjobtruncate.h
new file mode 100644
index 0000000..9a3f049
--- /dev/null
+++ b/daemon/gvfsjobtruncate.h
@@ -0,0 +1,63 @@
+/* GIO - GLib Input, Output and Streaming Library
+ * 
+ * Copyright (C) 2013 Ross Lagerwall
+ *
+ * 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.
+ */
+
+#ifndef __G_VFS_JOB_TRUNCATE_H__
+#define __G_VFS_JOB_TRUNCATE_H__
+
+#include <gvfsjob.h>
+#include <gvfsbackend.h>
+#include <gvfswritechannel.h>
+
+G_BEGIN_DECLS
+
+#define G_VFS_TYPE_JOB_TRUNCATE         (g_vfs_job_truncate_get_type ())
+#define G_VFS_JOB_TRUNCATE(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), G_VFS_TYPE_JOB_TRUNCATE, 
GVfsJobTruncate))
+#define G_VFS_JOB_TRUNCATE_CLASS(k)     (G_TYPE_CHECK_CLASS_CAST((k), G_VFS_TYPE_JOB_TRUNCATE, 
GVfsJobTruncateClass))
+#define G_VFS_IS_JOB_TRUNCATE(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), G_VFS_TYPE_JOB_TRUNCATE))
+#define G_VFS_IS_JOB_TRUNCATE_CLASS(k)  (G_TYPE_CHECK_CLASS_TYPE ((k), G_VFS_TYPE_JOB_TRUNCATE))
+#define G_VFS_JOB_TRUNCATE_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), G_VFS_TYPE_JOB_TRUNCATE, 
GVfsJobTruncateClass))
+
+typedef struct _GVfsJobTruncateClass   GVfsJobTruncateClass;
+
+struct _GVfsJobTruncate
+{
+  GVfsJob parent_instance;
+
+  GVfsWriteChannel *channel;
+  GVfsBackend *backend;
+  GVfsBackendHandle handle;
+  goffset size;
+};
+
+struct _GVfsJobTruncateClass
+{
+  GVfsJobClass parent_class;
+};
+
+GType g_vfs_job_truncate_get_type (void) G_GNUC_CONST;
+
+GVfsJob *g_vfs_job_truncate_new (GVfsWriteChannel  *channel,
+                                 GVfsBackendHandle  handle,
+                                 goffset            size,
+                                 GVfsBackend       *backend);
+
+G_END_DECLS
+
+#endif /* __G_VFS_JOB_TRUNCATE_H__ */
diff --git a/daemon/gvfswritechannel.c b/daemon/gvfswritechannel.c
index bc71f76..c691deb 100644
--- a/daemon/gvfswritechannel.c
+++ b/daemon/gvfswritechannel.c
@@ -38,6 +38,7 @@
 #include <gvfsdaemonutils.h>
 #include <gvfsjobwrite.h>
 #include <gvfsjobseekwrite.h>
+#include <gvfsjobtruncate.h>
 #include <gvfsjobclosewrite.h>
 #include <gvfsjobqueryinfowrite.h>
 
@@ -137,7 +138,12 @@ write_channel_handle_request (GVfsChannel *channel,
                                      ((goffset)arg1) | (((goffset)arg2) << 32),
                                      backend);
       break;
-      
+    case G_VFS_DAEMON_SOCKET_PROTOCOL_REQUEST_TRUNCATE:
+      job = g_vfs_job_truncate_new (write_channel,
+                                    backend_handle,
+                                    ((goffset)arg1) | (((goffset)arg2) << 32),
+                                    backend);
+      break;
     case G_VFS_DAEMON_SOCKET_PROTOCOL_REQUEST_QUERY_INFO:
       attrs = g_strndup (data, data_len);
       job = g_vfs_job_query_info_write_new (write_channel,
@@ -181,6 +187,24 @@ g_vfs_write_channel_send_seek_offset (GVfsWriteChannel *write_channel,
 /* Might be called on an i/o thread
  */
 void
+g_vfs_write_channel_send_truncated (GVfsWriteChannel *write_channel)
+{
+  GVfsDaemonSocketProtocolReply reply;
+  GVfsChannel *channel;
+
+  channel = G_VFS_CHANNEL (write_channel);
+
+  reply.type = g_htonl (G_VFS_DAEMON_SOCKET_PROTOCOL_REPLY_TRUNCATED);
+  reply.seq_nr = g_htonl (g_vfs_channel_get_current_seq_nr (channel));
+  reply.arg1 = g_htonl (0);
+  reply.arg2 = g_htonl (0);
+
+  g_vfs_channel_send_reply (channel, &reply, NULL, 0);
+}
+
+/* Might be called on an i/o thread
+ */
+void
 g_vfs_write_channel_send_closed (GVfsWriteChannel *write_channel,
                                 const char       *etag)
 {
diff --git a/daemon/gvfswritechannel.h b/daemon/gvfswritechannel.h
index 4fba258..d721f23 100644
--- a/daemon/gvfswritechannel.h
+++ b/daemon/gvfswritechannel.h
@@ -55,6 +55,7 @@ void              g_vfs_write_channel_send_closed      (GVfsWriteChannel *write_
                                                        const char       *etag);
 void              g_vfs_write_channel_send_seek_offset (GVfsWriteChannel *write_channel,
                                                        goffset           offset);
+void              g_vfs_write_channel_send_truncated   (GVfsWriteChannel *write_channel);
 
 G_END_DECLS
 



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]