diff options
author | Benjamin Otte <otte@gnome.org> | 2009-06-04 18:41:35 +0200 |
---|---|---|
committer | Benjamin Otte <otte@gnome.org> | 2009-06-11 10:05:40 +0200 |
commit | 5bca61ba06c374b5d4ef197e4805074880ddee20 (patch) | |
tree | e4469dcf08bbb7fbb4ab2d6960277db8bd0ebce4 /daemon | |
parent | 5e3ce10bd814834731ac70e584a90b64ab2860fe (diff) | |
download | gvfs-5bca61ba06c374b5d4ef197e4805074880ddee20.tar.gz |
[FTP] rework cache handling
The cache handling has been split into a separate file and structure
now. While the API still isn't perfect, it's much clearer than before.
It's also faster when looking up lots of symlinks.
Diffstat (limited to 'daemon')
-rw-r--r-- | daemon/Makefile.am | 1 | ||||
-rw-r--r-- | daemon/gvfsbackendftp.c | 771 | ||||
-rw-r--r-- | daemon/gvfsbackendftp.h | 13 | ||||
-rw-r--r-- | daemon/gvfsftpconnection.c | 19 | ||||
-rw-r--r-- | daemon/gvfsftpconnection.h | 1 | ||||
-rw-r--r-- | daemon/gvfsftpdircache.c | 596 | ||||
-rw-r--r-- | daemon/gvfsftpdircache.h | 76 | ||||
-rw-r--r-- | daemon/gvfsftpfile.c | 24 | ||||
-rw-r--r-- | daemon/gvfsftpfile.h | 1 |
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); |