[phodav: 1/6] server: add PhodavVirtualDir




commit 62a798641d22e05e25f133ad1c3f9c2532444fdb
Author: Jakub Janků <jjanku redhat com>
Date:   Wed May 27 15:28:05 2020 +0200

    server: add PhodavVirtualDir
    
    Signed-off-by: Jakub Janků <jjanku redhat com>

 doc/reference/phodav-2.0-docs.sgml    |   2 +-
 doc/reference/phodav-2.0-sections.txt |  12 +
 libphodav/libphodav.syms              |   5 +
 libphodav/meson.build                 |   2 +
 libphodav/phodav-method-movecopy.c    |   8 +
 libphodav/phodav-virtual-dir.c        | 664 ++++++++++++++++++++++++++++++++++
 libphodav/phodav-virtual-dir.h        |  41 +++
 libphodav/phodav.h                    |   1 +
 8 files changed, 734 insertions(+), 1 deletion(-)
---
diff --git a/doc/reference/phodav-2.0-docs.sgml b/doc/reference/phodav-2.0-docs.sgml
index cc6a574..a53c9c3 100644
--- a/doc/reference/phodav-2.0-docs.sgml
+++ b/doc/reference/phodav-2.0-docs.sgml
@@ -19,7 +19,7 @@
   <chapter>
     <title>PhoDAV API</title>
         <xi:include href="xml/phodav-server.xml"/>
-
+        <xi:include href="xml/phodav-virtual-dir.xml"/>
   </chapter>
   <chapter id="object-tree">
     <title>Object Hierarchy</title>
diff --git a/doc/reference/phodav-2.0-sections.txt b/doc/reference/phodav-2.0-sections.txt
index 776104c..6989f56 100644
--- a/doc/reference/phodav-2.0-sections.txt
+++ b/doc/reference/phodav-2.0-sections.txt
@@ -14,3 +14,15 @@ PhodavServerClass
 phodav_server_get_type
 </SECTION>
 
+<SECTION>
+<FILE>phodav-virtual-dir</FILE>
+phodav_virtual_dir_new_root
+phodav_virtual_dir_new_dir
+phodav_virtual_dir_attach_real_child
+
+<SUBSECTION Standard>
+PHODAV_TYPE_VIRTUAL_DIR
+PHODAV_TYPE_VIRTUAL_DIR_ENUMERATOR
+PhodavVirtualDir
+PhodavVirtualDirEnumerator
+</SECTION>
diff --git a/libphodav/libphodav.syms b/libphodav/libphodav.syms
index a7816d5..7b58d58 100644
--- a/libphodav/libphodav.syms
+++ b/libphodav/libphodav.syms
@@ -6,6 +6,11 @@ LIBPHODAV1_0.0 {
         phodav_server_new;
         phodav_server_quit;
         phodav_server_run;
+        phodav_virtual_dir_new_root;
+        phodav_virtual_dir_new_dir;
+        phodav_virtual_dir_attach_real_child;
+        phodav_virtual_dir_get_type;
+        phodav_virtual_dir_enumerator_get_type;
     local:
         *;
 };
diff --git a/libphodav/meson.build b/libphodav/meson.build
index dd3c79e..5443ce0 100644
--- a/libphodav/meson.build
+++ b/libphodav/meson.build
@@ -1,6 +1,7 @@
 headers = [
   'phodav.h',
   'phodav-server.h',
+  'phodav-virtual-dir.h',
 ]
 
 install_headers(headers, subdir : 'libphodav-2.0/libphodav')
@@ -21,6 +22,7 @@ sources = [
   'phodav-path.c',
   'phodav-server.c',
   'phodav-utils.c',
+  'phodav-virtual-dir.c'
 ]
 
 if not dependency('glib-2.0', version : '>= 2.51.2', required: false).found()
diff --git a/libphodav/phodav-method-movecopy.c b/libphodav/phodav-method-movecopy.c
index 0619824..b4ec78d 100644
--- a/libphodav/phodav-method-movecopy.c
+++ b/libphodav/phodav-method-movecopy.c
@@ -18,6 +18,7 @@
 #include "phodav-priv.h"
 #include "phodav-utils.h"
 #include "phodav-lock.h"
