[gnome-shell/hotplug: 6/21] sniffer: add a mimetype sniffer helper executable



commit 99b850e1d64709987ba3e995ed00bc8cdfa0ae6e
Author: Cosimo Cecchi <cosimoc gnome org>
Date:   Tue Jun 21 18:32:00 2011 -0400

    sniffer: add a mimetype sniffer helper executable

 configure.ac                                       |    2 +
 src/Makefile-hotplug-sniffer.am                    |   37 ++
 src/Makefile.am                                    |    1 +
 src/hotplug-sniffer/hotplug-mimetypes.h            |  144 +++++
 src/hotplug-sniffer/hotplug-sniffer.c              |  305 +++++++++++
 .../org.gnome.Shell.HotplugSniffer.service.in      |    3 +
 src/hotplug-sniffer/shell-mime-sniffer.c           |  563 ++++++++++++++++++++
 src/hotplug-sniffer/shell-mime-sniffer.h           |   68 +++
 8 files changed, 1123 insertions(+), 0 deletions(-)
---
diff --git a/configure.ac b/configure.ac
index f722c13..786a66d 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-hotplug-sniffer.am b/src/Makefile-hotplug-sniffer.am
new file mode 100644
index 0000000..0366a35
--- /dev/null
+++ b/src/Makefile-hotplug-sniffer.am
@@ -0,0 +1,37 @@
+
+servicedir       = $(datadir)/dbus-1/services
+service_in_files = hotplug-sniffer/org.gnome.Shell.HotplugSniffer.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)
+
+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			\
+	-DPREFIX=\""$(prefix)"\"		\
+	-DLIBDIR=\""$(libdir)"\"		\
+	-DDATADIR=\""$(datadir)"\"		\
+	-DG_DISABLE_DEPRECATED			\
+	-DG_LOG_DOMAIN=\"ShellHotplugSniffer\"	\
+	$(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 dafb50f..f789bac 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -62,6 +62,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..73ac0c1
--- /dev/null
+++ b/src/hotplug-sniffer/hotplug-mimetypes.h
@@ -0,0 +1,144 @@
+#ifndef __HOTPLUG_MIMETYPES_H__
+#define __HOTPLUG_MIMETYPES_H__
+
+#include <glib.h>
+
+#include "hotplug-mimetypes.h"
+#include "shell-mime-sniffer.h"
+
+#include <gdk-pixbuf/gdk-pixbuf.h>
+
+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
+};
+
+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
+};
+
+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..baaa18b
--- /dev/null
+++ b/src/hotplug-sniffer/hotplug-sniffer.c
@@ -0,0 +1,305 @@
+/*
+ * 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>
+ *
+ */
+
+#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='s' name='content_type' 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 *content_type;
+  GVariant *result;
+
+  content_type = shell_mime_sniffer_sniff_finish (SHELL_MIME_SNIFFER (source),
+                                                  res, NULL);
+
+  if (content_type == NULL)
+    content_type = g_strdup ("");
+
+  print_debug ("Sniffed mimetype %s", content_type);
+
+  result = g_variant_new ("(s)", content_type);
+  g_dbus_method_invocation_return_value (data->invocation, result);
+
+  g_variant_unref (result);
+  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..3f9f1f2
--- /dev/null
+++ b/src/hotplug-sniffer/shell-mime-sniffer.c
@@ -0,0 +1,563 @@
+/*
+ * 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>
+
+#define LOADER_ATTRS                          \
+  G_FILE_ATTRIBUTE_STANDARD_TYPE ","          \
+  G_FILE_ATTRIBUTE_STANDARD_NAME ","          \
+  G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE ","
+
+#define WATCHDOG_TIMEOUT_SECONDS 3
+#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;
+} DeepCountState;
+
+struct _ShellMimeSnifferPrivate {
+  GFile *file;
+
+  GCancellable *cancellable;
+  guint watchdog_id;
+
+  GSimpleAsyncResult *async_result;
+  gchar *content_type;
+};
+
+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)
+{
+  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++;
+}
+
+static void
+prepare_async_result (DeepCountState *state)
+{
+  ShellMimeSniffer *self = state->self;
+  gboolean video_wins, audio_wins, image_wins;
+
+  audio_wins = (state->audio_count > state->image_count) &&
+    (state->audio_count > state->document_count) &&
+    (state->audio_count > state->video_count);
+
+  if (audio_wins)
+    {
+      self->priv->content_type = g_strdup ("x-content/audio-player");
+      goto out;
+    }
+
+  image_wins = (state->image_count > state->audio_count) &&
+    (state->image_count > state->document_count) &&
+    (state->image_count > state->video_count);
+
+  if (image_wins)
+    {
+      self->priv->content_type = g_strdup ("x-content/image-dcf");
+      goto out;
+    }
+
+  video_wins = (state->video_count > state->image_count) &&
+    (state->video_count > state->document_count) &&
+    (state->video_count > state->audio_count);
+
+  /* FIXME: we don't have any content type for video collections! */
+  if (video_wins)
+    goto out;
+
+  /* FIXME: we don't have any content type for document collections! */
+  goto out;
+
+ out:
+  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_DEFAULT,
+                                          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_free (self->priv->content_type);
+
+  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 ();
+  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_seconds (WATCHDOG_TIMEOUT_SECONDS,
+                           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 self->priv->content_type;
+}
diff --git a/src/hotplug-sniffer/shell-mime-sniffer.h b/src/hotplug-sniffer/shell-mime-sniffer.h
new file mode 100644
index 0000000..c208eb6
--- /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]