diff --git a/.cvsignore b/.cvsignore index e1e444b..3aec9a6 100644 --- a/.cvsignore +++ b/.cvsignore @@ -1 +1 @@ -gnome-vfs-2.1.3.1.tar.bz2 +gnome-vfs-2.2.2.tar.bz2 diff --git a/desktop-method.c b/desktop-method.c new file mode 100644 index 0000000..609a4cf --- /dev/null +++ b/desktop-method.c @@ -0,0 +1,978 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: 8; c-basic-offset: 8 -*- */ + +/* desktop-method.c + + Copyright (C) 2001 Red Hat, Inc. + + The Gnome Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. + + The Gnome Library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with the Gnome Library; see the file COPYING.LIB. If not, + write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. +*/ + +/* URI scheme for remapping directories under magic URIs, used + * for the magic desktop file directories such as start-here. + */ + +#ifdef HAVE_CONFIG_H +# include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include + +/* FIXME Maybe when chaining to file:, we should call the gnome-vfs wrapper + * functions, instead of the file: methods directly. + */ + +#define N_ELEMENTS(arr) (sizeof (arr) / sizeof ((arr)[0])) + +static GnomeVFSURI* desktop_uri_to_file_uri (GnomeVFSURI *desktop_uri); + +static GnomeVFSResult open_merged_directory (GnomeVFSMethod *method, + GnomeVFSMethodHandle **method_handle, + GnomeVFSURI *uri, + GnomeVFSFileInfoOptions options, + GnomeVFSContext *context); + +static char* create_file_uri_in_dir (const char *dir, + const char *filename); + +static GnomeVFSMethod *parent_method = NULL; + +static GnomeVFSResult +do_open (GnomeVFSMethod *method, + GnomeVFSMethodHandle **method_handle, + GnomeVFSURI *uri, + GnomeVFSOpenMode mode, + GnomeVFSContext *context) +{ + GnomeVFSURI *file_uri; + GnomeVFSResult result; + + file_uri = desktop_uri_to_file_uri (uri); + result = (* parent_method->open) (parent_method, + method_handle, + file_uri, + mode, + context); + gnome_vfs_uri_unref (file_uri); + + return result; +} + +static GnomeVFSResult +do_create (GnomeVFSMethod *method, + GnomeVFSMethodHandle **method_handle, + GnomeVFSURI *uri, + GnomeVFSOpenMode mode, + gboolean exclusive, + guint perm, + GnomeVFSContext *context) +{ + GnomeVFSURI *file_uri; + GnomeVFSResult result; + + file_uri = desktop_uri_to_file_uri (uri); + result = (* parent_method->create) (parent_method, + method_handle, + file_uri, + mode, + exclusive, + perm, + context); + gnome_vfs_uri_unref (file_uri); + + return result; +} + +static GnomeVFSResult +do_close (GnomeVFSMethod *method, + GnomeVFSMethodHandle *method_handle, + GnomeVFSContext *context) +{ + GnomeVFSResult result; + + result = (* parent_method->close) (parent_method, + method_handle, + context); + + return result; +} + +static GnomeVFSResult +do_read (GnomeVFSMethod *method, + GnomeVFSMethodHandle *method_handle, + gpointer buffer, + GnomeVFSFileSize num_bytes, + GnomeVFSFileSize *bytes_read, + GnomeVFSContext *context) +{ + GnomeVFSResult result; + + result = (* parent_method->read) (parent_method, + method_handle, + buffer, num_bytes, + bytes_read, + context); + + return result; +} + +static GnomeVFSResult +do_write (GnomeVFSMethod *method, + GnomeVFSMethodHandle *method_handle, + gconstpointer buffer, + GnomeVFSFileSize num_bytes, + GnomeVFSFileSize *bytes_written, + GnomeVFSContext *context) +{ + GnomeVFSResult result; + + result = (* parent_method->write) (parent_method, + method_handle, + buffer, num_bytes, + bytes_written, + context); + + return result; +} + + +static GnomeVFSResult +do_seek (GnomeVFSMethod *method, + GnomeVFSMethodHandle *method_handle, + GnomeVFSSeekPosition whence, + GnomeVFSFileOffset offset, + GnomeVFSContext *context) +{ + GnomeVFSResult result; + + result = (* parent_method->seek) (parent_method, + method_handle, + whence, offset, + context); + + return result; +} + +static GnomeVFSResult +do_tell (GnomeVFSMethod *method, + GnomeVFSMethodHandle *method_handle, + GnomeVFSFileOffset *offset_return) +{ + GnomeVFSResult result; + + result = (* parent_method->tell) (parent_method, + method_handle, + offset_return); + + return result; +} + + +static GnomeVFSResult +do_truncate_handle (GnomeVFSMethod *method, + GnomeVFSMethodHandle *method_handle, + GnomeVFSFileSize where, + GnomeVFSContext *context) +{ + GnomeVFSResult result; + + result = (* parent_method->truncate_handle) (parent_method, + method_handle, + where, + context); + + return result; +} + +static GnomeVFSResult +do_truncate (GnomeVFSMethod *method, + GnomeVFSURI *uri, + GnomeVFSFileSize where, + GnomeVFSContext *context) +{ + GnomeVFSURI *file_uri; + GnomeVFSResult result; + + file_uri = desktop_uri_to_file_uri (uri); + result = (* parent_method->truncate) (parent_method, + file_uri, + where, + context); + + gnome_vfs_uri_unref (file_uri); + + return result; +} + +typedef struct _DirHandle DirHandle; + +struct _DirHandle +{ + GSList *next; + GSList *handles; +}; + +static GnomeVFSResult +do_open_directory (GnomeVFSMethod *method, + GnomeVFSMethodHandle **method_handle, + GnomeVFSURI *uri, + GnomeVFSFileInfoOptions options, + GnomeVFSContext *context) +{ + return open_merged_directory (method, method_handle, + uri, options, context); +} + +static GnomeVFSResult +do_close_directory (GnomeVFSMethod *method, + GnomeVFSMethodHandle *method_handle, + GnomeVFSContext *context) +{ + GnomeVFSResult result; + GSList *tmp; + DirHandle *dh; + + dh = (DirHandle*) method_handle; + + result = GNOME_VFS_OK; + tmp = dh->handles; + while (tmp != NULL) { + GnomeVFSResult this_result; + + this_result = (* parent_method->close_directory) (parent_method, + tmp->data, + context); + + if (this_result != GNOME_VFS_OK) + result = this_result; + + tmp = tmp->next; + } + + g_slist_free (dh->handles); + g_free (dh); + + return result; +} + +static GnomeVFSResult +do_read_directory (GnomeVFSMethod *method, + GnomeVFSMethodHandle *method_handle, + GnomeVFSFileInfo *file_info, + GnomeVFSContext *context) +{ + GnomeVFSResult result; + GnomeVFSMethodHandle *parent_handle; + DirHandle *dh; + + dh = (DirHandle*) method_handle; + + if (dh->next == NULL) { + return GNOME_VFS_ERROR_EOF; + } + + next: + parent_handle = dh->next->data; + + result = (* parent_method->read_directory) (parent_method, + parent_handle, + file_info, + context); + + if (result != GNOME_VFS_OK) { + dh->next = dh->next->next; + if (dh->next) + goto next; + else + return result; + } else { + return GNOME_VFS_OK; + } +} + + +static void +set_directory_mime_type (GnomeVFSFileInfo *file_info) +{ + g_free (file_info->mime_type); + + file_info->mime_type = g_strdup ("x-directory/vfolder-desktop"); + file_info->valid_fields |= GNOME_VFS_FILE_INFO_FIELDS_MIME_TYPE; +} + +static GnomeVFSResult +do_get_file_info (GnomeVFSMethod *method, + GnomeVFSURI *uri, + GnomeVFSFileInfo *file_info, + GnomeVFSFileInfoOptions options, + GnomeVFSContext *context) +{ + GnomeVFSURI *file_uri; + GnomeVFSResult result; + + file_uri = desktop_uri_to_file_uri (uri); + result = (* parent_method->get_file_info) (parent_method, + file_uri, + file_info, + options, + context); + + if (file_info->type == GNOME_VFS_FILE_TYPE_DIRECTORY) + set_directory_mime_type (file_info); + + gnome_vfs_uri_unref (file_uri); + + return result; +} + +static GnomeVFSResult +do_get_file_info_from_handle (GnomeVFSMethod *method, + GnomeVFSMethodHandle *method_handle, + GnomeVFSFileInfo *file_info, + GnomeVFSFileInfoOptions options, + GnomeVFSContext *context) +{ + GnomeVFSResult result; + + result = (* parent_method->get_file_info_from_handle) (parent_method, + method_handle, + file_info, + options, + context); + + if (file_info->type == GNOME_VFS_FILE_TYPE_DIRECTORY) + set_directory_mime_type (file_info); + + return result; +} + + +static gboolean +do_is_local (GnomeVFSMethod *method, + const GnomeVFSURI *uri) +{ + return TRUE; +} + + +static GnomeVFSResult +do_make_directory (GnomeVFSMethod *method, + GnomeVFSURI *uri, + guint perm, + GnomeVFSContext *context) +{ + GnomeVFSURI *file_uri; + GnomeVFSResult result; + + file_uri = desktop_uri_to_file_uri (uri); + result = (* parent_method->make_directory) (parent_method, + file_uri, + perm, + context); + + gnome_vfs_uri_unref (file_uri); + + return result; +} + +static GnomeVFSResult +do_remove_directory (GnomeVFSMethod *method, + GnomeVFSURI *uri, + GnomeVFSContext *context) +{ + GnomeVFSURI *file_uri; + GnomeVFSResult result; + + file_uri = desktop_uri_to_file_uri (uri); + result = (* parent_method->remove_directory) (parent_method, + file_uri, + context); + + gnome_vfs_uri_unref (file_uri); + + return result; +} + +static GnomeVFSResult +do_find_directory (GnomeVFSMethod *method, + GnomeVFSURI *near_uri, + GnomeVFSFindDirectoryKind kind, + GnomeVFSURI **result_uri, + gboolean create_if_needed, + gboolean find_if_needed, + guint permissions, + GnomeVFSContext *context) +{ + GnomeVFSURI *file_uri; + GnomeVFSURI *file_result_uri; + GnomeVFSResult result; + + file_result_uri = NULL; + file_uri = desktop_uri_to_file_uri (near_uri); + result = (* parent_method->find_directory) (parent_method, + file_uri, + kind, + &file_result_uri, + create_if_needed, + find_if_needed, + permissions, + context); + + gnome_vfs_uri_unref (file_uri); + + if (result_uri) + *result_uri = file_result_uri; + + if (file_result_uri == NULL) + result = GNOME_VFS_ERROR_NOT_FOUND; + + return result; +} + +static GnomeVFSResult +do_move (GnomeVFSMethod *method, + GnomeVFSURI *old_uri, + GnomeVFSURI *new_uri, + gboolean force_replace, + GnomeVFSContext *context) +{ + GnomeVFSURI *old_file_uri; + GnomeVFSURI *new_file_uri; + GnomeVFSResult result; + + old_file_uri = desktop_uri_to_file_uri (old_uri); + new_file_uri = desktop_uri_to_file_uri (new_uri); + + result = (* parent_method->move) (parent_method, + old_file_uri, + new_file_uri, + force_replace, + context); + gnome_vfs_uri_unref (old_file_uri); + gnome_vfs_uri_unref (new_file_uri); + + return result; +} + +static GnomeVFSResult +do_unlink (GnomeVFSMethod *method, + GnomeVFSURI *uri, + GnomeVFSContext *context) +{ + GnomeVFSURI *file_uri; + GnomeVFSResult result; + + file_uri = desktop_uri_to_file_uri (uri); + result = (* parent_method->unlink) (parent_method, + file_uri, + context); + + gnome_vfs_uri_unref (file_uri); + + return result; +} + +static GnomeVFSResult +do_create_symbolic_link (GnomeVFSMethod *method, + GnomeVFSURI *uri, + const char *target_reference, + GnomeVFSContext *context) +{ + GnomeVFSURI *file_uri; + GnomeVFSResult result; + + file_uri = desktop_uri_to_file_uri (uri); + result = (* parent_method->create_symbolic_link) (parent_method, + file_uri, + target_reference, + context); + + gnome_vfs_uri_unref (file_uri); + + return result; +} + +static GnomeVFSResult +do_check_same_fs (GnomeVFSMethod *method, + GnomeVFSURI *source_uri, + GnomeVFSURI *target_uri, + gboolean *same_fs_return, + GnomeVFSContext *context) +{ + GnomeVFSURI *source_file_uri; + GnomeVFSURI *target_file_uri; + GnomeVFSResult result; + + source_file_uri = desktop_uri_to_file_uri (source_uri); + target_file_uri = desktop_uri_to_file_uri (target_uri); + + result = (* parent_method->check_same_fs) (parent_method, + source_file_uri, + target_file_uri, + same_fs_return, + context); + gnome_vfs_uri_unref (source_file_uri); + gnome_vfs_uri_unref (target_file_uri); + + return result; +} + +static GnomeVFSResult +do_set_file_info (GnomeVFSMethod *method, + GnomeVFSURI *uri, + const GnomeVFSFileInfo *info, + GnomeVFSSetFileInfoMask mask, + GnomeVFSContext *context) +{ + GnomeVFSURI *file_uri; + GnomeVFSResult result; + + file_uri = desktop_uri_to_file_uri (uri); + result = (* parent_method->set_file_info) (parent_method, + file_uri, + info, + mask, + context); + + gnome_vfs_uri_unref (file_uri); + + return result; +} + +typedef struct { + GnomeVFSMonitorHandle *handle; + GnomeVFSURI *desktop_uri; +} DesktopMonitorHandle; + +static void +monitor_notify_cb (GnomeVFSMonitorHandle *handle, + const gchar *monitor_uri, + const gchar *info_uri, + GnomeVFSMonitorEventType event_type, + gpointer user_data) +{ + DesktopMonitorHandle *monitor_handle; + GnomeVFSURI *desktop_info_uri; + const gchar *uri_diff; + gint monitor_uri_len; + + monitor_handle = (DesktopMonitorHandle *) user_data; + desktop_info_uri = NULL; + monitor_uri_len = strlen (monitor_uri); + + if (info_uri != NULL && + strncmp (info_uri, monitor_uri, monitor_uri_len) == 0) { + uri_diff = &info_uri [monitor_uri_len]; + + if (*uri_diff != '\0') { + desktop_info_uri = + gnome_vfs_uri_append_string ( + monitor_handle->desktop_uri, + uri_diff); + } else { + desktop_info_uri = monitor_handle->desktop_uri; + gnome_vfs_uri_ref (desktop_info_uri); + } + } + + gnome_vfs_monitor_callback ((GnomeVFSMethodHandle *) monitor_handle, + desktop_info_uri, + event_type); + + gnome_vfs_uri_unref (desktop_info_uri); +} + +static GnomeVFSResult +do_monitor_add (GnomeVFSMethod *method, + GnomeVFSMethodHandle **method_handle_return, + GnomeVFSURI *uri, + GnomeVFSMonitorType monitor_type) +{ + DesktopMonitorHandle *monitor_handle; + GnomeVFSURI *file_uri; + GnomeVFSResult result; + + monitor_handle = g_new0 (DesktopMonitorHandle, 1); + monitor_handle->desktop_uri = uri; + gnome_vfs_uri_ref (uri); + + file_uri = desktop_uri_to_file_uri (uri); + result = gnome_vfs_monitor_do_add (parent_method, + &monitor_handle->handle, + file_uri, + monitor_type, + monitor_notify_cb, + monitor_handle); + gnome_vfs_uri_unref (file_uri); + + if (result != GNOME_VFS_OK) { + gnome_vfs_uri_unref (monitor_handle->desktop_uri); + g_free (monitor_handle); + } + + *method_handle_return = (GnomeVFSMethodHandle *) monitor_handle; + + return result; +} + +static GnomeVFSResult +do_monitor_cancel (GnomeVFSMethod *method, + GnomeVFSMethodHandle *method_handle) +{ + DesktopMonitorHandle *monitor_handle; + GnomeVFSResult result; + + monitor_handle = (DesktopMonitorHandle *) method_handle; + + result = gnome_vfs_monitor_do_cancel (monitor_handle->handle); + + gnome_vfs_uri_unref (monitor_handle->desktop_uri); + g_free (monitor_handle); + + return result; +} + + + +/* gnome-vfs bureaucracy */ + +static GnomeVFSMethod method = { + sizeof (GnomeVFSMethod), + do_open, + do_create, + do_close, + do_read, + do_write, + do_seek, + do_tell, + do_truncate_handle, + do_open_directory, + do_close_directory, + do_read_directory, + do_get_file_info, + do_get_file_info_from_handle, + do_is_local, + do_make_directory, + do_remove_directory, + do_move, + do_unlink, + do_check_same_fs, + do_set_file_info, + do_truncate, + do_find_directory, + do_create_symbolic_link, + do_monitor_add, + do_monitor_cancel +}; + + +typedef enum +{ + SCHEME_FAVORITES, + SCHEME_PREFERENCES, + SCHEME_START_HERE, + SCHEME_SYSTEM_SETTINGS, + SCHEME_SERVER_SETTINGS, + SCHEME_PROGRAMS +} SchemeID; + +#define MAX_DIRECTORIES 3 +#define DIRECTORIES_INITIALIZER { NULL, NULL, NULL } + +typedef struct _SchemeDescription SchemeDescription; + +struct _SchemeDescription +{ + SchemeID id; + + const char *scheme; + + char *directories[MAX_DIRECTORIES]; +}; + +static SchemeDescription schemes[] = +{ + { SCHEME_FAVORITES, "favorites", + DIRECTORIES_INITIALIZER }, + { SCHEME_PREFERENCES, "preferences", + DIRECTORIES_INITIALIZER }, + { SCHEME_START_HERE, "start-here", + DIRECTORIES_INITIALIZER }, + { SCHEME_SYSTEM_SETTINGS, "system-settings", + DIRECTORIES_INITIALIZER }, + { SCHEME_SERVER_SETTINGS, "server-settings", + DIRECTORIES_INITIALIZER }, + { SCHEME_PROGRAMS, "programs", + DIRECTORIES_INITIALIZER } +}; + +GnomeVFSMethod * +vfs_module_init (const char *method_name, + const char *args) +{ + int i; + + parent_method = gnome_vfs_method_get ("file"); + + if (parent_method == NULL) { + g_error ("Could not find 'file' method for gnome-vfs"); + return NULL; + } + + i = 0; + while (i < N_ELEMENTS (schemes)) { + switch (schemes[i].id) { + case SCHEME_FAVORITES: + schemes[i].directories[0] = + g_strconcat (g_get_home_dir (), + "/.gnome/apps", + NULL); + break; + case SCHEME_PREFERENCES: + /* FIXME I think the GNOME 2 control center will move + * this, but we don't know where to yet + */ + schemes[i].directories[0] = + g_strconcat (DATADIR, "/control-center/capplets", NULL); + break; + case SCHEME_START_HERE: + schemes[i].directories[0] = g_strconcat (SYSCONFDIR, + "/X11/starthere", + NULL); + break; + case SCHEME_SYSTEM_SETTINGS: + schemes[i].directories[0] = + g_strconcat (SYSCONFDIR, "/X11/sysconfig", NULL); + break; + case SCHEME_SERVER_SETTINGS: + schemes[i].directories[0] = + g_strconcat (SYSCONFDIR, "/X11/serverconfig", NULL); + break; + case SCHEME_PROGRAMS: + schemes[i].directories[0] = g_strconcat (SYSCONFDIR, + "/X11/applnk", + NULL); + schemes[i].directories[1] = + g_strconcat (DATADIR, "gnome/apps", NULL); + break; + default: + g_assert_not_reached (); + break; + } + + ++i; + } + + return &method; +} + +void +vfs_module_shutdown (GnomeVFSMethod *method) +{ + int i; + + i = 0; + while (i < N_ELEMENTS (schemes)) { + int j; + + j = 0; + while (j < MAX_DIRECTORIES) { + g_free (schemes[i].directories[j]); + schemes[i].directories[j] = NULL; + ++j; + } + + ++i; + } +} + + + +static const SchemeDescription* +get_desc_for_uri (GnomeVFSURI *desktop_uri) +{ + const SchemeDescription *desc; + int i; + const char *scheme; + + scheme = gnome_vfs_uri_get_scheme (desktop_uri); + + desc = NULL; + i = 0; + while (i < N_ELEMENTS (schemes)) { + if (strcmp (schemes[i].scheme, scheme) == 0) { + desc = &schemes[i]; + break; + } + + ++i; + } + + return desc; +} + +static GnomeVFSURI* +desktop_uri_to_file_uri (GnomeVFSURI *desktop_uri) +{ + const SchemeDescription *desc; + GnomeVFSURI *new_uri; + const char *path; + int i; + + desc = get_desc_for_uri (desktop_uri); + + if (desc == NULL) { + gnome_vfs_uri_ref (desktop_uri); + return desktop_uri; + } + + /* Prepend the base for the desktop URI. + * If the SchemeDescription contains > 1 directory, we use the directory + * after the first if the given file actually exists there. + */ + new_uri = NULL; + path = gnome_vfs_uri_get_path (desktop_uri); + i = 0; + while (desc->directories[i]) + ++i; + do { + char *s; + + --i; + + s = create_file_uri_in_dir (desc->directories[i], path); + + new_uri = gnome_vfs_uri_new (s); + + g_free (s); + + if (i == 0 || + gnome_vfs_uri_exists (new_uri)) { + return new_uri; + } else { + gnome_vfs_uri_unref (new_uri); + new_uri = NULL; + } + } while (i > 0); + + + g_assert_not_reached (); + + return NULL; +} + + +static GnomeVFSResult +open_merged_directory (GnomeVFSMethod *method, + GnomeVFSMethodHandle **method_handle, + GnomeVFSURI *desktop_uri, + GnomeVFSFileInfoOptions options, + GnomeVFSContext *context) +{ + GnomeVFSResult result; + DirHandle *dh; + const SchemeDescription *desc; + int i; + gboolean found; + const char *path; + + desc = get_desc_for_uri (desktop_uri); + + if (desc == NULL) { + return GNOME_VFS_ERROR_NOT_FOUND; + } + + dh = g_new0 (DirHandle, 1); + + /* Prepend the base for the desktop URI. + * If the SchemeDescription contains > 1 directory, we use the directory + * after the first if the given file actually exists there. + */ + found = FALSE; + path = gnome_vfs_uri_get_path (desktop_uri); + i = 0; + while (desc->directories[i]) { + char *s; + GnomeVFSURI *file_uri; + GnomeVFSMethodHandle *parent_handle = NULL; + + s = create_file_uri_in_dir (desc->directories[i], path); + + file_uri = gnome_vfs_uri_new (s); + + g_free (s); + + result = (* parent_method->open_directory) (parent_method, + &parent_handle, + file_uri, + options, + context); + + if (result == GNOME_VFS_OK) { + found = TRUE; + dh->handles = g_slist_prepend (dh->handles, parent_handle); + } + + gnome_vfs_uri_unref (file_uri); + + ++i; + } + + dh->next = dh->handles; + + *method_handle = (GnomeVFSMethodHandle*) dh; + + return found ? GNOME_VFS_OK : GNOME_VFS_ERROR_NOT_FOUND; +} + + +static char* +create_file_uri_in_dir (const char *dir, + const char *filename) +{ + char *dir_uri; + char *retval; + + dir_uri = gnome_vfs_get_uri_from_local_path (dir); + + retval = g_strconcat (dir_uri, "/", filename, NULL); + + g_free (dir_uri); + + return retval; +} + + + + + diff --git a/gnome-vfs-1.9.16-moved-menu-files.patch b/gnome-vfs-1.9.16-moved-menu-files.patch new file mode 100644 index 0000000..d7ea0f2 --- /dev/null +++ b/gnome-vfs-1.9.16-moved-menu-files.patch @@ -0,0 +1,14 @@ +--- gnome-vfs-1.9.16/modules/vfolder-desktop-method.c.moved-menu-files Tue Jun 11 17:39:20 2002 ++++ gnome-vfs-1.9.16/modules/vfolder-desktop-method.c Tue Jun 11 17:43:17 2002 +@@ -1433,9 +1433,8 @@ + + info->scheme = g_strdup (scheme); + +- info->filename = g_strconcat (SYSCONFDIR, +- "/gnome-vfs-2.0/vfolders/", +- scheme, ".vfolder-info", ++ info->filename = g_strconcat (SYSCONFDIR, "/X11/desktop-menus/", ++ scheme, ".menu", + NULL); + info->user_filename = g_strconcat (g_get_home_dir (), + "/" DOT_GNOME "/vfolders/", diff --git a/gnome-vfs-2.0.1-only-show-in.patch b/gnome-vfs-2.0.1-only-show-in.patch new file mode 100644 index 0000000..9546462 --- /dev/null +++ b/gnome-vfs-2.0.1-only-show-in.patch @@ -0,0 +1,13 @@ +--- gnome-vfs-2.0.1/modules/vfolder-desktop-method.c.only-show-in Tue Jul 16 20:11:46 2002 ++++ gnome-vfs-2.0.1/modules/vfolder-desktop-method.c Tue Jul 16 20:12:21 2002 +@@ -2466,8 +2466,8 @@ + + *result = g_strdup (p); + +- if (*result1 == NULL || +- (result2 != NULL && *result2 == NULL)) ++ if (*result1 != NULL && ++ (result2 == NULL || *result2 != NULL)) + break; + } + diff --git a/gnome-vfs-2.0.2-newstat.patch b/gnome-vfs-2.0.2-newstat.patch new file mode 100644 index 0000000..7c90801 --- /dev/null +++ b/gnome-vfs-2.0.2-newstat.patch @@ -0,0 +1,15 @@ +--- gnome-vfs-2.0.2/modules/vfolder-desktop-method.c.newstat 2002-08-19 12:45:42.000000000 -0400 ++++ gnome-vfs-2.0.2/modules/vfolder-desktop-method.c 2002-08-19 14:49:27.000000000 -0400 +@@ -3353,6 +3353,12 @@ + invalidate_folder_T (info->root); + + info->entries_valid = TRUE; ++ ++ /* Update modification time of all folders, ++ * kind of evil, but it will make adding new items work ++ * I hope. This is because rereading usually means ++ * something changed */ ++ info->modification_time = time (NULL); + } + return info; + } diff --git a/gnome-vfs-2.0.2-read-only.patch b/gnome-vfs-2.0.2-read-only.patch new file mode 100644 index 0000000..4ac6802 --- /dev/null +++ b/gnome-vfs-2.0.2-read-only.patch @@ -0,0 +1,52 @@ +--- gnome-vfs-2.0.2/modules/vfolder-desktop-method.c.read-only 2002-08-23 11:45:03.000000000 -0400 ++++ gnome-vfs-2.0.2/modules/vfolder-desktop-method.c 2002-08-23 12:09:27.000000000 -0400 +@@ -1162,7 +1162,8 @@ + folder->entry.type = ENTRY_FOLDER; + folder->entry.name = g_strdup (name); + folder->entry.refcount = 1; +- ++ folder->read_only = TRUE; ++ + return folder; + } + +@@ -6084,28 +6085,28 @@ + static GnomeVFSMethod method = { + sizeof (GnomeVFSMethod), + do_open, +- do_create, ++ NULL, /* do_create, */ + do_close, + do_read, +- do_write, ++ NULL, /* do_write, */ + do_seek, + do_tell, +- do_truncate_handle, ++ NULL, /* do_truncate_handle, */ + do_open_directory, + do_close_directory, + do_read_directory, + do_get_file_info, + do_get_file_info_from_handle, + do_is_local, +- do_make_directory, +- do_remove_directory, +- do_move, +- do_unlink, ++ NULL, /* do_make_directory, */ ++ NULL, /* do_remove_directory, */ ++ NULL, /* do_move, */ ++ NULL, /* do_unlink, */ + do_check_same_fs, +- do_set_file_info, +- do_truncate, +- NULL /* find_directory */, +- NULL /* create_symbolic_link */, ++ NULL, /* do_set_file_info, */ ++ NULL, /* do_truncate, */ ++ NULL /* find_directory */, ++ NULL /* create_symbolic_link */, + do_monitor_add, + do_monitor_cancel + }; diff --git a/gnome-vfs-2.1.6-hide-with-empty-subfolders.patch b/gnome-vfs-2.1.6-hide-with-empty-subfolders.patch new file mode 100644 index 0000000..ea5b1ad --- /dev/null +++ b/gnome-vfs-2.1.6-hide-with-empty-subfolders.patch @@ -0,0 +1,33 @@ +--- gnome-vfs-2.1.6/modules/vfolder-desktop-method.c.hide-with-empty-subfolders 2003-01-13 16:08:37.000000000 -0500 ++++ gnome-vfs-2.1.6/modules/vfolder-desktop-method.c 2003-01-13 16:09:21.000000000 -0500 +@@ -937,8 +937,19 @@ + /* Include subfolders */ + /* we always whack them onto the beginning */ + if (folder->subfolders != NULL) { +- GSList *subfolders = g_slist_copy (folder->subfolders); +- g_slist_foreach (subfolders, (GFunc)entry_ref_alloc, NULL); ++ GSList *li; ++ GSList *subfolders; ++ subfolders = NULL; ++ li = folder->subfolders; ++ for (li = folder->subfolders; li != NULL; li = li->next) { ++ Folder *f = li->data; ++ /* always dont_show_if_empty */ ++ if (f->entries != NULL) { ++ entry_ref_alloc (&f->entry); ++ subfolders = g_slist_prepend (subfolders, f); ++ } ++ } ++ subfolders = g_slist_reverse (subfolders); + folder->entries = g_slist_concat (subfolders, folder->entries); + } + +@@ -1079,7 +1090,7 @@ + GHashTable *entry_hash; + + ensure_folder (info, folder, +- FALSE /* subfolders */, ++ TRUE /* subfolders */, + NULL /* except */, + FALSE /* ignore_unallocated */); + if (folder->sorted) diff --git a/gnome-vfs-2.1.6-never-show-if-empty.patch b/gnome-vfs-2.1.6-never-show-if-empty.patch new file mode 100644 index 0000000..126a869 --- /dev/null +++ b/gnome-vfs-2.1.6-never-show-if-empty.patch @@ -0,0 +1,10 @@ +--- gnome-vfs-2.1.6/modules/vfolder-desktop-method.c.never-show-if-empty 2003-01-11 18:12:50.000000000 -0500 ++++ gnome-vfs-2.1.6/modules/vfolder-desktop-method.c 2003-01-11 18:12:55.000000000 -0500 +@@ -1163,6 +1163,7 @@ + folder->entry.name = g_strdup (name); + folder->entry.refcount = 1; + folder->read_only = TRUE; ++ folder->dont_show_if_empty = TRUE; + + return folder; + } diff --git a/gnome-vfs-2.1.6-no-private-methods.patch b/gnome-vfs-2.1.6-no-private-methods.patch new file mode 100644 index 0000000..e7fd1da --- /dev/null +++ b/gnome-vfs-2.1.6-no-private-methods.patch @@ -0,0 +1,32 @@ +--- gnome-vfs-2.1.6/modules/desktop-method.c.no-private-methods 2003-01-11 19:16:29.000000000 -0500 ++++ gnome-vfs-2.1.6/modules/desktop-method.c 2003-01-11 19:37:23.000000000 -0500 +@@ -627,12 +627,12 @@ + gnome_vfs_uri_ref (uri); + + file_uri = desktop_uri_to_file_uri (uri); +- result = gnome_vfs_monitor_do_add (parent_method, +- &monitor_handle->handle, +- file_uri, +- monitor_type, +- monitor_notify_cb, +- monitor_handle); ++ result = _gnome_vfs_monitor_do_add (parent_method, ++ &monitor_handle->handle, ++ file_uri, ++ monitor_type, ++ monitor_notify_cb, ++ monitor_handle); + gnome_vfs_uri_unref (file_uri); + + if (result != GNOME_VFS_OK) { +@@ -654,8 +654,8 @@ + + monitor_handle = (DesktopMonitorHandle *) method_handle; + +- result = gnome_vfs_monitor_do_cancel (monitor_handle->handle); +- ++ result = _gnome_vfs_monitor_do_cancel (monitor_handle->handle); ++ + gnome_vfs_uri_unref (monitor_handle->desktop_uri); + g_free (monitor_handle); + diff --git a/gnome-vfs-2.1.6-old-modules.patch b/gnome-vfs-2.1.6-old-modules.patch new file mode 100644 index 0000000..90b2565 --- /dev/null +++ b/gnome-vfs-2.1.6-old-modules.patch @@ -0,0 +1,30 @@ +--- gnome-vfs-2.1.6/modules/Makefile.am.old-modules 2003-01-11 16:28:30.000000000 -0500 ++++ gnome-vfs-2.1.6/modules/Makefile.am 2003-01-11 16:30:00.000000000 -0500 +@@ -60,6 +60,8 @@ + $(CDEMENU_LTLIBS) \ + libssh.la \ + libtar.la \ ++ libdesktop.la \ ++ libvfolder-desktop-old.la \ + $(NULL) + + # Not currently supported +@@ -142,6 +144,18 @@ + libftp_la_LDFLAGS = $(module_flags) + libftp_la_LIBADD = ../libgnomevfs/libgnomevfs-2.la + ++### `desktop' method ++ ++libdesktop_la_SOURCES = desktop-method.c ++libdesktop_la_LDFLAGS = $(module_flags) ++libdesktop_la_LIBADD = ../libgnomevfs/libgnomevfs-2.la ++ ++### `vfolder-desktop' method ++ ++libvfolder_desktop_old_la_SOURCES = vfolder-desktop-method.c ++libvfolder_desktop_old_la_LDFLAGS = $(module_flags) ++libvfolder_desktop_old_la_LIBADD = ../libgnomevfs/libgnomevfs-2.la ++ + ### `nfs' method + + #libnfs_la_SOURCES = \ diff --git a/gnome-vfs2.spec b/gnome-vfs2.spec index 35f6806..c8a98fd 100644 --- a/gnome-vfs2.spec +++ b/gnome-vfs2.spec @@ -1,18 +1,20 @@ -%define libbonobo_version 2.1.0 +%define libbonobo_version 2.2.0 %define gconf2_version 1.2.0 %define gtkdoc_version 0.9 %define gnome_mime_data_version 2.0.0-11 -%define redhat_menus_version 0.2 +%define redhat_menus_version 0.30 %define po_package gnome-vfs-2.0 Summary: The GNOME virtual file-system libraries. Name: gnome-vfs2 -Version: 2.1.3.1 -Release: 1 +Version: 2.2.2 +Release: 4 License: LGPL Group: System Environment/Libraries Source: gnome-vfs-%{version}.tar.bz2 +Source2: vfolder-desktop-method.c +Source3: desktop-method.c URL: http://www.gnome.org/ BuildRoot: %{_tmppath}/%{name}-%{version}-root Requires: gnome-mime-data >= %{gnome_mime_data_version} @@ -26,11 +28,31 @@ BuildRequires: pkgconfig BuildRequires: gnome-mime-data >= %{gnome_mime_data_version} BuildRequires: /usr/bin/automake-1.4 BuildRequires: gtk-doc >= %{gtkdoc_version} +Prereq: GConf2 >= %{gconf2_version} + Patch1: gnome-vfs-1.9.4.91-docdir.patch Patch2: gnome-vfs-2.1.3-moved-menu-files.patch Patch3: gnome-vfs-2.0.4-onlyshowin.patch +Patch5: gnome-vfs-1.9.16-moved-menu-files.patch +Patch6: gnome-vfs-2.0.1-only-show-in.patch +Patch7: gnome-vfs-2.1.6-modules-conf.patch +Patch8: gnome-vfs-2.0.2-newstat.patch +Patch9: gnome-vfs-2.0.2-read-only.patch + +## this is the automake-requiring patch +Patch10: gnome-vfs-2.1.6-old-modules.patch + +Patch11: gnome-vfs-2.1.6-network-uri.patch +Patch12: gnome-vfs-2.1.6-never-show-if-empty.patch +Patch13: gnome-vfs-2.1.6-hide-with-empty-subfolders.patch +Patch14: gnome-vfs-2.1.6-no-private-methods.patch + +Patch15: gnome-vfs-2.2.0-vfolder-hacks.patch +Patch16: gnome-vfs-2.2.2-notify-race.patch +Patch17: gnome-vfs-2.2.2-multiple-auth.patch + %description GNOME VFS is the GNOME virtual file system. It is the foundation of the Nautilus file manager. It provides a modular architecture and @@ -54,40 +76,80 @@ GNOME VFS modules and applications that use the GNOME VFS APIs. %prep %setup -q -n gnome-vfs-%{version} + +cp -f %{SOURCE2} modules +cp -f %{SOURCE3} modules + # Patch1: gnome-vfs-1.9.4.91-docdir.patch modifies doc/Makefile.am #%patch1 -p1 -b .docdir -%patch2 -p1 -b .moved-menu-files -%patch3 -p0 -b .onlyshowin +(cd modules && cp default-modules.conf default-modules.conf.with-menu-editing) + +## clean out the methods that don't work with our setup +DMC=modules/default-modules.conf.with-menu-editing +perl -pi -e 's/all-applications:\s+libvfolder-desktop.so//g' $DMC +perl -pi -e 's/all-preferences:\s+libvfolder-desktop.so//g' $DMC +perl -pi -e 's/favorites:\s+libvfolder-desktop.so//g' $DMC +perl -pi -e 's/network:\s+libvfolder-desktop.so//g' $DMC + +## these two should actually work +## perl -pi -e 's/applications-all-users:\s+libvfolder-desktop.so//g' $DMC +## perl -pi -e 's/preferences-all-users:\s+libvfolder-desktop.so//g' $DMC + +## these are now in the vfolder-hacks patch +#%patch2 -p1 -b .moved-menu-files +#%patch3 -p0 -b .onlyshowin + +%patch5 -p1 -b .moved-menu-files +%patch6 -p1 -b .only-show-in +%patch7 -p1 -b .modules-conf +%patch8 -p1 -b .newstat +%patch9 -p1 -b .read-only +%patch10 -p1 -b .old-modules +%patch11 -p1 -b .network-uri +%patch12 -p1 -b .never-show-if-empty +%patch13 -p1 -b .hide-with-empty-subfolders +%patch14 -p1 -b .no-private-methods + +%patch15 -p0 -b .vfolder-hacks +%patch16 -p0 -b .notify-race +%patch17 -p0 -b .multiple-auth %build -# patch1 is disabled, so we don't need this -#automake-1.4 +# needed for patch10 (changing makefile to old vfolder backend) +automake-1.4 +if pkg-config openssl ; then + CPPFLAGS=`pkg-config --cflags openssl`; export CPPFLAGS + LDFLAGS=`pkg-config --libs-only-L openssl`; export LDFLAGS +fi %configure -make +make LIBTOOL=/usr/bin/libtool %install rm -fr %{buildroot} export GCONF_DISABLE_MAKEFILE_SCHEMA_INSTALL=1 -%makeinstall +%makeinstall LIBTOOL=/usr/bin/libtool unset GCONF_DISABLE_MAKEFILE_SCHEMA_INSTALL rm -f $RPM_BUILD_ROOT%{_libdir}/gnome-vfs-2.0/modules/lib*.a rm -f $RPM_BUILD_ROOT%{_libdir}/*.la -## work around makefile glitch in modules/extfs/Makefile.am -if test "$RPM_BUILD_ROOT%{_prefix}/lib" != "$RPM_BUILD_ROOT%{_libdir}"; then - mv $RPM_BUILD_ROOT%{_prefix}/lib/vfs $RPM_BUILD_ROOT%{_libdir} -fi - rm -f $RPM_BUILD_ROOT%{_sysconfdir}/gnome-vfs-2.0/vfolders/*.vfolder-info rm -f $RPM_BUILD_ROOT%{_sysconfdir}/gnome-vfs-2.0/vfolders/*.vfolder-info-default ## why, dear god, why? rm -f $RPM_BUILD_ROOT%{_datadir}/aclocal/gob2.m4 +install -m 644 modules/default-modules.conf.with-menu-editing $RPM_BUILD_ROOT%{_sysconfdir}/gnome-vfs-2.0/modules/ + +## Remove this line and update file list to revert to +## no-menu-editing-by-default +# (cd $RPM_BUILD_ROOT%{_sysconfdir}/gnome-vfs-2.0/modules/; mv default-modules.conf default-modules.conf.without-menu-editing; mv default-modules.conf.with-menu-editing default-modules.conf) + +rm -f $RPM_BUILD_ROOT%{_libdir}/bonobo/monikers/*.{a,la} + %find_lang %{po_package} %clean @@ -111,8 +173,9 @@ done %dir %{_sysconfdir}/gnome-vfs-2.0 %dir %{_sysconfdir}/gnome-vfs-2.0/modules -%dir %{_sysconfdir}/gnome-vfs-2.0/vfolders +#%dir %{_sysconfdir}/gnome-vfs-2.0/vfolders %config %{_sysconfdir}/gnome-vfs-2.0/modules/*.conf +%config %{_sysconfdir}/gnome-vfs-2.0/modules/*.conf.with-menu-editing ## we aren't really using the .vfolder-info config files, ## so installing them is a little misleading. ## %config %{_sysconfdir}/gnome-vfs-2.0/vfolders/*.vfolder-info @@ -136,6 +199,95 @@ done %{_datadir}/gtk-doc %changelog +* Mon Feb 24 2003 Elliot Lee +- debuginfo rebuild + +* Mon Feb 24 2003 Alexander Larsson 2.2.2-3 +- Added patch to fix smb crash (#84982) + +* Mon Feb 24 2003 Alexander Larsson 2.2.2-2 +- Add patch to fix notify race (#84668) + +* Wed Feb 12 2003 Alexander Larsson 2.2.2-1 +- 2.2.2, fixes bad memleak (#83791) + +* Tue Feb 11 2003 Havoc Pennington 2.2.1-3 +- kill menu editing again, it was very very broken + +* Mon Feb 10 2003 Bill Nottingham 2.2.1-2 +- fix file list (#68226) +- prereq GConf2 + +* Mon Feb 10 2003 Alexander Larsson 2.2.1-1 +- Update to 2.2.1. fixes gnome-theme-manager hang + +* Thu Feb 6 2003 Havoc Pennington 2.2.0-7 +- move to menu editing modules.conf by default, we'll see if it works + +* Tue Jan 28 2003 Matt Wilson 2.2.0-6 +- use LIBTOOL=/usr/bin/libtool + +* Mon Jan 27 2003 Havoc Pennington +- it would help to *install* the stupid menu edit conf file +- update the vfolder-hacks patch with some fixes + +* Fri Jan 24 2003 Havoc Pennington +- hack up editable vfolder backend to maybe work + (still not the default config file) + +* Wed Jan 22 2003 Havoc Pennington +- include a non-default config file to use new vfolder method +- build the new vfolder method + +* Wed Jan 22 2003 Tim Powers +- rebuilt + +* Tue Jan 21 2003 Alexander Larsson +- Update to 2.2.0 + +* Mon Jan 13 2003 Havoc Pennington +- variation on the subfolders hack to try to fix it + +* Sat Jan 11 2003 Havoc Pennington +- fix bug where empty folders with empty subfolders would + still be visible. + +* Sat Jan 11 2003 Havoc Pennington +- finish adapting desktop-method.c to underscore-prefixing action + +* Sat Jan 11 2003 Havoc Pennington +- adapt desktop-method.c to underscore-prefixing action + +* Sat Jan 11 2003 Havoc Pennington +- hardcode , it's stupid to ever override this. + +* Sat Jan 11 2003 Havoc Pennington +- make network:/// use libdesktop.so, and modify libdesktop.so + to support it + +* Sat Jan 11 2003 Havoc Pennington +- Revert to George's vfolder backend from gnome-vfs-2.0.2 or so +- put back libdesktop.so +- don't build the new vfolder backend + +* Wed Jan 8 2003 Alexander Larsson 2.1.6-2 +- Removed __libdir fix that was fixed upstream + +* Wed Jan 8 2003 Alexander Larsson 2.1.6-1 +- Update to 2.1.6 + +* Tue Jan 7 2003 Nalin Dahyabhai 2.1.5-3 +- rebuild + +* Mon Dec 16 2002 Tim Powers 2.1.5-2 +- rebuild + +* Mon Dec 16 2002 Alexander Larsson 2.1.5-1 +- Update to 2.1.5 + +* Thu Dec 12 2002 Nalin Dahyabhai +- use OpenSSL's pkg-config information, if available + * Wed Dec 4 2002 Havoc Pennington - 2.1.3.1 diff --git a/sources b/sources index 5dfa1a8..f44bfa8 100644 --- a/sources +++ b/sources @@ -1 +1 @@ -f28f83a450cb2f770f3ec604329b5fe3 gnome-vfs-2.1.3.1.tar.bz2 +02845c284b27d1bed89176b72cb17890 gnome-vfs-2.2.2.tar.bz2 diff --git a/vfolder-desktop-method.c b/vfolder-desktop-method.c new file mode 100644 index 0000000..d945be9 --- /dev/null +++ b/vfolder-desktop-method.c @@ -0,0 +1,6130 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: 8; c-basic-offset: 8 -*- */ + +/* vfolder-desktop-method.c + + Copyright (C) 2001 Red Hat, Inc. + Copyright (C) 2001 The Dark Prince + + The Gnome Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. + + The Gnome Library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with the Gnome Library; see the file COPYING.LIB. If not, + write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. +*/ + +/* URI scheme for reading the "applications:" vfolder and other + * vfolder schemes. Lots of code stolen from the original desktop + * reading URI scheme. + */ + +#ifdef HAVE_CONFIG_H +# include +#endif + +/* Debugging foo: */ +/*#define D(x) x */ +#define D(x) ; + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include + +#define DOT_GNOME ".gnome2" + +typedef struct _VFolderInfo VFolderInfo; +typedef struct _Query Query; +typedef struct _QueryKeyword QueryKeyword; +typedef struct _QueryFilename QueryFilename; +typedef struct _Entry Entry; +typedef struct _Folder Folder; +typedef struct _EntryFile EntryFile; +typedef struct _Keyword Keyword; +typedef struct _FileMonitorHandle FileMonitorHandle; +typedef struct _StatLoc StatLoc; +typedef struct _VFolderURI VFolderURI; + +/* TODO before 2.0: */ +/* FIXME: also check/monitor desktop_dirs like we do the vfolder + * file and the item dirs */ +/* FIXME: check if thread locks are not completely on crack which + * is likely given my experience with threads */ +/* FIXME: use filename locking, currently we are full of races if + * multiple processes write to this filesystem */ +/* FIXME: implement monitors */ + +/* TODO for later (star trek future): */ +/* FIXME: Maybe when chaining to file:, we should call the gnome-vfs wrapper + * functions, instead of the file: methods directly. */ +/* FIXME: related to the above: we should support things being on non + * file: filesystems. Such as having the vfolder info file on http + * somewhere or some such nonsense :) */ + +static GnomeVFSMethod *parent_method = NULL; + +static GHashTable *infos = NULL; + +/* Note: I have no clue about how to write thread safe code and this + * is my first attempt, so it's probably wrong + * -George */ +G_LOCK_DEFINE_STATIC (vfolder_lock); + +/* Note: all keywords are quarks */ +/* Note: basenames are unique */ + +#define UNSUPPORTED_INFO_FIELDS (GNOME_VFS_FILE_INFO_FIELDS_PERMISSIONS | \ + GNOME_VFS_FILE_INFO_FIELDS_DEVICE | \ + GNOME_VFS_FILE_INFO_FIELDS_INODE | \ + GNOME_VFS_FILE_INFO_FIELDS_LINK_COUNT | \ + GNOME_VFS_FILE_INFO_FIELDS_ATIME) + + +enum { + QUERY_OR, + QUERY_AND, + QUERY_KEYWORD, + QUERY_FILENAME +}; + +struct _Query { + int type; + gboolean not; + GSList *queries; +}; + +struct _QueryKeyword { + int type; + gboolean not; + GQuark keyword; +}; + +struct _QueryFilename { + int type; + gboolean not; + char *filename; +}; + +enum { + ENTRY_FILE, + ENTRY_FOLDER +}; + +struct _Entry { + int type; + int refcount; + int alloc; /* not really useful for folders, + but oh well, whatever. It's the number + of times this is queried in some directory, + used for the Unallocated query type */ + char *name; + + GSList *monitors; +}; + +struct _EntryFile { + Entry entry; + + char *filename; + gboolean per_user; + GSList *keywords; + + gboolean implicit_keywords; /* the keywords were added by us */ +}; + +struct _Folder { + Entry entry; + + Folder *parent; + + char *desktop_file; /* the .directory file */ + + Query *query; + + /* The following is for per file + * access */ + /* excluded by filename */ + GHashTable *excludes; + /* included by filename */ + GSList *includes; + GHashTable *includes_ht; + + GSList *subfolders; + + /* Some flags */ + gboolean read_only; + gboolean dont_show_if_empty; + gboolean only_unallocated; /* include only unallocated items */ + + /* lazily done, will run query only when it + * needs to */ + gboolean up_to_date; + gboolean sorted; + GSList *entries; +}; + +struct _StatLoc { + time_t ctime; + time_t last_stat; + gboolean trigger_next; /* if true, next check will fail */ + char name[1]; /* the structure will be long enough to + fit name */ +}; + +struct _VFolderInfo { + char *scheme; + + char *filename; + char *user_filename; + time_t user_filename_last_write; + char *desktop_dir; /* directory with .directorys */ + char *user_desktop_dir; /* directory with .directorys */ + gboolean user_file_active; /* if using user_filename and + not filename */ + + GSList *item_dirs; + char *user_item_dir; /* dir where user changes to + items are stored */ + + /* old style dirs to merge in */ + GSList *merge_dirs; + + /* if entries are valid, else + * they need to be (re)read */ + gboolean entries_valid; + + GSList *entries; + + /* entry hash by basename */ + GHashTable *entries_ht; + + /* The root folder */ + Folder *root; + + /* The unallocated folders, the folder which only + * include unallocated items */ + GSList *unallocated_folders; + + /* some flags */ + gboolean read_only; + + gboolean dirty; + + int inhibit_write; + + /* change monitoring stuff */ + GnomeVFSMonitorHandle *filename_monitor; + GnomeVFSMonitorHandle *user_filename_monitor; + + /* stat locations (in case we aren't monitoring) */ + StatLoc *filename_statloc; + StatLoc *user_filename_statloc; + + /* for .directory dirs */ + /* FIXME: */GnomeVFSMonitorHandle *desktop_dir_monitor; + /* FIXME: */GnomeVFSMonitorHandle *user_desktop_dir_monitor; + + /* stat locations (in case we aren't monitoring) */ + /* FIXME: */StatLoc *desktop_dir_statloc; + /* FIXME: */StatLoc *user_desktop_dir_statloc; + + /* FIXME: */GSList *file_monitors; /* FileMonitorHandle */ + /* FIXME: */GSList *free_file_monitors; /* FileMonitorHandle */ + GSList *folder_monitors; /* FileMonitorHandle */ + GSList *free_folder_monitors; /* FileMonitorHandle */ + + GSList *item_dir_monitors; /* GnomeVFSMonitorHandle */ + + /* item dirs to stat */ + GSList *stat_dirs; + + /* ctime for folders */ + time_t modification_time; + + guint reread_queue; +}; + +struct _FileMonitorHandle { + int refcount; + gboolean exists; + gboolean dir_monitor; /* TRUE if dir, FALSE if file */ + GnomeVFSURI *uri; + GnomeVFSMonitorHandle *handle; /* A real handle if we're monitoring + an actual file here, or NULL */ + char *filename; /* Just the basename, used in the free_file_list */ + gboolean is_directory_file; +}; + +struct _VFolderURI { + const gchar *scheme; + gboolean is_all_scheme; + gboolean ends_in_slash; + gchar *path; + gchar *file; + GnomeVFSURI *uri; +}; + + +static Entry * entry_ref (Entry *entry); +static Entry * entry_ref_alloc (Entry *entry); +static void entry_unref (Entry *entry); +static void entry_unref_dealloc (Entry *entry); +static void query_destroy (Query *query); +static void ensure_folder (VFolderInfo *info, + Folder *folder, + gboolean subfolders, + Folder *except, + gboolean ignore_unallocated); +static void ensure_folder_unlocked (VFolderInfo *info, + Folder *folder, + gboolean subfolders, + Folder *except, + gboolean ignore_unallocated); +/* An EVIL function for quick reading of .desktop files, + * only reads in one or two keys, but that's ALL we need */ +static void readitem_entry (const char *filename, + const char *key1, + char **result1, + const char *key2, + char **result2); +static gboolean vfolder_info_reload (VFolderInfo *info, + GnomeVFSResult *result, + GnomeVFSContext *context, + gboolean force_read_items); +static gboolean vfolder_info_reload_unlocked (VFolderInfo *info, + GnomeVFSResult *result, + GnomeVFSContext *context, + gboolean force_read_items); +static void invalidate_folder_subfolders (Folder *folder, + gboolean lock_taken); +static Folder * resolve_folder (VFolderInfo *info, + const char *path, + gboolean ignore_basename, + GnomeVFSResult *result, + GnomeVFSContext *context); +static gboolean vfolder_info_read_items (VFolderInfo *info, + GnomeVFSResult *result, + GnomeVFSContext *context); + +/* assumes vuri->path already set */ +static gboolean +vfolder_uri_parse_internal (GnomeVFSURI *uri, VFolderURI *vuri) +{ + vuri->scheme = (gchar *) gnome_vfs_uri_get_scheme (uri); + + vuri->ends_in_slash = FALSE; + + if (strncmp (vuri->scheme, "all-", strlen ("all-")) == 0) { + vuri->scheme += strlen ("all-"); + vuri->is_all_scheme = TRUE; + } else + vuri->is_all_scheme = FALSE; + + if (vuri->path != NULL) { + int last_slash = strlen (vuri->path) - 1; + char *first; + + /* Note: This handling of paths is somewhat evil, may need a + * bit of a rework */ + + /* kill leading slashes, that is make sure there is + * only one */ + for (first = vuri->path; *first == '/'; first++) + ; + if (first != vuri->path) { + first--; + vuri->path = first; + } + + /* kill trailing slashes (leave first if all slashes) */ + while (last_slash > 0 && vuri->path [last_slash] == '/') { + vuri->path [last_slash--] = '\0'; + vuri->ends_in_slash = TRUE; + } + + /* get basename start */ + while (last_slash >= 0 && vuri->path [last_slash] != '/') + last_slash--; + + if (last_slash > -1) + vuri->file = vuri->path + last_slash + 1; + else + vuri->file = vuri->path; + + if (vuri->file[0] == '\0' && + strcmp (vuri->path, "/") == 0) { + vuri->file = NULL; + } + } else { + vuri->ends_in_slash = TRUE; + vuri->path = "/"; + vuri->file = NULL; + } + + vuri->uri = uri; + + return TRUE; +} + +#define VFOLDER_URI_PARSE(_uri, _vuri) { \ + gchar *path; \ + path = gnome_vfs_unescape_string ((_uri)->text, G_DIR_SEPARATOR_S); \ + if (path != NULL) { \ + (_vuri)->path = g_alloca (strlen (path) + 1); \ + strcpy ((_vuri)->path, path); \ + g_free (path); \ + } else { \ + (_vuri)->path = NULL; \ + } \ + vfolder_uri_parse_internal ((_uri), (_vuri)); \ +} + +static FileMonitorHandle * +file_monitor_handle_ref_unlocked (FileMonitorHandle *h) +{ + h->refcount ++; + return h; +} + +static void +file_monitor_handle_unref_unlocked (FileMonitorHandle *h) +{ + h->refcount --; + if (h->refcount == 0) { + gnome_vfs_uri_unref (h->uri); + h->uri = NULL; + + g_free (h->filename); + h->filename = NULL; + + if (h->handle != NULL) { + gnome_vfs_monitor_cancel (h->handle); + h->handle = NULL; + } + } +} + +/* This includes the .directory files */ +static void +emit_monitor (Folder *folder, int type) +{ + GSList *li; + for (li = ((Entry *)folder)->monitors; + li != NULL; + li = li->next) { + FileMonitorHandle *handle = li->data; + gnome_vfs_monitor_callback ((GnomeVFSMethodHandle *) handle, + handle->uri, type); + } +} + +static void +emit_file_deleted_monitor (VFolderInfo *info, Entry *entry, Folder *folder) +{ + GSList *li; + for (li = entry->monitors; + li != NULL; + li = li->next) { + VFolderURI vuri; + Folder *f; + GnomeVFSResult result; + FileMonitorHandle *handle = li->data; + + /* Evil! EVIL URI PARSING. this will eat a lot of + * stack if we have lots of monitors */ + + VFOLDER_URI_PARSE (handle->uri, &vuri); + + f = resolve_folder (info, + vuri.path, + TRUE /* ignore_basename */, + &result, + NULL); + + if (f == folder) + gnome_vfs_monitor_callback + ((GnomeVFSMethodHandle *) handle, + handle->uri, + GNOME_VFS_MONITOR_EVENT_DELETED); + } +} + +static void +emit_and_delete_monitor (VFolderInfo *info, Folder *folder) +{ + GSList *li; + for (li = ((Entry *)folder)->monitors; + li != NULL; + li = li->next) { + FileMonitorHandle *handle = li->data; + li->data = NULL; + + gnome_vfs_monitor_callback ((GnomeVFSMethodHandle *) handle, + handle->uri, + GNOME_VFS_MONITOR_EVENT_DELETED); + + if (handle->dir_monitor) + info->free_folder_monitors = + g_slist_prepend (info->free_folder_monitors, + handle); + else + info->free_file_monitors = + g_slist_prepend (info->free_file_monitors, + handle); + } + g_slist_free (((Entry *)folder)->monitors); + ((Entry *)folder)->monitors = NULL; +} + +static gboolean +check_ext (const char *name, const char *ext_check) +{ + const char *ext; + + ext = strrchr (name, '.'); + if (ext == NULL || + strcmp (ext, ext_check) != 0) + return FALSE; + else + return TRUE; +} + +static StatLoc * +bake_statloc (const char *name, + time_t curtime) +{ + struct stat s; + StatLoc *sl = NULL; + if (stat (name, &s) != 0) { + if (errno == ENOENT) { + sl = g_malloc0 (sizeof (StatLoc) + + strlen (name) + 1); + sl->last_stat = curtime; + sl->ctime = 0; + sl->trigger_next = FALSE; + strcpy (sl->name, name); + } + return sl; + } + + sl = g_malloc0 (sizeof (StatLoc) + + strlen (name) + 1); + sl->last_stat = curtime; + sl->ctime = s.st_ctime; + sl->trigger_next = FALSE; + strcpy (sl->name, name); + + return sl; +} + +/* returns FALSE if we must reread */ +static gboolean +check_statloc (StatLoc *sl, + time_t curtime) +{ + struct stat s; + + if (sl->trigger_next) { + sl->trigger_next = FALSE; + return FALSE; + } + + /* don't stat more then once every 3 seconds */ + if (curtime <= sl->last_stat + 3) + return TRUE; + + sl->last_stat = curtime; + + if (stat (sl->name, &s) != 0) { + if (errno == ENOENT && + sl->ctime == 0) + return TRUE; + else + return FALSE; + } + + if (sl->ctime == s.st_ctime) + return TRUE; + else + return FALSE; +} + +static gboolean +ensure_dir (const char *dirname, gboolean ignore_basename) +{ + char *parsed, *p; + + if (dirname == NULL) + return FALSE; + + if (ignore_basename) + parsed = g_path_get_dirname (dirname); + else + parsed = g_strdup (dirname); + + if (g_file_test (parsed, G_FILE_TEST_IS_DIR)) { + g_free (parsed); + return TRUE; + } + + p = strchr (parsed, '/'); + if (p == parsed) + p = strchr (p+1, '/'); + + while (p != NULL) { + *p = '\0'; + if (mkdir (parsed, 0700) != 0 && + errno != EEXIST) { + g_free (parsed); + return FALSE; + } + *p = '/'; + p = strchr (p+1, '/'); + } + + if (mkdir (parsed, 0700) != 0 && + errno != EEXIST) { + g_free (parsed); + return FALSE; + } + + g_free (parsed); + return TRUE; +} + +/* check for any directory name other then root */ +static gboolean +any_subdir (const char *dirname) +{ + const char *p; + if (dirname == NULL) + return FALSE; + + for (p = dirname; *p != '\0'; p++) { + if (*p != '/') { + return TRUE; + } + } + return FALSE; +} + +static void +destroy_entry_file (EntryFile *efile) +{ + if (efile == NULL) + return; + + g_free (efile->filename); + efile->filename = NULL; + + g_slist_free (efile->keywords); + efile->keywords = NULL; + + g_free (efile); +} + +static void +destroy_folder (Folder *folder) +{ + GSList *list; + + if (folder == NULL) + return; + + if (folder->parent != NULL) { + folder->parent->subfolders = + g_slist_remove (folder->parent->subfolders, folder); + folder->parent->up_to_date = FALSE; + folder->parent = NULL; + } + + g_free (folder->desktop_file); + folder->desktop_file = NULL; + + query_destroy (folder->query); + folder->query = NULL; + + if (folder->excludes != NULL) { + g_hash_table_destroy (folder->excludes); + folder->excludes = NULL; + } + + g_slist_foreach (folder->includes, (GFunc)g_free, NULL); + g_slist_free (folder->includes); + folder->includes = NULL; + if (folder->includes_ht != NULL) { + g_hash_table_destroy (folder->includes_ht); + folder->includes_ht = NULL; + } + + list = folder->subfolders; + folder->subfolders = NULL; + g_slist_foreach (list, (GFunc)entry_unref, NULL); + g_slist_free (list); + + list = folder->entries; + folder->entries = NULL; + g_slist_foreach (list, (GFunc)entry_unref, NULL); + g_slist_free (list); + + g_free (folder); +} + +static Entry * +entry_ref (Entry *entry) +{ + if (entry != NULL) + entry->refcount++; + return entry; +} + +static Entry * +entry_ref_alloc (Entry *entry) +{ + entry_ref (entry); + + if (entry != NULL) + entry->alloc++; + + return entry; +} + +static void +entry_unref (Entry *entry) +{ + if (entry == NULL) + return; + + entry->refcount--; + + if (entry->refcount == 0) { + g_free (entry->name); + entry->name = NULL; + + g_slist_foreach (entry->monitors, + (GFunc)file_monitor_handle_unref_unlocked, + NULL); + g_slist_free (entry->monitors); + entry->monitors = NULL; + + if (entry->type == ENTRY_FILE) + destroy_entry_file ((EntryFile *)entry); + else /* ENTRY_FOLDER */ + destroy_folder ((Folder *)entry); + } +} + +static void +entry_unref_dealloc (Entry *entry) +{ + if (entry != NULL) { + entry->alloc --; + entry_unref (entry); + } +} + +/* Handles ONLY files, not dirs */ +/* Also allocates the entries as well as refs them */ +static GSList * +alloc_entries_from_files (VFolderInfo *info, GSList *filenames) +{ + GSList *li; + GSList *files; + + files = NULL; + for (li = filenames; li != NULL; li = li->next) { + char *filename = li->data; + GSList *entry_list = g_hash_table_lookup (info->entries_ht, filename); + if (entry_list != NULL) + files = g_slist_prepend (files, + entry_ref_alloc (entry_list->data)); + } + + return files; +} + +static gboolean +matches_query (VFolderInfo *info, + Folder *folder, + EntryFile *efile, + Query *query) +{ + GSList *li; + + if (query == NULL) + return TRUE; + +#define INVERT_IF_NEEDED(val) (query->not ? !(val) : (val)) + switch (query->type) { + case QUERY_OR: + for (li = query->queries; li != NULL; li = li->next) { + Query *subquery = li->data; + if (matches_query (info, folder, efile, subquery)) + return INVERT_IF_NEEDED (TRUE); + } + return INVERT_IF_NEEDED (FALSE); + case QUERY_AND: + for (li = query->queries; li != NULL; li = li->next) { + Query *subquery = li->data; + if ( ! matches_query (info, folder, efile, subquery)) + return INVERT_IF_NEEDED (FALSE); + } + return INVERT_IF_NEEDED (TRUE); + case QUERY_KEYWORD: + { + QueryKeyword *qkeyword = (QueryKeyword *)query; + for (li = efile->keywords; li != NULL; li = li->next) { + GQuark keyword = GPOINTER_TO_INT (li->data); + if (keyword == qkeyword->keyword) + return INVERT_IF_NEEDED (TRUE); + } + return INVERT_IF_NEEDED (FALSE); + } + case QUERY_FILENAME: + { + QueryFilename *qfilename = (QueryFilename *)query; + if (strcmp (qfilename->filename, ((Entry *)efile)->name) == 0) { + return INVERT_IF_NEEDED (TRUE); + } else { + return INVERT_IF_NEEDED (FALSE); + } + } + } +#undef INVERT_IF_NEEDED + g_assert_not_reached (); + /* huh? */ + return FALSE; +} + +static void +dump_unallocated_folders (Folder *folder) +{ + GSList *li; + for (li = folder->subfolders; li != NULL; li = li->next) + dump_unallocated_folders (li->data); + + if (folder->only_unallocated && + folder->entries != NULL) { + g_slist_foreach (folder->entries, + (GFunc)entry_unref_dealloc, NULL); + g_slist_free (folder->entries); + folder->entries = NULL; + } +} + +/* Run query, allocs and refs the entries */ +static void +append_query (VFolderInfo *info, Folder *folder) +{ + GSList *li; + + if (folder->query == NULL && + ! folder->only_unallocated) + return; + + if (folder->only_unallocated) { + /* dump all folders that use unallocated + * items only. This sucks if you keep + * reading one and then another such + * folder, but oh well, life sucks for + * you then, but at least you get + * consistent results */ + dump_unallocated_folders (info->root); + + /* ensure all other folders, so that + * after this we know which ones are + * unallocated */ + ensure_folder_unlocked (info, + info->root, + TRUE /* subfolders */, + folder /* except */, + /* avoid infinite loops */ + TRUE /* ignore_unallocated */); + } + + for (li = info->entries; li != NULL; li = li->next) { + Entry *entry = li->data; + + if (/* if not file */ + entry->type != ENTRY_FILE || + /* if already included */ + (folder->includes_ht != NULL && + g_hash_table_lookup (folder->includes_ht, + entry->name) != NULL)) + continue; + + if (folder->only_unallocated && + entry->alloc != 0) + continue; + + if (matches_query (info, folder, (EntryFile *)entry, + folder->query)) + folder->entries = g_slist_prepend + (folder->entries, entry_ref_alloc (entry)); + } +} + +/* get entries in folder */ +/* FIXME: support cancellation here */ +static void +ensure_folder_unlocked (VFolderInfo *info, + Folder *folder, + gboolean subfolders, + Folder *except, + gboolean ignore_unallocated) +{ + if (subfolders) { + GSList *li; + for (li = folder->subfolders; li != NULL; li = li->next) + ensure_folder_unlocked (info, li->data, subfolders, + except, ignore_unallocated); + } + + if (except == folder) + return; + + if (ignore_unallocated && + folder->only_unallocated) + return; + + if (folder->up_to_date) + return; + + if (folder->entries != NULL) { + g_slist_foreach (folder->entries, + (GFunc)entry_unref_dealloc, NULL); + g_slist_free (folder->entries); + folder->entries = NULL; + } + + /* Include includes */ + folder->entries = alloc_entries_from_files (info, folder->includes); + + /* Run query */ + append_query (info, folder); + + /* We were prepending all this time */ + folder->entries = g_slist_reverse (folder->entries); + + /* Include subfolders */ + /* we always whack them onto the beginning */ + if (folder->subfolders != NULL) { + GSList *subfolders = g_slist_copy (folder->subfolders); + g_slist_foreach (subfolders, (GFunc)entry_ref_alloc, NULL); + folder->entries = g_slist_concat (subfolders, folder->entries); + } + + /* Exclude excludes */ + if (folder->excludes != NULL) { + GSList *li; + GSList *entries = folder->entries; + folder->entries = NULL; + for (li = entries; li != NULL; li = li->next) { + Entry *entry = li->data; + if (g_hash_table_lookup (folder->excludes, entry->name) == NULL) + folder->entries = g_slist_prepend (folder->entries, entry); + else + entry_unref_dealloc (entry); + + } + g_slist_free (entries); + + /* to preserve the Folders then everything else order */ + folder->entries = g_slist_reverse (folder->entries); + } + + folder->up_to_date = TRUE; + /* not yet sorted */ + folder->sorted = FALSE; +} + +static void +ensure_folder (VFolderInfo *info, + Folder *folder, + gboolean subfolders, + Folder *except, + gboolean ignore_unallocated) +{ + G_LOCK (vfolder_lock); + ensure_folder_unlocked (info, folder, subfolders, except, ignore_unallocated); + G_UNLOCK (vfolder_lock); +} + +static char * +get_directory_file_unlocked (VFolderInfo *info, Folder *folder) +{ + char *filename; + + /* FIXME: cache dir_files */ + + if (folder->desktop_file == NULL) + return NULL; + + if (folder->desktop_file[0] == G_DIR_SEPARATOR) + return g_strdup (folder->desktop_file); + + /* Now try the user directory */ + if (info->user_desktop_dir != NULL) { + filename = g_build_filename (info->user_desktop_dir, + folder->desktop_file, + NULL); + if (access (filename, F_OK) == 0) { + return filename; + } + + g_free (filename); + } + + filename = g_build_filename (info->desktop_dir, folder->desktop_file, NULL); + if (access (filename, F_OK) == 0) { + return filename; + } + g_free (filename); + + return NULL; +} + +static char * +get_directory_file (VFolderInfo *info, Folder *folder) +{ + char *ret; + + G_LOCK (vfolder_lock); + ret = get_directory_file_unlocked (info, folder); + G_UNLOCK (vfolder_lock); + + return ret; +} + +static GSList * +get_sort_order (VFolderInfo *info, Folder *folder) +{ + GSList *list; + char **parsed; + char *order; + int i; + char *filename; + + filename = get_directory_file_unlocked (info, folder); + if (filename == NULL) + return NULL; + + order = NULL; + readitem_entry (filename, + "SortOrder", + &order, + NULL, + NULL); + g_free (filename); + + if (order == NULL) + return NULL; + + parsed = g_strsplit (order, ":", -1); + + g_free (order); + + list = NULL; + for (i = 0; parsed[i] != NULL; i++) { + char *word = parsed[i]; + /* steal */ + parsed[i] = NULL; + /* ignore empty */ + if (word[0] == '\0') { + g_free (word); + continue; + } + list = g_slist_prepend (list, word); + } + /* we've stolen all strings from it */ + g_free (parsed); + + return g_slist_reverse (list); +} + +/* get entries in folder */ +static void +ensure_folder_sort (VFolderInfo *info, Folder *folder) +{ + GSList *li, *sort_order; + GSList *entries; + GHashTable *entry_hash; + + ensure_folder (info, folder, + FALSE /* subfolders */, + NULL /* except */, + FALSE /* ignore_unallocated */); + if (folder->sorted) + return; + + G_LOCK (vfolder_lock); + + sort_order = get_sort_order (info, folder); + if (sort_order == NULL) { + folder->sorted = TRUE; + G_UNLOCK (vfolder_lock); + return; + } + + entries = folder->entries; + folder->entries = NULL; + + entry_hash = g_hash_table_new (g_str_hash, g_str_equal); + for (li = entries; li != NULL; li = li->next) { + Entry *entry = li->data; + g_hash_table_insert (entry_hash, entry->name, li); + } + + for (li = sort_order; li != NULL; li = li->next) { + char *word = li->data; + GSList *entry_list; + Entry *entry; + + /* we kill the words here */ + li->data = NULL; + + entry_list = g_hash_table_lookup (entry_hash, word); + g_free (word); + + if (entry_list == NULL) + continue; + + entry = entry_list->data; + + entries = g_slist_delete_link (entries, entry_list); + + folder->entries = g_slist_prepend (folder->entries, + entry); + } + + /* put on those that weren't mentioned in the sort */ + for (li = entries; li != NULL; li = li->next) { + Entry *entry = li->data; + + folder->entries = g_slist_prepend (folder->entries, + entry); + } + + g_hash_table_destroy (entry_hash); + g_slist_free (entries); + g_slist_free (sort_order); + + folder->sorted = TRUE; + + G_UNLOCK (vfolder_lock); +} + +static EntryFile * +file_new (const char *name) +{ + EntryFile *efile = g_new0 (EntryFile, 1); + + efile->entry.type = ENTRY_FILE; + efile->entry.name = g_strdup (name); + efile->entry.refcount = 1; + + return efile; +} + +static Folder * +folder_new (const char *name) +{ + Folder *folder = g_new0 (Folder, 1); + + folder->entry.type = ENTRY_FOLDER; + folder->entry.name = g_strdup (name); + folder->entry.refcount = 1; + + return folder; +} + +static Query * +query_new (int type) +{ + Query *query; + + if (type == QUERY_KEYWORD) + query = (Query *)g_new0 (QueryKeyword, 1); + else if (type == QUERY_FILENAME) + query = (Query *)g_new0 (QueryFilename, 1); + else + query = g_new0 (Query, 1); + + query->type = type; + + return query; +} + +static void +query_destroy (Query *query) +{ + if (query == NULL) + return; + + if (query->type == QUERY_FILENAME) { + QueryFilename *qfile = (QueryFilename *)query; + g_free (qfile->filename); + qfile->filename = NULL; + } else if (query->type == QUERY_OR || + query->type == QUERY_AND) { + g_slist_foreach (query->queries, (GFunc)query_destroy, NULL); + g_slist_free (query->queries); + query->queries = NULL; + } + + g_free (query); +} + +static void +add_folder_monitor_unlocked (VFolderInfo *info, + FileMonitorHandle *handle) +{ + VFolderURI vuri; + GnomeVFSResult result; + Folder *folder; + + VFOLDER_URI_PARSE (handle->uri, &vuri); + + file_monitor_handle_ref_unlocked (handle); + + info->folder_monitors = + g_slist_prepend (info->folder_monitors, handle); + + folder = resolve_folder (info, + vuri.path, + FALSE /* ignore_basename */, + &result, + NULL); + + if (folder == NULL) { + file_monitor_handle_ref_unlocked (handle); + + info->free_folder_monitors = + g_slist_prepend (info->free_folder_monitors, handle); + + if (handle->exists) { + handle->exists = FALSE; + gnome_vfs_monitor_callback + ((GnomeVFSMethodHandle *)handle, + handle->uri, + GNOME_VFS_MONITOR_EVENT_DELETED); + } + } else { + file_monitor_handle_ref_unlocked (handle); + + ((Entry *)folder)->monitors = + g_slist_prepend (((Entry *)folder)->monitors, handle); + + if ( ! handle->exists) { + handle->exists = TRUE; + gnome_vfs_monitor_callback + ((GnomeVFSMethodHandle *)handle, + handle->uri, + GNOME_VFS_MONITOR_EVENT_CREATED); + } + } + +} + +static inline void +invalidate_folder_T (Folder *folder) +{ + folder->up_to_date = FALSE; + + invalidate_folder_subfolders (folder, TRUE); +} + +static inline void +invalidate_folder (Folder *folder) +{ + G_LOCK (vfolder_lock); + folder->up_to_date = FALSE; + G_UNLOCK (vfolder_lock); + + invalidate_folder_subfolders (folder, FALSE); +} + +static void +invalidate_folder_subfolders (Folder *folder, + gboolean lock_taken) +{ + GSList *li; + + for (li = folder->subfolders; li != NULL; li = li->next) { + Folder *subfolder = li->data; + + if (!lock_taken) + invalidate_folder (subfolder); + else + invalidate_folder_T (subfolder); + } + + emit_monitor (folder, GNOME_VFS_MONITOR_EVENT_CHANGED); +} + +/* FIXME: this is UGLY!, we need to figure out when the file + * got finished changing! */ +static gboolean +reread_timeout (gpointer data) +{ + VFolderInfo *info = data; + gboolean force_read_items = info->file_monitors != NULL; + vfolder_info_reload (info, NULL, NULL, force_read_items); + return FALSE; +} + +static void +queue_reread_in (VFolderInfo *info, int msec) +{ + G_LOCK (vfolder_lock); + if (info->reread_queue != 0) + g_source_remove (info->reread_queue); + info->reread_queue = g_timeout_add (msec, reread_timeout, info); + G_UNLOCK (vfolder_lock); +} + +static void +vfolder_desktop_dir_monitor (GnomeVFSMonitorHandle *handle, + const gchar *monitor_uri, + const gchar *info_uri, + GnomeVFSMonitorEventType event_type, + gpointer user_data) +{ + /* FIXME: implement */ +} + +static void +vfolder_user_desktop_dir_monitor (GnomeVFSMonitorHandle *handle, + const gchar *monitor_uri, + const gchar *info_uri, + GnomeVFSMonitorEventType event_type, + gpointer user_data) +{ + /* FIXME: implement */ +} + +static void +vfolder_filename_monitor (GnomeVFSMonitorHandle *handle, + const gchar *monitor_uri, + const gchar *info_uri, + GnomeVFSMonitorEventType event_type, + gpointer user_data) +{ + VFolderInfo *info = user_data; + + if ((event_type == GNOME_VFS_MONITOR_EVENT_CREATED || + event_type == GNOME_VFS_MONITOR_EVENT_CHANGED) && + ! info->user_file_active) { + queue_reread_in (info, 200); + } else if (event_type == GNOME_VFS_MONITOR_EVENT_DELETED && + ! info->user_file_active) { + /* FIXME: is this correct? I mean now + * there probably isn't ANY vfolder file, so we + * init to default values really. I have no clue what's + * right here */ + vfolder_info_reload (info, NULL, NULL, + TRUE /* force read items */); + } +} + +static void +vfolder_user_filename_monitor (GnomeVFSMonitorHandle *handle, + const gchar *monitor_uri, + const gchar *info_uri, + GnomeVFSMonitorEventType event_type, + gpointer user_data) +{ + VFolderInfo *info = user_data; + + if ((event_type == GNOME_VFS_MONITOR_EVENT_CREATED || + event_type == GNOME_VFS_MONITOR_EVENT_CHANGED) && + info->user_file_active) { + struct stat s; + + /* see if this was really our own change */ + if (info->user_filename_last_write == time (NULL)) + return; + /* anal retentive */ + if (stat (info->user_filename, &s) == 0 && + info->user_filename_last_write == s.st_ctime) + return; + + queue_reread_in (info, 200); + } else if ((event_type == GNOME_VFS_MONITOR_EVENT_CREATED || + event_type == GNOME_VFS_MONITOR_EVENT_CHANGED) && + ! info->user_file_active) { + queue_reread_in (info, 200); + } else if (event_type == GNOME_VFS_MONITOR_EVENT_DELETED && + info->user_file_active) { + gboolean force_read_items = info->file_monitors != NULL; + vfolder_info_reload (info, NULL, NULL, force_read_items); + } +} + +static void +item_dir_monitor (GnomeVFSMonitorHandle *handle, + const gchar *monitor_uri, + const gchar *info_uri, + GnomeVFSMonitorEventType event_type, + gpointer user_data) +{ + VFolderInfo *info = user_data; + + if (event_type == GNOME_VFS_MONITOR_EVENT_CREATED || + event_type == GNOME_VFS_MONITOR_EVENT_CHANGED) { + /* first invalidate all folders */ + invalidate_folder (info->root); + /* second invalidate all entries */ + info->entries_valid = FALSE; + + if (info->file_monitors != NULL) { + GnomeVFSResult result; + GSList *li; + + /* Whack all monitors here! */ + for (li = info->file_monitors; + li != NULL; + li = li->next) { + FileMonitorHandle *h = li->data; + if (h->handle != NULL) + gnome_vfs_monitor_cancel (h->handle); + h->handle = NULL; + } + + if (vfolder_info_read_items (info, &result, NULL)) { + info->entries_valid = TRUE; + } + } + } +} + +static gboolean +setup_dir_monitor (VFolderInfo *info, const char *dir, gboolean subdirs, + GnomeVFSResult *result, + GnomeVFSContext *context) +{ + GnomeVFSMonitorHandle *handle; + DIR *dh; + struct dirent *de; + char *uri; + + uri = gnome_vfs_get_uri_from_local_path (dir); + + if (gnome_vfs_monitor_add (&handle, + uri, + GNOME_VFS_MONITOR_DIRECTORY, + item_dir_monitor, + info) != GNOME_VFS_OK) { + StatLoc *sl = bake_statloc (dir, time (NULL)); + if (sl != NULL) + info->stat_dirs = g_slist_prepend (info->stat_dirs, sl); + g_free (uri); + return TRUE; + } + g_free (uri); + + if (gnome_vfs_context_check_cancellation (context)) { + gnome_vfs_monitor_cancel (handle); + *result = GNOME_VFS_ERROR_CANCELLED; + return FALSE; + } + + info->item_dir_monitors = + g_slist_prepend (info->item_dir_monitors, handle); + + if ( ! subdirs) + return TRUE; + + dh = opendir (dir); + if (dh == NULL) + return TRUE; + + while ((de = readdir (dh)) != NULL) { + char *full_path; + + if (gnome_vfs_context_check_cancellation (context)) { + *result = GNOME_VFS_ERROR_CANCELLED; + closedir (dh); + return FALSE; + } + + if (de->d_name[0] == '.') + continue; + + full_path = g_build_filename (dir, de->d_name, NULL); + if (g_file_test (full_path, G_FILE_TEST_IS_DIR)) { + if ( ! setup_dir_monitor (info, full_path, + TRUE /* subdirs */, + result, context)) { + closedir (dh); + return FALSE; + } + } + g_free (full_path); + } + + closedir (dh); + + return TRUE; +} + +static gboolean +monitor_setup (VFolderInfo *info, + gboolean setup_filenames, + gboolean setup_itemdirs, + gboolean setup_desktop_dirs, + GnomeVFSResult *result, + GnomeVFSContext *context) +{ + char *uri; + GSList *li; + + if (setup_filenames) { + uri = gnome_vfs_get_uri_from_local_path + (info->filename); + + if (gnome_vfs_monitor_add (&info->filename_monitor, + uri, + GNOME_VFS_MONITOR_FILE, + vfolder_filename_monitor, + info) != GNOME_VFS_OK) { + info->filename_monitor = NULL; + info->filename_statloc = bake_statloc (info->filename, + time (NULL)); + } + g_free (uri); + } + if (setup_filenames && + info->user_filename != NULL) { + uri = gnome_vfs_get_uri_from_local_path + (info->user_filename); + if (gnome_vfs_monitor_add (&info->user_filename_monitor, + uri, + GNOME_VFS_MONITOR_FILE, + vfolder_user_filename_monitor, + info) != GNOME_VFS_OK) { + info->user_filename_monitor = NULL; + info->user_filename_statloc = + bake_statloc (info->user_filename, + time (NULL)); + } + + g_free (uri); + } + + if (gnome_vfs_context_check_cancellation (context)) { + *result = GNOME_VFS_ERROR_CANCELLED; + return FALSE; + } + + if (setup_itemdirs) { + for (li = info->item_dirs; li != NULL; li = li->next) { + const char *dir = li->data; + if ( ! setup_dir_monitor (info, dir, + FALSE /* subdirs */, + result, context)) + return FALSE; + } + if (info->user_item_dir != NULL) { + if ( ! setup_dir_monitor (info, info->user_item_dir, + FALSE /* subdirs */, + result, context)) + return FALSE; + } + for (li = info->merge_dirs; li != NULL; li = li->next) { + const char *dir = li->data; + if ( ! setup_dir_monitor (info, dir, + TRUE /* subdirs */, + result, context)) + return FALSE; + } + } + + if (setup_desktop_dirs) { + uri = gnome_vfs_get_uri_from_local_path + (info->desktop_dir); + + if (gnome_vfs_monitor_add (&info->desktop_dir_monitor, + uri, + GNOME_VFS_MONITOR_FILE, + vfolder_desktop_dir_monitor, + info) != GNOME_VFS_OK) { + info->desktop_dir_monitor = NULL; + info->desktop_dir_statloc = + bake_statloc (info->desktop_dir, + time (NULL)); + } + g_free (uri); + } + if (setup_desktop_dirs && + info->user_desktop_dir != NULL) { + uri = gnome_vfs_get_uri_from_local_path + (info->user_desktop_dir); + if (gnome_vfs_monitor_add (&info->user_desktop_dir_monitor, + uri, + GNOME_VFS_MONITOR_DIRECTORY, + vfolder_user_desktop_dir_monitor, + info) != GNOME_VFS_OK) { + info->user_desktop_dir_monitor = NULL; + info->user_desktop_dir_statloc = + bake_statloc (info->user_desktop_dir, + time (NULL)); + } + + g_free (uri); + } + + return TRUE; +} + +static void +vfolder_info_init (VFolderInfo *info, const char *scheme) +{ + const char *path; + GSList *list; + + info->scheme = g_strdup (scheme); + + info->filename = g_strconcat (SYSCONFDIR, + "/gnome-vfs-2.0/vfolders/", + scheme, ".vfolder-info", + NULL); + info->user_filename = g_strconcat (g_get_home_dir (), + "/" DOT_GNOME "/vfolders/", + scheme, ".vfolder-info", + NULL); + info->desktop_dir = g_strconcat (SYSCONFDIR, + "/gnome-vfs-2.0/vfolders/", + NULL); + info->user_desktop_dir = g_strconcat (g_get_home_dir (), + "/" DOT_GNOME "/vfolders/", + NULL); + + /* Init the desktop paths */ + list = NULL; + list = g_slist_prepend (list, g_strdup ("/usr/share/applications/")); + if (strcmp ("/usr/share/applications/", DATADIR "/applications/") != 0) + list = g_slist_prepend (list, g_strdup (DATADIR "/applications/")); + path = g_getenv ("DESKTOP_FILE_PATH"); + if (path != NULL) { + int i; + char **ppath = g_strsplit (path, ":", -1); + for (i = 0; ppath[i] != NULL; i++) { + const char *dir = ppath[i]; + list = g_slist_prepend (list, g_strdup (dir)); + } + g_strfreev (ppath); + } + info->item_dirs = g_slist_reverse (list); + + info->user_item_dir = g_strconcat (g_get_home_dir (), + "/" DOT_GNOME "/vfolders/", + scheme, + NULL); + + info->entries_ht = g_hash_table_new (g_str_hash, g_str_equal); + + info->root = folder_new ("Root"); + + info->modification_time = time (NULL); +} + +static void +vfolder_info_free_internals_unlocked (VFolderInfo *info) +{ + if (info == NULL) + return; + + if (info->filename_monitor != NULL) { + gnome_vfs_monitor_cancel (info->filename_monitor); + info->filename_monitor = NULL; + } + + if (info->user_filename_monitor != NULL) { + gnome_vfs_monitor_cancel (info->user_filename_monitor); + info->user_filename_monitor = NULL; + } + + g_free (info->filename_statloc); + info->filename_statloc = NULL; + + g_free (info->user_filename_statloc); + info->user_filename_statloc = NULL; + + + if (info->desktop_dir_monitor != NULL) { + gnome_vfs_monitor_cancel (info->desktop_dir_monitor); + info->desktop_dir_monitor = NULL; + } + + if (info->user_desktop_dir_monitor != NULL) { + gnome_vfs_monitor_cancel (info->user_desktop_dir_monitor); + info->user_desktop_dir_monitor = NULL; + } + + g_free (info->desktop_dir_statloc); + info->desktop_dir_statloc = NULL; + + g_free (info->user_desktop_dir_statloc); + info->user_desktop_dir_statloc = NULL; + + + g_slist_foreach (info->item_dir_monitors, + (GFunc)gnome_vfs_monitor_cancel, NULL); + g_slist_free (info->item_dir_monitors); + info->item_dir_monitors = NULL; + + g_free (info->scheme); + info->scheme = NULL; + + g_free (info->filename); + info->filename = NULL; + + g_free (info->user_filename); + info->user_filename = NULL; + + g_free (info->desktop_dir); + info->desktop_dir = NULL; + + g_free (info->user_desktop_dir); + info->user_desktop_dir = NULL; + + g_slist_foreach (info->item_dirs, (GFunc)g_free, NULL); + g_slist_free (info->item_dirs); + info->item_dirs = NULL; + + g_free (info->user_item_dir); + info->user_item_dir = NULL; + + g_slist_foreach (info->merge_dirs, (GFunc)g_free, NULL); + g_slist_free (info->merge_dirs); + info->merge_dirs = NULL; + + g_slist_foreach (info->entries, (GFunc)entry_unref, NULL); + g_slist_free (info->entries); + info->entries = NULL; + + if (info->entries_ht != NULL) + g_hash_table_destroy (info->entries_ht); + info->entries_ht = NULL; + + g_slist_foreach (info->unallocated_folders, + (GFunc)entry_unref, + NULL); + g_slist_free (info->unallocated_folders); + info->unallocated_folders = NULL; + + entry_unref ((Entry *)info->root); + info->root = NULL; + + g_slist_foreach (info->stat_dirs, (GFunc)g_free, NULL); + g_slist_free (info->stat_dirs); + info->stat_dirs = NULL; + + g_slist_foreach (info->folder_monitors, + (GFunc)file_monitor_handle_unref_unlocked, NULL); + g_slist_free (info->folder_monitors); + info->folder_monitors = NULL; + + g_slist_foreach (info->free_folder_monitors, + (GFunc)file_monitor_handle_unref_unlocked, NULL); + g_slist_free (info->free_folder_monitors); + info->free_folder_monitors = NULL; + + g_slist_foreach (info->file_monitors, + (GFunc)file_monitor_handle_unref_unlocked, NULL); + g_slist_free (info->file_monitors); + info->file_monitors = NULL; + + g_slist_foreach (info->free_file_monitors, + (GFunc)file_monitor_handle_unref_unlocked, NULL); + g_slist_free (info->free_file_monitors); + info->free_file_monitors = NULL; + + if (info->reread_queue != 0) + g_source_remove (info->reread_queue); + info->reread_queue = 0; +} + +static void +vfolder_info_free_internals (VFolderInfo *info) +{ + G_LOCK (vfolder_lock); + vfolder_info_free_internals_unlocked (info); + G_UNLOCK (vfolder_lock); +} + +static void +vfolder_info_destroy (VFolderInfo *info) +{ + vfolder_info_free_internals (info); + g_free (info); +} + +static Query * +single_query_read (xmlNode *qnode) +{ + Query *query; + xmlNode *node; + + if (qnode->type != XML_ELEMENT_NODE || + qnode->name == NULL) + return NULL; + + query = NULL; + + if (g_ascii_strcasecmp (qnode->name, "Not") == 0 && + qnode->xmlChildrenNode != NULL) { + xmlNode *iter; + query = NULL; + for (iter = qnode->xmlChildrenNode; + iter != NULL && query == NULL; + iter = iter->next) + query = single_query_read (iter); + if (query != NULL) { + query->not = ! query->not; + } + return query; + } else if (g_ascii_strcasecmp (qnode->name, "Keyword") == 0) { + xmlChar *word = xmlNodeGetContent (qnode); + if (word != NULL) { + query = query_new (QUERY_KEYWORD); + ((QueryKeyword *)query)->keyword = + g_quark_from_string (word); + + xmlFree (word); + } + return query; + } else if (g_ascii_strcasecmp (qnode->name, "Filename") == 0) { + xmlChar *file = xmlNodeGetContent (qnode); + if (file != NULL) { + query = query_new (QUERY_FILENAME); + ((QueryFilename *)query)->filename = + g_strdup (file); + + xmlFree (file); + } + return query; + } else if (g_ascii_strcasecmp (qnode->name, "And") == 0) { + query = query_new (QUERY_AND); + } else if (g_ascii_strcasecmp (qnode->name, "Or") == 0) { + query = query_new (QUERY_OR); + } else { + /* We don't understand */ + return NULL; + } + + /* This must be OR or AND */ + g_assert (query != NULL); + + for (node = qnode->xmlChildrenNode; node != NULL; node = node->next) { + Query *new_query = single_query_read (node); + + if (new_query != NULL) + query->queries = g_slist_prepend + (query->queries, new_query); + } + + query->queries = g_slist_reverse (query->queries); + + return query; +} + +static void +add_or_set_query (Query **query, Query *new_query) +{ + if (*query == NULL) { + *query = new_query; + } else { + Query *old_query = *query; + *query = query_new (QUERY_OR); + (*query)->queries = + g_slist_append ((*query)->queries, old_query); + (*query)->queries = + g_slist_append ((*query)->queries, new_query); + } +} + +static Query * +query_read (xmlNode *qnode) +{ + Query *query; + xmlNode *node; + + query = NULL; + + for (node = qnode->xmlChildrenNode; node != NULL; node = node->next) { + if (node->type != XML_ELEMENT_NODE || + node->name == NULL) + continue; + + if (g_ascii_strcasecmp (node->name, "Not") == 0 && + node->xmlChildrenNode != NULL) { + xmlNode *iter; + Query *new_query = NULL; + + for (iter = node->xmlChildrenNode; + iter != NULL && new_query == NULL; + iter = iter->next) + new_query = single_query_read (iter); + if (new_query != NULL) { + new_query->not = ! new_query->not; + add_or_set_query (&query, new_query); + } + } else { + Query *new_query = single_query_read (node); + if (new_query != NULL) + add_or_set_query (&query, new_query); + } + } + + return query; +} + +static Folder * +folder_read (VFolderInfo *info, xmlNode *fnode) +{ + Folder *folder; + xmlNode *node; + + folder = folder_new (NULL); + + for (node = fnode->xmlChildrenNode; node != NULL; node = node->next) { + if (node->type != XML_ELEMENT_NODE || + node->name == NULL) + continue; + + if (g_ascii_strcasecmp (node->name, "Name") == 0) { + xmlChar *name = xmlNodeGetContent (node); + if (name != NULL) { + g_free (folder->entry.name); + folder->entry.name = g_strdup (name); + xmlFree (name); + } + } else if (g_ascii_strcasecmp (node->name, "Desktop") == 0) { + xmlChar *desktop = xmlNodeGetContent (node); + if (desktop != NULL) { + g_free (folder->desktop_file); + folder->desktop_file = g_strdup (desktop); + xmlFree (desktop); + } + } else if (g_ascii_strcasecmp (node->name, "Include") == 0) { + xmlChar *file = xmlNodeGetContent (node); + if (file != NULL) { + GSList *li; + char *str = g_strdup (file); + folder->includes = g_slist_prepend + (folder->includes, str); + if (folder->includes_ht == NULL) { + folder->includes_ht = + g_hash_table_new_full + (g_str_hash, + g_str_equal, + NULL, + NULL); + } + li = g_hash_table_lookup (folder->includes_ht, + file); + if (li != NULL) { + g_free (li->data); + /* Note: this will NOT change folder->includes + * pointer! */ + folder->includes = g_slist_delete_link + (folder->includes, li); + } + g_hash_table_replace (folder->includes_ht, + file, folder->includes); + xmlFree (file); + } + } else if (g_ascii_strcasecmp (node->name, "Exclude") == 0) { + xmlChar *file = xmlNodeGetContent (node); + if (file != NULL) { + char *s; + if (folder->excludes == NULL) { + folder->excludes = g_hash_table_new_full + (g_str_hash, + g_str_equal, + (GDestroyNotify)g_free, + NULL); + } + s = g_strdup (file); + g_hash_table_replace (folder->excludes, s, s); + xmlFree (file); + } + } else if (g_ascii_strcasecmp (node->name, "Query") == 0) { + Query *query; + + query = query_read (node); + + if (query != NULL) { + if (folder->query != NULL) + query_destroy (folder->query); + folder->query = query; + } + } else if (g_ascii_strcasecmp (node->name, "OnlyUnallocated") == 0) { + info->unallocated_folders = + g_slist_prepend (info->unallocated_folders, + (Folder *)entry_ref ((Entry *)folder)); + folder->only_unallocated = TRUE; + } else if (g_ascii_strcasecmp (node->name, "Folder") == 0) { + Folder *new_folder = folder_read (info, node); + if (new_folder != NULL) { + folder->subfolders = + g_slist_append (folder->subfolders, + new_folder); + new_folder->parent = folder; + } + } else if (g_ascii_strcasecmp (node->name, "ReadOnly") == 0) { + folder->read_only = TRUE; + } else if (g_ascii_strcasecmp (node->name, + "DontShowIfEmpty") == 0) { + folder->dont_show_if_empty = TRUE; + } + } + + /* Name is required */ + if (folder->entry.name == NULL) { + entry_unref ((Entry *)folder); + folder = NULL; + } + + folder->includes = g_slist_reverse (folder->includes); + + return folder; +} + +static char * +subst_home (const char *dir) +{ + if (dir[0] == '~') + return g_strconcat (g_get_home_dir (), + &dir[1], + NULL); + else + return g_strdup (dir); +} + +/* FORMAT looks like: + * + * + * /etc/X11/applnk + * + * /usr/share/applications + * + * /etc/X11/gnome/vfolders + * + * + * Root + * + * important.desktop + * + * + * + * SomeFolder + * + * + * Test_Folder + * + * Test_Folder.directory + * + * + * + * Application + * Game + * + * Clock + * + * + * somefile.desktop + * someotherfile.desktop + * yetanother.desktop + * + * + * + */ + +static gboolean +vfolder_info_read_info (VFolderInfo *info, + GnomeVFSResult *result, + GnomeVFSContext *context) +{ + xmlDoc *doc; + xmlNode *node; + gboolean got_a_vfolder_dir = FALSE; + + doc = NULL; + if (info->user_filename != NULL && + access (info->user_filename, F_OK) == 0) { + doc = xmlParseFile (info->user_filename); + if (doc != NULL) + info->user_file_active = TRUE; + } + if (doc == NULL && + access (info->filename, F_OK) == 0) + doc = xmlParseFile (info->filename); + + if (gnome_vfs_context_check_cancellation (context)) { + xmlFreeDoc(doc); + *result = GNOME_VFS_ERROR_CANCELLED; + return FALSE; + } + + if (doc == NULL + || doc->xmlRootNode == NULL + || doc->xmlRootNode->name == NULL + || g_ascii_strcasecmp (doc->xmlRootNode->name, "VFolderInfo") != 0) { + xmlFreeDoc(doc); + return TRUE; /* FIXME: really, shouldn't we error out? */ + } + + for (node = doc->xmlRootNode->xmlChildrenNode; node != NULL; node = node->next) { + if (node->type != XML_ELEMENT_NODE || + node->name == NULL) + continue; + + if (gnome_vfs_context_check_cancellation (context)) { + xmlFreeDoc(doc); + *result = GNOME_VFS_ERROR_CANCELLED; + return FALSE; + } + + if (g_ascii_strcasecmp (node->name, "MergeDir") == 0) { + xmlChar *dir = xmlNodeGetContent (node); + if (dir != NULL) { + info->merge_dirs = g_slist_append (info->merge_dirs, + g_strdup (dir)); + xmlFree (dir); + } + } else if (g_ascii_strcasecmp (node->name, "ItemDir") == 0) { + xmlChar *dir = xmlNodeGetContent (node); + if (dir != NULL) { + if ( ! got_a_vfolder_dir) { + g_slist_foreach (info->item_dirs, + (GFunc)g_free, NULL); + g_slist_free (info->item_dirs); + info->item_dirs = NULL; + } + got_a_vfolder_dir = TRUE; + info->item_dirs = g_slist_append (info->item_dirs, + g_strdup (dir)); + xmlFree (dir); + } + } else if (g_ascii_strcasecmp (node->name, "UserItemDir") == 0) { + xmlChar *dir = xmlNodeGetContent (node); + if (dir != NULL) { + g_free (info->user_item_dir); + info->user_item_dir = subst_home (dir); + xmlFree (dir); + } + } else if (g_ascii_strcasecmp (node->name, "DesktopDir") == 0) { + xmlChar *dir = xmlNodeGetContent (node); + if (dir != NULL) { + g_free (info->desktop_dir); + info->desktop_dir = g_strdup (dir); + xmlFree (dir); + } + } else if (g_ascii_strcasecmp (node->name, "UserDesktopDir") == 0) { + xmlChar *dir = xmlNodeGetContent (node); + if (dir != NULL) { + g_free (info->user_desktop_dir); + info->user_desktop_dir = subst_home (dir); + xmlFree (dir); + } + } else if (g_ascii_strcasecmp (node->name, "Folder") == 0) { + Folder *folder = folder_read (info, node); + if (folder != NULL) { + if (info->root != NULL) + entry_unref ((Entry *)info->root); + info->root = folder; + } + } else if (g_ascii_strcasecmp (node->name, "ReadOnly") == 0) { + info->read_only = TRUE; + } + } + + xmlFreeDoc(doc); + + return TRUE; +} + +static void +add_xml_tree_from_query (xmlNode *parent, Query *query) +{ + xmlNode *real_parent; + + if (query->not) + real_parent = xmlNewChild (parent /* parent */, + NULL /* ns */, + "Not" /* name */, + NULL /* content */); + else + real_parent = parent; + + if (query->type == QUERY_KEYWORD) { + QueryKeyword *qkeyword = (QueryKeyword *)query; + const char *string = g_quark_to_string (qkeyword->keyword); + + xmlNewChild (real_parent /* parent */, + NULL /* ns */, + "Keyword" /* name */, + string /* content */); + } else if (query->type == QUERY_FILENAME) { + QueryFilename *qfilename = (QueryFilename *)query; + + xmlNewChild (real_parent /* parent */, + NULL /* ns */, + "Filename" /* name */, + qfilename->filename /* content */); + } else if (query->type == QUERY_OR || + query->type == QUERY_AND) { + xmlNode *node; + const char *name; + GSList *li; + + if (query->type == QUERY_OR) + name = "Or"; + else /* QUERY_AND */ + name = "And"; + + node = xmlNewChild (real_parent /* parent */, + NULL /* ns */, + name /* name */, + NULL /* content */); + + for (li = query->queries; li != NULL; li = li->next) { + Query *subquery = li->data; + add_xml_tree_from_query (node, subquery); + } + } else { + g_assert_not_reached (); + } +} + +static void +add_excludes_to_xml (gpointer key, gpointer value, gpointer user_data) +{ + const char *filename = key; + xmlNode *folder_node = user_data; + + xmlNewChild (folder_node /* parent */, + NULL /* ns */, + "Exclude" /* name */, + filename /* content */); +} + +static void +add_xml_tree_from_folder (xmlNode *parent, Folder *folder) +{ + GSList *li; + xmlNode *folder_node; + + + folder_node = xmlNewChild (parent /* parent */, + NULL /* ns */, + "Folder" /* name */, + NULL /* content */); + + xmlNewChild (folder_node /* parent */, + NULL /* ns */, + "Name" /* name */, + folder->entry.name /* content */); + + if (folder->desktop_file != NULL) { + xmlNewChild (folder_node /* parent */, + NULL /* ns */, + "Desktop" /* name */, + folder->desktop_file /* content */); + } + + if (folder->read_only) + xmlNewChild (folder_node /* parent */, + NULL /* ns */, + "ReadOnly" /* name */, + NULL /* content */); + if (folder->dont_show_if_empty) + xmlNewChild (folder_node /* parent */, + NULL /* ns */, + "DontShowIfEmpty" /* name */, + NULL /* content */); + if (folder->only_unallocated) + xmlNewChild (folder_node /* parent */, + NULL /* ns */, + "OnlyUnallocated" /* name */, + NULL /* content */); + + for (li = folder->subfolders; li != NULL; li = li->next) { + Folder *subfolder = li->data; + add_xml_tree_from_folder (folder_node, subfolder); + } + + for (li = folder->includes; li != NULL; li = li->next) { + const char *include = li->data; + xmlNewChild (folder_node /* parent */, + NULL /* ns */, + "Include" /* name */, + include /* content */); + } + + if (folder->excludes) { + g_hash_table_foreach (folder->excludes, + add_excludes_to_xml, + folder_node); + } + + if (folder->query != NULL) { + xmlNode *query_node; + query_node = xmlNewChild (folder_node /* parent */, + NULL /* ns */, + "Query" /* name */, + NULL /* content */); + + add_xml_tree_from_query (query_node, folder->query); + } +} + +static xmlDoc * +xml_tree_from_vfolder (VFolderInfo *info) +{ + xmlDoc *doc; + xmlNode *topnode; + GSList *li; + + doc = xmlNewDoc ("1.0"); + + topnode = xmlNewDocNode (doc /* doc */, + NULL /* ns */, + "VFolderInfo" /* name */, + NULL /* content */); + doc->xmlRootNode = topnode; + + for (li = info->merge_dirs; li != NULL; li = li->next) { + const char *merge_dir = li->data; + xmlNewChild (topnode /* parent */, + NULL /* ns */, + "MergeDir" /* name */, + merge_dir /* content */); + } + + for (li = info->item_dirs; li != NULL; li = li->next) { + const char *item_dir = li->data; + xmlNewChild (topnode /* parent */, + NULL /* ns */, + "ItemDir" /* name */, + item_dir /* content */); + } + + if (info->user_item_dir != NULL) { + xmlNewChild (topnode /* parent */, + NULL /* ns */, + "UserItemDir" /* name */, + info->user_item_dir /* content */); + } + + if (info->desktop_dir != NULL) { + xmlNewChild (topnode /* parent */, + NULL /* ns */, + "DesktopDir" /* name */, + info->desktop_dir /* content */); + } + + if (info->user_desktop_dir != NULL) { + xmlNewChild (topnode /* parent */, + NULL /* ns */, + "UserDesktopDir" /* name */, + info->user_desktop_dir /* content */); + } + + if (info->root != NULL) + add_xml_tree_from_folder (topnode, info->root); + + return doc; +} + +/* FIXME: what to do about errors */ +static void +vfolder_info_write_user (VFolderInfo *info) +{ + xmlDoc *doc; + + if (info->inhibit_write > 0) + return; + + if (info->user_filename == NULL) + return; + + doc = xml_tree_from_vfolder (info); + if (doc == NULL) + return; + + /* FIXME: errors, anyone? */ + ensure_dir (info->user_filename, + TRUE /* ignore_basename */); + + xmlSaveFormatFile (info->user_filename, doc, TRUE /* format */); + /* not as good as a stat, but cheaper ... hmmm what is + * the likelyhood of this not being the same as ctime */ + info->user_filename_last_write = time (NULL); + + xmlFreeDoc(doc); + + info->user_file_active = TRUE; + info->dirty = FALSE; + + info->modification_time = time (NULL); +} + +/* An EVIL function for quick reading of .desktop files, + * only reads in one or two keys, but that's ALL we need */ +static void +readitem_entry (const char *filename, + const char *key1, + char **result1, + const char *key2, + char **result2) +{ + FILE *fp; + char buf[1024]; + int keylen1, keylen2; + + *result1 = NULL; + if (result2 != NULL) + *result2 = NULL; + + fp = fopen (filename, "r"); + + if (fp == NULL) + return; + + keylen1 = strlen (key1); + if (key2 != NULL) + keylen2 = strlen (key2); + else + keylen2 = -1; + + /* This is slightly wrong, it should only look + * at the correct section */ + while (fgets (buf, sizeof (buf), fp) != NULL) { + char *p; + int len; + int keylen; + char **result = NULL; + + /* check if it's one of the keys */ + if (strncmp (buf, key1, keylen1) == 0) { + result = result1; + keylen = keylen1; + } else if (keylen2 >= 0 && + strncmp (buf, key2, keylen2) == 0) { + result = result2; + keylen = keylen2; + } else { + continue; + } + + p = &buf[keylen]; + + /* still not our key */ + if (!(*p == '=' || *p == ' ')) { + continue; + } + do + p++; + while (*p == ' ' || *p == '='); + + /* get rid of trailing \n */ + len = strlen (p); + if (p[len-1] == '\n' || + p[len-1] == '\r') + p[len-1] = '\0'; + + *result = g_strdup (p); + + if (*result1 == NULL || + (result2 != NULL && *result2 == NULL)) + break; + } + + fclose (fp); +} + +static void +vfolder_info_insert_entry (VFolderInfo *info, EntryFile *efile) +{ + GSList *entry_list; + + entry_ref ((Entry *)efile); + + entry_list = g_hash_table_lookup (info->entries_ht, efile->entry.name); + + info->entries = g_slist_prepend (info->entries, efile); + /* The hash table contains the GSList pointer */ + g_hash_table_replace (info->entries_ht, efile->entry.name, + info->entries); + + if (entry_list != NULL) { + Entry *entry = entry_list->data; + info->entries = g_slist_delete_link (info->entries, + entry_list); + entry_unref (entry); + } +} + +static void +set_keywords (EntryFile *efile, const char *keywords) +{ + if (keywords != NULL) { + int i; + char **parsed = g_strsplit (keywords, ";", -1); + for (i = 0; parsed[i] != NULL; i++) { + GQuark quark; + const char *word = parsed[i]; + /* ignore empties (including end of list) */ + if (word[0] == '\0') + continue; + quark = g_quark_from_string (word); + efile->keywords = g_slist_prepend + (efile->keywords, + GINT_TO_POINTER (quark)); + } + g_strfreev (parsed); + } +} + +static EntryFile * +make_entry_file (const char *dir, const char *name) +{ + EntryFile *efile; + char *categories; + char *only_show_in; + char *filename; + int i; + + filename = g_build_filename (dir, name, NULL); + + readitem_entry (filename, + "Categories", + &categories, + "OnlyShowIn", + &only_show_in); + + if (only_show_in != NULL) { + gboolean show = FALSE; + char **parsed = g_strsplit (only_show_in, ";", -1); + for (i = 0; parsed[i] != NULL; i++) { + if (strcmp (parsed[i], "GNOME") == 0) { + show = TRUE; + break; + } + } + g_strfreev (parsed); + if ( ! show) { + g_free (filename); + g_free (only_show_in); + g_free (categories); + return NULL; + } + } + + efile = file_new (name); + efile->filename = filename; + + set_keywords (efile, categories); + + g_free (only_show_in); + g_free (categories); + + return efile; +} + +static gboolean +vfolder_info_read_items_from (VFolderInfo *info, + const char *item_dir, + gboolean per_user, + GnomeVFSResult *result, + GnomeVFSContext *context) +{ + DIR *dir; + struct dirent *de; + + dir = opendir (item_dir); + if (dir == NULL) + return TRUE; + + while ((de = readdir (dir)) != NULL) { + EntryFile *efile; + + if (gnome_vfs_context_check_cancellation (context)) { + closedir (dir); + *result = GNOME_VFS_ERROR_CANCELLED; + return FALSE; + } + + /* files MUST be called .desktop */ + if (de->d_name[0] == '.' || + ! check_ext (de->d_name, ".desktop")) + continue; + + efile = make_entry_file (item_dir, de->d_name); + if (efile == NULL) + continue; + + efile->per_user = per_user; + + vfolder_info_insert_entry (info, efile); + entry_unref ((Entry *)efile); + } + + closedir (dir); + + return TRUE; +} + +static gboolean +vfolder_info_read_items_merge (VFolderInfo *info, + const char *merge_dir, + const char *subdir, + GQuark inherited_keyword, + GnomeVFSResult *result, + GnomeVFSContext *context) +{ + DIR *dir; + struct dirent *de; + GQuark extra_keyword; + GQuark Application; + GQuark Merged; + GQuark inheritance; + gboolean pass_down_extra_keyword = TRUE; + + dir = opendir (merge_dir); + if (dir == NULL) + return TRUE; + + Application = g_quark_from_static_string ("Application"); + Merged = g_quark_from_static_string ("Merged"); + + /* FIXME: this should be a hash or something */ + extra_keyword = 0; + if (subdir == NULL) { + extra_keyword = g_quark_from_static_string ("Core"); + pass_down_extra_keyword = FALSE; + } else if (g_ascii_strcasecmp (subdir, "Development") == 0) + extra_keyword = g_quark_from_static_string ("Development"); + else if (g_ascii_strcasecmp (subdir, "Editors") == 0) + extra_keyword = g_quark_from_static_string ("TextEditor"); + else if (g_ascii_strcasecmp (subdir, "Games") == 0) + extra_keyword = g_quark_from_static_string ("Game"); + else if (g_ascii_strcasecmp (subdir, "Graphics") == 0) + extra_keyword = g_quark_from_static_string ("Graphics"); + else if (g_ascii_strcasecmp (subdir, "Internet") == 0) + extra_keyword = g_quark_from_static_string ("Network"); + else if (g_ascii_strcasecmp (subdir, "Multimedia") == 0) + extra_keyword = g_quark_from_static_string ("AudioVideo"); + else if (g_ascii_strcasecmp (subdir, "Office") == 0) + extra_keyword = g_quark_from_static_string ("Office"); + else if (g_ascii_strcasecmp (subdir, "Settings") == 0) + extra_keyword = g_quark_from_static_string ("Settings"); + else if (g_ascii_strcasecmp (subdir, "System") == 0) + extra_keyword = g_quark_from_static_string ("System"); + else if (g_ascii_strcasecmp (subdir, "Utilities") == 0) + extra_keyword = g_quark_from_static_string ("Utility"); + + while ((de = readdir (dir)) != NULL) { + EntryFile *efile; + + if (gnome_vfs_context_check_cancellation (context)) { + closedir (dir); + *result = GNOME_VFS_ERROR_CANCELLED; + return FALSE; + } + + /* ignore hidden */ + if (de->d_name[0] == '.') + continue; + + /* files MUST be called .desktop, so + * treat all others as dirs. If we're wrong, + * the open will fail, which is ok */ + if ( ! check_ext (de->d_name, ".desktop")) { + /* if this is a directory recurse */ + char *fullname = g_build_filename (merge_dir, de->d_name, NULL); + if ((pass_down_extra_keyword == TRUE) && (extra_keyword != 0)) { + inheritance = extra_keyword; + } else { + inheritance = inherited_keyword; + } + + if ( ! vfolder_info_read_items_merge (info, + fullname, + de->d_name, + inheritance, + result, + context)) { + g_free (fullname); + return FALSE; + } + g_free (fullname); + continue; + } + + /* FIXME: add some keywords about some known apps + * like gimp and whatnot, perhaps take these from the vfolder + * file or some such */ + + efile = make_entry_file (merge_dir, de->d_name); + if (efile == NULL) + continue; + + /* If no keywords set, then add the standard ones */ + if (efile->keywords == NULL) { + efile->keywords = g_slist_prepend + (efile->keywords, + GINT_TO_POINTER (Application)); + + efile->keywords = g_slist_prepend + (efile->keywords, + GINT_TO_POINTER (Merged)); + + if (inherited_keyword != 0) { + efile->keywords = g_slist_prepend + (efile->keywords, + GINT_TO_POINTER (inherited_keyword)); + } + + if (extra_keyword != 0) { + efile->keywords = g_slist_prepend + (efile->keywords, + GINT_TO_POINTER (extra_keyword)); + } + efile->implicit_keywords = TRUE; + } + + vfolder_info_insert_entry (info, efile); + entry_unref ((Entry *)efile); + } + + closedir (dir); + + return TRUE; +} + +static Entry * +find_entry (GSList *list, const char *name) +{ + GSList *li; + + for (li = list; li != NULL; li = li->next) { + Entry *entry = li->data; + if (strcmp (name, entry->name) == 0) + return entry; + } + return NULL; +} + +static void +file_monitor (GnomeVFSMonitorHandle *handle, + const gchar *monitor_uri, + const gchar *info_uri, + GnomeVFSMonitorEventType event_type, + gpointer user_data) +{ + FileMonitorHandle *h = user_data; + + /* proxy the event through if it is a changed event + * only */ + + if (event_type == GNOME_VFS_MONITOR_EVENT_CHANGED && + h->handle != NULL) + gnome_vfs_monitor_callback ((GnomeVFSMethodHandle *) h, + h->uri, event_type); +} + +static void +try_free_file_monitors_create_files_unlocked (VFolderInfo *info) +{ + GSList *li, *list; + + list = g_slist_copy (info->free_file_monitors); + + for (li = list; li != NULL; li = li->next) { + FileMonitorHandle *handle = li->data; + Entry *entry; + GnomeVFSResult result; + char *dirfile = NULL; + + if (handle->is_directory_file) { + VFolderURI vuri; + Folder *folder; + + /* Evil! EVIL URI PARSING. this will eat a lot of + * stack if we have lots of free monitors */ + + VFOLDER_URI_PARSE (handle->uri, &vuri); + + folder = resolve_folder (info, + vuri.path, + TRUE /* ignore_basename */, + &result, + NULL); + + if (folder == NULL) + continue; + + dirfile = get_directory_file_unlocked (info, folder); + if (dirfile == NULL) + continue; + + entry = (Entry *)folder; + } else { + VFolderURI vuri; + Folder *f; + GnomeVFSResult result; + + entry = NULL; + + /* Evil! EVIL URI PARSING. this will eat a lot of + * stack if we have lots of monitors */ + + VFOLDER_URI_PARSE (handle->uri, &vuri); + + f = resolve_folder (info, + vuri.path, + TRUE /* ignore_basename */, + &result, + NULL); + + if (f != NULL) { + ensure_folder_unlocked ( + info, f, + FALSE /* subfolders */, + NULL /* except */, + FALSE /* ignore_unallocated */); + entry = find_entry (f->entries, vuri.file); + } + + if (entry == NULL) + continue; + } + + info->free_file_monitors = + g_slist_remove (info->free_file_monitors, handle); + entry->monitors = + g_slist_prepend (entry->monitors, handle); + + handle->exists = TRUE; + gnome_vfs_monitor_callback ((GnomeVFSMethodHandle *)handle, + handle->uri, + GNOME_VFS_MONITOR_EVENT_CREATED); + + /* recreate a handle */ + if (handle->handle == NULL && + entry->type == ENTRY_FILE) { + EntryFile *efile = (EntryFile *)entry; + char *uri = gnome_vfs_get_uri_from_local_path + (efile->filename); + + gnome_vfs_monitor_add (&(handle->handle), + uri, + GNOME_VFS_MONITOR_FILE, + file_monitor, + handle); + + g_free (uri); + } else if (handle->handle == NULL && + dirfile != NULL) { + char *uri = gnome_vfs_get_uri_from_local_path (dirfile); + + gnome_vfs_monitor_add (&(handle->handle), + uri, + GNOME_VFS_MONITOR_FILE, + file_monitor, + handle); + + g_free (uri); + } + + g_free (dirfile); + } + + g_slist_free (list); +} + +static void /* unlocked */ +rescan_monitors (VFolderInfo *info) +{ + GSList *li; + + if (info->file_monitors == NULL) + return; + + for (li = info->file_monitors; li != NULL; li = li->next) { + FileMonitorHandle *h = li->data; + GnomeVFSResult result; + Entry *entry; + char *dirfile = NULL; + + /* these are handled below */ + if ( ! h->exists) + continue; + + if (h->is_directory_file) { + VFolderURI vuri; + Folder *folder; + + /* Evil! EVIL URI PARSING. this will eat a lot of + * stack if we have lots of monitors */ + + VFOLDER_URI_PARSE (h->uri, &vuri); + + folder = resolve_folder (info, + vuri.path, + TRUE /* ignore_basename */, + &result, + NULL); + if (folder != NULL) + dirfile = get_directory_file_unlocked (info, + folder); + + if (dirfile == NULL) { + h->exists = FALSE; + gnome_vfs_monitor_callback + ((GnomeVFSMethodHandle *)h, + h->uri, + GNOME_VFS_MONITOR_EVENT_DELETED); + info->free_file_monitors = g_slist_prepend + (info->free_file_monitors, h); + file_monitor_handle_ref_unlocked (h); + /* it has been unreffed when the entry was + * whacked */ + continue; + } + + entry = (Entry *)folder; + } else { + VFolderURI vuri; + Folder *f; + GnomeVFSResult result; + + entry = NULL; + + /* Evil! EVIL URI PARSING. this will eat a lot of + * stack if we have lots of monitors */ + + VFOLDER_URI_PARSE (h->uri, &vuri); + + f = resolve_folder (info, + vuri.path, + TRUE /* ignore_basename */, + &result, + NULL); + + if (f != NULL) { + ensure_folder_unlocked ( + info, f, + FALSE /* subfolders */, + NULL /* except */, + FALSE /* ignore_unallocated */); + entry = find_entry (f->entries, vuri.file); + } + + if (entry == NULL) { + h->exists = FALSE; + gnome_vfs_monitor_callback + ((GnomeVFSMethodHandle *)h, + h->uri, + GNOME_VFS_MONITOR_EVENT_DELETED); + info->free_file_monitors = g_slist_prepend + (info->free_file_monitors, h); + file_monitor_handle_ref_unlocked (h); + /* it has been unreffed when the entry was + * whacked */ + continue; + } + } + + /* recreate a handle */ + if (h->handle == NULL && + entry->type == ENTRY_FILE) { + EntryFile *efile = (EntryFile *)entry; + char *uri = gnome_vfs_get_uri_from_local_path + (efile->filename); + + gnome_vfs_monitor_add (&(h->handle), + uri, + GNOME_VFS_MONITOR_FILE, + file_monitor, + h); + + g_free (uri); + } else if (h->handle == NULL && + dirfile != NULL) { + char *uri = gnome_vfs_get_uri_from_local_path (dirfile); + + gnome_vfs_monitor_add (&(h->handle), + uri, + GNOME_VFS_MONITOR_FILE, + file_monitor, + h); + + g_free (uri); + } + + g_free (dirfile); + } + + try_free_file_monitors_create_files_unlocked (info); +} + +static gboolean /* unlocked */ +vfolder_info_read_items (VFolderInfo *info, + GnomeVFSResult *result, + GnomeVFSContext *context) +{ + GSList *li; + + /* First merge */ + for (li = info->merge_dirs; li != NULL; li = li->next) { + const char *merge_dir = li->data; + + if ( ! vfolder_info_read_items_merge (info, merge_dir, NULL, FALSE, + result, context)) + return FALSE; + } + + /* Then read the real thing (later overrides) */ + for (li = info->item_dirs; li != NULL; li = li->next) { + const char *item_dir = li->data; + + if ( ! vfolder_info_read_items_from (info, item_dir, + FALSE /* per_user */, + result, context)) + return FALSE; + } + + if (info->user_item_dir != NULL) { + if ( ! vfolder_info_read_items_from (info, + info->user_item_dir, + TRUE /* per_user */, + result, context)) + return FALSE; + } + + rescan_monitors (info); + + return TRUE; +} + +static gboolean +string_slist_equal (GSList *list1, GSList *list2) +{ + GSList *li1, *li2; + + for (li1 = list1, li2 = list2; + li1 != NULL && li2 != NULL; + li1 = li1->next, li2 = li2->next) { + const char *s1 = li1->data; + const char *s2 = li2->data; + if (strcmp (s1, s2) != 0) + return FALSE; + } + /* if both are not NULL, then lengths are + * different */ + if (li1 != li2) + return FALSE; + return TRUE; +} + +static gboolean +safe_string_same (const char *string1, const char *string2) +{ + if (string1 == string2 && + string1 == NULL) + return TRUE; + + if (string1 != NULL && string2 != NULL && + strcmp (string1, string2) == 0) + return TRUE; + + return FALSE; +} + +static gboolean +vfolder_info_item_dirs_same (VFolderInfo *info1, VFolderInfo *info2) +{ + if ( ! string_slist_equal (info1->item_dirs, + info2->item_dirs)) + return FALSE; + + if ( ! string_slist_equal (info1->merge_dirs, + info2->merge_dirs)) + return FALSE; + + if ( ! safe_string_same (info1->user_item_dir, + info2->user_item_dir)) + return FALSE; + + return TRUE; +} + +static gboolean +vfolder_info_reload_unlocked (VFolderInfo *info, + GnomeVFSResult *result, + GnomeVFSContext *context, + gboolean force_read_items) +{ + VFolderInfo *newinfo; + gboolean setup_filenames; + gboolean setup_itemdirs; + GSList *li; + + /* FIXME: Hmmm, race, there is no locking YAIKES, + * we need filename locking for changes. eek, eek, eek */ + if (info->dirty) { + return TRUE; + } + + newinfo = g_new0 (VFolderInfo, 1); + vfolder_info_init (newinfo, info->scheme); + + g_free (newinfo->filename); + g_free (newinfo->user_filename); + newinfo->filename = g_strdup (info->filename); + newinfo->user_filename = g_strdup (info->user_filename); + + if (gnome_vfs_context_check_cancellation (context)) { + vfolder_info_destroy (newinfo); + *result = GNOME_VFS_ERROR_CANCELLED; + return FALSE; + } + + if ( ! vfolder_info_read_info (newinfo, result, context)) { + vfolder_info_destroy (newinfo); + return FALSE; + } + + /* FIXME: reload logic for 'desktop_dir' and + * 'user_desktop_dir' */ + + setup_itemdirs = TRUE; + + /* Validity of entries and item dirs and all that is unchanged */ + if (vfolder_info_item_dirs_same (info, newinfo)) { + newinfo->entries = info->entries; + info->entries = NULL; + newinfo->entries_ht = info->entries_ht; + info->entries_ht = NULL /* some places assume this + non-null, but we're only + going to destroy this */; + newinfo->entries_valid = info->entries_valid; + + /* move over the monitors/statlocs since those are valid */ + newinfo->item_dir_monitors = info->item_dir_monitors; + info->item_dir_monitors = NULL; + newinfo->stat_dirs = info->stat_dirs; + info->stat_dirs = NULL; + + /* No need to resetup dir monitors */ + setup_itemdirs = FALSE; + + /* No need to do anything with file monitors */ + } else { + /* Whack all monitors here! */ + for (li = info->file_monitors; li != NULL; li = li->next) { + FileMonitorHandle *h = li->data; + if (h->handle != NULL) + gnome_vfs_monitor_cancel (h->handle); + h->handle = NULL; + } + } + + setup_filenames = TRUE; + + if (safe_string_same (info->filename, newinfo->filename) && + safe_string_same (info->user_filename, newinfo->user_filename)) { + newinfo->user_filename_last_write = + info->user_filename_last_write; + + /* move over the monitors/statlocs since those are valid */ + newinfo->filename_monitor = info->filename_monitor; + info->filename_monitor = NULL; + newinfo->user_filename_monitor = info->user_filename_monitor; + info->user_filename_monitor = NULL; + + if (info->filename_statloc != NULL && + info->filename != NULL) + newinfo->filename_statloc = + bake_statloc (info->filename, + time (NULL)); + if (info->user_filename_statloc != NULL && + info->user_filename != NULL) + newinfo->user_filename_statloc = + bake_statloc (info->user_filename, + time (NULL)); + + /* No need to resetup filename monitors */ + setup_filenames = FALSE; + } + + /* Note: not cancellable anymore, since we've + * already started nibbling on the info structure, + * so we'd need to back things out or some such, + * too complex, so screw that */ + monitor_setup (info, + setup_filenames, + setup_itemdirs, + /* FIXME: setup_desktop_dirs */ TRUE, + NULL, NULL); + + for (li = info->folder_monitors; + li != NULL; + li = li->next) { + FileMonitorHandle *handle = li->data; + li->data = NULL; + + add_folder_monitor_unlocked (newinfo, handle); + + file_monitor_handle_unref_unlocked (handle); + } + g_slist_free (info->folder_monitors); + info->folder_monitors = NULL; + + g_slist_foreach (info->free_folder_monitors, + (GFunc)file_monitor_handle_unref_unlocked, NULL); + g_slist_free (info->free_folder_monitors); + info->folder_monitors = NULL; + + /* we can just copy these for now, they will be readded + * and all the fun stuff will be done with them later */ + newinfo->file_monitors = info->file_monitors; + info->file_monitors = NULL; + newinfo->free_file_monitors = info->free_file_monitors; + info->free_file_monitors = NULL; + + /* emit changed on all folders, a bit drastic, but oh well, + * we also invalidate all folders at the same time, but that is + * irrelevant since they should all just be invalid to begin with */ + invalidate_folder_T (info->root); + + /* FIXME: make sure if this was enough, I think it was */ + + vfolder_info_free_internals_unlocked (info); + memcpy (info, newinfo, sizeof (VFolderInfo)); + g_free (newinfo); + + /* must rescan the monitors here */ + if (info->entries_valid) { + rescan_monitors (info); + } + + if ( ! info->entries_valid && + force_read_items) { + GnomeVFSResult res; + /* FIXME: I bet cancelation plays havoc with monitors, + * I'm not sure however */ + if (info->file_monitors != NULL) { + vfolder_info_read_items (info, &res, NULL); + } else { + if ( ! vfolder_info_read_items (info, result, context)) + return FALSE; + } + info->entries_valid = TRUE; + } + + return TRUE; +} + +static gboolean +vfolder_info_reload (VFolderInfo *info, + GnomeVFSResult *result, + GnomeVFSContext *context, + gboolean force_read_items) +{ + G_LOCK (vfolder_lock); + if (vfolder_info_reload_unlocked (info, result, context, + force_read_items)) { + G_UNLOCK (vfolder_lock); + return TRUE; + } else { + G_UNLOCK (vfolder_lock); + return FALSE; + } +} + +static gboolean +vfolder_info_recheck (VFolderInfo *info, + GnomeVFSResult *result, + GnomeVFSContext *context) +{ + GSList *li; + time_t curtime = time (NULL); + gboolean reread = FALSE; + + if (info->filename_statloc != NULL && + ! check_statloc (info->filename_statloc, curtime)) { + if ( ! vfolder_info_reload_unlocked (info, result, context, + FALSE /* force read items */)) { + /* we have failed, make sure we fail + * next time too */ + info->filename_statloc->trigger_next = TRUE; + return FALSE; + } + reread = TRUE; + } + if ( ! reread && + info->user_filename_statloc != NULL && + ! check_statloc (info->user_filename_statloc, curtime)) { + if ( ! vfolder_info_reload_unlocked (info, result, context, + FALSE /* force read items */)) { + /* we have failed, make sure we fail + * next time too */ + info->user_filename_statloc->trigger_next = TRUE; + return FALSE; + } + reread = TRUE; + } + + if (info->entries_valid) { + for (li = info->stat_dirs; li != NULL; li = li->next) { + StatLoc *sl = li->data; + if ( ! check_statloc (sl, curtime)) { + info->entries_valid = FALSE; + break; + } + } + } + return TRUE; +} + +static VFolderInfo * +get_vfolder_info_unlocked (const char *scheme, + GnomeVFSResult *result, + GnomeVFSContext *context) +{ + VFolderInfo *info; + + if (infos != NULL && + (info = g_hash_table_lookup (infos, scheme)) != NULL) { + if ( ! vfolder_info_recheck (info, result, context)) { + return NULL; + } + if ( ! info->entries_valid) { + g_slist_foreach (info->entries, + (GFunc)entry_unref, NULL); + g_slist_free (info->entries); + info->entries = NULL; + + if (info->entries_ht != NULL) + g_hash_table_destroy (info->entries_ht); + info->entries_ht = g_hash_table_new (g_str_hash, + g_str_equal); + + if ( ! vfolder_info_read_items (info, + result, context)) { + info->entries_valid = FALSE; + return NULL; + } + + invalidate_folder_T (info->root); + + info->entries_valid = TRUE; + } + return info; + } + + if (gnome_vfs_context_check_cancellation (context)) { + *result = GNOME_VFS_ERROR_CANCELLED; + return NULL; + } + + if (infos == NULL) + infos = g_hash_table_new_full + (g_str_hash, g_str_equal, + (GDestroyNotify)g_free, + (GDestroyNotify)vfolder_info_destroy); + + info = g_new0 (VFolderInfo, 1); + vfolder_info_init (info, scheme); + + if (gnome_vfs_context_check_cancellation (context)) { + vfolder_info_destroy (info); + *result = GNOME_VFS_ERROR_CANCELLED; + return NULL; + } + + if ( ! vfolder_info_read_info (info, result, context)) { + vfolder_info_destroy (info); + return NULL; + } + + if ( ! monitor_setup (info, + TRUE /* setup_filenames */, + TRUE /* setup_itemdirs */, + TRUE /* setup_desktop_dirs */, + result, context)) { + vfolder_info_destroy (info); + return NULL; + } + + g_hash_table_insert (infos, g_strdup (scheme), info); + + if ( ! vfolder_info_read_items (info, result, context)) { + info->entries_valid = FALSE; + return NULL; + } + info->entries_valid = TRUE; + + return info; +} + +static VFolderInfo * +get_vfolder_info (const char *scheme, + GnomeVFSResult *result, + GnomeVFSContext *context) +{ + VFolderInfo *info; + G_LOCK (vfolder_lock); + info = get_vfolder_info_unlocked (scheme, result, context); + G_UNLOCK (vfolder_lock); + return info; +} + + +static char * +keywords_to_string (GSList *keywords) +{ + GSList *li; + GString *str = g_string_new (NULL); + + for (li = keywords; li != NULL; li = li->next) { + GQuark word = GPOINTER_TO_INT (li->data); + g_string_append (str, g_quark_to_string (word)); + g_string_append_c (str, ';'); + } + + return g_string_free (str, FALSE); +} + +/* copy file and add keywords line */ +static gboolean +copy_file_with_keywords (const char *from, const char *to, GSList *keywords) +{ + FILE *fp; + FILE *wfp; + int wfd; + char buf[BUFSIZ]; + char *keyword_string; + + if ( ! ensure_dir (to, + TRUE /* ignore_basename */)) + return FALSE; + + wfd = open (to, O_CREAT | O_WRONLY | O_TRUNC, 0600); + if (wfd < 0) { + return FALSE; + } + + keyword_string = keywords_to_string (keywords); + + wfp = fdopen (wfd, "w"); + + fp = fopen (from, "r"); + if (fp != NULL) { + gboolean wrote_keywords = FALSE; + while (fgets (buf, sizeof (buf), fp) != NULL) { + fprintf (wfp, "%s", buf); + if ( ! wrote_keywords && + (strncmp (buf, "[Desktop Entry]", + strlen ("[Desktop Entry]")) == 0 || + strncmp (buf, "[KDE Desktop Entry]", + strlen ("[KDE Desktop Entry]")) == 0)) { + fprintf (wfp, "Categories=%s\n", + keyword_string); + wrote_keywords = TRUE; + } + } + + fclose (fp); + } else { + fprintf (wfp, "[Desktop Entry]\nCategories=%s\n", + keyword_string); + } + + /* FIXME: does this close wfd???? */ + fclose (wfp); + + close (wfd); + + g_free (keyword_string); + + return TRUE; +} + +static gboolean +copy_file (const char *from, const char *to) +{ + int fd; + int wfd; + + if ( ! ensure_dir (to, + TRUE /* ignore_basename */)) + return FALSE; + + wfd = open (to, O_CREAT | O_WRONLY | O_TRUNC, 0600); + if (wfd < 0) { + return FALSE; + } + + fd = open (from, O_RDONLY); + if (fd >= 0) { + char buf[1024]; + ssize_t n; + + while ((n = read (fd, buf, sizeof(buf))) > 0) { + write (wfd, buf, n); + } + + close (fd); + } + + close (wfd); + + return TRUE; +} + +static gboolean +make_file_private (VFolderInfo *info, EntryFile *efile) +{ + char *newfname; + Entry *entry = (Entry *)efile; + + if (efile->per_user) + return TRUE; + + /* this file already exists so whack its monitors */ + if (efile->filename != NULL) { + GSList *li; + + for (li = entry->monitors; li != NULL; li = li->next) { + FileMonitorHandle *h = li->data; + if (h->handle != NULL) + gnome_vfs_monitor_cancel (h->handle); + h->handle = NULL; + } + } + + newfname = g_build_filename (g_get_home_dir (), + DOT_GNOME, + "vfolders", + info->scheme, + efile->entry.name, + NULL); + + if (efile->implicit_keywords) { + if (efile->filename != NULL && + ! copy_file_with_keywords (efile->filename, + newfname, + efile->keywords)) { + /* FIXME: what to do with monitors here, they + * have already been whacked, a corner case + * not handled! */ + g_free (newfname); + return FALSE; + } + } else { + if (efile->filename != NULL && + ! copy_file (efile->filename, newfname)) { + /* FIXME: what to do with monitors here, they + * have already been whacked, a corner case + * not handled! */ + g_free (newfname); + return FALSE; + } + } + + /* we didn't copy but ensure path anyway */ + if (efile->filename == NULL && + ! ensure_dir (newfname, + TRUE /* ignore_basename */)) { + g_free (newfname); + return FALSE; + } + + /* this file already exists so re-add monitors at the new location */ + if (efile->filename != NULL) { + GSList *li; + char *uri = gnome_vfs_get_uri_from_local_path (newfname); + + for (li = entry->monitors; li != NULL; li = li->next) { + FileMonitorHandle *h = li->data; + + gnome_vfs_monitor_add (&(h->handle), + uri, + GNOME_VFS_MONITOR_FILE, + file_monitor, + h); + } + + g_free (uri); + } + + g_free (efile->filename); + efile->filename = newfname; + efile->per_user = TRUE; + + return TRUE; +} + +static void +try_free_file_monitors_create_dirfile_unlocked (VFolderInfo *info, + Folder *folder) +{ + GSList *li, *list; + + list = g_slist_copy (info->free_file_monitors); + + for (li = list; li != NULL; li = li->next) { + FileMonitorHandle *handle = li->data; + Folder *f; + VFolderURI vuri; + GnomeVFSResult result; + + if ( ! handle->is_directory_file) + continue; + + /* Evil! EVIL URI PARSING. this will eat a lot of stack if we + * have lots of free monitors */ + + VFOLDER_URI_PARSE (handle->uri, &vuri); + + f = resolve_folder (info, + vuri.path, + TRUE /* ignore_basename */, + &result, + NULL); + + if (folder != f) + continue; + + info->free_file_monitors = + g_slist_remove (info->free_file_monitors, handle); + ((Entry *)folder)->monitors = + g_slist_prepend (((Entry *)folder)->monitors, handle); + + handle->exists = TRUE; + gnome_vfs_monitor_callback ((GnomeVFSMethodHandle *)handle, + handle->uri, + GNOME_VFS_MONITOR_EVENT_CREATED); + } + + g_slist_free (list); +} + +static void +make_new_dirfile (VFolderInfo *info, Folder *folder) +{ + char *name = g_strdup (folder->entry.name); + char *fname; + char *p; + int i; + int fd; + + for (p = name; *p != '\0'; p++) { + if ( ! ( (*p >= 'a' && *p <= 'z') || + (*p >= 'A' && *p <= 'Z') || + (*p >= '0' && *p <= '9') || + *p == '_')) { + *p = '_'; + } + } + + i = 0; + fname = NULL; + do { + char *fullname; + + g_free (fname); + + if (i > 0) { + fname = g_strdup_printf ("%s-%d.directory", name, i); + } else { + fname = g_strdup_printf ("%s.directory", name); + } + + fullname = g_build_filename + (info->user_desktop_dir, fname, NULL); + fd = open (fullname, O_CREAT | O_WRONLY | O_EXCL, 0600); + g_free (fullname); + } while (fd < 0); + + close (fd); + + folder->desktop_file = fname; + info->dirty = TRUE; + + try_free_file_monitors_create_dirfile_unlocked (info, folder); +} + +static gboolean +make_dirfile_private (VFolderInfo *info, Folder *folder) +{ + char *fname; + char *desktop_file; + GSList *li; + char *uri; + gboolean ret; + + if (info->user_desktop_dir == NULL) + return FALSE; + + if ( ! ensure_dir (info->user_desktop_dir, + FALSE /* ignore_basename */)) + return FALSE; + + + if (folder->desktop_file == NULL) { + make_new_dirfile (info, folder); + return TRUE; + } + + /* FIXME: this is broken! What if the desktop file exists + * in the local but there is a different (but with a same name) + * .directory in the system. */ + fname = g_build_filename (info->user_desktop_dir, + folder->desktop_file, + NULL); + + if (access (fname, F_OK) == 0) { + g_free (fname); + return TRUE; + } + + desktop_file = get_directory_file (info, folder); + + if (desktop_file == NULL) { + int fd = open (fname, O_CREAT | O_EXCL | O_WRONLY, 0600); + g_free (fname); + if (fd >= 0) { + close (fd); + return TRUE; + } + return FALSE; + } + + for (li = ((Entry *)folder)->monitors; li != NULL; li = li->next) { + FileMonitorHandle *h = li->data; + if (h->is_directory_file) { + if (h->handle != NULL) + gnome_vfs_monitor_cancel (h->handle); + h->handle = NULL; + } + } + + ret = TRUE; + + if ( ! copy_file (desktop_file, fname)) { + ret = FALSE; + g_free (fname); + fname = desktop_file; + desktop_file = NULL; + } + + uri = gnome_vfs_get_uri_from_local_path (fname); + + for (li = ((Entry *)folder)->monitors; li != NULL; li = li->next) { + FileMonitorHandle *h = li->data; + + if (h->is_directory_file) { + gnome_vfs_monitor_add (&(h->handle), + uri, + GNOME_VFS_MONITOR_FILE, + file_monitor, + h); + } + } + + g_free (uri); + + g_free (desktop_file); + g_free (fname); + + return ret; +} + +static Folder * +resolve_folder (VFolderInfo *info, + const char *path, + gboolean ignore_basename, + GnomeVFSResult *result, + GnomeVFSContext *context) +{ + char **ppath; + int i; + Folder *folder = info->root; + + ppath = g_strsplit (path, "/", -1); + + if (ppath == NULL || + ppath[0] == NULL) { + g_strfreev (ppath); + *result = GNOME_VFS_ERROR_INVALID_URI; + return NULL; + } + + for (i = 0; ppath [i] != NULL; i++) { + const char *segment = ppath[i]; + + if (*segment == '\0') + continue; + + if (ignore_basename && ppath [i + 1] == NULL) + break; + else { + folder = (Folder *) find_entry (folder->subfolders, + segment); + if (folder == NULL) + break; + } + } + g_strfreev (ppath); + + if (gnome_vfs_context_check_cancellation (context)) { + *result = GNOME_VFS_ERROR_CANCELLED; + return NULL; + } + + if (folder == NULL) + *result = GNOME_VFS_ERROR_NOT_FOUND; + + return folder; +} + +static Entry * +resolve_path (VFolderInfo *info, + const char *path, + const char *basename, + Folder **return_folder, + GnomeVFSResult *result, + GnomeVFSContext *context) +{ + Entry *entry; + Folder *folder; + + if (strcmp (path, "/") == 0) + return (Entry *)info->root; + + folder = resolve_folder (info, path, + TRUE /* ignore_basename */, + result, context); + + if (return_folder != NULL) + *return_folder = folder; + + if (folder == NULL) { + return NULL; + } + + /* Make sure we have the entries here */ + ensure_folder_unlocked (info, folder, + FALSE /* subfolders */, + NULL /* except */, + FALSE /* ignore_unallocated */); + + entry = find_entry (folder->entries, basename); + + if (entry == NULL) + *result = GNOME_VFS_ERROR_NOT_FOUND; + + return entry; +} + +static Entry * +get_entry_unlocked (VFolderURI *vuri, + Folder **parent, + gboolean *is_directory_file, + GnomeVFSResult *result, + GnomeVFSContext *context) +{ + VFolderInfo *info; + Entry *entry; + + if (is_directory_file != NULL) + *is_directory_file = FALSE; + if (parent != NULL) + *parent = NULL; + + info = get_vfolder_info_unlocked (vuri->scheme, result, context); + if (info == NULL) + return NULL; + + if (gnome_vfs_context_check_cancellation (context)) { + *result = GNOME_VFS_ERROR_CANCELLED; + return NULL; + } + + if (vuri->is_all_scheme) { + GSList *efile_list; + + if (vuri->file == NULL) { + entry = resolve_path (info, + vuri->path, + vuri->file, + parent, + result, + context); + return entry; + } + + efile_list = g_hash_table_lookup (info->entries_ht, vuri->file); + + if (efile_list == NULL) { + *result = GNOME_VFS_ERROR_NOT_FOUND; + return NULL; + } else { + return efile_list->data; + } + } + + if (vuri->file != NULL && + check_ext (vuri->file, ".directory") == TRUE) { + Folder *folder; + + folder = resolve_folder (info, vuri->path, + TRUE /* ignore_basename */, + result, context); + if (folder == NULL) { + return NULL; + } + + if (is_directory_file != NULL) + *is_directory_file = TRUE; + + if (parent != NULL) + *parent = folder; + + return (Entry *)folder; + } else { + entry = resolve_path (info, vuri->path, vuri->file, parent, + result, context); + return entry; + } +} + +static Entry * +get_entry (VFolderURI *vuri, + Folder **parent, + gboolean *is_directory_file, + GnomeVFSResult *result, + GnomeVFSContext *context) +{ + Entry *entry; + + G_LOCK (vfolder_lock); + entry = get_entry_unlocked (vuri, + parent, + is_directory_file, + result, context); + G_UNLOCK (vfolder_lock); + + return entry; +} + +/* only works for files and only those that exist */ +/* unlocked function */ +static GnomeVFSURI * +desktop_uri_to_file_uri (VFolderInfo *info, + VFolderURI *desktop_vuri, + Entry **the_entry, + gboolean *the_is_directory_file, + Folder **the_folder, + gboolean privatize, + GnomeVFSResult *result, + GnomeVFSContext *context) +{ + gboolean is_directory_file; + GnomeVFSURI *ret_uri; + Folder *folder = NULL; + Entry *entry; + + entry = get_entry_unlocked (desktop_vuri, + &folder, + &is_directory_file, + result, + context); + if (entry == NULL) + return NULL; + + if (gnome_vfs_context_check_cancellation (context)) { + *result = GNOME_VFS_ERROR_CANCELLED; + return NULL; + } + + if (the_folder != NULL) + *the_folder = folder; + + if (the_entry != NULL) + *the_entry = entry; + if (the_is_directory_file != NULL) + *the_is_directory_file = is_directory_file; + + if (is_directory_file && + entry->type == ENTRY_FOLDER) { + char *desktop_file; + + folder = (Folder *)entry; + + if (the_folder != NULL) + *the_folder = folder; + + /* we'll be doing something write like */ + if (folder->read_only && + privatize) { + *result = GNOME_VFS_ERROR_READ_ONLY; + return NULL; + } + + if (privatize) { + char *fname; + + if (gnome_vfs_context_check_cancellation (context)) { + *result = GNOME_VFS_ERROR_CANCELLED; + return NULL; + } + + if ( ! make_dirfile_private (info, folder)) { + *result = GNOME_VFS_ERROR_GENERIC; + return NULL; + } + fname = g_build_filename (g_get_home_dir (), + folder->desktop_file, + NULL); + ret_uri = gnome_vfs_uri_new (fname); + g_free (fname); + return ret_uri; + } + + desktop_file = get_directory_file_unlocked (info, folder); + if (desktop_file != NULL) { + char *s = gnome_vfs_get_uri_from_local_path + (desktop_file); + + g_free (desktop_file); + + ret_uri = gnome_vfs_uri_new (s); + g_free (s); + + return ret_uri; + } else { + *result = GNOME_VFS_ERROR_NOT_FOUND; + return NULL; + } + } else if (entry->type == ENTRY_FILE) { + EntryFile *efile = (EntryFile *)entry; + char *s; + + /* we'll be doing something write like */ + if (folder != NULL && + folder->read_only && + privatize) { + *result = GNOME_VFS_ERROR_READ_ONLY; + return NULL; + } + + if (gnome_vfs_context_check_cancellation (context)) { + *result = GNOME_VFS_ERROR_CANCELLED; + return NULL; + } + + if (privatize && + ! make_file_private (info, efile)) { + *result = GNOME_VFS_ERROR_GENERIC; + return NULL; + } + + s = gnome_vfs_get_uri_from_local_path (efile->filename); + ret_uri = gnome_vfs_uri_new (s); + g_free (s); + + return ret_uri; + } else { + if (the_folder != NULL) + *the_folder = (Folder *)entry; + *result = GNOME_VFS_ERROR_IS_DIRECTORY; + return NULL; + } +} + +static void +remove_file (Folder *folder, const char *basename) +{ + GSList *li; + char *s; + + if (folder->includes_ht != NULL) { + li = g_hash_table_lookup (folder->includes_ht, basename); + if (li != NULL) { + char *name = li->data; + folder->includes = g_slist_delete_link + (folder->includes, li); + g_hash_table_remove (folder->includes_ht, basename); + g_free (name); + } + } + + if (folder->excludes == NULL) { + folder->excludes = g_hash_table_new_full + (g_str_hash, g_str_equal, + (GDestroyNotify)g_free, + NULL); + } + s = g_strdup (basename); + g_hash_table_replace (folder->excludes, s, s); +} + +static void +add_file (Folder *folder, const char *basename) +{ + GSList *li = NULL; + + if (folder->includes_ht != NULL) { + li = g_hash_table_lookup (folder->includes_ht, basename); + } + + /* if not found */ + if (li == NULL) { + char *str = g_strdup (basename); + folder->includes = + g_slist_prepend (folder->includes, str); + if (folder->includes_ht == NULL) { + folder->includes_ht = + g_hash_table_new_full (g_str_hash, + g_str_equal, + NULL, + NULL); + } + g_hash_table_replace (folder->includes_ht, + str, folder->includes); + } + if (folder->excludes != NULL) + g_hash_table_remove (folder->excludes, basename); +} + +typedef struct _FileHandle FileHandle; +struct _FileHandle { + VFolderInfo *info; + GnomeVFSMethodHandle *handle; + Entry *entry; + gboolean write; + gboolean is_directory_file; +}; + +static void +make_handle (GnomeVFSMethodHandle **method_handle, + GnomeVFSMethodHandle *file_handle, + VFolderInfo *info, + Entry *entry, + gboolean is_directory_file, + gboolean write) +{ + if (file_handle != NULL) { + FileHandle *handle = g_new0 (FileHandle, 1); + + handle->info = info; + handle->handle = file_handle; + handle->entry = entry_ref (entry); + handle->is_directory_file = is_directory_file; + handle->write = write; + + *method_handle = (GnomeVFSMethodHandle *) handle; + } else { + *method_handle = NULL; + } +} + +static void +whack_handle (FileHandle *handle) +{ + entry_unref (handle->entry); + handle->entry = NULL; + + handle->handle = NULL; + handle->info = NULL; + + g_free (handle); +} + +static GnomeVFSResult +do_open (GnomeVFSMethod *method, + GnomeVFSMethodHandle **method_handle, + GnomeVFSURI *uri, + GnomeVFSOpenMode mode, + GnomeVFSContext *context) +{ + GnomeVFSURI *file_uri; + GnomeVFSResult result = GNOME_VFS_OK; + VFolderInfo *info; + Entry *entry; + gboolean is_directory_file; + GnomeVFSMethodHandle *file_handle = NULL; + VFolderURI vuri; + + VFOLDER_URI_PARSE (uri, &vuri); + + /* These can't be very nice FILE names */ + if (vuri.file == NULL || + vuri.ends_in_slash) + return GNOME_VFS_ERROR_INVALID_URI; + + info = get_vfolder_info (vuri.scheme, &result, context); + if (info == NULL) + return result; + + if (mode & GNOME_VFS_OPEN_WRITE && + (info->read_only || vuri.is_all_scheme)) + return GNOME_VFS_ERROR_READ_ONLY; + + G_LOCK (vfolder_lock); + file_uri = desktop_uri_to_file_uri (info, + &vuri, + &entry, + &is_directory_file, + NULL /* the_folder */, + mode & GNOME_VFS_OPEN_WRITE, + &result, + context); + + if (file_uri == NULL) { + G_UNLOCK (vfolder_lock); + return result; + } + + result = (* parent_method->open) (parent_method, + &file_handle, + file_uri, + mode, + context); + + if (result == GNOME_VFS_ERROR_CANCELLED) { + G_UNLOCK (vfolder_lock); + gnome_vfs_uri_unref (file_uri); + return result; + } + + make_handle (method_handle, + file_handle, + info, + entry, + is_directory_file, + mode & GNOME_VFS_OPEN_WRITE); + + gnome_vfs_uri_unref (file_uri); + + if (info->dirty) { + vfolder_info_write_user (info); + } + + G_UNLOCK (vfolder_lock); + + return result; +} + +static void +remove_from_all_except (Folder *root, + const char *name, + Folder *except) +{ + GSList *li; + + if (root != except) { + remove_file (root, name); + if (root->up_to_date) { + for (li = root->entries; li != NULL; li = li->next) { + Entry *entry = li->data; + if (strcmp (name, entry->name) == 0) { + root->entries = + g_slist_delete_link + (root->entries, li); + break; + } + } + } + } + + for (li = root->subfolders; li != NULL; li = li->next) { + Folder *subfolder = li->data; + + remove_from_all_except (subfolder, name, except); + } +} + +static GnomeVFSResult +do_create (GnomeVFSMethod *method, + GnomeVFSMethodHandle **method_handle, + GnomeVFSURI *uri, + GnomeVFSOpenMode mode, + gboolean exclusive, + guint perm, + GnomeVFSContext *context) +{ + GnomeVFSResult result = GNOME_VFS_OK; + GnomeVFSMethodHandle *file_handle; + GnomeVFSURI *file_uri; + VFolderURI vuri; + VFolderInfo *info; + Folder *parent; + Entry *entry; + EntryFile *efile; + char *s; + GSList *li; + + VFOLDER_URI_PARSE (uri, &vuri); + + /* These can't be very nice FILE names */ + if (vuri.file == NULL || + vuri.ends_in_slash) + return GNOME_VFS_ERROR_INVALID_URI; + + if ( ! check_ext (vuri.file, ".desktop") && + ! strcmp (vuri.file, ".directory") == 0) { + return GNOME_VFS_ERROR_INVALID_URI; + } + + /* all scheme is read only */ + if (vuri.is_all_scheme) + return GNOME_VFS_ERROR_READ_ONLY; + + info = get_vfolder_info (vuri.scheme, &result, context); + if (info == NULL) + return result; + + if (info->user_filename == NULL || + info->read_only) + return GNOME_VFS_ERROR_READ_ONLY; + + parent = resolve_folder (info, vuri.path, + TRUE /* ignore_basename */, + &result, context); + if (parent == NULL) + return result; + + if (parent->read_only) + return GNOME_VFS_ERROR_READ_ONLY; + + if (strcmp (vuri.file, ".directory") == 0) { + char *fname; + + G_LOCK (vfolder_lock); + + if (exclusive) { + char *desktop_file; + desktop_file = get_directory_file_unlocked (info, parent); + if (desktop_file != NULL) { + g_free (desktop_file); + G_UNLOCK (vfolder_lock); + return GNOME_VFS_ERROR_FILE_EXISTS; + } + } + + if ( ! make_dirfile_private (info, parent)) { + G_UNLOCK (vfolder_lock); + return GNOME_VFS_ERROR_GENERIC; + } + fname = g_build_filename (g_get_home_dir (), + parent->desktop_file, + NULL); + s = gnome_vfs_get_uri_from_local_path (fname); + file_uri = gnome_vfs_uri_new (s); + g_free (fname); + g_free (s); + + if (file_uri == NULL) { + G_UNLOCK (vfolder_lock); + return GNOME_VFS_ERROR_GENERIC; + } + + result = (* parent_method->create) (parent_method, + &file_handle, + file_uri, + mode, + exclusive, + perm, + context); + gnome_vfs_uri_unref (file_uri); + + make_handle (method_handle, + file_handle, + info, + (Entry *)parent, + TRUE /* is_directory_file */, + TRUE /* write */); + + if (info->dirty) + vfolder_info_write_user (info); + + G_UNLOCK (vfolder_lock); + + return result; + } + + ensure_folder (info, parent, + FALSE /* subfolders */, + NULL /* except */, + FALSE /* ignore_unallocated */); + + entry = find_entry (parent->entries, vuri.file); + + if (entry != NULL && + entry->type == ENTRY_FOLDER) + return GNOME_VFS_ERROR_IS_DIRECTORY; + + efile = (EntryFile *)entry; + + if (efile != NULL) { + if (exclusive) + return GNOME_VFS_ERROR_FILE_EXISTS; + + G_LOCK (vfolder_lock); + if ( ! make_file_private (info, efile)) { + G_UNLOCK (vfolder_lock); + return GNOME_VFS_ERROR_GENERIC; + } + + s = gnome_vfs_get_uri_from_local_path (efile->filename); + file_uri = gnome_vfs_uri_new (s); + g_free (s); + + if (file_uri == NULL) { + G_UNLOCK (vfolder_lock); + return GNOME_VFS_ERROR_GENERIC; + } + + result = (* parent_method->create) (parent_method, + &file_handle, + file_uri, + mode, + exclusive, + perm, + context); + gnome_vfs_uri_unref (file_uri); + + make_handle (method_handle, + file_handle, + info, + (Entry *)efile, + FALSE /* is_directory_file */, + TRUE /* write */); + + G_UNLOCK (vfolder_lock); + + return result; + } + + G_LOCK (vfolder_lock); + + li = g_hash_table_lookup (info->entries_ht, vuri.file); + + if (exclusive && li != NULL) { + G_UNLOCK (vfolder_lock); + return GNOME_VFS_ERROR_FILE_EXISTS; + } + + if (li == NULL) { + efile = file_new (vuri.file); + vfolder_info_insert_entry (info, efile); + entry_unref ((Entry *)efile); + } else { + efile = li->data; + } + + /* this will make a private name for this */ + if ( ! make_file_private (info, efile)) { + G_UNLOCK (vfolder_lock); + return GNOME_VFS_ERROR_GENERIC; + } + + add_file (parent, vuri.file); + parent->sorted = FALSE; + + if (parent->up_to_date) + parent->entries = g_slist_prepend (parent->entries, efile); + + /* if we created a brand new name, then we exclude it + * from everywhere else to ensure overall sanity */ + if (li == NULL) + remove_from_all_except (info->root, vuri.file, parent); + + s = gnome_vfs_get_uri_from_local_path (efile->filename); + file_uri = gnome_vfs_uri_new (s); + g_free (s); + + result = (* parent_method->create) (parent_method, + &file_handle, + file_uri, + mode, + exclusive, + perm, + context); + gnome_vfs_uri_unref (file_uri); + + make_handle (method_handle, + file_handle, + info, + (Entry *)efile, + FALSE /* is_directory_file */, + TRUE /* write */); + + vfolder_info_write_user (info); + + G_UNLOCK (vfolder_lock); + + return result; +} + +static GnomeVFSResult +do_close (GnomeVFSMethod *method, + GnomeVFSMethodHandle *method_handle, + GnomeVFSContext *context) +{ + GnomeVFSResult result; + FileHandle *handle = (FileHandle *)method_handle; + if (method_handle == (GnomeVFSMethodHandle *)method) + return GNOME_VFS_OK; + + G_LOCK (vfolder_lock); + + result = (* parent_method->close) (parent_method, + handle->handle, + context); + handle->handle = NULL; + + /* we reread the Categories keyword */ + if (handle->write && + handle->entry != NULL && + handle->entry->type == ENTRY_FILE) { + EntryFile *efile = (EntryFile *)handle->entry; + char *categories; + readitem_entry (efile->filename, + "Categories", + &categories, + NULL, + NULL); + set_keywords (efile, categories); + g_free (categories); + /* FIXME: what about OnlyShowIn */ + + /* FIXME: check if the keywords changed, if not, do + * nothing */ + + /* Perhaps a bit drastic */ + /* also this emits the CHANGED monitor signal */ + invalidate_folder_T (handle->info->root); + + /* the file changed monitor will happen by itself + * as the underlying file is changed */ + } else if (handle->write && + handle->entry != NULL && + handle->entry->type == ENTRY_FOLDER && + handle->is_directory_file) { + /* if we're monitoring this directory, emit the CHANGED + * monitor thing, it will also emit a changed on + * the file itself. It is better to emit changed + * just in case. */ + emit_monitor ((Folder *)(handle->entry), + GNOME_VFS_MONITOR_EVENT_CHANGED); + } + + whack_handle (handle); + + G_UNLOCK (vfolder_lock); + + return result; +} + +static void +fill_buffer (gpointer buffer, + GnomeVFSFileSize num_bytes, + GnomeVFSFileSize *bytes_read) +{ + char *buf = buffer; + GnomeVFSFileSize i; + for (i = 0; i < num_bytes; i++) { + if (rand () % 32 == 0 || + i == num_bytes-1) + buf[i] = '\n'; + else + buf[i] = ((rand()>>4) % 94) + 32; + } + if (bytes_read != 0) + *bytes_read = i; +} + +static GnomeVFSResult +do_read (GnomeVFSMethod *method, + GnomeVFSMethodHandle *method_handle, + gpointer buffer, + GnomeVFSFileSize num_bytes, + GnomeVFSFileSize *bytes_read, + GnomeVFSContext *context) +{ + GnomeVFSResult result; + FileHandle *handle = (FileHandle *)method_handle; + + if (method_handle == (GnomeVFSMethodHandle *)method) { + if ((rand () >> 4) & 0x3) { + fill_buffer (buffer, num_bytes, bytes_read); + return GNOME_VFS_OK; + } else { + return GNOME_VFS_ERROR_EOF; + } + } + + result = (* parent_method->read) (parent_method, + handle->handle, + buffer, num_bytes, + bytes_read, + context); + + return result; +} + +static GnomeVFSResult +do_write (GnomeVFSMethod *method, + GnomeVFSMethodHandle *method_handle, + gconstpointer buffer, + GnomeVFSFileSize num_bytes, + GnomeVFSFileSize *bytes_written, + GnomeVFSContext *context) +{ + GnomeVFSResult result; + FileHandle *handle = (FileHandle *)method_handle; + + if (method_handle == (GnomeVFSMethodHandle *)method) + return GNOME_VFS_OK; + + result = (* parent_method->write) (parent_method, + handle->handle, + buffer, num_bytes, + bytes_written, + context); + + return result; +} + + +static GnomeVFSResult +do_seek (GnomeVFSMethod *method, + GnomeVFSMethodHandle *method_handle, + GnomeVFSSeekPosition whence, + GnomeVFSFileOffset offset, + GnomeVFSContext *context) +{ + GnomeVFSResult result; + FileHandle *handle = (FileHandle *)method_handle; + + if (method_handle == (GnomeVFSMethodHandle *)method) + return GNOME_VFS_OK; + + result = (* parent_method->seek) (parent_method, + handle->handle, + whence, offset, + context); + + return result; +} + +static GnomeVFSResult +do_tell (GnomeVFSMethod *method, + GnomeVFSMethodHandle *method_handle, + GnomeVFSFileOffset *offset_return) +{ + GnomeVFSResult result; + FileHandle *handle = (FileHandle *)method_handle; + + result = (* parent_method->tell) (parent_method, + handle->handle, + offset_return); + + return result; +} + + +static GnomeVFSResult +do_truncate_handle (GnomeVFSMethod *method, + GnomeVFSMethodHandle *method_handle, + GnomeVFSFileSize where, + GnomeVFSContext *context) +{ + GnomeVFSResult result; + FileHandle *handle = (FileHandle *)method_handle; + + if (method_handle == (GnomeVFSMethodHandle *)method) + return GNOME_VFS_OK; + + result = (* parent_method->truncate_handle) (parent_method, + handle->handle, + where, + context); + + return result; +} + +static GnomeVFSResult +do_truncate (GnomeVFSMethod *method, + GnomeVFSURI *uri, + GnomeVFSFileSize where, + GnomeVFSContext *context) +{ + GnomeVFSURI *file_uri; + GnomeVFSResult result = GNOME_VFS_OK; + VFolderInfo *info; + Entry *entry; + VFolderURI vuri; + + VFOLDER_URI_PARSE (uri, &vuri); + + /* These can't be very nice FILE names */ + if (vuri.file == NULL || + vuri.ends_in_slash) + return GNOME_VFS_ERROR_INVALID_URI; + + if (vuri.is_all_scheme) + return GNOME_VFS_ERROR_READ_ONLY; + + info = get_vfolder_info (vuri.scheme, &result, context); + if (info == NULL) + return result; + + if (info->read_only) + return GNOME_VFS_ERROR_READ_ONLY; + + G_LOCK (vfolder_lock); + file_uri = desktop_uri_to_file_uri (info, + &vuri, + &entry, + NULL /* the_is_directory_file */, + NULL /* the_folder */, + TRUE /* privatize */, + &result, + context); + G_UNLOCK (vfolder_lock); + + if (file_uri == NULL) + return result; + + result = (* parent_method->truncate) (parent_method, + file_uri, + where, + context); + + gnome_vfs_uri_unref (file_uri); + + if (info->dirty) { + G_LOCK (vfolder_lock); + vfolder_info_write_user (info); + G_UNLOCK (vfolder_lock); + } + + if (entry->type == ENTRY_FILE) { + EntryFile *efile = (EntryFile *)entry; + + G_LOCK (vfolder_lock); + g_slist_free (efile->keywords); + efile->keywords = NULL; + G_UNLOCK (vfolder_lock); + } + + /* Perhaps a bit drastic, but oh well */ + invalidate_folder (info->root); + + return result; +} + +typedef struct _DirHandle DirHandle; +struct _DirHandle { + VFolderInfo *info; + Folder *folder; + + GnomeVFSFileInfoOptions options; + + /* List of Entries */ + GSList *list; + GSList *current; +}; + +static GnomeVFSResult +do_open_directory (GnomeVFSMethod *method, + GnomeVFSMethodHandle **method_handle, + GnomeVFSURI *uri, + GnomeVFSFileInfoOptions options, + GnomeVFSContext *context) +{ + GnomeVFSResult result = GNOME_VFS_OK; + VFolderURI vuri; + DirHandle *dh; + Folder *folder; + VFolderInfo *info; + char *desktop_file; + + VFOLDER_URI_PARSE (uri, &vuri); + + info = get_vfolder_info (vuri.scheme, &result, context); + if (info == NULL) + return result; + + /* In the all- scheme just list all filenames */ + if (vuri.is_all_scheme) { + if (any_subdir (vuri.path)) + return GNOME_VFS_ERROR_NOT_FOUND; + + dh = g_new0 (DirHandle, 1); + dh->info = info; + dh->options = options; + dh->folder = NULL; + + G_LOCK (vfolder_lock); + dh->list = g_slist_copy (info->entries); + g_slist_foreach (dh->list, (GFunc)entry_ref, NULL); + dh->current = dh->list; + G_UNLOCK (vfolder_lock); + + *method_handle = (GnomeVFSMethodHandle*) dh; + return GNOME_VFS_OK; + } + + folder = resolve_folder (info, vuri.path, + FALSE /* ignore_basename */, + &result, context); + if (folder == NULL) + return result; + + /* Make sure we have the entries and sorted here */ + ensure_folder_sort (info, folder); + + dh = g_new0 (DirHandle, 1); + dh->info = info; + dh->options = options; + + G_LOCK (vfolder_lock); + dh->folder = (Folder *)entry_ref ((Entry *)folder); + dh->list = g_slist_copy (folder->entries); + g_slist_foreach (folder->entries, (GFunc)entry_ref, NULL); + G_UNLOCK (vfolder_lock); + + desktop_file = get_directory_file (info, folder); + if (desktop_file != NULL) { + EntryFile *efile = file_new (".directory"); + dh->list = g_slist_prepend (dh->list, efile); + g_free (desktop_file); + } + + dh->current = dh->list; + + *method_handle = (GnomeVFSMethodHandle*) dh; + + return GNOME_VFS_OK; +} + +static GnomeVFSResult +do_close_directory (GnomeVFSMethod *method, + GnomeVFSMethodHandle *method_handle, + GnomeVFSContext *context) +{ + DirHandle *dh; + + dh = (DirHandle*) method_handle; + + G_LOCK (vfolder_lock); + + g_slist_foreach (dh->list, (GFunc)entry_unref, NULL); + g_slist_free (dh->list); + dh->list = NULL; + + dh->current = NULL; + + if (dh->folder != NULL) + entry_unref ((Entry *)dh->folder); + dh->folder = NULL; + + dh->info = NULL; + + g_free (dh); + + G_UNLOCK (vfolder_lock); + + return GNOME_VFS_OK; +} + +static GnomeVFSResult +do_read_directory (GnomeVFSMethod *method, + GnomeVFSMethodHandle *method_handle, + GnomeVFSFileInfo *file_info, + GnomeVFSContext *context) +{ + DirHandle *dh; + Entry *entry; + GnomeVFSFileInfoOptions options; + + dh = (DirHandle*) method_handle; + +read_directory_again: + + if (dh->current == NULL) { + return GNOME_VFS_ERROR_EOF; + } + + entry = dh->current->data; + dh->current = dh->current->next; + + options = dh->options; + + if (entry->type == ENTRY_FILE && + ((EntryFile *)entry)->filename != NULL) { + EntryFile *efile = (EntryFile *)entry; + char *furi = gnome_vfs_get_uri_from_local_path (efile->filename); + GnomeVFSURI *uri = gnome_vfs_uri_new (furi); + + /* we always get mime-type by forcing it below */ + if (options & GNOME_VFS_FILE_INFO_GET_MIME_TYPE) + options &= ~GNOME_VFS_FILE_INFO_GET_MIME_TYPE; + + file_info->valid_fields = GNOME_VFS_FILE_INFO_FIELDS_NONE; + + /* Get the file info for this */ + (* parent_method->get_file_info) (parent_method, + uri, + file_info, + options, + context); + + /* we ignore errors from this since the file_info just + * won't be filled completely if there's an error, that's all */ + + g_free (file_info->mime_type); + file_info->mime_type = g_strdup ("application/x-gnome-app-info"); + file_info->valid_fields |= GNOME_VFS_FILE_INFO_FIELDS_MIME_TYPE; + + /* Now we wipe those fields we don't support */ + file_info->valid_fields &= ~(UNSUPPORTED_INFO_FIELDS); + + gnome_vfs_uri_unref (uri); + g_free (furi); + } else if (entry->type == ENTRY_FILE) { + file_info->valid_fields = GNOME_VFS_FILE_INFO_FIELDS_NONE; + + file_info->name = g_strdup (entry->name); + GNOME_VFS_FILE_INFO_SET_LOCAL (file_info, TRUE); + + file_info->type = GNOME_VFS_FILE_TYPE_REGULAR; + file_info->valid_fields |= GNOME_VFS_FILE_INFO_FIELDS_TYPE; + + /* FIXME: Is this correct? isn't there an xdg mime type? */ + file_info->mime_type = g_strdup ("application/x-gnome-app-info"); + file_info->valid_fields |= GNOME_VFS_FILE_INFO_FIELDS_MIME_TYPE; + + /* FIXME: get some ctime/mtime */ + } else /* ENTRY_FOLDER */ { + Folder *folder = (Folder *)entry; + + /* Skip empty folders if they have + * the flag set */ + if (folder->dont_show_if_empty) { + /* Make sure we have the entries */ + ensure_folder (dh->info, folder, + FALSE /* subfolders */, + NULL /* except */, + FALSE /* ignore_unallocated */); + + if (folder->entries == NULL) { + /* start this function over on the + * next item */ + goto read_directory_again; + } + } + + file_info->valid_fields = GNOME_VFS_FILE_INFO_FIELDS_NONE; + + file_info->name = g_strdup (entry->name); + GNOME_VFS_FILE_INFO_SET_LOCAL (file_info, TRUE); + + file_info->type = GNOME_VFS_FILE_TYPE_DIRECTORY; + file_info->valid_fields |= GNOME_VFS_FILE_INFO_FIELDS_TYPE; + + file_info->mime_type = g_strdup ("x-directory/vfolder-desktop"); + file_info->valid_fields |= GNOME_VFS_FILE_INFO_FIELDS_MIME_TYPE; + + file_info->ctime = dh->info->modification_time; + file_info->mtime = dh->info->modification_time; + file_info->valid_fields |= (GNOME_VFS_FILE_INFO_FIELDS_CTIME | + GNOME_VFS_FILE_INFO_FIELDS_MTIME); + } + + return GNOME_VFS_OK; +} + +static GnomeVFSResult +do_get_file_info (GnomeVFSMethod *method, + GnomeVFSURI *uri, + GnomeVFSFileInfo *file_info, + GnomeVFSFileInfoOptions options, + GnomeVFSContext *context) +{ + GnomeVFSURI *file_uri; + GnomeVFSResult result = GNOME_VFS_OK; + Folder *folder; + VFolderInfo *info; + VFolderURI vuri; + + VFOLDER_URI_PARSE (uri, &vuri); + + info = get_vfolder_info (vuri.scheme, &result, context); + if (info == NULL) + return result; + + G_LOCK (vfolder_lock); + file_uri = desktop_uri_to_file_uri (info, + &vuri, + NULL /* the_entry */, + NULL /* the_is_directory_file */, + &folder, + FALSE /* privatize */, + &result, + context); + G_UNLOCK (vfolder_lock); + + if (file_uri == NULL && + result != GNOME_VFS_ERROR_IS_DIRECTORY) + return result; + + if (file_uri != NULL) { + /* we always get mime-type by forcing it below */ + if (options & GNOME_VFS_FILE_INFO_GET_MIME_TYPE) + options &= ~GNOME_VFS_FILE_INFO_GET_MIME_TYPE; + + result = (* parent_method->get_file_info) (parent_method, + file_uri, + file_info, + options, + context); + + g_free (file_info->mime_type); + file_info->mime_type = g_strdup ("application/x-gnome-app-info"); + file_info->valid_fields |= GNOME_VFS_FILE_INFO_FIELDS_MIME_TYPE; + + /* Now we wipe those fields we don't support */ + file_info->valid_fields &= ~(UNSUPPORTED_INFO_FIELDS); + + gnome_vfs_uri_unref (file_uri); + + return result; + } else if (folder != NULL) { + file_info->valid_fields = GNOME_VFS_FILE_INFO_FIELDS_NONE; + + file_info->name = g_strdup (folder->entry.name); + GNOME_VFS_FILE_INFO_SET_LOCAL (file_info, TRUE); + + file_info->type = GNOME_VFS_FILE_TYPE_DIRECTORY; + file_info->valid_fields |= GNOME_VFS_FILE_INFO_FIELDS_TYPE; + + file_info->mime_type = g_strdup ("x-directory/vfolder-desktop"); + file_info->valid_fields |= GNOME_VFS_FILE_INFO_FIELDS_MIME_TYPE; + + file_info->ctime = info->modification_time; + file_info->mtime = info->modification_time; + file_info->valid_fields |= (GNOME_VFS_FILE_INFO_FIELDS_CTIME | + GNOME_VFS_FILE_INFO_FIELDS_MTIME); + + return GNOME_VFS_OK; + } else { + return GNOME_VFS_ERROR_NOT_FOUND; + } +} + +static GnomeVFSResult +do_get_file_info_from_handle (GnomeVFSMethod *method, + GnomeVFSMethodHandle *method_handle, + GnomeVFSFileInfo *file_info, + GnomeVFSFileInfoOptions options, + GnomeVFSContext *context) +{ + GnomeVFSResult result; + FileHandle *handle = (FileHandle *)method_handle; + + if (method_handle == (GnomeVFSMethodHandle *)method) { + g_free (file_info->mime_type); + file_info->mime_type = g_strdup ("text/plain"); + file_info->valid_fields |= GNOME_VFS_FILE_INFO_FIELDS_MIME_TYPE; + return GNOME_VFS_OK; + } + + /* we always get mime-type by forcing it below */ + if (options & GNOME_VFS_FILE_INFO_GET_MIME_TYPE) + options &= ~GNOME_VFS_FILE_INFO_GET_MIME_TYPE; + + result = (* parent_method->get_file_info_from_handle) (parent_method, + handle->handle, + file_info, + options, + context); + + /* any file is of the .desktop type */ + g_free (file_info->mime_type); + file_info->mime_type = g_strdup ("application/x-gnome-app-info"); + file_info->valid_fields |= GNOME_VFS_FILE_INFO_FIELDS_MIME_TYPE; + + /* Now we wipe those fields we don't support */ + file_info->valid_fields &= ~(UNSUPPORTED_INFO_FIELDS); + + return result; +} + + +static gboolean +do_is_local (GnomeVFSMethod *method, + const GnomeVFSURI *uri) +{ + return TRUE; +} + +static void +try_free_folder_monitors_create_unlocked (VFolderInfo *info, + Folder *folder) +{ + GSList *li, *list; + + list = g_slist_copy (info->free_folder_monitors); + + for (li = list; li != NULL; li = li->next) { + FileMonitorHandle *handle = li->data; + Folder *f; + VFolderURI vuri; + GnomeVFSResult result; + + /* Evil! EVIL URI PARSING. this will eat a lot of stack if we + * have lots of free monitors */ + + VFOLDER_URI_PARSE (handle->uri, &vuri); + + f = resolve_folder (info, + vuri.path, + FALSE /* ignore_basename */, + &result, + NULL); + + if (folder != f) + continue; + + info->free_folder_monitors = + g_slist_remove (info->free_folder_monitors, handle); + ((Entry *)folder)->monitors = + g_slist_prepend (((Entry *)folder)->monitors, handle); + + handle->exists = TRUE; + gnome_vfs_monitor_callback ((GnomeVFSMethodHandle *)handle, + handle->uri, + GNOME_VFS_MONITOR_EVENT_CREATED); + } +} + + +static GnomeVFSResult +do_make_directory (GnomeVFSMethod *method, + GnomeVFSURI *uri, + guint perm, + GnomeVFSContext *context) +{ + GnomeVFSResult result = GNOME_VFS_OK; + VFolderInfo *info; + Folder *parent, *folder; + VFolderURI vuri; + + VFOLDER_URI_PARSE (uri, &vuri); + + if (vuri.is_all_scheme) + return GNOME_VFS_ERROR_READ_ONLY; + + info = get_vfolder_info (vuri.scheme, &result, context); + if (info == NULL) + return result; + + if (info->user_filename == NULL || + info->read_only) + return GNOME_VFS_ERROR_READ_ONLY; + + parent = resolve_folder (info, vuri.path, + TRUE /* ignore_basename */, + &result, context); + if (parent == NULL) + return result; + else if (parent->read_only) + return GNOME_VFS_ERROR_READ_ONLY; + + G_LOCK (vfolder_lock); + + folder = (Folder *)find_entry (parent->subfolders, + vuri.file); + if (folder != NULL) { + G_UNLOCK (vfolder_lock); + return GNOME_VFS_ERROR_FILE_EXISTS; + } + + folder = folder_new (vuri.file); + parent->subfolders = g_slist_append (parent->subfolders, folder); + folder->parent = parent; + parent->up_to_date = FALSE; + + try_free_folder_monitors_create_unlocked (info, folder); + + /* parent changed */ + emit_monitor (parent, GNOME_VFS_MONITOR_EVENT_CHANGED); + + vfolder_info_write_user (info); + G_UNLOCK (vfolder_lock); + + return GNOME_VFS_OK; +} + +static GnomeVFSResult +do_remove_directory (GnomeVFSMethod *method, + GnomeVFSURI *uri, + GnomeVFSContext *context) +{ + GnomeVFSResult result = GNOME_VFS_OK; + Folder *folder; + VFolderInfo *info; + VFolderURI vuri; + + VFOLDER_URI_PARSE (uri, &vuri); + + if (vuri.is_all_scheme) + return GNOME_VFS_ERROR_READ_ONLY; + + info = get_vfolder_info (vuri.scheme, &result, context); + if (info == NULL) + return result; + + if (info->user_filename == NULL || + info->read_only) + return GNOME_VFS_ERROR_READ_ONLY; + + G_LOCK (vfolder_lock); + + folder = resolve_folder (info, vuri.path, + FALSE /* ignore_basename */, + &result, context); + if (folder == NULL) { + G_UNLOCK (vfolder_lock); + return result; + } + + if (folder->read_only || + (folder->parent != NULL && + folder->parent->read_only)) { + G_UNLOCK (vfolder_lock); + return GNOME_VFS_ERROR_READ_ONLY; + } + + /* don't make removing directories easy */ + if (folder->desktop_file != NULL) { + G_UNLOCK (vfolder_lock); + return GNOME_VFS_ERROR_DIRECTORY_NOT_EMPTY; + } + + /* Make sure we have the entries */ + ensure_folder_unlocked (info, folder, + FALSE /* subfolders */, + NULL /* except */, + FALSE /* ignore_unallocated */); + + /* don't make removing directories easy */ + if (folder->entries != NULL) { + G_UNLOCK (vfolder_lock); + return GNOME_VFS_ERROR_DIRECTORY_NOT_EMPTY; + } + + emit_and_delete_monitor (info, folder); + + if (folder->only_unallocated) { + GSList *li = g_slist_find (info->unallocated_folders, + folder); + if (li != NULL) { + info->unallocated_folders = g_slist_delete_link + (info->unallocated_folders, li); + entry_unref ((Entry *)folder); + } + } + + if (folder == info->root) { + info->root = NULL; + entry_unref ((Entry *)folder); + info->root = folder_new ("Root"); + } else { + Folder *parent = folder->parent; + + g_assert (parent != NULL); + + parent->subfolders = + g_slist_remove (parent->subfolders, folder); + + parent->up_to_date = FALSE; + + entry_unref ((Entry *)folder); + + /* parent changed */ + emit_monitor (parent, GNOME_VFS_MONITOR_EVENT_CHANGED); + } + + vfolder_info_write_user (info); + + G_UNLOCK (vfolder_lock); + + return GNOME_VFS_OK; +} + +/* a fairly evil function that does the whole move bit by copy and + * remove */ +static GnomeVFSResult +long_move (GnomeVFSMethod *method, + VFolderURI *old_vuri, + VFolderURI *new_vuri, + gboolean force_replace, + GnomeVFSContext *context) +{ + GnomeVFSResult result; + GnomeVFSMethodHandle *handle; + GnomeVFSURI *file_uri; + const char *path; + int fd; + char buf[BUFSIZ]; + int bytes; + VFolderInfo *info; + + info = get_vfolder_info (old_vuri->scheme, &result, context); + if (info == NULL) + return result; + + G_LOCK (vfolder_lock); + file_uri = desktop_uri_to_file_uri (info, + old_vuri, + NULL /* the_entry */, + NULL /* the_is_directory_file */, + NULL /* the_folder */, + FALSE /* privatize */, + &result, + context); + G_UNLOCK (vfolder_lock); + + if (file_uri == NULL) + return result; + + path = gnome_vfs_uri_get_path (file_uri); + if (path == NULL) { + gnome_vfs_uri_unref (file_uri); + return GNOME_VFS_ERROR_INVALID_URI; + } + + fd = open (path, O_RDONLY); + if (fd < 0) { + gnome_vfs_uri_unref (file_uri); + return gnome_vfs_result_from_errno (); + } + + gnome_vfs_uri_unref (file_uri); + + info->inhibit_write++; + + result = method->create (method, + &handle, + new_vuri->uri, + GNOME_VFS_OPEN_WRITE, + force_replace /* exclusive */, + 0600 /* perm */, + context); + if (result != GNOME_VFS_OK) { + close (fd); + info->inhibit_write--; + return result; + } + + while ((bytes = read (fd, buf, BUFSIZ)) > 0) { + GnomeVFSFileSize bytes_written = 0; + result = method->write (method, + handle, + buf, + bytes, + &bytes_written, + context); + if (result == GNOME_VFS_OK && + bytes_written != bytes) + result = GNOME_VFS_ERROR_NO_SPACE; + if (result != GNOME_VFS_OK) { + close (fd); + method->close (method, handle, context); + /* FIXME: is this completely correct ? */ + method->unlink (method, + new_vuri->uri, + context); + G_LOCK (vfolder_lock); + info->inhibit_write--; + vfolder_info_write_user (info); + G_UNLOCK (vfolder_lock); + return result; + } + } + + close (fd); + + result = method->close (method, handle, context); + if (result != GNOME_VFS_OK) { + G_LOCK (vfolder_lock); + info->inhibit_write--; + vfolder_info_write_user (info); + G_UNLOCK (vfolder_lock); + return result; + } + + result = method->unlink (method, old_vuri->uri, context); + + G_LOCK (vfolder_lock); + info->inhibit_write--; + vfolder_info_write_user (info); + G_UNLOCK (vfolder_lock); + + return result; +} + +static GnomeVFSResult +move_directory_file (VFolderInfo *info, + Folder *old_folder, + Folder *new_folder) +{ + if (old_folder->desktop_file == NULL) + return GNOME_VFS_ERROR_NOT_FOUND; + + /* "move" the desktop file */ + g_free (new_folder->desktop_file); + new_folder->desktop_file = old_folder->desktop_file; + old_folder->desktop_file = NULL; + + /* is this too drastic, it will requery the folder? */ + new_folder->up_to_date = FALSE; + old_folder->up_to_date = FALSE; + + emit_monitor (new_folder, GNOME_VFS_MONITOR_EVENT_CHANGED); + emit_monitor (old_folder, GNOME_VFS_MONITOR_EVENT_CHANGED); + + vfolder_info_write_user (info); + + return GNOME_VFS_OK; +} + +static gboolean +is_sub (Folder *master, Folder *sub) +{ + GSList *li; + + for (li = master->subfolders; li != NULL; li = li->next) { + Folder *subfolder = li->data; + + if (subfolder == sub || + is_sub (subfolder, sub)) + return TRUE; + } + + return FALSE; +} + +static GnomeVFSResult +move_folder (VFolderInfo *info, + Folder *old_folder, Entry *old_entry, + Folder *new_folder, Entry *new_entry) +{ + Folder *source = (Folder *)old_entry; + Folder *target; + + if (new_entry != NULL && + new_entry->type != ENTRY_FOLDER) + return GNOME_VFS_ERROR_NOT_A_DIRECTORY; + + if (new_entry != NULL) { + target = (Folder *)new_entry; + } else { + target = new_folder; + } + + /* move to where we are, yay, we're done :) */ + if (source->parent == target) + return GNOME_VFS_OK; + + if (source == target || + is_sub (source, target)) + return GNOME_VFS_ERROR_LOOP; + + /* this will never happen, but we're paranoid */ + if (source->parent == NULL) + return GNOME_VFS_ERROR_LOOP; + + source->parent->subfolders = g_slist_remove (source->parent->subfolders, + source); + target->subfolders = g_slist_append (target->subfolders, + source); + + source->parent = target; + + source->up_to_date = FALSE; + target->up_to_date = FALSE; + + emit_monitor (source, GNOME_VFS_MONITOR_EVENT_CHANGED); + emit_monitor (target, GNOME_VFS_MONITOR_EVENT_CHANGED); + + vfolder_info_write_user (info); + + return GNOME_VFS_OK; +} + +static GnomeVFSResult +do_move (GnomeVFSMethod *method, + GnomeVFSURI *old_uri, + GnomeVFSURI *new_uri, + gboolean force_replace, + GnomeVFSContext *context) +{ + GnomeVFSResult result = GNOME_VFS_OK; + VFolderInfo *info; + Folder *old_folder, *new_folder; + Entry *old_entry, *new_entry; + gboolean old_is_directory_file, new_is_directory_file; + VFolderURI old_vuri, new_vuri; + + VFOLDER_URI_PARSE (old_uri, &old_vuri); + VFOLDER_URI_PARSE (new_uri, &new_vuri); + + if (old_vuri.file == NULL) + return GNOME_VFS_ERROR_INVALID_URI; + + if (old_vuri.is_all_scheme) + return GNOME_VFS_ERROR_READ_ONLY; + + if (strcmp (old_vuri.scheme, new_vuri.scheme) != 0) + return GNOME_VFS_ERROR_NOT_SAME_FILE_SYSTEM; + + info = get_vfolder_info (old_vuri.scheme, &result, context); + if (info == NULL) + return result; + + if (info->read_only) + return GNOME_VFS_ERROR_READ_ONLY; + + old_entry = get_entry (&old_vuri, + &old_folder, + &old_is_directory_file, + &result, + context); + if (old_entry == NULL) + return result; + + if (old_folder != NULL && old_folder->read_only) + return GNOME_VFS_ERROR_READ_ONLY; + + new_entry = get_entry (&new_vuri, + &new_folder, + &new_is_directory_file, + &result, + context); + if (new_entry == NULL && new_folder == NULL) + return result; + + if (new_folder != NULL && new_folder->read_only) + return GNOME_VFS_ERROR_READ_ONLY; + + if (new_is_directory_file != old_is_directory_file) { + /* this will do another set of lookups + * perhaps this can be done in a nicer way, + * but is this the common case? I don't think so */ + return long_move (method, &old_vuri, &new_vuri, + force_replace, context); + } + + if (new_is_directory_file) { + g_assert (old_entry != NULL); + g_assert (new_entry != NULL); + G_LOCK (vfolder_lock); + result = move_directory_file (info, + (Folder *)old_entry, + (Folder *)new_entry); + G_UNLOCK (vfolder_lock); + return result; + } + + if (old_entry->type == ENTRY_FOLDER) { + G_LOCK (vfolder_lock); + result = move_folder (info, + old_folder, old_entry, + new_folder, new_entry); + G_UNLOCK (vfolder_lock); + return result; + } + + /* move into self, just whack the old one */ + if (old_entry == new_entry) { + /* same folder */ + if (new_folder == old_folder) + return GNOME_VFS_OK; + + if ( ! force_replace) + return GNOME_VFS_ERROR_FILE_EXISTS; + + G_LOCK (vfolder_lock); + + remove_file (old_folder, old_vuri.file); + + old_folder->entries = g_slist_remove (old_folder->entries, + old_entry); + entry_unref (old_entry); + + emit_monitor (old_folder, GNOME_VFS_MONITOR_EVENT_CHANGED); + + vfolder_info_write_user (info); + + G_UNLOCK (vfolder_lock); + + return GNOME_VFS_OK; + } + + /* this is a simple move */ + if (new_entry == NULL || + new_entry->type == ENTRY_FOLDER) { + if (new_entry != NULL) { + new_folder = (Folder *)new_entry; + } else { + /* a file and a totally different one */ + if (strcmp (new_vuri.file, old_entry->name) != 0) { + /* yay, a long move */ + /* this will do another set of lookups + * perhaps this can be done in a nicer way, + * but is this the common case? I don't think + * so */ + return long_move (method, &old_vuri, &new_vuri, + force_replace, context); + } + } + + /* same folder */ + if (new_folder == old_folder) + return GNOME_VFS_OK; + + G_LOCK (vfolder_lock); + + remove_file (old_folder, old_entry->name); + add_file (new_folder, old_entry->name); + + new_folder->entries = g_slist_prepend (new_folder->entries, + old_entry); + entry_ref (old_entry); + new_folder->sorted = FALSE; + + old_folder->entries = g_slist_remove (old_folder->entries, + old_entry); + entry_unref (old_entry); + + emit_monitor (new_folder, GNOME_VFS_MONITOR_EVENT_CHANGED); + emit_monitor (old_folder, GNOME_VFS_MONITOR_EVENT_CHANGED); + + vfolder_info_write_user (info); + + G_UNLOCK (vfolder_lock); + + return GNOME_VFS_OK; + } + + /* do we EVER get here? */ + + /* this will do another set of lookups + * perhaps this can be done in a nicer way, + * but is this the common case? I don't think so */ + return long_move (method, &old_vuri, &new_vuri, + force_replace, context); +} + +static GnomeVFSResult +do_unlink (GnomeVFSMethod *method, + GnomeVFSURI *uri, + GnomeVFSContext *context) +{ + GnomeVFSResult result = GNOME_VFS_OK; + Entry *entry; + Folder *the_folder; + gboolean is_directory_file; + VFolderInfo *info; + VFolderURI vuri; + GSList *li; + + VFOLDER_URI_PARSE (uri, &vuri); + + if (vuri.file == NULL) + return GNOME_VFS_ERROR_INVALID_URI; + + if (vuri.is_all_scheme == TRUE) + return GNOME_VFS_ERROR_READ_ONLY; + + info = get_vfolder_info (vuri.scheme, &result, context); + if (info == NULL) + return result; + else if (info->read_only) + return GNOME_VFS_ERROR_READ_ONLY; + + entry = get_entry (&vuri, + &the_folder, + &is_directory_file, + &result, context); + if (entry == NULL) + return result; + else if (the_folder != NULL && + the_folder->read_only) + return GNOME_VFS_ERROR_READ_ONLY; + + if (entry->type == ENTRY_FOLDER && + is_directory_file) { + Folder *folder = (Folder *)entry; + + if (folder->desktop_file == NULL) + return GNOME_VFS_ERROR_NOT_FOUND; + + G_LOCK (vfolder_lock); + + g_free (folder->desktop_file); + folder->desktop_file = NULL; + + emit_monitor (folder, GNOME_VFS_MONITOR_EVENT_CHANGED); + + vfolder_info_write_user (info); + + G_UNLOCK (vfolder_lock); + + return GNOME_VFS_OK; + } else if (entry->type == ENTRY_FOLDER) { + return GNOME_VFS_ERROR_IS_DIRECTORY; + } else if (the_folder == NULL) { + return GNOME_VFS_ERROR_NOT_FOUND; + } + + G_LOCK (vfolder_lock); + + the_folder->entries = g_slist_remove (the_folder->entries, + entry); + entry_unref (entry); + + remove_file (the_folder, vuri.file); + + emit_monitor (the_folder, GNOME_VFS_MONITOR_EVENT_CHANGED); + + /* evil, we must remove this from the unallocated folders as well + * so that it magically doesn't appear there. But it's not so simple. + * We only want to remove it if it isn't in that folder already. */ + for (li = info->unallocated_folders; + li != NULL; + li = li->next) { + Folder *folder = li->data; + GSList *l; + + /* This is actually really evil since ensuring + * an unallocated folder clears all other unallocated + * folders in it's wake. I'm not sure it's worth + * optimizing however */ + ensure_folder_unlocked (info, folder, + FALSE /* subfolders */, + NULL /* except */, + FALSE /* ignore_unallocated */); + l = g_slist_find (folder->entries, entry); + if (l == NULL) { + remove_file (folder, vuri.file); + } + } + + emit_file_deleted_monitor (info, entry, the_folder); + + /* FIXME: if this was a user file and this is the only + * reference to it, unlink it. */ + + vfolder_info_write_user (info); + + G_UNLOCK (vfolder_lock); + + return GNOME_VFS_OK; +} + +static GnomeVFSResult +do_check_same_fs (GnomeVFSMethod *method, + GnomeVFSURI *source_uri, + GnomeVFSURI *target_uri, + gboolean *same_fs_return, + GnomeVFSContext *context) +{ + VFolderURI source_vuri, target_vuri; + + *same_fs_return = FALSE; + + VFOLDER_URI_PARSE (source_uri, &source_vuri); + VFOLDER_URI_PARSE (target_uri, &target_vuri); + + if (strcmp (source_vuri.scheme, target_vuri.scheme) != 0 || + source_vuri.is_all_scheme != target_vuri.is_all_scheme) + *same_fs_return = FALSE; + else + *same_fs_return = TRUE; + + return GNOME_VFS_OK; +} + +static GnomeVFSResult +do_set_file_info (GnomeVFSMethod *method, + GnomeVFSURI *uri, + const GnomeVFSFileInfo *info, + GnomeVFSSetFileInfoMask mask, + GnomeVFSContext *context) +{ + VFolderURI vuri; + + VFOLDER_URI_PARSE (uri, &vuri); + + if (vuri.file == NULL) + return GNOME_VFS_ERROR_INVALID_URI; + + if (mask & GNOME_VFS_SET_FILE_INFO_NAME) { + GnomeVFSResult result = GNOME_VFS_OK; + char *dirname = gnome_vfs_uri_extract_dirname (uri); + GnomeVFSURI *new_uri = gnome_vfs_uri_dup (uri); + + G_LOCK (vfolder_lock); + g_free (new_uri->text); + new_uri->text = g_build_path ("/", dirname, info->name, NULL); + G_UNLOCK (vfolder_lock); + + result = do_move (method, + uri, + new_uri, + FALSE /* force_replace */, + context); + + g_free (dirname); + gnome_vfs_uri_unref (new_uri); + return result; + } else { + /* We don't support setting any of this other permission, + * times and all that voodoo */ + return GNOME_VFS_ERROR_NOT_SUPPORTED; + } +} + +static GnomeVFSResult +do_monitor_add (GnomeVFSMethod *method, + GnomeVFSMethodHandle **method_handle_return, + GnomeVFSURI *uri, + GnomeVFSMonitorType monitor_type) +{ + VFolderInfo *info; + VFolderURI vuri; + GnomeVFSResult result; + Folder *folder; + Entry *entry; + GnomeVFSURI *file_uri; + FileMonitorHandle *handle; + gboolean is_directory_file; + + VFOLDER_URI_PARSE (uri, &vuri); + + info = get_vfolder_info (vuri.scheme, &result, NULL); + if (info == NULL) + return result; + + if (monitor_type == GNOME_VFS_MONITOR_DIRECTORY) { + G_LOCK (vfolder_lock); + + folder = resolve_folder (info, + vuri.path, + FALSE /* ignore_basename */, + &result, + NULL); + + handle = g_new0 (FileMonitorHandle, 1); + handle->refcount = 2; + handle->uri = gnome_vfs_uri_dup (uri); + handle->dir_monitor = TRUE; + handle->handle = NULL; + handle->filename = NULL; + + if (folder == NULL) { + handle->exists = FALSE; + info->free_folder_monitors = + g_slist_prepend (info->free_folder_monitors, + handle); + } else { + handle->exists = TRUE; + ((Entry *)folder)->monitors = + g_slist_prepend (((Entry *)folder)->monitors, + handle); + } + + info->folder_monitors = + g_slist_prepend (info->folder_monitors, handle); + + G_UNLOCK (vfolder_lock); + + *method_handle_return = (GnomeVFSMethodHandle *) handle; + + return GNOME_VFS_OK; + } else { + /* These can't be very nice FILE names */ + if (vuri.file == NULL || + vuri.ends_in_slash) + return GNOME_VFS_ERROR_INVALID_URI; + + G_LOCK (vfolder_lock); + file_uri = desktop_uri_to_file_uri (info, + &vuri, + &entry, + &is_directory_file, + NULL /* the_folder */, + FALSE, + &result, + NULL); + + handle = g_new0 (FileMonitorHandle, 1); + handle->refcount = 2; + handle->uri = gnome_vfs_uri_dup (uri); + handle->dir_monitor = FALSE; + handle->handle = NULL; + handle->filename = g_strdup (vuri.file); + handle->is_directory_file = is_directory_file; + + info->file_monitors = + g_slist_prepend (info->file_monitors, handle); + + + if (file_uri == NULL) { + handle->exists = FALSE; + info->free_file_monitors = + g_slist_prepend (info->free_file_monitors, + handle); + } else { + char *uri_string = gnome_vfs_uri_to_string (file_uri, 0); + handle->exists = TRUE; + gnome_vfs_monitor_add (&(handle->handle), + uri_string, + GNOME_VFS_MONITOR_FILE, + file_monitor, + handle); + g_free (uri_string); + + entry->monitors = g_slist_prepend (entry->monitors, + handle); + gnome_vfs_uri_unref (file_uri); + } + + *method_handle_return = (GnomeVFSMethodHandle *) handle; + + G_UNLOCK (vfolder_lock); + + return GNOME_VFS_OK; + } +} + +static GnomeVFSResult +do_monitor_cancel (GnomeVFSMethod *method, + GnomeVFSMethodHandle *method_handle) +{ + FileMonitorHandle *handle; + VFolderInfo *info; + VFolderURI vuri; + GnomeVFSResult result; + Folder *folder; + GSList *li; + + handle = (FileMonitorHandle *)method_handle; + + /* FIXME: is this correct? */ + if (method_handle == NULL) + return GNOME_VFS_OK; + + VFOLDER_URI_PARSE (handle->uri, &vuri); + + info = get_vfolder_info (vuri.scheme, &result, NULL); + if (info == NULL) + return result; + + if (handle->dir_monitor) { + G_LOCK (vfolder_lock); + + folder = resolve_folder (info, + vuri.path, + FALSE /* ignore_basename */, + &result, + NULL); + + for (li = info->folder_monitors; li != NULL; li = li->next) { + FileMonitorHandle *h = li->data; + if (h != handle) + continue; + info->folder_monitors = g_slist_delete_link + (info->folder_monitors, li); + file_monitor_handle_unref_unlocked (h); + break; + } + + if (folder == NULL) { + for (li = info->free_folder_monitors; + li != NULL; + li = li->next) { + FileMonitorHandle *h = li->data; + if (h != handle) + continue; + info->free_folder_monitors = g_slist_delete_link + (info->free_folder_monitors, li); + file_monitor_handle_unref_unlocked (h); + break; + } + } else { + for (li = ((Entry *)folder)->monitors; + li != NULL; + li = li->next) { + FileMonitorHandle *h = li->data; + if (h != handle) + continue; + ((Entry *)folder)->monitors = + g_slist_delete_link + (((Entry *)folder)->monitors, li); + file_monitor_handle_unref_unlocked (h); + break; + } + } + + G_UNLOCK (vfolder_lock); + + return GNOME_VFS_OK; + } else { + G_LOCK (vfolder_lock); + + for (li = info->file_monitors; li != NULL; li = li->next) { + FileMonitorHandle *h = li->data; + if (h != handle) + continue; + info->file_monitors = g_slist_delete_link + (info->file_monitors, li); + file_monitor_handle_unref_unlocked (h); + break; + } + + for (li = info->free_file_monitors; + li != NULL; + li = li->next) { + FileMonitorHandle *h = li->data; + if (h != handle) + continue; + info->free_file_monitors = g_slist_delete_link + (info->free_file_monitors, li); + file_monitor_handle_unref_unlocked (h); + break; + } + + for (li = info->entries; li != NULL; li = li->next) { + Entry *e = li->data; + GSList *link = g_slist_find (e->monitors, handle); + + if (link == NULL) + continue; + link->data = NULL; + e->monitors = g_slist_delete_link (e->monitors, link); + + file_monitor_handle_unref_unlocked (handle); + break; + } + + G_UNLOCK (vfolder_lock); + + /* Note: last unref of our monitor will cancel the + * underlying handle */ + + return GNOME_VFS_OK; + } +} + + +/* gnome-vfs bureaucracy */ + +static GnomeVFSMethod method = { + sizeof (GnomeVFSMethod), + do_open, + do_create, + do_close, + do_read, + do_write, + do_seek, + do_tell, + do_truncate_handle, + do_open_directory, + do_close_directory, + do_read_directory, + do_get_file_info, + do_get_file_info_from_handle, + do_is_local, + do_make_directory, + do_remove_directory, + do_move, + do_unlink, + do_check_same_fs, + do_set_file_info, + do_truncate, + NULL /* find_directory */, + NULL /* create_symbolic_link */, + do_monitor_add, + do_monitor_cancel +}; + +GnomeVFSMethod * +vfs_module_init (const char *method_name, + const char *args) +{ + parent_method = gnome_vfs_method_get ("file"); + + if (parent_method == NULL) { + g_error ("Could not find 'file' method for gnome-vfs"); + return NULL; + } + + return &method; +} + +void +vfs_module_shutdown (GnomeVFSMethod *method) +{ + if (infos == NULL) + return; + + g_hash_table_destroy (infos); + infos = NULL; +}