summaryrefslogtreecommitdiffstats
path: root/src/hotplug-sniffer
diff options
context:
space:
mode:
Diffstat (limited to 'src/hotplug-sniffer')
-rw-r--r--src/hotplug-sniffer/hotplug-mimetypes.h141
-rw-r--r--src/hotplug-sniffer/hotplug-sniffer.c298
-rw-r--r--src/hotplug-sniffer/meson.build22
-rw-r--r--src/hotplug-sniffer/org.gnome.Shell.HotplugSniffer.service.in3
-rw-r--r--src/hotplug-sniffer/shell-mime-sniffer.c590
-rw-r--r--src/hotplug-sniffer/shell-mime-sniffer.h46
6 files changed, 1100 insertions, 0 deletions
diff --git a/src/hotplug-sniffer/hotplug-mimetypes.h b/src/hotplug-sniffer/hotplug-mimetypes.h
new file mode 100644
index 0000000..b034020
--- /dev/null
+++ b/src/hotplug-sniffer/hotplug-mimetypes.h
@@ -0,0 +1,141 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+
+#ifndef __HOTPLUG_MIMETYPES_H__
+#define __HOTPLUG_MIMETYPES_H__
+
+#include <glib.h>
+
+G_GNUC_UNUSED static const gchar *docs_mimetypes[] = {
+ "application/vnd.oasis.opendocument.text",
+ "application/vnd.oasis.opendocument.presentation",
+ "application/vnd.oasis.opendocument.spreadsheet",
+ "application/msword",
+ "application/vnd.ms-excel",
+ "application/vnd.ms-powerpoint",
+ "application/rtf",
+ "application/pdf",
+ "application/x-bzpdf",
+ "application/x-gzpdf",
+ "application/x-xzpdf",
+ "application/postscript",
+ "application/x-bzpostscript",
+ "application/x-gzpostscript",
+ "image/x-eps",
+ "image/x-bzeps",
+ "image/x-gzeps",
+ "application/x-dvi",
+ "application/x-bzdvi",
+ "application/x-gzdvi",
+ "image/vnd.djvu",
+ "application/x-cbr",
+ "application/x-cbz",
+ "application/x-cb7",
+ "application/x-cbt",
+ NULL
+};
+
+G_GNUC_UNUSED static const gchar *video_mimetypes[] = {
+ "application/mxf",
+ "application/ogg",
+ "application/ram",
+ "application/sdp",
+ "application/vnd.ms-wpl",
+ "application/vnd.rn-realmedia",
+ "application/x-extension-m4a",
+ "application/x-extension-mp4",
+ "application/x-flash-video",
+ "application/x-matroska",
+ "application/x-netshow-channel",
+ "application/x-ogg",
+ "application/x-quicktimeplayer",
+ "application/x-shorten",
+ "image/vnd.rn-realpix",
+ "image/x-pict",
+ "misc/ultravox",
+ "text/x-google-video-pointer",
+ "video/3gpp",
+ "video/dv",
+ "video/fli",
+ "video/flv",
+ "video/mp2t",
+ "video/mp4",
+ "video/mp4v-es",
+ "video/mpeg",
+ "video/msvideo",
+ "video/ogg",
+ "video/quicktime",
+ "video/vivo",
+ "video/vnd.divx",
+ "video/vnd.rn-realvideo",
+ "video/vnd.vivo",
+ "video/webm",
+ "video/x-anim",
+ "video/x-avi",
+ "video/x-flc",
+ "video/x-fli",
+ "video/x-flic",
+ "video/x-flv",
+ "video/x-m4v",
+ "video/x-matroska",
+ "video/x-mpeg",
+ "video/x-ms-asf",
+ "video/x-ms-asx",
+ "video/x-msvideo",
+ "video/x-ms-wm",
+ "video/x-ms-wmv",
+ "video/x-ms-wmx",
+ "video/x-ms-wvx",
+ "video/x-nsv",
+ "video/x-ogm+ogg",
+ "video/x-theora+ogg",
+ "video/x-totem-stream",
+ NULL
+};
+
+G_GNUC_UNUSED static const gchar *audio_mimetypes[] = {
+ "audio/3gpp",
+ "audio/ac3",
+ "audio/AMR",
+ "audio/AMR-WB",
+ "audio/basic",
+ "audio/flac",
+ "audio/midi",
+ "audio/mp2",
+ "audio/mp4",
+ "audio/mpeg",
+ "audio/ogg",
+ "audio/prs.sid",
+ "audio/vnd.rn-realaudio",
+ "audio/x-aiff",
+ "audio/x-ape",
+ "audio/x-flac",
+ "audio/x-gsm",
+ "audio/x-it",
+ "audio/x-m4a",
+ "audio/x-matroska",
+ "audio/x-mod",
+ "audio/x-mp3",
+ "audio/x-mpeg",
+ "audio/x-ms-asf",
+ "audio/x-ms-asx",
+ "audio/x-ms-wax",
+ "audio/x-ms-wma",
+ "audio/x-musepack",
+ "audio/x-pn-aiff",
+ "audio/x-pn-au",
+ "audio/x-pn-wav",
+ "audio/x-pn-windows-acm",
+ "audio/x-realaudio",
+ "audio/x-real-audio",
+ "audio/x-sbc",
+ "audio/x-speex",
+ "audio/x-tta",
+ "audio/x-wav",
+ "audio/x-wavpack",
+ "audio/x-vorbis",
+ "audio/x-vorbis+ogg",
+ "audio/x-xm",
+ NULL
+};
+
+#endif /* __HOTPLUG_MIMETYPES_H__ */
diff --git a/src/hotplug-sniffer/hotplug-sniffer.c b/src/hotplug-sniffer/hotplug-sniffer.c
new file mode 100644
index 0000000..fd087fd
--- /dev/null
+++ b/src/hotplug-sniffer/hotplug-sniffer.c
@@ -0,0 +1,298 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/*
+ * Copyright (C) 2011 Red Hat, Inc.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU 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, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: David Zeuthen <davidz@redhat.com>
+ * Cosimo Cecchi <cosimoc@redhat.com>
+ *
+ */
+
+#include "shell-mime-sniffer.h"
+#include "hotplug-mimetypes.h"
+
+/* Set the environment variable HOTPLUG_SNIFFER_DEBUG to show debug */
+static void print_debug (const gchar *str, ...);
+
+#define BUS_NAME "org.gnome.Shell.HotplugSniffer"
+#define AUTOQUIT_TIMEOUT 5
+
+static const gchar introspection_xml[] =
+ "<node>"
+ " <interface name='org.gnome.Shell.HotplugSniffer'>"
+ " <method name='SniffURI'>"
+ " <arg type='s' name='uri' direction='in'/>"
+ " <arg type='as' name='content_types' direction='out'/>"
+ " </method>"
+ " </interface>"
+ "</node>";
+
+static GDBusNodeInfo *introspection_data = NULL;
+static GMainLoop *loop = NULL;
+static guint autoquit_id = 0;
+
+static gboolean
+autoquit_timeout_cb (gpointer _unused)
+{
+ print_debug ("Timeout reached, quitting...");
+
+ autoquit_id = 0;
+ g_main_loop_quit (loop);
+
+ return FALSE;
+}
+
+static void
+ensure_autoquit_off (void)
+{
+ if (g_getenv ("HOTPLUG_SNIFFER_PERSIST") != NULL)
+ return;
+
+ g_clear_handle_id (&autoquit_id, g_source_remove);
+}
+
+static void
+ensure_autoquit_on (void)
+{
+ if (g_getenv ("HOTPLUG_SNIFFER_PERSIST") != NULL)
+ return;
+
+ autoquit_id =
+ g_timeout_add_seconds (AUTOQUIT_TIMEOUT,
+ autoquit_timeout_cb, NULL);
+ g_source_set_name_by_id (autoquit_id, "[gnome-shell] autoquit_timeout_cb");
+}
+
+typedef struct {
+ GVariant *parameters;
+ GDBusMethodInvocation *invocation;
+} InvocationData;
+
+static InvocationData *
+invocation_data_new (GVariant *params,
+ GDBusMethodInvocation *invocation)
+{
+ InvocationData *ret;
+
+ ret = g_slice_new0 (InvocationData);
+ ret->parameters = g_variant_ref (params);
+ ret->invocation = g_object_ref (invocation);
+
+ return ret;
+}
+
+static void
+invocation_data_free (InvocationData *data)
+{
+ g_variant_unref (data->parameters);
+ g_clear_object (&data->invocation);
+
+ g_slice_free (InvocationData, data);
+}
+
+static void
+sniff_async_ready_cb (GObject *source,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ InvocationData *data = user_data;
+ gchar **types;
+ GError *error = NULL;
+
+ types = shell_mime_sniffer_sniff_finish (SHELL_MIME_SNIFFER (source),
+ res, &error);
+
+ if (error != NULL)
+ {
+ g_dbus_method_invocation_return_gerror (data->invocation, error);
+ g_error_free (error);
+ goto out;
+ }
+
+ g_dbus_method_invocation_return_value (data->invocation,
+ g_variant_new ("(^as)", types));
+ g_strfreev (types);
+
+ out:
+ invocation_data_free (data);
+ ensure_autoquit_on ();
+}
+
+static void
+handle_sniff_uri (InvocationData *data)
+{
+ ShellMimeSniffer *sniffer;
+ const gchar *uri;
+ GFile *file;
+
+ ensure_autoquit_off ();
+
+ g_variant_get (data->parameters,
+ "(&s)", &uri,
+ NULL);
+ file = g_file_new_for_uri (uri);
+
+ print_debug ("Initiating sniff for uri %s", uri);
+
+ sniffer = shell_mime_sniffer_new (file);
+ shell_mime_sniffer_sniff_async (sniffer,
+ sniff_async_ready_cb,
+ data);
+
+ g_object_unref (sniffer);
+ g_object_unref (file);
+}
+
+static void
+handle_method_call (GDBusConnection *connection,
+ const gchar *sender,
+ const gchar *object_path,
+ const gchar *interface_name,
+ const gchar *method_name,
+ GVariant *parameters,
+ GDBusMethodInvocation *invocation,
+ gpointer user_data)
+{
+ InvocationData *data;
+
+ data = invocation_data_new (parameters, invocation);
+
+ if (g_strcmp0 (method_name, "SniffURI") == 0)
+ handle_sniff_uri (data);
+ else
+ g_assert_not_reached ();
+}
+
+static const GDBusInterfaceVTable interface_vtable =
+{
+ handle_method_call,
+ NULL, /* get_property */
+ NULL, /* set_property */
+};
+
+static void
+on_bus_acquired (GDBusConnection *connection,
+ const gchar *name,
+ gpointer user_data)
+{
+ GError *error = NULL;
+
+ print_debug ("Connected to the session bus: %s", name);
+
+ g_dbus_connection_register_object (connection,
+ "/org/gnome/Shell/HotplugSniffer",
+ introspection_data->interfaces[0],
+ &interface_vtable,
+ NULL,
+ NULL,
+ &error);
+
+ if (error != NULL)
+ {
+ g_printerr ("Error exporting object on the session bus: %s",
+ error->message);
+ g_error_free (error);
+
+ _exit(1);
+ }
+
+ print_debug ("Object exported on the session bus");
+}
+
+static void
+on_name_lost (GDBusConnection *connection,
+ const gchar *name,
+ gpointer user_data)
+{
+ print_debug ("Lost bus name: %s, exiting", name);
+
+ g_main_loop_quit (loop);
+}
+
+static void
+on_name_acquired (GDBusConnection *connection,
+ const gchar *name,
+ gpointer user_data)
+{
+ print_debug ("Acquired bus name: %s", name);
+}
+
+int
+main (int argc,
+ char **argv)
+{
+ guint name_owner_id;
+
+ introspection_data = g_dbus_node_info_new_for_xml (introspection_xml, NULL);
+ g_assert (introspection_data != NULL);
+
+ ensure_autoquit_on ();
+ loop = g_main_loop_new (NULL, FALSE);
+
+ name_owner_id = g_bus_own_name (G_BUS_TYPE_SESSION,
+ BUS_NAME, 0,
+ on_bus_acquired,
+ on_name_acquired,
+ on_name_lost,
+ NULL,
+ NULL);
+
+ g_main_loop_run (loop);
+
+ if (name_owner_id != 0)
+ g_bus_unown_name (name_owner_id);
+
+ if (loop != NULL)
+ g_main_loop_unref (loop);
+
+ return 0;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void __attribute__((format(printf, 1, 0)))
+print_debug (const gchar *format, ...)
+{
+ g_autofree char *s = NULL;
+ g_autofree char *timestamp = NULL;
+ va_list ap;
+ g_autoptr (GDateTime) now = NULL;
+ static volatile gsize once_init_value = 0;
+ static gboolean show_debug = FALSE;
+ static guint pid = 0;
+
+ if (g_once_init_enter (&once_init_value))
+ {
+ show_debug = (g_getenv ("HOTPLUG_SNIFFER_DEBUG") != NULL);
+ pid = getpid ();
+ g_once_init_leave (&once_init_value, 1);
+ }
+
+ if (!show_debug)
+ goto out;
+
+ now = g_date_time_new_now_local ();
+ timestamp = g_date_time_format (now, "%H:%M:%S");
+
+ va_start (ap, format);
+ s = g_strdup_vprintf (format, ap);
+ va_end (ap);
+
+ g_print ("gnome-shell-hotplug-sniffer[%d]: %s.%03d: %s\n",
+ pid, timestamp, g_date_time_get_microsecond (now), s);
+ out:
+ ;
+}
+
diff --git a/src/hotplug-sniffer/meson.build b/src/hotplug-sniffer/meson.build
new file mode 100644
index 0000000..4a777e5
--- /dev/null
+++ b/src/hotplug-sniffer/meson.build
@@ -0,0 +1,22 @@
+hotplug_sources = [
+ 'hotplug-mimetypes.h',
+ 'shell-mime-sniffer.h',
+ 'shell-mime-sniffer.c',
+ 'hotplug-sniffer.c'
+]
+
+executable('gnome-shell-hotplug-sniffer', hotplug_sources,
+ dependencies: [gio_dep, gdk_pixbuf_dep],
+ include_directories: include_directories('../..'),
+ install_dir: libexecdir,
+ install: true
+)
+
+service_file = 'org.gnome.Shell.HotplugSniffer.service'
+
+configure_file(
+ input: service_file + '.in',
+ output: service_file,
+ configuration: service_data,
+ install_dir: servicedir
+)
diff --git a/src/hotplug-sniffer/org.gnome.Shell.HotplugSniffer.service.in b/src/hotplug-sniffer/org.gnome.Shell.HotplugSniffer.service.in
new file mode 100644
index 0000000..b14cea9
--- /dev/null
+++ b/src/hotplug-sniffer/org.gnome.Shell.HotplugSniffer.service.in
@@ -0,0 +1,3 @@
+[D-BUS Service]
+Name=org.gnome.Shell.HotplugSniffer
+Exec=@libexecdir@/gnome-shell-hotplug-sniffer
diff --git a/src/hotplug-sniffer/shell-mime-sniffer.c b/src/hotplug-sniffer/shell-mime-sniffer.c
new file mode 100644
index 0000000..7a1c1fe
--- /dev/null
+++ b/src/hotplug-sniffer/shell-mime-sniffer.c
@@ -0,0 +1,590 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/*
+ * Copyright (C) 1999, 2000, 2001 Eazel, Inc.
+ * Copyright (C) 2011 Red Hat, Inc.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU 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, see <http://www.gnu.org/licenses/>.
+ *
+ * Author: Cosimo Cecchi <cosimoc@redhat.com>
+ *
+ * The code for crawling the directory hierarchy is based on
+ * nautilus/libnautilus-private/nautilus-directory-async.c, with
+ * the following copyright and author:
+ *
+ * Copyright (C) 1999, 2000, 2001 Eazel, Inc.
+ * Author: Darin Adler <darin@bentspoon.com>
+ *
+ */
+
+#include "shell-mime-sniffer.h"
+#include "hotplug-mimetypes.h"
+
+#include <glib/gi18n.h>
+
+#include <gdk-pixbuf/gdk-pixbuf.h>
+
+#define LOADER_ATTRS \
+ G_FILE_ATTRIBUTE_STANDARD_TYPE "," \
+ G_FILE_ATTRIBUTE_STANDARD_NAME "," \
+ G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE ","
+
+#define WATCHDOG_TIMEOUT 1500
+#define DIRECTORY_LOAD_ITEMS_PER_CALLBACK 100
+#define HIGH_SCORE_RATIO 0.10
+
+enum {
+ PROP_FILE = 1,
+ NUM_PROPERTIES
+};
+
+static GHashTable *image_type_table = NULL;
+static GHashTable *audio_type_table = NULL;
+static GHashTable *video_type_table = NULL;
+static GHashTable *docs_type_table = NULL;
+
+static GParamSpec *properties[NUM_PROPERTIES] = { NULL, };
+
+typedef struct {
+ ShellMimeSniffer *self;
+
+ GFile *file;
+ GFileEnumerator *enumerator;
+ GList *deep_count_subdirectories;
+
+ gint audio_count;
+ gint image_count;
+ gint document_count;
+ gint video_count;
+
+ gint total_items;
+} DeepCountState;
+
+typedef struct _ShellMimeSnifferPrivate ShellMimeSnifferPrivate;
+
+struct _ShellMimeSniffer
+{
+ GObject parent_instance;
+
+ ShellMimeSnifferPrivate *priv;
+};
+
+struct _ShellMimeSnifferPrivate {
+ GFile *file;
+
+ GCancellable *cancellable;
+ guint watchdog_id;
+
+ GTask *task;
+};
+
+G_DEFINE_TYPE_WITH_PRIVATE (ShellMimeSniffer, shell_mime_sniffer, G_TYPE_OBJECT);
+
+static void deep_count_load (DeepCountState *state,
+ GFile *file);
+
+static void
+init_mimetypes (void)
+{
+ static gsize once_init = 0;
+
+ if (g_once_init_enter (&once_init))
+ {
+ GSList *formats, *l;
+ GdkPixbufFormat *format;
+ gchar **types;
+ gint idx;
+
+ image_type_table = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
+ video_type_table = g_hash_table_new (g_str_hash, g_str_equal);
+ audio_type_table = g_hash_table_new (g_str_hash, g_str_equal);
+ docs_type_table = g_hash_table_new (g_str_hash, g_str_equal);
+
+ formats = gdk_pixbuf_get_formats ();
+
+ for (l = formats; l != NULL; l = l->next)
+ {
+ format = l->data;
+ types = gdk_pixbuf_format_get_mime_types (format);
+
+ for (idx = 0; types[idx] != NULL; idx++)
+ g_hash_table_insert (image_type_table, g_strdup (types[idx]), GINT_TO_POINTER (1));
+
+ g_strfreev (types);
+ }
+
+ g_slist_free (formats);
+
+ for (idx = 0; audio_mimetypes[idx] != NULL; idx++)
+ g_hash_table_insert (audio_type_table, (gpointer) audio_mimetypes[idx], GINT_TO_POINTER (1));
+
+ for (idx = 0; video_mimetypes[idx] != NULL; idx++)
+ g_hash_table_insert (video_type_table, (gpointer) video_mimetypes[idx], GINT_TO_POINTER (1));
+
+ for (idx = 0; docs_mimetypes[idx] != NULL; idx++)
+ g_hash_table_insert (docs_type_table, (gpointer) docs_mimetypes[idx], GINT_TO_POINTER (1));
+
+ g_once_init_leave (&once_init, 1);
+ }
+}
+
+static void
+add_content_type_to_cache (DeepCountState *state,
+ const gchar *content_type)
+{
+ gboolean matched = TRUE;
+
+ if (g_hash_table_lookup (image_type_table, content_type))
+ state->image_count++;
+ else if (g_hash_table_lookup (video_type_table, content_type))
+ state->video_count++;
+ else if (g_hash_table_lookup (docs_type_table, content_type))
+ state->document_count++;
+ else if (g_hash_table_lookup (audio_type_table, content_type))
+ state->audio_count++;
+ else
+ matched = FALSE;
+
+ if (matched)
+ state->total_items++;
+}
+
+typedef struct {
+ const gchar *type;
+ gdouble ratio;
+} SniffedResult;
+
+static gint
+results_cmp_func (gconstpointer a,
+ gconstpointer b)
+{
+ const SniffedResult *sniffed_a = a;
+ const SniffedResult *sniffed_b = b;
+
+ if (sniffed_a->ratio < sniffed_b->ratio)
+ return 1;
+
+ if (sniffed_a->ratio > sniffed_b->ratio)
+ return -1;
+
+ return 0;
+}
+
+static void
+prepare_async_result (DeepCountState *state)
+{
+ ShellMimeSniffer *self = state->self;
+ GArray *results;
+ GPtrArray *sniffed_mime;
+ SniffedResult result;
+ char **mimes;
+
+ sniffed_mime = g_ptr_array_new ();
+ results = g_array_new (TRUE, TRUE, sizeof (SniffedResult));
+
+ if (state->total_items == 0)
+ goto out;
+
+ result.type = "x-content/video";
+ result.ratio = (gdouble) state->video_count / (gdouble) state->total_items;
+ g_array_append_val (results, result);
+
+ result.type = "x-content/audio";
+ result.ratio = (gdouble) state->audio_count / (gdouble) state->total_items;
+ g_array_append_val (results, result);
+
+ result.type = "x-content/pictures";
+ result.ratio = (gdouble) state->image_count / (gdouble) state->total_items;
+ g_array_append_val (results, result);
+
+ result.type = "x-content/documents";
+ result.ratio = (gdouble) state->document_count / (gdouble) state->total_items;
+ g_array_append_val (results, result);
+
+ g_array_sort (results, results_cmp_func);
+
+ result = g_array_index (results, SniffedResult, 0);
+ g_ptr_array_add (sniffed_mime, g_strdup (result.type));
+
+ /* if other types score high in ratio, add them, up to three */
+ result = g_array_index (results, SniffedResult, 1);
+ if (result.ratio < HIGH_SCORE_RATIO)
+ goto out;
+ g_ptr_array_add (sniffed_mime, g_strdup (result.type));
+
+ result = g_array_index (results, SniffedResult, 2);
+ if (result.ratio < HIGH_SCORE_RATIO)
+ goto out;
+ g_ptr_array_add (sniffed_mime, g_strdup (result.type));
+
+ out:
+ g_ptr_array_add (sniffed_mime, NULL);
+ mimes = (gchar **) g_ptr_array_free (sniffed_mime, FALSE);
+
+ g_array_free (results, TRUE);
+ g_task_return_pointer (self->priv->task, mimes, (GDestroyNotify)g_strfreev);
+}
+
+/* adapted from nautilus/libnautilus-private/nautilus-directory-async.c */
+static void
+deep_count_one (DeepCountState *state,
+ GFileInfo *info)
+{
+ GFile *subdir;
+ const char *content_type;
+
+ if (g_file_info_get_file_type (info) == G_FILE_TYPE_DIRECTORY)
+ {
+ /* record the fact that we have to descend into this directory */
+ subdir = g_file_get_child (state->file, g_file_info_get_name (info));
+ state->deep_count_subdirectories =
+ g_list_append (state->deep_count_subdirectories, subdir);
+ }
+ else
+ {
+ content_type = g_file_info_get_content_type (info);
+
+ if (content_type)
+ add_content_type_to_cache (state, content_type);
+ }
+}
+
+static void
+deep_count_finish (DeepCountState *state)
+{
+ prepare_async_result (state);
+
+ if (state->enumerator)
+ {
+ if (!g_file_enumerator_is_closed (state->enumerator))
+ g_file_enumerator_close_async (state->enumerator,
+ 0, NULL, NULL, NULL);
+
+ g_object_unref (state->enumerator);
+ }
+
+ g_cancellable_reset (state->self->priv->cancellable);
+ g_clear_object (&state->file);
+
+ g_list_free_full (state->deep_count_subdirectories, g_object_unref);
+
+ g_free (state);
+}
+
+static void
+deep_count_next_dir (DeepCountState *state)
+{
+ GFile *new_file;
+
+ g_clear_object (&state->file);
+
+ if (state->deep_count_subdirectories != NULL)
+ {
+ /* Work on a new directory. */
+ new_file = state->deep_count_subdirectories->data;
+ state->deep_count_subdirectories =
+ g_list_remove (state->deep_count_subdirectories, new_file);
+
+ deep_count_load (state, new_file);
+ g_object_unref (new_file);
+ }
+ else
+ {
+ deep_count_finish (state);
+ }
+}
+
+static void
+deep_count_more_files_callback (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ DeepCountState *state;
+ GList *files, *l;
+ GFileInfo *info;
+
+ state = user_data;
+
+ if (g_cancellable_is_cancelled (state->self->priv->cancellable))
+ {
+ deep_count_finish (state);
+ return;
+ }
+
+ files = g_file_enumerator_next_files_finish (state->enumerator,
+ res, NULL);
+
+ for (l = files; l != NULL; l = l->next)
+ {
+ info = l->data;
+ deep_count_one (state, info);
+ g_object_unref (info);
+ }
+
+ if (files == NULL)
+ {
+ g_file_enumerator_close_async (state->enumerator, 0, NULL, NULL, NULL);
+ g_object_unref (state->enumerator);
+ state->enumerator = NULL;
+
+ deep_count_next_dir (state);
+ }
+ else
+ {
+ g_file_enumerator_next_files_async (state->enumerator,
+ DIRECTORY_LOAD_ITEMS_PER_CALLBACK,
+ G_PRIORITY_LOW,
+ state->self->priv->cancellable,
+ deep_count_more_files_callback,
+ state);
+ }
+
+ g_list_free (files);
+}
+
+static void
+deep_count_callback (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ DeepCountState *state;
+ GFileEnumerator *enumerator;
+
+ state = user_data;
+
+ if (g_cancellable_is_cancelled (state->self->priv->cancellable))
+ {
+ deep_count_finish (state);
+ return;
+ }
+
+ enumerator = g_file_enumerate_children_finish (G_FILE (source_object),
+ res, NULL);
+
+ if (enumerator == NULL)
+ {
+ deep_count_next_dir (state);
+ }
+ else
+ {
+ state->enumerator = enumerator;
+ g_file_enumerator_next_files_async (state->enumerator,
+ DIRECTORY_LOAD_ITEMS_PER_CALLBACK,
+ G_PRIORITY_LOW,
+ state->self->priv->cancellable,
+ deep_count_more_files_callback,
+ state);
+ }
+}
+
+static void
+deep_count_load (DeepCountState *state,
+ GFile *file)
+{
+ state->file = g_object_ref (file);
+
+ g_file_enumerate_children_async (state->file,
+ LOADER_ATTRS,
+ G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, /* flags */
+ G_PRIORITY_LOW, /* prio */
+ state->self->priv->cancellable,
+ deep_count_callback,
+ state);
+}
+
+static void
+deep_count_start (ShellMimeSniffer *self)
+{
+ DeepCountState *state;
+
+ state = g_new0 (DeepCountState, 1);
+ state->self = self;
+
+ deep_count_load (state, self->priv->file);
+}
+
+static void
+query_info_async_ready_cb (GObject *source,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ GFileInfo *info;
+ GError *error = NULL;
+ ShellMimeSniffer *self = user_data;
+
+ info = g_file_query_info_finish (G_FILE (source),
+ res, &error);
+
+ if (error != NULL)
+ {
+ g_task_return_error (self->priv->task, error);
+
+ return;
+ }
+
+ if (g_file_info_get_file_type (info) != G_FILE_TYPE_DIRECTORY)
+ {
+ g_task_return_new_error (self->priv->task,
+ G_IO_ERROR,
+ G_IO_ERROR_NOT_DIRECTORY,
+ "Not a directory");
+
+ return;
+ }
+
+ deep_count_start (self);
+}
+
+static gboolean
+watchdog_timeout_reached_cb (gpointer user_data)
+{
+ ShellMimeSniffer *self = user_data;
+
+ self->priv->watchdog_id = 0;
+ g_cancellable_cancel (self->priv->cancellable);
+
+ return FALSE;
+}
+
+static void
+start_loading_file (ShellMimeSniffer *self)
+{
+ g_file_query_info_async (self->priv->file,
+ LOADER_ATTRS,
+ G_FILE_QUERY_INFO_NONE,
+ G_PRIORITY_DEFAULT,
+ self->priv->cancellable,
+ query_info_async_ready_cb,
+ self);
+}
+
+static void
+shell_mime_sniffer_set_file (ShellMimeSniffer *self,
+ GFile *file)
+{
+ g_clear_object (&self->priv->file);
+ self->priv->file = g_object_ref (file);
+}
+
+static void
+shell_mime_sniffer_dispose (GObject *object)
+{
+ ShellMimeSniffer *self = SHELL_MIME_SNIFFER (object);
+
+ g_clear_object (&self->priv->file);
+ g_clear_object (&self->priv->cancellable);
+ g_clear_object (&self->priv->task);
+
+ g_clear_handle_id (&self->priv->watchdog_id, g_source_remove);
+
+ G_OBJECT_CLASS (shell_mime_sniffer_parent_class)->dispose (object);
+}
+
+static void
+shell_mime_sniffer_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ ShellMimeSniffer *self = SHELL_MIME_SNIFFER (object);
+
+ switch (prop_id) {
+ case PROP_FILE:
+ g_value_set_object (value, self->priv->file);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+shell_mime_sniffer_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ ShellMimeSniffer *self = SHELL_MIME_SNIFFER (object);
+
+ switch (prop_id) {
+ case PROP_FILE:
+ shell_mime_sniffer_set_file (self, g_value_get_object (value));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+shell_mime_sniffer_class_init (ShellMimeSnifferClass *klass)
+{
+ GObjectClass *oclass;
+
+ oclass = G_OBJECT_CLASS (klass);
+ oclass->dispose = shell_mime_sniffer_dispose;
+ oclass->get_property = shell_mime_sniffer_get_property;
+ oclass->set_property = shell_mime_sniffer_set_property;
+
+ properties[PROP_FILE] =
+ g_param_spec_object ("file",
+ "File",
+ "The loaded file",
+ G_TYPE_FILE,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+
+ g_object_class_install_properties (oclass, NUM_PROPERTIES, properties);
+}
+
+static void
+shell_mime_sniffer_init (ShellMimeSniffer *self)
+{
+ self->priv = shell_mime_sniffer_get_instance_private (self);
+ init_mimetypes ();
+}
+
+ShellMimeSniffer *
+shell_mime_sniffer_new (GFile *file)
+{
+ return g_object_new (SHELL_TYPE_MIME_SNIFFER,
+ "file", file,
+ NULL);
+}
+
+void
+shell_mime_sniffer_sniff_async (ShellMimeSniffer *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ g_assert (self->priv->watchdog_id == 0);
+ g_assert (self->priv->task == NULL);
+
+ self->priv->cancellable = g_cancellable_new ();
+ self->priv->task = g_task_new (self, self->priv->cancellable,
+ callback, user_data);
+
+ self->priv->watchdog_id =
+ g_timeout_add (WATCHDOG_TIMEOUT,
+ watchdog_timeout_reached_cb, self);
+ g_source_set_name_by_id (self->priv->watchdog_id, "[gnome-shell] watchdog_timeout_reached_cb");
+
+ start_loading_file (self);
+}
+
+gchar **
+shell_mime_sniffer_sniff_finish (ShellMimeSniffer *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_pointer (self->priv->task, error);
+}
diff --git a/src/hotplug-sniffer/shell-mime-sniffer.h b/src/hotplug-sniffer/shell-mime-sniffer.h
new file mode 100644
index 0000000..3936eef
--- /dev/null
+++ b/src/hotplug-sniffer/shell-mime-sniffer.h
@@ -0,0 +1,46 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/*
+ * Copyright (C) 2011 Red Hat, Inc.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU 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, see <http://www.gnu.org/licenses/>.
+ *
+ * Author: Cosimo Cecchi <cosimoc@redhat.com>
+ *
+ */
+
+#ifndef __SHELL_MIME_SNIFFER_H__
+#define __SHELL_MIME_SNIFFER_H__
+
+#include <glib-object.h>
+#include <gio/gio.h>
+
+G_BEGIN_DECLS
+
+#define SHELL_TYPE_MIME_SNIFFER (shell_mime_sniffer_get_type ())
+G_DECLARE_FINAL_TYPE (ShellMimeSniffer, shell_mime_sniffer,
+ SHELL, MIME_SNIFFER, GObject)
+
+ShellMimeSniffer *shell_mime_sniffer_new (GFile *file);
+
+void shell_mime_sniffer_sniff_async (ShellMimeSniffer *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+
+gchar ** shell_mime_sniffer_sniff_finish (ShellMimeSniffer *self,
+ GAsyncResult *res,
+ GError **error);
+
+G_END_DECLS
+
+#endif /* __SHELL_MIME_SNIFFER_H__ */