mirror of https://gitee.com/openkylin/gvfs.git
427 lines
9.7 KiB
C
427 lines
9.7 KiB
C
/*
|
|
* Copyright © 2008 Ryan Lortie
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of version 3 of the GNU General Public License as
|
|
* published by the Free Software Foundation.
|
|
*/
|
|
|
|
#include "trashdir.h"
|
|
|
|
#include <sys/stat.h>
|
|
#include <string.h>
|
|
|
|
#include "dirwatch.h"
|
|
|
|
struct OPAQUE_TYPE__TrashDir
|
|
{
|
|
TrashRoot *root;
|
|
GSList *items;
|
|
|
|
GFile *directory;
|
|
GFile *topdir;
|
|
gboolean is_homedir;
|
|
GDateTime *mtime;
|
|
|
|
DirWatch *watch;
|
|
GFileMonitor *monitor;
|
|
};
|
|
|
|
static gint
|
|
compare_basename (gconstpointer a,
|
|
gconstpointer b)
|
|
{
|
|
GFile *file_a, *file_b;
|
|
char *name_a, *name_b;
|
|
gint result;
|
|
|
|
file_a = (GFile *) a;
|
|
file_b = (GFile *) b;
|
|
|
|
name_a = g_file_get_basename (file_a);
|
|
name_b = g_file_get_basename (file_b);
|
|
|
|
result = strcmp (name_a, name_b);
|
|
|
|
g_free (name_a);
|
|
g_free (name_b);
|
|
|
|
return result;
|
|
}
|
|
|
|
static GDateTime *
|
|
trash_dir_query_mtime (TrashDir *dir)
|
|
{
|
|
GFileInfo *info;
|
|
GDateTime *datetime = NULL;
|
|
|
|
info = g_file_query_info (dir->directory, G_FILE_ATTRIBUTE_TIME_MODIFIED ","
|
|
G_FILE_ATTRIBUTE_TIME_MODIFIED_USEC,
|
|
G_FILE_QUERY_INFO_NONE, NULL, NULL);
|
|
if (info)
|
|
{
|
|
datetime = g_file_info_get_modification_date_time (info);
|
|
g_object_unref (info);
|
|
}
|
|
|
|
return datetime;
|
|
}
|
|
|
|
static void
|
|
trash_dir_set_files (TrashDir *dir,
|
|
GSList *items)
|
|
{
|
|
GSList **old, *new;
|
|
|
|
items = g_slist_sort (items, (GCompareFunc) compare_basename);
|
|
old = &dir->items;
|
|
new = items;
|
|
|
|
while (new || *old)
|
|
{
|
|
int result;
|
|
|
|
if ((result = (new == NULL) - (*old == NULL)) == 0)
|
|
result = compare_basename (new->data, (*old)->data);
|
|
|
|
if (result < 0)
|
|
{
|
|
/* new entry. add it. */
|
|
*old = g_slist_prepend (*old, new->data); /* take reference */
|
|
old = &(*old)->next;
|
|
trash_root_add_item (dir->root, new->data, dir->topdir, dir->is_homedir);
|
|
new = new->next;
|
|
}
|
|
else if (result > 0)
|
|
{
|
|
/* old entry. remove it. */
|
|
trash_root_remove_item (dir->root, (*old)->data, dir->is_homedir);
|
|
g_object_unref ((*old)->data);
|
|
*old = g_slist_delete_link (*old, *old);
|
|
}
|
|
else
|
|
{
|
|
/* match. no change. */
|
|
old = &(*old)->next;
|
|
g_object_unref (new->data);
|
|
new = new->next;
|
|
}
|
|
}
|
|
|
|
g_slist_free (items);
|
|
|
|
trash_root_thaw (dir->root);
|
|
}
|
|
|
|
static void
|
|
trash_dir_empty (TrashDir *dir)
|
|
{
|
|
trash_dir_set_files (dir, NULL);
|
|
}
|
|
|
|
static void
|
|
trash_dir_enumerate (TrashDir *dir)
|
|
{
|
|
GFileEnumerator *enumerator;
|
|
GSList *files = NULL;
|
|
|
|
g_clear_pointer (&dir->mtime, g_date_time_unref);
|
|
dir->mtime = trash_dir_query_mtime (dir);
|
|
enumerator = g_file_enumerate_children (dir->directory,
|
|
G_FILE_ATTRIBUTE_STANDARD_NAME,
|
|
G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
|
|
NULL, NULL);
|
|
|
|
if (enumerator)
|
|
{
|
|
GFileInfo *info;
|
|
|
|
while ((info = g_file_enumerator_next_file (enumerator, NULL, NULL)))
|
|
{
|
|
GFile *file;
|
|
|
|
file = g_file_get_child (dir->directory,
|
|
g_file_info_get_name (info));
|
|
files = g_slist_prepend (files, file);
|
|
|
|
g_object_unref (info);
|
|
}
|
|
|
|
g_object_unref (enumerator);
|
|
}
|
|
|
|
trash_dir_set_files (dir, files); /* consumes files */
|
|
}
|
|
|
|
static void
|
|
trash_dir_changed (GFileMonitor *monitor,
|
|
GFile *file,
|
|
GFile *other_file,
|
|
GFileMonitorEvent event_type,
|
|
gpointer user_data)
|
|
{
|
|
TrashDir *dir = user_data;
|
|
|
|
if (event_type == G_FILE_MONITOR_EVENT_CREATED)
|
|
trash_root_add_item (dir->root, file, dir->topdir, dir->is_homedir);
|
|
|
|
else if (event_type == G_FILE_MONITOR_EVENT_DELETED)
|
|
trash_root_remove_item (dir->root, file, dir->is_homedir);
|
|
|
|
else if (event_type == G_FILE_MONITOR_EVENT_PRE_UNMOUNT ||
|
|
event_type == G_FILE_MONITOR_EVENT_UNMOUNTED ||
|
|
event_type == G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT)
|
|
;
|
|
|
|
else
|
|
{
|
|
static gboolean already_did_warning;
|
|
char *dirname;
|
|
char *name;
|
|
|
|
g_warning ("*** Unsupported operation detected on trash directory");
|
|
if (!already_did_warning)
|
|
{
|
|
g_warning (" A trash files/ directory should only have files "
|
|
"linked or unlinked (via moves or deletes). Some other "
|
|
"operation has been detected on a file in the directory "
|
|
"(eg: a file has been modified). Likely, the data "
|
|
"reported by the trash backend will now be "
|
|
"inconsistent.");
|
|
already_did_warning = TRUE;
|
|
}
|
|
|
|
name = file ? g_file_get_basename (file) : NULL;
|
|
dirname = g_file_get_path (dir->directory);
|
|
g_warning (" dir: %s, file: %s, type: %d\n\n",
|
|
dirname, name, event_type);
|
|
g_free (dirname);
|
|
g_free (name);
|
|
}
|
|
|
|
trash_root_thaw (dir->root);
|
|
}
|
|
|
|
static void
|
|
trash_dir_created (gpointer user_data)
|
|
{
|
|
TrashDir *dir = user_data;
|
|
|
|
g_assert (dir->monitor == NULL);
|
|
dir->monitor = g_file_monitor_directory (dir->directory, 0, NULL, NULL);
|
|
g_signal_connect (dir->monitor, "changed",
|
|
G_CALLBACK (trash_dir_changed), dir);
|
|
trash_dir_enumerate (dir);
|
|
}
|
|
|
|
static void
|
|
trash_dir_check (gpointer user_data)
|
|
{
|
|
TrashDir *dir = user_data;
|
|
|
|
trash_dir_enumerate (dir);
|
|
}
|
|
|
|
static void
|
|
trash_dir_destroyed (gpointer user_data)
|
|
{
|
|
TrashDir *dir = user_data;
|
|
|
|
g_assert (dir->monitor != NULL);
|
|
g_object_unref (dir->monitor);
|
|
dir->monitor = NULL;
|
|
g_clear_pointer (&dir->mtime, g_date_time_unref);
|
|
|
|
trash_dir_empty (dir);
|
|
}
|
|
|
|
void
|
|
trash_dir_watch (TrashDir *dir)
|
|
{
|
|
g_assert (dir->monitor == NULL);
|
|
g_assert (dir->watch == NULL);
|
|
|
|
/* start monitoring after a period of not monitoring.
|
|
*
|
|
* there are two possible cases here:
|
|
* 1) the directory now exists
|
|
* - we have to rescan the directory to ensure that we notice
|
|
* any changes that have occured since we last looked
|
|
*
|
|
* 2) the directory does not exist
|
|
* - if it existed last time we looked then we may have stale
|
|
* toplevel items that need to be removed.
|
|
*
|
|
* in case 1, trash_dir_created() will be called from
|
|
* dir_watch_new(). it calls trash_enumerate() itself.
|
|
*
|
|
* in case 2, no other function will be called and we must manually
|
|
* call trash_dir_empty().
|
|
*
|
|
* we can tell if case 1 happened because trash_dir_created() also
|
|
* sets the dir->monitor.
|
|
*/
|
|
dir->watch = dir_watch_new (dir->directory, dir->topdir,
|
|
trash_dir_created,
|
|
trash_dir_check,
|
|
trash_dir_destroyed,
|
|
dir);
|
|
|
|
if (dir->monitor == NULL)
|
|
/* case 2 */
|
|
trash_dir_empty (dir);
|
|
}
|
|
|
|
void
|
|
trash_dir_unwatch (TrashDir *dir)
|
|
{
|
|
g_assert (dir->watch != NULL);
|
|
|
|
/* stop monitoring.
|
|
*
|
|
* in all cases, we just fall silent.
|
|
*/
|
|
|
|
if (dir->monitor != NULL)
|
|
{
|
|
g_object_unref (dir->monitor);
|
|
dir->monitor = NULL;
|
|
}
|
|
|
|
dir_watch_free (dir->watch);
|
|
dir->watch = NULL;
|
|
}
|
|
|
|
static gboolean
|
|
dir_exists (GFile *directory,
|
|
GFile *top_dir)
|
|
{
|
|
gboolean result = FALSE;
|
|
GFile *parent;
|
|
|
|
if (g_file_equal (directory, top_dir))
|
|
return TRUE;
|
|
|
|
parent = g_file_get_parent (directory);
|
|
|
|
if (dir_exists (parent, top_dir))
|
|
{
|
|
struct stat buf;
|
|
gchar *path;
|
|
|
|
path = g_file_get_path (directory);
|
|
result = !lstat (path, &buf) && S_ISDIR (buf.st_mode);
|
|
|
|
g_free (path);
|
|
}
|
|
|
|
g_object_unref (parent);
|
|
|
|
return result;
|
|
}
|
|
|
|
static gboolean
|
|
trash_dir_is_dirty (TrashDir *dir)
|
|
{
|
|
GDateTime *mtime;
|
|
gboolean retval = TRUE;
|
|
|
|
if (dir->mtime == NULL)
|
|
return TRUE;
|
|
|
|
mtime = trash_dir_query_mtime (dir);
|
|
if (mtime == NULL)
|
|
return TRUE;
|
|
|
|
if (g_date_time_equal (mtime, dir->mtime))
|
|
retval = FALSE;
|
|
g_date_time_unref (mtime);
|
|
|
|
return retval;
|
|
}
|
|
|
|
void
|
|
trash_dir_rescan (TrashDir *dir)
|
|
{
|
|
if (!trash_dir_is_dirty (dir))
|
|
return;
|
|
|
|
if (dir->watch)
|
|
dir_watch_check (dir->watch);
|
|
|
|
else if (dir_exists (dir->directory, dir->topdir))
|
|
trash_dir_enumerate (dir);
|
|
|
|
else
|
|
trash_dir_empty (dir);
|
|
}
|
|
|
|
static trash_dir_ui_hook ui_hook;
|
|
|
|
TrashDir *
|
|
trash_dir_new (TrashRoot *root,
|
|
gboolean watching,
|
|
gboolean is_homedir,
|
|
const char *mount_point,
|
|
const char *format,
|
|
...)
|
|
{
|
|
TrashDir *dir;
|
|
va_list ap;
|
|
char *rel;
|
|
|
|
va_start (ap, format);
|
|
rel = g_strdup_vprintf (format, ap);
|
|
va_end (ap);
|
|
|
|
dir = g_slice_new (TrashDir);
|
|
|
|
dir->root = root;
|
|
dir->items = NULL;
|
|
dir->topdir = g_file_new_for_path (mount_point);
|
|
dir->directory = g_file_get_child (dir->topdir, rel);
|
|
dir->monitor = NULL;
|
|
dir->is_homedir = is_homedir;
|
|
dir->mtime = NULL;
|
|
|
|
if (watching)
|
|
dir->watch = dir_watch_new (dir->directory,
|
|
dir->topdir,
|
|
trash_dir_created,
|
|
trash_dir_check,
|
|
trash_dir_destroyed,
|
|
dir);
|
|
else
|
|
dir->watch = NULL;
|
|
|
|
if (ui_hook)
|
|
ui_hook (dir, dir->directory);
|
|
|
|
g_free (rel);
|
|
|
|
return dir;
|
|
}
|
|
|
|
void
|
|
trash_dir_set_ui_hook (trash_dir_ui_hook _ui_hook)
|
|
{
|
|
ui_hook = _ui_hook;
|
|
}
|
|
|
|
void
|
|
trash_dir_free (TrashDir *dir)
|
|
{
|
|
if (dir->watch)
|
|
dir_watch_free (dir->watch);
|
|
|
|
if (dir->monitor)
|
|
g_object_unref (dir->monitor);
|
|
|
|
trash_dir_set_files (dir, NULL);
|
|
|
|
g_object_unref (dir->directory);
|
|
g_object_unref (dir->topdir);
|
|
|
|
g_slice_free (TrashDir, dir);
|
|
}
|