[glib] GFile: add new g_file_measure_disk_usage() API



commit 6ec2bb17c393c411a2182e865aa0979165dfbac5
Author: Ryan Lortie <desrt desrt ca>
Date:   Sun Jul 28 13:41:17 2013 -0400

    GFile: add new g_file_measure_disk_usage() API
    
    This is essentially the equivalent of 'du'.
    
    This is currently only supported on local files.  gvfs will add support for the
    interface later.
    
    https://bugzilla.gnome.org/show_bug.cgi?id=704893

 docs/reference/gio/gio-sections.txt |    4 +
 gio/gfile.c                         |  301 +++++++++++++++++++++++++++++++++++
 gio/gfile.h                         |   53 ++++++
 gio/gioenums.h                      |   23 +++
 gio/giotypes.h                      |   43 +++++
 gio/glocalfile.c                    |  289 +++++++++++++++++++++++++++++++++
 6 files changed, 713 insertions(+), 0 deletions(-)
---
diff --git a/docs/reference/gio/gio-sections.txt b/docs/reference/gio/gio-sections.txt
index c85cfef..2b931f5 100644
--- a/docs/reference/gio/gio-sections.txt
+++ b/docs/reference/gio/gio-sections.txt
@@ -71,6 +71,7 @@ GFileQueryInfoFlags
 GFileCreateFlags
 GFileCopyFlags
 GFileMonitorFlags
+GFileDiskUsageFlags
 GFilesystemPreviewType
 GFileProgressCallback
 GFileReadMoreCallback
@@ -118,6 +119,9 @@ g_file_query_filesystem_info
 g_file_query_filesystem_info_async
 g_file_query_filesystem_info_finish
 g_file_query_default_handler
+g_file_query_disk_usage
+g_file_query_disk_usage_async
+g_file_query_disk_usage_finish
 g_file_find_enclosing_mount
 g_file_find_enclosing_mount_async
 g_file_find_enclosing_mount_finish
diff --git a/gio/gfile.c b/gio/gfile.c
index b64a97d..0c95942 100644
--- a/gio/gfile.c
+++ b/gio/gfile.c
@@ -316,6 +316,30 @@ static gboolean           g_file_real_copy_finish                 (GFile
                                                                    GAsyncResult           *res,
                                                                    GError                **error);
 
+static gboolean           g_file_real_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);
+static void               g_file_real_measure_disk_usage_async    (GFile                         *file,
+                                                                   GFileMeasureFlags              flags,
+                                                                   gint                           
io_priority,
+                                                                   GCancellable                  
*cancellable,
+                                                                   GFileMeasureProgressCallback   
progress_callback,
+                                                                   gpointer                       
progress_data,
+                                                                   GAsyncReadyCallback            callback,
+                                                                   gpointer                       user_data);
+static gboolean           g_file_real_measure_disk_usage_finish   (GFile                         *file,
+                                                                   GAsyncResult                  *result,
+                                                                   guint64                       *disk_usage,
+                                                                   guint64                       *num_dirs,
+                                                                   guint64                       *num_files,
+                                                                   GError                       **error);
+
 typedef GFileIface GFileInterface;
 G_DEFINE_INTERFACE (GFile, g_file, G_TYPE_OBJECT)
 
@@ -357,6 +381,9 @@ g_file_default_init (GFileIface *iface)
   iface->set_attributes_from_info = g_file_real_set_attributes_from_info;
   iface->copy_async = g_file_real_copy_async;
   iface->copy_finish = g_file_real_copy_finish;
+  iface->measure_disk_usage = g_file_real_measure_disk_usage;
+  iface->measure_disk_usage_async = g_file_real_measure_disk_usage_async;
+  iface->measure_disk_usage_finish = g_file_real_measure_disk_usage_finish;
 }
 
 
@@ -7336,6 +7363,280 @@ g_file_replace_contents_finish (GFile         *file,
   return TRUE;
 }
 
