diff --git a/configure.ac b/configure.ac index 9e4d03cc1..eb8098e8c 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 502185dff..cf45b642c 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 000000000..15992183a --- /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 51bc1ac8b..9308d15e7 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 000000000..2dee87dea --- /dev/null +++ b/src/hotplug-sniffer/hotplug-mimetypes.h @@ -0,0 +1,139 @@ +#ifndef __HOTPLUG_MIMETYPES_H__ +#define __HOTPLUG_MIMETYPES_H__ + +#include + +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 000000000..cccf5703f --- /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 + * Cosimo Cecchi + * + */ + +#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[] = + "" + " " + " " + " " + " " + " " + " " + ""; + +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 000000000..b14cea9b5 --- /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 000000000..3bbe6837e --- /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 + * + * 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 + * + */ + +#include "shell-mime-sniffer.h" +#include "hotplug-mimetypes.h" + +#include + +#include + +#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 000000000..b87e38774 --- /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 + * + */ + +#ifndef __SHELL_MIME_SNIFFER_H__ +#define __SHELL_MIME_SNIFFER_H__ + +#include +#include + +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__ */