gvfs/daemon/gvfsftpdircache.c

833 lines
25 KiB
C

/* 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., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*
* Author: Benjamin Otte <otte@gnome.org>
*/
#include <stdio.h>
#include <sys/stat.h>
#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 */
const GVfsFtpDirFuncs *funcs; /* functions to call */
};
GVfsFtpDirCache *
g_vfs_ftp_dir_cache_new (const GVfsFtpDirFuncs *funcs)
{
GVfsFtpDirCache *cache;
g_return_val_if_fail (funcs != NULL, 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);
g_mutex_init (&cache->lock);
cache->funcs = funcs;
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_clear (&cache->lock);
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_setup_data_connection (task);
g_vfs_ftp_task_send (task,
G_VFS_FTP_PASS_100 | G_VFS_FTP_FAIL_200,
"%s", cache->funcs->command);
g_vfs_ftp_task_open_data_connection (task);
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)),
g_vfs_ftp_connection_get_debug_id (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_lookup_file_internal (GVfsFtpDirCache * cache,
GVfsFtpTask * task,
const GVfsFtpFile *file,
guint stamp)
{
GVfsFtpDirCacheEntry *entry;
GVfsFtpFile *dir;
GFileInfo *info;
if (g_vfs_ftp_task_is_in_error (task))
return NULL;
if (!g_vfs_ftp_file_is_root (file))
{
dir = g_vfs_ftp_file_new_parent (file);
entry = g_vfs_ftp_dir_cache_lookup_entry (cache, task, dir, stamp);
g_vfs_ftp_file_free (dir);
if (entry == NULL)
return NULL;
info = g_hash_table_lookup (entry->files, file);
if (info != NULL)
{
/* NB: the order of ref/unref is important here */
g_object_ref (info);
g_vfs_ftp_dir_cache_entry_unref (entry);
return info;
}
g_vfs_ftp_dir_cache_entry_unref (entry);
}
if (g_vfs_ftp_task_is_in_error (task))
return NULL;
return cache->funcs->lookup_uncached (task, file);
}
static GFileInfo *
g_vfs_ftp_dir_cache_resolve_symlink (GVfsFtpDirCache * cache,
GVfsFtpTask * task,
const GVfsFtpFile *file,
GFileInfo * original,
guint stamp)
{
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;
const char *target;
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 {
target = g_file_info_get_symlink_target (info);
if (target == NULL)
{
/* This happens when bad servers don't report a symlink target.
* We now want to figure out if this is a directory or regular file,
* so we can at least report something useful.
*/
g_object_unref (info);
info = cache->funcs->lookup_uncached (task, file);
break;
}
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;
}
info = g_vfs_ftp_dir_cache_lookup_file_internal (cache, task, link, stamp);
if (info == NULL)
{
g_vfs_ftp_file_free (link);
g_vfs_ftp_task_clear_error (task);
return original;
}
}
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)
{
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);
info = g_vfs_ftp_dir_cache_lookup_file_internal (cache, task, file, 0);
if (info != NULL && resolve_symlinks)
info = g_vfs_ftp_dir_cache_resolve_symlink (cache, task, file, info, 0);
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);
g_assert (!g_vfs_ftp_task_is_in_error (task));
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 GFileInfo *
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);
icon = g_themed_icon_new ("folder-remote-symbolic");
g_file_info_set_symbolic_icon (info, icon);
g_object_unref (icon);
return info;
}
static GFileInfo *
g_vfs_ftp_dir_cache_funcs_lookup_uncached (GVfsFtpTask * task,
const GVfsFtpFile *file)
{
GFileInfo *info;
char **reply;
if (g_vfs_ftp_file_is_root (file))
return create_root_file_info (task->backend);
/* 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_send (task, 0, "CWD %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_DIRECTORY);
g_file_info_set_is_hidden (info, TRUE);
return info;
}
g_vfs_ftp_task_clear_error (task);
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);
return info;
}
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 NULL;
}
static gboolean
g_vfs_ftp_parse_mode (char file_mode[10],
guint32 *mode,
GFileType *file_type)
{
/* File type */
switch (file_mode[0])
{
case '-': /* Regular file */
*mode = S_IFREG;
*file_type = G_FILE_TYPE_REGULAR;
break;
case 'b': /* Block special file */
*mode = S_IFBLK;
*file_type = G_FILE_TYPE_SPECIAL;
break;
case 'c': /* Character special */
*mode = S_IFCHR;
*file_type = G_FILE_TYPE_SPECIAL;
break;
case 'd': /* Directory */
*mode = S_IFDIR;
*file_type = G_FILE_TYPE_DIRECTORY;
break;
case 'l': /* Symbolic link */
*mode = S_IFLNK;
*file_type = G_FILE_TYPE_SYMBOLIC_LINK;
break;
case 'p': /* FIFO */
*mode = S_IFIFO;
*file_type = G_FILE_TYPE_SPECIAL;
break;
default:
g_debug ("# couldn't parse file type from mode %.10s\n", file_mode);
*mode = 0;
*file_type = G_FILE_TYPE_UNKNOWN;
return FALSE;
}
/* Permissions */
if (file_mode[1] == 'r')
*mode |= S_IRUSR;
if (file_mode[2] == 'w')
*mode |= S_IWUSR;
switch (file_mode[3])
{
case 'x': *mode |= S_IXUSR; break;
case 'S': *mode |= S_ISUID; break;
case 's': *mode |= S_ISUID | S_IXUSR; break;
}
if (file_mode[4] == 'r')
*mode |= S_IRGRP;
if (file_mode[5] == 'w')
*mode |= S_IWGRP;
switch (file_mode[6])
{
case 'x': *mode |= S_IXGRP; break;
case 'S': *mode |= S_ISGID; break;
case 's': *mode |= S_ISGID | S_IXGRP; break;
}
if (file_mode[7] == 'r')
*mode |= S_IROTH;
if (file_mode[8] == 'w')
*mode |= S_IWOTH;
switch (file_mode[9])
{
case 'x': *mode |= S_IXOTH; break;
case 'T': *mode |= S_ISVTX; break;
case 't': *mode |= S_ISVTX | S_IXOTH; break;
}
return TRUE;
}
static gboolean
g_vfs_ftp_dir_cache_funcs_process (GInputStream * stream,
int debug_id,
const GVfsFtpFile * dir,
GVfsFtpDirCacheEntry *entry,
gboolean is_unix,
GCancellable * cancellable,
GError ** error)
{
struct list_state state = { NULL, };
GDataInputStream *data;
GFileInfo *info;
int type;
GVfsFtpFile *file;
char *line, *s;
gsize length;
/* 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, &length, cancellable, error)))
{
struct list_result result = { 0, };
GFileType file_type = G_FILE_TYPE_UNKNOWN;
time_t mtime;
/* strip trailing \r - ParseFTPList only removes it if the line ends in \r\n,
* but we stripped the \n already.
*/
if (length > 0 && line[length - 1] == '\r')
line[--length] = '\0';
g_debug ("<<%2d << %s\n", debug_id, line);
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_debug ("# invalid filename, skipping");
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));
/* If unix format then parse the attributes */
if (state.lstyle == 'U')
{
char file_mode[10], uid[64], gid[64];
guint32 mode;
/* POSIX ls -l form: mode, links, owner, group */
if (sscanf(line, "%10c %*u %63s %63s", file_mode, uid, gid) == 3)
{
if (g_vfs_ftp_parse_mode (file_mode, &mode, &file_type))
{
g_file_info_set_attribute_uint32 (info, G_FILE_ATTRIBUTE_UNIX_MODE, mode);
g_file_info_set_attribute_string (info, G_FILE_ATTRIBUTE_OWNER_USER, uid);
g_file_info_set_attribute_string (info, G_FILE_ATTRIBUTE_OWNER_GROUP, gid);
}
}
else
g_debug ("# unknown listing format\n");
}
g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_TRASH, FALSE);
if (file_type == G_FILE_TYPE_UNKNOWN)
{
file_type = type == 'f' ? G_FILE_TYPE_REGULAR :
type == 'l' ? G_FILE_TYPE_SYMBOLIC_LINK :
G_FILE_TYPE_DIRECTORY;
}
gvfs_file_info_populate_default (info,
g_vfs_ftp_file_get_gvfs_path (file),
file_type);
if (is_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;
mtime = mktime (&result.fe_time);
if (mtime != -1)
{
char *etag = g_strdup_printf ("%ld", mtime);
g_file_info_set_attribute_string (info,
G_FILE_ATTRIBUTE_ETAG_VALUE,
etag);
g_free (etag);
g_file_info_set_attribute_uint64 (info, G_FILE_ATTRIBUTE_TIME_MODIFIED, mtime);
g_file_info_set_attribute_uint32 (info, G_FILE_ATTRIBUTE_TIME_MODIFIED_USEC, 0);
}
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] == '/')
{
new_path = g_string_new (target);
}
else
{
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,
int debug_id,
const GVfsFtpFile * dir,
GVfsFtpDirCacheEntry *entry,
GCancellable * cancellable,
GError ** error)
{
return g_vfs_ftp_dir_cache_funcs_process (stream, debug_id, dir, entry, TRUE, cancellable, error);
}
static gboolean
g_vfs_ftp_dir_cache_funcs_process_default (GInputStream * stream,
int debug_id,
const GVfsFtpFile * dir,
GVfsFtpDirCacheEntry *entry,
GCancellable * cancellable,
GError ** error)
{
return g_vfs_ftp_dir_cache_funcs_process (stream, debug_id, 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_lookup_uncached,
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_lookup_uncached,
g_vfs_ftp_dir_cache_funcs_resolve_default
};