+gboolean
+g_file_real_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)
+{
+  g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
+                       "Operation not supported for the current backend.");
+  return FALSE;
+}
+
+typedef struct
+{
+  GFileMeasureFlags             flags;
+  GFileMeasureProgressCallback  progress_callback;
+  gpointer                      progress_data;
+} MeasureTaskData;
+
+typedef struct
+{
+  guint64 disk_usage;
+  guint64 num_dirs;
+  guint64 num_files;
+} MeasureResult;
+
+typedef struct
+{
+  GFileMeasureProgressCallback callback;
+  gpointer                     user_data;
+  gboolean                     reporting;
+  guint64                      current_size;
+  guint64                      num_dirs;
+  guint64                      num_files;
+} MeasureProgress;
+
+static gboolean
+measure_disk_usage_invoke_progress (gpointer user_data)
+{
+  MeasureProgress *progress = user_data;
+
+  (* progress->callback) (progress->reporting,
+                          progress->current_size, progress->num_dirs, progress->num_files,
+                          progress->user_data);
+
+  return FALSE;
+}
+
+static void
+measure_disk_usage_progress (gboolean reporting,
+                             guint64  current_size,
+                             guint64  num_dirs,
+                             guint64  num_files,
+                             gpointer user_data)
+{
+  MeasureProgress progress;
+  GTask *task = user_data;
+  MeasureTaskData *data;
+
+  data = g_task_get_task_data (task);
+
+  progress.callback = data->progress_callback;
+  progress.user_data = data->progress_data;
+  progress.reporting = reporting;
+  progress.current_size = current_size;
+  progress.num_dirs = num_dirs;
+  progress.num_files = num_files;
+
+  g_main_context_invoke_full (g_task_get_context (task),
+                              g_task_get_priority (task),
+                              measure_disk_usage_invoke_progress,
+                              g_memdup (&progress, sizeof progress),
+                              g_free);
+}
+
+static void
+measure_disk_usage_thread (GTask        *task,
+                           gpointer      source_object,
+                           gpointer      task_data,
+                           GCancellable *cancellable)
+{
+  MeasureTaskData *data = task_data;
+  GError *error = NULL;
+  MeasureResult result;
+
+  if (g_file_measure_disk_usage (source_object, data->flags, cancellable,
+                                 measure_disk_usage_progress, task,
+                                 &result.disk_usage, &result.num_dirs, &result.num_files,
+                                 &error))
+    g_task_return_pointer (task, g_memdup (&result, sizeof result), g_free);
+  else
+    g_task_return_error (task, error);
+}
+
+static void
+g_file_real_measure_disk_usage_async (GFile                        *file,
+                                      GFileMeasureFlags             flags,
+                                      gint                          io_priority,
+                                      GCancellable                 *cancellable,
+                                      GFileMeasureProgressCallback  progress_callback,
+                                      gpointer                      progress_data,
+                                      GAsyncReadyCallback           callback,
+                                      gpointer                      user_data)
+{
+  MeasureTaskData data;
+  GTask *task;
+
+  data.flags = flags;
+  data.progress_callback = progress_callback;
+  data.progress_data = progress_data;
+
+  task = g_task_new (file, cancellable, callback, user_data);
+  g_task_set_task_data (task, g_memdup (&data, sizeof data), g_free);
+  g_task_set_priority (task, io_priority);
+
+  g_task_run_in_thread (task, measure_disk_usage_thread);
+  g_object_unref (task);
+}
+
+static gboolean
+g_file_real_measure_disk_usage_finish (GFile         *file,
+                                       GAsyncResult  *result,
+                                       guint64       *disk_usage,
+                                       guint64       *num_dirs,
+                                       guint64       *num_files,
+                                       GError       **error)
+{
+  guint64 *reported_usage;
+
+  g_return_val_if_fail (g_task_is_valid (result, file), FALSE);
+
+  reported_usage = g_task_propagate_pointer (G_TASK (result), error);
+
+  if (reported_usage == NULL)
+    return FALSE;
+
+  if (disk_usage)
+    *disk_usage = *reported_usage;
+
+  g_free (reported_usage);
+
+  return TRUE;
+}
+
+/**
+ * g_file_measure_disk_usage:
+ * @file: a #GFile
+ * @flags: #GFileMeasureFlags
+ * @cancellable: (allow-none): optional #GCancellable
+ * @progress_callback: (allow-none): a #GFileMeasureProgressCallback
+ * @progress_data: user_data for @progress_callback
+ * @disk_usage: (allow-none) (out): the number of bytes of disk space used
+ * @num_dirs: (allow-none) (out): the number of directories encountered
+ * @num_files: (allow-none) (out): the number of non-directories encountered
+ * @error: (allow-none): %NULL, or a pointer to a %NULL #GError pointer
+ *
+ * Recursively measures the disk usage of @file.
+ *
+ * This is essentially an analog of the '<literal>du</literal>' command,
+ * but it also reports the number of directories and non-directory files
+ * encountered (including things like symbolic links).
+ *
+ * By default, errors are only reported against the toplevel file
+ * itself.  Errors found while recursing are silently ignored, unless
+ * %G_FILE_DISK_USAGE_REPORT_ALL_ERRORS is given in @flags.
+ *
+ * The returned size, @disk_usage, is in bytes and should be formatted
+ * with g_format_size() in order to get something reasonable for showing
+ * in a user interface.
+ *
+ * @progress_callback and @progress_data can be given to request
+ * periodic progress updates while scanning.  See the documentation for
+ * #GFileMeasureProgressCallback for information about when and how the
+ * callback will be invoked.
+ *
+ * Returns: %TRUE if successful, with the out parameters set.
+ *          %FALSE otherwise, with @error set.
+ *
+ * Since: 2.38
+ **/
+gboolean
+g_file_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)
+{
+  g_return_val_if_fail (G_IS_FILE (file), FALSE);
+  g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), FALSE);
+  g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+  return G_FILE_GET_IFACE (file)->measure_disk_usage (file, flags, cancellable,
+                                                      progress_callback, progress_data,
+                                                      disk_usage, num_dirs, num_files,
+                                                      error);
+}
+
+/**
+ * g_file_measure_disk_usage_async:
+ * @file: a #GFile
+ * @flags: #GFileMeasureFlags
+ * @io_priority: the <link linkend="io-priority">I/O priority</link>
+ *     of the request
+ * @cancellable: (allow-none): optional #GCancellable
+ * @progress_callback: (allow-none): a #GFileMeasureProgressCallback
+ * @progress_data: user_data for @progress_callback
+ * @callback: (allow-none): a #GAsyncReadyCallback to call when complete
+ * @user_data: the data to pass to callback function
+ *
+ * Recursively measures the disk usage of @file.
+ *
+ * This is the asynchronous version of g_file_measure_disk_usage().  See
+ * there for more information.
+ *
+ * Since: 2.38
+ **/
+void
+g_file_measure_disk_usage_async (GFile                        *file,
+                                 GFileMeasureFlags             flags,
+                                 gint                          io_priority,
+                                 GCancellable                 *cancellable,
+                                 GFileMeasureProgressCallback  progress_callback,
+                                 gpointer                      progress_data,
+                                 GAsyncReadyCallback           callback,
+                                 gpointer                      user_data)
+{
+  g_return_if_fail (G_IS_FILE (file));
+  g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable));
+
+  return G_FILE_GET_IFACE (file)->measure_disk_usage_async (file, flags, io_priority, cancellable,
+                                                            progress_callback, progress_data,
+                                                            callback, user_data);
+}
+
+/**
+ * g_file_measure_disk_usage_finish:
+ * @file: a #GFile
+ * @result: the #GAsyncResult passed to your #GAsyncReadyCallback
+ * @disk_usage: (allow-none) (out): the number of bytes of disk space used
+ * @num_dirs: (allow-none) (out): the number of directories encountered
+ * @num_files: (allow-none) (out): the number of non-directories encountered
+ * @error: (allow-none): %NULL, or a pointer to a %NULL #GError pointer
+ *
+ * Collects the results from an earlier call to
+ * g_file_measure_disk_usage_async().  See g_file_measure_disk_usage() for
+ * more information.
+ *
+ * Returns: %TRUE if successful, with the out parameters set.
+ *          %FALSE otherwise, with @error set.
+ *
+ * Since: 2.38
+ **/
+gboolean
+g_file_measure_disk_usage_finish (GFile         *file,
+                                  GAsyncResult  *result,
+                                  guint64       *disk_usage,
+                                  guint64       *num_dirs,
+                                  guint64       *num_files,
+                                  GError       **error)
+{
+  g_return_val_if_fail (G_IS_FILE (file), FALSE);
+  g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+  return G_FILE_GET_IFACE (file)->measure_disk_usage_finish (file, result, disk_usage, num_dirs, num_files, 
error);
+}
+
 /**
  * g_file_start_mountable:
  * @file: input #GFile
diff --git a/gio/gfile.h b/gio/gfile.h
index 0cf6ee2..394c643 100644
--- a/gio/gfile.h
+++ b/gio/gfile.h
@@ -561,6 +561,30 @@ struct _GFileIface
   gboolean            (* poll_mountable_finish)       (GFile                *file,
                                                        GAsyncResult         *result,
                                                        GError              **error);
+
+  gboolean            (* 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);
+  void                (* measure_disk_usage_async)    (GFile                         *file,
+                                                       GFileMeasureFlags              flags,
+                                                       gint                           io_priority,
+                                                       GCancellable                  *cancellable,
+                                                       GFileMeasureProgressCallback   progress_callback,
+                                                       gpointer                       progress_data,
+                                                       GAsyncReadyCallback            callback,
+                                                       gpointer                       user_data);
+  gboolean            (* measure_disk_usage_finish)   (GFile                         *file,
+                                                       GAsyncResult                  *result,
+                                                       guint64                       *disk_usage,
+                                                       guint64                       *num_dirs,
+                                                       guint64                       *num_files,
+                                                       GError                       **error);
 };
 
 GLIB_AVAILABLE_IN_ALL
@@ -1085,6 +1109,35 @@ GFileMonitor*           g_file_monitor                    (GFile
                                                           GCancellable           *cancellable,
                                                           GError                **error);
 
+GLIB_AVAILABLE_IN_2_38
+gboolean                g_file_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);
+
+GLIB_AVAILABLE_IN_2_38
+void                    g_file_measure_disk_usage_async   (GFile                         *file,
+                                                           GFileMeasureFlags              flags,
+                                                           gint                           io_priority,
+                                                           GCancellable                  *cancellable,
+                                                           GFileMeasureProgressCallback   progress_callback,
+                                                           gpointer                       progress_data,
+                                                           GAsyncReadyCallback            callback,
+                                                           gpointer                       user_data);
+
+GLIB_AVAILABLE_IN_2_38
+gboolean                g_file_measure_disk_usage_finish  (GFile                         *file,
+                                                           GAsyncResult                  *result,
+                                                           guint64                       *disk_usage,
+                                                           guint64                       *num_dirs,
+                                                           guint64                       *num_files,
+                                                           GError                       **error);
+
 GLIB_AVAILABLE_IN_ALL
 void                    g_file_start_mountable            (GFile                      *file,
                                                           GDriveStartFlags            flags,
diff --git a/gio/gioenums.h b/gio/gioenums.h
index 9767226..4ce0855 100644
--- a/gio/gioenums.h
+++ b/gio/gioenums.h
@@ -211,6 +211,29 @@ typedef enum {
   G_FILE_CREATE_REPLACE_DESTINATION = (1 << 1)
 } GFileCreateFlags;
 
+/**
+ * GFileMeasureFlags:
+ * @G_FILE_MEASURE_NONE: No flags set.
+ * @G_FILE_MEASURE_REPORT_ANY_ERROR: Report any error encountered
+ *   while traversing the directory tree.  Normally errors are only
+ *   reported for the toplevel file.
+ * @G_FILE_MEASURE_APPARENT_SIZE: Tally usage based on apparent file
+ *   sizes.  Normally, the block-size is used, if available, as this is a
+ *   more accurate representation of disk space used.
+ *   Compare with '<literal>du --apparent-size</literal>'.
+ * @G_FILE_MEASURE_NO_XDEV: Do not cross mount point boundaries.
+ *   Compare with '<literal>du -x</literal>'.
+ *
+ * Flags that can be used with g_file_measure_disk_usage().
+ *
+ * Since: 2.38
+ **/
+typedef enum {
+  G_FILE_MEASURE_NONE                 = 0,
+  G_FILE_MEASURE_REPORT_ANY_ERROR     = (1 << 1),
+  G_FILE_MEASURE_APPARENT_SIZE        = (1 << 2),
+  G_FILE_MEASURE_NO_XDEV              = (1 << 3)
+} GFileMeasureFlags;
 
 /**
  * GMountMountFlags:
diff --git a/gio/giotypes.h b/gio/giotypes.h
index adcbdae..fdf7202 100644
--- a/gio/giotypes.h
+++ b/gio/giotypes.h
@@ -295,6 +295,49 @@ typedef gboolean (* GFileReadMoreCallback) (const char *file_contents,
                                             goffset file_size,
                                             gpointer callback_data);
 
+/**
+ * GFileMeasureProgressCallback:
+ * @reporting: %TRUE if more reports will come
+ * @current_size: the current cumulative size measurement
+ * @num_dirs: the number of directories visited so far
+ * @num_files: the number of non-directory files encountered
+ * @user_data: the data passed to the original request for this callback
+ *
+ * This callback type is used by g_file_measure_disk_usage() to make
+ * periodic progress reports when measuring the amount of disk spaced
+ * used by a directory.
+ *
+ * These calls are made on a best-effort basis and not all types of
+ * #GFile will support them.  At the minimum, however, one call will
+ * always be made immediately.
+ *
+ * In the case that there is no support, @reporting will be set to
+ * %FALSE (and the other values undefined) and no further calls will be
+ * made.  Otherwise, the @reporting will be %TRUE and the other values
+ * all-zeros during the first (immediate) call.  In this way, you can
+ * know which type of progress UI to show without a delay.
+ *
+ * For g_file_measure_disk_usage() the callback is made directly.  For
+ * g_file_measure_disk_usage_async() the callback is made via the
+ * default main context of the calling thread (ie: the same way that the
+ * final async result would be reported).
+ *
+ * @current_size is in the same units as requested by the operation (see
+ * %G_FILE_DISK_USAGE_APPARENT_SIZE).
+ *
+ * The frequency of the updates is implementation defined, but is
+ * ideally about once every 200ms.
+ *
+ * The last progress callback may or may not be equal to the final
+ * result.  Always check the async result to get the final value.
+ *
+ * Since: 2.38
+ **/
+typedef void (* GFileMeasureProgressCallback) (gboolean reporting,
+                                               guint64  current_size,
+                                               guint64  num_dirs,
+                                               guint64  num_files,
+                                               gpointer user_data);
 
 /**
  * GIOSchedulerJobFunc:
diff --git a/gio/glocalfile.c b/gio/glocalfile.c
index 67e8313..ea59018 100644
--- a/gio/glocalfile.c
+++ b/gio/glocalfile.c
@@ -27,6 +27,7 @@
 #include <string.h>
 #include <errno.h>
 #include <fcntl.h>
+#include <dirent.h>
 #ifdef HAVE_UNISTD_H
 #include <unistd.h>
 #endif
@@ -2514,6 +2515,293 @@ g_local_file_monitor_file (GFile             *file,
   return _g_local_file_monitor_new (local_file->filename, flags, is_remote (local_file->filename), error);
 }
 
+
+/* Here is the GLocalFile implementation of g_file_measure_disk_usage().
+ *
+ * If available, we use fopenat() in preference to filenames for
+ * efficiency and safety reasons.  We know that fopenat() is available
+ * based on if AT_FDCWD is defined.  POSIX guarantees that this will be
+ * defined as a macro.
+ *
+ * We use a linked list of stack-allocated GSList nodes in order to be
+ * able to reconstruct the filename for error messages.  We actually
+ * pass the filename to operate on through the top node of the list.
+ *
+ * In case we're using openat(), this top filename will be a basename
+ * which should be opened in the directory which has also had its fd
+ * passed along.  If we're not using openat() then it will be a full
+ * absolute filename.
+ */
+
+static gboolean
+g_local_file_measure_size_error (GFileMeasureFlags   flags,
+                                 gint                saved_errno,
+                                 GSList             *name,
+                                 GError            **error)
+{
+  /* Only report an error if we were at the toplevel or if the caller
+   * requested reporting of all errors.
+   */
+  if ((name->next == NULL) || (flags & G_FILE_MEASURE_REPORT_ANY_ERROR))
+    {
+      GString *filename;
+      GSList *node;
+
+      /* Skip some work if there is no error return */
+      if (!error)
+        return FALSE;
+
+#ifdef AT_FDCWD
+      /* If using openat() we need to rebuild the filename for the message */
+      filename = g_string_new (name->data);
+      for (node = name->next; node; node = node->next)
+        {
+          g_string_prepend_c (filename, G_DIR_SEPARATOR);
+          g_string_prepend (filename, node->data);
+        }
+
+      g_string_prepend (filename, "file://");
+#else
+      /* Otherwise, we already have it, so just use it. */
+      node = name;
+      filename = g_string_new ("file://");
+      g_string_append (filename, node->data);
+#endif
+
+      g_set_error (error, G_IO_ERROR, g_io_error_from_errno (saved_errno),
+                   _("Could not determine the disk usage of %s: %s"),
+                   filename->str, g_strerror (saved_errno));
+
+      g_string_free (filename, TRUE);
+
+      return FALSE;
+    }
+
+  else
+    /* We're not reporting this error... */
+    return TRUE;
+}
+
+typedef struct
+{
+  GFileMeasureFlags  flags;
+  dev_t              contained_on;
+  GCancellable      *cancellable;
+
+  GFileMeasureProgressCallback progress_callback;
+  gpointer                     progress_data;
+
+  guint64 disk_usage;
+  guint64 num_dirs;
+  guint64 num_files;
+
+  guint64 last_progress_report;
+} MeasureState;
+
+static gboolean
+g_local_file_measure_size_of_contents (gint           fd,
+                                       GSList        *dir_name,
+                                       MeasureState  *state,
+                                       GError       **error);
+
+static gboolean
+g_local_file_measure_size_of_file (gint           parent_fd,
+                                   GSList        *name,
+                                   MeasureState  *state,
+                                   GError       **error)
+{
+  struct stat buf;
+
+  if (g_cancellable_set_error_if_cancelled (state->cancellable, error))
+    return FALSE;
+
+#if defined (AT_FDCWD)
+  if (fstatat (parent_fd, name->data, &buf, AT_SYMLINK_NOFOLLOW) != 0)
+#elif defined (HAVE_LSTAT)
+  if (lstat (name->data, &buf) != 0)
+#else
+  if (stat (name->data, &buf) != 0)
+#endif
+    return g_local_file_measure_size_error (state->flags, errno, name, error);
+
+  if (name->next)
+    {
+      /* If not at the toplevel, check for a device boundary. */
+
+      if (state->flags & G_FILE_MEASURE_NO_XDEV)
+        if (state->contained_on != buf.st_dev)
+          return TRUE;
+    }
+  else
+    {
+      /* If, however, this is the toplevel, set the device number so
+       * that recursive invocations can compare against it.
+       */
+      state->contained_on = buf.st_dev;
+    }
+
+#if defined (HAVE_STRUCT_STAT_ST_BLOCKS)
+  if (~state->flags & G_FILE_MEASURE_APPARENT_SIZE)
+    state->disk_usage += buf.st_blocks * G_GUINT64_CONSTANT (512);
+  else
+#endif
+    state->disk_usage += buf.st_size;
+
+  if (S_ISDIR (buf.st_mode))
+    state->num_dirs++;
+  else
+    state->num_files++;
+
+  if (state->progress_callback)
+    {
+      /* We could attempt to do some cleverness here in order to avoid
+       * calling clock_gettime() so much, but we're doing stats and opens
+       * all over the place already...
+       */
+      if (state->last_progress_report)
+        {
+          guint64 now;
+
+          now = g_get_monotonic_time ();
+
+          if (state->last_progress_report + 200 * G_TIME_SPAN_MILLISECOND < now)
+            {
+              (* state->progress_callback) (TRUE,
+                                            state->disk_usage, state->num_dirs, state->num_files,
+                                            state->progress_data);
+              state->last_progress_report = now;
+            }
+        }
+      else
+        {
+          /* We must do an initial report to inform that more reports
+           * will be coming.
+           */
+          (* state->progress_callback) (TRUE, 0, 0, 0, state->progress_data);
+          state->last_progress_report = g_get_monotonic_time ();
+        }
+    }
+
+  if (S_ISDIR (buf.st_mode))
+    {
+      int dir_fd = -1;
+
+      if (g_cancellable_set_error_if_cancelled (state->cancellable, error))
+        return FALSE;
+
+#ifdef AT_FDCWD
+      dir_fd = openat (parent_fd, name->data, O_RDONLY | O_DIRECTORY);
+      if (dir_fd < 0)
+        return g_local_file_measure_size_error (state->flags, errno, name, error);
+#endif
+
+      if (!g_local_file_measure_size_of_contents (dir_fd, name, state, error))
+        return FALSE;
+    }
+
+  return TRUE;
+}
+
+static gboolean
+g_local_file_measure_size_of_contents (gint           fd,
+                                       GSList        *dir_name,
+                                       MeasureState  *state,
+                                       GError       **error)
+{
+  gboolean success = TRUE;
+  struct dirent *entry;
+  DIR *dirp;
+
+#ifdef AT_FDCWD
+  dirp = fdopendir (fd);
+#else
+  dirp = opendir (dir_name->data);
+#endif
+
+  if (dirp == NULL)
+    {
+      gint saved_errno = errno;
+
+#ifdef AT_FDCWD
+      close (fd);
+#endif
+
+      return g_local_file_measure_size_error (state->flags, saved_errno, dir_name, error);
+    }
+
+  while (success && (entry = readdir (dirp)))
+    {
+      gchar *name = entry->d_name;
+      GSList node;
+
+      node.next = dir_name;
+#ifdef AT_FDCWD
+      node.data = name;
+#else
+      node.data = g_build_filename (dir_name->data, name, NULL);
+#endif
+
+      /* skip '.' and '..' */
+      if (name[0] == '.' &&
+          (name[1] == '\0' ||
+           (name[1] == '.' && name[2] == '\0')))
+        continue;
+
+      success = g_local_file_measure_size_of_file (fd, &node, state, error);
+
+#ifndef AT_FDCWD
+      g_free (node.data);
+#endif
+    }
+
+  closedir (dirp);
+
+  return success;
+}
+
+static gboolean
+g_local_file_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)
+{
+  GLocalFile *local_file = G_LOCAL_FILE (file);
+  MeasureState state = { 0, };
+  gint root_fd = -1;
+  GSList node;
+
+  state.flags = flags;
+  state.cancellable = cancellable;
+  state.progress_callback = progress_callback;
+  state.progress_data = progress_data;
+
+#ifdef AT_FDCWD
+  root_fd = AT_FDCWD;
+#endif
+
+  node.data = local_file->filename;
+  node.next = NULL;
+
+  if (!g_local_file_measure_size_of_file (root_fd, &node, &state, error))
+    return FALSE;
+
+  if (disk_usage)
+    *disk_usage = state.disk_usage;
+
+  if (num_dirs)
+    *num_dirs = state.num_dirs;
+
+  if (num_files)
+    *num_files = state.num_files;
+
+  return TRUE;
+}
+
 static void
 g_local_file_file_iface_init (GFileIface *iface)
 {
@@ -2556,6 +2844,7 @@ g_local_file_file_iface_init (GFileIface *iface)
   iface->move = g_local_file_move;
   iface->monitor_dir = g_local_file_monitor_dir;
   iface->monitor_file = g_local_file_monitor_file;
+  iface->measure_disk_usage = g_local_file_measure_disk_usage;
 
   iface->supports_thread_contexts = TRUE;
 }


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