forked from AuroraMiddleware/gtk
ae42f286bb
This lets us do proper completion in GtkFileChooserEntry even when no base folder has been set. Completion for relative paths won't work, as usual, as expected.
1868 lines
45 KiB
C
1868 lines
45 KiB
C
/* GTK - The GIMP Toolkit
|
|
* gtkfilesystem.c: Filesystem abstraction functions.
|
|
* Copyright (C) 2003, Red Hat, Inc.
|
|
* Copyright (C) 2007-2008 Carlos Garnacho
|
|
*
|
|
* This program 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 program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
|
|
*
|
|
* Authors: Carlos Garnacho <carlos@imendio.com>
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#include <string.h>
|
|
|
|
#include <glib/gi18n-lib.h>
|
|
|
|
#include "gtkfilechooser.h"
|
|
#include "gtkfilesystem.h"
|
|
#include "gtkicontheme.h"
|
|
#include "gtkprivate.h"
|
|
|
|
/* #define DEBUG_MODE */
|
|
#ifdef DEBUG_MODE
|
|
#define DEBUG(x) g_debug (x);
|
|
#else
|
|
#define DEBUG(x)
|
|
#endif
|
|
|
|
#define FILES_PER_QUERY 100
|
|
|
|
/* The pointers we return for a GtkFileSystemVolume are opaque tokens; they are
|
|
* really pointers to GDrive, GVolume or GMount objects. We need an extra
|
|
* token for the fake "File System" volume. So, we'll return a pointer to
|
|
* this particular string.
|
|
*/
|
|
static const gchar *root_volume_token = N_("File System");
|
|
#define IS_ROOT_VOLUME(volume) ((gpointer) (volume) == (gpointer) root_volume_token)
|
|
|
|
enum {
|
|
PROP_0,
|
|
PROP_FILE,
|
|
PROP_ENUMERATOR,
|
|
PROP_ATTRIBUTES
|
|
};
|
|
|
|
enum {
|
|
BOOKMARKS_CHANGED,
|
|
VOLUMES_CHANGED,
|
|
FS_LAST_SIGNAL
|
|
};
|
|
|
|
enum {
|
|
FILES_ADDED,
|
|
FILES_REMOVED,
|
|
FILES_CHANGED,
|
|
FINISHED_LOADING,
|
|
DELETED,
|
|
FOLDER_LAST_SIGNAL
|
|
};
|
|
|
|
static guint fs_signals [FS_LAST_SIGNAL] = { 0, };
|
|
static guint folder_signals [FOLDER_LAST_SIGNAL] = { 0, };
|
|
|
|
typedef struct AsyncFuncData AsyncFuncData;
|
|
|
|
struct GtkFileSystemPrivate
|
|
{
|
|
GVolumeMonitor *volume_monitor;
|
|
|
|
/* This list contains elements that can be
|
|
* of type GDrive, GVolume and GMount
|
|
*/
|
|
GSList *volumes;
|
|
|
|
/* This list contains GtkFileSystemBookmark structs */
|
|
GSList *bookmarks;
|
|
|
|
GFileMonitor *bookmarks_monitor;
|
|
};
|
|
|
|
struct GtkFolderPrivate
|
|
{
|
|
GFile *folder_file;
|
|
GHashTable *children;
|
|
GFileMonitor *directory_monitor;
|
|
GFileEnumerator *enumerator;
|
|
GCancellable *cancellable;
|
|
gchar *attributes;
|
|
|
|
guint finished_loading : 1;
|
|
};
|
|
|
|
struct AsyncFuncData
|
|
{
|
|
GtkFileSystem *file_system;
|
|
GFile *file;
|
|
GtkFolder *folder;
|
|
GCancellable *cancellable;
|
|
gchar *attributes;
|
|
|
|
gpointer callback;
|
|
gpointer data;
|
|
};
|
|
|
|
struct GtkFileSystemBookmark
|
|
{
|
|
GFile *file;
|
|
gchar *label;
|
|
};
|
|
|
|
G_DEFINE_TYPE (GtkFileSystem, _gtk_file_system, G_TYPE_OBJECT)
|
|
|
|
G_DEFINE_TYPE (GtkFolder, _gtk_folder, G_TYPE_OBJECT)
|
|
|
|
|
|
static void gtk_folder_set_finished_loading (GtkFolder *folder,
|
|
gboolean finished_loading);
|
|
static void gtk_folder_add_file (GtkFolder *folder,
|
|
GFile *file,
|
|
GFileInfo *info);
|
|
|
|
|
|
/* GtkFileSystemBookmark methods */
|
|
void
|
|
_gtk_file_system_bookmark_free (GtkFileSystemBookmark *bookmark)
|
|
{
|
|
g_object_unref (bookmark->file);
|
|
g_free (bookmark->label);
|
|
g_slice_free (GtkFileSystemBookmark, bookmark);
|
|
}
|
|
|
|
/* GtkFileSystem methods */
|
|
static void
|
|
volumes_changed (GVolumeMonitor *volume_monitor,
|
|
gpointer volume,
|
|
gpointer user_data)
|
|
{
|
|
GtkFileSystem *file_system;
|
|
|
|
gdk_threads_enter ();
|
|
|
|
file_system = GTK_FILE_SYSTEM (user_data);
|
|
g_signal_emit (file_system, fs_signals[VOLUMES_CHANGED], 0, volume);
|
|
gdk_threads_leave ();
|
|
}
|
|
|
|
static void
|
|
gtk_file_system_dispose (GObject *object)
|
|
{
|
|
GtkFileSystem *file_system = GTK_FILE_SYSTEM (object);
|
|
GtkFileSystemPrivate *priv = file_system->priv;
|
|
|
|
DEBUG ("dispose");
|
|
|
|
if (priv->volumes)
|
|
{
|
|
g_slist_foreach (priv->volumes, (GFunc) g_object_unref, NULL);
|
|
g_slist_free (priv->volumes);
|
|
priv->volumes = NULL;
|
|
}
|
|
|
|
if (priv->volume_monitor)
|
|
{
|
|
g_signal_handlers_disconnect_by_func (priv->volume_monitor, volumes_changed, object);
|
|
g_object_unref (priv->volume_monitor);
|
|
priv->volume_monitor = NULL;
|
|
}
|
|
|
|
G_OBJECT_CLASS (_gtk_file_system_parent_class)->dispose (object);
|
|
}
|
|
|
|
static void
|
|
gtk_file_system_finalize (GObject *object)
|
|
{
|
|
GtkFileSystem *file_system = GTK_FILE_SYSTEM (object);
|
|
GtkFileSystemPrivate *priv = file_system->priv;
|
|
|
|
DEBUG ("finalize");
|
|
|
|
if (priv->bookmarks_monitor)
|
|
g_object_unref (priv->bookmarks_monitor);
|
|
|
|
if (priv->bookmarks)
|
|
{
|
|
g_slist_foreach (priv->bookmarks, (GFunc) _gtk_file_system_bookmark_free, NULL);
|
|
g_slist_free (priv->bookmarks);
|
|
}
|
|
|
|
G_OBJECT_CLASS (_gtk_file_system_parent_class)->finalize (object);
|
|
}
|
|
|
|
static void
|
|
_gtk_file_system_class_init (GtkFileSystemClass *class)
|
|
{
|
|
GObjectClass *object_class = G_OBJECT_CLASS (class);
|
|
|
|
object_class->dispose = gtk_file_system_dispose;
|
|
object_class->finalize = gtk_file_system_finalize;
|
|
|
|
fs_signals[BOOKMARKS_CHANGED] =
|
|
g_signal_new ("bookmarks-changed",
|
|
G_TYPE_FROM_CLASS (object_class),
|
|
G_SIGNAL_RUN_LAST,
|
|
G_STRUCT_OFFSET (GtkFileSystemClass, bookmarks_changed),
|
|
NULL, NULL,
|
|
g_cclosure_marshal_VOID__VOID,
|
|
G_TYPE_NONE, 0);
|
|
|
|
fs_signals[VOLUMES_CHANGED] =
|
|
g_signal_new ("volumes-changed",
|
|
G_TYPE_FROM_CLASS (object_class),
|
|
G_SIGNAL_RUN_LAST,
|
|
G_STRUCT_OFFSET (GtkFileSystemClass, volumes_changed),
|
|
NULL, NULL,
|
|
g_cclosure_marshal_VOID__VOID,
|
|
G_TYPE_NONE, 0);
|
|
|
|
g_type_class_add_private (object_class, sizeof (GtkFileSystemPrivate));
|
|
}
|
|
|
|
static GFile *
|
|
get_bookmarks_file (void)
|
|
{
|
|
GFile *file;
|
|
gchar *filename;
|
|
|
|
filename = g_build_filename (g_get_home_dir (), ".gtk-bookmarks", NULL);
|
|
file = g_file_new_for_path (filename);
|
|
g_free (filename);
|
|
|
|
return file;
|
|
}
|
|
|
|
static GSList *
|
|
read_bookmarks (GFile *file)
|
|
{
|
|
gchar *contents;
|
|
gchar **lines, *space;
|
|
GSList *bookmarks = NULL;
|
|
gint i;
|
|
|
|
if (!g_file_load_contents (file, NULL, &contents,
|
|
NULL, NULL, NULL))
|
|
return NULL;
|
|
|
|
lines = g_strsplit (contents, "\n", -1);
|
|
|
|
for (i = 0; lines[i]; i++)
|
|
{
|
|
GtkFileSystemBookmark *bookmark;
|
|
|
|
if (!*lines[i])
|
|
continue;
|
|
|
|
bookmark = g_slice_new0 (GtkFileSystemBookmark);
|
|
|
|
if ((space = strchr (lines[i], ' ')) != NULL)
|
|
{
|
|
space[0] = '\0';
|
|
bookmark->label = g_strdup (space + 1);
|
|
}
|
|
|
|
bookmark->file = g_file_new_for_uri (lines[i]);
|
|
bookmarks = g_slist_prepend (bookmarks, bookmark);
|
|
}
|
|
|
|
bookmarks = g_slist_reverse (bookmarks);
|
|
g_strfreev (lines);
|
|
g_free (contents);
|
|
|
|
return bookmarks;
|
|
}
|
|
|
|
static void
|
|
save_bookmarks (GFile *bookmarks_file,
|
|
GSList *bookmarks)
|
|
{
|
|
GError *error = NULL;
|
|
GString *contents;
|
|
|
|
contents = g_string_new ("");
|
|
|
|
while (bookmarks)
|
|
{
|
|
GtkFileSystemBookmark *bookmark;
|
|
gchar *uri;
|
|
|
|
bookmark = bookmarks->data;
|
|
uri = g_file_get_uri (bookmark->file);
|
|
g_string_append (contents, uri);
|
|
|
|
if (bookmark->label)
|
|
g_string_append_printf (contents, " %s", bookmark->label);
|
|
|
|
g_string_append_c (contents, '\n');
|
|
bookmarks = bookmarks->next;
|
|
g_free (uri);
|
|
}
|
|
|
|
if (!g_file_replace_contents (bookmarks_file,
|
|
contents->str,
|
|
strlen (contents->str),
|
|
NULL, FALSE, 0, NULL,
|
|
NULL, &error))
|
|
{
|
|
g_critical ("%s", error->message);
|
|
g_error_free (error);
|
|
}
|
|
|
|
g_string_free (contents, TRUE);
|
|
}
|
|
|
|
static void
|
|
bookmarks_file_changed (GFileMonitor *monitor,
|
|
GFile *file,
|
|
GFile *other_file,
|
|
GFileMonitorEvent event,
|
|
gpointer data)
|
|
{
|
|
GtkFileSystem *file_system = GTK_FILE_SYSTEM (data);
|
|
GtkFileSystemPrivate *priv = file_system->priv;
|
|
|
|
switch (event)
|
|
{
|
|
case G_FILE_MONITOR_EVENT_CHANGED:
|
|
case G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT:
|
|
case G_FILE_MONITOR_EVENT_CREATED:
|
|
case G_FILE_MONITOR_EVENT_DELETED:
|
|
g_slist_foreach (priv->bookmarks, (GFunc) _gtk_file_system_bookmark_free, NULL);
|
|
g_slist_free (priv->bookmarks);
|
|
|
|
priv->bookmarks = read_bookmarks (file);
|
|
|
|
gdk_threads_enter ();
|
|
g_signal_emit (data, fs_signals[BOOKMARKS_CHANGED], 0);
|
|
gdk_threads_leave ();
|
|
break;
|
|
default:
|
|
/* ignore at the moment */
|
|
break;
|
|
}
|
|
}
|
|
|
|
static gboolean
|
|
mount_referenced_by_volume_activation_root (GList *volumes, GMount *mount)
|
|
{
|
|
GList *l;
|
|
GFile *mount_root;
|
|
gboolean ret;
|
|
|
|
ret = FALSE;
|
|
|
|
mount_root = g_mount_get_root (mount);
|
|
|
|
for (l = volumes; l != NULL; l = l->next)
|
|
{
|
|
GVolume *volume = G_VOLUME (l->data);
|
|
GFile *volume_activation_root;
|
|
|
|
volume_activation_root = g_volume_get_activation_root (volume);
|
|
if (volume_activation_root != NULL)
|
|
{
|
|
if (g_file_has_prefix (volume_activation_root, mount_root))
|
|
{
|
|
ret = TRUE;
|
|
g_object_unref (volume_activation_root);
|
|
break;
|
|
}
|
|
g_object_unref (volume_activation_root);
|
|
}
|
|
}
|
|
|
|
g_object_unref (mount_root);
|
|
return ret;
|
|
}
|
|
|
|
static void
|
|
get_volumes_list (GtkFileSystem *file_system)
|
|
{
|
|
GtkFileSystemPrivate *priv = file_system->priv;
|
|
GList *l, *ll;
|
|
GList *drives;
|
|
GList *volumes;
|
|
GList *mounts;
|
|
GDrive *drive;
|
|
GVolume *volume;
|
|
GMount *mount;
|
|
|
|
if (priv->volumes)
|
|
{
|
|
g_slist_foreach (priv->volumes, (GFunc) g_object_unref, NULL);
|
|
g_slist_free (priv->volumes);
|
|
priv->volumes = NULL;
|
|
}
|
|
|
|
/* first go through all connected drives */
|
|
drives = g_volume_monitor_get_connected_drives (priv->volume_monitor);
|
|
|
|
for (l = drives; l != NULL; l = l->next)
|
|
{
|
|
drive = l->data;
|
|
volumes = g_drive_get_volumes (drive);
|
|
|
|
if (volumes)
|
|
{
|
|
for (ll = volumes; ll != NULL; ll = ll->next)
|
|
{
|
|
volume = ll->data;
|
|
mount = g_volume_get_mount (volume);
|
|
|
|
if (mount)
|
|
{
|
|
/* Show mounted volume */
|
|
priv->volumes = g_slist_prepend (priv->volumes, g_object_ref (mount));
|
|
g_object_unref (mount);
|
|
}
|
|
else
|
|
{
|
|
/* Do show the unmounted volumes in the sidebar;
|
|
* this is so the user can mount it (in case automounting
|
|
* is off).
|
|
*
|
|
* Also, even if automounting is enabled, this gives a visual
|
|
* cue that the user should remember to yank out the media if
|
|
* he just unmounted it.
|
|
*/
|
|
priv->volumes = g_slist_prepend (priv->volumes, g_object_ref (volume));
|
|
}
|
|
|
|
g_object_unref (volume);
|
|
}
|
|
|
|
g_list_free (volumes);
|
|
}
|
|
else if (g_drive_is_media_removable (drive) && !g_drive_is_media_check_automatic (drive))
|
|
{
|
|
/* If the drive has no mountable volumes and we cannot detect media change.. we
|
|
* display the drive in the sidebar so the user can manually poll the drive by
|
|
* right clicking and selecting "Rescan..."
|
|
*
|
|
* This is mainly for drives like floppies where media detection doesn't
|
|
* work.. but it's also for human beings who like to turn off media detection
|
|
* in the OS to save battery juice.
|
|
*/
|
|
|
|
priv->volumes = g_slist_prepend (priv->volumes, g_object_ref (drive));
|
|
}
|
|
|
|
g_object_unref (drive);
|
|
}
|
|
|
|
g_list_free (drives);
|
|
|
|
/* add all volumes that is not associated with a drive */
|
|
volumes = g_volume_monitor_get_volumes (priv->volume_monitor);
|
|
|
|
for (l = volumes; l != NULL; l = l->next)
|
|
{
|
|
volume = l->data;
|
|
drive = g_volume_get_drive (volume);
|
|
|
|
if (drive)
|
|
{
|
|
g_object_unref (drive);
|
|
continue;
|
|
}
|
|
|
|
mount = g_volume_get_mount (volume);
|
|
|
|
if (mount)
|
|
{
|
|
/* show this mount */
|
|
priv->volumes = g_slist_prepend (priv->volumes, g_object_ref (mount));
|
|
g_object_unref (mount);
|
|
}
|
|
else
|
|
{
|
|
/* see comment above in why we add an icon for a volume */
|
|
priv->volumes = g_slist_prepend (priv->volumes, g_object_ref (volume));
|
|
}
|
|
|
|
g_object_unref (volume);
|
|
}
|
|
|
|
/* add mounts that has no volume (/etc/mtab mounts, ftp, sftp,...) */
|
|
mounts = g_volume_monitor_get_mounts (priv->volume_monitor);
|
|
|
|
for (l = mounts; l != NULL; l = l->next)
|
|
{
|
|
mount = l->data;
|
|
volume = g_mount_get_volume (mount);
|
|
|
|
if (volume)
|
|
{
|
|
g_object_unref (volume);
|
|
continue;
|
|
}
|
|
|
|
/* if there's exists one or more volumes with an activation root inside the mount,
|
|
* don't display the mount
|
|
*/
|
|
if (mount_referenced_by_volume_activation_root (volumes, mount))
|
|
{
|
|
g_object_unref (mount);
|
|
continue;
|
|
}
|
|
|
|
/* show this mount */
|
|
priv->volumes = g_slist_prepend (priv->volumes, g_object_ref (mount));
|
|
g_object_unref (mount);
|
|
}
|
|
|
|
g_list_free (volumes);
|
|
|
|
g_list_free (mounts);
|
|
}
|
|
|
|
static void
|
|
_gtk_file_system_init (GtkFileSystem *file_system)
|
|
{
|
|
GtkFileSystemPrivate *priv;
|
|
GFile *bookmarks_file;
|
|
GError *error = NULL;
|
|
|
|
DEBUG ("init");
|
|
|
|
file_system->priv = G_TYPE_INSTANCE_GET_PRIVATE (file_system,
|
|
GTK_TYPE_FILE_SYSTEM,
|
|
GtkFileSystemPrivate);
|
|
priv = file_system->priv;
|
|
|
|
/* Volumes */
|
|
priv->volume_monitor = g_volume_monitor_get ();
|
|
|
|
g_signal_connect (priv->volume_monitor, "mount-added",
|
|
G_CALLBACK (volumes_changed), file_system);
|
|
g_signal_connect (priv->volume_monitor, "mount-removed",
|
|
G_CALLBACK (volumes_changed), file_system);
|
|
g_signal_connect (priv->volume_monitor, "mount-changed",
|
|
G_CALLBACK (volumes_changed), file_system);
|
|
g_signal_connect (priv->volume_monitor, "volume-added",
|
|
G_CALLBACK (volumes_changed), file_system);
|
|
g_signal_connect (priv->volume_monitor, "volume-removed",
|
|
G_CALLBACK (volumes_changed), file_system);
|
|
g_signal_connect (priv->volume_monitor, "volume-changed",
|
|
G_CALLBACK (volumes_changed), file_system);
|
|
g_signal_connect (priv->volume_monitor, "drive-connected",
|
|
G_CALLBACK (volumes_changed), file_system);
|
|
g_signal_connect (priv->volume_monitor, "drive-disconnected",
|
|
G_CALLBACK (volumes_changed), file_system);
|
|
g_signal_connect (priv->volume_monitor, "drive-changed",
|
|
G_CALLBACK (volumes_changed), file_system);
|
|
|
|
/* Bookmarks */
|
|
bookmarks_file = get_bookmarks_file ();
|
|
priv->bookmarks = read_bookmarks (bookmarks_file);
|
|
priv->bookmarks_monitor = g_file_monitor_file (bookmarks_file,
|
|
G_FILE_MONITOR_NONE,
|
|
NULL, &error);
|
|
if (error)
|
|
{
|
|
g_warning ("%s", error->message);
|
|
g_error_free (error);
|
|
}
|
|
else
|
|
g_signal_connect (priv->bookmarks_monitor, "changed",
|
|
G_CALLBACK (bookmarks_file_changed), file_system);
|
|
|
|
g_object_unref (bookmarks_file);
|
|
}
|
|
|
|
/* GtkFileSystem public methods */
|
|
GtkFileSystem *
|
|
_gtk_file_system_new (void)
|
|
{
|
|
return g_object_new (GTK_TYPE_FILE_SYSTEM, NULL);
|
|
}
|
|
|
|
GSList *
|
|
_gtk_file_system_list_volumes (GtkFileSystem *file_system)
|
|
{
|
|
GtkFileSystemPrivate *priv = file_system->priv;
|
|
GSList *list;
|
|
|
|
DEBUG ("list_volumes");
|
|
|
|
get_volumes_list (file_system);
|
|
|
|
list = g_slist_copy (priv->volumes);
|
|
|
|
#ifndef G_OS_WIN32
|
|
/* Prepend root volume */
|
|
list = g_slist_prepend (list, (gpointer) root_volume_token);
|
|
#endif
|
|
|
|
return list;
|
|
}
|
|
|
|
GSList *
|
|
_gtk_file_system_list_bookmarks (GtkFileSystem *file_system)
|
|
{
|
|
GtkFileSystemPrivate *priv = file_system->priv;
|
|
GSList *bookmarks, *files = NULL;
|
|
|
|
DEBUG ("list_bookmarks");
|
|
|
|
bookmarks = priv->bookmarks;
|
|
|
|
while (bookmarks)
|
|
{
|
|
GtkFileSystemBookmark *bookmark;
|
|
|
|
bookmark = bookmarks->data;
|
|
bookmarks = bookmarks->next;
|
|
|
|
files = g_slist_prepend (files, g_object_ref (bookmark->file));
|
|
}
|
|
|
|
return g_slist_reverse (files);
|
|
}
|
|
|
|
static gboolean
|
|
is_valid_scheme_character (char c)
|
|
{
|
|
return g_ascii_isalnum (c) || c == '+' || c == '-' || c == '.';
|
|
}
|
|
|
|
static gboolean
|
|
has_uri_scheme (const char *str)
|
|
{
|
|
const char *p;
|
|
|
|
p = str;
|
|
|
|
if (!is_valid_scheme_character (*p))
|
|
return FALSE;
|
|
|
|
do
|
|
p++;
|
|
while (is_valid_scheme_character (*p));
|
|
|
|
return (strncmp (p, "://", 3) == 0);
|
|
}
|
|
|
|
gboolean
|
|
_gtk_file_system_parse (GtkFileSystem *file_system,
|
|
GFile *base_file,
|
|
const gchar *str,
|
|
GFile **folder,
|
|
gchar **file_part,
|
|
GError **error)
|
|
{
|
|
GFile *file;
|
|
gboolean result = FALSE;
|
|
gboolean is_dir = FALSE;
|
|
gchar *last_slash = NULL;
|
|
gboolean is_uri;
|
|
|
|
DEBUG ("parse");
|
|
|
|
if (str && *str)
|
|
is_dir = (str [strlen (str) - 1] == G_DIR_SEPARATOR);
|
|
|
|
last_slash = strrchr (str, G_DIR_SEPARATOR);
|
|
|
|
is_uri = has_uri_scheme (str);
|
|
|
|
if (is_uri)
|
|
{
|
|
const char *colon;
|
|
const char *slash_after_hostname;
|
|
|
|
colon = strchr (str, ':');
|
|
g_assert (colon != NULL);
|
|
g_assert (strncmp (colon, "://", 3) == 0);
|
|
|
|
slash_after_hostname = strchr (colon + 3, '/');
|
|
|
|
if (slash_after_hostname == NULL)
|
|
{
|
|
/* We don't have a full hostname yet. So, don't switch the folder
|
|
* until we have seen a full hostname. Otherwise, completion will
|
|
* happen for every character the user types for the hostname.
|
|
*/
|
|
|
|
*folder = NULL;
|
|
*file_part = NULL;
|
|
g_set_error (error,
|
|
GTK_FILE_CHOOSER_ERROR,
|
|
GTK_FILE_CHOOSER_ERROR_INCOMPLETE_HOSTNAME,
|
|
"Incomplete hostname");
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
if (str[0] == '~' || g_path_is_absolute (str) || is_uri)
|
|
file = g_file_parse_name (str);
|
|
else
|
|
{
|
|
if (base_file)
|
|
file = g_file_resolve_relative_path (base_file, str);
|
|
else
|
|
{
|
|
*folder = NULL;
|
|
*file_part = NULL;
|
|
g_set_error (error,
|
|
GTK_FILE_CHOOSER_ERROR,
|
|
GTK_FILE_CHOOSER_ERROR_BAD_FILENAME,
|
|
_("Invalid path"));
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
if (base_file && g_file_equal (base_file, file))
|
|
{
|
|
/* this is when user types '.', could be the
|
|
* beginning of a hidden file, ./ or ../
|
|
*/
|
|
*folder = g_object_ref (file);
|
|
*file_part = g_strdup (str);
|
|
result = TRUE;
|
|
}
|
|
else if (is_dir)
|
|
{
|
|
/* it's a dir, or at least it ends with the dir separator */
|
|
*folder = g_object_ref (file);
|
|
*file_part = g_strdup ("");
|
|
result = TRUE;
|
|
}
|
|
else
|
|
{
|
|
GFile *parent_file;
|
|
|
|
parent_file = g_file_get_parent (file);
|
|
|
|
if (!parent_file)
|
|
{
|
|
g_set_error (error,
|
|
GTK_FILE_CHOOSER_ERROR,
|
|
GTK_FILE_CHOOSER_ERROR_NONEXISTENT,
|
|
"Could not get parent file");
|
|
*folder = NULL;
|
|
*file_part = NULL;
|
|
}
|
|
else
|
|
{
|
|
*folder = parent_file;
|
|
result = TRUE;
|
|
|
|
if (last_slash)
|
|
*file_part = g_strdup (last_slash + 1);
|
|
else
|
|
*file_part = g_strdup (str);
|
|
}
|
|
}
|
|
|
|
g_object_unref (file);
|
|
|
|
return result;
|
|
}
|
|
|
|
static void
|
|
free_async_data (AsyncFuncData *async_data)
|
|
{
|
|
g_object_unref (async_data->file_system);
|
|
g_object_unref (async_data->file);
|
|
g_object_unref (async_data->cancellable);
|
|
|
|
if (async_data->folder)
|
|
g_object_unref (async_data->folder);
|
|
|
|
g_free (async_data->attributes);
|
|
g_free (async_data);
|
|
}
|
|
|
|
static void
|
|
enumerate_children_callback (GObject *source_object,
|
|
GAsyncResult *result,
|
|
gpointer user_data)
|
|
{
|
|
GFileEnumerator *enumerator;
|
|
AsyncFuncData *async_data;
|
|
GtkFolder *folder = NULL;
|
|
GFile *file;
|
|
GError *error = NULL;
|
|
|
|
file = G_FILE (source_object);
|
|
async_data = (AsyncFuncData *) user_data;
|
|
enumerator = g_file_enumerate_children_finish (file, result, &error);
|
|
|
|
if (enumerator)
|
|
{
|
|
folder = g_object_new (GTK_TYPE_FOLDER,
|
|
"file", source_object,
|
|
"enumerator", enumerator,
|
|
"attributes", async_data->attributes,
|
|
NULL);
|
|
g_object_unref (enumerator);
|
|
}
|
|
|
|
gdk_threads_enter ();
|
|
((GtkFileSystemGetFolderCallback) async_data->callback) (async_data->cancellable,
|
|
folder, error, async_data->data);
|
|
gdk_threads_leave ();
|
|
|
|
free_async_data (async_data);
|
|
|
|
if (error)
|
|
g_error_free (error);
|
|
}
|
|
|
|
GCancellable *
|
|
_gtk_file_system_get_folder (GtkFileSystem *file_system,
|
|
GFile *file,
|
|
const gchar *attributes,
|
|
GtkFileSystemGetFolderCallback callback,
|
|
gpointer data)
|
|
{
|
|
GCancellable *cancellable;
|
|
AsyncFuncData *async_data;
|
|
|
|
g_return_val_if_fail (GTK_IS_FILE_SYSTEM (file_system), NULL);
|
|
g_return_val_if_fail (G_IS_FILE (file), NULL);
|
|
|
|
cancellable = g_cancellable_new ();
|
|
|
|
async_data = g_new0 (AsyncFuncData, 1);
|
|
async_data->file_system = g_object_ref (file_system);
|
|
async_data->file = g_object_ref (file);
|
|
async_data->cancellable = g_object_ref (cancellable);
|
|
async_data->attributes = g_strdup (attributes);
|
|
|
|
async_data->callback = callback;
|
|
async_data->data = data;
|
|
|
|
g_file_enumerate_children_async (file,
|
|
attributes,
|
|
G_FILE_QUERY_INFO_NONE,
|
|
G_PRIORITY_DEFAULT,
|
|
cancellable,
|
|
enumerate_children_callback,
|
|
async_data);
|
|
return cancellable;
|
|
}
|
|
|
|
static void
|
|
query_info_callback (GObject *source_object,
|
|
GAsyncResult *result,
|
|
gpointer user_data)
|
|
{
|
|
AsyncFuncData *async_data;
|
|
GError *error = NULL;
|
|
GFileInfo *file_info;
|
|
GFile *file;
|
|
|
|
DEBUG ("query_info_callback");
|
|
|
|
file = G_FILE (source_object);
|
|
async_data = (AsyncFuncData *) user_data;
|
|
file_info = g_file_query_info_finish (file, result, &error);
|
|
|
|
if (async_data->callback)
|
|
{
|
|
gdk_threads_enter ();
|
|
((GtkFileSystemGetInfoCallback) async_data->callback) (async_data->cancellable,
|
|
file_info, error, async_data->data);
|
|
gdk_threads_leave ();
|
|
}
|
|
|
|
if (file_info)
|
|
g_object_unref (file_info);
|
|
|
|
if (error)
|
|
g_error_free (error);
|
|
|
|
free_async_data (async_data);
|
|
}
|
|
|
|
GCancellable *
|
|
_gtk_file_system_get_info (GtkFileSystem *file_system,
|
|
GFile *file,
|
|
const gchar *attributes,
|
|
GtkFileSystemGetInfoCallback callback,
|
|
gpointer data)
|
|
{
|
|
GCancellable *cancellable;
|
|
AsyncFuncData *async_data;
|
|
|
|
g_return_val_if_fail (GTK_IS_FILE_SYSTEM (file_system), NULL);
|
|
g_return_val_if_fail (G_IS_FILE (file), NULL);
|
|
|
|
cancellable = g_cancellable_new ();
|
|
|
|
async_data = g_new0 (AsyncFuncData, 1);
|
|
async_data->file_system = g_object_ref (file_system);
|
|
async_data->file = g_object_ref (file);
|
|
async_data->cancellable = g_object_ref (cancellable);
|
|
|
|
async_data->callback = callback;
|
|
async_data->data = data;
|
|
|
|
g_file_query_info_async (file,
|
|
attributes,
|
|
G_FILE_QUERY_INFO_NONE,
|
|
G_PRIORITY_DEFAULT,
|
|
cancellable,
|
|
query_info_callback,
|
|
async_data);
|
|
|
|
return cancellable;
|
|
}
|
|
|
|
static void
|
|
drive_poll_for_media_cb (GObject *source_object,
|
|
GAsyncResult *result,
|
|
gpointer user_data)
|
|
{
|
|
AsyncFuncData *async_data;
|
|
GError *error = NULL;
|
|
|
|
g_drive_poll_for_media_finish (G_DRIVE (source_object), result, &error);
|
|
async_data = (AsyncFuncData *) user_data;
|
|
|
|
gdk_threads_enter ();
|
|
((GtkFileSystemVolumeMountCallback) async_data->callback) (async_data->cancellable,
|
|
(GtkFileSystemVolume *) source_object,
|
|
error, async_data->data);
|
|
gdk_threads_leave ();
|
|
|
|
if (error)
|
|
g_error_free (error);
|
|
}
|
|
|
|
static void
|
|
volume_mount_cb (GObject *source_object,
|
|
GAsyncResult *result,
|
|
gpointer user_data)
|
|
{
|
|
AsyncFuncData *async_data;
|
|
GError *error = NULL;
|
|
|
|
g_volume_mount_finish (G_VOLUME (source_object), result, &error);
|
|
async_data = (AsyncFuncData *) user_data;
|
|
|
|
gdk_threads_enter ();
|
|
((GtkFileSystemVolumeMountCallback) async_data->callback) (async_data->cancellable,
|
|
(GtkFileSystemVolume *) source_object,
|
|
error, async_data->data);
|
|
gdk_threads_leave ();
|
|
|
|
if (error)
|
|
g_error_free (error);
|
|
}
|
|
|
|
GCancellable *
|
|
_gtk_file_system_mount_volume (GtkFileSystem *file_system,
|
|
GtkFileSystemVolume *volume,
|
|
GMountOperation *mount_operation,
|
|
GtkFileSystemVolumeMountCallback callback,
|
|
gpointer data)
|
|
{
|
|
GCancellable *cancellable;
|
|
AsyncFuncData *async_data;
|
|
gboolean handled = FALSE;
|
|
|
|
DEBUG ("volume_mount");
|
|
|
|
cancellable = g_cancellable_new ();
|
|
|
|
async_data = g_new0 (AsyncFuncData, 1);
|
|
async_data->file_system = g_object_ref (file_system);
|
|
async_data->cancellable = g_object_ref (cancellable);
|
|
|
|
async_data->callback = callback;
|
|
async_data->data = data;
|
|
|
|
if (G_IS_DRIVE (volume))
|
|
{
|
|
/* this path happens for drives that are not polled by the OS and where the last media
|
|
* check indicated that no media was available. So the thing to do here is to
|
|
* invoke poll_for_media() on the drive
|
|
*/
|
|
g_drive_poll_for_media (G_DRIVE (volume), cancellable, drive_poll_for_media_cb, async_data);
|
|
handled = TRUE;
|
|
}
|
|
else if (G_IS_VOLUME (volume))
|
|
{
|
|
g_volume_mount (G_VOLUME (volume), G_MOUNT_MOUNT_NONE, mount_operation, cancellable, volume_mount_cb, async_data);
|
|
handled = TRUE;
|
|
}
|
|
|
|
if (!handled)
|
|
free_async_data (async_data);
|
|
|
|
return cancellable;
|
|
}
|
|
|
|
static void
|
|
enclosing_volume_mount_cb (GObject *source_object,
|
|
GAsyncResult *result,
|
|
gpointer user_data)
|
|
{
|
|
GtkFileSystemVolume *volume;
|
|
AsyncFuncData *async_data;
|
|
GError *error = NULL;
|
|
|
|
async_data = (AsyncFuncData *) user_data;
|
|
g_file_mount_enclosing_volume_finish (G_FILE (source_object), result, &error);
|
|
volume = _gtk_file_system_get_volume_for_file (async_data->file_system, G_FILE (source_object));
|
|
|
|
/* Silently drop G_IO_ERROR_ALREADY_MOUNTED error for gvfs backends without visible mounts. */
|
|
/* Better than doing query_info with additional I/O every time. */
|
|
if (error && g_error_matches (error, G_IO_ERROR, G_IO_ERROR_ALREADY_MOUNTED))
|
|
g_clear_error (&error);
|
|
|
|
gdk_threads_enter ();
|
|
((GtkFileSystemVolumeMountCallback) async_data->callback) (async_data->cancellable, volume,
|
|
error, async_data->data);
|
|
gdk_threads_leave ();
|
|
|
|
if (error)
|
|
g_error_free (error);
|
|
|
|
_gtk_file_system_volume_unref (volume);
|
|
}
|
|
|
|
GCancellable *
|
|
_gtk_file_system_mount_enclosing_volume (GtkFileSystem *file_system,
|
|
GFile *file,
|
|
GMountOperation *mount_operation,
|
|
GtkFileSystemVolumeMountCallback callback,
|
|
gpointer data)
|
|
{
|
|
GCancellable *cancellable;
|
|
AsyncFuncData *async_data;
|
|
|
|
g_return_val_if_fail (GTK_IS_FILE_SYSTEM (file_system), NULL);
|
|
g_return_val_if_fail (G_IS_FILE (file), NULL);
|
|
|
|
DEBUG ("mount_enclosing_volume");
|
|
|
|
cancellable = g_cancellable_new ();
|
|
|
|
async_data = g_new0 (AsyncFuncData, 1);
|
|
async_data->file_system = g_object_ref (file_system);
|
|
async_data->file = g_object_ref (file);
|
|
async_data->cancellable = g_object_ref (cancellable);
|
|
|
|
async_data->callback = callback;
|
|
async_data->data = data;
|
|
|
|
g_file_mount_enclosing_volume (file,
|
|
G_MOUNT_MOUNT_NONE,
|
|
mount_operation,
|
|
cancellable,
|
|
enclosing_volume_mount_cb,
|
|
async_data);
|
|
return cancellable;
|
|
}
|
|
|
|
gboolean
|
|
_gtk_file_system_insert_bookmark (GtkFileSystem *file_system,
|
|
GFile *file,
|
|
gint position,
|
|
GError **error)
|
|
{
|
|
GtkFileSystemPrivate *priv = file_system->priv;
|
|
GSList *bookmarks;
|
|
GtkFileSystemBookmark *bookmark;
|
|
gboolean result = TRUE;
|
|
GFile *bookmarks_file;
|
|
|
|
bookmarks = priv->bookmarks;
|
|
|
|
while (bookmarks)
|
|
{
|
|
bookmark = bookmarks->data;
|
|
bookmarks = bookmarks->next;
|
|
|
|
if (g_file_equal (bookmark->file, file))
|
|
{
|
|
/* File is already in bookmarks */
|
|
result = FALSE;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!result)
|
|
{
|
|
gchar *uri = g_file_get_uri (file);
|
|
|
|
g_set_error (error,
|
|
GTK_FILE_CHOOSER_ERROR,
|
|
GTK_FILE_CHOOSER_ERROR_ALREADY_EXISTS,
|
|
"%s already exists in the bookmarks list",
|
|
uri);
|
|
|
|
g_free (uri);
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
bookmark = g_slice_new0 (GtkFileSystemBookmark);
|
|
bookmark->file = g_object_ref (file);
|
|
|
|
priv->bookmarks = g_slist_insert (priv->bookmarks, bookmark, position);
|
|
|
|
bookmarks_file = get_bookmarks_file ();
|
|
save_bookmarks (bookmarks_file, priv->bookmarks);
|
|
g_object_unref (bookmarks_file);
|
|
|
|
g_signal_emit (file_system, fs_signals[BOOKMARKS_CHANGED], 0);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
gboolean
|
|
_gtk_file_system_remove_bookmark (GtkFileSystem *file_system,
|
|
GFile *file,
|
|
GError **error)
|
|
{
|
|
GtkFileSystemPrivate *priv = file_system->priv;
|
|
GtkFileSystemBookmark *bookmark;
|
|
GSList *bookmarks;
|
|
gboolean result = FALSE;
|
|
GFile *bookmarks_file;
|
|
|
|
if (!priv->bookmarks)
|
|
return FALSE;
|
|
|
|
bookmarks = priv->bookmarks;
|
|
|
|
while (bookmarks)
|
|
{
|
|
bookmark = bookmarks->data;
|
|
|
|
if (g_file_equal (bookmark->file, file))
|
|
{
|
|
result = TRUE;
|
|
priv->bookmarks = g_slist_remove_link (priv->bookmarks, bookmarks);
|
|
_gtk_file_system_bookmark_free (bookmark);
|
|
g_slist_free_1 (bookmarks);
|
|
break;
|
|
}
|
|
|
|
bookmarks = bookmarks->next;
|
|
}
|
|
|
|
if (!result)
|
|
{
|
|
gchar *uri = g_file_get_uri (file);
|
|
|
|
g_set_error (error,
|
|
GTK_FILE_CHOOSER_ERROR,
|
|
GTK_FILE_CHOOSER_ERROR_NONEXISTENT,
|
|
"%s does not exist in the bookmarks list",
|
|
uri);
|
|
|
|
g_free (uri);
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
bookmarks_file = get_bookmarks_file ();
|
|
save_bookmarks (bookmarks_file, priv->bookmarks);
|
|
g_object_unref (bookmarks_file);
|
|
|
|
g_signal_emit (file_system, fs_signals[BOOKMARKS_CHANGED], 0);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
gchar *
|
|
_gtk_file_system_get_bookmark_label (GtkFileSystem *file_system,
|
|
GFile *file)
|
|
{
|
|
GtkFileSystemPrivate *priv = file_system->priv;
|
|
GSList *bookmarks;
|
|
gchar *label = NULL;
|
|
|
|
DEBUG ("get_bookmark_label");
|
|
|
|
bookmarks = priv->bookmarks;
|
|
|
|
while (bookmarks)
|
|
{
|
|
GtkFileSystemBookmark *bookmark;
|
|
|
|
bookmark = bookmarks->data;
|
|
bookmarks = bookmarks->next;
|
|
|
|
if (g_file_equal (file, bookmark->file))
|
|
{
|
|
label = g_strdup (bookmark->label);
|
|
break;
|
|
}
|
|
}
|
|
|
|
return label;
|
|
}
|
|
|
|
void
|
|
_gtk_file_system_set_bookmark_label (GtkFileSystem *file_system,
|
|
GFile *file,
|
|
const gchar *label)
|
|
{
|
|
GtkFileSystemPrivate *priv = file_system->priv;
|
|
gboolean changed = FALSE;
|
|
GFile *bookmarks_file;
|
|
GSList *bookmarks;
|
|
|
|
DEBUG ("set_bookmark_label");
|
|
|
|
bookmarks = priv->bookmarks;
|
|
|
|
while (bookmarks)
|
|
{
|
|
GtkFileSystemBookmark *bookmark;
|
|
|
|
bookmark = bookmarks->data;
|
|
bookmarks = bookmarks->next;
|
|
|
|
if (g_file_equal (file, bookmark->file))
|
|
{
|
|
g_free (bookmark->label);
|
|
bookmark->label = g_strdup (label);
|
|
changed = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
|
|
bookmarks_file = get_bookmarks_file ();
|
|
save_bookmarks (bookmarks_file, priv->bookmarks);
|
|
g_object_unref (bookmarks_file);
|
|
|
|
if (changed)
|
|
g_signal_emit_by_name (file_system, "bookmarks-changed", 0);
|
|
}
|
|
|
|
GtkFileSystemVolume *
|
|
_gtk_file_system_get_volume_for_file (GtkFileSystem *file_system,
|
|
GFile *file)
|
|
{
|
|
GMount *mount;
|
|
|
|
DEBUG ("get_volume_for_file");
|
|
|
|
mount = g_file_find_enclosing_mount (file, NULL, NULL);
|
|
|
|
if (!mount && g_file_is_native (file))
|
|
return (GtkFileSystemVolume *) root_volume_token;
|
|
|
|
return (GtkFileSystemVolume *) mount;
|
|
}
|
|
|
|
/* GtkFolder methods */
|
|
static void
|
|
gtk_folder_set_property (GObject *object,
|
|
guint prop_id,
|
|
const GValue *value,
|
|
GParamSpec *pspec)
|
|
{
|
|
GtkFolder *folder = GTK_FOLDER (object);
|
|
GtkFolderPrivate *priv = folder->priv;
|
|
|
|
switch (prop_id)
|
|
{
|
|
case PROP_FILE:
|
|
priv->folder_file = g_value_dup_object (value);
|
|
break;
|
|
case PROP_ENUMERATOR:
|
|
priv->enumerator = g_value_dup_object (value);
|
|
break;
|
|
case PROP_ATTRIBUTES:
|
|
priv->attributes = g_value_dup_string (value);
|
|
break;
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
gtk_folder_get_property (GObject *object,
|
|
guint prop_id,
|
|
GValue *value,
|
|
GParamSpec *pspec)
|
|
{
|
|
GtkFolder *folder = GTK_FOLDER (object);
|
|
GtkFolderPrivate *priv = folder->priv;
|
|
|
|
switch (prop_id)
|
|
{
|
|
case PROP_FILE:
|
|
g_value_set_object (value, priv->folder_file);
|
|
break;
|
|
case PROP_ENUMERATOR:
|
|
g_value_set_object (value, priv->enumerator);
|
|
break;
|
|
case PROP_ATTRIBUTES:
|
|
g_value_set_string (value, priv->attributes);
|
|
break;
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
query_created_file_info_callback (GObject *source_object,
|
|
GAsyncResult *result,
|
|
gpointer user_data)
|
|
{
|
|
GFile *file = G_FILE (source_object);
|
|
GError *error = NULL;
|
|
GFileInfo *info;
|
|
GtkFolder *folder;
|
|
GSList *files;
|
|
|
|
info = g_file_query_info_finish (file, result, &error);
|
|
|
|
if (error)
|
|
{
|
|
g_error_free (error);
|
|
return;
|
|
}
|
|
|
|
gdk_threads_enter ();
|
|
|
|
folder = GTK_FOLDER (user_data);
|
|
gtk_folder_add_file (folder, file, info);
|
|
|
|
files = g_slist_prepend (NULL, file);
|
|
g_signal_emit (folder, folder_signals[FILES_ADDED], 0, files);
|
|
g_slist_free (files);
|
|
|
|
g_object_unref (info);
|
|
gdk_threads_leave ();
|
|
}
|
|
|
|
static void
|
|
directory_monitor_changed (GFileMonitor *monitor,
|
|
GFile *file,
|
|
GFile *other_file,
|
|
GFileMonitorEvent event,
|
|
gpointer data)
|
|
{
|
|
GtkFolder *folder = GTK_FOLDER (data);
|
|
GtkFolderPrivate *priv = folder->priv;
|
|
GSList *files;
|
|
|
|
files = g_slist_prepend (NULL, file);
|
|
|
|
gdk_threads_enter ();
|
|
|
|
switch (event)
|
|
{
|
|
case G_FILE_MONITOR_EVENT_CREATED:
|
|
g_file_query_info_async (file,
|
|
priv->attributes,
|
|
G_FILE_QUERY_INFO_NONE,
|
|
G_PRIORITY_DEFAULT,
|
|
priv->cancellable,
|
|
query_created_file_info_callback,
|
|
folder);
|
|
break;
|
|
case G_FILE_MONITOR_EVENT_DELETED:
|
|
if (g_file_equal (file, priv->folder_file))
|
|
g_signal_emit (folder, folder_signals[DELETED], 0);
|
|
else
|
|
g_signal_emit (folder, folder_signals[FILES_REMOVED], 0, files);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
gdk_threads_leave ();
|
|
|
|
g_slist_free (files);
|
|
}
|
|
|
|
static void
|
|
enumerator_files_callback (GObject *source_object,
|
|
GAsyncResult *result,
|
|
gpointer user_data)
|
|
{
|
|
GtkFolder *folder = GTK_FOLDER (user_data);
|
|
GtkFolderPrivate *priv = folder->priv;
|
|
GFileEnumerator *enumerator;
|
|
GError *error = NULL;
|
|
GSList *files = NULL;
|
|
GList *file_infos, *f;
|
|
|
|
enumerator = G_FILE_ENUMERATOR (source_object);
|
|
file_infos = g_file_enumerator_next_files_finish (enumerator, result, &error);
|
|
|
|
if (error)
|
|
{
|
|
if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
|
|
g_warning ("%s", error->message);
|
|
|
|
g_error_free (error);
|
|
return;
|
|
}
|
|
|
|
if (!file_infos)
|
|
{
|
|
g_file_enumerator_close_async (enumerator,
|
|
G_PRIORITY_DEFAULT,
|
|
NULL, NULL, NULL);
|
|
|
|
gtk_folder_set_finished_loading (folder, TRUE);
|
|
return;
|
|
}
|
|
|
|
g_file_enumerator_next_files_async (enumerator, FILES_PER_QUERY,
|
|
G_PRIORITY_DEFAULT,
|
|
priv->cancellable,
|
|
enumerator_files_callback,
|
|
folder);
|
|
|
|
for (f = file_infos; f; f = f->next)
|
|
{
|
|
GFileInfo *info;
|
|
GFile *child_file;
|
|
|
|
info = f->data;
|
|
child_file = g_file_get_child (priv->folder_file, g_file_info_get_name (info));
|
|
gtk_folder_add_file (folder, child_file, info);
|
|
files = g_slist_prepend (files, child_file);
|
|
}
|
|
|
|
gdk_threads_enter ();
|
|
g_signal_emit (folder, folder_signals[FILES_ADDED], 0, files);
|
|
gdk_threads_leave ();
|
|
|
|
g_list_foreach (file_infos, (GFunc) g_object_unref, NULL);
|
|
g_list_free (file_infos);
|
|
|
|
g_slist_foreach (files, (GFunc) g_object_unref, NULL);
|
|
g_slist_free (files);
|
|
}
|
|
|
|
static void
|
|
gtk_folder_constructed (GObject *object)
|
|
{
|
|
GtkFolder *folder = GTK_FOLDER (object);
|
|
GtkFolderPrivate *priv = folder->priv;
|
|
GError *error = NULL;
|
|
|
|
priv->directory_monitor = g_file_monitor_directory (priv->folder_file, G_FILE_MONITOR_NONE, NULL, &error);
|
|
|
|
if (error)
|
|
{
|
|
g_warning ("%s", error->message);
|
|
g_error_free (error);
|
|
}
|
|
else
|
|
g_signal_connect (priv->directory_monitor, "changed",
|
|
G_CALLBACK (directory_monitor_changed), object);
|
|
|
|
g_file_enumerator_next_files_async (priv->enumerator,
|
|
FILES_PER_QUERY,
|
|
G_PRIORITY_DEFAULT,
|
|
priv->cancellable,
|
|
enumerator_files_callback,
|
|
object);
|
|
/* This isn't needed anymore */
|
|
g_object_unref (priv->enumerator);
|
|
priv->enumerator = NULL;
|
|
}
|
|
|
|
static void
|
|
gtk_folder_finalize (GObject *object)
|
|
{
|
|
GtkFolder *folder = GTK_FOLDER (object);
|
|
GtkFolderPrivate *priv = folder->priv;
|
|
|
|
g_hash_table_unref (priv->children);
|
|
|
|
if (priv->folder_file)
|
|
g_object_unref (priv->folder_file);
|
|
|
|
if (priv->directory_monitor)
|
|
g_object_unref (priv->directory_monitor);
|
|
|
|
g_cancellable_cancel (priv->cancellable);
|
|
g_object_unref (priv->cancellable);
|
|
g_free (priv->attributes);
|
|
|
|
G_OBJECT_CLASS (_gtk_folder_parent_class)->finalize (object);
|
|
}
|
|
|
|
static void
|
|
_gtk_folder_class_init (GtkFolderClass *class)
|
|
{
|
|
GObjectClass *object_class = G_OBJECT_CLASS (class);
|
|
|
|
object_class->set_property = gtk_folder_set_property;
|
|
object_class->get_property = gtk_folder_get_property;
|
|
object_class->constructed = gtk_folder_constructed;
|
|
object_class->finalize = gtk_folder_finalize;
|
|
|
|
g_object_class_install_property (object_class,
|
|
PROP_FILE,
|
|
g_param_spec_object ("file",
|
|
"File",
|
|
"GFile for the folder",
|
|
G_TYPE_FILE,
|
|
GTK_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
|
|
g_object_class_install_property (object_class,
|
|
PROP_ENUMERATOR,
|
|
g_param_spec_object ("enumerator",
|
|
"Enumerator",
|
|
"GFileEnumerator to list files",
|
|
G_TYPE_FILE_ENUMERATOR,
|
|
GTK_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
|
|
g_object_class_install_property (object_class,
|
|
PROP_ATTRIBUTES,
|
|
g_param_spec_string ("attributes",
|
|
"Attributes",
|
|
"Attributes to query for",
|
|
NULL,
|
|
GTK_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
|
|
folder_signals[FILES_ADDED] =
|
|
g_signal_new ("files-added",
|
|
G_TYPE_FROM_CLASS (object_class),
|
|
G_SIGNAL_RUN_LAST,
|
|
G_STRUCT_OFFSET (GtkFolderClass, files_added),
|
|
NULL, NULL,
|
|
g_cclosure_marshal_VOID__POINTER,
|
|
G_TYPE_NONE, 1, G_TYPE_POINTER);
|
|
folder_signals[FILES_REMOVED] =
|
|
g_signal_new ("files-removed",
|
|
G_TYPE_FROM_CLASS (object_class),
|
|
G_SIGNAL_RUN_LAST,
|
|
G_STRUCT_OFFSET (GtkFolderClass, files_removed),
|
|
NULL, NULL,
|
|
g_cclosure_marshal_VOID__POINTER,
|
|
G_TYPE_NONE, 1, G_TYPE_POINTER);
|
|
folder_signals[FILES_CHANGED] =
|
|
g_signal_new ("files-changed",
|
|
G_TYPE_FROM_CLASS (object_class),
|
|
G_SIGNAL_RUN_LAST,
|
|
G_STRUCT_OFFSET (GtkFolderClass, files_changed),
|
|
NULL, NULL,
|
|
g_cclosure_marshal_VOID__POINTER,
|
|
G_TYPE_NONE, 1, G_TYPE_POINTER);
|
|
folder_signals[FINISHED_LOADING] =
|
|
g_signal_new ("finished-loading",
|
|
G_TYPE_FROM_CLASS (object_class),
|
|
G_SIGNAL_RUN_LAST,
|
|
G_STRUCT_OFFSET (GtkFolderClass, finished_loading),
|
|
NULL, NULL,
|
|
g_cclosure_marshal_VOID__VOID,
|
|
G_TYPE_NONE, 0);
|
|
folder_signals[DELETED] =
|
|
g_signal_new ("deleted",
|
|
G_TYPE_FROM_CLASS (object_class),
|
|
G_SIGNAL_RUN_LAST,
|
|
G_STRUCT_OFFSET (GtkFolderClass, deleted),
|
|
NULL, NULL,
|
|
g_cclosure_marshal_VOID__VOID,
|
|
G_TYPE_NONE, 0);
|
|
|
|
g_type_class_add_private (object_class, sizeof (GtkFolderPrivate));
|
|
}
|
|
|
|
static void
|
|
_gtk_folder_init (GtkFolder *folder)
|
|
{
|
|
GtkFolderPrivate *priv;
|
|
|
|
folder->priv = G_TYPE_INSTANCE_GET_PRIVATE (folder,
|
|
GTK_TYPE_FOLDER,
|
|
GtkFolderPrivate);
|
|
priv = folder->priv;
|
|
priv->children = g_hash_table_new_full (g_file_hash,
|
|
(GEqualFunc) g_file_equal,
|
|
(GDestroyNotify) g_object_unref,
|
|
(GDestroyNotify) g_object_unref);
|
|
priv->cancellable = g_cancellable_new ();
|
|
}
|
|
|
|
static void
|
|
gtk_folder_set_finished_loading (GtkFolder *folder,
|
|
gboolean finished_loading)
|
|
{
|
|
GtkFolderPrivate *priv = folder->priv;
|
|
|
|
priv->finished_loading = (finished_loading == TRUE);
|
|
|
|
gdk_threads_enter ();
|
|
g_signal_emit (folder, folder_signals[FINISHED_LOADING], 0);
|
|
gdk_threads_leave ();
|
|
}
|
|
|
|
static void
|
|
gtk_folder_add_file (GtkFolder *folder,
|
|
GFile *file,
|
|
GFileInfo *info)
|
|
{
|
|
GtkFolderPrivate *priv = folder->priv;
|
|
|
|
g_hash_table_insert (priv->children,
|
|
g_object_ref (file),
|
|
g_object_ref (info));
|
|
}
|
|
|
|
GSList *
|
|
_gtk_folder_list_children (GtkFolder *folder)
|
|
{
|
|
GtkFolderPrivate *priv = folder->priv;
|
|
GList *files, *elem;
|
|
GSList *children = NULL;
|
|
|
|
files = g_hash_table_get_keys (priv->children);
|
|
children = NULL;
|
|
|
|
for (elem = files; elem; elem = elem->next)
|
|
children = g_slist_prepend (children, g_object_ref (elem->data));
|
|
|
|
g_list_free (files);
|
|
|
|
return children;
|
|
}
|
|
|
|
GFileInfo *
|
|
_gtk_folder_get_info (GtkFolder *folder,
|
|
GFile *file)
|
|
{
|
|
GtkFolderPrivate *priv = folder->priv;
|
|
GFileInfo *info;
|
|
|
|
info = g_hash_table_lookup (priv->children, file);
|
|
|
|
if (!info)
|
|
return NULL;
|
|
|
|
return g_object_ref (info);
|
|
}
|
|
|
|
gboolean
|
|
_gtk_folder_is_finished_loading (GtkFolder *folder)
|
|
{
|
|
return folder->priv->finished_loading;
|
|
}
|
|
|
|
/* GtkFileSystemVolume public methods */
|
|
gchar *
|
|
_gtk_file_system_volume_get_display_name (GtkFileSystemVolume *volume)
|
|
{
|
|
DEBUG ("volume_get_display_name");
|
|
|
|
if (IS_ROOT_VOLUME (volume))
|
|
return g_strdup (_(root_volume_token));
|
|
if (G_IS_DRIVE (volume))
|
|
return g_drive_get_name (G_DRIVE (volume));
|
|
else if (G_IS_MOUNT (volume))
|
|
return g_mount_get_name (G_MOUNT (volume));
|
|
else if (G_IS_VOLUME (volume))
|
|
return g_volume_get_name (G_VOLUME (volume));
|
|
|
|
return NULL;
|
|
}
|
|
|
|
gboolean
|
|
_gtk_file_system_volume_is_mounted (GtkFileSystemVolume *volume)
|
|
{
|
|
gboolean mounted;
|
|
|
|
DEBUG ("volume_is_mounted");
|
|
|
|
if (IS_ROOT_VOLUME (volume))
|
|
return TRUE;
|
|
|
|
mounted = FALSE;
|
|
|
|
if (G_IS_MOUNT (volume))
|
|
mounted = TRUE;
|
|
else if (G_IS_VOLUME (volume))
|
|
{
|
|
GMount *mount;
|
|
|
|
mount = g_volume_get_mount (G_VOLUME (volume));
|
|
|
|
if (mount)
|
|
{
|
|
mounted = TRUE;
|
|
g_object_unref (mount);
|
|
}
|
|
}
|
|
|
|
return mounted;
|
|
}
|
|
|
|
GFile *
|
|
_gtk_file_system_volume_get_root (GtkFileSystemVolume *volume)
|
|
{
|
|
GFile *file = NULL;
|
|
|
|
DEBUG ("volume_get_base");
|
|
|
|
if (IS_ROOT_VOLUME (volume))
|
|
return g_file_new_for_uri ("file:///");
|
|
|
|
if (G_IS_MOUNT (volume))
|
|
file = g_mount_get_root (G_MOUNT (volume));
|
|
else if (G_IS_VOLUME (volume))
|
|
{
|
|
GMount *mount;
|
|
|
|
mount = g_volume_get_mount (G_VOLUME (volume));
|
|
|
|
if (mount)
|
|
{
|
|
file = g_mount_get_root (mount);
|
|
g_object_unref (mount);
|
|
}
|
|
}
|
|
|
|
return file;
|
|
}
|
|
|
|
static GdkPixbuf *
|
|
get_pixbuf_from_gicon (GIcon *icon,
|
|
GtkWidget *widget,
|
|
gint icon_size,
|
|
GError **error)
|
|
{
|
|
GdkScreen *screen;
|
|
GtkIconTheme *icon_theme;
|
|
GtkIconInfo *icon_info;
|
|
GdkPixbuf *pixbuf;
|
|
|
|
screen = gtk_widget_get_screen (GTK_WIDGET (widget));
|
|
icon_theme = gtk_icon_theme_get_for_screen (screen);
|
|
|
|
icon_info = gtk_icon_theme_lookup_by_gicon (icon_theme,
|
|
icon,
|
|
icon_size,
|
|
GTK_ICON_LOOKUP_USE_BUILTIN);
|
|
|
|
if (!icon_info)
|
|
return NULL;
|
|
|
|
pixbuf = gtk_icon_info_load_icon (icon_info, error);
|
|
gtk_icon_info_free (icon_info);
|
|
|
|
return pixbuf;
|
|
}
|
|
|
|
GdkPixbuf *
|
|
_gtk_file_system_volume_render_icon (GtkFileSystemVolume *volume,
|
|
GtkWidget *widget,
|
|
gint icon_size,
|
|
GError **error)
|
|
{
|
|
GIcon *icon = NULL;
|
|
GdkPixbuf *pixbuf;
|
|
|
|
DEBUG ("volume_get_icon_name");
|
|
|
|
if (IS_ROOT_VOLUME (volume))
|
|
icon = g_themed_icon_new ("drive-harddisk");
|
|
else if (G_IS_DRIVE (volume))
|
|
icon = g_drive_get_icon (G_DRIVE (volume));
|
|
else if (G_IS_VOLUME (volume))
|
|
icon = g_volume_get_icon (G_VOLUME (volume));
|
|
else if (G_IS_MOUNT (volume))
|
|
icon = g_mount_get_icon (G_MOUNT (volume));
|
|
|
|
if (!icon)
|
|
return NULL;
|
|
|
|
pixbuf = get_pixbuf_from_gicon (icon, widget, icon_size, error);
|
|
|
|
g_object_unref (icon);
|
|
|
|
return pixbuf;
|
|
}
|
|
|
|
GtkFileSystemVolume *
|
|
_gtk_file_system_volume_ref (GtkFileSystemVolume *volume)
|
|
{
|
|
if (IS_ROOT_VOLUME (volume))
|
|
return volume;
|
|
|
|
if (G_IS_MOUNT (volume) ||
|
|
G_IS_VOLUME (volume) ||
|
|
G_IS_DRIVE (volume))
|
|
g_object_ref (volume);
|
|
|
|
return volume;
|
|
}
|
|
|
|
void
|
|
_gtk_file_system_volume_unref (GtkFileSystemVolume *volume)
|
|
{
|
|
/* Root volume doesn't need to be freed */
|
|
if (IS_ROOT_VOLUME (volume))
|
|
return;
|
|
|
|
if (G_IS_MOUNT (volume) ||
|
|
G_IS_VOLUME (volume) ||
|
|
G_IS_DRIVE (volume))
|
|
g_object_unref (volume);
|
|
}
|
|
|
|
/* GFileInfo helper functions */
|
|
GdkPixbuf *
|
|
_gtk_file_info_render_icon (GFileInfo *info,
|
|
GtkWidget *widget,
|
|
gint icon_size)
|
|
{
|
|
GIcon *icon;
|
|
GdkPixbuf *pixbuf = NULL;
|
|
const gchar *thumbnail_path;
|
|
|
|
thumbnail_path = g_file_info_get_attribute_byte_string (info, G_FILE_ATTRIBUTE_THUMBNAIL_PATH);
|
|
|
|
if (thumbnail_path)
|
|
pixbuf = gdk_pixbuf_new_from_file_at_size (thumbnail_path,
|
|
icon_size, icon_size,
|
|
NULL);
|
|
|
|
if (!pixbuf)
|
|
{
|
|
icon = g_file_info_get_icon (info);
|
|
|
|
if (icon)
|
|
pixbuf = get_pixbuf_from_gicon (icon, widget, icon_size, NULL);
|
|
|
|
if (!pixbuf)
|
|
{
|
|
/* Use general fallback for all files without icon */
|
|
icon = g_themed_icon_new ("text-x-generic");
|
|
pixbuf = get_pixbuf_from_gicon (icon, widget, icon_size, NULL);
|
|
g_object_unref (icon);
|
|
}
|
|
}
|
|
|
|
return pixbuf;
|
|
}
|
|
|
|
gboolean
|
|
_gtk_file_info_consider_as_directory (GFileInfo *info)
|
|
{
|
|
GFileType type = g_file_info_get_file_type (info);
|
|
|
|
return (type == G_FILE_TYPE_DIRECTORY ||
|
|
type == G_FILE_TYPE_MOUNTABLE ||
|
|
type == G_FILE_TYPE_SHORTCUT);
|
|
}
|
|
|