Tree of tag thunar-4.20.7 (commit df0e054) from https://gitlab.xfce.org/xfce/thunar. Upstream history is not included here to keep the repository small.
429 lines
13 KiB
C
429 lines
13 KiB
C
/* vi:set sw=2 sts=2 ts=2 et ai: */
|
|
/*-
|
|
* Copyright (c) 2009 Jannis Pohlmann <jannis@xfce.org>
|
|
* Copyright (c) 2012 Nick Schermer <nick@xfce.org>
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or (at
|
|
* your option) any later version.
|
|
*
|
|
* This program 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
|
|
* General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 59 Temple Place, Suite 330, Boston,
|
|
* MA 02111-1307 USA
|
|
*/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include "config.h"
|
|
#endif
|
|
|
|
#include "thunar/thunar-deep-count-job.h"
|
|
#include "thunar/thunar-job.h"
|
|
#include "thunar/thunar-marshal.h"
|
|
#include "thunar/thunar-private.h"
|
|
#include "thunar/thunar-util.h"
|
|
|
|
#include <gio/gio.h>
|
|
#include <glib-object.h>
|
|
#include <glib.h>
|
|
|
|
|
|
|
|
/* Signal identifiers */
|
|
enum
|
|
{
|
|
STATUS_UPDATE,
|
|
LAST_SIGNAL,
|
|
};
|
|
|
|
|
|
#define DEEP_COUNT_FILE_INFO_NAMESPACE \
|
|
G_FILE_ATTRIBUTE_STANDARD_TYPE "," G_FILE_ATTRIBUTE_STANDARD_SIZE "," G_FILE_ATTRIBUTE_STANDARD_ALLOCATED_SIZE "," G_FILE_ATTRIBUTE_ID_FILESYSTEM
|
|
|
|
static void
|
|
thunar_deep_count_job_finalize (GObject *object);
|
|
static gboolean
|
|
thunar_deep_count_job_execute (ExoJob *job,
|
|
GError **error);
|
|
|
|
|
|
|
|
struct _ThunarDeepCountJobClass
|
|
{
|
|
ThunarJobClass __parent__;
|
|
|
|
/* signals */
|
|
void (*status_update) (ThunarJob *job,
|
|
guint64 total_size,
|
|
guint64 total_size_on_disk,
|
|
guint file_count,
|
|
guint directory_count,
|
|
guint unreadable_directory_count);
|
|
};
|
|
|
|
struct _ThunarDeepCountJob
|
|
{
|
|
ThunarJob __parent__;
|
|
|
|
GList *files;
|
|
GFileQueryInfoFlags query_flags;
|
|
|
|
/* the time of the last "status-update" emission */
|
|
gint64 last_time;
|
|
|
|
/* status information */
|
|
guint64 total_size;
|
|
guint64 total_size_on_disk;
|
|
guint file_count;
|
|
guint directory_count;
|
|
guint unreadable_directory_count;
|
|
};
|
|
|
|
|
|
|
|
static guint deep_count_signals[LAST_SIGNAL];
|
|
|
|
|
|
|
|
G_DEFINE_TYPE (ThunarDeepCountJob, thunar_deep_count_job, THUNAR_TYPE_JOB)
|
|
|
|
|
|
|
|
static void
|
|
thunar_deep_count_job_class_init (ThunarDeepCountJobClass *klass)
|
|
{
|
|
ExoJobClass *job_class;
|
|
GObjectClass *gobject_class;
|
|
|
|
gobject_class = G_OBJECT_CLASS (klass);
|
|
gobject_class->finalize = thunar_deep_count_job_finalize;
|
|
|
|
job_class = EXO_JOB_CLASS (klass);
|
|
job_class->execute = thunar_deep_count_job_execute;
|
|
|
|
/**
|
|
* ThunarDeepCountJob::status-update:
|
|
* @job : a #ThunarJob.
|
|
* @total_size : the total size in bytes.
|
|
* @total_size_on_disk : the total allocated size in bytes.
|
|
* @file_count : the number of files.
|
|
* @directory_count : the number of directories.
|
|
* @unreadable_directory_count : the number of unreadable directories.
|
|
*
|
|
* Emitted by the @job to inform listeners about the number of files,
|
|
* directories and bytes counted so far.
|
|
**/
|
|
deep_count_signals[STATUS_UPDATE] =
|
|
g_signal_new ("status-update",
|
|
G_TYPE_FROM_CLASS (klass),
|
|
G_SIGNAL_NO_HOOKS,
|
|
G_STRUCT_OFFSET (ThunarDeepCountJobClass, status_update),
|
|
NULL, NULL,
|
|
_thunar_marshal_VOID__UINT64_UINT64_UINT_UINT_UINT,
|
|
G_TYPE_NONE, 5,
|
|
G_TYPE_UINT64,
|
|
G_TYPE_UINT64,
|
|
G_TYPE_UINT,
|
|
G_TYPE_UINT,
|
|
G_TYPE_UINT);
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
thunar_deep_count_job_init (ThunarDeepCountJob *job)
|
|
{
|
|
job->query_flags = G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS;
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
thunar_deep_count_job_finalize (GObject *object)
|
|
{
|
|
ThunarDeepCountJob *job = THUNAR_DEEP_COUNT_JOB (object);
|
|
|
|
g_list_free_full (job->files, g_object_unref);
|
|
|
|
(*G_OBJECT_CLASS (thunar_deep_count_job_parent_class)->finalize) (object);
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
thunar_deep_count_job_status_update (ThunarDeepCountJob *job)
|
|
{
|
|
_thunar_return_if_fail (THUNAR_IS_DEEP_COUNT_JOB (job));
|
|
|
|
exo_job_emit (EXO_JOB (job),
|
|
deep_count_signals[STATUS_UPDATE],
|
|
0,
|
|
job->total_size,
|
|
job->total_size_on_disk,
|
|
job->file_count,
|
|
job->directory_count,
|
|
job->unreadable_directory_count);
|
|
}
|
|
|
|
|
|
|
|
static gboolean
|
|
thunar_deep_count_job_process (ExoJob *job,
|
|
GFile *file,
|
|
GFileInfo *file_info,
|
|
const gchar *toplevel_fs_id,
|
|
GError **error)
|
|
{
|
|
ThunarDeepCountJob *count_job = THUNAR_DEEP_COUNT_JOB (job);
|
|
GFileEnumerator *enumerator;
|
|
GFileInfo *child_info;
|
|
GFileInfo *info;
|
|
gboolean success = TRUE;
|
|
GFile *child;
|
|
gint64 real_time;
|
|
const gchar *fs_id;
|
|
gboolean toplevel_file = (toplevel_fs_id == NULL);
|
|
|
|
_thunar_return_val_if_fail (THUNAR_IS_JOB (job), FALSE);
|
|
_thunar_return_val_if_fail (G_IS_FILE (file), FALSE);
|
|
_thunar_return_val_if_fail (file_info == NULL || G_IS_FILE_INFO (file_info), FALSE);
|
|
_thunar_return_val_if_fail (error == NULL || *error == NULL, FALSE);
|
|
|
|
/* abort if job was already cancelled */
|
|
if (exo_job_is_cancelled (job))
|
|
return FALSE;
|
|
|
|
if (file_info != NULL)
|
|
{
|
|
/* use the enumerator info */
|
|
info = g_object_ref (file_info);
|
|
}
|
|
else
|
|
{
|
|
/* query size and type of the current file */
|
|
info = g_file_query_info (file,
|
|
DEEP_COUNT_FILE_INFO_NAMESPACE,
|
|
count_job->query_flags,
|
|
exo_job_get_cancellable (job),
|
|
error);
|
|
}
|
|
|
|
/* abort on invalid info or cancellation */
|
|
if (info == NULL)
|
|
return FALSE;
|
|
|
|
/* abort on cancellation */
|
|
if (exo_job_is_cancelled (job))
|
|
{
|
|
g_object_unref (info);
|
|
return FALSE;
|
|
}
|
|
|
|
/* only check files on the same filesystem so no remote mounts or
|
|
* dummy filesystems are counted */
|
|
fs_id = g_file_info_get_attribute_string (info, G_FILE_ATTRIBUTE_ID_FILESYSTEM);
|
|
if (fs_id == NULL)
|
|
fs_id = "";
|
|
|
|
if (toplevel_fs_id == NULL)
|
|
{
|
|
/* first toplevel, so use this id */
|
|
toplevel_fs_id = fs_id;
|
|
}
|
|
else if (strcmp (fs_id, toplevel_fs_id) != 0)
|
|
{
|
|
/* release the file info */
|
|
g_object_unref (info);
|
|
|
|
/* other filesystem, continue */
|
|
return TRUE;
|
|
}
|
|
|
|
/* recurse if we have a directory */
|
|
if (g_file_info_get_file_type (info) == G_FILE_TYPE_DIRECTORY)
|
|
{
|
|
/* try to read from the directory */
|
|
enumerator = g_file_enumerate_children (file,
|
|
DEEP_COUNT_FILE_INFO_NAMESPACE "," G_FILE_ATTRIBUTE_STANDARD_NAME,
|
|
count_job->query_flags,
|
|
exo_job_get_cancellable (job),
|
|
error);
|
|
|
|
if (!exo_job_is_cancelled (job))
|
|
{
|
|
if (enumerator == NULL)
|
|
{
|
|
/* directory was unreadable */
|
|
count_job->unreadable_directory_count++;
|
|
|
|
if (toplevel_file
|
|
&& g_list_length (count_job->files) < 2)
|
|
{
|
|
/* we only bail out if the job file is unreadable */
|
|
success = FALSE;
|
|
}
|
|
else
|
|
{
|
|
/* ignore errors from files other than the job file */
|
|
g_clear_error (error);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* directory was readable */
|
|
count_job->directory_count++;
|
|
|
|
while (!exo_job_is_cancelled (job))
|
|
{
|
|
/* query next child info */
|
|
child_info = g_file_enumerator_next_file (enumerator,
|
|
exo_job_get_cancellable (job),
|
|
error);
|
|
|
|
/* abort on invalid child info (iteration ends) or cancellation */
|
|
if (child_info == NULL)
|
|
break;
|
|
|
|
if (!exo_job_is_cancelled (job))
|
|
{
|
|
/* generate a GFile for the child */
|
|
child = g_file_resolve_relative_path (file, g_file_info_get_name (child_info));
|
|
|
|
/* recurse unless the job was cancelled before */
|
|
thunar_deep_count_job_process (job, child, child_info, toplevel_fs_id, error);
|
|
|
|
/* free resources */
|
|
g_object_unref (child);
|
|
}
|
|
|
|
g_object_unref (child_info);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* destroy the enumerator */
|
|
if (enumerator != NULL)
|
|
g_object_unref (enumerator);
|
|
|
|
/* emit status update whenever we've finished a directory,
|
|
* but not more than four times per second */
|
|
real_time = g_get_real_time ();
|
|
if (real_time >= count_job->last_time)
|
|
{
|
|
if (count_job->last_time != 0)
|
|
thunar_deep_count_job_status_update (count_job);
|
|
count_job->last_time = real_time + (G_USEC_PER_SEC / 4);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* we have a regular file or at least not a directory */
|
|
count_job->file_count++;
|
|
|
|
/* add size of the file to the total size */
|
|
count_job->total_size += g_file_info_get_attribute_uint64 (info, G_FILE_ATTRIBUTE_STANDARD_SIZE);
|
|
|
|
/* add allocated size of the file to the total allocated size */
|
|
if (count_job->total_size_on_disk != (guint64) -1)
|
|
{
|
|
if (g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_STANDARD_ALLOCATED_SIZE))
|
|
count_job->total_size_on_disk += g_file_info_get_attribute_uint64 (info, G_FILE_ATTRIBUTE_STANDARD_ALLOCATED_SIZE);
|
|
else
|
|
count_job->total_size_on_disk = (guint64) -1;
|
|
}
|
|
}
|
|
|
|
/* destroy the file info */
|
|
g_object_unref (info);
|
|
|
|
/* we've succeeded if there was no error when loading information
|
|
* about the job file itself and the job was not cancelled */
|
|
return !exo_job_is_cancelled (job) && success;
|
|
}
|
|
|
|
|
|
|
|
static gboolean
|
|
thunar_deep_count_job_execute (ExoJob *job,
|
|
GError **error)
|
|
{
|
|
ThunarDeepCountJob *count_job = THUNAR_DEEP_COUNT_JOB (job);
|
|
gboolean success = TRUE;
|
|
GError *err = NULL;
|
|
GList *lp;
|
|
GFile *gfile;
|
|
|
|
_thunar_return_val_if_fail (THUNAR_IS_JOB (job), FALSE);
|
|
_thunar_return_val_if_fail (error == NULL || *error == NULL, FALSE);
|
|
|
|
/* don't start the job if it was already cancelled */
|
|
if (exo_job_set_error_if_cancelled (job, error))
|
|
return FALSE;
|
|
|
|
/* reset counters */
|
|
count_job->total_size = 0;
|
|
count_job->total_size_on_disk = 0;
|
|
count_job->file_count = 0;
|
|
count_job->directory_count = 0;
|
|
count_job->unreadable_directory_count = 0;
|
|
count_job->last_time = 0;
|
|
|
|
/* count files, directories and compute size of the job files */
|
|
for (lp = count_job->files; lp != NULL; lp = lp->next)
|
|
{
|
|
gfile = thunar_file_get_file (THUNAR_FILE (lp->data));
|
|
success = thunar_deep_count_job_process (job, gfile, NULL, NULL, &err);
|
|
if (G_UNLIKELY (!success))
|
|
break;
|
|
}
|
|
|
|
if (!success)
|
|
{
|
|
g_assert (err != NULL || exo_job_is_cancelled (job));
|
|
|
|
/* set error if the job was cancelled. otherwise just propagate
|
|
* the results of the processing function */
|
|
if (exo_job_set_error_if_cancelled (job, error))
|
|
{
|
|
if (err != NULL)
|
|
g_error_free (err);
|
|
}
|
|
else
|
|
{
|
|
if (err != NULL)
|
|
g_propagate_error (error, err);
|
|
}
|
|
}
|
|
else if (!exo_job_is_cancelled (job))
|
|
{
|
|
/* emit final status update at the very end of the computation */
|
|
thunar_deep_count_job_status_update (count_job);
|
|
}
|
|
|
|
return success;
|
|
}
|
|
|
|
|
|
|
|
ThunarDeepCountJob *
|
|
thunar_deep_count_job_new (GList *files,
|
|
GFileQueryInfoFlags flags)
|
|
{
|
|
ThunarDeepCountJob *job;
|
|
|
|
_thunar_return_val_if_fail (files != NULL, NULL);
|
|
|
|
job = g_object_new (THUNAR_TYPE_DEEP_COUNT_JOB, NULL);
|
|
job->files = g_list_copy (files);
|
|
job->query_flags = flags;
|
|
|
|
g_list_foreach (job->files, (GFunc) (void (*) (void)) g_object_ref, NULL);
|
|
|
|
return job;
|
|
}
|