summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlexander Larsson <alexl@redhat.com>2015-04-16 21:00:21 +0200
committerAlexander Larsson <alexl@redhat.com>2015-04-16 21:00:21 +0200
commit9b2439d22870baf33b6d87191202339db41f60f8 (patch)
tree1d17cb5a94bfa54ff5459af31999e9ba734df21a
parent13aafd9f3518c9d39b4b7e25558fa19d899af221 (diff)
downloadgvfs-wip/alexl/documents.tar.gz
Initial version to do simple read and replace from xdg-document-portalwip/alexl/documents
For example: $ echo foo | gvfs-save document:1 $ gvfs-cat document:1 foo $ echo bar | gvfs-save document:1 $ gvfs-cat document:1 bar
-rw-r--r--client/Makefile.am3
-rw-r--r--client/gdaemonvfs.c12
-rw-r--r--client/gvfsdocumentfile.c691
-rw-r--r--client/gvfsdocumentfile.h58
-rw-r--r--client/gvfsdocumentinputstream.c311
-rw-r--r--client/gvfsdocumentinputstream.h39
-rw-r--r--client/gvfsdocumentoutputstream.c426
-rw-r--r--client/gvfsdocumentoutputstream.h47
8 files changed, 1587 insertions, 0 deletions
diff --git a/client/Makefile.am b/client/Makefile.am
index 8c7b76a1..ce9c5480 100644
--- a/client/Makefile.am
+++ b/client/Makefile.am
@@ -39,6 +39,9 @@ vfssources = \
gvfsiconloadable.c gvfsiconloadable.h \
gvfsuriutils.c gvfsuriutils.h \
gvfsurimapper.c gvfsurimapper.h \
+ gvfsdocumentfile.c gvfsdocumentfile.h \
+ gvfsdocumentinputstream.c gvfsdocumentinputstream.h \
+ gvfsdocumentoutputstream.c gvfsdocumentoutputstream.h \
$(URI_PARSER_SOURCES) \
$(NULL)
diff --git a/client/gdaemonvfs.c b/client/gdaemonvfs.c
index 19e65b1a..dfca085d 100644
--- a/client/gdaemonvfs.c
+++ b/client/gdaemonvfs.c
@@ -29,6 +29,7 @@
#include "gdaemonvfs.h"
#include "gvfsuriutils.h"
#include "gdaemonfile.h"
+#include "gvfsdocumentfile.h"
#include <gio/gio.h>
#include <gvfsdaemonprotocol.h>
#include <gmodule.h>
@@ -410,6 +411,17 @@ g_daemon_vfs_get_file_for_uri (GVfs *vfs,
g_free (path);
return file;
}
+
+ if (g_ascii_strncasecmp (uri, "document:", 9) == 0)
+ {
+ file = gvfs_document_file_new (uri);
+
+ if (file == NULL)
+ /* Dummy file */
+ return g_vfs_get_file_for_uri (G_DAEMON_VFS (vfs)->wrapped_vfs, uri);
+
+ return file;
+ }
if (get_mountspec_from_uri (daemon_vfs, uri, &spec, &path))
{
diff --git a/client/gvfsdocumentfile.c b/client/gvfsdocumentfile.c
new file mode 100644
index 00000000..a38d9971
--- /dev/null
+++ b/client/gvfsdocumentfile.c
@@ -0,0 +1,691 @@
+/* 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 <unistd.h>
+#include <string.h>
+#include <stdio.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+
+#include "gvfsdocumentfile.h"
+#include "gvfsdocumentinputstream.h"
+#include "gvfsdocumentoutputstream.h"
+#include <glib/gi18n-lib.h>
+#include <gio/gio.h>
+#include <gvfsdbus.h>
+#include <gio/gunixfdlist.h>
+
+static void gvfs_document_file_file_iface_init (GFileIface *iface);
+
+static void gvfs_document_file_read_async (GFile *file,
+ int io_priority,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer callback_data);
+
+G_DEFINE_TYPE_WITH_CODE (GVfsDocumentFile, gvfs_document_file, G_TYPE_OBJECT,
+ G_IMPLEMENT_INTERFACE (G_TYPE_FILE,
+ gvfs_document_file_file_iface_init))
+
+static void
+gvfs_document_file_finalize (GObject *object)
+{
+ GVfsDocumentFile *doc;
+
+ doc = GVFS_DOCUMENT_FILE (object);
+
+ g_free (doc->path);
+
+ if (G_OBJECT_CLASS (gvfs_document_file_parent_class)->finalize)
+ (*G_OBJECT_CLASS (gvfs_document_file_parent_class)->finalize) (object);
+}
+
+static void
+gvfs_document_file_class_init (GVfsDocumentFileClass *klass)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+
+ gobject_class->finalize = gvfs_document_file_finalize;
+}
+
+static void
+gvfs_document_file_init (GVfsDocumentFile *doc)
+{
+}
+
+static char *
+canonicalize_path (char *path)
+{
+ char *p, *q;
+
+ /* Canonicalize multiple consecutive slashes */
+ p = path;
+ while (*p != 0)
+ {
+ q = p;
+ while (*q && *q == '/')
+ q++;
+
+ if (q > p + 1)
+ memmove (p+1, q, strlen (q)+1);
+
+ /* Skip over the one separator */
+ p++;
+
+ /* Drop trailing (not first) slash */
+ if (*p == 0 && p > path + 1)
+ {
+ p--;
+ *p = 0;
+ }
+
+ /* Skip until next separator */
+ while (*p != 0 && *p != '/')
+ p++;
+ }
+
+ return path;
+}
+
+static char *
+path_from_uri (const char *uri)
+{
+ char *to_free = NULL;
+ char *path, *res, *p, *q;
+ const char *path_part, *hash;
+ int len = -1;
+
+ path_part = uri + strlen ("document:");
+
+ if (g_str_has_prefix (path_part, "///"))
+ path_part += 2;
+ else if (g_str_has_prefix (path_part, "//"))
+ return NULL; /* Has hostname, not valid */
+
+ hash = strchr (path_part, '#');
+ if (hash != NULL)
+ {
+ len = hash - path_part;
+ path_part = to_free = g_strndup (path_part, len);
+ }
+
+ res = g_uri_unescape_string (path_part, "/");
+
+ g_clear_pointer (&to_free, g_free);
+
+ if (res == NULL)
+ return NULL;
+
+ if (*res != '/')
+ {
+ to_free = res;
+ res = g_strconcat ("/", res, NULL);
+ g_free (to_free);
+ }
+
+ return canonicalize_path (res);
+}
+
+/* Takes ownership of path */
+static GFile *
+gvfs_document_file_new_steals_path (char *path)
+{
+ GVfsDocumentFile *doc;
+
+ doc = g_object_new (GVFS_TYPE_DOCUMENT_FILE, NULL);
+ doc->path = path;
+
+ return G_FILE (doc);
+}
+
+GFile *
+gvfs_document_file_new (const char *uri)
+{
+ GVfsDocumentFile *doc;
+ char *path;
+
+ path = path_from_uri (uri);
+
+ if (path == NULL)
+ return NULL; /* Creates a dummy GFile */
+
+ return gvfs_document_file_new_steals_path (path);
+}
+
+static gboolean
+gvfs_document_file_is_native (GFile *file)
+{
+ return FALSE;
+}
+
+static gboolean
+gvfs_document_file_has_uri_scheme (GFile *file,
+ const char *uri_scheme)
+{
+ GVfsDocumentFile *doc = GVFS_DOCUMENT_FILE (file);
+
+ return g_ascii_strcasecmp ("document", uri_scheme) == 0;
+}
+
+static char *
+gvfs_document_file_get_uri_scheme (GFile *file)
+{
+ GVfsDocumentFile *doc = GVFS_DOCUMENT_FILE (file);
+ const char *scheme;
+
+ return g_strdup ("document");
+}
+
+static char *
+gvfs_document_file_get_basename (GFile *file)
+{
+ GVfsDocumentFile *doc = GVFS_DOCUMENT_FILE (file);
+ char *last_slash;
+
+ return g_path_get_basename (doc->path);
+}
+
+static char *
+gvfs_document_file_get_path (GFile *file)
+{
+ return NULL;
+}
+
+static char *
+gvfs_document_file_get_uri (GFile *file)
+{
+ GVfsDocumentFile *doc = GVFS_DOCUMENT_FILE (file);
+ char *res, *escaped_path;
+
+ escaped_path = g_uri_escape_string (doc->path,
+ G_URI_RESERVED_CHARS_ALLOWED_IN_PATH, FALSE);
+ res = g_strconcat ("document://", escaped_path, NULL);
+ g_free (escaped_path);
+
+ return res;
+}
+
+static char *
+gvfs_document_file_get_parse_name (GFile *file)
+{
+ GVfsDocumentFile *doc = GVFS_DOCUMENT_FILE (file);
+
+ return gvfs_document_file_get_uri (file);
+}
+
+static GFile *
+gvfs_document_file_get_parent (GFile *file)
+{
+ GVfsDocumentFile *doc = GVFS_DOCUMENT_FILE (file);
+ GVfsDocumentFile *parent;
+ char *dirname;
+
+ if (strcmp (doc->path, "/"))
+ return NULL;
+
+ dirname = g_path_get_dirname (doc->path);
+
+ return gvfs_document_file_new_steals_path (dirname);
+}
+
+static GFile *
+gvfs_document_file_dup (GFile *file)
+{
+ GVfsDocumentFile *doc = GVFS_DOCUMENT_FILE (file);
+
+ return gvfs_document_file_new_steals_path (g_strdup (doc->path));
+}
+
+static guint
+gvfs_document_file_hash (GFile *file)
+{
+ GVfsDocumentFile *doc = GVFS_DOCUMENT_FILE (file);
+
+ return g_str_hash (doc->path);
+}
+
+static gboolean
+gvfs_document_file_equal (GFile *file1,
+ GFile *file2)
+{
+ GVfsDocumentFile *doc1 = GVFS_DOCUMENT_FILE (file1);
+ GVfsDocumentFile *doc2 = GVFS_DOCUMENT_FILE (file2);
+
+ return g_str_equal (doc1->path, doc2->path);
+}
+
+static const char *
+match_prefix (const char *path,
+ const char *prefix)
+{
+ int prefix_len;
+
+ prefix_len = strlen (prefix);
+ if (strncmp (path, prefix, prefix_len) != 0)
+ return NULL;
+
+ /* Handle the case where prefix is the root, so that
+ * the IS_DIR_SEPRARATOR check below works */
+ if (prefix_len > 0 &&
+ prefix[prefix_len-1] == '/')
+ prefix_len--;
+
+ return path + prefix_len;
+}
+
+static gboolean
+gvfs_document_file_prefix_matches (GFile *parent,
+ GFile *descendant)
+{
+ GVfsDocumentFile *parent_doc = GVFS_DOCUMENT_FILE (parent);
+ GVfsDocumentFile *descendant_doc = GVFS_DOCUMENT_FILE (descendant);
+ const char *remainder;
+
+ remainder = match_prefix (descendant_doc->path, parent_doc->path);
+ if (remainder != NULL && *remainder == '/')
+ return TRUE;
+ return FALSE;
+}
+
+static char *
+gvfs_document_file_get_relative_path (GFile *parent,
+ GFile *descendant)
+{
+ GVfsDocumentFile *parent_doc = GVFS_DOCUMENT_FILE (parent);
+ GVfsDocumentFile *descendant_doc = GVFS_DOCUMENT_FILE (descendant);
+ const char *remainder;
+
+ remainder = match_prefix (descendant_doc->path, parent_doc->path);
+ if (remainder != NULL && *remainder == '/')
+ return g_strdup (remainder + 1);
+ return NULL;
+}
+
+static GFile *
+gvfs_document_file_resolve_relative_path (GFile *file,
+ const char *relative_path)
+{
+ GVfsDocumentFile *doc = GVFS_DOCUMENT_FILE (file);
+ char *filename;
+ GFile *child;
+
+ if (g_path_is_absolute (relative_path))
+ return gvfs_document_file_new_steals_path (canonicalize_path (g_strdup (doc->path)));
+
+ filename = g_build_filename (doc->path, relative_path, NULL);
+ child = gvfs_document_file_new_steals_path (canonicalize_path (filename));
+ g_free (filename);
+
+ return child;
+}
+
+static GFileEnumerator *
+gvfs_document_file_enumerate_children (GFile *file,
+ const char *attributes,
+ GFileQueryInfoFlags flags,
+ GCancellable *cancellable,
+ GError **error)
+{
+ return NULL;
+}
+
+static GFileInfo *
+gvfs_document_file_query_info (GFile *file,
+ const char *attributes,
+ GFileQueryInfoFlags flags,
+ GCancellable *cancellable,
+ GError **error)
+{
+ return NULL;
+}
+
+static void
+gvfs_document_file_query_info_async (GFile *file,
+ const char *attributes,
+ GFileQueryInfoFlags flags,
+ int io_priority,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+}
+
+static GFileInfo *
+gvfs_document_file_query_info_finish (GFile *file,
+ GAsyncResult *res,
+ GError **error)
+{
+ return NULL;
+}
+
+static void
+gvfs_document_file_read_async (GFile *file,
+ int io_priority,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer callback_data)
+{
+}
+
+static GFileInputStream *
+gvfs_document_file_read_finish (GFile *file,
+ GAsyncResult *res,
+ GError **error)
+{
+ GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT (res);
+ gpointer op;
+
+ op = g_simple_async_result_get_op_res_gpointer (simple);
+ if (op)
+ return g_object_ref (op);
+
+ return NULL;
+}
+
+static gboolean
+verify_file_path (GVfsDocumentFile *doc,
+ GError **error)
+{
+ if (strcmp (doc->path, "/") == 0)
+ {
+ g_set_error_literal (error, G_IO_ERROR,
+ G_IO_ERROR_IS_DIRECTORY,
+ _("Can't open directory"));
+ return FALSE;
+ }
+
+ if (strchr (doc->path + 1, '/') != NULL)
+ {
+ g_set_error_literal (error, G_IO_ERROR,
+ G_IO_ERROR_NOT_FOUND,
+ _("No such file"));
+ return FALSE;
+ }
+
+ return TRUE;
+
+}
+
+static GVariant *
+sync_document_call (GVfsDocumentFile *doc,
+ const char *method,
+ GVariant *parameters,
+ const GVariantType *reply_type,
+ GUnixFDList **out_fd_list,
+ GCancellable *cancellable,
+ GError **error)
+{
+ GVariant *res;
+ GDBusConnection *bus;
+ GUnixFDList *ret_fd_list;
+ GVariant *reply, *fd_v;
+ char *path;
+ int handle, fd;
+
+ if (!verify_file_path (doc, error))
+ return NULL;
+
+ bus = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, error);
+ if (bus == NULL)
+ return NULL;
+
+ path = g_build_filename ("/org/freedesktop/portal/document", doc->path, NULL);
+
+ res = g_dbus_connection_call_with_unix_fd_list_sync (bus,
+ "org.freedesktop.portal.DocumentPortal",
+ path,
+ "org.freedesktop.portal.Document",
+ method,
+ parameters, reply_type,
+ G_DBUS_CALL_FLAGS_NONE,
+ -1,
+ NULL,
+ out_fd_list,
+ cancellable,
+ error);
+
+ g_object_unref (bus);
+ return res;
+}
+
+static GFileInputStream *
+gvfs_document_file_read (GFile *file,
+ GCancellable *cancellable,
+ GError **error)
+{
+ GVfsDocumentFile *doc = GVFS_DOCUMENT_FILE (file);
+ GDBusConnection *bus;
+ GUnixFDList *ret_fd_list;
+ GVariant *reply, *fd_v;
+ char *path;
+ int handle, fd;
+
+ reply = sync_document_call (doc, "Read",
+ g_variant_new ("()"),
+ G_VARIANT_TYPE("(h)"),
+ &ret_fd_list,
+ cancellable, error);
+ if (reply == NULL)
+ return NULL;
+
+ if (ret_fd_list == NULL)
+ {
+ g_variant_unref (reply);
+ g_set_error_literal (error, G_IO_ERROR,
+ G_IO_ERROR_FAILED,
+ _("No file descriptor returned"));
+ return NULL;
+ }
+
+ g_variant_get (reply, "(@h)", &fd_v);
+ handle = g_variant_get_handle (fd_v);
+ g_variant_unref (reply);
+
+ fd = g_unix_fd_list_get (ret_fd_list, handle, error);
+ g_object_unref (ret_fd_list);
+ if (fd == -1)
+ return NULL;
+
+ return _gvfs_document_input_stream_new (fd);
+}
+
+static GFileOutputStream *
+gvfs_document_file_create (GFile *file,
+ GFileCreateFlags flags,
+ GCancellable *cancellable,
+ GError **error)
+{
+ return NULL;
+}
+
+static GFileOutputStream *
+gvfs_document_file_replace (GFile *file,
+ const char *etag,
+ gboolean make_backup,
+ GFileCreateFlags flags,
+ GCancellable *cancellable,
+ GError **error)
+{
+ GVfsDocumentFile *doc = GVFS_DOCUMENT_FILE (file);
+ GDBusConnection *bus;
+ GUnixFDList *ret_fd_list;
+ GVariant *reply, *fd_v;
+ char *path;
+ int handle, fd;
+ guint32 id;
+ char *flags_array[] = {
+ NULL
+ };
+
+ reply = sync_document_call (doc, "PrepareUpdate",
+ g_variant_new ("(s^as)",
+ etag ? etag : "",
+ flags_array),
+ G_VARIANT_TYPE("(uh)"),
+ &ret_fd_list,
+ cancellable, error);
+ if (reply == NULL)
+ return NULL;
+
+ if (ret_fd_list == NULL)
+ {
+ g_variant_unref (reply);
+ g_set_error_literal (error, G_IO_ERROR,
+ G_IO_ERROR_FAILED,
+ _("No file descriptor returned"));
+ return NULL;
+ }
+
+ g_variant_get (reply, "(u@h)", &id, &fd_v);
+ handle = g_variant_get_handle (fd_v);
+ g_variant_unref (reply);
+
+ fd = g_unix_fd_list_get (ret_fd_list, handle, error);
+ g_object_unref (ret_fd_list);
+ if (fd == -1)
+ return NULL;
+
+ return gvfs_document_output_stream_new (doc->path, id, fd);
+}
+
+static void
+gvfs_document_file_create_async (GFile *file,
+ GFileCreateFlags flags,
+ int io_priority,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ file_open_write_async (file,
+ 0, "", FALSE, flags, io_priority,
+ cancellable,
+ callback, user_data);
+}
+
+static GFileOutputStream *
+gvfs_document_file_create_finish (GFile *file,
+ GAsyncResult *res,
+ GError **error)
+{
+ GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT (res);
+ GFileOutputStream *output_stream;
+
+ output_stream = g_simple_async_result_get_op_res_gpointer (simple);
+ if (output_stream)
+ return g_object_ref (output_stream);
+
+ return NULL;
+}
+
+static void
+gvfs_document_file_enumerate_children_async (GFile *file,
+ const char *attributes,
+ GFileQueryInfoFlags flags,
+ int io_priority,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+}
+
+static GFileEnumerator *
+gvfs_document_file_enumerate_children_finish (GFile *file,
+ GAsyncResult *res,
+ GError **error)
+{
+ GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT (res);
+ GFileEnumerator *enumerator;
+
+ enumerator = g_simple_async_result_get_op_res_gpointer (simple);
+ if (enumerator)
+ return g_object_ref (enumerator);
+
+ return NULL;
+}
+
+static void
+gvfs_document_file_replace_async (GFile *file,
+ const char *etag,
+ gboolean make_backup,
+ GFileCreateFlags flags,
+ int io_priority,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+}
+
+static GFileOutputStream *
+gvfs_document_file_replace_finish (GFile *file,
+ GAsyncResult *res,
+ GError **error)
+{
+ GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT (res);
+ GFileOutputStream *output_stream;
+
+ output_stream = g_simple_async_result_get_op_res_gpointer (simple);
+ if (output_stream)
+ return g_object_ref (output_stream);
+
+ return NULL;
+}
+
+static void
+gvfs_document_file_file_iface_init (GFileIface *iface)
+{
+ iface->dup = gvfs_document_file_dup;
+ iface->hash = gvfs_document_file_hash;
+ iface->equal = gvfs_document_file_equal;
+ iface->is_native = gvfs_document_file_is_native;
+ iface->has_uri_scheme = gvfs_document_file_has_uri_scheme;
+ iface->get_uri_scheme = gvfs_document_file_get_uri_scheme;
+ iface->get_basename = gvfs_document_file_get_basename;
+ iface->get_path = gvfs_document_file_get_path;
+ iface->get_uri = gvfs_document_file_get_uri;
+ iface->get_parse_name = gvfs_document_file_get_parse_name;
+ iface->get_parent = gvfs_document_file_get_parent;
+ iface->prefix_matches = gvfs_document_file_prefix_matches;
+ iface->get_relative_path = gvfs_document_file_get_relative_path;
+ iface->resolve_relative_path = gvfs_document_file_resolve_relative_path;
+ iface->enumerate_children = gvfs_document_file_enumerate_children;
+ iface->query_info = gvfs_document_file_query_info;
+ iface->query_info_async = gvfs_document_file_query_info_async;
+ iface->query_info_finish = gvfs_document_file_query_info_finish;
+ iface->read_fn = gvfs_document_file_read;
+ iface->create = gvfs_document_file_create;
+ iface->replace = gvfs_document_file_replace;
+
+ /* Async operations */
+
+ /*
+ iface->read_async = gvfs_document_file_read_async;
+ iface->read_finish = gvfs_document_file_read_finish;
+ iface->create_async = gvfs_document_file_create_async;
+ iface->create_finish = gvfs_document_file_create_finish;
+ iface->enumerate_children_async = gvfs_document_file_enumerate_children_async;
+ iface->enumerate_children_finish = gvfs_document_file_enumerate_children_finish;
+ iface->replace_async = gvfs_document_file_replace_async;
+ iface->replace_finish = gvfs_document_file_replace_finish;
+ */
+}
diff --git a/client/gvfsdocumentfile.h b/client/gvfsdocumentfile.h
new file mode 100644
index 00000000..3e2a1ee5
--- /dev/null
+++ b/client/gvfsdocumentfile.h
@@ -0,0 +1,58 @@
+/* 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>
+ */
+
+#ifndef __G_DOCUMENT_FILE_H__
+#define __G_DOCUMENT_FILE_H__
+
+#include <gio/gio.h>
+
+G_BEGIN_DECLS
+
+#define GVFS_TYPE_DOCUMENT_FILE (gvfs_document_file_get_type ())
+#define GVFS_DOCUMENT_FILE(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GVFS_TYPE_DOCUMENT_FILE, GVfsDocumentFile))
+#define GVFS_DOCUMENT_FILE_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), GVFS_TYPE_DOCUMENT_FILE, GVfsDocumentFileClass))
+#define GVFS_IS_DOCUMENT_FILE(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GVFS_TYPE_DOCUMENT_FILE))
+#define GVFS_IS_DOCUMENT_FILE_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GVFS_TYPE_DOCUMENT_FILE))
+#define GVFS_DOCUMENT_FILE_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), G_VFS_TYPE_DOCUMENT_FILE, GVfsDocumentFileClass))
+
+typedef struct _GVfsDocumentFile GVfsDocumentFile;
+typedef struct _GVfsDocumentFileClass GVfsDocumentFileClass;
+
+struct _GVfsDocumentFileClass
+{
+ GObjectClass parent_class;
+};
+
+struct _GVfsDocumentFile
+{
+ GObject parent_instance;
+
+ char *path;
+};
+
+GType gvfs_document_file_get_type (void) G_GNUC_CONST;
+
+GFile * gvfs_document_file_new (const char *uri);
+
+G_END_DECLS
+
+#endif /* __G_DOCUMENT_FILE_H__ */
diff --git a/client/gvfsdocumentinputstream.c b/client/gvfsdocumentinputstream.c
new file mode 100644
index 00000000..f1ea855b
--- /dev/null
+++ b/client/gvfsdocumentinputstream.c
@@ -0,0 +1,311 @@
+#include "config.h"
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <errno.h>
+
+#include <glib.h>
+#include <glib/gstdio.h>
+#include "gvfsdocumentinputstream.h"
+
+#include <unistd.h>
+#include "glib-unix.h"
+#include <gio/gfiledescriptorbased.h>
+#include <glib/gi18n-lib.h>
+
+
+struct _GVfsDocumentInputStreamPrivate {
+ int fd;
+};
+
+static void g_file_descriptor_based_iface_init (GFileDescriptorBasedIface *iface);
+
+#define gvfs_document_input_stream_get_type _gvfs_document_input_stream_get_type
+G_DEFINE_TYPE_WITH_CODE (GVfsDocumentInputStream, gvfs_document_input_stream, G_TYPE_FILE_INPUT_STREAM,
+ G_ADD_PRIVATE (GVfsDocumentInputStream)
+ G_IMPLEMENT_INTERFACE (G_TYPE_FILE_DESCRIPTOR_BASED,
+ g_file_descriptor_based_iface_init))
+
+static gssize gvfs_document_input_stream_read (GInputStream *stream,
+ void *buffer,
+ gsize count,
+ GCancellable *cancellable,
+ GError **error);
+static gssize gvfs_document_input_stream_skip (GInputStream *stream,
+ gsize count,
+ GCancellable *cancellable,
+ GError **error);
+static gboolean gvfs_document_input_stream_close (GInputStream *stream,
+ GCancellable *cancellable,
+ GError **error);
+static goffset gvfs_document_input_stream_tell (GFileInputStream *stream);
+static gboolean gvfs_document_input_stream_can_seek (GFileInputStream *stream);
+static gboolean gvfs_document_input_stream_seek (GFileInputStream *stream,
+ goffset offset,
+ GSeekType type,
+ GCancellable *cancellable,
+ GError **error);
+static GFileInfo *gvfs_document_input_stream_query_info (GFileInputStream *stream,
+ const char *attributes,
+ GCancellable *cancellable,
+ GError **error);
+static int gvfs_document_input_stream_get_fd (GFileDescriptorBased *stream);
+
+static void
+gvfs_document_input_stream_class_init (GVfsDocumentInputStreamClass *klass)
+{
+ GInputStreamClass *stream_class = G_INPUT_STREAM_CLASS (klass);
+ GFileInputStreamClass *file_stream_class = G_FILE_INPUT_STREAM_CLASS (klass);
+
+ stream_class->read_fn = gvfs_document_input_stream_read;
+ stream_class->skip = gvfs_document_input_stream_skip;
+ stream_class->close_fn = gvfs_document_input_stream_close;
+ file_stream_class->tell = gvfs_document_input_stream_tell;
+ file_stream_class->can_seek = gvfs_document_input_stream_can_seek;
+ file_stream_class->seek = gvfs_document_input_stream_seek;
+ file_stream_class->query_info = gvfs_document_input_stream_query_info;
+}
+
+static void
+g_file_descriptor_based_iface_init (GFileDescriptorBasedIface *iface)
+{
+ iface->get_fd = gvfs_document_input_stream_get_fd;
+}
+
+static void
+gvfs_document_input_stream_init (GVfsDocumentInputStream *info)
+{
+ info->priv = gvfs_document_input_stream_get_instance_private (info);
+}
+
+GFileInputStream *
+_gvfs_document_input_stream_new (int fd)
+{
+ GVfsDocumentInputStream *stream;
+
+ stream = g_object_new (GVFS_TYPE_DOCUMENT_INPUT_STREAM, NULL);
+ stream->priv->fd = fd;
+
+ return G_FILE_INPUT_STREAM (stream);
+}
+
+static gssize
+gvfs_document_input_stream_read (GInputStream *stream,
+ void *buffer,
+ gsize count,
+ GCancellable *cancellable,
+ GError **error)
+{
+ GVfsDocumentInputStream *file;
+ gssize res;
+
+ file = GVFS_DOCUMENT_INPUT_STREAM (stream);
+
+ res = -1;
+ while (1)
+ {
+ if (g_cancellable_set_error_if_cancelled (cancellable, error))
+ break;
+ res = read (file->priv->fd, buffer, count);
+ if (res == -1)
+ {
+ int errsv = errno;
+
+ if (errsv == EINTR)
+ continue;
+
+ g_set_error (error, G_IO_ERROR,
+ g_io_error_from_errno (errsv),
+ _("Error reading from file: %s"),
+ g_strerror (errsv));
+ }
+
+ break;
+ }
+
+ return res;
+}
+
+static gssize
+gvfs_document_input_stream_skip (GInputStream *stream,
+ gsize count,
+ GCancellable *cancellable,
+ GError **error)
+{
+ off_t start, end;
+ GVfsDocumentInputStream *file;
+
+ file = GVFS_DOCUMENT_INPUT_STREAM (stream);
+
+ if (g_cancellable_set_error_if_cancelled (cancellable, error))
+ return -1;
+
+ start = lseek (file->priv->fd, 0, SEEK_CUR);
+ if (start == -1)
+ {
+ int errsv = errno;
+
+ g_set_error (error, G_IO_ERROR,
+ g_io_error_from_errno (errsv),
+ _("Error seeking in file: %s"),
+ g_strerror (errsv));
+ return -1;
+ }
+
+ end = lseek (file->priv->fd, 0, SEEK_END);
+ if (end == -1)
+ {
+ int errsv = errno;
+
+ g_set_error (error, G_IO_ERROR,
+ g_io_error_from_errno (errsv),
+ _("Error seeking in file: %s"),
+ g_strerror (errsv));
+ return -1;
+ }
+
+ if (end - start > count)
+ {
+ end = lseek (file->priv->fd, count - (end - start), SEEK_CUR);
+ if (end == -1)
+ {
+ int errsv = errno;
+
+ g_set_error (error, G_IO_ERROR,
+ g_io_error_from_errno (errsv),
+ _("Error seeking in file: %s"),
+ g_strerror (errsv));
+ return -1;
+ }
+ }
+
+ return end - start;
+}
+
+static gboolean
+gvfs_document_input_stream_close (GInputStream *stream,
+ GCancellable *cancellable,
+ GError **error)
+{
+ GVfsDocumentInputStream *file;
+
+ file = GVFS_DOCUMENT_INPUT_STREAM (stream);
+
+ if (!g_close (file->priv->fd, NULL))
+ {
+ int errsv = errno;
+
+ g_set_error (error, G_IO_ERROR,
+ g_io_error_from_errno (errsv),
+ _("Error closing file: %s"),
+ g_strerror (errsv));
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+
+static goffset
+gvfs_document_input_stream_tell (GFileInputStream *stream)
+{
+ GVfsDocumentInputStream *file;
+ off_t pos;
+
+ file = GVFS_DOCUMENT_INPUT_STREAM (stream);
+
+ pos = lseek (file->priv->fd, 0, SEEK_CUR);
+
+ if (pos == (off_t)-1)
+ return 0;
+
+ return pos;
+}
+
+static gboolean
+gvfs_document_input_stream_can_seek (GFileInputStream *stream)
+{
+ GVfsDocumentInputStream *file;
+ off_t pos;
+
+ file = GVFS_DOCUMENT_INPUT_STREAM (stream);
+
+ pos = lseek (file->priv->fd, 0, SEEK_CUR);
+
+ if (pos == (off_t)-1 && errno == ESPIPE)
+ return FALSE;
+
+ return TRUE;
+}
+
+static int
+seek_type_to_lseek (GSeekType type)
+{
+ switch (type)
+ {
+ default:
+ case G_SEEK_CUR:
+ return SEEK_CUR;
+
+ case G_SEEK_SET:
+ return SEEK_SET;
+
+ case G_SEEK_END:
+ return SEEK_END;
+ }
+}
+
+static gboolean
+gvfs_document_input_stream_seek (GFileInputStream *stream,
+ goffset offset,
+ GSeekType type,
+ GCancellable *cancellable,
+ GError **error)
+{
+ GVfsDocumentInputStream *file;
+ off_t pos;
+
+ file = GVFS_DOCUMENT_INPUT_STREAM (stream);
+
+ pos = lseek (file->priv->fd, offset, seek_type_to_lseek (type));
+
+ if (pos == (off_t)-1)
+ {
+ int errsv = errno;
+
+ g_set_error (error, G_IO_ERROR,
+ g_io_error_from_errno (errsv),
+ _("Error seeking in file: %s"),
+ g_strerror (errsv));
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static GFileInfo *
+gvfs_document_input_stream_query_info (GFileInputStream *stream,
+ const char *attributes,
+ GCancellable *cancellable,
+ GError **error)
+{
+ GVfsDocumentInputStream *file;
+ GFileInfo *info;
+
+ file = GVFS_DOCUMENT_INPUT_STREAM (stream);
+
+ if (g_cancellable_set_error_if_cancelled (cancellable, error))
+ return NULL;
+
+ info = g_file_info_new ();
+
+ return info;
+}
+
+static int
+gvfs_document_input_stream_get_fd (GFileDescriptorBased *fd_based)
+{
+ GVfsDocumentInputStream *stream = GVFS_DOCUMENT_INPUT_STREAM (fd_based);
+ return stream->priv->fd;
+}
diff --git a/client/gvfsdocumentinputstream.h b/client/gvfsdocumentinputstream.h
new file mode 100644
index 00000000..f8488c4a
--- /dev/null
+++ b/client/gvfsdocumentinputstream.h
@@ -0,0 +1,39 @@
+
+#ifndef __GVFS_DOCUMENT_INPUT_STREAM_H__
+#define __GVFS_DOCUMENT_INPUT_STREAM_H__
+
+#include <gio/gio.h>
+
+G_BEGIN_DECLS
+
+#define GVFS_TYPE_DOCUMENT_INPUT_STREAM (_gvfs_document_input_stream_get_type ())
+#define GVFS_DOCUMENT_INPUT_STREAM(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GVFS_TYPE_DOCUMENT_INPUT_STREAM, GVfsDocumentInputStream))
+#define GVFS_DOCUMENT_INPUT_STREAM_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), GVFS_TYPE_DOCUMENT_INPUT_STREAM, GVfsDocumentInputStreamClass))
+#define GVFS_IS_DOCUMENT_INPUT_STREAM(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GVFS_TYPE_DOCUMENT_INPUT_STREAM))
+#define GVFS_IS_DOCUMENT_INPUT_STREAM_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GVFS_TYPE_DOCUMENT_INPUT_STREAM))
+#define GVFS_DOCUMENT_INPUT_STREAM_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GVFS_TYPE_DOCUMENT_INPUT_STREAM, GVfsDocumentInputStreamClass))
+
+typedef struct _GVfsDocumentInputStream GVfsDocumentInputStream;
+typedef struct _GVfsDocumentInputStreamClass GVfsDocumentInputStreamClass;
+typedef struct _GVfsDocumentInputStreamPrivate GVfsDocumentInputStreamPrivate;
+
+struct _GVfsDocumentInputStream
+{
+ GFileInputStream parent_instance;
+
+ /*< private >*/
+ GVfsDocumentInputStreamPrivate *priv;
+};
+
+struct _GVfsDocumentInputStreamClass
+{
+ GFileInputStreamClass parent_class;
+};
+
+GType _gvfs_document_input_stream_get_type (void) G_GNUC_CONST;
+
+GFileInputStream *_gvfs_document_input_stream_new (int fd);
+
+G_END_DECLS
+
+#endif /* __GVFS_DOCUMENT_INPUT_STREAM_H__ */
diff --git a/client/gvfsdocumentoutputstream.c b/client/gvfsdocumentoutputstream.c
new file mode 100644
index 00000000..ab30e15a
--- /dev/null
+++ b/client/gvfsdocumentoutputstream.c
@@ -0,0 +1,426 @@
+/* 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, see <http://www.gnu.org/licenses/>.
+ *
+ * Author: Alexander Larsson <alexl@redhat.com>
+ */
+
+#include "config.h"
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <string.h>
+
+#include <glib.h>
+#include <glib/gstdio.h>
+#include <glib/gi18n-lib.h>
+#include <gio/gio.h>
+
+#include <unistd.h>
+#include <gio/gfiledescriptorbased.h>
+
+#include "gvfsdocumentoutputstream.h"
+
+#ifndef O_BINARY
+#define O_BINARY 0
+#endif
+
+struct _GVfsDocumentOutputStreamPrivate {
+ char *etag;
+ int fd;
+ guint32 id;
+ char *doc_handle;
+};
+
+static void g_file_descriptor_based_iface_init (GFileDescriptorBasedIface *iface);
+
+#define gvfs_document_output_stream_get_type _gvfs_document_output_stream_get_type
+G_DEFINE_TYPE_WITH_CODE (GVfsDocumentOutputStream, gvfs_document_output_stream, G_TYPE_FILE_OUTPUT_STREAM,
+ G_ADD_PRIVATE (GVfsDocumentOutputStream)
+ G_IMPLEMENT_INTERFACE (G_TYPE_FILE_DESCRIPTOR_BASED,
+ g_file_descriptor_based_iface_init))
+
+
+
+static gssize gvfs_document_output_stream_write (GOutputStream *stream,
+ const void *buffer,
+ gsize count,
+ GCancellable *cancellable,
+ GError **error);
+static gboolean gvfs_document_output_stream_close (GOutputStream *stream,
+ GCancellable *cancellable,
+ GError **error);
+static GFileInfo *gvfs_document_output_stream_query_info (GFileOutputStream *stream,
+ const char *attributes,
+ GCancellable *cancellable,
+ GError **error);
+static char * gvfs_document_output_stream_get_etag (GFileOutputStream *stream);
+static goffset gvfs_document_output_stream_tell (GFileOutputStream *stream);
+static gboolean gvfs_document_output_stream_can_seek (GFileOutputStream *stream);
+static gboolean gvfs_document_output_stream_seek (GFileOutputStream *stream,
+ goffset offset,
+ GSeekType type,
+ GCancellable *cancellable,
+ GError **error);
+static gboolean gvfs_document_output_stream_can_truncate (GFileOutputStream *stream);
+static gboolean gvfs_document_output_stream_truncate (GFileOutputStream *stream,
+ goffset size,
+ GCancellable *cancellable,
+ GError **error);
+static int gvfs_document_output_stream_get_fd (GFileDescriptorBased *stream);
+
+
+static void
+gvfs_document_output_stream_finalize (GObject *object)
+{
+ GVfsDocumentOutputStream *file;
+
+ file = GVFS_DOCUMENT_OUTPUT_STREAM (object);
+
+ g_free (file->priv->doc_handle);
+ g_free (file->priv->etag);
+
+ G_OBJECT_CLASS (gvfs_document_output_stream_parent_class)->finalize (object);
+}
+
+static void
+gvfs_document_output_stream_class_init (GVfsDocumentOutputStreamClass *klass)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+ GOutputStreamClass *stream_class = G_OUTPUT_STREAM_CLASS (klass);
+ GFileOutputStreamClass *file_stream_class = G_FILE_OUTPUT_STREAM_CLASS (klass);
+
+ gobject_class->finalize = gvfs_document_output_stream_finalize;
+
+ stream_class->write_fn = gvfs_document_output_stream_write;
+ stream_class->close_fn = gvfs_document_output_stream_close;
+ file_stream_class->query_info = gvfs_document_output_stream_query_info;
+ file_stream_class->get_etag = gvfs_document_output_stream_get_etag;
+ file_stream_class->tell = gvfs_document_output_stream_tell;
+ file_stream_class->can_seek = gvfs_document_output_stream_can_seek;
+ file_stream_class->seek = gvfs_document_output_stream_seek;
+ file_stream_class->can_truncate = gvfs_document_output_stream_can_truncate;
+ file_stream_class->truncate_fn = gvfs_document_output_stream_truncate;
+}
+
+static void
+g_file_descriptor_based_iface_init (GFileDescriptorBasedIface *iface)
+{
+ iface->get_fd = gvfs_document_output_stream_get_fd;
+}
+
+static void
+gvfs_document_output_stream_init (GVfsDocumentOutputStream *stream)
+{
+ stream->priv = gvfs_document_output_stream_get_instance_private (stream);
+}
+
+static gssize
+gvfs_document_output_stream_write (GOutputStream *stream,
+ const void *buffer,
+ gsize count,
+ GCancellable *cancellable,
+ GError **error)
+{
+ GVfsDocumentOutputStream *file;
+ gssize res;
+
+ file = GVFS_DOCUMENT_OUTPUT_STREAM (stream);
+
+ while (1)
+ {
+ if (g_cancellable_set_error_if_cancelled (cancellable, error))
+ return -1;
+ res = write (file->priv->fd, buffer, count);
+ if (res == -1)
+ {
+ int errsv = errno;
+
+ if (errsv == EINTR)
+ continue;
+
+ g_set_error (error, G_IO_ERROR,
+ g_io_error_from_errno (errsv),
+ _("Error writing to file: %s"),
+ g_strerror (errsv));
+ }
+
+ break;
+ }
+
+ return res;
+}
+
+static GVariant *
+sync_document_call (GVfsDocumentOutputStream *stream,
+ const char *method,
+ GVariant *parameters,
+ const GVariantType *reply_type,
+ GUnixFDList **out_fd_list,
+ GCancellable *cancellable,
+ GError **error)
+{
+ GVariant *res;
+ GDBusConnection *bus;
+ GUnixFDList *ret_fd_list;
+ GVariant *reply, *fd_v;
+ char *path;
+ int handle, fd;
+
+ bus = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, error);
+ if (bus == NULL)
+ return NULL;
+
+ path = g_build_filename ("/org/freedesktop/portal/document", stream->priv->doc_handle, NULL);
+
+ res = g_dbus_connection_call_with_unix_fd_list_sync (bus,
+ "org.freedesktop.portal.DocumentPortal",
+ path,
+ "org.freedesktop.portal.Document",
+ method,
+ parameters, reply_type,
+ G_DBUS_CALL_FLAGS_NONE,
+ -1,
+ NULL,
+ out_fd_list,
+ cancellable,
+ error);
+
+ g_object_unref (bus);
+ return res;
+}
+
+static gboolean
+gvfs_document_output_stream_close (GOutputStream *stream,
+ GCancellable *cancellable,
+ GError **error)
+{
+ GVfsDocumentOutputStream *file;
+ gboolean failed;
+ GVariant *reply;
+
+ file = GVFS_DOCUMENT_OUTPUT_STREAM (stream);
+
+ failed = g_cancellable_set_error_if_cancelled (cancellable, error);
+
+ /* Always close, even if cancelled */
+ if (!g_close (file->priv->fd, NULL) && !failed)
+ {
+ int errsv = errno;
+ g_set_error (error, G_IO_ERROR,
+ g_io_error_from_errno (errsv),
+ _("Error closing file: %s"),
+ g_strerror (errsv));
+ }
+
+ if (failed)
+ {
+ reply = sync_document_call (file, "AbortUpdate",
+ g_variant_new ("(u)", file->priv->id),
+ G_VARIANT_TYPE("()"),
+ NULL,
+ NULL, NULL);
+ g_variant_unref (reply);
+ return FALSE;
+ }
+
+ /* TODO: What if this is cancelled? Do we leak the update? */
+ reply = sync_document_call (file, "FinishUpdate",
+ g_variant_new ("(u)", file->priv->id),
+ G_VARIANT_TYPE("()"),
+ NULL,
+ cancellable, error);
+ if (reply == NULL)
+ return FALSE;
+
+ g_variant_unref (reply);
+
+ return TRUE;
+}
+
+static char *
+gvfs_document_output_stream_get_etag (GFileOutputStream *stream)
+{
+ GVfsDocumentOutputStream *file;
+
+ file = GVFS_DOCUMENT_OUTPUT_STREAM (stream);
+
+ return g_strdup (file->priv->etag);
+}
+
+static goffset
+gvfs_document_output_stream_tell (GFileOutputStream *stream)
+{
+ GVfsDocumentOutputStream *file;
+ off_t pos;
+
+ file = GVFS_DOCUMENT_OUTPUT_STREAM (stream);
+
+ pos = lseek (file->priv->fd, 0, SEEK_CUR);
+
+ if (pos == (off_t)-1)
+ return 0;
+
+ return pos;
+}
+
+static gboolean
+gvfs_document_output_stream_can_seek (GFileOutputStream *stream)
+{
+ GVfsDocumentOutputStream *file;
+ off_t pos;
+
+ file = GVFS_DOCUMENT_OUTPUT_STREAM (stream);
+
+ pos = lseek (file->priv->fd, 0, SEEK_CUR);
+
+ if (pos == (off_t)-1 && errno == ESPIPE)
+ return FALSE;
+
+ return TRUE;
+}
+
+static int
+seek_type_to_lseek (GSeekType type)
+{
+ switch (type)
+ {
+ default:
+ case G_SEEK_CUR:
+ return SEEK_CUR;
+
+ case G_SEEK_SET:
+ return SEEK_SET;
+
+ case G_SEEK_END:
+ return SEEK_END;
+ }
+}
+
+static gboolean
+gvfs_document_output_stream_seek (GFileOutputStream *stream,
+ goffset offset,
+ GSeekType type,
+ GCancellable *cancellable,
+ GError **error)
+{
+ GVfsDocumentOutputStream *file;
+ off_t pos;
+
+ file = GVFS_DOCUMENT_OUTPUT_STREAM (stream);
+
+ pos = lseek (file->priv->fd, offset, seek_type_to_lseek (type));
+
+ if (pos == (off_t)-1)
+ {
+ int errsv = errno;
+
+ g_set_error (error, G_IO_ERROR,
+ g_io_error_from_errno (errsv),
+ _("Error seeking in file: %s"),
+ g_strerror (errsv));
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static gboolean
+gvfs_document_output_stream_can_truncate (GFileOutputStream *stream)
+{
+ /* We can't truncate pipes and stuff where we can't seek */
+ return gvfs_document_output_stream_can_seek (stream);
+}
+
+static gboolean
+gvfs_document_output_stream_truncate (GFileOutputStream *stream,
+ goffset size,
+ GCancellable *cancellable,
+ GError **error)
+{
+ GVfsDocumentOutputStream *file;
+ int res;
+
+ file = GVFS_DOCUMENT_OUTPUT_STREAM (stream);
+
+ restart:
+ res = ftruncate (file->priv->fd, size);
+
+ if (res == -1)
+ {
+ int errsv = errno;
+
+ if (errsv == EINTR)
+ {
+ if (g_cancellable_set_error_if_cancelled (cancellable, error))
+ return FALSE;
+ goto restart;
+ }
+
+ g_set_error (error, G_IO_ERROR,
+ g_io_error_from_errno (errsv),
+ _("Error truncating file: %s"),
+ g_strerror (errsv));
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+
+static GFileInfo *
+gvfs_document_output_stream_query_info (GFileOutputStream *stream,
+ const char *attributes,
+ GCancellable *cancellable,
+ GError **error)
+{
+ GVfsDocumentOutputStream *file;
+ GFileInfo *info;
+
+ file = GVFS_DOCUMENT_OUTPUT_STREAM (stream);
+
+ if (g_cancellable_set_error_if_cancelled (cancellable, error))
+ return NULL;
+
+ info = g_file_info_new ();
+
+ return info;
+}
+
+GFileOutputStream *
+gvfs_document_output_stream_new (const char *handle,
+ guint32 id,
+ int fd)
+{
+ GVfsDocumentOutputStream *stream;
+
+ stream = g_object_new (GVFS_TYPE_DOCUMENT_OUTPUT_STREAM, NULL);
+ stream->priv->doc_handle = g_strdup (handle);
+ stream->priv->id = id;
+ stream->priv->fd = fd;
+
+ return G_FILE_OUTPUT_STREAM (stream);
+}
+
+static int
+gvfs_document_output_stream_get_fd (GFileDescriptorBased *fd_based)
+{
+ GVfsDocumentOutputStream *stream = GVFS_DOCUMENT_OUTPUT_STREAM (fd_based);
+
+ return stream->priv->fd;
+}
diff --git a/client/gvfsdocumentoutputstream.h b/client/gvfsdocumentoutputstream.h
new file mode 100644
index 00000000..f8b9248b
--- /dev/null
+++ b/client/gvfsdocumentoutputstream.h
@@ -0,0 +1,47 @@
+
+#ifndef __GVFS_DOCUMENT_OUTPUT_STREAM_H__
+#define __GVFS_DOCUMENT_OUTPUT_STREAM_H__
+
+#include <gio/gfileoutputstream.h>
+
+G_BEGIN_DECLS
+
+#define GVFS_TYPE_DOCUMENT_OUTPUT_STREAM (_gvfs_document_output_stream_get_type ())
+#define GVFS_DOCUMENT_OUTPUT_STREAM(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GVFS_TYPE_DOCUMENT_OUTPUT_STREAM, GVfsDocumentOutputStream))
+#define GVFS_DOCUMENT_OUTPUT_STREAM_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), GVFS_TYPE_DOCUMENT_OUTPUT_STREAM, GVfsDocumentOutputStreamClass))
+#define GVFS_IS_DOCUMENT_OUTPUT_STREAM(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GVFS_TYPE_DOCUMENT_OUTPUT_STREAM))
+#define GVFS_IS_DOCUMENT_OUTPUT_STREAM_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GVFS_TYPE_DOCUMENT_OUTPUT_STREAM))
+#define GVFS_DOCUMENT_OUTPUT_STREAM_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GVFS_TYPE_DOCUMENT_OUTPUT_STREAM, GVfsDocumentOutputStreamClass))
+
+typedef struct _GVfsDocumentOutputStream GVfsDocumentOutputStream;
+typedef struct _GVfsDocumentOutputStreamClass GVfsDocumentOutputStreamClass;
+typedef struct _GVfsDocumentOutputStreamPrivate GVfsDocumentOutputStreamPrivate;
+
+struct _GVfsDocumentOutputStream
+{
+ GFileOutputStream parent_instance;
+
+ /*< private >*/
+ GVfsDocumentOutputStreamPrivate *priv;
+};
+
+struct _GVfsDocumentOutputStreamClass
+{
+ GFileOutputStreamClass parent_class;
+};
+
+GType gvfs_document_output_stream_get_type (void) G_GNUC_CONST;
+
+void gvfs_document_output_stream_set_do_close (GVfsDocumentOutputStream *out,
+ gboolean do_close);
+gboolean gvfs_document_output_stream_really_close (GVfsDocumentOutputStream *out,
+ GCancellable *cancellable,
+ GError **error);
+
+GFileOutputStream * gvfs_document_output_stream_new (const char *handle,
+ guint32 id,
+ int fd);
+
+G_END_DECLS
+
+#endif /* __GVFS_DOCUMENT_OUTPUT_STREAM_H__ */