nautilus/libnautilus-private/nautilus-directory-async.c
Darin Adler e82bab8c49 reviewed by: John Sullivan <sullivan@eazel.com>
Fixed bug 2147 (NautilusFile for trash needs non-empty values for
	more properties), bug 5652 (MIME type field appears but shouldn't
	in properties window for Trash), and bug 4620 (trash sidebar
	should show number of items).

	* libnautilus-extensions/nautilus-file.h:
	* libnautilus-extensions/nautilus-file-private.h:
	* libnautilus-extensions/nautilus-file.c:
	(nautilus_file_new_from_relative_uri): Create trash file subclass
	when appropriate.
	(nautilus_file_info_missing): Rename so it can be used in other
	source files.
	(nautilus_file_get_date): New method that returns numeric dates.
	(nautilus_file_get_date_as_string): Change to call
	nautilus_file_get_date to get the numeric date.
	(nautilus_file_get_file_type): Turn into a method.

	* libnautilus-extensions/nautilus-merged-directory.h:
	* libnautilus-extensions/nautilus-merged-directory.c:
	(merged_call_when_ready): Rolled in the old
	merged_callback_connect_directory function.
	(merged_contains_file): Moved down to a more appropriate part of
	the source file.
	(nautilus_merged_directory_get_real_directories): New public
	function for use by trash.

	* libnautilus-extensions/nautilus-trash-file.c:
	(trash_callback_hash), (trash_callback_equal),
	(trash_callback_destroy), (trash_callback_check_done),
	(trash_callback_remove_file), (ready_callback),
	(real_file_changed_callback), (monitor_add_file), (add_real_file),
	(trash_callback_remove_file_cover), (monitor_remove_file),
	(remove_real_file), (add_real_file_given_directory),
	(add_directory_callback), (remove_directory_callback),
	(trash_file_call_when_ready), (trash_file_cancel_call_when_ready),
	(trash_file_check_if_ready), (trash_file_monitor_add),
	(trash_file_monitor_remove), (trash_file_get_file_type),
	(trash_file_get_item_count), (trash_file_get_deep_counts),
	(trash_file_get_date), (remove_all_real_files),
	(nautilus_trash_file_initialize), (trash_destroy),
	(nautilus_trash_file_initialize_class):
	New code. First implementation of getting some attributes for the
	merged trash. Gets list of directories from the
	NautilusTrashDirectory object. The code in here is similar to the
	NautilusMergedDirectory class and at some point perhaps we can
	make the two share more.

	* libnautilus-extensions/nautilus-vfs-file.c:
	(vfs_file_get_file_type), (vfs_file_get_date),
	(nautilus_vfs_file_initialize_class): Implement the two new
	methods for the standard vfs case.

	Fixed various things that affect the properties window for the
	trash that showed up while I was testing the above work:

	* libnautilus-extensions/nautilus-icon-factory.c:
	(nautilus_icon_factory_get_icon_name_for_regular_file),
	(nautilus_icon_factory_get_icon_name_for_file): Move trash special
	case out so it doesn't matter what type the trash seems to be. The
	old code relied on trash not being a directory.

	* src/file-manager/fm-properties-window.c:
	(create_properties_window), (create_properties_window_callback),
	(cancel_create_properties_window_callback),
	(directory_view_destroyed_callback), (remove_pending_file): Rework
	code to fix problems where the timed wait or the signal handler
	wasn't properly cleaned up.

	Fixed remaining bit of bug 5631 (Tear-offs of right-click menus
	don't work.):

	* src/file-manager/nautilus-desktop-icon-view-ui.xml: Mark Disks
	right-click submenu so it won't get a tear-off item.

	Fixed bug 5650 (nautilus --quit fails to return to prompt
	sometimes):

	* src/nautilus-application.c: (nautilus_application_startup): Add
	special case so "nautilus --quit" doesn't hang waiting for a
	response from the (now quit) "server" instance of Nautilus.
	* src/nautilus-shell-interface.idl: Had to make the quit call no
	longer be "oneway" to get it to work right, otherwise the quit
	message could be lost when the sending application exited too
	fast.

	Fix bug 5675 (Nitpicks in new "could not be moved to new special
	location" dialog):

	* libnautilus-extensions/nautilus-file-operations.c:
	(handle_transfer_overwrite): Updated message wording as Eli (and
	Vera) suggest.

	Other changes:

	* libnautilus-extensions/nautilus-view-identifier.c:
	(get_lang_list): Fix comment and a small storage leak in the case
	where lang_with_locale is non-NULL but empty.

	* po/POTFILES.ignore: Obsolete file, no longer used.

	* src/file-manager/fm-desktop-icon-view.c:
	(update_home_link_and_delete_copies): Update comment to help
	translators understand better.

	* libnautilus-extensions/nautilus-directory-async.c:
	(directory_load_callback): Added a new assertion that could help
	in future gnome-vfs debugging.

	* libnautilus/nautilus-view-component.idl: Removed long-ago-fixed
	FIXME comment.

	* src/nautilus-window-manage-views.c: Tweak spacing.
2001-01-18 19:26:03 +00:00

3352 lines
89 KiB
C

/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*-
nautilus-directory.c: Nautilus directory model.
Copyright (C) 1999, 2000 Eazel, Inc.
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.
Author: Darin Adler <darin@eazel.com>
*/
#include <config.h>
#include "nautilus-directory-metafile.h"
#include "nautilus-directory-notify.h"
#include "nautilus-directory-private.h"
#include "nautilus-file-attributes.h"
#include "nautilus-file-private.h"
#include "nautilus-glib-extensions.h"
#include "nautilus-global-preferences.h"
#include "nautilus-link.h"
#include "nautilus-search-uri.h"
#include "nautilus-string.h"
#include <ctype.h>
#include <gnome-xml/parser.h>
#include <gnome-xml/xmlmemory.h>
#include <gtk/gtkmain.h>
#include <stdlib.h>
#include <stdio.h>
/* turn this on to see messages about each load_directory call: */
#if 0
#define DEBUG_LOAD_DIRECTORY
#endif
/* turn this on to check if async. job calls are balanced */
#if 0
#define DEBUG_ASYNC_JOBS
#endif
#define METAFILE_PERMISSIONS (GNOME_VFS_PERM_USER_READ | GNOME_VFS_PERM_USER_WRITE \
| GNOME_VFS_PERM_GROUP_READ | GNOME_VFS_PERM_GROUP_WRITE \
| GNOME_VFS_PERM_OTHER_READ | GNOME_VFS_PERM_OTHER_WRITE)
#define DIRECTORY_LOAD_ITEMS_PER_CALLBACK 32
/* Keep async. jobs down to this number for all directories. */
#define MAX_ASYNC_JOBS 10
struct MetafileReadState {
gboolean use_public_metafile;
NautilusReadFileHandle *handle;
GnomeVFSAsyncHandle *get_file_info_handle;
};
struct MetafileWriteState {
gboolean use_public_metafile;
GnomeVFSAsyncHandle *handle;
xmlChar *buffer;
GnomeVFSFileSize size;
gboolean write_again;
};
struct TopLeftTextReadState {
NautilusFile *file;
NautilusReadFileHandle *handle;
};
struct ActivationURIReadState {
NautilusFile *file;
NautilusReadFileHandle *handle;
};
typedef struct {
NautilusFile *file; /* Which file, NULL means all. */
union {
NautilusDirectoryCallback directory;
NautilusFileCallback file;
} callback;
gpointer callback_data;
Request request;
} ReadyCallback;
typedef struct {
NautilusFile *file; /* Which file, NULL means all. */
gboolean monitor_hidden_files; /* defines whether "all" includes hidden files */
gboolean monitor_backup_files; /* defines whether "all" includes backup files */
gconstpointer client;
Request request;
} Monitor;
typedef gboolean (* RequestCheck) (const Request *);
typedef gboolean (* FileCheck) (NautilusFile *);
/* Current number of async. jobs. */
static int async_job_count;
static GHashTable *waiting_directories;
#ifdef DEBUG_ASYNC_JOBS
static GHashTable *async_jobs;
#endif
/* Forward declarations for functions that need them. */
static void deep_count_load (NautilusDirectory *directory,
const char *uri);
static void metafile_read_restart (NautilusDirectory *directory);
static gboolean request_is_satisfied (NautilusDirectory *directory,
NautilusFile *file,
Request *request);
static void cancel_loading_attributes (NautilusDirectory *directory,
GList *file_attributes);
/* Some helpers for case-insensitive strings.
* Move to nautilus-glib-extensions?
*/
static gboolean
istr_equal (gconstpointer v, gconstpointer v2)
{
return g_strcasecmp (v, v2) == 0;
}
static guint
istr_hash (gconstpointer key)
{
const char *p;
guint h;
h = 0;
for (p = key; *p != '\0'; p++) {
h = (h << 5) - h + tolower ((guchar) *p);
}
return h;
}
static GHashTable *
istr_set_new (void)
{
return g_hash_table_new (istr_hash, istr_equal);
}
static void
istr_set_insert (GHashTable *table, const char *istr)
{
char *key;
if (g_hash_table_lookup (table, istr) == NULL) {
key = g_strdup (istr);
g_hash_table_insert (table, key, key);
}
}
static void
add_istr_to_list (gpointer key, gpointer value, gpointer callback_data)
{
GList **list;
list = callback_data;
*list = g_list_prepend (*list, g_strdup (key));
}
static GList *
istr_set_get_as_list (GHashTable *table)
{
GList *list;
list = NULL;
g_hash_table_foreach (table, add_istr_to_list, &list);
return list;
}
static void
istr_set_destroy (GHashTable *table)
{
nautilus_g_hash_table_destroy_deep (table);
}
/* Start a job. This is really just a way of limiting the number of
* async. requests that we issue at any given time. Without this, the
* number of requests is unbounded.
*/
static gboolean
async_job_start (NautilusDirectory *directory,
const char *job)
{
#ifdef DEBUG_ASYNC_JOBS
char *key;
#endif
g_assert (async_job_count >= 0);
g_assert (async_job_count <= MAX_ASYNC_JOBS);
if (async_job_count >= MAX_ASYNC_JOBS) {
if (waiting_directories == NULL) {
waiting_directories = nautilus_g_hash_table_new_free_at_exit
(NULL, NULL,
"nautilus-directory-async.c: waiting_directories");
}
g_hash_table_insert (waiting_directories,
directory,
directory);
return FALSE;
}
#ifdef DEBUG_ASYNC_JOBS
if (async_jobs == NULL) {
async_jobs = nautilus_g_hash_table_new_free_at_exit
(g_str_hash, g_str_equal,
"nautilus-directory-async.c: async_jobs");
}
key = g_strconcat (directory->details->uri, ": ", job, NULL);
if (g_hash_table_lookup (async_jobs, key) != NULL) {
g_warning ("same job twice: %s in %s",
job,
directory->details->uri);
}
g_hash_table_insert (async_jobs, key, directory);
#endif
async_job_count += 1;
return TRUE;
}
/* End a job. */
static void
async_job_end (NautilusDirectory *directory,
const char *job)
{
#ifdef DEBUG_ASYNC_JOBS
char *key;
gpointer table_key, value;
#endif
g_assert (async_job_count > 0);
#ifdef DEBUG_ASYNC_JOBS
g_assert (async_jobs != NULL);
key = g_strconcat (directory->details->uri, ": ", job, NULL);
if (!g_hash_table_lookup_extended (async_jobs, key, &table_key, &value)) {
g_warning ("ending job we didn't start: %s in %s",
job,
directory->details->uri);
} else {
g_hash_table_remove (async_jobs, key);
g_free (table_key);
}
g_free (key);
#endif
async_job_count -= 1;
}
/* Helper to get one value from a hash table. */
static void
get_one_value_callback (gpointer key, gpointer value, gpointer callback_data)
{
gpointer *returned_value;
returned_value = callback_data;
*returned_value = value;
}
/* return a single value from a hash table. */
static gpointer
get_one_value (GHashTable *table)
{
gpointer value;
value = NULL;
if (table != NULL) {
g_hash_table_foreach (table, get_one_value_callback, &value);
}
return value;
}
/* Wake up directories that are "blocked" as long as there are job
* slots available.
*/
static void
async_job_wake_up (void)
{
static gboolean already_waking_up = FALSE;
gpointer value;
g_assert (async_job_count >= 0);
g_assert (async_job_count <= MAX_ASYNC_JOBS);
if (already_waking_up) {
return;
}
already_waking_up = TRUE;
while (async_job_count < MAX_ASYNC_JOBS) {
value = get_one_value (waiting_directories);
if (value == NULL) {
break;
}
g_hash_table_remove (waiting_directories, value);
nautilus_directory_async_state_changed
(NAUTILUS_DIRECTORY (value));
}
already_waking_up = FALSE;
}
static void
directory_count_cancel (NautilusDirectory *directory)
{
if (directory->details->count_in_progress != NULL) {
gnome_vfs_async_cancel (directory->details->count_in_progress);
directory->details->count_file = NULL;
directory->details->count_in_progress = NULL;
async_job_end (directory, "directory count");
}
}
static void
deep_count_cancel (NautilusDirectory *directory)
{
if (directory->details->deep_count_in_progress != NULL) {
g_assert (NAUTILUS_IS_FILE (directory->details->deep_count_file));
gnome_vfs_async_cancel (directory->details->deep_count_in_progress);
directory->details->deep_count_file->details->deep_counts_status = NAUTILUS_REQUEST_NOT_STARTED;
directory->details->deep_count_file = NULL;
directory->details->deep_count_in_progress = NULL;
g_free (directory->details->deep_count_uri);
directory->details->deep_count_uri = NULL;
nautilus_g_list_free_deep (directory->details->deep_count_subdirectories);
directory->details->deep_count_subdirectories = NULL;
async_job_end (directory, "deep count");
}
}
static void
mime_list_cancel (NautilusDirectory *directory)
{
if (directory->details->mime_list_in_progress != NULL) {
g_assert (NAUTILUS_IS_FILE (directory->details->mime_list_file));
gnome_vfs_async_cancel (directory->details->mime_list_in_progress);
istr_set_destroy (directory->details->mime_list_hash);
directory->details->mime_list_file = NULL;
directory->details->mime_list_in_progress = NULL;
directory->details->mime_list_hash = NULL;
async_job_end (directory, "MIME list");
}
}
static void
top_left_cancel (NautilusDirectory *directory)
{
if (directory->details->top_left_read_state != NULL) {
nautilus_read_file_cancel (directory->details->top_left_read_state->handle);
g_free (directory->details->top_left_read_state);
directory->details->top_left_read_state = NULL;
async_job_end (directory, "top left");
}
}
static void
activation_uri_cancel (NautilusDirectory *directory)
{
if (directory->details->activation_uri_read_state != NULL) {
nautilus_read_file_cancel (directory->details->activation_uri_read_state->handle);
g_free (directory->details->activation_uri_read_state);
directory->details->activation_uri_read_state = NULL;
async_job_end (directory, "activation URI");
}
}
static void
file_info_cancel (NautilusDirectory *directory)
{
if (directory->details->get_info_in_progress != NULL) {
gnome_vfs_async_cancel (directory->details->get_info_in_progress);
directory->details->get_info_file = NULL;
directory->details->get_info_in_progress = NULL;
async_job_end (directory, "file info");
}
}
static void
metafile_read_cancel (NautilusDirectory *directory)
{
if (directory->details->metafile_read_state != NULL) {
if (directory->details->metafile_read_state->handle != NULL) {
nautilus_read_file_cancel (directory->details->metafile_read_state->handle);
}
if (directory->details->metafile_read_state->get_file_info_handle != NULL) {
gnome_vfs_async_cancel (directory->details->metafile_read_state->get_file_info_handle);
}
g_free (directory->details->metafile_read_state);
directory->details->metafile_read_state = NULL;
async_job_end (directory, "metafile read");
}
}
static gboolean
can_use_public_metafile (NautilusDirectory *directory)
{
NautilusSpeedTradeoffValue preference_value;
g_return_val_if_fail (NAUTILUS_IS_DIRECTORY (directory), FALSE);
if (directory->details->public_metafile_vfs_uri == NULL) {
return FALSE;
}
preference_value = nautilus_preferences_get_integer (NAUTILUS_PREFERENCES_USE_PUBLIC_METADATA);
if (preference_value == NAUTILUS_SPEED_TRADEOFF_ALWAYS) {
return TRUE;
}
if (preference_value == NAUTILUS_SPEED_TRADEOFF_NEVER) {
return FALSE;
}
g_assert (preference_value == NAUTILUS_SPEED_TRADEOFF_LOCAL_ONLY);
return nautilus_directory_is_local (directory);
}
static void
metafile_read_mark_done (NautilusDirectory *directory)
{
g_free (directory->details->metafile_read_state);
directory->details->metafile_read_state = NULL;
directory->details->metafile_read = TRUE;
/* Move over the changes to the metafile that were in the hash table. */
nautilus_directory_metafile_apply_pending_changes (directory);
/* Tell change-watchers that we have update information. */
nautilus_directory_emit_metadata_changed (directory);
/* Let the callers that were waiting for the metafile know. */
nautilus_directory_async_state_changed (directory);
}
static void
metafile_read_done (NautilusDirectory *directory)
{
async_job_end (directory, "metafile read");
metafile_read_mark_done (directory);
}
static void
metafile_read_try_public_metafile (NautilusDirectory *directory)
{
directory->details->metafile_read_state->use_public_metafile = TRUE;
metafile_read_restart (directory);
}
static void
metafile_read_check_for_directory_callback (GnomeVFSAsyncHandle *handle,
GList *results,
gpointer callback_data)
{
NautilusDirectory *directory;
GnomeVFSGetFileInfoResult *result;
directory = NAUTILUS_DIRECTORY (callback_data);
g_assert (directory->details->metafile_read_state->get_file_info_handle == handle);
g_assert (nautilus_g_list_exactly_one_item (results));
directory->details->metafile_read_state->get_file_info_handle = NULL;
result = results->data;
if (result->result == GNOME_VFS_OK
&& ((result->file_info->valid_fields & GNOME_VFS_FILE_INFO_FIELDS_TYPE) != 0)
&& result->file_info->type == GNOME_VFS_FILE_TYPE_DIRECTORY) {
/* Is a directory. */
metafile_read_try_public_metafile (directory);
} else {
/* Not a directory. */
metafile_read_done (directory);
}
}
static void
metafile_read_check_for_directory (NautilusDirectory *directory)
{
GList fake_list;
/* We only get here if the public metafile is in question,
* which in turn only happens if the URI is one that gnome-vfs
* can handle.
*/
g_assert (directory->details->vfs_uri != NULL);
/* We have to do a get_info call to check if this a directory. */
fake_list.data = directory->details->vfs_uri;
fake_list.next = NULL;
fake_list.prev = NULL;
gnome_vfs_async_get_file_info
(&directory->details->metafile_read_state->get_file_info_handle,
&fake_list,
GNOME_VFS_FILE_INFO_DEFAULT,
metafile_read_check_for_directory_callback,
directory);
}
static void
metafile_read_failed (NautilusDirectory *directory)
{
NautilusFile *file;
gboolean need_directory_check, is_directory;
g_assert (NAUTILUS_IS_DIRECTORY (directory));
g_assert (directory->details->metafile == NULL);
directory->details->metafile_read_state->handle = NULL;
if (!directory->details->metafile_read_state->use_public_metafile
&& can_use_public_metafile (directory)) {
/* The goal here is to read the real metafile, but
* only if the directory is actually a directory.
*/
/* First, check if we already know if it a directory. */
file = nautilus_file_get (directory->details->uri);
if (file == NULL || file->details->is_gone) {
need_directory_check = FALSE;
is_directory = FALSE;
} else if (file->details->info == NULL) {
need_directory_check = TRUE;
is_directory = TRUE;
} else {
need_directory_check = FALSE;
is_directory = nautilus_file_is_directory (file);
}
nautilus_file_unref (file);
/* Do the directory check if we don't know. */
if (need_directory_check) {
metafile_read_check_for_directory (directory);
return;
}
/* Try for the public metafile if it is a directory. */
if (is_directory) {
metafile_read_try_public_metafile (directory);
return;
}
}
metafile_read_done (directory);
}
static void
metafile_read_done_callback (GnomeVFSResult result,
GnomeVFSFileSize file_size,
char *file_contents,
gpointer callback_data)
{
NautilusDirectory *directory;
int size;
char *buffer;
directory = NAUTILUS_DIRECTORY (callback_data);
g_assert (directory->details->metafile == NULL);
if (result != GNOME_VFS_OK) {
g_assert (file_contents == NULL);
metafile_read_failed (directory);
return;
}
size = file_size;
if ((GnomeVFSFileSize) size != file_size) {
g_free (file_contents);
metafile_read_failed (directory);
return;
}
/* The gnome-xml parser requires a zero-terminated array. */
buffer = g_realloc (file_contents, size + 1);
buffer[size] = '\0';
nautilus_directory_set_metafile_contents (directory,
xmlParseMemory (buffer, size));
g_free (buffer);
metafile_read_done (directory);
}
static void
metafile_read_restart (NautilusDirectory *directory)
{
char *text_uri;
g_assert (NAUTILUS_IS_DIRECTORY (directory));
text_uri = gnome_vfs_uri_to_string
(directory->details->metafile_read_state->use_public_metafile
? directory->details->public_metafile_vfs_uri
: directory->details->private_metafile_vfs_uri,
GNOME_VFS_URI_HIDE_NONE);
directory->details->metafile_read_state->handle = nautilus_read_entire_file_async
(text_uri, metafile_read_done_callback, directory);
g_free (text_uri);
}
static gboolean
allow_metafile (NautilusDirectory *directory)
{
const char *uri;
g_assert (NAUTILUS_IS_DIRECTORY (directory));
/* Note that this inhibits both reading and writing metadata
* completely. In the future we may want to inhibit writing to
* the real directory while allowing parallel-directory
* metadata.
*/
/* For now, hard-code these schemes. Perhaps we should
* hardcode the schemes that are good for metadata instead of
* the schemes that are bad for it.
*/
/* FIXME bugzilla.eazel.com 2434:
* We need to handle this in a better way. Perhaps a
* better way can wait until we have support for metadata
* access inside gnome-vfs.
*/
uri = directory->details->uri;
if (nautilus_is_search_uri (uri)
|| nautilus_istr_has_prefix (uri, "ghelp:")
|| nautilus_istr_has_prefix (uri, "gnome-help:")
|| nautilus_istr_has_prefix (uri, "help:")
|| nautilus_istr_has_prefix (uri, "info:")
|| nautilus_istr_has_prefix (uri, "man:")
|| nautilus_istr_has_prefix (uri, "pipe:")
) {
return FALSE;
}
return TRUE;
}
/* This checks if there's a request for the metafile contents. */
static gboolean
is_anyone_waiting_for_metafile (NautilusDirectory *directory)
{
GList *node;
ReadyCallback *callback;
Monitor *monitor;
for (node = directory->details->call_when_ready_list; node != NULL; node = node->next) {
callback = node->data;
if (callback->request.metafile) {
return TRUE;
}
}
for (node = directory->details->monitor_list; node != NULL; node = node->next) {
monitor = node->data;
if (monitor->request.metafile) {
return TRUE;
}
}
return FALSE;
}
static void
metafile_read_start (NautilusDirectory *directory)
{
g_assert (NAUTILUS_IS_DIRECTORY (directory));
if (directory->details->metafile_read
|| directory->details->metafile_read_state != NULL) {
return;
}
g_assert (directory->details->metafile == NULL);
if (!is_anyone_waiting_for_metafile (directory)) {
return;
}
if (!allow_metafile (directory)) {
metafile_read_mark_done (directory);
} else {
if (!async_job_start (directory, "metafile read")) {
return;
}
directory->details->metafile_read_state = g_new0 (MetafileReadState, 1);
metafile_read_restart (directory);
}
}
static void
metafile_write_done (NautilusDirectory *directory)
{
if (directory->details->metafile_write_state->write_again) {
nautilus_metafile_write_start (directory);
return;
}
xmlFree (directory->details->metafile_write_state->buffer);
g_free (directory->details->metafile_write_state);
directory->details->metafile_write_state = NULL;
nautilus_directory_unref (directory);
}
static void
metafile_write_failed (NautilusDirectory *directory)
{
if (directory->details->metafile_write_state->use_public_metafile) {
directory->details->metafile_write_state->use_public_metafile = FALSE;
nautilus_metafile_write_start (directory);
return;
}
metafile_write_done (directory);
}
static void
metafile_write_failure_close_callback (GnomeVFSAsyncHandle *handle,
GnomeVFSResult result,
gpointer callback_data)
{
NautilusDirectory *directory;
directory = NAUTILUS_DIRECTORY (callback_data);
metafile_write_failed (directory);
}
static void
metafile_write_success_close_callback (GnomeVFSAsyncHandle *handle,
GnomeVFSResult result,
gpointer callback_data)
{
NautilusDirectory *directory;
directory = NAUTILUS_DIRECTORY (callback_data);
g_assert (directory->details->metafile_write_state->handle == NULL);
if (result != GNOME_VFS_OK) {
metafile_write_failed (directory);
return;
}
/* Now that we have finished writing, it is time to delete the
* private file if we wrote the public one.
*/
if (directory->details->metafile_write_state->use_public_metafile) {
/* A synchronous unlink is OK here because the private
* metafiles are local, so an unlink is very fast.
*/
gnome_vfs_unlink_from_uri (directory->details->private_metafile_vfs_uri);
}
metafile_write_done (directory);
}
static void
metafile_write_callback (GnomeVFSAsyncHandle *handle,
GnomeVFSResult result,
gconstpointer buffer,
GnomeVFSFileSize bytes_requested,
GnomeVFSFileSize bytes_read,
gpointer callback_data)
{
NautilusDirectory *directory;
directory = NAUTILUS_DIRECTORY (callback_data);
g_assert (directory->details->metafile_write_state->handle == handle);
g_assert (directory->details->metafile_write_state->buffer == buffer);
g_assert (directory->details->metafile_write_state->size == bytes_requested);
g_assert (directory->details->metafile_write_state->handle != NULL);
gnome_vfs_async_close (directory->details->metafile_write_state->handle,
result == GNOME_VFS_OK
? metafile_write_success_close_callback
: metafile_write_failure_close_callback,
directory);
directory->details->metafile_write_state->handle = NULL;
}
static void
metafile_write_create_callback (GnomeVFSAsyncHandle *handle,
GnomeVFSResult result,
gpointer callback_data)
{
NautilusDirectory *directory;
directory = NAUTILUS_DIRECTORY (callback_data);
g_assert (directory->details->metafile_write_state->handle == handle);
if (result != GNOME_VFS_OK) {
metafile_write_failed (directory);
return;
}
gnome_vfs_async_write (directory->details->metafile_write_state->handle,
directory->details->metafile_write_state->buffer,
directory->details->metafile_write_state->size,
metafile_write_callback,
directory);
}
void
nautilus_metafile_write_start (NautilusDirectory *directory)
{
g_assert (NAUTILUS_IS_DIRECTORY (directory));
directory->details->metafile_write_state->write_again = FALSE;
/* Open the file. */
gnome_vfs_async_create_uri
(&directory->details->metafile_write_state->handle,
directory->details->metafile_write_state->use_public_metafile
? directory->details->public_metafile_vfs_uri
: directory->details->private_metafile_vfs_uri,
GNOME_VFS_OPEN_WRITE, FALSE, METAFILE_PERMISSIONS,
metafile_write_create_callback, directory);
}
static void
metafile_write (NautilusDirectory *directory)
{
int xml_doc_size;
g_assert (NAUTILUS_IS_DIRECTORY (directory));
nautilus_directory_ref (directory);
/* If we are already writing, then just remember to do it again. */
if (directory->details->metafile_write_state != NULL) {
nautilus_directory_unref (directory);
directory->details->metafile_write_state->write_again = TRUE;
return;
}
/* Don't write anything if there's nothing to write.
* At some point, we might want to change this to actually delete
* the metafile in this case.
*/
if (directory->details->metafile == NULL) {
nautilus_directory_unref (directory);
return;
}
/* Create the write state. */
directory->details->metafile_write_state = g_new0 (MetafileWriteState, 1);
directory->details->metafile_write_state->use_public_metafile
= can_use_public_metafile (directory);
xmlDocDumpMemory (directory->details->metafile,
&directory->details->metafile_write_state->buffer,
&xml_doc_size);
directory->details->metafile_write_state->size = xml_doc_size;
nautilus_metafile_write_start (directory);
}
static gboolean
metafile_write_idle_callback (gpointer callback_data)
{
NautilusDirectory *directory;
directory = NAUTILUS_DIRECTORY (callback_data);
directory->details->write_metafile_idle_id = 0;
metafile_write (directory);
nautilus_directory_unref (directory);
return FALSE;
}
void
nautilus_directory_request_write_metafile (NautilusDirectory *directory)
{
g_assert (NAUTILUS_IS_DIRECTORY (directory));
if (!allow_metafile (directory)) {
return;
}
/* Set up an idle task that will write the metafile. */
if (directory->details->write_metafile_idle_id == 0) {
nautilus_directory_ref (directory);
directory->details->write_metafile_idle_id =
gtk_idle_add (metafile_write_idle_callback,
directory);
}
}
static int
monitor_key_compare (gconstpointer a,
gconstpointer data)
{
const Monitor *monitor;
const Monitor *compare_monitor;
monitor = a;
compare_monitor = data;
if (monitor->client < compare_monitor->client) {
return -1;
}
if (monitor->client > compare_monitor->client) {
return +1;
}
if (monitor->file < compare_monitor->file) {
return -1;
}
if (monitor->file > compare_monitor->file) {
return +1;
}
return 0;
}
static GList *
find_monitor (NautilusDirectory *directory,
NautilusFile *file,
gconstpointer client)
{
GList *result;
Monitor *monitor;
monitor = g_new0 (Monitor, 1);
monitor->client = client;
monitor->file = file;
result = g_list_find_custom (directory->details->monitor_list,
monitor,
monitor_key_compare);
g_free (monitor);
return result;
}
static void
remove_monitor_link (NautilusDirectory *directory,
GList *link)
{
if (link != NULL) {
directory->details->monitor_list =
g_list_remove_link (directory->details->monitor_list, link);
g_free (link->data);
g_list_free_1 (link);
}
}
static void
remove_monitor (NautilusDirectory *directory,
NautilusFile *file,
gconstpointer client)
{
remove_monitor_link (directory, find_monitor (directory, file, client));
}
void
nautilus_directory_set_up_request (Request *request,
GList *file_attributes)
{
memset (request, 0, sizeof (*request));
request->directory_count = g_list_find_custom
(file_attributes,
NAUTILUS_FILE_ATTRIBUTE_DIRECTORY_ITEM_COUNT,
nautilus_strcmp_compare_func) != NULL;
request->deep_count = g_list_find_custom
(file_attributes,
NAUTILUS_FILE_ATTRIBUTE_DEEP_COUNTS,
nautilus_strcmp_compare_func) != NULL;
request->mime_list = g_list_find_custom
(file_attributes,
NAUTILUS_FILE_ATTRIBUTE_DIRECTORY_ITEM_MIME_TYPES,
nautilus_strcmp_compare_func) != NULL;
request->file_info = g_list_find_custom
(file_attributes,
NAUTILUS_FILE_ATTRIBUTE_MIME_TYPE,
nautilus_strcmp_compare_func) != NULL;
request->file_info |= g_list_find_custom
(file_attributes,
NAUTILUS_FILE_ATTRIBUTE_IS_DIRECTORY,
nautilus_strcmp_compare_func) != NULL;
request->file_info |= g_list_find_custom
(file_attributes,
NAUTILUS_FILE_ATTRIBUTE_CAPABILITIES,
nautilus_strcmp_compare_func) != NULL;
request->file_info |= g_list_find_custom
(file_attributes,
NAUTILUS_FILE_ATTRIBUTE_FILE_TYPE,
nautilus_strcmp_compare_func) != NULL;
if (g_list_find_custom (file_attributes,
NAUTILUS_FILE_ATTRIBUTE_TOP_LEFT_TEXT,
nautilus_strcmp_compare_func) != NULL) {
request->top_left_text = TRUE;
request->file_info = TRUE;
}
if (g_list_find_custom (file_attributes,
NAUTILUS_FILE_ATTRIBUTE_ACTIVATION_URI,
nautilus_strcmp_compare_func) != NULL) {
request->file_info = TRUE;
request->activation_uri = TRUE;
}
request->metafile |= g_list_find_custom
(file_attributes,
NAUTILUS_FILE_ATTRIBUTE_METADATA,
nautilus_strcmp_compare_func) != NULL;
/* FIXME bugzilla.eazel.com 2435:
* Some file attributes are really pieces of metadata.
* This is a confusing/broken design, since other metadata
* pieces are handled separately from file attributes. There
* are many ways this could be changed, ranging from making
* all metadata pieces have corresponding file attributes, to
* making a single file attribute that means "get all metadata",
* to making metadata keys be acceptable as file attributes
* directly (would need some funky char trick to prevent
* namespace collisions).
*/
request->metafile |= g_list_find_custom
(file_attributes,
NAUTILUS_FILE_ATTRIBUTE_CUSTOM_ICON,
nautilus_strcmp_compare_func) != NULL;
}
static gboolean
is_tentative (gpointer data, gpointer callback_data)
{
NautilusFile *file;
g_assert (callback_data == NULL);
file = NAUTILUS_FILE (data);
return file->details->info == NULL;
}
static GList *
get_non_tentative_file_list (NautilusDirectory *directory)
{
GList *tentative_files, *non_tentative_files;
tentative_files = nautilus_g_list_partition
(g_list_copy (directory->details->file_list),
is_tentative, NULL, &non_tentative_files);
g_list_free (tentative_files);
nautilus_file_list_ref (non_tentative_files);
return non_tentative_files;
}
void
nautilus_directory_monitor_add_internal (NautilusDirectory *directory,
NautilusFile *file,
gconstpointer client,
gboolean monitor_hidden_files,
gboolean monitor_backup_files,
GList *file_attributes)
{
Monitor *monitor;
GList *file_list;
g_assert (NAUTILUS_IS_DIRECTORY (directory));
/* Replace any current monitor for this client/file pair. */
remove_monitor (directory, file, client);
/* Add the new monitor. */
monitor = g_new (Monitor, 1);
monitor->file = file;
monitor->monitor_hidden_files = monitor_hidden_files;
monitor->monitor_backup_files = monitor_backup_files;
monitor->client = client;
nautilus_directory_set_up_request (&monitor->request, file_attributes);
monitor->request.file_list = file == NULL;
directory->details->monitor_list =
g_list_prepend (directory->details->monitor_list, monitor);
/* Re-send the "files_added" signal for this set of files.
* Old monitorers already know about them, but it's harmless
* to hear about the same files again.
*/
if (file == NULL) {
file_list = get_non_tentative_file_list (directory);
if (file_list != NULL) {
nautilus_directory_emit_files_added
(directory, file_list);
nautilus_file_list_free (file_list);
}
}
/* Kick off I/O. */
nautilus_directory_async_state_changed (directory);
}
static void
set_file_unconfirmed (NautilusFile *file, gboolean unconfirmed)
{
NautilusDirectory *directory;
g_assert (NAUTILUS_IS_FILE (file));
g_assert (unconfirmed == FALSE || unconfirmed == TRUE);
if (file->details->unconfirmed == unconfirmed) {
return;
}
file->details->unconfirmed = unconfirmed;
directory = file->details->directory;
if (unconfirmed) {
directory->details->confirmed_file_count--;
} else {
directory->details->confirmed_file_count++;
}
}
static GnomeVFSDirectoryFilterOptions
get_filter_options_for_directory_count (void)
{
GnomeVFSDirectoryFilterOptions filter_options;
filter_options = GNOME_VFS_DIRECTORY_FILTER_NOSELFDIR
| GNOME_VFS_DIRECTORY_FILTER_NOPARENTDIR;
if (!nautilus_preferences_get_boolean (NAUTILUS_PREFERENCES_SHOW_HIDDEN_FILES)) {
filter_options |= GNOME_VFS_DIRECTORY_FILTER_NODOTFILES;
}
if (!nautilus_preferences_get_boolean (NAUTILUS_PREFERENCES_SHOW_BACKUP_FILES)) {
filter_options |= GNOME_VFS_DIRECTORY_FILTER_NOBACKUPFILES;
}
return filter_options;
}
static void
load_directory_done (NautilusDirectory *directory)
{
NautilusFile *file;
if (directory->details->load_mime_list_hash != NULL) {
istr_set_destroy (directory->details->load_mime_list_hash);
directory->details->load_mime_list_hash = NULL;
}
file = directory->details->load_directory_file;
if (file != NULL) {
directory->details->load_directory_file = NULL;
file->details->loading_directory = FALSE;
if (file->details->directory != directory) {
nautilus_directory_async_state_changed (file->details->directory);
}
nautilus_file_unref (file);
}
gnome_vfs_directory_filter_destroy (directory->details->load_file_count_filter);
directory->details->load_file_count_filter = NULL;
nautilus_directory_async_state_changed (directory);
}
static gboolean
dequeue_pending_idle_callback (gpointer callback_data)
{
NautilusDirectory *directory;
GList *pending_file_info;
GList *node, *next;
NautilusFile *file;
GList *changed_files, *added_files;
GnomeVFSFileInfo *file_info;
directory = NAUTILUS_DIRECTORY (callback_data);
directory->details->dequeue_pending_idle_id = 0;
/* Handle the files in the order we saw them. */
pending_file_info = g_list_reverse (directory->details->pending_file_info);
directory->details->pending_file_info = NULL;
/* If we are no longer monitoring, then throw away these. */
if (!nautilus_directory_is_file_list_monitored (directory)) {
gnome_vfs_file_info_list_free (pending_file_info);
load_directory_done (directory);
return FALSE;
}
added_files = NULL;
changed_files = NULL;
/* Build a list of NautilusFile objects. */
for (node = pending_file_info; node != NULL; node = node->next) {
file_info = node->data;
/* Update the file count. */
/* FIXME bugzilla.eazel.com 5063: This could count a file twice if we get it
* from both load_directory and from
* new_files_callback. Not too hard to fix by moving
* this into the actual callback instead of waiting
* for the idle function.
*/
if (gnome_vfs_directory_filter_apply (directory->details->load_file_count_filter,
file_info)) {
directory->details->load_file_count += 1;
}
/* Add the MIME type to the set. */
if (directory->details->load_mime_list_hash != NULL) {
istr_set_insert (directory->details->load_mime_list_hash,
file_info->mime_type);
}
/* check if the file already exists */
file = nautilus_directory_find_file_by_name (directory, file_info->name);
if (file != NULL) {
/* file already exists, check if it changed */
set_file_unconfirmed (file, FALSE);
if (nautilus_file_update_info (file, file_info)) {
/* File changed, notify about the change. */
nautilus_file_ref (file);
changed_files = g_list_prepend (changed_files, file);
}
nautilus_file_ref (file);
} else {
/* new file, create a nautilus file object and add it to the list */
file = nautilus_file_new_from_info (directory, file_info);
nautilus_directory_add_file (directory, file);
}
added_files = g_list_prepend (added_files, file);
}
gnome_vfs_file_info_list_free (pending_file_info);
/* If we are done loading, then we assume that any unconfirmed
* files are gone.
*/
if (directory->details->directory_loaded) {
for (node = directory->details->file_list;
node != NULL; node = next) {
file = NAUTILUS_FILE (node->data);
next = node->next;
if (file->details->unconfirmed) {
nautilus_file_ref (file);
changed_files = g_list_prepend (changed_files, file);
file->details->is_gone = TRUE;
nautilus_directory_remove_file (directory, file);
}
}
}
/* Send the changed and added signals. */
nautilus_directory_emit_change_signals_deep (directory, changed_files);
nautilus_file_list_free (changed_files);
nautilus_directory_emit_files_added (directory, added_files);
nautilus_file_list_free (added_files);
if (directory->details->directory_loaded
&& !directory->details->directory_loaded_sent_notification) {
/* Send the done_loading signal. */
nautilus_directory_emit_done_loading (directory);
file = directory->details->load_directory_file;
if (file != NULL) {
file->details->directory_count_is_up_to_date = TRUE;
file->details->got_directory_count = TRUE;
file->details->directory_count = directory->details->load_file_count;
file->details->got_mime_list = TRUE;
file->details->mime_list_is_up_to_date = TRUE;
file->details->mime_list = istr_set_get_as_list
(directory->details->load_mime_list_hash);
nautilus_file_changed (file);
}
load_directory_done (directory);
directory->details->directory_loaded_sent_notification = TRUE;
}
/* Get the state machine running again. */
nautilus_directory_async_state_changed (directory);
return FALSE;
}
void
nautilus_directory_schedule_dequeue_pending (NautilusDirectory *directory)
{
if (directory->details->dequeue_pending_idle_id == 0) {
directory->details->dequeue_pending_idle_id
= gtk_idle_add (dequeue_pending_idle_callback, directory);
}
}
static void
directory_load_one (NautilusDirectory *directory,
GnomeVFSFileInfo *info)
{
if (info == NULL) {
return;
}
/* Arrange for the "loading" part of the work. */
gnome_vfs_file_info_ref (info);
directory->details->pending_file_info
= g_list_prepend (directory->details->pending_file_info, info);
nautilus_directory_schedule_dequeue_pending (directory);
}
static void
file_list_cancel (NautilusDirectory *directory)
{
if (directory->details->directory_load_in_progress != NULL) {
gnome_vfs_async_cancel (directory->details->directory_load_in_progress);
directory->details->directory_load_in_progress = NULL;
async_job_end (directory, "file list");
}
}
static void
directory_load_done (NautilusDirectory *directory,
GnomeVFSResult result)
{
GList *node;
file_list_cancel (directory);
directory->details->directory_loaded = TRUE;
directory->details->directory_loaded_sent_notification = FALSE;
/* Note that GNOME_VFS_OK can make it this far when the file-list
* length limit has been reached. In that case, don't treat it as
* an error.
*/
if (result != GNOME_VFS_ERROR_EOF && result != GNOME_VFS_OK) {
/* The load did not complete successfully. This means
* we don't know the status of the files in this directory.
* We clear the unconfirmed bit on each file here so that
* they won't be marked "gone" later -- we don't know enough
* about them to know whether they are really gone.
*/
for (node = directory->details->file_list;
node != NULL; node = node->next) {
set_file_unconfirmed (NAUTILUS_FILE (node->data), FALSE);
}
nautilus_directory_emit_load_error (directory,
result);
}
/* Call the idle function right away. */
if (directory->details->dequeue_pending_idle_id != 0) {
gtk_idle_remove (directory->details->dequeue_pending_idle_id);
}
dequeue_pending_idle_callback (directory);
}
static GnomeVFSDirectoryListPosition
directory_list_get_next_position (GnomeVFSDirectoryList *list,
GnomeVFSDirectoryListPosition position)
{
if (position != GNOME_VFS_DIRECTORY_LIST_POSITION_NONE) {
return gnome_vfs_directory_list_position_next (position);
}
if (list == NULL) {
return GNOME_VFS_DIRECTORY_LIST_POSITION_NONE;
}
return gnome_vfs_directory_list_get_first_position (list);
}
static void
directory_load_callback (GnomeVFSAsyncHandle *handle,
GnomeVFSResult result,
GnomeVFSDirectoryList *list,
guint entries_read,
gpointer callback_data)
{
NautilusDirectory *directory;
GnomeVFSDirectoryListPosition last_handled, p;
directory = NAUTILUS_DIRECTORY (callback_data);
g_assert (directory->details->directory_load_in_progress != NULL);
g_assert (directory->details->directory_load_in_progress == handle);
/* Move items from the list onto our pending queue.
* We can't do this in the most straightforward way, becuse the position
* for a gnome_vfs_directory_list does not have a way of representing one
* past the end. So we must keep a position to the last item we handled
* rather than keeping a position past the last item we handled.
*/
last_handled = directory->details->directory_load_list_last_handled;
p = last_handled;
while ((p = directory_list_get_next_position (list, p))
!= GNOME_VFS_DIRECTORY_LIST_POSITION_NONE) {
directory_load_one
(directory, gnome_vfs_directory_list_get (list, p));
last_handled = p;
}
directory->details->directory_load_list_last_handled = last_handled;
if (nautilus_directory_file_list_length_reached (directory) ||
result != GNOME_VFS_OK) {
directory_load_done (directory,
result);
}
}
void
nautilus_directory_monitor_remove_internal (NautilusDirectory *directory,
NautilusFile *file,
gconstpointer client)
{
g_assert (NAUTILUS_IS_DIRECTORY (directory));
g_assert (file == NULL || NAUTILUS_IS_FILE (file));
g_assert (client != NULL);
remove_monitor (directory, file, client);
nautilus_directory_async_state_changed (directory);
}
static int
ready_callback_key_compare (gconstpointer a, gconstpointer b)
{
const ReadyCallback *callback_a, *callback_b;
callback_a = a;
callback_b = b;
if (callback_a->file < callback_b->file) {
return -1;
}
if (callback_a->file > callback_b->file) {
return 1;
}
if (callback_a->file == NULL) {
if (callback_a->callback.directory < callback_b->callback.directory) {
return -1;
}
if (callback_a->callback.directory > callback_b->callback.directory) {
return 1;
}
} else {
if (callback_a->callback.file < callback_b->callback.file) {
return -1;
}
if (callback_a->callback.file > callback_b->callback.file) {
return 1;
}
}
if (callback_a->callback_data < callback_b->callback_data) {
return -1;
}
if (callback_a->callback_data > callback_b->callback_data) {
return 1;
}
return 0;
}
static void
ready_callback_call (NautilusDirectory *directory,
const ReadyCallback *callback)
{
GList *file_list;
/* Call the callback. */
if (callback->file != NULL) {
(* callback->callback.file) (callback->file,
callback->callback_data);
} else {
if (directory == NULL || !callback->request.file_list) {
file_list = NULL;
} else {
file_list = get_non_tentative_file_list (directory);
}
/* Pass back the file list if the user was waiting for it. */
(* callback->callback.directory) (directory,
file_list,
callback->callback_data);
nautilus_file_list_free (file_list);
}
}
void
nautilus_directory_call_when_ready_internal (NautilusDirectory *directory,
NautilusFile *file,
GList *file_attributes,
NautilusDirectoryCallback directory_callback,
NautilusFileCallback file_callback,
gpointer callback_data)
{
ReadyCallback callback;
g_assert (directory == NULL || NAUTILUS_IS_DIRECTORY (directory));
g_assert (file == NULL || NAUTILUS_IS_FILE (file));
g_assert (file != NULL || directory_callback != NULL);
g_assert (file == NULL || file_callback != NULL);
/* Construct a callback object. */
callback.file = file;
if (file == NULL) {
callback.callback.directory = directory_callback;
} else {
callback.callback.file = file_callback;
}
callback.callback_data = callback_data;
nautilus_directory_set_up_request (&callback.request, file_attributes);
callback.request.file_list = file == NULL && file_attributes != NULL;
/* Handle the NULL case. */
if (directory == NULL) {
ready_callback_call (NULL, &callback);
return;
}
/* Check if the callback is already there. */
if (g_list_find_custom (directory->details->call_when_ready_list,
&callback,
ready_callback_key_compare) != NULL) {
g_warning ("tried to add a new callback while an old one was pending");
return;
}
/* Add the new callback to the list. */
directory->details->call_when_ready_list = g_list_prepend
(directory->details->call_when_ready_list,
g_memdup (&callback, sizeof (callback)));
nautilus_directory_async_state_changed (directory);
}
gboolean
nautilus_directory_check_if_ready_internal (NautilusDirectory *directory,
NautilusFile *file,
GList *file_attributes)
{
Request request;
g_assert (NAUTILUS_IS_DIRECTORY (directory));
nautilus_directory_set_up_request (&request, file_attributes);
return request_is_satisfied (directory, file, &request);
}
static void
remove_callback_link_keep_data (NautilusDirectory *directory,
GList *link)
{
directory->details->call_when_ready_list = g_list_remove_link
(directory->details->call_when_ready_list, link);
g_list_free_1 (link);
}
static void
remove_callback_link (NautilusDirectory *directory,
GList *link)
{
g_free (link->data);
remove_callback_link_keep_data (directory, link);
}
void
nautilus_directory_cancel_callback_internal (NautilusDirectory *directory,
NautilusFile *file,
NautilusDirectoryCallback directory_callback,
NautilusFileCallback file_callback,
gpointer callback_data)
{
ReadyCallback callback;
GList *node;
if (directory == NULL) {
return;
}
g_assert (NAUTILUS_IS_DIRECTORY (directory));
g_assert (file == NULL || NAUTILUS_IS_FILE (file));
g_assert (file != NULL || directory_callback != NULL);
g_assert (file == NULL || file_callback != NULL);
/* Construct a callback object. */
callback.file = file;
if (file == NULL) {
callback.callback.directory = directory_callback;
} else {
callback.callback.file = file_callback;
}
callback.callback_data = callback_data;
/* Remove queued callback from the list. */
node = g_list_find_custom (directory->details->call_when_ready_list,
&callback,
ready_callback_key_compare);
if (node != NULL) {
remove_callback_link (directory, node);
nautilus_directory_async_state_changed (directory);
}
}
static void
directory_count_callback (GnomeVFSAsyncHandle *handle,
GnomeVFSResult result,
GnomeVFSDirectoryList *list,
guint entries_read,
gpointer callback_data)
{
NautilusDirectory *directory;
NautilusFile *count_file;
directory = NAUTILUS_DIRECTORY (callback_data);
g_assert (directory->details->count_in_progress == handle);
count_file = directory->details->count_file;
g_assert (NAUTILUS_IS_FILE (count_file));
if (result == GNOME_VFS_OK) {
return;
}
count_file->details->directory_count_is_up_to_date = TRUE;
/* Record either a failure or success. */
if (result != GNOME_VFS_ERROR_EOF) {
count_file->details->directory_count_failed = TRUE;
} else {
count_file->details->got_directory_count = TRUE;
count_file->details->directory_count = entries_read;
}
directory->details->count_file = NULL;
directory->details->count_in_progress = NULL;
/* Send file-changed even if count failed, so interested parties can
* distinguish between unknowable and not-yet-known cases.
*/
nautilus_file_changed (count_file);
/* Start up the next one. */
async_job_end (directory, "directory count");
nautilus_directory_async_state_changed (directory);
}
static void
new_files_callback (GnomeVFSAsyncHandle *handle,
GList *results,
gpointer callback_data)
{
GList **handles, *node;
NautilusDirectory *directory;
GnomeVFSGetFileInfoResult *result;
directory = NAUTILUS_DIRECTORY (callback_data);
handles = &directory->details->get_file_infos_in_progress;
g_assert (handle == NULL || g_list_find (*handles, handle) != NULL);
/* Note that this call is done. */
*handles = g_list_remove (*handles, handle);
/* Queue up the new files. */
for (node = results; node != NULL; node = node->next) {
result = node->data;
if (result->result == GNOME_VFS_OK) {
directory_load_one (directory, result->file_info);
}
}
}
void
nautilus_directory_get_info_for_new_files (NautilusDirectory *directory,
GList *vfs_uri_list)
{
GnomeVFSAsyncHandle *handle;
gnome_vfs_async_get_file_info
(&handle,
vfs_uri_list,
(GNOME_VFS_FILE_INFO_GET_MIME_TYPE
| GNOME_VFS_FILE_INFO_FOLLOW_LINKS),
new_files_callback,
directory);
directory->details->get_file_infos_in_progress
= g_list_prepend (directory->details->get_file_infos_in_progress,
handle);
}
void
nautilus_async_destroying_file (NautilusFile *file)
{
NautilusDirectory *directory;
gboolean changed;
GList *node, *next;
ReadyCallback *callback;
Monitor *monitor;
directory = file->details->directory;
changed = FALSE;
/* Check for callbacks. */
for (node = directory->details->call_when_ready_list; node != NULL; node = next) {
next = node->next;
callback = node->data;
if (callback->file == file) {
/* Client should have cancelled callback. */
g_warning ("destroyed file has call_when_ready pending");
remove_callback_link (directory, node);
changed = TRUE;
}
}
/* Check for monitors. */
for (node = directory->details->monitor_list; node != NULL; node = next) {
next = node->next;
monitor = node->data;
if (monitor->file == file) {
/* Client should have removed monitor earlier. */
g_warning ("destroyed file still being monitored");
remove_monitor_link (directory, node);
changed = TRUE;
}
}
/* Check if it's a file that's currently being worked on.
* If so, make that NULL so it gets canceled right away.
*/
if (directory->details->count_file == file) {
directory->details->count_file = NULL;
changed = TRUE;
}
if (directory->details->deep_count_file == file) {
directory->details->deep_count_file = NULL;
changed = TRUE;
}
if (directory->details->mime_list_file == file) {
directory->details->mime_list_file = NULL;
changed = TRUE;
}
if (directory->details->get_info_file == file) {
directory->details->get_info_file = NULL;
changed = TRUE;
}
if (directory->details->top_left_read_state != NULL
&& directory->details->top_left_read_state->file == file) {
directory->details->top_left_read_state->file = NULL;
changed = TRUE;
}
if (directory->details->activation_uri_read_state != NULL
&& directory->details->activation_uri_read_state->file == file) {
directory->details->activation_uri_read_state->file = NULL;
changed = TRUE;
}
/* Let the directory take care of the rest. */
if (changed) {
nautilus_directory_async_state_changed (directory);
}
}
static gboolean
lacks_directory_count (NautilusFile *file)
{
return nautilus_file_is_directory (file)
&& !file->details->directory_count_is_up_to_date;
}
static gboolean
should_get_directory_count (NautilusFile *file)
{
return lacks_directory_count (file)
&& !file->details->loading_directory;
}
static gboolean
wants_directory_count (const Request *request)
{
return request->directory_count;
}
static gboolean
lacks_top_left (NautilusFile *file)
{
return nautilus_file_should_get_top_left_text (file)
&& nautilus_file_contains_text (file)
&& !file->details->top_left_text_is_up_to_date;
}
static gboolean
wants_top_left (const Request *request)
{
return request->top_left_text;
}
static gboolean
lacks_info (NautilusFile *file)
{
return !file->details->file_info_is_up_to_date
&& !file->details->is_gone;
}
static gboolean
wants_info (const Request *request)
{
return request->file_info;
}
static gboolean
lacks_deep_count (NautilusFile *file)
{
return nautilus_file_is_directory (file)
&& file->details->deep_counts_status != NAUTILUS_REQUEST_DONE;
}
static gboolean
wants_deep_count (const Request *request)
{
return request->deep_count;
}
static gboolean
lacks_mime_list (NautilusFile *file)
{
return nautilus_file_is_directory (file)
&& !file->details->mime_list_is_up_to_date;
}
static gboolean
should_get_mime_list (NautilusFile *file)
{
return lacks_mime_list (file)
&& !file->details->loading_directory;
}
static gboolean
wants_mime_list (const Request *request)
{
return request->mime_list;
}
static gboolean
lacks_activation_uri (NautilusFile *file)
{
return file->details->info != NULL
&& !file->details->activation_uri_is_up_to_date;
}
static gboolean
wants_activation_uri (const Request *request)
{
return request->activation_uri;
}
static gboolean
has_problem (NautilusDirectory *directory, NautilusFile *file, FileCheck problem)
{
GList *node;
if (file != NULL) {
return (* problem) (file);
}
for (node = directory->details->file_list; node != NULL; node = node->next) {
if ((* problem) (node->data)) {
return TRUE;
}
}
return FALSE;
}
static gboolean
request_is_satisfied (NautilusDirectory *directory,
NautilusFile *file,
Request *request)
{
if (request->metafile && !directory->details->metafile_read) {
return FALSE;
}
if (request->file_list && !(directory->details->directory_loaded &&
directory->details->directory_loaded_sent_notification)) {
return FALSE;
}
if (request->directory_count) {
if (has_problem (directory, file, lacks_directory_count)) {
return FALSE;
}
}
if (request->file_info) {
if (has_problem (directory, file, lacks_info)) {
return FALSE;
}
}
if (request->top_left_text) {
if (has_problem (directory, file, lacks_top_left)) {
return FALSE;
}
}
if (request->deep_count) {
if (has_problem (directory, file, lacks_deep_count)) {
return FALSE;
}
}
if (request->mime_list) {
if (has_problem (directory, file, lacks_mime_list)) {
return FALSE;
}
}
if (request->activation_uri) {
if (has_problem (directory, file, lacks_activation_uri)) {
return FALSE;
}
}
return TRUE;
}
static gboolean
call_ready_callbacks (NautilusDirectory *directory)
{
gboolean called_any;
GList *node, *next;
ReadyCallback *callback;
callback = NULL;
called_any = FALSE;
while (1) {
/* Check if any callbacks are satisifed and call them if they are. */
for (node = directory->details->call_when_ready_list;
node != NULL; node = next) {
next = node->next;
callback = node->data;
if (request_is_satisfied (directory, callback->file, &callback->request)) {
break;
}
}
if (node == NULL) {
return called_any;
}
/* Callbacks are one-shots, so remove it now. */
remove_callback_link_keep_data (directory, node);
/* Call the callback. */
ready_callback_call (directory, callback);
g_free (callback);
called_any = TRUE;
}
}
/* This checks if there's a request for monitoring the file list. */
gboolean
nautilus_directory_is_anyone_monitoring_file_list (NautilusDirectory *directory)
{
GList *node;
ReadyCallback *callback;
Monitor *monitor;
for (node = directory->details->call_when_ready_list;
node != NULL; node = node->next) {
callback = node->data;
if (callback->request.file_list) {
return TRUE;
}
}
for (node = directory->details->monitor_list;
node != NULL; node = node->next) {
monitor = node->data;
if (monitor->request.file_list) {
return TRUE;
}
}
return FALSE;
}
/* This checks if the file list being monitored. */
gboolean
nautilus_directory_is_file_list_monitored (NautilusDirectory *directory)
{
return directory->details->file_list_monitored;
}
static void
mark_all_files_unconfirmed (NautilusDirectory *directory)
{
GList *node;
NautilusFile *file;
for (node = directory->details->file_list; node != NULL; node = node->next) {
file = node->data;
set_file_unconfirmed (file, TRUE);
}
}
static gboolean
should_display_file_name (const char *name,
GnomeVFSDirectoryFilterOptions options)
{
/* Note that the name is URI-encoded, but this should not
* affect the . or the ~.
*/
if ((options & GNOME_VFS_DIRECTORY_FILTER_NODOTFILES) != 0
&& nautilus_file_name_matches_hidden_pattern (name)) {
return FALSE;
}
if ((options & GNOME_VFS_DIRECTORY_FILTER_NOBACKUPFILES) != 0
&& nautilus_file_name_matches_backup_pattern (name)) {
return FALSE;
}
/* Note that we don't bother to check for "." or ".." here, because
* this function is used only for search results, which never include
* those special files. If we later use this function more generally,
* we might have to change this.
*/
return TRUE;
}
/* Filter search results based on user preferences. This must be done
* differently than filtering other files because the search results
* are encoded: the entire file path is encoded and stored as the file
* name.
*/
static gboolean
filter_search_uri (const GnomeVFSFileInfo *info, gpointer data)
{
GnomeVFSDirectoryFilterOptions options;
char *real_file_uri;
gboolean result;
options = GPOINTER_TO_INT (data);
real_file_uri = nautilus_get_target_uri_from_search_result_name (info->name);
result = should_display_file_name (g_basename (real_file_uri), options);
g_free (real_file_uri);
return result;
}
static GnomeVFSDirectoryFilter *
get_file_count_filter (NautilusDirectory *directory)
{
if (nautilus_is_search_uri (directory->details->uri)) {
return gnome_vfs_directory_filter_new_custom
(filter_search_uri,
GNOME_VFS_DIRECTORY_FILTER_NEEDS_NAME,
GINT_TO_POINTER (get_filter_options_for_directory_count ()));
}
return gnome_vfs_directory_filter_new
(GNOME_VFS_DIRECTORY_FILTER_NONE,
get_filter_options_for_directory_count (),
NULL);
}
/* Start monitoring the file list if it isn't already. */
static void
start_monitoring_file_list (NautilusDirectory *directory)
{
if (!directory->details->file_list_monitored) {
g_assert (directory->details->directory_load_in_progress == NULL);
directory->details->file_list_monitored = TRUE;
nautilus_file_list_ref (directory->details->file_list);
}
if (directory->details->directory_loaded
|| directory->details->directory_load_in_progress != NULL) {
return;
}
if (!async_job_start (directory, "file list")) {
return;
}
mark_all_files_unconfirmed (directory);
g_assert (directory->details->uri != NULL);
directory->details->directory_load_list_last_handled
= GNOME_VFS_DIRECTORY_LIST_POSITION_NONE;
directory->details->load_directory_file =
nautilus_directory_get_corresponding_file (directory);
directory->details->load_directory_file->details->loading_directory = TRUE;
directory->details->load_file_count = 0;
directory->details->load_file_count_filter = get_file_count_filter (directory);
directory->details->load_mime_list_hash = istr_set_new ();
#ifdef DEBUG_LOAD_DIRECTORY
g_message ("load_directory called to monitor file list of %s", directory->details->uri);
#endif
gnome_vfs_async_load_directory
(&directory->details->directory_load_in_progress, /* handle */
directory->details->uri, /* uri */
(GNOME_VFS_FILE_INFO_GET_MIME_TYPE /* options */
| GNOME_VFS_FILE_INFO_FOLLOW_LINKS),
NULL, /* sort_rules */
FALSE, /* reverse_order */
GNOME_VFS_DIRECTORY_FILTER_NONE, /* filter_type */
(GNOME_VFS_DIRECTORY_FILTER_NOSELFDIR /* filter_options */
| GNOME_VFS_DIRECTORY_FILTER_NOPARENTDIR),
NULL, /* filter_pattern */
DIRECTORY_LOAD_ITEMS_PER_CALLBACK, /* items_per_notification */
directory_load_callback, /* callback */
directory);
}
/* Stop monitoring the file list if it is being monitored. */
void
nautilus_directory_stop_monitoring_file_list (NautilusDirectory *directory)
{
if (!directory->details->file_list_monitored) {
g_assert (directory->details->directory_load_in_progress == NULL);
return;
}
directory->details->file_list_monitored = FALSE;
file_list_cancel (directory);
nautilus_file_list_unref (directory->details->file_list);
directory->details->directory_loaded = FALSE;
}
static void
file_list_start (NautilusDirectory *directory)
{
if (nautilus_directory_is_anyone_monitoring_file_list (directory)) {
start_monitoring_file_list (directory);
} else {
nautilus_directory_stop_monitoring_file_list (directory);
}
}
void
nautilus_directory_invalidate_counts (NautilusDirectory *directory)
{
NautilusFile *file;
NautilusDirectory *parent_directory;
file = nautilus_directory_get_existing_corresponding_file (directory);
if (file != NULL) {
parent_directory = file->details->directory;
if (parent_directory->details->count_file == file) {
directory_count_cancel (parent_directory);
}
if (parent_directory->details->deep_count_file == file) {
deep_count_cancel (parent_directory);
}
if (parent_directory->details->mime_list_file == file) {
mime_list_cancel (parent_directory);
}
file->details->directory_count_is_up_to_date = FALSE;
file->details->deep_counts_status = NAUTILUS_REQUEST_NOT_STARTED;
file->details->mime_list_is_up_to_date = FALSE;
nautilus_file_unref (file);
nautilus_directory_async_state_changed (parent_directory);
}
}
static void
nautilus_directory_invalidate_file_attributes (NautilusDirectory *directory,
GList *file_attributes)
{
GList *node;
cancel_loading_attributes (directory, file_attributes);
for (node = directory->details->file_list; node != NULL; node = node->next) {
nautilus_file_invalidate_attributes_internal (NAUTILUS_FILE (node->data),
file_attributes);
}
if (directory->details->as_file != NULL) {
nautilus_file_invalidate_attributes_internal (directory->details->as_file,
file_attributes);
}
}
void
nautilus_directory_force_reload (NautilusDirectory *directory,
GList *file_attributes)
{
/* invalidate attributes that are getting reloaded for all files */
nautilus_directory_invalidate_file_attributes (directory, file_attributes);
/* Start a new directory load. */
file_list_cancel (directory);
directory->details->directory_loaded = FALSE;
/* Start a new directory count. */
nautilus_directory_invalidate_counts (directory);
nautilus_directory_async_state_changed (directory);
}
static gboolean
monitor_includes_file (const Monitor *monitor,
NautilusFile *file)
{
if (monitor->file == file) {
return TRUE;
}
if (monitor->file != NULL) {
return FALSE;
}
if (file == file->details->directory->details->as_file) {
return FALSE;
}
return nautilus_file_should_show (file,
monitor->monitor_hidden_files,
monitor->monitor_backup_files);
}
static gboolean
is_needy (NautilusFile *file,
FileCheck check_missing,
RequestCheck check_wanted)
{
NautilusDirectory *directory;
GList *node;
ReadyCallback *callback;
Monitor *monitor;
g_assert (NAUTILUS_IS_FILE (file));
if (!(* check_missing) (file)) {
return FALSE;
}
directory = file->details->directory;
for (node = directory->details->call_when_ready_list;
node != NULL; node = node->next) {
callback = node->data;
if ((* check_wanted) (&callback->request)) {
if (callback->file == file) {
return TRUE;
}
if (callback->file == NULL
&& file != directory->details->as_file) {
return TRUE;
}
}
}
for (node = directory->details->monitor_list;
node != NULL; node = node->next) {
monitor = node->data;
if ((* check_wanted) (&monitor->request)) {
if (monitor_includes_file (monitor, file)) {
return TRUE;
}
}
}
return FALSE;
}
static NautilusFile *
select_needy_file (NautilusDirectory *directory,
FileCheck check_missing,
RequestCheck check_wanted)
{
GList *node, *node_2;
ReadyCallback *callback;
Monitor *monitor;
NautilusFile *file;
/* Quick out if no one is interested. */
for (node = directory->details->call_when_ready_list;
node != NULL; node = node->next) {
callback = node->data;
if ((* check_wanted) (&callback->request)) {
break;
}
}
if (node == NULL) {
for (node = directory->details->monitor_list;
node != NULL; node = node->next) {
monitor = node->data;
if ((* check_wanted) (&monitor->request)) {
break;
}
}
if (node == NULL) {
return NULL;
}
}
/* Search for a file that has an unfulfilled request. */
for (node = directory->details->file_list;
node != NULL; node = node->next) {
file = node->data;
if ((* check_missing) (file)) {
for (node_2 = directory->details->call_when_ready_list;
node_2 != NULL; node_2 = node_2->next) {
callback = node_2->data;
if ((callback->file == NULL || callback->file == file)
&& (* check_wanted) (&callback->request)) {
break;
}
}
if (node_2 != NULL) {
return file;
}
for (node_2 = directory->details->monitor_list;
node_2 != NULL; node_2 = node_2->next) {
monitor = node_2->data;
if (monitor_includes_file (monitor, file)
&& (* check_wanted) (&monitor->request)) {
break;
}
}
if (node_2 != NULL) {
return file;
}
}
}
/* Finally, check the file for the directory itself. */
file = directory->details->as_file;
if (file != NULL) {
if ((* check_missing) (file)) {
for (node_2 = directory->details->call_when_ready_list;
node_2 != NULL; node_2 = node_2->next) {
callback = node_2->data;
if (callback->file == file
&& (* check_wanted) (&callback->request)) {
break;
}
}
if (node_2 != NULL) {
return file;
}
for (node_2 = directory->details->monitor_list;
node_2 != NULL; node_2 = node_2->next) {
monitor = node_2->data;
if (monitor->file == file
&& (* check_wanted) (&monitor->request)) {
break;
}
}
if (node_2 != NULL) {
return file;
}
}
}
return NULL;
}
static void
directory_count_start (NautilusDirectory *directory)
{
NautilusFile *file;
char *uri;
/* If there's already a count in progress, check to be sure
* it's still wanted.
*/
if (directory->details->count_in_progress != NULL) {
file = directory->details->count_file;
if (file != NULL) {
g_assert (NAUTILUS_IS_FILE (file));
g_assert (file->details->directory == directory);
if (is_needy (file,
should_get_directory_count,
wants_directory_count)) {
return;
}
}
/* The count is not wanted, so stop it. */
directory_count_cancel (directory);
}
/* Figure out which file to get a count for. */
file = select_needy_file (directory,
should_get_directory_count,
wants_directory_count);
if (file == NULL) {
return;
}
if (!async_job_start (directory, "directory count")) {
return;
}
/* Start counting. */
directory->details->count_file = file;
uri = nautilus_file_get_uri (file);
#ifdef DEBUG_LOAD_DIRECTORY
g_message ("load_directory called to get shallow file count for %s", uri);
#endif
gnome_vfs_async_load_directory
(&directory->details->count_in_progress,
uri,
GNOME_VFS_FILE_INFO_DEFAULT,
NULL,
FALSE,
GNOME_VFS_DIRECTORY_FILTER_NONE,
get_filter_options_for_directory_count (),
NULL,
G_MAXINT,
directory_count_callback,
directory);
g_free (uri);
}
static void
deep_count_one (NautilusDirectory *directory,
GnomeVFSFileInfo *info)
{
NautilusFile *file;
char *escaped_name, *uri;
file = directory->details->deep_count_file;
if ((info->valid_fields & GNOME_VFS_FILE_INFO_FIELDS_TYPE) != 0
&& info->type == GNOME_VFS_FILE_TYPE_DIRECTORY) {
/* Count the directory. */
file->details->deep_directory_count += 1;
/* Record the fact that we have to descend into this directory. */
escaped_name = gnome_vfs_escape_string (info->name);
uri = nautilus_make_path (directory->details->deep_count_uri, escaped_name);
g_free (escaped_name);
directory->details->deep_count_subdirectories = g_list_prepend
(directory->details->deep_count_subdirectories, uri);
} else {
/* Even non-regular files count as files. */
file->details->deep_file_count += 1;
}
/* Count the size. */
if ((info->valid_fields & GNOME_VFS_FILE_INFO_FIELDS_SIZE) != 0) {
file->details->deep_size += info->size;
}
}
static void
deep_count_callback (GnomeVFSAsyncHandle *handle,
GnomeVFSResult result,
GnomeVFSDirectoryList *list,
guint entries_read,
gpointer callback_data)
{
NautilusDirectory *directory;
NautilusFile *file;
GnomeVFSDirectoryListPosition last_handled, p;
char *uri;
gboolean done;
directory = NAUTILUS_DIRECTORY (callback_data);
g_assert (directory->details->deep_count_in_progress == handle);
file = directory->details->deep_count_file;
g_assert (NAUTILUS_IS_FILE (file));
/* We can't do this in the most straightforward way, becuse the position
* for a gnome_vfs_directory_list does not have a way of representing one
* past the end. So we must keep a position to the last item we handled
* rather than keeping a position past the last item we handled.
*/
last_handled = directory->details->deep_count_last_handled;
p = last_handled;
while ((p = directory_list_get_next_position (list, p))
!= GNOME_VFS_DIRECTORY_LIST_POSITION_NONE) {
deep_count_one (directory, gnome_vfs_directory_list_get (list, p));
last_handled = p;
}
directory->details->deep_count_last_handled = last_handled;
done = FALSE;
if (result != GNOME_VFS_OK) {
if (result != GNOME_VFS_ERROR_EOF) {
file->details->deep_unreadable_count += 1;
}
directory->details->deep_count_in_progress = NULL;
g_free (directory->details->deep_count_uri);
directory->details->deep_count_uri = NULL;
if (directory->details->deep_count_subdirectories != NULL) {
/* Work on a new directory. */
uri = directory->details->deep_count_subdirectories->data;
directory->details->deep_count_subdirectories = g_list_remove
(directory->details->deep_count_subdirectories, uri);
deep_count_load (directory, uri);
g_free (uri);
} else {
file->details->deep_counts_status = NAUTILUS_REQUEST_DONE;
directory->details->deep_count_file = NULL;
done = TRUE;
}
}
nautilus_file_changed (file);
if (done) {
async_job_end (directory, "deep count");
nautilus_directory_async_state_changed (directory);
}
}
static void
deep_count_load (NautilusDirectory *directory, const char *uri)
{
g_assert (directory->details->deep_count_uri == NULL);
directory->details->deep_count_uri = g_strdup (uri);
directory->details->deep_count_last_handled
= GNOME_VFS_DIRECTORY_LIST_POSITION_NONE;
#ifdef DEBUG_LOAD_DIRECTORY
g_message ("load_directory called to get deep file count for %s", uri);
#endif
gnome_vfs_async_load_directory
(&directory->details->deep_count_in_progress,
uri,
GNOME_VFS_FILE_INFO_DEFAULT,
NULL,
FALSE,
GNOME_VFS_DIRECTORY_FILTER_NONE,
(GNOME_VFS_DIRECTORY_FILTER_NOSELFDIR
| GNOME_VFS_DIRECTORY_FILTER_NOPARENTDIR),
NULL,
G_MAXINT,
deep_count_callback,
directory);
}
static void
deep_count_start (NautilusDirectory *directory)
{
NautilusFile *file;
char *uri;
/* If there's already a count in progress, check to be sure
* it's still wanted.
*/
if (directory->details->deep_count_in_progress != NULL) {
file = directory->details->deep_count_file;
if (file != NULL) {
g_assert (NAUTILUS_IS_FILE (file));
g_assert (file->details->directory == directory);
if (is_needy (file,
lacks_deep_count,
wants_deep_count)) {
return;
}
}
/* The count is not wanted, so stop it. */
deep_count_cancel (directory);
}
/* Figure out which file to get a count for. */
file = select_needy_file (directory,
lacks_deep_count,
wants_deep_count);
if (file == NULL) {
return;
}
if (!async_job_start (directory, "deep count")) {
return;
}
/* Start counting. */
file->details->deep_counts_status = NAUTILUS_REQUEST_IN_PROGRESS;
file->details->deep_directory_count = 0;
file->details->deep_file_count = 0;
file->details->deep_unreadable_count = 0;
file->details->deep_size = 0;
directory->details->deep_count_file = file;
uri = nautilus_file_get_uri (file);
deep_count_load (directory, uri);
g_free (uri);
}
static void
mime_list_one (NautilusDirectory *directory,
GnomeVFSFileInfo *info)
{
istr_set_insert (directory->details->mime_list_hash, info->mime_type);
}
static void
mime_list_callback (GnomeVFSAsyncHandle *handle,
GnomeVFSResult result,
GnomeVFSDirectoryList *list,
guint entries_read,
gpointer callback_data)
{
NautilusDirectory *directory;
NautilusFile *file;
GnomeVFSDirectoryListPosition last_handled, p;
directory = NAUTILUS_DIRECTORY (callback_data);
g_assert (directory->details->mime_list_in_progress == handle);
file = directory->details->mime_list_file;
g_assert (NAUTILUS_IS_FILE (file));
/* We can't do this in the most straightforward way, becuse the position
* for a gnome_vfs_directory_list does not have a way of representing one
* past the end. So we must keep a position to the last item we handled
* rather than keeping a position past the last item we handled.
*/
last_handled = directory->details->mime_list_last_handled;
p = last_handled;
while ((p = directory_list_get_next_position (list, p))
!= GNOME_VFS_DIRECTORY_LIST_POSITION_NONE) {
mime_list_one (directory, gnome_vfs_directory_list_get (list, p));
last_handled = p;
}
directory->details->mime_list_last_handled = last_handled;
if (result == GNOME_VFS_OK) {
return;
}
file->details->mime_list_is_up_to_date = TRUE;
/* Record either a failure or success. */
nautilus_g_list_free_deep (file->details->mime_list);
if (result != GNOME_VFS_ERROR_EOF) {
file->details->mime_list_failed = TRUE;
file->details->mime_list = NULL;
} else {
file->details->got_mime_list = TRUE;
file->details->mime_list = istr_set_get_as_list
(directory->details->mime_list_hash);
}
istr_set_destroy (directory->details->mime_list_hash);
directory->details->mime_list_in_progress = NULL;
directory->details->mime_list_file = NULL;
directory->details->mime_list_hash = NULL;
/* Send file-changed even if getting the item type list
* failed, so interested parties can distinguish between
* unknowable and not-yet-known cases.
*/
nautilus_file_changed (file);
/* Start up the next one. */
async_job_end (directory, "MIME list");
nautilus_directory_async_state_changed (directory);
}
static void
mime_list_load (NautilusDirectory *directory, const char *uri)
{
directory->details->mime_list_last_handled
= GNOME_VFS_DIRECTORY_LIST_POSITION_NONE;
directory->details->mime_list_hash = istr_set_new ();
#ifdef DEBUG_LOAD_DIRECTORY
g_message ("load_directory called to get MIME list of %s", uri);
#endif
gnome_vfs_async_load_directory
(&directory->details->mime_list_in_progress,
uri,
GNOME_VFS_FILE_INFO_GET_MIME_TYPE,
NULL,
FALSE,
GNOME_VFS_DIRECTORY_FILTER_NONE,
(GNOME_VFS_DIRECTORY_FILTER_NOSELFDIR
| GNOME_VFS_DIRECTORY_FILTER_NOPARENTDIR),
NULL,
DIRECTORY_LOAD_ITEMS_PER_CALLBACK,
mime_list_callback,
directory);
}
static void
mime_list_start (NautilusDirectory *directory)
{
NautilusFile *file;
char *uri;
/* If there's already a count in progress, check to be sure
* it's still wanted.
*/
if (directory->details->mime_list_in_progress != NULL) {
file = directory->details->mime_list_file;
if (file != NULL) {
g_assert (NAUTILUS_IS_FILE (file));
g_assert (file->details->directory == directory);
if (is_needy (file,
should_get_mime_list,
wants_mime_list)) {
return;
}
}
/* The count is not wanted, so stop it. */
mime_list_cancel (directory);
}
/* Figure out which file to get a mime list for. */
file = select_needy_file (directory,
should_get_mime_list,
wants_mime_list);
if (file == NULL) {
return;
}
if (!async_job_start (directory, "MIME list")) {
return;
}
directory->details->mime_list_file = file;
uri = nautilus_file_get_uri (file);
mime_list_load (directory, uri);
g_free (uri);
}
static int
count_lines (const char *text, int length)
{
int count, i;
count = 0;
for (i = 0; i < length; i++) {
count += *text++ == '\n';
}
return count;
}
static void
top_left_read_done (NautilusDirectory *directory)
{
g_assert (directory->details->top_left_read_state->handle == NULL);
g_assert (NAUTILUS_IS_FILE (directory->details->top_left_read_state->file));
directory->details->top_left_read_state->file->details->got_top_left_text = TRUE;
g_free (directory->details->top_left_read_state);
directory->details->top_left_read_state = NULL;
async_job_end (directory, "top left");
nautilus_directory_async_state_changed (directory);
}
static void
top_left_read_callback (GnomeVFSResult result,
GnomeVFSFileSize bytes_read,
char *file_contents,
gpointer callback_data)
{
NautilusDirectory *directory;
directory = NAUTILUS_DIRECTORY (callback_data);
directory->details->top_left_read_state->handle = NULL;
directory->details->top_left_read_state->file->details->top_left_text_is_up_to_date = TRUE;
if (result == GNOME_VFS_OK) {
g_free (directory->details->top_left_read_state->file->details->top_left_text);
directory->details->top_left_read_state->file->details->top_left_text =
nautilus_extract_top_left_text (file_contents, bytes_read);
directory->details->top_left_read_state->file->details->got_top_left_text = TRUE;
nautilus_file_changed (directory->details->top_left_read_state->file);
g_free (file_contents);
}
top_left_read_done (directory);
}
static gboolean
top_left_read_more_callback (GnomeVFSFileSize bytes_read,
const char *file_contents,
gpointer callback_data)
{
g_assert (NAUTILUS_IS_DIRECTORY (callback_data));
/* Stop reading when we have enough. */
return bytes_read < NAUTILUS_FILE_TOP_LEFT_TEXT_MAXIMUM_BYTES
&& count_lines (file_contents, bytes_read) <= NAUTILUS_FILE_TOP_LEFT_TEXT_MAXIMUM_LINES;
}
static void
top_left_start (NautilusDirectory *directory)
{
NautilusFile *file;
char *uri;
/* If there's already a read in progress, check to be sure
* it's still wanted.
*/
if (directory->details->top_left_read_state != NULL) {
file = directory->details->top_left_read_state->file;
if (file != NULL) {
g_assert (NAUTILUS_IS_FILE (file));
g_assert (file->details->directory == directory);
if (is_needy (file,
lacks_top_left,
wants_top_left)) {
return;
}
}
/* The top left is not wanted, so stop it. */
top_left_cancel (directory);
}
/* Figure out which file to read the top left for. */
file = select_needy_file (directory,
lacks_top_left,
wants_top_left);
if (file == NULL) {
return;
}
if (!async_job_start (directory, "top left")) {
return;
}
/* Start reading. */
directory->details->top_left_read_state = g_new0 (TopLeftTextReadState, 1);
directory->details->top_left_read_state->file = file;
uri = nautilus_file_get_uri (file);
directory->details->top_left_read_state->handle = nautilus_read_file_async
(uri,
top_left_read_callback,
top_left_read_more_callback,
directory);
g_free (uri);
}
static void
get_info_callback (GnomeVFSAsyncHandle *handle,
GList *results,
gpointer callback_data)
{
NautilusDirectory *directory;
NautilusFile *get_info_file;
GnomeVFSGetFileInfoResult *result;
directory = NAUTILUS_DIRECTORY (callback_data);
g_assert (handle == NULL || handle == directory->details->get_info_in_progress);
g_assert (nautilus_g_list_exactly_one_item (results));
get_info_file = directory->details->get_info_file;
g_assert (NAUTILUS_IS_FILE (get_info_file));
directory->details->get_info_file = NULL;
directory->details->get_info_in_progress = NULL;
/* ref here because we might be removing the last ref when we
* mark the file gone below, but we need to keep a ref at
* least long enough to send the change notification.
*/
nautilus_file_ref (get_info_file);
result = results->data;
if (result->result != GNOME_VFS_OK) {
get_info_file->details->file_info_is_up_to_date = TRUE;
if (get_info_file->details->info != NULL) {
gnome_vfs_file_info_unref (get_info_file->details->info);
get_info_file->details->info = NULL;
}
get_info_file->details->get_info_failed = TRUE;
get_info_file->details->get_info_error = result->result;
if (result->result == GNOME_VFS_ERROR_NOT_FOUND) {
/* mark file as gone */
get_info_file->details->is_gone = TRUE;
if (get_info_file != directory->details->as_file) {
nautilus_directory_remove_file (directory, get_info_file);
}
}
} else {
nautilus_file_update_info (get_info_file, result->file_info);
}
nautilus_file_changed (get_info_file);
nautilus_file_unref (get_info_file);
async_job_end (directory, "file info");
nautilus_directory_async_state_changed (directory);
}
static void
file_info_start (NautilusDirectory *directory)
{
NautilusFile *file;
char *uri;
GnomeVFSURI *vfs_uri;
GList fake_list;
/* If there's already a file info fetch in progress, check to
* be sure it's still wanted.
*/
if (directory->details->get_info_in_progress != NULL) {
file = directory->details->get_info_file;
if (file != NULL) {
g_assert (NAUTILUS_IS_FILE (file));
g_assert (file->details->directory == directory);
if (is_needy (file, lacks_info, wants_info)) {
return;
}
}
/* The info is not wanted, so stop it. */
file_info_cancel (directory);
}
/* Figure out which file to get file info for. */
do {
file = select_needy_file (directory, lacks_info, wants_info);
if (file == NULL) {
return;
}
uri = nautilus_file_get_uri (file);
vfs_uri = gnome_vfs_uri_new (uri);
g_free (uri);
if (vfs_uri == NULL) {
file->details->file_info_is_up_to_date = TRUE;
file->details->get_info_failed = TRUE;
file->details->get_info_error = GNOME_VFS_ERROR_INVALID_URI;
nautilus_file_changed (file);
}
} while (vfs_uri == NULL);
/* Found one we need to get the info for. */
if (!async_job_start (directory, "file info")) {
return;
}
directory->details->get_info_file = file;
fake_list.data = vfs_uri;
fake_list.prev = NULL;
fake_list.next = NULL;
gnome_vfs_async_get_file_info
(&directory->details->get_info_in_progress,
&fake_list,
GNOME_VFS_FILE_INFO_GET_MIME_TYPE
| GNOME_VFS_FILE_INFO_FOLLOW_LINKS,
get_info_callback,
directory);
gnome_vfs_uri_unref (vfs_uri);
}
static void
activation_uri_done (NautilusDirectory *directory,
NautilusFile *file,
const char *uri)
{
file->details->activation_uri_is_up_to_date = TRUE;
file->details->got_activation_uri = TRUE;
g_free (file->details->activation_uri);
file->details->activation_uri = g_strdup (uri);
async_job_end (directory, "activation URI");
nautilus_directory_async_state_changed (directory);
}
static void
activation_uri_read_done (NautilusDirectory *directory,
const char *uri)
{
NautilusFile *file;
file = directory->details->activation_uri_read_state->file;
g_free (directory->details->activation_uri_read_state);
directory->details->activation_uri_read_state = NULL;
activation_uri_done (directory, file, uri);
}
static void
activation_uri_nautilus_link_read_callback (GnomeVFSResult result,
GnomeVFSFileSize bytes_read,
char *file_contents,
gpointer callback_data)
{
NautilusDirectory *directory;
char *buffer, *uri;
directory = NAUTILUS_DIRECTORY (callback_data);
/* Handle the case where we read the Nautilus link. */
if (result != GNOME_VFS_OK) {
/* FIXME bugzilla.eazel.com 2433: We should report this error to the user. */
g_free (file_contents);
uri = NULL;
} else {
/* The gnome-xml parser requires a zero-terminated array. */
buffer = g_realloc (file_contents, bytes_read + 1);
buffer[bytes_read] = '\0';
uri = nautilus_link_get_link_uri_given_file_contents (buffer, bytes_read);
g_free (buffer);
}
activation_uri_read_done (directory, uri);
g_free (uri);
}
static void
activation_uri_gmc_link_read_callback (GnomeVFSResult result,
GnomeVFSFileSize bytes_read,
char *file_contents,
gpointer callback_data)
{
NautilusDirectory *directory;
char *end_of_line, *uri;
directory = NAUTILUS_DIRECTORY (callback_data);
/* Handle the case where we read the GMC link. */
if (result != GNOME_VFS_OK || !nautilus_str_has_prefix (file_contents, "URL: ")) {
/* FIXME bugzilla.eazel.com 2433: We should report this error to the user. */
uri = NULL;
} else {
/* Make sure we don't run off the end of the buffer. */
end_of_line = memchr (file_contents, '\n', bytes_read);
if (end_of_line != NULL) {
uri = g_strndup (file_contents, end_of_line - file_contents);
} else {
uri = g_strndup (file_contents, bytes_read);
}
}
g_free (file_contents);
activation_uri_read_done (directory, uri);
g_free (uri);
}
static gboolean
activation_uri_gmc_link_read_more_callback (GnomeVFSFileSize bytes_read,
const char *file_contents,
gpointer callback_data)
{
g_assert (NAUTILUS_IS_DIRECTORY (callback_data));
/* We need the first 512 bytes to see if something is a gmc link. */
return bytes_read < 512;
}
static void
activation_uri_start (NautilusDirectory *directory)
{
NautilusFile *file;
char *mime_type, *uri;
gboolean gmc_style_link, nautilus_style_link;
/* If there's already a activation URI read in progress, check
* to be sure it's still wanted.
*/
if (directory->details->activation_uri_read_state != NULL) {
file = directory->details->activation_uri_read_state->file;
if (file != NULL) {
g_assert (NAUTILUS_IS_FILE (file));
g_assert (file->details->directory == directory);
if (is_needy (file, lacks_info, wants_info)) {
return;
}
}
/* The count is not wanted, so stop it. */
activation_uri_cancel (directory);
}
/* Figure out which file to get activation_uri for. */
file = select_needy_file (directory,
lacks_activation_uri,
wants_activation_uri);
if (file == NULL) {
return;
}
if (!async_job_start (directory, "activation URI")) {
return;
}
/* Figure out if it is a link. */
mime_type = nautilus_file_get_mime_type (file);
gmc_style_link = nautilus_strcasecmp (mime_type, "application/x-gmc-link") == 0;
g_free (mime_type);
nautilus_style_link = nautilus_file_is_nautilus_link (file);
/* If it's not a link we are done. If it is, we need to read it. */
if (!(gmc_style_link || nautilus_style_link)) {
activation_uri_done (directory, file, NULL);
} else {
directory->details->activation_uri_read_state = g_new0 (ActivationURIReadState, 1);
directory->details->activation_uri_read_state->file = file;
uri = nautilus_file_get_uri (file);
if (gmc_style_link) {
directory->details->activation_uri_read_state->handle = nautilus_read_file_async
(uri,
activation_uri_gmc_link_read_callback,
activation_uri_gmc_link_read_more_callback,
directory);
} else {
directory->details->activation_uri_read_state->handle = nautilus_read_entire_file_async
(uri,
activation_uri_nautilus_link_read_callback,
directory);
}
g_free (uri);
}
}
static void
start_or_stop_io (NautilusDirectory *directory)
{
/* Start or stop getting file info. */
file_info_start (directory);
/* Start or stop reading the metafile. */
metafile_read_start (directory);
/* Start or stop reading files. */
file_list_start (directory);
/* Start or stop getting directory counts. */
directory_count_start (directory);
deep_count_start (directory);
/* Start or stop getting mime lists. */
mime_list_start (directory);
/* Start or stop getting top left pieces of files. */
top_left_start (directory);
/* Start or stop getting activation URIs, which includes
* reading the contents of Nautilus and GMC link files.
*/
activation_uri_start (directory);
}
/* Call this when the monitor or call when ready list changes,
* or when some I/O is completed.
*/
void
nautilus_directory_async_state_changed (NautilusDirectory *directory)
{
/* Check if any callbacks are satisfied and call them if they
* are. Do this last so that any changes done in start or stop
* I/O functions immediately (not in callbacks) are taken into
* consideration. If any callbacks are called, consider the
* I/O state again so that we can release or cancel I/O that
* is not longer needed once the callbacks are satisfied.
*/
nautilus_directory_ref (directory);
do {
start_or_stop_io (directory);
} while (call_ready_callbacks (directory));
nautilus_directory_unref (directory);
/* Check if any directories should wake up. */
async_job_wake_up ();
}
void
nautilus_directory_cancel (NautilusDirectory *directory)
{
/* Arbitrary order (kept alphabetical). */
activation_uri_cancel (directory);
deep_count_cancel (directory);
directory_count_cancel (directory);
file_info_cancel (directory);
file_list_cancel (directory);
metafile_read_cancel (directory);
mime_list_cancel (directory);
top_left_cancel (directory);
/* We aren't waiting for anything any more. */
if (waiting_directories != NULL) {
g_hash_table_remove (waiting_directories, directory);
}
/* Check if any directories should wake up. */
async_job_wake_up ();
}
static void
cancel_directory_count_for_file (NautilusDirectory *directory,
NautilusFile *file)
{
if (directory->details->count_file == file) {
directory_count_cancel (directory);
}
}
static void
cancel_deep_counts_for_file (NautilusDirectory *directory,
NautilusFile *file)
{
if (directory->details->deep_count_file == file) {
deep_count_cancel (directory);
}
}
static void
cancel_mime_list_for_file (NautilusDirectory *directory,
NautilusFile *file)
{
if (directory->details->mime_list_file == file) {
mime_list_cancel (directory);
}
}
static void
cancel_top_left_text_for_file (NautilusDirectory *directory,
NautilusFile *file)
{
if (directory->details->top_left_read_state != NULL &&
directory->details->top_left_read_state->file == file) {
top_left_cancel (directory);
}
}
static void
cancel_file_info_for_file (NautilusDirectory *directory,
NautilusFile *file)
{
if (directory->details->get_info_file == file) {
file_info_cancel (directory);
}
}
static void
cancel_activation_uri_for_file (NautilusDirectory *directory,
NautilusFile *file)
{
if (directory->details->activation_uri_read_state != NULL &&
directory->details->activation_uri_read_state->file == file) {
activation_uri_cancel (directory);
}
}
static void
cancel_loading_attributes (NautilusDirectory *directory,
GList *file_attributes)
{
Request request;
nautilus_directory_set_up_request (&request,
file_attributes);
if (request.directory_count) {
directory_count_cancel (directory);
}
if (request.deep_count) {
deep_count_cancel (directory);
}
if (request.mime_list) {
mime_list_cancel (directory);
}
if (request.top_left_text) {
top_left_cancel (directory);
}
if (request.file_info) {
file_info_cancel (directory);
}
if (request.activation_uri) {
file_info_cancel (directory);
}
/* FIXME bugzilla.eazel.com 5064: implement cancelling metadata when we
implement invalidating metadata */
}
void
nautilus_directory_cancel_loading_file_attributes (NautilusDirectory *directory,
NautilusFile *file,
GList *file_attributes)
{
Request request;
nautilus_directory_set_up_request (&request,
file_attributes);
if (request.directory_count) {
cancel_directory_count_for_file (directory, file);
}
if (request.deep_count) {
cancel_deep_counts_for_file (directory, file);
}
if (request.mime_list) {
cancel_mime_list_for_file (directory, file);
}
if (request.top_left_text) {
cancel_top_left_text_for_file (directory, file);
}
if (request.file_info) {
cancel_file_info_for_file (directory, file);
}
if (request.activation_uri) {
cancel_activation_uri_for_file (directory, file);
}
/* FIXME bugzilla.eazel.com 5064: implement cancelling metadata when we
implement invalidating metadata */
}