Commit 6fe2ce60 authored by Ernestas Kulik's avatar Ernestas Kulik 🦑

Can’t be arsed to split this :|

parent dc99c0ee
......@@ -14,12 +14,51 @@ got_info (NautilusFile *file,
{
g_message ("Got info for %p",
(gpointer) file);
g_message ("\tDisplay name: %s\n",
g_message ("\tDisplay name: %s",
g_file_info_get_display_name (info));
g_message ("\tFile is directory: %s\n",
NAUTILUS_IS_DIRECTORY (file)? "yes" : "no");
g_object_unref (info);
g_main_loop_quit ((GMainLoop *) user_data);
if (user_data != NULL)
{
g_main_loop_quit ((GMainLoop *) user_data);
}
}
static void
got_children (NautilusDirectory *directory,
GList *children,
GError *error,
gpointer user_data)
{
g_message ("Got children for %p", (gpointer) directory);
if (children == NULL)
{
g_list_free (children);
g_main_loop_quit ((GMainLoop *) user_data);
return;
}
for (GList *i = children; i != NULL; i = i->next)
{
if (G_UNLIKELY (i->next == NULL))
{
nautilus_file_query_info (NAUTILUS_FILE (i->data),
NULL, got_info, user_data);
}
else
{
nautilus_file_query_info (NAUTILUS_FILE (i->data),
NULL, got_info, NULL);
}
}
g_list_free (children);
}
int
......@@ -43,9 +82,7 @@ main (int argc,
g_message ("Creating NautilusFile");
file = nautilus_file_new (location);
g_message ("\tGot %p", (gpointer) file);
g_message ("\tFile is directory: %s\n",
NAUTILUS_IS_DIRECTORY (file)? "yes" : "no");
g_message ("\tGot %p\n", (gpointer) file);
g_message ("Creating another NautilusFile for the same location");
duplicate_file = nautilus_file_new (location);
......@@ -55,7 +92,22 @@ main (int argc,
loop = g_main_loop_new (NULL, TRUE);
nautilus_file_query_info (file, NULL, got_info, loop);
if (NAUTILUS_IS_DIRECTORY (file))
{
nautilus_file_query_info (file, NULL, got_info, NULL);
}
else
{
nautilus_file_query_info (file, NULL, got_info, loop);
}
if (NAUTILUS_IS_DIRECTORY (file))
{
nautilus_directory_enumerate_children (NAUTILUS_DIRECTORY (file),
NULL,
got_children,
loop);
}
g_main_loop_run (loop);
......
......@@ -8,6 +8,10 @@ nautilus_ng_sources = ['nautilus-task.c',
'tasks/nautilus-attribute-task.h',
'nautilus-directory.c',
'nautilus-directory.h',
'tasks/nautilus-enumerate-children-task.c',
'tasks/nautilus-enumerate-children-task.h',
'nautilus-cache.c',
'nautilus-cache.h',
'main.c']
nautilus_ng_dependencies = [gio, glib]
......
/* Copyright (C) 2017 Ernestas Kulik <ernestask@gnome.org>
*
* This file is part of Nautilus.
*
* Nautilus 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.
*
* Nautilus 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 Nautilus. If not, see <http://www.gnu.org/licenses/>.
*/
#include "nautilus-cache.h"
typedef struct
{
gpointer value;
GDestroyNotify destroy_func;
NautilusCacheState state;
GMutex mutex;
} CacheItemDetails;
struct _NautilusCache
{
GObject parent_instance;
GHashTable *items;
};
G_DEFINE_TYPE (NautilusCache, nautilus_cache, G_TYPE_OBJECT)
static CacheItemDetails *
cache_item_details_new (GDestroyNotify destroy_func)
{
CacheItemDetails *details;
details = g_new0 (CacheItemDetails, 1);
details->destroy_func = destroy_func;
g_mutex_init (&details->mutex);
return details;
}
static void
cache_item_details_destroy (CacheItemDetails *details)
{
if (details->destroy_func != NULL)
{
details->destroy_func (details->value);
}
g_mutex_clear (&details->mutex);
g_free (details);
}
static void
finalize (GObject *object)
{
NautilusCache *self;
self = NAUTILUS_CACHE (object);
g_hash_table_destroy (self->items);
G_OBJECT_CLASS (nautilus_cache_parent_class)->finalize (object);
}
static void
nautilus_cache_class_init (NautilusCacheClass *klass)
{
GObjectClass *object_class;
object_class = G_OBJECT_CLASS (klass);
object_class->finalize = finalize;
}
static void
nautilus_cache_init (NautilusCache *self)
{
self->items =
g_hash_table_new_full (g_direct_hash, g_direct_equal,
NULL,
(GDestroyNotify) cache_item_details_destroy);
}
NautilusCacheState
nautilus_cache_item_get_state (NautilusCache *cache,
gssize item)
{
CacheItemDetails *details;
g_return_val_if_fail (NAUTILUS_IS_CACHE (cache), NAUTILUS_CACHE_INVALID);
g_return_val_if_fail (g_hash_table_contains (cache->items,
GINT_TO_POINTER (item)),
NAUTILUS_CACHE_INVALID);
details = g_hash_table_lookup (cache->items, GINT_TO_POINTER (item));
return details->state;
}
void
nautilus_cache_item_invalidate (NautilusCache *cache,
gssize item,
gboolean destroy)
{
CacheItemDetails *details;
g_return_if_fail (NAUTILUS_IS_CACHE (cache));
g_return_if_fail (g_hash_table_contains (cache->items,
GINT_TO_POINTER (item)));
details = g_hash_table_lookup (cache->items, GINT_TO_POINTER (item));
g_mutex_lock (&details->mutex);
/* There might be cases where the cache holding a ref on an object is a
* problem.
*/
if (destroy &&
details->destroy_func != NULL && G_LIKELY (details->value != NULL))
{
details->destroy_func (details->value);
}
details->state = NAUTILUS_CACHE_INVALID;
g_mutex_unlock (&details->mutex);
}
void
nautilus_cache_item_set_pending (NautilusCache *cache,
gssize item)
{
CacheItemDetails *details;
g_return_if_fail (NAUTILUS_IS_CACHE (cache));
g_return_if_fail (g_hash_table_contains (cache->items,
GINT_TO_POINTER (item)));
details = g_hash_table_lookup (cache->items, GINT_TO_POINTER (item));
g_mutex_lock (&details->mutex);
/* This whole method feels quite clunky.
* Changing the state from valid to pending should probably be treated
* as programmer error (or a bug).
*/
g_warn_if_fail (details->state == NAUTILUS_CACHE_INVALID);
if (details->state != NAUTILUS_CACHE_INVALID)
{
g_mutex_unlock (&details->mutex);
return;
}
details->state = NAUTILUS_CACHE_PENDING;
g_mutex_unlock (&details->mutex);
}
gpointer
nautilus_cache_item_get_value (NautilusCache *cache,
gssize item,
NautilusCopyFunc copy_func)
{
CacheItemDetails *details;
g_return_val_if_fail (NAUTILUS_IS_CACHE (cache), NULL);
g_return_val_if_fail (g_hash_table_contains (cache->items,
GINT_TO_POINTER (item)),
NULL);
details = g_hash_table_lookup (cache->items, GINT_TO_POINTER (item));
if (copy_func == NULL)
{
return details->value;
}
else
{
gpointer ret;
g_mutex_lock (&details->mutex);
ret = copy_func (details->value);
g_mutex_unlock (&details->mutex);
return ret;
}
}
void
nautilus_cache_item_set_value (NautilusCache *cache,
gssize item,
gpointer value)
{
CacheItemDetails *details;
g_return_if_fail (NAUTILUS_IS_CACHE (cache));
g_return_if_fail (g_hash_table_contains (cache->items,
GINT_TO_POINTER (item)));
details = g_hash_table_lookup (cache->items, GINT_TO_POINTER (item));
g_mutex_lock (&details->mutex);
/* We’ll treat this as a cancellation of the update. */
if (details->state != NAUTILUS_CACHE_PENDING)
{
g_mutex_unlock (&details->mutex);
return;
}
if (details->destroy_func != NULL && G_LIKELY (details->value != NULL))
{
g_clear_pointer (&details->value, details->destroy_func);
}
details->value = value;
details->state = NAUTILUS_CACHE_VALID;
g_mutex_unlock (&details->mutex);
}
gssize
nautilus_cache_install_item (NautilusCache *cache,
GDestroyNotify destroy_func)
{
CacheItemDetails *details;
guint size;
g_return_val_if_fail (NAUTILUS_IS_CACHE (cache), -1);
details = cache_item_details_new (destroy_func);
size = g_hash_table_size (cache->items);
g_hash_table_insert (cache->items, GUINT_TO_POINTER (size), details);
/* This is a bit evil, but is unlikely to cause problems… ever. */
return (gssize) size;
}
NautilusCache *
nautilus_cache_new (void)
{
return g_object_new (NAUTILUS_TYPE_CACHE, NULL);
}
/* Copyright (C) 2017 Ernestas Kulik <ernestask@gnome.org>
*
* This file is part of Nautilus.
*
* Nautilus 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.
*
* Nautilus 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 Nautilus. If not, see <http://www.gnu.org/licenses/>.
*/
#include <glib.h>
#include <glib-object.h>
#define NAUTILUS_TYPE_CACHE (nautilus_cache_get_type ())
G_DECLARE_FINAL_TYPE (NautilusCache, nautilus_cache, NAUTILUS, CACHE, GObject)
/* GCopyFunc has too many parameters for our taste. */
typedef gpointer (*NautilusCopyFunc) (gpointer data);
#define NAUTILUS_COPY_FUNC(x) ((NautilusCopyFunc) x)
typedef enum
{
NAUTILUS_CACHE_INVALID,
NAUTILUS_CACHE_PENDING,
NAUTILUS_CACHE_VALID
} NautilusCacheState;
void nautilus_cache_invalidate_all (NautilusCache *cache);
NautilusCacheState nautilus_cache_item_get_state (NautilusCache *cache,
gssize item);
void nautilus_cache_item_invalidate (NautilusCache *cache,
gssize item,
gboolean destroy);
void nautilus_cache_item_set_pending (NautilusCache *cache,
gssize item);
gpointer nautilus_cache_item_get_value (NautilusCache *cache,
gssize item,
NautilusCopyFunc copy_func);
void nautilus_cache_item_set_value (NautilusCache *cache,
gssize item,
gpointer value);
/* TODO: TTL for highly volatile items? */
gssize nautilus_cache_install_item (NautilusCache *cache,
GDestroyNotify destroy_func);
NautilusCache *nautilus_cache_new (void);
......@@ -18,16 +18,174 @@
#include "nautilus-directory.h"
G_DEFINE_TYPE (NautilusDirectory, nautilus_directory, NAUTILUS_TYPE_FILE)
#include "nautilus-cache.h"
#include "nautilus-task-manager.h"
#include "tasks/nautilus-enumerate-children-task.h"
enum
{
CHILDREN,
N_ITEMS
};
typedef struct
{
NautilusCache *cache;
gssize cache_items[N_ITEMS];
} NautilusDirectoryPrivate;
G_DEFINE_TYPE_WITH_PRIVATE (NautilusDirectory, nautilus_directory,
NAUTILUS_TYPE_FILE)
static void
nautilus_directory_class_init (NautilusDirectoryClass *klass)
{
}
static void
file_list_free (gpointer data)
{
g_autoptr (GList) list = NULL;
list = data;
for (GList *i = list; i != NULL; i = i->next)
{
g_object_unref (i->data);
}
}
static void
nautilus_directory_init (NautilusDirectory *self)
{
NautilusDirectoryPrivate *priv;
priv = nautilus_directory_get_instance_private (self);
priv->cache = nautilus_cache_new ();
priv->cache_items[CHILDREN] =
nautilus_cache_install_item (priv->cache,
(GDestroyNotify) file_list_free);
}
typedef struct
{
NautilusDirectory *directory;
NautilusEnumerateChildrenCallback callback;
gpointer callback_data;
} EnumerateChildrenDetails;
static void
create_file_list (gpointer key,
gpointer value,
gpointer user_data)
{
GList **list;
list = user_data;
*list = g_list_prepend (*list,
nautilus_file_new_with_info (G_FILE (key),
G_FILE_INFO (value)));
}
static void
on_enumerate_children_finished (NautilusEnumerateChildrenTask *task,
GFile *file,
GHashTable *files,
GError *error,
gpointer user_data)
{
EnumerateChildrenDetails *details;
NautilusDirectoryPrivate *priv;
NautilusCacheState cache_state;
GList *children = NULL;
details = user_data;
priv = nautilus_directory_get_instance_private (details->directory);
cache_state = nautilus_cache_item_get_state (priv->cache,
priv->cache_items[CHILDREN]);
if (cache_state == NAUTILUS_CACHE_INVALID)
{
/* TODO: restart */
return;
}
g_hash_table_foreach (files, create_file_list, &children);
nautilus_cache_item_set_value (priv->cache, priv->cache_items[CHILDREN],
children);
details->callback (details->directory, g_list_copy (children), error,
details->callback_data);
g_free (details);
}
void
nautilus_directory_enumerate_children (NautilusDirectory *directory,
GCancellable *cancellable,
NautilusEnumerateChildrenCallback callback,
gpointer user_data)
{
NautilusDirectoryPrivate *priv;
NautilusCacheState cache_state;
g_autoptr (GFile) location = NULL;
g_autoptr (NautilusTask) task = NULL;
EnumerateChildrenDetails *details;
g_autoptr (NautilusTaskManager) task_manager = NULL;
g_return_if_fail (NAUTILUS_IS_DIRECTORY (directory));
priv = nautilus_directory_get_instance_private (directory);
cache_state = nautilus_cache_item_get_state (priv->cache,
priv->cache_items[CHILDREN]);
if (cache_state == NAUTILUS_CACHE_PENDING ||
cache_state == NAUTILUS_CACHE_VALID)
{
callback (directory,
nautilus_cache_item_get_value (priv->cache,
priv->cache_items[CHILDREN],
(NautilusCopyFunc) g_list_copy),
NULL, user_data);
return;
}
nautilus_cache_item_set_pending (priv->cache,
priv->cache_items[CHILDREN]);
location = nautilus_file_get_location (NAUTILUS_FILE (directory));
task = nautilus_enumerate_children_task_new (location,
"standard::*,"
"access::*,"
"mountable::*,"
"time::*,"
"unix::*,"
"owner::*,"
"selinux::*,"
"thumbnail::*,"
"id::filesystem,"
"trash::orig-path,"
"trash::deletion-date,"
"metadata::*,"
"recent::*",
G_FILE_QUERY_INFO_NONE,
cancellable);
details = g_new0 (EnumerateChildrenDetails, 1);
task_manager = nautilus_task_manager_dup_singleton ();
details->directory = directory;
details->callback = callback;
details->callback_data = user_data;
g_signal_connect (task, "finished",
G_CALLBACK (on_enumerate_children_finished), details);
nautilus_task_manager_queue_task (task_manager, task);
}
NautilusFile *
......
......@@ -24,9 +24,19 @@ G_DECLARE_DERIVABLE_TYPE (NautilusDirectory, nautilus_directory,
NAUTILUS, DIRECTORY,
NautilusFile)
typedef void (*NautilusEnumerateChildrenCallback) (NautilusDirectory *directory,
GList *children,
GError *error,
gpointer user_data);
struct _NautilusDirectoryClass
{
NautilusFileClass parent_class;
};
void nautilus_directory_enumerate_children (NautilusDirectory *directory,
GCancellable *cancellable,
NautilusEnumerateChildrenCallback callback,
gpointer user_data);
NautilusFile *nautilus_directory_new (GFile *location);
......@@ -18,25 +18,23 @@
#include "nautilus-file.h"
#include "nautilus-cache.h"
#include "nautilus-directory.h"
#include "nautilus-task-manager.h"
#include "tasks/nautilus-attribute-task.h"
typedef enum
enum
{
INVALID,
PENDING,
VALID
} CacheState;
INFO,
N_ITEMS
};
typedef struct
{
GFile *location;
GFileInfo *info;
CacheState info_state;
GMutex cache_mutex;
NautilusCache *cache;
gssize cache_items[N_ITEMS];
} NautilusFilePrivate;
G_DEFINE_TYPE_WITH_PRIVATE (NautilusFile, nautilus_file, G_TYPE_OBJECT)
......@@ -135,8 +133,6 @@ finalize (GObject *object)
g_hash_table_remove (files, priv->location);
g_mutex_unlock (&files_mutex);
g_mutex_clear (&priv->cache_mutex);
G_OBJECT_CLASS (nautilus_file_parent_class)->finalize (object);
}
......@@ -166,9 +162,9 @@ nautilus_file_init (NautilusFile *self)
priv = nautilus_file_get_instance_private (self);
priv->info = g_file_info_new ();
g_mutex_init (&priv->cache_mutex);
priv->cache = nautilus_cache_new ();
priv->cache_items[INFO] = nautilus_cache_install_item (priv->cache,
g_object_unref);
}
typedef struct
......@@ -188,14 +184,21 @@ on_query_info_finished (NautilusAttributeTask *task,
{
QueryInfoDetails *details;
NautilusFilePrivate *priv;
NautilusCacheState cache_state;
details = data;
priv = nautilus_file_get_instance_private (details->file);
cache_state = nautilus_cache_item_get_state (priv->cache,
priv->cache_items[INFO]);
g_mutex_lock (&priv->cache_mutex);
g_file_info_copy_into (info, priv->info);
priv->info_state = VALID;
g_mutex_unlock (&priv->cache_mutex);
if (cache_state == NAUTILUS_CACHE_INVALID)
{
/* TODO: restart */
return;
}
nautilus_cache_item_set_value (priv->cache, priv->cache_items[INFO],
info);
details->callback (details->file, g_file_info_dup (info), error,
details->callback_data);
......@@ -210,24 +213,41 @@ nautilus_file_query_info (NautilusFile *file,
gpointer user_data)
{
NautilusFilePrivate *priv;
NautilusCacheState cache_state;
g_autoptr (NautilusTask) task = NULL;
QueryInfoDetails *details;
g_autoptr (NautilusTaskManager) manager = NULL;
g_return_if_fail (NAUTILUS_IS_FILE (file));
g_debug ("%s: called for %p", __func__, (gpointer) file);
priv = nautilus_file_get_instance_private (file);
cache_state = nautilus_cache_item_get_state (priv->cache,
priv->cache_items[INFO]);
g_mutex_lock (&priv->cache_mutex);
/* This is not the right thing to do if a cache update is pending.
* A task reference could be stored and we could connect to the signal,
* but there might be a better way.
*/
if (priv->info_state == PENDING || priv->info_state == VALID)
if (cache_state == NAUTILUS_CACHE_PENDING ||
cache_state == NAUTILUS_CACHE_VALID)
{
g_mutex_unlock (&priv->cache_mutex);
GFileInfo *info;
g_debug ("%s: info for %p is either pending or valid",
__func__, (gpointer) file);
info = nautilus_cache_item_get_value (priv->cache,
priv->cache_items[INFO],
NAUTILUS_COPY_FUNC (g_file_info_dup));
callback (file, info, NULL, user_data);
callback (file, g_file_info_dup (priv->info), NULL, user_data);
return;
}
g_mutex_unlock (&priv->cache_mutex);
nautilus_cache_item_set_pending (priv->cache, priv->cache_items[INFO]);
task = nautilus_attribute_task_new (priv->location,
"standard::*,"
......@@ -258,6 +278,39 @@ nautilus_file_query_info (NautilusFile *file,
nautilus_task_manager_queue_task (manager, task);
}
GFile *
nautilus_file_get_location (NautilusFile *file)
{
NautilusFilePrivate *priv;
g_return_val_if_fail (NAUTILUS_IS_FILE (file), NULL);