/* vi:set sw=2 sts=2 ts=2 et ai: */ /*- * Copyright (c) 2009 Jannis Pohlmann * Copyright (c) 2012 Nick Schermer * * 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 #endif #include #include #include #include #include #include #include #include /* 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_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, 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; 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. * @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_UINT_UINT_UINT, G_TYPE_NONE, 4, 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->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_size (info); } /* 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->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; }