summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--daemon/Makefile.am1
-rw-r--r--daemon/gvfsbackendftp.c771
-rw-r--r--daemon/gvfsbackendftp.h13
-rw-r--r--daemon/gvfsftpconnection.c19
-rw-r--r--daemon/gvfsftpconnection.h1
-rw-r--r--daemon/gvfsftpdircache.c596
-rw-r--r--daemon/gvfsftpdircache.h76
-rw-r--r--daemon/gvfsftpfile.c24
-rw-r--r--daemon/gvfsftpfile.h1
9 files changed, 864 insertions, 638 deletions
diff --git a/daemon/Makefile.am b/daemon/Makefile.am
index c9e17ae7..9761eff6 100644
--- a/daemon/Makefile.am
+++ b/daemon/Makefile.am
@@ -231,6 +231,7 @@ gvfsd_obexftp_LDADD = $(OBEXFTP_LIBS) $(XML_LIBS) $(HAL_LIBS) $(libraries)
gvfsd_ftp_SOURCES = \
gvfsftpconnection.c gvfsftpconnection.h \
+ gvfsftpdircache.c gvfsftpdircache.h \
gvfsftpfile.c gvfsftpfile.h \
gvfsftptask.c gvfsftptask.h \
gvfsbackendftp.c gvfsbackendftp.h \
diff --git a/daemon/gvfsbackendftp.c b/daemon/gvfsbackendftp.c
index 03834e44..847266e1 100644
--- a/daemon/gvfsbackendftp.c
+++ b/daemon/gvfsbackendftp.c
@@ -50,6 +50,7 @@
#include "ParseFTPList.h"
#include "gvfsftpconnection.h"
+#include "gvfsftpdircache.h"
#include "gvfsftpfile.h"
#include "gvfsftptask.h"
@@ -67,30 +68,8 @@
* paths exactly match those of a TVFS-using FTP server.
*/
-typedef struct _FtpDirEntry FtpDirEntry;
-struct _FtpDirEntry {
- gsize size;
- gsize length;
- gchar data[1];
-};
-
-struct FtpDirReader {
- void (* init_data) (GVfsFtpTask * task,
- const GVfsFtpFile *dir);
- gpointer (* iter_new) (GVfsFtpTask * task);
- GFileInfo * (* iter_process)(gpointer iter,
- GVfsFtpTask * task,
- const GVfsFtpFile *dirname,
- const GVfsFtpFile *must_match_file,
- const char * line,
- char ** symlink);
- void (* iter_free) (gpointer iter);
-};
-
G_DEFINE_TYPE (GVfsBackendFtp, g_vfs_backend_ftp, G_VFS_TYPE_BACKEND)
-/*** CODE ***/
-
static gboolean
gvfs_backend_ftp_determine_features (GVfsFtpTask *task)
{
@@ -180,6 +159,44 @@ gvfs_backend_ftp_determine_system (GVfsFtpTask *task)
g_strfreev (reply);
}
+static GFileInfo *
+g_vfs_bacend_ftp_create_root_file_info (GVfsBackendFtp *ftp)
+{
+ GFileInfo *info;
+ GIcon *icon;
+ char *display_name;
+
+ info = g_file_info_new ();
+ g_file_info_set_file_type (info, G_FILE_TYPE_DIRECTORY);
+
+ g_file_info_set_name (info, "/");
+ display_name = g_strdup_printf (_("/ on %s"), ftp->host_display_name);
+ g_file_info_set_display_name (info, display_name);
+ g_free (display_name);
+ g_file_info_set_edit_name (info, "/");
+
+ g_file_info_set_content_type (info, "inode/directory");
+ g_file_info_set_attribute_string (info, G_FILE_ATTRIBUTE_STANDARD_FAST_CONTENT_TYPE, "inode/directory");
+
+ icon = g_themed_icon_new ("folder-remote");
+ g_file_info_set_icon (info, icon);
+ g_object_unref (icon);
+
+ return info;
+}
+
+static void
+gvfs_backend_ftp_setup_directory_cache (GVfsBackendFtp *ftp)
+{
+ if (ftp->system == G_VFS_FTP_SYSTEM_UNIX)
+ ftp->dir_funcs = &g_vfs_ftp_dir_cache_funcs_unix;
+ else
+ ftp->dir_funcs = &g_vfs_ftp_dir_cache_funcs_default;
+
+ ftp->dir_cache = g_vfs_ftp_dir_cache_new (ftp->dir_funcs,
+ g_vfs_bacend_ftp_create_root_file_info (ftp));
+}
+
/*** COMMON FUNCTIONS WITH SPECIAL HANDLING ***/
static gboolean
@@ -217,169 +234,6 @@ g_vfs_ftp_task_try_cd (GVfsFtpTask *task, const GVfsFtpFile *file)
/*** default directory reading ***/
static void
-dir_default_init_data (GVfsFtpTask *task, const GVfsFtpFile *dir)
-{
- g_vfs_ftp_task_cd (task, dir);
- g_vfs_ftp_task_open_data_connection (task);
-
- g_vfs_ftp_task_send (task,
- G_VFS_FTP_PASS_100 | G_VFS_FTP_FAIL_200,
- (task->backend->system == G_VFS_FTP_SYSTEM_UNIX) ? "LIST -a" : "LIST");
-}
-
-static gpointer
-dir_default_iter_new (GVfsFtpTask *task)
-{
- return g_slice_new0 (struct list_state);
-}
-
-static GFileInfo *
-dir_default_iter_process (gpointer iter,
- GVfsFtpTask * task,
- const GVfsFtpFile *dir,
- const GVfsFtpFile *must_match_file,
- const char *line,
- char **symlink)
-{
- struct list_state *state = iter;
- struct list_result result = { 0, };
- GTimeVal tv = { 0, 0 };
- GFileInfo *info;
- int type;
- GVfsFtpFile *name;
- const char *s;
- char *t;
-
- type = ParseFTPList (line, state, &result);
- if (type != 'd' && type != 'f' && type != 'l')
- return NULL;
-
- /* don't list . and .. directories
- * Let's hope they're not important files on some ftp servers
- */
- if (type == 'd')
- {
- if (result.fe_fnlen == 1 &&
- result.fe_fname[0] == '.')
- return NULL;
- if (result.fe_fnlen == 2 &&
- result.fe_fname[0] == '.' &&
- result.fe_fname[1] == '.')
- return NULL;
- }
-
- t = g_strndup (result.fe_fname, result.fe_fnlen);
- if (dir)
- {
- name = g_vfs_ftp_file_new_child (dir, t, NULL);
- g_free (t);
- }
- else
- {
- name = g_vfs_ftp_file_new_from_ftp (task->backend, t);
- g_free (t);
- }
- if (name == NULL)
- return NULL;
-
- if (must_match_file && !g_vfs_ftp_file_equal (name, must_match_file))
- {
- g_vfs_ftp_file_free (name);
- return NULL;
- }
-
- info = g_file_info_new ();
-
- s = g_vfs_ftp_file_get_gvfs_path (name);
-
- t = g_path_get_basename (s);
- g_file_info_set_name (info, t);
- g_free (t);
-
- if (type == 'l')
- {
- char *link;
-
- link = g_strndup (result.fe_lname, result.fe_lnlen);
-
- /* FIXME: this whole stuff is not GVfsFtpFile save */
- g_file_info_set_symlink_target (info, link);
- g_file_info_set_is_symlink (info, TRUE);
-
- if (symlink)
- {
- char *str = g_path_get_dirname (s);
- char *symlink_file = g_build_path ("/", str, link, NULL);
-
- g_free (str);
- while ((str = strstr (symlink_file, "/../")))
- {
- char *end = str + 4;
- char *start;
- start = str - 1;
- while (start >= symlink_file && *start != '/')
- start--;
-
- if (start < symlink_file) {
- *symlink_file = '/';
- start = symlink_file;
- }
-
- memmove (start + 1, end, strlen (end) + 1);
- }
- str = symlink_file + strlen (symlink_file) - 1;
- while (*str == '/' && str > symlink_file)
- *str-- = 0;
- *symlink = symlink_file;
- }
- g_free (link);
- }
- else if (symlink)
- *symlink = NULL;
-
- g_file_info_set_size (info, g_ascii_strtoull (result.fe_size, NULL, 10));
-
- gvfs_file_info_populate_default (info, s,
- type == 'f' ? G_FILE_TYPE_REGULAR :
- type == 'l' ? G_FILE_TYPE_SYMBOLIC_LINK :
- G_FILE_TYPE_DIRECTORY);
-
- if (task->backend->system == G_VFS_FTP_SYSTEM_UNIX)
- g_file_info_set_is_hidden (info, result.fe_fnlen > 0 &&
- result.fe_fname[0] == '.');
-
- g_vfs_ftp_file_free (name);
-
- /* Workaround:
- * result.fetime.tm_year contains actual year instead of offset-from-1900,
- * which mktime expects.
- */
- if (result.fe_time.tm_year >= 1900)
- result.fe_time.tm_year -= 1900;
-
- tv.tv_sec = mktime (&result.fe_time);
- if (tv.tv_sec != -1)
- g_file_info_set_modification_time (info, &tv);
-
- return info;
-}
-
-static void
-dir_default_iter_free (gpointer iter)
-{
- g_slice_free (struct list_state, iter);
-}
-
-static const FtpDirReader dir_default = {
- dir_default_init_data,
- dir_default_iter_new,
- dir_default_iter_process,
- dir_default_iter_free
-};
-
-/*** BACKEND ***/
-
-static void
g_vfs_backend_ftp_finalize (GObject *object)
{
GVfsBackendFtp *ftp = G_VFS_BACKEND_FTP (object);
@@ -392,9 +246,6 @@ g_vfs_backend_ftp_finalize (GObject *object)
g_cond_free (ftp->cond);
g_mutex_free (ftp->mutex);
- g_hash_table_destroy (ftp->directory_cache);
- g_static_rw_lock_free (&ftp->directory_cache_lock);
-
g_free (ftp->user);
g_free (ftp->password);
@@ -403,46 +254,10 @@ g_vfs_backend_ftp_finalize (GObject *object)
}
static void
-ftp_dir_entry_free (gpointer entry)
-{
- g_free (entry);
-}
-
-static FtpDirEntry *
-ftp_dir_entry_grow (FtpDirEntry *entry)
-{
- entry = g_try_realloc (entry, sizeof (FtpDirEntry) + entry->size + 4096);
- if (entry == NULL)
- return NULL;
- entry->size += 4096;
- return entry;
-}
-
-static FtpDirEntry *
-ftp_dir_entry_new (void)
-{
- FtpDirEntry *entry;
-
- entry = g_malloc (4096);
- entry->size = 4096 - sizeof (FtpDirEntry);
- entry->length = 0;
-
- return entry;
-}
-
-static void
g_vfs_backend_ftp_init (GVfsBackendFtp *ftp)
{
ftp->mutex = g_mutex_new ();
ftp->cond = g_cond_new ();
-
- ftp->directory_cache = g_hash_table_new_full (g_vfs_ftp_file_hash,
- g_vfs_ftp_file_equal,
- g_vfs_ftp_file_free,
- ftp_dir_entry_free);
- g_static_rw_lock_init (&ftp->directory_cache_lock);
-
- ftp->dir_ops = &dir_default;
}
static void
@@ -600,6 +415,7 @@ try_login:
}
g_vfs_ftp_task_setup_connection (&task);
gvfs_backend_ftp_determine_system (&task);
+ gvfs_backend_ftp_setup_directory_cache (ftp);
/* Save the address of the current connection, so that for future connections,
* we are sure to connect to the same machine.
@@ -903,30 +719,74 @@ do_start_write (GVfsFtpTask *task,
}
}
-static void
-gvfs_backend_ftp_purge_cache_directory (GVfsBackendFtp * ftp,
- const GVfsFtpFile *dir)
+/* NB: This gets a file info for the given object, no matter if it's a dir
+ * or a file */
+static GFileInfo *
+create_file_info (GVfsFtpTask *task, GVfsFtpFile *file, gboolean resolve_symlinks)
{
- g_static_rw_lock_writer_lock (&ftp->directory_cache_lock);
- g_hash_table_remove (ftp->directory_cache, dir);
- g_static_rw_lock_writer_unlock (&ftp->directory_cache_lock);
-}
+ GFileInfo *info;
+ char **reply;
-static void
-gvfs_backend_ftp_purge_cache_of_file (GVfsBackendFtp * ftp,
- const GVfsFtpFile *file)
-{
- GVfsFtpFile *dir = g_vfs_ftp_file_new_parent (file);
+ if (g_vfs_ftp_task_is_in_error (task))
+ return NULL;
- if (!g_vfs_ftp_file_equal (file, dir))
- gvfs_backend_ftp_purge_cache_directory (ftp, dir);
+ info = g_vfs_ftp_dir_cache_lookup_file (task->backend->dir_cache, task, file, resolve_symlinks);
+ if (info)
+ return info;
- g_vfs_ftp_file_free (dir);
+ g_vfs_ftp_task_clear_error (task);
+
+ /* the directory cache fails when the parent directory of the file is not readable.
+ * This cannot happen on Unix, but it can happen on FTP.
+ * In this case we try to figure out as much as possible about the file (does it even exist?)
+ * using standard ftp commands.
+ */
+ if (g_vfs_ftp_task_try_cd (task, file))
+ {
+ char *tmp;
+
+ info = g_file_info_new ();
+
+ tmp = g_path_get_basename (g_vfs_ftp_file_get_gvfs_path (file));
+ g_file_info_set_name (info, tmp);
+ g_free (tmp);
+
+ gvfs_file_info_populate_default (info, g_vfs_ftp_file_get_gvfs_path (file), G_FILE_TYPE_DIRECTORY);
+
+ g_file_info_set_is_hidden (info, TRUE);
+ }
+ else if (g_vfs_ftp_task_send_and_check (task, 0, NULL, NULL, &reply, "SIZE %s", g_vfs_ftp_file_get_ftp_path (file)))
+ {
+ char *tmp;
+
+ info = g_file_info_new ();
+
+ tmp = g_path_get_basename (g_vfs_ftp_file_get_gvfs_path (file));
+ g_file_info_set_name (info, tmp);
+ g_free (tmp);
+
+ gvfs_file_info_populate_default (info, g_vfs_ftp_file_get_gvfs_path (file), G_FILE_TYPE_REGULAR);
+
+ g_file_info_set_size (info, g_ascii_strtoull (reply[0] + 4, NULL, 0));
+ g_strfreev (reply);
+
+ g_file_info_set_is_hidden (info, TRUE);
+ }
+ else
+ {
+ info = NULL;
+ /* clear error from ftp_connection_send() in else if line above */
+ g_vfs_ftp_task_clear_error (task);
+
+ /* note that there might still be a file/directory, we just have
+ * no way to figure this out (in particular on ftp servers that
+ * don't support SIZE.
+ * If you have ways to improve file detection, patches are welcome. */
+ }
+
+ return info;
}
-/* forward declaration */
-static GFileInfo *
-create_file_info (GVfsFtpTask *task, const char *filename, char **symlink);
static void
do_create (GVfsBackend *backend,
@@ -939,7 +799,8 @@ do_create (GVfsBackend *backend,
GFileInfo *info;
GVfsFtpFile *file;
- info = create_file_info (&task, filename, NULL);
+ file = g_vfs_ftp_file_new_from_gvfs (ftp, filename);
+ info = create_file_info (&task, file, FALSE);
if (info)
{
g_object_unref (info);
@@ -947,12 +808,12 @@ do_create (GVfsBackend *backend,
G_IO_ERROR,
G_IO_ERROR_EXISTS,
_("Target file already exists"));
+ g_vfs_ftp_file_free (file);
g_vfs_ftp_task_done (&task);
return;
}
- file = g_vfs_ftp_file_new_from_gvfs (ftp, filename);
do_start_write (&task, flags, "STOR %s", g_vfs_ftp_file_get_ftp_path (file));
- gvfs_backend_ftp_purge_cache_of_file (ftp, file);
+ g_vfs_ftp_dir_cache_purge_file (ftp->dir_cache, file);
g_vfs_ftp_file_free (file);
g_vfs_ftp_task_done (&task);
@@ -970,7 +831,7 @@ do_append (GVfsBackend *backend,
file = g_vfs_ftp_file_new_from_gvfs (ftp, filename);
do_start_write (&task, flags, "APPE %s", g_vfs_ftp_file_get_ftp_path (file));
- gvfs_backend_ftp_purge_cache_of_file (ftp, file);
+ g_vfs_ftp_dir_cache_purge_file (ftp->dir_cache, file);
g_vfs_ftp_file_free (file);
g_vfs_ftp_task_done (&task);
@@ -1001,7 +862,7 @@ do_replace (GVfsBackend *backend,
file = g_vfs_ftp_file_new_from_gvfs (ftp, filename);
do_start_write (&task, flags, "STOR %s", g_vfs_ftp_file_get_ftp_path (file));
- gvfs_backend_ftp_purge_cache_of_file (ftp, file);
+ g_vfs_ftp_dir_cache_purge_file (ftp->dir_cache, file);
g_vfs_ftp_file_free (file);
g_vfs_ftp_task_done (&task);
@@ -1048,301 +909,6 @@ do_write (GVfsBackend *backend,
g_vfs_ftp_task_done (&task);
}
-static GFileInfo *
-create_file_info_for_root (GVfsBackendFtp *ftp)
-{
- GFileInfo *info;
- GIcon *icon;
- char *display_name;
-
- info = g_file_info_new ();
- g_file_info_set_file_type (info, G_FILE_TYPE_DIRECTORY);
-
- g_file_info_set_name (info, "/");
- display_name = g_strdup_printf (_("/ on %s"), ftp->host_display_name);
- g_file_info_set_display_name (info, display_name);
- g_free (display_name);
- g_file_info_set_edit_name (info, "/");
-
- g_file_info_set_content_type (info, "inode/directory");
- g_file_info_set_attribute_string (info, G_FILE_ATTRIBUTE_STANDARD_FAST_CONTENT_TYPE, "inode/directory");
-
- icon = g_themed_icon_new ("folder-remote");
- g_file_info_set_icon (info, icon);
- g_object_unref (icon);
-
- return info;
-}
-
-static FtpDirEntry *
-do_enumerate_directory (GVfsFtpTask *task)
-{
- gssize n_bytes;
- FtpDirEntry *entry;
-
- if (g_vfs_ftp_task_is_in_error (task))
- return NULL;
-
- entry = ftp_dir_entry_new ();
-
- do
- {
- if (entry->size - entry->length < 128)
- {
- entry = ftp_dir_entry_grow (entry);
- if (entry == NULL)
- {
- g_set_error_literal (&task->error, G_IO_ERROR, G_IO_ERROR_FAILED,
- _("Out of memory while reading directory contents"));
- return NULL;
- }
- }
- n_bytes = g_vfs_ftp_connection_read_data (task->conn,
- entry->data + entry->length,
- entry->size - entry->length - 1,
- task->cancellable,
- &task->error);
-
- if (n_bytes < 0)
- {
- ftp_dir_entry_free (entry);
- return NULL;
- }
-
- entry->length += n_bytes;
- }
- while (n_bytes > 0);
-
- g_vfs_ftp_task_close_data_connection (task);
- g_vfs_ftp_task_receive (task, 0, NULL);
- if (g_vfs_ftp_task_is_in_error (task))
- {
- ftp_dir_entry_free (entry);
- return NULL;
- }
- /* null-terminate, just because */
- entry->data[entry->length] = 0;
-
- return entry;
-}
-
-/* IMPORTANT: SUCK ALARM!
- * locks ftp->directory_cache_lock but only iff it returns !NULL */
-static const FtpDirEntry *
-enumerate_directory (GVfsFtpTask * task,
- const GVfsFtpFile *dir,
- gboolean use_cache)
-{
- GVfsBackendFtp *ftp = task->backend;
- FtpDirEntry *entry;
-
- g_static_rw_lock_reader_lock (&ftp->directory_cache_lock);
- do {
- if (use_cache)
- entry = g_hash_table_lookup (ftp->directory_cache, dir);
- else
- {
- use_cache = TRUE;
- entry = NULL;
- }
- if (entry == NULL)
- {
- g_static_rw_lock_reader_unlock (&ftp->directory_cache_lock);
- ftp->dir_ops->init_data (task, dir);
- entry = do_enumerate_directory (task);
- if (entry == NULL)
- return NULL;
- g_static_rw_lock_writer_lock (&ftp->directory_cache_lock);
- g_hash_table_insert (ftp->directory_cache, g_vfs_ftp_file_copy (dir), entry);
- g_static_rw_lock_writer_unlock (&ftp->directory_cache_lock);
- entry = NULL;
- g_static_rw_lock_reader_lock (&ftp->directory_cache_lock);
- }
- } while (entry == NULL);
-
- return entry;
-}
-
-static GFileInfo *
-create_file_info_from_parent (GVfsFtpTask * task,
- const GVfsFtpFile *dir,
- const GVfsFtpFile *file,
- char ** symlink)
-{
- GFileInfo *info = NULL;
- gpointer iter;
- const FtpDirEntry *entry;
- const char *sol, *eol;
-
- entry = enumerate_directory (task, dir, TRUE);
- if (entry == NULL)
- return NULL;
-
- iter = task->backend->dir_ops->iter_new (task);
- for (sol = eol = entry->data; eol; sol = eol + 1)
- {
- eol = memchr (sol, '\n', entry->length - (sol - entry->data));
- info = task->backend->dir_ops->iter_process (iter,
- task,
- dir,
- file,
- sol,
- symlink);
- if (info)
- break;
- }
- task->backend->dir_ops->iter_free (iter);
- g_static_rw_lock_reader_unlock (&task->backend->directory_cache_lock);
-
- return info;
-}
-
-static GFileInfo *
-create_file_info_from_file (GVfsFtpTask *task, const GVfsFtpFile *file,
- const char *filename, char **symlink)
-{
- GFileInfo *info;
- char **reply;
-
- if (g_vfs_ftp_task_try_cd (task, file))
- {
- char *tmp;
-
- info = g_file_info_new ();
-
- tmp = g_path_get_basename (filename);
- g_file_info_set_name (info, tmp);
- g_free (tmp);
-
- gvfs_file_info_populate_default (info, filename, G_FILE_TYPE_DIRECTORY);
-
- g_file_info_set_is_hidden (info, TRUE);
- }
- else if (g_vfs_ftp_task_send_and_check (task, 0, NULL, NULL, &reply, "SIZE %s", g_vfs_ftp_file_get_ftp_path (file)))
- {
- char *tmp;
-
- info = g_file_info_new ();
-
- tmp = g_path_get_basename (filename);
- g_file_info_set_name (info, tmp);
- g_free (tmp);
-
- gvfs_file_info_populate_default (info, filename, G_FILE_TYPE_REGULAR);
-
- g_file_info_set_size (info, g_ascii_strtoull (reply[0] + 4, NULL, 0));
- g_strfreev (reply);
-
- g_file_info_set_is_hidden (info, TRUE);
- }
- else
- {
- info = NULL;
- /* clear error from ftp_connection_send() in else if line above */
- g_vfs_ftp_task_clear_error (task);
-
- /* note that there might still be a file/directory, we just have
- * no way to figure this out (in particular on ftp servers that
- * don't support SIZE.
- * If you have ways to improve file detection, patches are welcome. */
- }
-
- return info;
-}
-
-/* NB: This gets a file info for the given object, no matter if it's a dir
- * or a file */
-static GFileInfo *
-create_file_info (GVfsFtpTask *task, const char *filename, char **symlink)
-{
- GVfsFtpFile *dir, *file;
- GFileInfo *info;
-
- if (symlink)
- *symlink = NULL;
-
- if (g_str_equal (filename, "/"))
- return create_file_info_for_root (task->backend);
-
- file = g_vfs_ftp_file_new_from_gvfs (task->backend, filename);
- dir = g_vfs_ftp_file_new_parent (file);
-
- info = create_file_info_from_parent (task, dir, file, symlink);
- if (info == NULL)
- info = create_file_info_from_file (task, file, filename, symlink);
-
- g_vfs_ftp_file_free (dir);
- g_vfs_ftp_file_free (file);
- return info;
-}
-
-static GFileInfo *
-resolve_symlink (GVfsFtpTask *task, GFileInfo *original, const char *filename)
-{
- GFileInfo *info = NULL;
- char *symlink, *newlink;
- guint i;
- static const char *copy_attributes[] = {
- G_FILE_ATTRIBUTE_STANDARD_IS_SYMLINK,
- G_FILE_ATTRIBUTE_STANDARD_IS_HIDDEN,
- G_FILE_ATTRIBUTE_STANDARD_NAME,
- G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME,
- G_FILE_ATTRIBUTE_STANDARD_EDIT_NAME,
- G_FILE_ATTRIBUTE_STANDARD_COPY_NAME,
- G_FILE_ATTRIBUTE_STANDARD_SYMLINK_TARGET
- };
-
- if (g_vfs_ftp_task_is_in_error (task))
- return original;
-
- /* How many symlinks should we follow?
- * <alex> maybe 8?
- */
- symlink = g_strdup (filename);
- for (i = 0; i < 8 && symlink; i++)
- {
- info = create_file_info (task,
- symlink,
- &newlink);
- if (!newlink)
- break;
-
- g_free (symlink);
- symlink = newlink;
- }
- g_free (symlink);
-
- if (g_vfs_ftp_task_is_in_error (task))
- {
- g_assert (info == NULL);
- g_vfs_ftp_task_clear_error (task);
- return original;
- }
- if (info == NULL)
- return original;
-
- for (i = 0; i < G_N_ELEMENTS (copy_attributes); i++)
- {
- GFileAttributeType type;
- gpointer value;
-
- if (!g_file_info_get_attribute_data (original,
- copy_attributes[i],
- &type,
- &value,
- NULL))
- continue;
-
- g_file_info_set_attribute (info,
- copy_attributes[i],
- type,
- value);
- }
- g_object_unref (original);
-
- return info;
-}
-
static void
do_query_info (GVfsBackend *backend,
GVfsJobQueryInfo *job,
@@ -1353,26 +919,11 @@ do_query_info (GVfsBackend *backend,
{
GVfsBackendFtp *ftp = G_VFS_BACKEND_FTP (backend);
GVfsFtpTask task = G_VFS_FTP_TASK_INIT (ftp, G_VFS_JOB (job));
+ GVfsFtpFile *file;
GFileInfo *real;
- char *symlink;
-
- if (query_flags & G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS)
- {
- real = create_file_info (&task,
- filename,
- NULL);
- }
- else
- {
- real = create_file_info (&task,
- filename,
- &symlink);
- if (symlink)
- {
- real = resolve_symlink (&task, real, symlink);
- g_free (symlink);
- }
- }
+
+ file = g_vfs_ftp_file_new_from_gvfs (ftp, filename);
+ real = create_file_info (&task, file, query_flags & G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS ? FALSE : TRUE);
if (real)
{
@@ -1400,68 +951,29 @@ do_enumerate (GVfsBackend *backend,
GVfsBackendFtp *ftp = G_VFS_BACKEND_FTP (backend);
GVfsFtpTask task = G_VFS_FTP_TASK_INIT (ftp, G_VFS_JOB (job));
GVfsFtpFile *dir;
- gpointer iter;
- GSList *symlink_targets = NULL;
- GSList *symlink_fileinfos = NULL;
- GSList *twalk, *fwalk;
- GFileInfo *info;
- const FtpDirEntry *entry;
- const char *sol, *eol;
-
- /* no need to check for IS_DIR, because the enumeration code will return that
- * automatically.
- */
+ GList *list;
dir = g_vfs_ftp_file_new_from_gvfs (ftp, dirname);
- entry = enumerate_directory (&task, dir, FALSE);
- if (entry != NULL)
+ list = g_vfs_ftp_dir_cache_lookup_dir (ftp->dir_cache,
+ &task,
+ dir,
+ TRUE,
+ query_flags & G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS ? FALSE : TRUE);
+ if (g_vfs_ftp_task_is_in_error (&task))
{
- g_vfs_job_succeeded (task.job);
- task.job = NULL;
+ g_assert (list == NULL);
+ g_vfs_ftp_task_done (&task);
+ return;
+ }
- iter = ftp->dir_ops->iter_new (&task);
- for (sol = eol = entry->data; eol; sol = eol + 1)
- {
- char *symlink = NULL;
-
- eol = memchr (sol, '\n', entry->length - (sol - entry->data));
- info = ftp->dir_ops->iter_process (iter,
- &task,
- dir,
- NULL,
- sol,
- query_flags & G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS ? NULL : &symlink);
- if (symlink)
- {
- /* This is necessary due to our locking.
- * And we must not unlock here because it might invalidate the list we iterate */
- symlink_targets = g_slist_prepend (symlink_targets, symlink);
- symlink_fileinfos = g_slist_prepend (symlink_fileinfos, info);
- }
- else if (info)
- {
- g_vfs_job_enumerate_add_info (job, info);
- g_object_unref (info);
- }
- }
- ftp->dir_ops->iter_free (iter);
- g_static_rw_lock_reader_unlock (&ftp->directory_cache_lock);
- for (twalk = symlink_targets, fwalk = symlink_fileinfos; twalk;
- twalk = twalk->next, fwalk = fwalk->next)
- {
- info = resolve_symlink (&task, fwalk->data, twalk->data);
- g_free (twalk->data);
- g_vfs_job_enumerate_add_info (job, info);
- g_object_unref (info);
- }
- g_slist_free (symlink_targets);
- g_slist_free (symlink_fileinfos);
+ g_vfs_ftp_task_done (&task);
- g_vfs_job_enumerate_done (job);
- }
-
+ g_vfs_job_enumerate_add_infos (job, list);
+ g_vfs_job_enumerate_done (job);
+
+ g_list_foreach (list, (GFunc) g_object_unref, NULL);
+ g_list_free (list);
g_vfs_ftp_file_free (dir);
- g_vfs_ftp_task_done (&task);
}
static void
@@ -1486,7 +998,7 @@ do_set_display_name (GVfsBackend *backend,
/* FIXME: parse result of RNTO here? */
g_vfs_job_set_display_name_set_new_path (job, g_vfs_ftp_file_get_gvfs_path (now));
- gvfs_backend_ftp_purge_cache_directory (ftp, dir);
+ g_vfs_ftp_dir_cache_purge_dir (ftp->dir_cache, dir);
g_vfs_ftp_file_free (now);
g_vfs_ftp_file_free (dir);
g_vfs_ftp_file_free (original);
@@ -1517,14 +1029,19 @@ do_delete (GVfsBackend *backend,
"RMD %s", g_vfs_ftp_file_get_ftp_path (file));
if (response == 550)
{
- const FtpDirEntry *entry = enumerate_directory (&task, file, FALSE);
- if (entry)
+ GList *list = g_vfs_ftp_dir_cache_lookup_dir (ftp->dir_cache,
+ &task,
+ file,
+ FALSE,
+ FALSE);
+ if (list)
{
- g_static_rw_lock_reader_unlock (&ftp->directory_cache_lock);
g_set_error_literal (&task.error,
G_IO_ERROR,
G_IO_ERROR_NOT_EMPTY,
g_strerror (ENOTEMPTY));
+ g_list_foreach (list, (GFunc) g_object_unref, NULL);
+ g_list_free (list);
}
else
{
@@ -1534,7 +1051,7 @@ do_delete (GVfsBackend *backend,
}
}
- gvfs_backend_ftp_purge_cache_of_file (ftp, file);
+ g_vfs_ftp_dir_cache_purge_file (ftp->dir_cache, file);
g_vfs_ftp_file_free (file);
g_vfs_ftp_task_done (&task);
@@ -1560,7 +1077,7 @@ do_make_directory (GVfsBackend *backend,
/* FIXME: Compare created file with name from server result to be sure
* it's correct and otherwise fail. */
- gvfs_backend_ftp_purge_cache_of_file (ftp, file);
+ g_vfs_ftp_dir_cache_purge_file (ftp->dir_cache, file);
g_vfs_ftp_file_free (file);
g_vfs_ftp_task_done (&task);
@@ -1614,8 +1131,8 @@ do_move (GVfsBackend *backend,
if (!(flags & G_FILE_COPY_OVERWRITE))
{
GFileInfo *info = create_file_info (&task,
- g_vfs_ftp_file_get_gvfs_path (destfile),
- NULL);
+ destfile,
+ FALSE);
if (info)
{
@@ -1635,8 +1152,8 @@ do_move (GVfsBackend *backend,
0,
"RNTO %s", g_vfs_ftp_file_get_ftp_path (destfile));
- gvfs_backend_ftp_purge_cache_of_file (ftp, srcfile);
- gvfs_backend_ftp_purge_cache_of_file (ftp, destfile);
+ g_vfs_ftp_dir_cache_purge_file (ftp->dir_cache, srcfile);
+ g_vfs_ftp_dir_cache_purge_file (ftp->dir_cache, destfile);
out:
g_vfs_ftp_file_free (srcfile);
g_vfs_ftp_file_free (destfile);
diff --git a/daemon/gvfsbackendftp.h b/daemon/gvfsbackendftp.h
index e1df8b0b..c29b53fa 100644
--- a/daemon/gvfsbackendftp.h
+++ b/daemon/gvfsbackendftp.h
@@ -58,7 +58,9 @@ typedef enum {
G_VFS_FTP_WORKAROUND_FEAT_AFTER_LOGIN,
} GVfsFtpWorkaround;
-typedef struct FtpDirReader FtpDirReader;
+/* forward declarations */
+typedef struct _GVfsFtpDirCache GVfsFtpDirCache;
+typedef struct _GVfsFtpDirFuncs GVfsFtpDirFuncs;
#define G_VFS_TYPE_BACKEND_FTP (g_vfs_backend_ftp_get_type ())
#define G_VFS_BACKEND_FTP(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), G_VFS_TYPE_BACKEND_FTP, GVfsBackendFtp))
@@ -85,8 +87,9 @@ struct _GVfsBackendFtp
int features; /* GVfsFtpFeatures that are supported */
int workarounds; /* GVfsFtpWorkarounds in use - int because it's atomic */
- /* vfuncs */
- const FtpDirReader * dir_ops;
+ /* directory cache */
+ const GVfsFtpDirFuncs *dir_funcs; /* functions used in directory cache */
+ GVfsFtpDirCache * dir_cache; /* directory cache */
/* connection collection - accessed from gvfsftptask.c */
GMutex * mutex; /* mutex protecting the following variables */
@@ -94,10 +97,6 @@ struct _GVfsBackendFtp
GQueue * queue; /* queue containing the connections */
guint connections; /* current number of connections */
guint max_connections; /* upper server limit for number of connections - dynamically generated */
-
- /* caching results from dir queries */
- GStaticRWLock directory_cache_lock;
- GHashTable * directory_cache;
};
struct _GVfsBackendFtpClass
diff --git a/daemon/gvfsftpconnection.c b/daemon/gvfsftpconnection.c
index 485ad68d..d6b1274a 100644
--- a/daemon/gvfsftpconnection.c
+++ b/daemon/gvfsftpconnection.c
@@ -227,6 +227,25 @@ g_vfs_ftp_connection_close_data_connection (GVfsFtpConnection *conn)
conn->data = NULL;
}
+/**
+ * g_vfs_ftp_connection_get_data_stream:
+ * @conn: a connection
+ *
+ * Gets the data stream in use by @conn. It is an error to call this function
+ * when no data stream exists. Be sure to check the return value of
+ * g_vfs_ftp_connection_open_data_connection().
+ *
+ * Returns: the data stream of @conn
+ **/
+GIOStream *
+g_vfs_ftp_connection_get_data_stream (GVfsFtpConnection *conn)
+{
+ g_return_val_if_fail (conn != NULL, NULL);
+ g_return_val_if_fail (conn->data != NULL, NULL);
+
+ return conn->data;
+}
+
gssize
g_vfs_ftp_connection_write_data (GVfsFtpConnection *conn,
const char * data,
diff --git a/daemon/gvfsftpconnection.h b/daemon/gvfsftpconnection.h
index 234f4e4d..4fbc91be 100644
--- a/daemon/gvfsftpconnection.h
+++ b/daemon/gvfsftpconnection.h
@@ -64,6 +64,7 @@ gboolean g_vfs_ftp_connection_open_data_connection
GError ** error);
void g_vfs_ftp_connection_close_data_connection
(GVfsFtpConnection * conn);
+GIOStream * g_vfs_ftp_connection_get_data_stream (GVfsFtpConnection * conn);
gssize g_vfs_ftp_connection_write_data (GVfsFtpConnection * conn,
const char * data,
gsize len,
diff --git a/daemon/gvfsftpdircache.c b/daemon/gvfsftpdircache.c
new file mode 100644
index 00000000..d5082780
--- /dev/null
+++ b/daemon/gvfsftpdircache.c
@@ -0,0 +1,596 @@
+/* GIO - GLib Input, Output and Streaming Library
+ *
+ * Copyright (C) 2009 Benjamin Otte <otte@gnome.org>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General
+ * Public License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: Benjamin Otte <otte@gnome.org>
+ */
+
+#include <config.h>
+
+#include <glib/gi18n.h>
+
+#include "gvfsftpdircache.h"
+
+/*** CACHE ENTRY ***/
+
+struct _GVfsFtpDirCacheEntry
+{
+ GHashTable * files; /* GVfsFtpFile => GFileInfo mapping */
+ guint stamp; /* cache's stamp when this entry was created */
+ volatile int refcount; /* need to refount this struct for thread safety */
+};
+
+static GVfsFtpDirCacheEntry *
+g_vfs_ftp_dir_cache_entry_new (guint stamp)
+{
+ GVfsFtpDirCacheEntry *entry;
+
+ entry = g_slice_new0 (GVfsFtpDirCacheEntry);
+ entry->files = g_hash_table_new_full (g_vfs_ftp_file_hash,
+ g_vfs_ftp_file_equal,
+ (GDestroyNotify) g_vfs_ftp_file_free,
+ g_object_unref);
+ entry->stamp = stamp;
+ entry->refcount = 1;
+
+ return entry;
+}
+
+static GVfsFtpDirCacheEntry *
+g_vfs_ftp_dir_cache_entry_ref (GVfsFtpDirCacheEntry *entry)
+{
+ g_atomic_int_inc (&entry->refcount);
+
+ return entry;
+}
+
+static void
+g_vfs_ftp_dir_cache_entry_unref (GVfsFtpDirCacheEntry *entry)
+{
+ if (!g_atomic_int_dec_and_test (&entry->refcount))
+ return;
+
+ g_hash_table_destroy (entry->files);
+ g_slice_free (GVfsFtpDirCacheEntry, entry);
+}
+
+/**
+ * g_vfs_ftp_dir_cache_entry_add:
+ * @entry: the entry to add data to
+ * @file: the file to add. The function takes ownership of the argument.
+ * @info: the file info of the @file. The function takes ownership of the
+ * reference.
+ *
+ * Adds a new file entry to the directory belonging to @entry. This function
+ * must only be called from a @GVfsFtpListDirFunc.
+ **/
+void
+g_vfs_ftp_dir_cache_entry_add (GVfsFtpDirCacheEntry *entry, GVfsFtpFile *file, GFileInfo *info)
+{
+ g_return_if_fail (entry != NULL);
+ g_return_if_fail (file != NULL);
+ g_return_if_fail (G_IS_FILE_INFO (info));
+
+ g_hash_table_insert (entry->files, file, info);
+}
+
+/*** CACHE ***/
+
+struct _GVfsFtpDirCache
+{
+ GHashTable * directories; /* GVfsFtpFile of directory => GVfsFtpDirCacheEntry mapping */
+ guint stamp; /* used to identify validity of cache when flushing */
+ GMutex * lock; /* mutex for thread safety of stamp and hash table */
+ GFileInfo * root; /* file info for '/' */
+ const GVfsFtpDirFuncs *funcs; /* functions to call */
+};
+
+GVfsFtpDirCache *
+g_vfs_ftp_dir_cache_new (const GVfsFtpDirFuncs *funcs, GFileInfo *root)
+{
+ GVfsFtpDirCache *cache;
+
+ g_return_val_if_fail (funcs != NULL, NULL);
+ g_return_val_if_fail (G_IS_FILE_INFO (root), NULL);
+
+ cache = g_slice_new0 (GVfsFtpDirCache);
+ cache->directories = g_hash_table_new_full (g_vfs_ftp_file_hash,
+ g_vfs_ftp_file_equal,
+ (GDestroyNotify) g_vfs_ftp_file_free,
+ (GDestroyNotify) g_vfs_ftp_dir_cache_entry_unref);
+ cache->lock = g_mutex_new();
+ cache->funcs = funcs;
+ cache->root = root;
+
+ return cache;
+}
+
+void
+g_vfs_ftp_dir_cache_free (GVfsFtpDirCache *cache)
+{
+ g_return_if_fail (cache != NULL);
+
+ g_hash_table_destroy (cache->directories);
+ g_mutex_free (cache->lock);
+ g_object_unref (cache->root);
+ g_slice_free (GVfsFtpDirCache, cache);
+}
+
+static GVfsFtpDirCacheEntry *
+g_vfs_ftp_dir_cache_lookup_entry (GVfsFtpDirCache * cache,
+ GVfsFtpTask * task,
+ const GVfsFtpFile *dir,
+ guint stamp)
+{
+ GVfsFtpDirCacheEntry *entry;
+
+ g_mutex_lock (cache->lock);
+ entry = g_hash_table_lookup (cache->directories, dir);
+ if (entry)
+ g_vfs_ftp_dir_cache_entry_ref (entry);
+ g_mutex_unlock (cache->lock);
+ if (entry && entry->stamp < stamp)
+ g_vfs_ftp_dir_cache_entry_unref (entry);
+ else if (entry)
+ return entry;
+
+ if (g_vfs_ftp_task_send (task,
+ G_VFS_FTP_PASS_550,
+ "CWD %s", g_vfs_ftp_file_get_ftp_path (dir)) == 550)
+ {
+ g_set_error_literal (&task->error,
+ G_IO_ERROR, G_IO_ERROR_NOT_DIRECTORY,
+ _("The file is not a directory"));
+ }
+ g_vfs_ftp_task_open_data_connection (task);
+
+ g_vfs_ftp_task_send (task,
+ G_VFS_FTP_PASS_100 | G_VFS_FTP_FAIL_200,
+ "%s", cache->funcs->command);
+ if (g_vfs_ftp_task_is_in_error (task))
+ return NULL;
+
+ entry = g_vfs_ftp_dir_cache_entry_new (stamp);
+ cache->funcs->process (g_io_stream_get_input_stream (g_vfs_ftp_connection_get_data_stream (task->conn)),
+ dir,
+ entry,
+ task->cancellable,
+ &task->error);
+ g_vfs_ftp_task_close_data_connection (task);
+ g_vfs_ftp_task_receive (task, 0, NULL);
+ if (g_vfs_ftp_task_is_in_error (task))
+ {
+ g_vfs_ftp_dir_cache_entry_unref (entry);
+ return NULL;
+ }
+ g_mutex_lock (cache->lock);
+ g_hash_table_insert (cache->directories,
+ g_vfs_ftp_file_copy (dir),
+ g_vfs_ftp_dir_cache_entry_ref (entry));
+ g_mutex_unlock (cache->lock);
+ return entry;
+}
+
+static GFileInfo *
+g_vfs_ftp_dir_cache_resolve_symlink (GVfsFtpDirCache * cache,
+ GVfsFtpTask * task,
+ const GVfsFtpFile *file,
+ GFileInfo * original,
+ guint stamp)
+{
+ GVfsFtpDirCacheEntry *entry;
+ static const char *copy_attributes[] = {
+ G_FILE_ATTRIBUTE_STANDARD_IS_SYMLINK,
+ G_FILE_ATTRIBUTE_STANDARD_IS_HIDDEN,
+ G_FILE_ATTRIBUTE_STANDARD_NAME,
+ G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME,
+ G_FILE_ATTRIBUTE_STANDARD_EDIT_NAME,
+ G_FILE_ATTRIBUTE_STANDARD_COPY_NAME,
+ G_FILE_ATTRIBUTE_STANDARD_SYMLINK_TARGET
+ };
+ GFileInfo *info, *result;
+ GVfsFtpFile *tmp, *link;
+ guint i, lookups = 0;
+
+ if (!g_file_info_get_is_symlink (original) ||
+ g_vfs_ftp_task_is_in_error (task))
+ return original;
+
+ info = g_object_ref (original);
+ link = g_vfs_ftp_file_copy (file);
+ do
+ {
+ /* This must not happen, as we use one of our own GFileInfos */
+ g_assert (g_file_info_get_symlink_target (info) != NULL);
+ tmp = link;
+ link = cache->funcs->resolve_symlink (task, tmp, g_file_info_get_symlink_target (info));
+ g_vfs_ftp_file_free (tmp);
+ g_object_unref (info);
+ if (link == NULL)
+ {
+ g_vfs_ftp_task_clear_error (task);
+ return original;
+ }
+ tmp = g_vfs_ftp_file_new_parent (link);
+ entry = g_vfs_ftp_dir_cache_lookup_entry (cache, task, tmp, stamp);
+ g_vfs_ftp_file_free (tmp);
+ if (entry == NULL)
+ {
+ g_vfs_ftp_file_free (link);
+ /* clear the (potential) error here, dangling symlinks etc should not cause errors */
+ g_vfs_ftp_task_clear_error (task);
+ return original;
+ }
+ info = g_hash_table_lookup (entry->files, link);
+ if (info == NULL)
+ {
+ g_vfs_ftp_dir_cache_entry_unref (entry);
+ g_vfs_ftp_file_free (link);
+ return original;
+ }
+ else
+ {
+ g_object_ref (info);
+ g_vfs_ftp_dir_cache_entry_unref (entry);
+ }
+ }
+ while (g_file_info_get_is_symlink (info) && lookups++ < 8);
+
+ g_vfs_ftp_file_free (link);
+ if (g_file_info_get_is_symlink (info))
+ {
+ /* too many recursions */
+ g_object_unref (info);
+ return original;
+ }
+
+ result = g_file_info_dup (info);
+ g_object_unref (info);
+ for (i = 0; i < G_N_ELEMENTS (copy_attributes); i++)
+ {
+ GFileAttributeType type;
+ gpointer value;
+
+ if (!g_file_info_get_attribute_data (original,
+ copy_attributes[i],
+ &type,
+ &value,
+ NULL))
+ continue;
+
+ g_file_info_set_attribute (result,
+ copy_attributes[i],
+ type,
+ value);
+ }
+ g_object_unref (original);
+
+ return result;
+}
+
+GFileInfo *
+g_vfs_ftp_dir_cache_lookup_file (GVfsFtpDirCache * cache,
+ GVfsFtpTask * task,
+ const GVfsFtpFile *file,
+ gboolean resolve_symlinks)
+{
+ GVfsFtpDirCacheEntry *entry;
+ GVfsFtpFile *dir;
+ GFileInfo *info;
+
+ g_return_val_if_fail (cache != NULL, NULL);
+ g_return_val_if_fail (task != NULL, NULL);
+ g_return_val_if_fail (file != NULL, NULL);
+
+ if (g_vfs_ftp_task_is_in_error (task))
+ return NULL;
+
+ if (g_vfs_ftp_file_is_root (file))
+ return g_object_ref (cache->root);
+
+ dir = g_vfs_ftp_file_new_parent (file);
+ entry = g_vfs_ftp_dir_cache_lookup_entry (cache, task, dir, 0);
+ g_vfs_ftp_file_free (dir);
+ if (entry == NULL)
+ {
+ g_vfs_ftp_dir_cache_entry_unref (entry);
+ return NULL;
+ }
+
+ info = g_hash_table_lookup (entry->files, file);
+ if (info != NULL)
+ {
+ g_object_ref (info);
+ if (resolve_symlinks)
+ info = g_vfs_ftp_dir_cache_resolve_symlink (cache, task, file, info, 0);
+ }
+
+ g_vfs_ftp_dir_cache_entry_unref (entry);
+ return info;
+}
+
+GList *
+g_vfs_ftp_dir_cache_lookup_dir (GVfsFtpDirCache * cache,
+ GVfsFtpTask * task,
+ const GVfsFtpFile *dir,
+ gboolean flush,
+ gboolean resolve_symlinks)
+{
+ GVfsFtpDirCacheEntry *entry;
+ GHashTableIter iter;
+ gpointer file, info;
+ guint stamp;
+ GList *result = NULL;
+
+ g_return_val_if_fail (cache != NULL, NULL);
+ g_return_val_if_fail (task != NULL, NULL);
+ g_return_val_if_fail (dir != NULL, NULL);
+
+ if (g_vfs_ftp_task_is_in_error (task))
+ return NULL;
+
+ if (flush)
+ {
+ g_mutex_lock (cache->lock);
+ g_assert (cache->stamp != G_MAXUINT);
+ stamp = ++cache->stamp;
+ g_mutex_unlock (cache->lock);
+ }
+ else
+ stamp = 0;
+
+ entry = g_vfs_ftp_dir_cache_lookup_entry (cache, task, dir, stamp);
+ if (entry == NULL)
+ return NULL;
+
+ g_hash_table_iter_init (&iter, entry->files);
+ while (g_hash_table_iter_next (&iter, &file, &info))
+ {
+ g_object_ref (info);
+ if (resolve_symlinks)
+ info = g_vfs_ftp_dir_cache_resolve_symlink (cache, task, file, info, stamp);
+ result = g_list_prepend (result, info);
+ }
+ g_vfs_ftp_dir_cache_entry_unref (entry);
+
+ return result;
+}
+
+void
+g_vfs_ftp_dir_cache_purge_dir (GVfsFtpDirCache * cache,
+ const GVfsFtpFile *dir)
+{
+ g_return_if_fail (cache != NULL);
+ g_return_if_fail (dir != NULL);
+
+ g_mutex_lock (cache->lock);
+ g_hash_table_remove (cache->directories, dir);
+ g_mutex_unlock (cache->lock);
+}
+
+void
+g_vfs_ftp_dir_cache_purge_file (GVfsFtpDirCache * cache,
+ const GVfsFtpFile *file)
+{
+ GVfsFtpFile *dir;
+
+ g_return_if_fail (cache != NULL);
+ g_return_if_fail (file != NULL);
+
+ if (g_vfs_ftp_file_is_root (file))
+ return;
+
+ dir = g_vfs_ftp_file_new_parent (file);
+ g_vfs_ftp_dir_cache_purge_dir (cache, dir);
+ g_vfs_ftp_file_free (dir);
+}
+
+/*** DIR CACHE FUNCS ***/
+
+#include "ParseFTPList.h"
+#include "gvfsdaemonutils.h"
+
+static gboolean
+g_vfs_ftp_dir_cache_funcs_process (GInputStream * stream,
+ const GVfsFtpFile * dir,
+ GVfsFtpDirCacheEntry *entry,
+ gboolean is_unix,
+ GCancellable * cancellable,
+ GError ** error)
+{
+ struct list_state state = { 0, };
+ GDataInputStream *data;
+ GFileInfo *info;
+ int type;
+ GVfsFtpFile *file;
+ char *line, *s;
+
+ /* protect against code reorg - in current code, error never is NULL */
+ g_assert (error != NULL);
+ g_assert (*error == NULL);
+
+ data = g_data_input_stream_new (stream);
+ /* we use LF only, because the mozilla code can handle lines ending in CR */
+ g_data_input_stream_set_newline_type (data, G_DATA_STREAM_NEWLINE_TYPE_LF);
+ while ((line = g_data_input_stream_read_line (data, NULL, cancellable, error)))
+ {
+ struct list_result result = { 0, };
+ GTimeVal tv = { 0, 0 };
+
+ type = ParseFTPList (line, &state, &result);
+ if (type != 'd' && type != 'f' && type != 'l')
+ {
+ g_free (line);
+ continue;
+ }
+
+ /* don't list . and .. directories
+ * Let's hope they're not important files on some ftp servers
+ */
+ if (result.fe_fnlen == 1 &&
+ result.fe_fname[0] == '.')
+ {
+ g_free (line);
+ continue;
+ }
+ if (result.fe_fnlen == 2 &&
+ result.fe_fname[0] == '.' &&
+ result.fe_fname[1] == '.')
+ {
+ g_free (line);
+ continue;
+ }
+
+ s = g_strndup (result.fe_fname, result.fe_fnlen);
+ file = g_vfs_ftp_file_new_child (dir, s, NULL);
+ g_free (s);
+ if (file == NULL)
+ {
+ g_free (line);
+ continue;
+ }
+
+ info = g_file_info_new ();
+
+ s = g_path_get_basename (g_vfs_ftp_file_get_gvfs_path (file));
+ g_file_info_set_name (info, s);
+ g_free (s);
+
+ if (type == 'l')
+ {
+ char *link;
+
+ link = g_strndup (result.fe_lname, result.fe_lnlen);
+ g_file_info_set_symlink_target (info, link);
+ g_file_info_set_is_symlink (info, TRUE);
+ g_free (link);
+ }
+
+ g_file_info_set_size (info, g_ascii_strtoull (result.fe_size, NULL, 10));
+
+ gvfs_file_info_populate_default (info, g_vfs_ftp_file_get_gvfs_path (file),
+ type == 'f' ? G_FILE_TYPE_REGULAR :
+ type == 'l' ? G_FILE_TYPE_SYMBOLIC_LINK :
+ G_FILE_TYPE_DIRECTORY);
+
+ if (unix)
+ g_file_info_set_is_hidden (info, result.fe_fnlen > 0 &&
+ result.fe_fname[0] == '.');
+
+ /* Workaround:
+ * result.fetime.tm_year contains actual year instead of offset-from-1900,
+ * which mktime expects.
+ */
+ if (result.fe_time.tm_year >= 1900)
+ result.fe_time.tm_year -= 1900;
+
+ tv.tv_sec = mktime (&result.fe_time);
+ if (tv.tv_sec != -1)
+ g_file_info_set_modification_time (info, &tv);
+
+ g_vfs_ftp_dir_cache_entry_add (entry, file, info);
+ g_free (line);
+ }
+
+ g_object_unref (data);
+ return *error != NULL;
+}
+
+static GVfsFtpFile *
+g_vfs_ftp_dir_cache_funcs_resolve_default (GVfsFtpTask * task,
+ const GVfsFtpFile *file,
+ const char * target)
+{
+ GVfsFtpFile *link;
+ GString *new_path;
+ char *match;
+
+ g_return_val_if_fail (file != NULL, NULL);
+ g_return_val_if_fail (target != NULL, NULL);
+
+ if (target[0] == '/')
+ return g_vfs_ftp_file_new_from_ftp (task->backend, target);
+
+ new_path = g_string_new (g_vfs_ftp_file_get_ftp_path (file));
+ /* only take directory */
+ match = strrchr (new_path->str, '/');
+ g_string_truncate (new_path, match - new_path->str + 1);
+ g_string_append (new_path, target);
+ g_string_append_c (new_path, '/'); /* slash at end makes code easier */
+ /* cleanup: remove all double slashes */
+ while ((match = strstr (new_path->str, "//")) != NULL)
+ {
+ g_string_erase (new_path, match - new_path->str, 1);
+ }
+ /* cleanup: remove all ".." and the preceeding directory */
+ while ((match = strstr (new_path->str, "/../")) != NULL)
+ {
+ if (match == new_path->str)
+ {
+ g_string_erase (new_path, 0, 3);
+ }
+ else
+ {
+ char *start = match - 1;
+ while (*start != '/')
+ start--;
+ g_string_erase (new_path, start - new_path->str, match - start + 3);
+ }
+ }
+ /* cleanup: remove all "." directories */
+ while ((match = strstr (new_path->str, "/./")) != NULL)
+ g_string_erase (new_path, match - new_path->str, 2);
+ /* remove trailing / */
+ g_string_set_size (new_path, new_path->len - 1);
+
+ link = g_vfs_ftp_file_new_from_ftp (task->backend, new_path->str);
+ g_string_free (new_path, TRUE);
+ return link;
+}
+
+static gboolean
+g_vfs_ftp_dir_cache_funcs_process_unix (GInputStream * stream,
+ const GVfsFtpFile * dir,
+ GVfsFtpDirCacheEntry *entry,
+ GCancellable * cancellable,
+ GError ** error)
+{
+ return g_vfs_ftp_dir_cache_funcs_process (stream, dir, entry, TRUE, cancellable, error);
+}
+
+static gboolean
+g_vfs_ftp_dir_cache_funcs_process_default (GInputStream * stream,
+ const GVfsFtpFile * dir,
+ GVfsFtpDirCacheEntry *entry,
+ GCancellable * cancellable,
+ GError ** error)
+{
+ return g_vfs_ftp_dir_cache_funcs_process (stream, dir, entry, FALSE, cancellable, error);
+}
+
+const GVfsFtpDirFuncs g_vfs_ftp_dir_cache_funcs_unix = {
+ "LIST -a",
+ g_vfs_ftp_dir_cache_funcs_process_unix,
+ g_vfs_ftp_dir_cache_funcs_resolve_default
+};
+
+const GVfsFtpDirFuncs g_vfs_ftp_dir_cache_funcs_default = {
+ "LIST",
+ g_vfs_ftp_dir_cache_funcs_process_default,
+ g_vfs_ftp_dir_cache_funcs_resolve_default
+};
diff --git a/daemon/gvfsftpdircache.h b/daemon/gvfsftpdircache.h
new file mode 100644
index 00000000..5a1237a8
--- /dev/null
+++ b/daemon/gvfsftpdircache.h
@@ -0,0 +1,76 @@
+/* GIO - GLib Input, Output and Streaming Library
+ *
+ * Copyright (C) 2009 Benjamin Otte <otte@gnome.org>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General
+ * Public License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: Benjamin Otte <otte@gnome.org>
+ */
+
+#ifndef __G_VFS_FTP_DIRCACHE_H__
+#define __G_VFS_FTP_DIRCACHE_H__
+
+#include <gvfsftpfile.h>
+#include <gvfsftptask.h>
+
+G_BEGIN_DECLS
+
+
+//typedef struct _GVfsFtpDirCache GVfsFtpDirCache;
+typedef struct _GVfsFtpDirCacheEntry GVfsFtpDirCacheEntry;
+//typedef struct _GVfsFtpDirFuncs GVfsFtpDirFuncs;
+
+struct _GVfsFtpDirFuncs {
+ const char * command;
+ gboolean (* process) (GInputStream * stream,
+ const GVfsFtpFile * dir,
+ GVfsFtpDirCacheEntry * entry,
+ GCancellable * cancellable,
+ GError ** error);
+ GVfsFtpFile * (* resolve_symlink) (GVfsFtpTask * task,
+ const GVfsFtpFile * file,
+ const char * target);
+};
+
+extern const GVfsFtpDirFuncs g_vfs_ftp_dir_cache_funcs_unix;
+extern const GVfsFtpDirFuncs g_vfs_ftp_dir_cache_funcs_default;
+
+GVfsFtpDirCache * g_vfs_ftp_dir_cache_new (const GVfsFtpDirFuncs *funcs,
+ GFileInfo * root);
+void g_vfs_ftp_dir_cache_free (GVfsFtpDirCache * cache);
+
+GFileInfo * g_vfs_ftp_dir_cache_lookup_file (GVfsFtpDirCache * cache,
+ GVfsFtpTask * task,
+ const GVfsFtpFile * file,
+ gboolean resolve_symlinks);
+GList * g_vfs_ftp_dir_cache_lookup_dir (GVfsFtpDirCache * cache,
+ GVfsFtpTask * task,
+ const GVfsFtpFile * dir,
+ gboolean flush,
+ gboolean resolve_symlinks);
+void g_vfs_ftp_dir_cache_purge_file (GVfsFtpDirCache * cache,
+ const GVfsFtpFile * file);
+void g_vfs_ftp_dir_cache_purge_dir (GVfsFtpDirCache * cache,
+ const GVfsFtpFile * dir);
+
+void g_vfs_ftp_dir_cache_entry_add (GVfsFtpDirCacheEntry * entry,
+ GVfsFtpFile * file,
+ GFileInfo * info);
+
+
+G_END_DECLS
+
+#endif /* __G_VFS_FTP_DIRCACHE_H__ */
diff --git a/daemon/gvfsftpfile.c b/daemon/gvfsftpfile.c
index f72c0fac..c16cc469 100644
--- a/daemon/gvfsftpfile.c
+++ b/daemon/gvfsftpfile.c
@@ -132,11 +132,11 @@ g_vfs_ftp_file_new_parent (const GVfsFtpFile *file)
g_return_val_if_fail (file != NULL, NULL);
+ if (g_vfs_ftp_file_is_root (file))
+ return g_vfs_ftp_file_copy (file);
+
dirname = g_path_get_dirname (file->gvfs_path);
- if (dirname[0] == '.' && dirname[1] == 0)
- dir = g_vfs_ftp_file_copy (file);
- else
- dir = g_vfs_ftp_file_new_from_gvfs (file->backend, dirname);
+ dir = g_vfs_ftp_file_new_from_gvfs (file->backend, dirname);
g_free (dirname);
return dir;
@@ -217,6 +217,22 @@ g_vfs_ftp_file_free (GVfsFtpFile *file)
g_slice_free (GVfsFtpFile, file);
}
+/**
+ * g_vfs_ftp_file_is_root:
+ * @file: the file to check
+ *
+ * Checks if the given file references the root directory.
+ *
+ * Returns: %TRUE if @file references the root directory
+ **/
+gboolean
+g_vfs_ftp_file_is_root (const GVfsFtpFile *file)
+{
+ g_return_val_if_fail (file != NULL, FALSE);
+
+ return file->gvfs_path[0] == '/' &&
+ file->gvfs_path[1] == 0;
+}
/**
* g_vfs_ftp_file_get_ftp_path:
diff --git a/daemon/gvfsftpfile.h b/daemon/gvfsftpfile.h
index ac007d87..d924a7fa 100644
--- a/daemon/gvfsftpfile.h
+++ b/daemon/gvfsftpfile.h
@@ -41,6 +41,7 @@ GVfsFtpFile * g_vfs_ftp_file_new_child (const GVfsFtpFile *
GVfsFtpFile * g_vfs_ftp_file_copy (const GVfsFtpFile * file);
void g_vfs_ftp_file_free (GVfsFtpFile * file);
+gboolean g_vfs_ftp_file_is_root (const GVfsFtpFile * file);
const char * g_vfs_ftp_file_get_ftp_path (const GVfsFtpFile * file);
const char * g_vfs_ftp_file_get_gvfs_path (const GVfsFtpFile * file);