gvfs/common/gvfsmountinfo.c

865 lines
26 KiB
C

/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
/* GIO - GLib Input, Output and Streaming Library
*
* Copyright (C) 2009 Red Hat, Inc.
*
* 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: David Zeuthen <davidz@redhat.com>
*/
#include <config.h>
#include <string.h>
#include <glib/gi18n-lib.h>
#ifdef HAVE_BLURAY
#include <langinfo.h>
#include <libbluray/bluray.h>
#include <libbluray/meta_data.h>
#endif /* HAVE_BLURAY */
#include "gvfsmountinfo.h"
#define VOLUME_INFO_GROUP "Volume Info"
static GFile *
_g_find_file_insensitive_finish (GFile *parent,
GAsyncResult *result,
GError **error);
static void
_g_find_file_insensitive_async (GFile *parent,
const gchar *name,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data);
static void
on_icon_file_located (GObject *source_object,
GAsyncResult *res,
gpointer user_data)
{
GTask *task = G_TASK (user_data);
GFile *icon_file;
GError *error;
error = NULL;
icon_file = _g_find_file_insensitive_finish (G_FILE (source_object),
res,
&error);
if (icon_file != NULL)
{
g_task_return_pointer (task, g_file_icon_new (icon_file), g_object_unref);
g_object_unref (icon_file);
}
else
{
g_task_return_error (task, error);
}
g_object_unref (task);
}
static void
on_autorun_loaded (GObject *source_object,
GAsyncResult *res,
gpointer user_data)
{
GTask *task = G_TASK (user_data);
GFile *autorun_file;
gchar *content;
gchar *relative_icon_path;
gsize content_length;
GError *error;
relative_icon_path = NULL;
autorun_file = G_FILE (source_object);
error = NULL;
if (g_file_load_contents_finish (autorun_file,
res,
&content,
&content_length,
NULL,
&error))
{
/* Scan through for an "icon=" line. Can't use GKeyFile,
* because .inf files aren't always valid key files
**/
GRegex *icon_regex;
GMatchInfo *match_info;
/* [^,] is because sometimes the icon= line
* has a comma at the end
**/
icon_regex = g_regex_new ("icon\\s*=\\s*+([^,\\r\\n]+)",
G_REGEX_CASELESS | G_REGEX_RAW, 0, NULL);
g_regex_match (icon_regex, content, 0,
&match_info);
/* Even if there are multiple matches, pick only the
* first.
**/
if (g_match_info_matches (match_info))
{
gchar *chr;
gchar *word = g_match_info_fetch (match_info, 1);
/* Replace '\' with '/' */
while ((chr = strchr (word, '\\')) != NULL)
*chr = '/';
/* If the file name's not valid UTF-8,
* don't even try to load it
**/
if (g_utf8_validate (word, -1, NULL))
{
relative_icon_path = word;
}
else
{
/* TODO: mark for translation. Strictly, this isn't very important; this string
* will never be displayed since all current users of g_vfs_mount_info_query_autorun_info()
* passes NULL for the GError**.
*/
error = g_error_new_literal (G_IO_ERROR,
G_IO_ERROR_FAILED,
"Icon name is not valid UTF-8");
g_free (word);
}
}
g_match_info_free (match_info);
g_regex_unref (icon_regex);
g_free (content);
}
/* some autorun.in points to the .exe file for the icon; make sure we avoid using that */
if (relative_icon_path != NULL)
{
if (!g_str_has_suffix (relative_icon_path, ".exe"))
{
GFile *root;
root = g_file_get_parent (autorun_file);
_g_find_file_insensitive_async (root,
relative_icon_path,
g_task_get_cancellable (task),
on_icon_file_located,
task);
g_object_unref (root);
}
else
{
/* TODO: mark for translation. Strictly, this isn't very important; this string
* will never be displayed since all current users of g_vfs_mount_info_query_autorun_info()
* passes NULL for the GError**.
*/
error = g_error_new_literal (G_IO_ERROR,
G_IO_ERROR_FAILED,
"Icon is an .exe file");
}
}
if (error != NULL)
{
g_task_return_error (task, error);
g_object_unref (task);
}
g_free (relative_icon_path);
}
static void
on_autorun_located (GObject *source_object,
GAsyncResult *res,
gpointer user_data)
{
GTask *task = G_TASK (user_data);
GFile *autorun_path;
GError *error;
error = NULL;
autorun_path = _g_find_file_insensitive_finish (G_FILE (source_object),
res,
&error);
if (error != NULL)
{
g_task_return_error (task, error);
g_object_unref (task);
}
else
{
g_file_load_contents_async (autorun_path,
g_task_get_cancellable (task),
on_autorun_loaded,
task);
g_object_unref (autorun_path);
}
}
void
g_vfs_mount_info_query_autorun_info (GFile *directory,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
GTask *task;
task = g_task_new (directory, cancellable, callback, user_data);
g_task_set_source_tag (task, g_vfs_mount_info_query_autorun_info);
_g_find_file_insensitive_async (directory,
"autorun.inf",
cancellable,
on_autorun_located,
task);
}
GIcon *
g_vfs_mount_info_query_autorun_info_finish (GFile *directory,
GAsyncResult *res,
GError **error)
{
g_return_val_if_fail (g_task_is_valid (res, directory), NULL);
g_return_val_if_fail (g_async_result_is_tagged (res, g_vfs_mount_info_query_autorun_info), NULL);
return g_task_propagate_pointer (G_TASK (res), error);
}
/* ---------------------------------------------------------------------------------------------------- */
static void
on_xdg_volume_info_loaded (GObject *source_object,
GAsyncResult *res,
gpointer user_data)
{
GTask *task = G_TASK (user_data);
GFile *xdg_volume_info_file;
gchar *content;
gsize content_length;
GError *error;
GKeyFile *key_file;
gchar *name;
gchar *icon_name;
gchar *icon_file;
GIcon *icon;
content = NULL;
key_file = NULL;
name = NULL;
icon_name = NULL;
icon_file = NULL;
xdg_volume_info_file = G_FILE (source_object);
error = NULL;
if (g_file_load_contents_finish (xdg_volume_info_file,
res,
&content,
&content_length,
NULL,
&error))
{
key_file = g_key_file_new ();
if (!g_key_file_load_from_data (key_file,
content,
content_length,
G_KEY_FILE_NONE,
&error))
goto out;
name = g_key_file_get_locale_string (key_file,
VOLUME_INFO_GROUP,
"Name",
NULL,
NULL);
icon_name = g_key_file_get_string (key_file,
VOLUME_INFO_GROUP,
"Icon",
NULL);
icon_file = g_key_file_get_string (key_file,
VOLUME_INFO_GROUP,
"IconFile",
NULL);
icon = NULL;
if (icon_file != NULL)
{
GFile *dir, *f;
dir = g_file_get_parent (xdg_volume_info_file);
if (dir)
{
f = g_file_resolve_relative_path (dir, icon_file);
if (f)
{
icon = g_file_icon_new (f);
g_object_unref (f);
}
g_object_unref (dir);
}
}
if (icon == NULL && icon_name != NULL)
{
icon = g_themed_icon_new (icon_name);
g_themed_icon_append_name (G_THEMED_ICON (icon), "drive-removable-media");
g_themed_icon_append_name (G_THEMED_ICON (icon), "drive-removable");
g_themed_icon_append_name (G_THEMED_ICON (icon), "drive");
}
g_object_set_data_full (G_OBJECT (task), "name", name, g_free);
g_task_return_pointer (task, icon, g_object_unref);
name = NULL; /* steals name */
}
out:
if (key_file != NULL)
g_key_file_free (key_file);
if (error != NULL)
g_task_return_error (task, error);
g_object_unref (task);
g_free (name);
g_free (icon_name);
g_free (icon_file);
g_free (content);
}
void
g_vfs_mount_info_query_xdg_volume_info (GFile *directory,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
GTask *task;
GFile *file;
task = g_task_new (directory, cancellable, callback, user_data);
g_task_set_source_tag (task, g_vfs_mount_info_query_xdg_volume_info);
file = g_file_resolve_relative_path (directory, ".xdg-volume-info");
g_file_load_contents_async (file,
cancellable,
on_xdg_volume_info_loaded,
task);
g_object_unref (file);
}
GIcon *g_vfs_mount_info_query_xdg_volume_info_finish (GFile *directory,
GAsyncResult *res,
gchar **out_name,
GError **error)
{
g_return_val_if_fail (g_task_is_valid (res, directory), NULL);
g_return_val_if_fail (g_async_result_is_tagged (res, g_vfs_mount_info_query_xdg_volume_info), NULL);
if (g_task_had_error (G_TASK (res)))
goto out;
if (out_name != NULL)
*out_name = g_strdup (g_object_get_data (G_OBJECT (res), "name"));
out:
return g_task_propagate_pointer (G_TASK (res), error);
}
/* ---------------------------------------------------------------------------------------------------- */
#ifdef HAVE_BLURAY
static const char *
get_iso_639_3_for_locale (void)
{
const char *lang = NULL;
#ifdef HAVE_NL_ADDRESS_LANG_TERM
lang = nl_langinfo (_NL_ADDRESS_LANG_TERM);
if (lang == NULL || *lang == '\0')
{
#ifdef HAVE_NL_ADDRESS_COUNTRY_AB3
lang = nl_langinfo (_NL_ADDRESS_COUNTRY_AB3);
if (lang == NULL || *lang == '\0')
#endif
return NULL;
}
#endif
return lang;
}
static const char *
get_icon (const META_DL *meta)
{
const char *icon;
guint i;
guint size;
icon = NULL;
size = 0;
for (i = 0; i < meta->thumb_count; i++)
{
if (meta->thumbnails[i].xres > size)
{
icon = meta->thumbnails[i].path;
size = meta->thumbnails[i].xres;
}
}
return icon;
}
static void
bdmv_metadata_thread (GTask *task,
gpointer object,
gpointer task_data,
GCancellable *cancellable)
{
BLURAY *bd;
const META_DL *meta;
GError *error;
GFile *file;
char *disc_root;
char *icon;
char *name;
const char *lang;
char *path;
file = G_FILE (object);
disc_root = g_file_get_path (file);
if (!disc_root)
{
error = g_error_new_literal (G_IO_ERROR,
G_IO_ERROR_FAILED,
"Device is not a Blu-Ray disc");
goto error;
}
path = g_build_filename (disc_root, "BDMV", NULL);
if (!g_file_test (path, G_FILE_TEST_IS_DIR))
{
error = g_error_new_literal (G_IO_ERROR,
G_IO_ERROR_FAILED,
"Device is not a Blu-Ray disc");
goto error;
}
g_free (path);
bd = bd_open (disc_root, NULL);
g_free (disc_root);
if (bd == NULL)
{
error = g_error_new_literal (G_IO_ERROR,
G_IO_ERROR_FAILED,
"Device is not a Blu-Ray disc");
goto error;
}
lang = get_iso_639_3_for_locale ();
if (lang != NULL)
bd_set_player_setting_str (bd, BLURAY_PLAYER_SETTING_MENU_LANG, lang);
meta = bd_get_meta (bd);
if (meta == NULL)
{
error = g_error_new_literal (G_IO_ERROR,
G_IO_ERROR_FAILED,
"Device is not a Blu-Ray disc, or has no metadata");
bd_close (bd);
goto error;
}
name = icon = NULL;
if (meta != NULL)
{
if (meta->di_name && *meta->di_name)
name = g_strdup (meta->di_name);
icon = g_strdup (get_icon (meta));
}
/* We're missing either an icon, or the name */
if (!name || !icon)
{
bd_set_player_setting_str (bd, BLURAY_PLAYER_SETTING_MENU_LANG, "eng");
meta = bd_get_meta (bd);
if (meta != NULL && name == NULL && meta->di_name && *meta->di_name)
name = g_strdup (meta->di_name);
if (meta != NULL && icon == NULL)
icon = g_strdup (get_icon (meta));
}
if (name != NULL)
g_object_set_data_full (G_OBJECT (task), "name", name, g_free);
/* Set the results */
if (icon != NULL)
{
char *icon_path;
GFile *icon_file;
icon_path = g_strdup_printf ("BDMV/META/DL/%s", icon);
g_free (icon);
icon_file = g_file_resolve_relative_path (file, icon_path);
g_free (icon_path);
g_task_return_pointer (task, g_file_icon_new (icon_file), g_object_unref);
g_object_unref (icon_file);
}
else
{
g_task_return_pointer (task, NULL, NULL);
}
g_object_unref (task);
bd_close (bd);
return;
error:
g_task_return_error (task, error);
g_object_unref (task);
}
#endif /* HAVE_BLURAY */
void
g_vfs_mount_info_query_bdmv_volume_info (GFile *directory,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
GTask *task;
task = g_task_new (directory, cancellable, callback, user_data);
g_task_set_source_tag (task, g_vfs_mount_info_query_bdmv_volume_info);
#ifdef HAVE_BLURAY
g_task_run_in_thread (task, (GTaskThreadFunc) bdmv_metadata_thread);
#else
g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
"gvfs built without Expat support, no BDMV support");
g_object_unref (task);
#endif /* HAVE_BLURAY */
}
GIcon *g_vfs_mount_info_query_bdmv_volume_info_finish (GFile *directory,
GAsyncResult *res,
gchar **out_name,
GError **error)
{
g_return_val_if_fail (g_task_is_valid (res, directory), NULL);
g_return_val_if_fail (g_async_result_is_tagged (res, g_vfs_mount_info_query_bdmv_volume_info), NULL);
if (g_task_had_error (G_TASK (res)))
goto out;
if (out_name != NULL)
*out_name = g_strdup (g_object_get_data (G_OBJECT (res), "name"));
out:
return g_task_propagate_pointer (G_TASK (res), error);
}
/* ---------------------------------------------------------------------------------------------------- */
#define INSENSITIVE_SEARCH_ITEMS_PER_CALLBACK 100
static void
enumerated_children_callback (GObject *source_object, GAsyncResult *res,
gpointer user_data);
static void
more_files_callback (GObject *source_object, GAsyncResult *res,
gpointer user_data);
static void
find_file_insensitive_exists_callback (GObject *source_object,
GAsyncResult *res,
gpointer user_data);
typedef struct _InsensitiveFileSearchData
{
gchar *original_path;
gchar **split_path;
gint index;
GFileEnumerator *enumerator;
GFile *current_file;
} InsensitiveFileSearchData;
static void
clear_find_file_insensitive_state (InsensitiveFileSearchData *data);
static void
_g_find_file_insensitive_async (GFile *parent,
const gchar *name,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
GTask *task;
InsensitiveFileSearchData *data;
GFile *direct_file = g_file_get_child (parent, name);
task = g_task_new (parent, cancellable, callback, user_data);
g_task_set_source_tag (task, _g_find_file_insensitive_async);
data = g_new0 (InsensitiveFileSearchData, 1);
data->original_path = g_strdup (name);
g_task_set_task_data (task, data, (GDestroyNotify)clear_find_file_insensitive_state);
g_file_query_info_async (direct_file, G_FILE_ATTRIBUTE_STANDARD_TYPE,
G_FILE_QUERY_INFO_NONE, G_PRIORITY_DEFAULT,
cancellable,
find_file_insensitive_exists_callback, task);
}
static void
clear_find_file_insensitive_state (InsensitiveFileSearchData *data)
{
g_free (data->original_path);
if (data->split_path)
g_strfreev (data->split_path);
if (data->enumerator)
g_object_unref (data->enumerator);
if (data->current_file)
g_object_unref (data->current_file);
g_free (data);
}
static void
find_file_insensitive_exists_callback (GObject *source_object,
GAsyncResult *res,
gpointer user_data)
{
GTask *task = G_TASK (user_data);
GFileInfo *info;
InsensitiveFileSearchData *data = g_task_get_task_data (task);
/* The file exists and can be found with the given path, no need to search. */
if ((info = g_file_query_info_finish (G_FILE (source_object), res, NULL)))
{
g_task_return_pointer (task, g_object_ref (source_object), g_object_unref);
g_object_unref (task);
g_object_unref (info);
}
else
{
data->split_path = g_strsplit (data->original_path, G_DIR_SEPARATOR_S, -1);
data->index = 0;
data->enumerator = NULL;
data->current_file = g_object_ref (g_task_get_source_object (task));
/* Skip any empty components due to multiple slashes */
while (data->split_path[data->index] != NULL &&
*data->split_path[data->index] == 0)
data->index++;
g_file_enumerate_children_async (data->current_file,
G_FILE_ATTRIBUTE_STANDARD_NAME,
0, G_PRIORITY_DEFAULT,
g_task_get_cancellable (task),
enumerated_children_callback, task);
}
}
static void
enumerated_children_callback (GObject *source_object, GAsyncResult *res,
gpointer user_data)
{
GTask *task = G_TASK (user_data);
GFileEnumerator *enumerator;
InsensitiveFileSearchData *data = g_task_get_task_data (task);
enumerator = g_file_enumerate_children_finish (G_FILE (source_object),
res, NULL);
if (enumerator == NULL)
{
GFile *file;
file = g_file_get_child (g_task_get_source_object (task), data->original_path);
g_task_return_pointer (task, file, g_object_unref);
g_object_unref (task);
return;
}
data->enumerator = enumerator;
g_file_enumerator_next_files_async (enumerator,
INSENSITIVE_SEARCH_ITEMS_PER_CALLBACK,
G_PRIORITY_DEFAULT,
g_task_get_cancellable (task),
more_files_callback,
task);
}
static void
more_files_callback (GObject *source_object, GAsyncResult *res,
gpointer user_data)
{
GTask *task = G_TASK (user_data);
InsensitiveFileSearchData *data = g_task_get_task_data (task);
GList *files, *l;
gchar *filename = NULL, *component, *case_folded_name,
*name_collation_key;
gboolean end_of_files, is_utf8;
files = g_file_enumerator_next_files_finish (data->enumerator,
res, NULL);
end_of_files = files == NULL;
component = data->split_path[data->index];
g_return_if_fail (component != NULL);
is_utf8 = (g_utf8_validate (component, -1, NULL));
if (is_utf8)
{
case_folded_name = g_utf8_casefold (component, -1);
name_collation_key = g_utf8_collate_key (case_folded_name, -1);
g_free (case_folded_name);
}
else
{
name_collation_key = g_ascii_strdown (component, -1);
}
for (l = files; l != NULL; l = l->next)
{
GFileInfo *info;
const gchar *this_name;
gchar *key;
info = l->data;
this_name = g_file_info_get_name (info);
if (is_utf8 && g_utf8_validate (this_name, -1, NULL))
{
gchar *case_folded;
case_folded = g_utf8_casefold (this_name, -1);
key = g_utf8_collate_key (case_folded, -1);
g_free (case_folded);
}
else
{
key = g_ascii_strdown (this_name, -1);
}
if (strcmp (key, name_collation_key) == 0)
filename = g_strdup (this_name);
g_free (key);
if (filename)
break;
}
g_list_free_full (files, g_object_unref);
g_free (name_collation_key);
if (filename)
{
GFile *next_file;
g_file_enumerator_close_async (data->enumerator,
G_PRIORITY_DEFAULT,
g_task_get_cancellable (task),
NULL, NULL);
g_object_unref (data->enumerator);
data->enumerator = NULL;
/* Set the current file and continue searching */
next_file = g_file_get_child (data->current_file, filename);
g_free (filename);
g_object_unref (data->current_file);
data->current_file = next_file;
data->index++;
/* Skip any empty components due to multiple slashes */
while (data->split_path[data->index] != NULL &&
*data->split_path[data->index] == 0)
data->index++;
if (data->split_path[data->index] == NULL)
{
/* Search is complete, file was found */
g_task_return_pointer (task, g_object_ref (data->current_file), g_object_unref);
g_object_unref (task);
return;
}
/* Continue searching down the tree */
g_file_enumerate_children_async (data->current_file,
G_FILE_ATTRIBUTE_STANDARD_NAME,
0, G_PRIORITY_DEFAULT,
g_task_get_cancellable (task),
enumerated_children_callback,
task);
return;
}
if (end_of_files)
{
/* Could not find the given file, abort the search */
GFile *file;
g_object_unref (data->enumerator);
data->enumerator = NULL;
file = g_file_get_child (g_task_get_source_object (task), data->original_path);
g_task_return_pointer (task, file, g_object_unref);
g_object_unref (task);
return;
}
/* Continue enumerating */
g_file_enumerator_next_files_async (data->enumerator,
INSENSITIVE_SEARCH_ITEMS_PER_CALLBACK,
G_PRIORITY_DEFAULT,
g_task_get_cancellable (task),
more_files_callback,
task);
}
static GFile *
_g_find_file_insensitive_finish (GFile *parent,
GAsyncResult *result,
GError **error)
{
g_return_val_if_fail (g_task_is_valid (result, parent), NULL);
g_return_val_if_fail (g_async_result_is_tagged (result, _g_find_file_insensitive_async), NULL);
return g_task_propagate_pointer (G_TASK (result), error);
}