+#include "phodav-virtual-dir.h"
 
 static gboolean
 do_copy_r (GFile *src, GFile *dest, GFileCopyFlags flags,
@@ -183,6 +184,13 @@ phodav_method_movecopy (PathHandler *handler, SoupMessage *msg,
   g_free (udest);
 
   file = g_file_get_child (handler_get_file (handler), path + 1);
+
+  /* take the short path as g_file_copy seems to be rather complicated */
+  if (PHODAV_IS_VIRTUAL_DIR (file) || PHODAV_IS_VIRTUAL_DIR (dest_file))
+    {
+      status = SOUP_STATUS_FORBIDDEN;
+      goto end;
+    }
   status = do_movecopy_file (msg, file, dest_file, dest,
                              cancellable, err);
 
diff --git a/libphodav/phodav-virtual-dir.c b/libphodav/phodav-virtual-dir.c
new file mode 100644
index 0000000..702ba8c
--- /dev/null
+++ b/libphodav/phodav-virtual-dir.c
@@ -0,0 +1,664 @@
+/*
+ * Copyright (C) 2020 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.1 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/>.
+ */
+
+#include "config.h"
+
+#include <gio/gio.h>
+
+#include "phodav-virtual-dir.h"
+
+/**
+ * SECTION:phodav-virtual-dir
+ * @title: PhodavVirtualDir
+ * @short_description: in-memory directory
+ * @see_also: #GFileIface
+ * @include: libphodav/phodav.h
+ *
+ * PhodavVirtualDir implements #GFileIface and can be used with all #GFile functions.
+ *
+ * PhodavVirtualDir, as the name suggests, does not represent any real file.
+ * Instead, it is a virtual element that can be used to build a directory tree structure
+ * in the memory. However, a PhodavVirtualDir can have a real file as its child.
+ *
+ * The first building block of such tree must be phodav_virtual_dir_new_root().
+ * Further directories can be added using phodav_virtual_dir_new_dir().
+ * To link a real file as a child of a PhodavVirtualDir, use phodav_virtual_dir_attach_real_child().
+ *
+ * PhodavVirtualDir allows you to easily share two resources when they have no common ancestor
+ * (like "C:\fileA" and "D:\fileB") or when sharing their common ancestor would be impractical
+ * (sharing the whole "/" when the files are "/d1/d2/fileA" and "/d3/d4/fileB").
+ *
+ * PhodavVirtualDir was designed primarily for the purposes of the
+ * [SPICE project](https://www.spice-space.org/) and hence the functionality is very narrow.
+ * If other projects find it handy as well, it could be extended quite easily.
+ *
+ * Supported methods:
+ * - GET
+ * - PROPFIND
+ * - LOCK
+ * - UNLOCK
+ *
+ * This concerns only the virtual directories. If you have a real #GFile
+ * linked to a virtual directory, all the other methods are supported on such file.
+ *
+ * You currently cannot delete a #PhodavVirtualDir. Once the last reference to the root
+ * is dropped, the whole structure is destroyed. Children that have other references (non-internal)
+ * become dummies, otherwise they're freed.
+ *
+ * #PhodavVirtualDir is available since phodav 2.5
+ */
+
+struct _PhodavVirtualDir {
+  GObject           parent_instance;
+
+  gboolean          dummy;
+  PhodavVirtualDir *parent;
+  GList            *children;
+
+  /* TODO: we could store just the base name and build up the path when needed */
+  gchar            *path;
+};
+
+static void phodav_virtual_dir_file_interface_init (GFileIface *iface);
+
+G_DEFINE_TYPE_WITH_CODE (PhodavVirtualDir, phodav_virtual_dir, G_TYPE_OBJECT,
+                         G_IMPLEMENT_INTERFACE (G_TYPE_FILE,
+                                                phodav_virtual_dir_file_interface_init))
+
+struct _PhodavVirtualDirEnumerator {
+  GFileEnumerator      parent_instance;
+  gchar               *attributes;
+  GFileQueryInfoFlags  flags;
+  GList               *children;
+  GList               *current;
+};
+
+G_DEFINE_TYPE (PhodavVirtualDirEnumerator, phodav_virtual_dir_enumerator, G_TYPE_FILE_ENUMERATOR)
+
+static GFileInfo *
+phodav_virtual_dir_enumerator_next_file (GFileEnumerator *enumerator,
+                                         GCancellable    *cancellable,
+                                         GError         **error)
+{
+  PhodavVirtualDirEnumerator *self = PHODAV_VIRTUAL_DIR_ENUMERATOR (enumerator);
+  GFile *file;
+
+  if (!self->current || !self->current->data)
+    return NULL;
+
+  file = G_FILE (self->current->data);
+  self->current = self->current->next;
+  return g_file_query_info (G_FILE (file), self->attributes, self->flags, cancellable, error);
+}
+
+static gboolean
+phodav_virtual_dir_enumerator_close (GFileEnumerator *enumerator,
+                                     GCancellable    *cancellable,
+                                     GError         **error)
+{
+  PhodavVirtualDirEnumerator *self = PHODAV_VIRTUAL_DIR_ENUMERATOR (enumerator);
+  g_clear_pointer (&self->attributes, g_free);
+  g_list_free_full (self->children, g_object_unref);
+  self->children = NULL;
+  return TRUE;
+}
+
+static void
+phodav_virtual_dir_enumerator_class_init (PhodavVirtualDirEnumeratorClass *klass)
+{
+  GFileEnumeratorClass *enumerator_class = G_FILE_ENUMERATOR_CLASS (klass);
+  enumerator_class->next_file = phodav_virtual_dir_enumerator_next_file;
+  enumerator_class->close_fn  = phodav_virtual_dir_enumerator_close;
+}
+
+static void
+phodav_virtual_dir_enumerator_init (PhodavVirtualDirEnumerator *self)
+{
+}
+
+// --------------------------------------------------------
+
+static gboolean
+is_root (PhodavVirtualDir *file)
+{
+  return !g_strcmp0 (file->path, "/");
+}
+
+/* create a new instance of PhodavVirtualDir
+ * that is not attached to any parent */
+static GFile *
+virtual_dir_dummy_new (void)
+{
+  PhodavVirtualDir *dummy;
+  dummy = g_object_new (PHODAV_TYPE_VIRTUAL_DIR, NULL);
+  dummy->dummy = TRUE;
+  return G_FILE (dummy);
+}
+
+static gchar *
+phodav_virtual_dir_get_basename (GFile *file)
+{
+  PhodavVirtualDir *self = PHODAV_VIRTUAL_DIR (file);
+  return g_path_get_basename (self->path);
+}
+
+static gchar *
+phodav_virtual_dir_get_path (GFile *file)
+{
+  PhodavVirtualDir *self = PHODAV_VIRTUAL_DIR (file);
+  return g_strdup (self->path);
+}
+
+static GFileInfo *
+phodav_virtual_dir_query_info (GFile                *file,
+                               const char           *attributes,
+                               GFileQueryInfoFlags   flags,
+                               GCancellable         *cancellable,
+                               GError              **error)
+{
+  PhodavVirtualDir *self = PHODAV_VIRTUAL_DIR (file);
+  GFileInfo *info;
+  gchar *base;
+
+  if (self->dummy)
+    {
+      g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, "file has no parent");
+      return NULL;
+    }
+
+  info = g_file_info_new ();
+  base = phodav_virtual_dir_get_basename (file);
+  g_file_info_set_name (info, base);
+  g_file_info_set_display_name (info, base);
+  g_free (base);
+  g_file_info_set_file_type (info, G_FILE_TYPE_DIRECTORY);
+  /* TODO: set more attributes? e.g. etag, time*, access* */
+
+  return info;
+}
+
+static GFileInfo *
+phodav_virtual_dir_query_filesystem_info (GFile         *file,
+                                          const char    *attributes,
+                                          GCancellable  *cancellable,
+                                          GError       **error)
+{
+  PhodavVirtualDir *self = PHODAV_VIRTUAL_DIR (file);
+  GFileInfo *info;
+
+  if (self->dummy)
+    {
+      g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, "file has no parent");
+      return NULL;
+    }
+
+  info = g_file_info_new ();
+  g_file_info_set_attribute_uint64 (info, G_FILE_ATTRIBUTE_FILESYSTEM_FREE, 0);
+  return info;
+}
+
+static gboolean
+phodav_virtual_dir_measure_disk_usage (GFile                         *file,
+                                       GFileMeasureFlags              flags,
+                                       GCancellable                  *cancellable,
+                                       GFileMeasureProgressCallback   progress_callback,
+                                       gpointer                       progress_data,
+                                       guint64                       *disk_usage,
+                                       guint64                       *num_dirs,
+                                       guint64                       *num_files,
+                                       GError                       **error)
+{
+  /* prop_quota_used in phodav-method-propfind.c is only interested in @disk_usage */
+  g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "Operation not supported");
+  return FALSE;
+}
+
+static GFile *
+phodav_virtual_dir_get_parent (GFile *file)
+{
+  PhodavVirtualDir *self = PHODAV_VIRTUAL_DIR (file);
+
+  if (is_root (self))
+    return NULL;
+
+  if (self->parent)
+    return G_FILE (g_object_ref (self->parent));
+
+  return virtual_dir_dummy_new ();
+}
+
+static GFileEnumerator *
+phodav_virtual_dir_enumerate_children (GFile                *file,
+                                       const char           *attributes,
+                                       GFileQueryInfoFlags   flags,
+                                       GCancellable         *cancellable,
+                                       GError              **error)
+{
+  PhodavVirtualDir *self = PHODAV_VIRTUAL_DIR (file);
+  PhodavVirtualDirEnumerator *enumerator;
+
+  enumerator = g_object_new (PHODAV_TYPE_VIRTUAL_DIR_ENUMERATOR, "container", file, NULL);
+  enumerator->attributes = g_strdup (attributes);
+  enumerator->flags = flags;
+  enumerator->children = g_list_copy_deep (self->children, (GCopyFunc) g_object_ref, NULL);
+  enumerator->current = enumerator->children;
+  return G_FILE_ENUMERATOR (enumerator);
+}
+
+/* searches for an immediate descendant of @file with the given @name,
+ * the reference count of the returned child is not incremented */
+static GFile *
+phodav_virtual_dir_find_direct_child (PhodavVirtualDir *parent,
+                                      const gchar      *name)
+{
+  GFile *child;
+  GList *l;
+  gchar *base;
+
+  /* TODO: perhaps a hash table would be better? */
+  for (l = parent->children; l; l = l->next)
+    {
+      child = G_FILE (l->data);
+      base = g_file_get_basename (child);
+      if (!g_strcmp0 (name, base))
+        {
+          g_free (base);
+          return child;
+        }
+      g_free (base);
+    }
+
+  return NULL;
+}
+
+/* recursively searches for a child of @parent given the relative @path.
+ * The result can be either:
+ *     1) PhodavVirtualDir
+ *     2) GFile attached to PhodavVirtualDir
+ *     3) GFile (child of attached GFile)
+ *     4) NULL
+ * In case 1) and 2), the reference count is incremented by 1,
+ * this means that the result should always be freed. */
+static GFile *
+phodav_virtual_dir_find_child_recursive (PhodavVirtualDir *parent,
+                                         const gchar      *path)
+{
+  GFile *current;
+  gchar **segments, **segment_ptr, *real;
+
+  g_return_val_if_fail (parent != NULL, NULL);
+  g_return_val_if_fail (path != NULL, NULL);
+  g_return_val_if_fail (path[0] != '\0', NULL);
+
+  segments = g_strsplit (path, "/", -1);
+
+  current = G_FILE (parent);
+  for (segment_ptr = segments; *segment_ptr; segment_ptr++)
+    {
+      /* empty string means there were leading/trailing/consecutive slashes, skip them  */
+      if (*segment_ptr[0] == '\0')
+        continue;
+
+      /* we arrived at a real file */
+      if (!PHODAV_IS_VIRTUAL_DIR (current))
+        {
+          /* build up a new path from the remaining segments
+           * and let GLib handle the rest */
+          real = g_build_pathv ("/", segment_ptr);
+          current = g_file_get_child (current, real);
+          g_free (real);
+          g_strfreev (segments);
+          return current;
+        }
+      current = phodav_virtual_dir_find_direct_child (PHODAV_VIRTUAL_DIR (current), *segment_ptr);
+      if (!current)
+        break;
+    }
+
+  g_strfreev (segments);
+  if (current)
+    g_object_ref (current);
+  return current;
+}
+
+static GFile *
+phodav_virtual_dir_resolve_relative_path (GFile       *file,
+                                          const gchar *relative_path)
+{
+  PhodavVirtualDir *parent;
+  GFile *child;
+
+  if (relative_path[0] == '\0')
+    return g_object_ref (file);
+
+  /* try to find the file first */
+  parent = PHODAV_VIRTUAL_DIR (file);
+  child = phodav_virtual_dir_find_child_recursive (parent, relative_path);
+  if (child)
+    return child;
+
+  return virtual_dir_dummy_new ();
+}
+
+static GFile *
+phodav_virtual_dir_dup (GFile *file)
+{
+  return NULL;
+}
+
+static guint
+phodav_virtual_dir_hash (GFile *file)
+{
+  PhodavVirtualDir *self = PHODAV_VIRTUAL_DIR (file);
+  return g_str_hash (self->path);
+}
+
+static gboolean
+phodav_virtual_dir_equal (GFile *file1,
+                          GFile *file2)
+{
+  return file1 == file2;
+}
+
+static gboolean
+phodav_virtual_dir_is_native (GFile *file)
+{
+  return FALSE;
+}
+
+static gboolean
+phodav_virtual_dir_has_uri_scheme (GFile       *file,
+                                   const gchar *uri_scheme)
+{
+  return FALSE;
+}
+
+static gchar *
+phodav_virtual_dir_get_uri_scheme (GFile *file)
+{
+  return NULL;
+}
+
+static gchar *
+phodav_virtual_dir_get_uri (GFile *file)
+{
+  return NULL;
+}
+
+static gchar *
+phodav_virtual_dir_get_parse_name (GFile *file)
+{
+  return NULL;
+}
+
+static gboolean
+phodav_virtual_dir_prefix_matches (GFile *prefix,
+                                   GFile *file)
+{
+  return FALSE; /* TODO */
+}
+
+static gchar *
+phodav_virtual_dir_get_relative_path (GFile *parent,
+                                      GFile *descendant)
+{
+  return NULL; /* TODO */
+}
+
+static GFile *
+phodav_virtual_dir_get_child_for_display_name (GFile        *file,
+                                               const gchar  *display_name,
+                                               GError      **error)
+{
+  g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "Operation not supported");
+  return NULL;
+}
+
+static GFile *
+phodav_virtual_dir_set_display_name (GFile         *file,
+                                     const gchar   *display_name,
+                                     GCancellable  *cancellable,
+                                     GError       **error)
+{
+  g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "Operation not supported");
+  return NULL;
+}
+
+static gboolean
+phodav_virtual_dir_set_attributes_from_info (GFile                *file,
+                                             GFileInfo            *info,
+                                             GFileQueryInfoFlags   flags,
+                                             GCancellable         *cancellable,
+                                             GError              **error)
+{
+  g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "Operation not supported");
+  return FALSE;
+}
+
+static void
+phodav_virtual_dir_file_interface_init (GFileIface *iface)
+{
+  iface->get_basename = phodav_virtual_dir_get_basename;
+  iface->get_path = phodav_virtual_dir_get_path;
+  iface->query_info = phodav_virtual_dir_query_info;
+  iface->query_filesystem_info = phodav_virtual_dir_query_filesystem_info;
+  iface->measure_disk_usage = phodav_virtual_dir_measure_disk_usage;
+
+  iface->get_parent = phodav_virtual_dir_get_parent;
+  iface->enumerate_children = phodav_virtual_dir_enumerate_children;
+  iface->resolve_relative_path = phodav_virtual_dir_resolve_relative_path;
+
+  /* GLib does not check if these functions are implemented or not, it simply calls them,
+   * so we need to provide these stubs to avoid crashes */
+  iface->dup = phodav_virtual_dir_dup;
+  iface->hash = phodav_virtual_dir_hash;
+  iface->equal = phodav_virtual_dir_equal;
+  iface->is_native = phodav_virtual_dir_is_native;
+  iface->has_uri_scheme = phodav_virtual_dir_has_uri_scheme;
+  iface->get_uri_scheme = phodav_virtual_dir_get_uri_scheme;
+  iface->get_uri = phodav_virtual_dir_get_uri;
+  iface->get_parse_name = phodav_virtual_dir_get_parse_name;
+  iface->prefix_matches = phodav_virtual_dir_prefix_matches;
+  iface->get_relative_path = phodav_virtual_dir_get_relative_path;
+  iface->get_child_for_display_name = phodav_virtual_dir_get_child_for_display_name;
+  iface->set_display_name = phodav_virtual_dir_set_display_name;
+  iface->set_attributes_from_info = phodav_virtual_dir_set_attributes_from_info;
+}
+
+static void
+parent_gone_cb (gpointer  data,
+                GObject  *where_the_object_was)
+{
+  PhodavVirtualDir *self = PHODAV_VIRTUAL_DIR (data);
+  self->dummy = TRUE;
+  self->parent = NULL;
+}
+
+static void
+phodav_virtual_dir_dispose (GObject *object)
+{
+  PhodavVirtualDir *self = PHODAV_VIRTUAL_DIR (object);
+
+  /* TODO: would be nice to have a test for this */
+  if (self->parent)
+    {
+      g_object_weak_unref (G_OBJECT (self->parent), parent_gone_cb, self);
+      self->parent = NULL;
+    }
+  self->dummy = TRUE;
+  g_list_free_full (self->children, g_object_unref);
+  self->children = NULL;
+
+  G_OBJECT_CLASS (phodav_virtual_dir_parent_class)->dispose (object);
+}
+
+static void
+phodav_virtual_dir_finalize (GObject *object)
+{
+  PhodavVirtualDir *self = PHODAV_VIRTUAL_DIR (object);
+  g_free (self->path);
+  G_OBJECT_CLASS (phodav_virtual_dir_parent_class)->finalize (object);
+}
+
+static void
+phodav_virtual_dir_class_init (PhodavVirtualDirClass *klass)
+{
+  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+  gobject_class->dispose = phodav_virtual_dir_dispose;
+  gobject_class->finalize = phodav_virtual_dir_finalize;
+}
+
+static void
+phodav_virtual_dir_init (PhodavVirtualDir *self)
+{
+}
+
+/**
+ * phodav_virtual_dir_new_root:
+ *
+ * Creates a new root virtual directory that acts as the ancestor
+ * of all further virtual directories.
+ *
+ * Returns: (transfer full): a new #PhodavVirtualDir with the path `/`
+ **/
+PhodavVirtualDir *
+phodav_virtual_dir_new_root (void)
+{
+  PhodavVirtualDir *root;
+  root = g_object_new (PHODAV_TYPE_VIRTUAL_DIR, NULL);
+  root->path = g_strdup ("/");
+  root->dummy = FALSE;
+  return root;
+}
+
+/**
+ * phodav_virtual_dir_new_dir:
+ * @root: #PhodavVirtualDir returned by phodav_virtual_dir_new_root()
+ * @path: path of the #PhodavVirtualDir that should be created
+ * @error: (nullable): #GError to set on error, or %NULL to ignore
+ *
+ * Tries to create a new virtual directory with the specified @path.
+ * If it fails, @error is set accordingly.
+ *
+ * A real #GFile child cannot act as parent to #PhodavVirtualDir.
+ *
+ * Note that this does not create parent directories.
+ * You have to call this repeatedly yourself if the parent(s) don't exist yet.
+ *
+ * Returns: (transfer full): a new #PhodavVirtualDir that corresponds to the @path
+ **/
+PhodavVirtualDir *
+phodav_virtual_dir_new_dir (PhodavVirtualDir  *root,
+                            const gchar       *path,
+                            GError           **error)
+{
+  GFile *f = NULL;
+  PhodavVirtualDir *file = NULL, *parent;
+  gchar *dir = NULL, *base = NULL;
+
+  g_return_val_if_fail (root != NULL, NULL);
+  g_return_val_if_fail (path != NULL, NULL);
+
+  dir = g_path_get_dirname (path);
+  if (!dir || !g_strcmp0 (dir, "."))
+    {
+      g_set_error_literal (error,
+                           G_IO_ERROR,
+                           G_IO_ERROR_INVALID_FILENAME,
+                           "invalid path");
+      goto end;
+    }
+
+  f = phodav_virtual_dir_find_child_recursive (root, dir);
+  if (!f)
+    {
+      g_set_error_literal (error,
+                           G_IO_ERROR,
+                           G_IO_ERROR_NOT_FOUND,
+                           "parent dir not found");
+      goto end;
+    }
+  if (!PHODAV_IS_VIRTUAL_DIR (f))
+    {
+      g_set_error_literal (error,
+                           G_IO_ERROR,
+                           G_IO_ERROR_FAILED,
+                           "cannot add virtual dir to real parent");
+      goto end;
+    }
+  parent = PHODAV_VIRTUAL_DIR (f);
+
+  base = g_path_get_basename (path);
+  if (phodav_virtual_dir_find_direct_child (parent, base))
+    {
+      g_set_error_literal (error,
+                           G_IO_ERROR,
+                           G_IO_ERROR_EXISTS,
+                           "dir already exists");
+      goto end;
+    }
+
+  file = g_object_new (PHODAV_TYPE_VIRTUAL_DIR, NULL);
+  file->path = g_strdup (path);
+  file->dummy = FALSE;
+  parent->children = g_list_prepend (parent->children, g_object_ref (file));
+  g_object_weak_ref (G_OBJECT (parent), parent_gone_cb, file);
+  /* weak ref to parent allows us to remove all subdirectories by dropping the last ref to parent */
+  file->parent = parent;
+
+end:
+  g_clear_pointer (&dir, g_free);
+  g_clear_pointer (&base, g_free);
+  g_clear_object (&f);
+  return file;
+}
+
+/**
+ * phodav_virtual_dir_attach_real_child:
+ * @parent: a #PhodavVirtualDir that should simulate the direct ancestor of @child
+ * @child: a real file that should be linked to @parent
+ *
+ * If successful, @child becomes a descendant of @parent.
+ *
+ * Keep in mind that the link is only unidirectional - @child does not know about its @parent.
+ * That means that functions such as g_file_enumerate_children(), g_file_resolve_relative_path()
+ * (used with @parent) work as expected, but g_file_get_parent() (used with @child) does not.
+ *
+ * If you want to add a #PhodavVirtualDir to @parent, use phodav_virtual_dir_new_dir() instead.
+ *
+ * Returns: %TRUE on success, otherwise %FALSE
+ **/
+gboolean
+phodav_virtual_dir_attach_real_child (PhodavVirtualDir *parent,
+                                      GFile            *child)
+{
+  g_return_val_if_fail (parent != NULL, FALSE);
+  g_return_val_if_fail (child != NULL, FALSE);
+  g_return_val_if_fail (PHODAV_IS_VIRTUAL_DIR (parent), FALSE);
+  g_return_val_if_fail (!PHODAV_IS_VIRTUAL_DIR (child), FALSE);
+
+  gchar *base = g_file_get_basename (child);
+  if (phodav_virtual_dir_find_direct_child (parent, base))
+    {
+      g_free (base);
+      return FALSE;
+    }
+  g_free (base);
+
+  parent->children = g_list_prepend (parent->children, g_object_ref (child));
+  return TRUE;
+}
diff --git a/libphodav/phodav-virtual-dir.h b/libphodav/phodav-virtual-dir.h
new file mode 100644
index 0000000..bc482ff
--- /dev/null
+++ b/libphodav/phodav-virtual-dir.h
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2020 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.1 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/>.
+ */
+
+#ifndef __PHODAV_VIRTUAL_DIR_H__
+#define __PHODAV_VIRTUAL_DIR_H__
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define PHODAV_TYPE_VIRTUAL_DIR phodav_virtual_dir_get_type ()
+G_DECLARE_FINAL_TYPE (PhodavVirtualDir, phodav_virtual_dir, PHODAV, VIRTUAL_DIR, GObject)
+
+PhodavVirtualDir *    phodav_virtual_dir_new_root             (void);
+PhodavVirtualDir *    phodav_virtual_dir_new_dir              (PhodavVirtualDir *root,
+                                                               const gchar      *path,
+                                                               GError          **error);
+gboolean              phodav_virtual_dir_attach_real_child    (PhodavVirtualDir *parent,
+                                                               GFile            *child);
+
+#define PHODAV_TYPE_VIRTUAL_DIR_ENUMERATOR phodav_virtual_dir_enumerator_get_type ()
+G_DECLARE_FINAL_TYPE (PhodavVirtualDirEnumerator, phodav_virtual_dir_enumerator,
+                      PHODAV, VIRTUAL_DIR_ENUMERATOR, GFileEnumerator)
+
+G_END_DECLS
+
+#endif /* __PHODAV_VIRTUAL_DIR_H__ */
diff --git a/libphodav/phodav.h b/libphodav/phodav.h
index 470b0da..681b74e 100644
--- a/libphodav/phodav.h
+++ b/libphodav/phodav.h
@@ -18,5 +18,6 @@
 #define __PHODAV_H__
 
 #include <libphodav/phodav-server.h>
+#include <libphodav/phodav-virtual-dir.h>
 
 #endif /* __PHODAV_H__ */


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