[gnome-shell] sniffer: add a mimetype sniffer helper executable



commit 0b9c726b4e0a6aafb0e5d4bbf809f3292c5df188
Author: Cosimo Cecchi <cosimoc gnome org>
Date:   Tue Jul 12 10:34:41 2011 -0400

    sniffer: add a mimetype sniffer helper executable
    
    The sniffer is a simple helper process, activated as a DBus service,
    that tries to crawl as many files as possible in the provided target
    directory (i.e. the new mount's root), for a maximum amount of time -
    which is set here to 1.5 seconds (i.e. it will crawl either all the
    files in the directory tree, or as many as it can before the specified
    timeout expires).
    
    Crawled files are ordered by their content type, and a generic estimation
    of the type of files composing the directory is returned to the caller,
    using generic 'x-content/*' mimetypes.
    
    The process will then set an autoquit timeout on itself, which can be
    disabled by setting the env variable HOTPLUG_SNIFFER_PERSIST for
    debugging purposes. The HOTPLUG_SNIFFER_DEBUG env variable can also be
    set to enable debugging output.
    
    https://bugzilla.gnome.org/show_bug.cgi?id=653520

 configure.ac                                       |    2 +
 src/Makefile-calendar-server.am                    |   11 +-
 src/Makefile-hotplug-sniffer.am                    |   24 +
 src/Makefile.am                                    |   11 +
 src/hotplug-sniffer/hotplug-mimetypes.h            |  139 +++++
 src/hotplug-sniffer/hotplug-sniffer.c              |  319 ++++++++++
 .../org.gnome.Shell.HotplugSniffer.service.in      |    3 +
 src/hotplug-sniffer/shell-mime-sniffer.c           |  607 ++++++++++++++++++++
 src/hotplug-sniffer/shell-mime-sniffer.h           |   68 +++
 9 files changed, 1174 insertions(+), 10 deletions(-)
---
diff --git a/configure.ac b/configure.ac
index 9e4d03c..eb8098e 100644
--- a/configure.ac
+++ b/configure.ac
@@ -97,6 +97,8 @@ PKG_CHECK_MODULES(GNOME_SHELL, gio-2.0 >= $GIO_MIN_VERSION
 
 PKG_CHECK_MODULES(SHELL_PERF_HELPER, gtk+-3.0 gio-2.0)
 
+PKG_CHECK_MODULES(SHELL_HOTPLUG_SNIFFER, gio-2.0 gdk-pixbuf-2.0)
+
 GJS_VERSION=`$PKG_CONFIG --modversion gjs-internals-1.0`
 AC_DEFINE_UNQUOTED([GJS_VERSION], ["$GJS_VERSION"], [The version of GJS we're linking to])
 AC_SUBST([GJS_VERSION], ["$GJS_VERSION"])
diff --git a/src/Makefile-calendar-server.am b/src/Makefile-calendar-server.am
index 502185d..cf45b64 100644
--- a/src/Makefile-calendar-server.am
+++ b/src/Makefile-calendar-server.am
@@ -1,13 +1,4 @@
-
-servicedir       = $(datadir)/dbus-1/services
-service_in_files = calendar-server/org.gnome.Shell.CalendarServer.service.in
-service_DATA     = $(service_in_files:.service.in=.service)
-
-$(service_DATA): $(service_in_files) Makefile
-	$(AM_V_GEN)									\
-		[ -d $(@D) ] || $(mkdir_p) $(@D) ;					\
-		sed -e "s|\ libexecdir\@|$(libexecdir)|" $< > $  tmp && mv $  tmp $@
-CLEANFILES += $(service_DATA)
+service_in_files += calendar-server/org.gnome.Shell.CalendarServer.service.in
 
 libexec_PROGRAMS += gnome-shell-calendar-server
 
diff --git a/src/Makefile-hotplug-sniffer.am b/src/Makefile-hotplug-sniffer.am
new file mode 100644
index 0000000..1599218
--- /dev/null
+++ b/src/Makefile-hotplug-sniffer.am
@@ -0,0 +1,24 @@
+service_in_files += hotplug-sniffer/org.gnome.Shell.HotplugSniffer.service.in
+
+libexec_PROGRAMS += gnome-shell-hotplug-sniffer
+
+gnome_shell_hotplug_sniffer_SOURCES =								\
+	hotplug-sniffer/hotplug-mimetypes.h \
+	hotplug-sniffer/shell-mime-sniffer.h \
+	hotplug-sniffer/shell-mime-sniffer.c \
+	hotplug-sniffer/hotplug-sniffer.c \
+	$(NULL)
+
+gnome_shell_hotplug_sniffer_CFLAGS =		\
+	-I$(top_srcdir)/src			\
+	-DG_DISABLE_DEPRECATED			\
+	$(SHELL_HOTPLUG_SNIFFER_CFLAGS)		\
+	$(NULL)
+
+gnome_shell_hotplug_sniffer_LDFLAGS =		\
+	$(SHELL_HOTPLUG_SNIFFER_LIBS)		\
+	$(NULL)
+
+EXTRA_DIST += 							  \
+	hotplug-sniffer/org.gnome.Shell.HotplugSniffer.service.in \
+	$(NULL)
diff --git a/src/Makefile.am b/src/Makefile.am
index 51bc1ac..9308d15 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -6,6 +6,7 @@ bin_SCRIPTS =
 libexec_PROGRAMS =
 noinst_LTLIBRARIES =
 noinst_PROGRAMS =
+service_in_files =
 
 -include $(INTROSPECTION_MAKEFILE)
 INTROSPECTION_GIRS =
@@ -15,6 +16,15 @@ INTROSPECTION_COMPILER_ARGS = --includedir=$(srcdir) --includedir=$(MUTTER_TYPEL
 typelibdir = $(pkglibdir)
 typelib_DATA = $(INTROSPECTION_GIRS:.gir=.typelib)
 
+servicedir = $(datadir)/dbus-1/services
+service_DATA = $(service_in_files:.service.in=.service)
+
+$(service_DATA): $(service_in_files) Makefile
+	$(AM_V_GEN)									\
+		[ -d $(@D) ] || $(mkdir_p) $(@D) ;					\
+		sed -e "s|\ libexecdir\@|$(libexecdir)|" $< > $  tmp && mv $  tmp $@
+CLEANFILES += $(service_DATA)
+
 CLEANFILES += $(gir_DATA) $(typelib_DATA)
 
 bin_SCRIPTS += gnome-shell-extension-tool
@@ -62,6 +72,7 @@ include Makefile-st.am
 include Makefile-tray.am
 include Makefile-gvc.am
 include Makefile-calendar-server.am
+include Makefile-hotplug-sniffer.am
 
 gnome_shell_cflags =				\
 	$(GNOME_SHELL_CFLAGS)			\
diff --git a/src/hotplug-sniffer/hotplug-mimetypes.h b/src/hotplug-sniffer/hotplug-mimetypes.h
new file mode 100644
index 0000000..2dee87d
--- /dev/null
+++ b/src/hotplug-sniffer/hotplug-mimetypes.h
@@ -0,0 +1,139 @@
+#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..cccf570
--- /dev/null
+++ b/src/hotplug-sniffer/hotplug-sniffer.c
@@ -0,0 +1,319 @@
+/*
+ * 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ *
+ * 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;
+
+  if (autoquit_id != 0)
+    {
+      g_source_remove (autoquit_id);
+      autoquit_id = 0;
+    }
+}
+
+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);
+}
+
+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;
+  gint idx;
+  GError *error = NULL;
+  GVariantBuilder *builder;
+  GVariant *result;
+
+  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;
+    }
+
+  builder = g_variant_builder_new (G_VARIANT_TYPE ("as"));
+
+  for (idx = 0; types[idx] != NULL; idx++)
+    g_variant_builder_add (builder, "s", types[idx]);
+
+  result = g_variant_new ("(as)", builder);
+  g_dbus_method_invocation_return_value (data->invocation, result);
+
+  g_variant_unref (result);
+  g_variant_builder_unref (builder);
+  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;
+
+  g_type_init ();
+
+  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
+print_debug (const gchar *format, ...)
+{
+  gchar *s;
+  va_list ap;
+  gchar timebuf[64];
+  GTimeVal now;
+  time_t now_t;
+  struct tm broken_down;
+  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;
+
+  g_get_current_time (&now);
+  now_t = now.tv_sec;
+  localtime_r (&now_t, &broken_down);
+  strftime (timebuf, sizeof timebuf, "%H:%M:%S", &broken_down);
+
+  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, timebuf, (gint) (now.tv_usec / 1000), s);
+  g_free (s);
+ out:
+  ;
+}
+
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..3bbe683
--- /dev/null
+++ b/src/hotplug-sniffer/shell-mime-sniffer.c
@@ -0,0 +1,607 @@
+/*
+ * 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ *
+ * 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
+
+G_DEFINE_TYPE (ShellMimeSniffer, shell_mime_sniffer, G_TYPE_OBJECT);
+
+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;
+
+struct _ShellMimeSnifferPrivate {
+  GFile *file;
+
+  GCancellable *cancellable;
+  guint watchdog_id;
+
+  GSimpleAsyncResult *async_result;
+  gchar **sniffed_mime;
+};
+
+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;
+
+  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);
+  self->priv->sniffed_mime = (gchar **) g_ptr_array_free (sniffed_mime, FALSE);
+
+  g_array_free (results, TRUE);
+  g_simple_async_result_complete_in_idle (self->priv->async_result);
+}
+
+/* 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);
+      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_simple_async_result_take_error (self->priv->async_result,
+                                        error);
+      g_simple_async_result_complete_in_idle (self->priv->async_result);
+
+      return;
+    }
+
+  if (g_file_info_get_file_type (info) != G_FILE_TYPE_DIRECTORY)
+    {
+      g_simple_async_result_set_error (self->priv->async_result,
+                                       G_IO_ERROR,
+                                       G_IO_ERROR_NOT_DIRECTORY,
+                                       "Not a directory");
+      g_simple_async_result_complete_in_idle (self->priv->async_result);
+
+      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->async_result);
+
+  if (self->priv->watchdog_id != 0)
+    {
+      g_source_remove (self->priv->watchdog_id);
+      self->priv->watchdog_id = 0;
+    }
+
+  G_OBJECT_CLASS (shell_mime_sniffer_parent_class)->dispose (object);
+}
+
+static void
+shell_mime_sniffer_finalize (GObject *object)
+{
+  ShellMimeSniffer *self = SHELL_MIME_SNIFFER (object);
+
+  g_strfreev (self->priv->sniffed_mime);
+
+  G_OBJECT_CLASS (shell_mime_sniffer_parent_class)->finalize (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->finalize = shell_mime_sniffer_finalize;
+  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_type_class_add_private (klass, sizeof (ShellMimeSnifferPrivate));
+  g_object_class_install_properties (oclass, NUM_PROPERTIES, properties);
+}
+
+static void
+shell_mime_sniffer_init (ShellMimeSniffer *self)
+{
+  self->priv =
+    G_TYPE_INSTANCE_GET_PRIVATE (self,
+                                 SHELL_TYPE_MIME_SNIFFER,
+                                 ShellMimeSnifferPrivate);
+  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->async_result == NULL);
+
+  self->priv->async_result = 
+    g_simple_async_result_new (G_OBJECT (self),
+                               callback, user_data,
+                               shell_mime_sniffer_sniff_finish);
+  
+  self->priv->cancellable = g_cancellable_new ();
+
+  self->priv->watchdog_id =
+    g_timeout_add (WATCHDOG_TIMEOUT,
+                   watchdog_timeout_reached_cb, self);
+
+  start_loading_file (self);
+}
+
+gchar **
+shell_mime_sniffer_sniff_finish (ShellMimeSniffer *self,
+                                 GAsyncResult *res,
+                                 GError **error)
+{
+  if (g_simple_async_result_propagate_error (self->priv->async_result, error))
+    return NULL;
+
+  return g_strdupv (self->priv->sniffed_mime);
+}
diff --git a/src/hotplug-sniffer/shell-mime-sniffer.h b/src/hotplug-sniffer/shell-mime-sniffer.h
new file mode 100644
index 0000000..b87e387
--- /dev/null
+++ b/src/hotplug-sniffer/shell-mime-sniffer.h
@@ -0,0 +1,68 @@
+/*
+ * 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ *
+ * 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 ())
+#define SHELL_MIME_SNIFFER(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), SHELL_TYPE_MIME_SNIFFER, ShellMimeSniffer))
+#define SHELL_IS_MIME_SNIFFER(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SHELL_TYPE_MIME_SNIFFER))
+#define SHELL_MIME_SNIFFER_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass),  SHELL_TYPE_MIME_SNIFFER, ShellMimeSnifferClass))
+#define SHELL_IS_MIME_SNIFFER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass),  SHELL_TYPE_MIME_SNIFFER))
+#define SHELL_MIME_SNIFFER_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj),  SHELL_TYPE_MIME_SNIFFER, ShellMimeSnifferClass))
+
+typedef struct _ShellMimeSniffer          ShellMimeSniffer;
+typedef struct _ShellMimeSnifferPrivate   ShellMimeSnifferPrivate;
+typedef struct _ShellMimeSnifferClass     ShellMimeSnifferClass;
+
+struct _ShellMimeSniffer
+{
+  GObject parent_instance;
+
+  ShellMimeSnifferPrivate *priv;
+};
+
+struct _ShellMimeSnifferClass
+{
+  GObjectClass parent_class;
+};
+
+GType    shell_mime_sniffer_get_type     (void) G_GNUC_CONST;
+
+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__ */



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]