thunar-usagebars/thunar/thunar-deep-count-job.c
4aiman df58603323 thunar 4.20.7 upstream
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.
2026-11-04 01:40:30 +03:00

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;
}