gvfs/daemon/gvfsbackendrecent.c

845 lines
24 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
* Copyright © 2012 Red Hat, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published
* by the Free Software Foundation; either version 2 of the licence or (at
* your option) any later version.
*
* See the included COPYING file for more information.
*/
#include "gvfsbackendrecent.h"
#include <glib/gi18n.h> /* _() */
#include <string.h>
#include "gvfsjobcreatemonitor.h"
#include "gvfsjobopenforread.h"
#include "gvfsjobqueryfsinfo.h"
#include "gvfsjobqueryinfo.h"
#include "gvfsjobenumerate.h"
#include "gvfsjobseekread.h"
#include "gvfsjobqueryinforead.h"
#include "gvfsjobread.h"
typedef GVfsBackendClass GVfsBackendRecentClass;
typedef struct {
char *guid;
char *uri;
char *display_name;
GFile *file;
GDateTime *modified;
} RecentItem;
struct OPAQUE_TYPE__GVfsBackendRecent
{
GVfsBackend parent_instance;
GBookmarkFile *bookmarks;
gchar *filename;
GFileMonitor *monitor;
GHashTable *uri_map;
GHashTable *items;
GVfsMonitor *file_monitor;
GVfsMonitor *dir_monitor;
};
G_DEFINE_TYPE (GVfsBackendRecent, g_vfs_backend_recent, G_VFS_TYPE_BACKEND);
#define RECENTLY_USED_FILE "recently-used.xbel"
static GVfsMonitor *
recent_backend_get_file_monitor (GVfsBackendRecent *backend,
gboolean create)
{
if (backend->file_monitor == NULL && create == FALSE)
return NULL;
else if (backend->file_monitor == NULL)
{
/* 'create' is only ever set in the main thread, so we will have
* no possibility here for creating more than one new monitor.
*/
/* FIXME */
backend->file_monitor = g_vfs_monitor_new (G_VFS_BACKEND (backend));
}
return g_object_ref (backend->file_monitor);
}
static GVfsMonitor *
recent_backend_get_dir_monitor (GVfsBackendRecent *backend,
gboolean create)
{
if (backend->dir_monitor == NULL && create == FALSE)
return NULL;
else if (backend->dir_monitor == NULL)
{
backend->dir_monitor = g_vfs_monitor_new (G_VFS_BACKEND (backend));
}
return g_object_ref (backend->dir_monitor);
}
static GFile *
recent_backend_get_file (GVfsBackendRecent *backend,
const char *filename,
RecentItem **item_ret,
GError **error)
{
GFile *file = NULL;
RecentItem *item;
filename++;
item = g_hash_table_lookup (backend->items, filename);
if (item)
{
file = g_object_ref (item->file);
if (item_ret)
*item_ret = item;
}
if (file == NULL)
g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND,
_("No such file or directory"));
return file;
}
/* ======================= method implementations ======================= */
static gboolean
recent_backend_open_for_read (GVfsBackend *vfs_backend,
GVfsJobOpenForRead *job,
const char *filename)
{
GVfsBackendRecent *backend = G_VFS_BACKEND_RECENT (vfs_backend);
GError *error = NULL;
if (filename[1] == '\0')
g_set_error_literal (&error, G_IO_ERROR, G_IO_ERROR_IS_DIRECTORY,
_("Cant open directory"));
else
{
GFile *real;
real = recent_backend_get_file (backend, filename, NULL, &error);
if (real)
{
GFileInputStream *stream;
stream = g_file_read (real, G_VFS_JOB (job)->cancellable, &error);
g_object_unref (real);
if (stream)
{
g_vfs_job_open_for_read_set_handle (job, stream);
g_vfs_job_open_for_read_set_can_seek (job, TRUE);
g_vfs_job_succeeded (G_VFS_JOB (job));
return TRUE;
}
}
}
g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
g_error_free (error);
return TRUE;
}
static gboolean
recent_backend_read (GVfsBackend *vfs_backend,
GVfsJobRead *job,
GVfsBackendHandle handle,
char *buffer,
gsize bytes_requested)
{
GError *error = NULL;
gssize bytes;
bytes = g_input_stream_read (handle, buffer, bytes_requested,
G_VFS_JOB (job)->cancellable, &error);
if (bytes >= 0)
{
g_vfs_job_read_set_size (job, bytes);
g_vfs_job_succeeded (G_VFS_JOB (job));
return TRUE;
}
g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
g_error_free (error);
return TRUE;
}
static gboolean
recent_backend_seek_on_read (GVfsBackend *vfs_backend,
GVfsJobSeekRead *job,
GVfsBackendHandle handle,
goffset offset,
GSeekType type)
{
GError *error = NULL;
if (g_seekable_seek (handle, offset, type, NULL, &error))
{
g_vfs_job_seek_read_set_offset (job, g_seekable_tell (handle));
g_vfs_job_succeeded (G_VFS_JOB (job));
return TRUE;
}
g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
g_error_free (error);
return TRUE;
}
static void
recent_backend_query_info_on_read (GVfsBackend *backend,
GVfsJobQueryInfoRead *job,
GVfsBackendHandle handle,
GFileInfo *info,
GFileAttributeMatcher *matcher)
{
GError *error = NULL;
GFileInfo *real_info;
real_info = g_file_input_stream_query_info (handle,
job->attributes,
G_VFS_JOB (job)->cancellable,
&error);
if (real_info)
{
g_file_info_copy_into (real_info, info);
g_vfs_job_succeeded (G_VFS_JOB (job));
g_object_unref (real_info);
}
else
{
g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
g_error_free (error);
}
}
static gboolean
recent_backend_close_read (GVfsBackend *vfs_backend,
GVfsJobCloseRead *job,
GVfsBackendHandle handle)
{
GError *error = NULL;
if (g_input_stream_close (handle, G_VFS_JOB (job)->cancellable, &error))
{
g_vfs_job_succeeded (G_VFS_JOB (job));
g_object_unref (handle);
return TRUE;
}
g_object_unref (handle);
g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
g_error_free (error);
return TRUE;
}
static gboolean
recent_backend_delete (GVfsBackend *vfs_backend,
GVfsJobDelete *job,
const char *filename)
{
GVfsBackendRecent *backend = G_VFS_BACKEND_RECENT (vfs_backend);
GError *error = NULL;
g_debug ("before job: %d\n", G_OBJECT(job)->ref_count);
if (filename[1] == '\0')
{
g_set_error_literal (&error, G_IO_ERROR, G_IO_ERROR_PERMISSION_DENIED,
_("The recent folder may not be deleted"));
}
else
{
RecentItem *item;
item = g_hash_table_lookup (backend->items, filename + 1);
if (item)
{
gboolean res;
res = g_bookmark_file_remove_item (backend->bookmarks, item->uri, &error);
if (res)
res = g_bookmark_file_to_file (backend->bookmarks, backend->filename, &error);
if (res)
{
g_vfs_job_succeeded (G_VFS_JOB (job));
return TRUE;
}
}
else
g_set_error_literal (&error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND,
_("No such file or directory"));
}
g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
g_error_free (error);
return TRUE;
}
static void
recent_backend_add_info (RecentItem *item,
GFileInfo *info)
{
g_assert (item != NULL);
g_file_info_set_name (info, item->guid);
g_file_info_set_display_name (info, item->display_name);
g_file_info_set_attribute_string (info, G_FILE_ATTRIBUTE_STANDARD_TARGET_URI, item->uri);
g_file_info_set_attribute_boolean (info,
G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE,
FALSE);
g_file_info_set_attribute_boolean (info,
G_FILE_ATTRIBUTE_ACCESS_CAN_EXECUTE,
FALSE);
g_file_info_set_attribute_boolean (info,
G_FILE_ATTRIBUTE_ACCESS_CAN_RENAME,
FALSE);
g_file_info_set_attribute_boolean (info,
G_FILE_ATTRIBUTE_ACCESS_CAN_TRASH,
FALSE);
g_file_info_set_attribute_boolean (info,
G_FILE_ATTRIBUTE_ACCESS_CAN_DELETE,
TRUE);
/* G_FILE_ATTRIBUTE_RECENT_MODIFIED */
g_file_info_set_attribute_int64 (info, "recent::modified",
g_date_time_to_unix (item->modified));
}
static gboolean
recent_backend_enumerate (GVfsBackend *vfs_backend,
GVfsJobEnumerate *job,
const char *filename,
GFileAttributeMatcher *attribute_matcher,
GFileQueryInfoFlags flags)
{
GVfsBackendRecent *backend = G_VFS_BACKEND_RECENT (vfs_backend);
GHashTableIter iter;
gpointer key, value;
g_assert (filename[0] == '/');
g_vfs_job_succeeded (G_VFS_JOB (job));
g_hash_table_iter_init (&iter, backend->items);
while (g_hash_table_iter_next (&iter, &key, &value))
{
RecentItem *item = value;
GFileInfo *info;
info = g_file_query_info (item->file,
job->attributes,
flags,
G_VFS_JOB (job)->cancellable,
NULL);
if (info)
{
g_file_info_set_attribute_mask (info, attribute_matcher);
recent_backend_add_info (item, info);
g_vfs_job_enumerate_add_info (job, info);
g_object_unref (info);
}
}
g_vfs_job_enumerate_done (job);
return TRUE;
}
static void
recent_item_free (RecentItem *item)
{
g_free (item->uri);
g_free (item->display_name);
g_free (item->guid);
g_clear_object (&item->file);
g_date_time_unref (item->modified);
g_free (item);
}
static gboolean
recent_item_update (RecentItem *item,
const gchar *uri,
const gchar *display_name,
GDateTime *modified)
{
gboolean changed = FALSE;
if (g_strcmp0 (item->uri, uri) != 0)
{
changed = TRUE;
g_free (item->uri);
item->uri = g_strdup (uri);
g_clear_object (&item->file);
item->file = g_file_new_for_uri (item->uri);
}
if (g_strcmp0 (item->display_name, display_name) != 0)
{
changed = TRUE;
g_free (item->display_name);
item->display_name = g_strdup (display_name);
}
if (!g_date_time_equal (item->modified, modified))
{
changed = TRUE;
g_date_time_unref (item->modified);
item->modified = g_date_time_ref (modified);
}
return changed;
}
static RecentItem *
recent_item_new (const gchar *uri,
const gchar *display_name,
GDateTime *modified)
{
RecentItem *item;
item = g_new0 (RecentItem, 1);
item->guid = g_dbus_generate_guid ();
item->modified = g_date_time_ref (modified);
recent_item_update (item, uri, display_name, modified);
return item;
}
static gboolean
should_include (GBookmarkFile *bookmarks,
const gchar *uri)
{
gchar *mimetype, *filename;
/* Is public */
if (g_bookmark_file_get_is_private (bookmarks, uri, NULL))
return FALSE;
/* Is local */
if (g_ascii_strncasecmp (uri, "file:/", 6) != 0)
return FALSE;
/* Is not dir */
mimetype = g_bookmark_file_get_mime_type (bookmarks, uri, NULL);
if (g_strcmp0 (mimetype, "inode/directory") == 0)
{
g_free (mimetype);
return FALSE;
}
g_free (mimetype);
/* Exists */
filename = g_filename_from_uri (uri, NULL, NULL);
if (!g_file_test (filename, G_FILE_TEST_EXISTS))
{
g_free (filename);
return FALSE;
}
g_free (filename);
return TRUE;
}
static char *
get_display_name (GBookmarkFile *bookmarks,
const gchar *uri)
{
gchar *filename, *display_name;
display_name = g_bookmark_file_get_title (bookmarks, uri, NULL);
if (display_name == NULL)
{
filename = g_filename_from_uri (uri, NULL, NULL);
display_name = g_filename_display_basename (filename);
g_free (filename);
}
return display_name;
}
static void
reload_recent_items (GVfsBackendRecent *backend)
{
GVfsMonitor *monitor;
GList *added = NULL;
GList *changed = NULL;
GList *not_seen_items = NULL;
GList *l;
GError *error = NULL;
gchar **uris;
gsize uris_len, i;
g_debug ("reloading recent items\n");
g_bookmark_file_load_from_file (backend->bookmarks, backend->filename, &error);
if (error != NULL)
{
g_warning ("Unable to load %s: %s", backend->filename, error->message);
g_clear_error (&error);
g_bookmark_file_free (backend->bookmarks);
backend->bookmarks = g_bookmark_file_new ();
}
not_seen_items = g_hash_table_get_values (backend->items);
uris = g_bookmark_file_get_uris (backend->bookmarks, &uris_len);
for (i = 0; i < uris_len; i++)
{
const char *uri = uris[i];
const char *guid;
char *display_name;
GDateTime *modified;
if (should_include (backend->bookmarks, uri))
{
display_name = get_display_name (backend->bookmarks, uri);
modified = g_bookmark_file_get_modified_date_time (backend->bookmarks, uri, NULL);
guid = g_hash_table_lookup (backend->uri_map, uri);
if (guid)
{
RecentItem *item;
item = g_hash_table_lookup (backend->items, guid);
if (recent_item_update (item, uri, display_name, modified))
changed = g_list_prepend (changed, item->guid);
not_seen_items = g_list_remove (not_seen_items, item);
}
else
{
RecentItem *item;
item = recent_item_new (uri, display_name, modified);
added = g_list_prepend (added, item->guid);
g_hash_table_insert (backend->items, item->guid, item);
g_hash_table_insert (backend->uri_map, item->uri, item->guid);
}
g_free (display_name);
}
}
g_strfreev (uris);
monitor = recent_backend_get_dir_monitor (backend, FALSE);
/* process removals */
for (l = not_seen_items; l; l = l->next)
{
RecentItem *item = l->data;
g_hash_table_remove (backend->uri_map, item->uri);
g_hash_table_steal (backend->items, item->guid);
if (monitor)
g_vfs_monitor_emit_event (monitor, G_FILE_MONITOR_EVENT_DELETED, item->guid, NULL);
recent_item_free (item);
}
g_list_free (not_seen_items);
/* process additions */
if (monitor)
{
for (l = added; l; l = l->next)
g_vfs_monitor_emit_event (monitor, G_FILE_MONITOR_EVENT_CREATED, l->data, NULL);
}
g_list_free (added);
/* process changes */
if (monitor)
{
for (l = changed; l; l = l->next)
g_vfs_monitor_emit_event (monitor, G_FILE_MONITOR_EVENT_ATTRIBUTE_CHANGED, l->data, NULL);
}
g_list_free (changed);
if (monitor)
g_object_unref (monitor);
}
static void
bookmarks_changed (GFileMonitor *monitor,
GFile *file,
GFile *other_file,
GFileMonitorEvent event_type,
gpointer user_data)
{
GVfsBackendRecent *backend = G_VFS_BACKEND_RECENT (user_data);
gchar *filename;
switch (event_type)
{
case G_FILE_MONITOR_EVENT_CREATED:
case G_FILE_MONITOR_EVENT_DELETED:
filename = g_file_get_path (file);
if (g_strcmp0 (filename, backend->filename) != 0)
{
g_free (filename);
break;
}
g_free (filename);
case G_FILE_MONITOR_EVENT_CHANGED:
reload_recent_items (backend);
break;
default:
break;
}
}
static gboolean
recent_backend_mount (GVfsBackend *vfs_backend,
GVfsJobMount *job,
GMountSpec *mount_spec,
GMountSource *mount_source,
gboolean is_automount)
{
GVfsBackendRecent *backend = G_VFS_BACKEND_RECENT (vfs_backend);
GError *error = NULL;
GFile *file;
backend->bookmarks = g_bookmark_file_new ();
backend->filename = g_build_filename (g_get_user_data_dir (), RECENTLY_USED_FILE, NULL);
file = g_file_new_for_path (backend->filename);
backend->monitor = g_file_monitor_file (file, G_FILE_MONITOR_NONE, NULL, &error);
g_object_unref (file);
if (error != NULL)
{
g_warning ("Unable to monitor %s: %s", backend->filename, error->message);
g_clear_error (&error);
}
else
g_signal_connect (backend->monitor, "changed", G_CALLBACK (bookmarks_changed), backend);
reload_recent_items (backend);
g_vfs_job_succeeded (G_VFS_JOB (job));
return TRUE;
}
static gboolean
recent_backend_query_info (GVfsBackend *vfs_backend,
GVfsJobQueryInfo *job,
const char *filename,
GFileQueryInfoFlags flags,
GFileInfo *info,
GFileAttributeMatcher *matcher)
{
GVfsBackendRecent *backend = G_VFS_BACKEND_RECENT (vfs_backend);
g_assert (filename[0] == '/');
if (filename[1])
{
GError *error = NULL;
RecentItem *item = NULL;
GFile *real;
real = recent_backend_get_file (backend, filename, &item, &error);
if (real)
{
GFileInfo *real_info;
real_info = g_file_query_info (real,
job->attributes,
flags,
G_VFS_JOB (job)->cancellable,
&error);
g_object_unref (real);
if (real_info)
{
g_file_info_copy_into (real_info, info);
recent_backend_add_info (item, info);
g_vfs_job_succeeded (G_VFS_JOB (job));
g_object_unref (real_info);
return TRUE;
}
}
g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
g_error_free (error);
}
else
{
GIcon *icon;
g_file_info_set_file_type (info, G_FILE_TYPE_DIRECTORY);
g_file_info_set_name (info, "/");
/* Translators: this is the display name of the backend */
g_file_info_set_display_name (info, _("Recent"));
g_file_info_set_content_type (info, "inode/directory");
g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE, FALSE);
g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_DELETE, TRUE);
g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_TRASH, FALSE);
icon = g_themed_icon_new ("document-open-recent");
g_file_info_set_icon (info, icon);
g_object_unref (icon);
icon = g_themed_icon_new ("document-open-recent-symbolic");
g_file_info_set_symbolic_icon (info, icon);
g_object_unref (icon);
g_vfs_job_succeeded (G_VFS_JOB (job));
}
return TRUE;
}
static gboolean
recent_backend_query_fs_info (GVfsBackend *vfs_backend,
GVfsJobQueryFsInfo *job,
const char *filename,
GFileInfo *info,
GFileAttributeMatcher *matcher)
{
g_file_info_set_attribute_string (info,
G_FILE_ATTRIBUTE_FILESYSTEM_TYPE,
"recent");
g_file_info_set_attribute_boolean (info,
G_FILE_ATTRIBUTE_FILESYSTEM_REMOTE,
FALSE);
g_file_info_set_attribute_boolean (info,
G_FILE_ATTRIBUTE_FILESYSTEM_READONLY,
FALSE);
g_file_info_set_attribute_uint32 (info,
G_FILE_ATTRIBUTE_FILESYSTEM_USE_PREVIEW,
G_FILESYSTEM_PREVIEW_TYPE_IF_LOCAL);
g_vfs_job_succeeded (G_VFS_JOB (job));
return TRUE;
}
static gboolean
recent_backend_create_dir_monitor (GVfsBackend *vfs_backend,
GVfsJobCreateMonitor *job,
const char *filename,
GFileMonitorFlags flags)
{
GVfsBackendRecent *backend = G_VFS_BACKEND_RECENT (vfs_backend);
GVfsMonitor *monitor;
if (filename[1])
monitor = g_vfs_monitor_new (vfs_backend);
else
monitor = recent_backend_get_dir_monitor (backend, TRUE);
g_vfs_job_create_monitor_set_monitor (job, monitor);
g_vfs_job_succeeded (G_VFS_JOB (job));
g_object_unref (monitor);
return TRUE;
}
static gboolean
recent_backend_create_file_monitor (GVfsBackend *vfs_backend,
GVfsJobCreateMonitor *job,
const char *filename,
GFileMonitorFlags flags)
{
GVfsBackendRecent *backend = G_VFS_BACKEND_RECENT (vfs_backend);
GVfsMonitor *monitor;
if (filename[1])
monitor = g_vfs_monitor_new (vfs_backend);
else
monitor = recent_backend_get_file_monitor (backend, TRUE);
g_vfs_job_create_monitor_set_monitor (job, monitor);
g_vfs_job_succeeded (G_VFS_JOB (job));
g_object_unref (monitor);
return TRUE;
}
static void
recent_backend_finalize (GObject *object)
{
GVfsBackendRecent *backend = G_VFS_BACKEND_RECENT (object);
g_clear_object (&backend->dir_monitor);
g_clear_object (&backend->file_monitor);
g_hash_table_destroy (backend->items);
g_hash_table_destroy (backend->uri_map);
g_clear_pointer (&backend->filename, g_free);
g_clear_pointer (&backend->bookmarks, g_bookmark_file_free);
if (backend->monitor)
{
g_signal_handlers_disconnect_by_func (backend->monitor, reload_recent_items, backend);
g_clear_object (&backend->monitor);
}
if (G_OBJECT_CLASS (g_vfs_backend_recent_parent_class)->finalize)
(*G_OBJECT_CLASS (g_vfs_backend_recent_parent_class)->finalize) (object);
}
static void
g_vfs_backend_recent_init (GVfsBackendRecent *backend)
{
GVfsBackend *vfs_backend = G_VFS_BACKEND (backend);
GMountSpec *mount_spec;
backend->items = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, (GDestroyNotify)recent_item_free);
backend->uri_map = g_hash_table_new (g_str_hash, g_str_equal);
/* translators: This is the name of the backend */
g_vfs_backend_set_display_name (vfs_backend, _("Recent"));
g_vfs_backend_set_icon_name (vfs_backend, "document-open-recent");
g_vfs_backend_set_symbolic_icon_name (vfs_backend, "document-open-recent-symbolic");
g_vfs_backend_set_user_visible (vfs_backend, FALSE);
mount_spec = g_mount_spec_new ("recent");
g_vfs_backend_set_mount_spec (vfs_backend, mount_spec);
g_mount_spec_unref (mount_spec);
}
static void
g_vfs_backend_recent_class_init (GVfsBackendRecentClass *class)
{
GObjectClass *gobject_class = G_OBJECT_CLASS (class);
GVfsBackendClass *backend_class = G_VFS_BACKEND_CLASS (class);
gobject_class->finalize = recent_backend_finalize;
backend_class->try_mount = recent_backend_mount;
backend_class->try_open_for_read = recent_backend_open_for_read;
backend_class->try_read = recent_backend_read;
backend_class->try_seek_on_read = recent_backend_seek_on_read;
backend_class->query_info_on_read = recent_backend_query_info_on_read;
backend_class->try_close_read = recent_backend_close_read;
backend_class->try_query_info = recent_backend_query_info;
backend_class->try_query_fs_info = recent_backend_query_fs_info;
backend_class->try_enumerate = recent_backend_enumerate;
backend_class->try_delete = recent_backend_delete;
backend_class->try_create_dir_monitor = recent_backend_create_dir_monitor;
backend_class->try_create_file_monitor = recent_backend_create_file_monitor;
}