Add GAppInfoMonitor

This is a simple object that emits a "change" signal when the installed
applications may have changed in some way.
This commit is contained in:
Ryan Lortie 2013-09-25 09:19:43 -04:00
parent 52552e4231
commit 02dab68174
4 changed files with 238 additions and 0 deletions

View File

@ -21,7 +21,10 @@
*/
#include "config.h"
#include "gappinfo.h"
#include "gappinfoprivate.h"
#include "glibintl.h"
#include <gioerror.h>
#include <gfile.h>
@ -1025,3 +1028,192 @@ g_app_launch_context_launch_failed (GAppLaunchContext *context,
g_signal_emit (context, signals[LAUNCH_FAILED], 0, startup_notify_id);
}
/* GAppInfoMonitor:
*
* We have one of each of these per main context and hand them out
* according to the thread default main context at the time of the call
* to g_app_info_monitor_get().
*
* g_object_unref() is only ever called from the same context, so we
* effectively have a single-threaded scenario for each GAppInfoMonitor.
*
* We use a hashtable to cache the per-context monitor (but we do not
* hold a ref). During finalize, we remove it. This is possible
* because we don't have to worry about the usual races due to the
* single-threaded nature of each object.
*
* We keep a global list of all contexts that have a monitor for them,
* which we have to access under a lock. When we dispatch the events to
* be handled in each context, we don't pass the monitor, but the
* context itself.
*
* We dispatch from the GLib worker context, so if we passed the
* monitor, we would need to take a ref on it (in case it was destroyed
* in its own thread meanwhile). The monitor holds a ref on a context
* and the dispatch would mean that the context would hold a ref on the
* monitor. If someone stopped iterating the context at just this
* moment both the context and monitor would leak.
*
* Instead, we dispatch the context to itself. We don't hold a ref.
* There is the danger that the context will be destroyed during the
* dispatch, but if that is the case then we just won't receive our
* callback.
*
* When the dispatch occurs we just lookup the monitor in the hashtable,
* by context. We can now add and remove refs, since the context will
* have been acquired.
*/
typedef struct _GAppInfoMonitorClass GAppInfoMonitorClass;
struct _GAppInfoMonitor
{
GObject parent_instance;
GMainContext *context;
};
struct _GAppInfoMonitorClass
{
GObjectClass parent_class;
};
static GHashTable *g_app_info_monitors;
static GMutex g_app_info_monitor_lock;
static guint g_app_info_monitor_changed_signal;
G_DEFINE_TYPE (GAppInfoMonitor, g_app_info_monitor, G_TYPE_OBJECT)
static void
g_app_info_monitor_finalize (GObject *object)
{
GAppInfoMonitor *monitor = G_APP_INFO_MONITOR (object);
g_mutex_lock (&g_app_info_monitor_lock);
g_hash_table_remove (g_app_info_monitors, monitor->context);
g_mutex_unlock (&g_app_info_monitor_lock);
G_OBJECT_CLASS (g_app_info_monitor_parent_class)->finalize (object);
}
static void
g_app_info_monitor_init (GAppInfoMonitor *monitor)
{
}
static void
g_app_info_monitor_class_init (GAppInfoMonitorClass *class)
{
GObjectClass *object_class = G_OBJECT_CLASS (class);
g_app_info_monitor_changed_signal = g_signal_new ("changed", G_TYPE_APP_INFO_MONITOR, G_SIGNAL_RUN_FIRST,
0, NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0);
object_class->finalize = g_app_info_monitor_finalize;
}
/**
* g_app_info_monitor_get:
*
* Gets the #GAppInfoMonitor for the current thread-default main
* context.
*
* The #GAppInfoMonitor will emit a "changed" signal in the
* thread-default main context whenever the list of installed
* applications (as reported by g_app_info_get_all()) may have changed.
*
* You must only call g_object_unref() on the return value from under
* the same main context as you created it.
*
* Returns: (transfer full): a reference to a #GAppInfoMonitor
*
* Since: 2.40
**/
GAppInfoMonitor *
g_app_info_monitor_get (void)
{
GAppInfoMonitor *monitor;
GMainContext *context;
context = g_main_context_get_thread_default ();
if (!context)
context = g_main_context_default ();
g_return_val_if_fail (g_main_context_acquire (context), NULL);
g_mutex_lock (&g_app_info_monitor_lock);
if (!g_app_info_monitors)
g_app_info_monitors = g_hash_table_new (NULL, NULL);
monitor = g_hash_table_lookup (g_app_info_monitors, context);
g_mutex_unlock (&g_app_info_monitor_lock);
if (!monitor)
{
monitor = g_object_new (G_TYPE_APP_INFO_MONITOR, NULL);
monitor->context = g_main_context_ref (context);
g_mutex_lock (&g_app_info_monitor_lock);
g_hash_table_insert (g_app_info_monitors, context, monitor);
g_mutex_unlock (&g_app_info_monitor_lock);
}
else
g_object_ref (monitor);
g_main_context_release (context);
return monitor;
}
static gboolean
g_app_info_monitor_emit (gpointer user_data)
{
GMainContext *context = user_data;
GAppInfoMonitor *monitor;
g_mutex_lock (&g_app_info_monitor_lock);
monitor = g_hash_table_lookup (g_app_info_monitors, context);
g_mutex_unlock (&g_app_info_monitor_lock);
/* It is possible that the monitor was already destroyed by the time
* we get here, so make sure it's not NULL.
*/
if (monitor != NULL)
{
/* We don't have to worry about another thread disposing the
* monitor but we do have to worry about the possibility that one
* of the attached handlers may do so.
*
* Take a ref so that the monitor doesn't disappear in the middle
* of the emission.
*/
g_object_ref (monitor);
g_signal_emit (monitor, g_app_info_monitor_changed_signal, 0);
g_object_unref (monitor);
}
return FALSE;
}
void
g_app_info_monitor_fire (void)
{
GHashTableIter iter;
gpointer context;
g_mutex_lock (&g_app_info_monitor_lock);
g_hash_table_iter_init (&iter, g_app_info_monitors);
while (g_hash_table_iter_next (&iter, &context, NULL))
{
GSource *idle;
idle = g_idle_source_new ();
g_source_set_callback (idle, g_app_info_monitor_emit, context, NULL);
g_source_attach (idle, context);
g_source_unref (idle);
}
g_mutex_unlock (&g_app_info_monitor_lock);
}

View File

@ -296,6 +296,20 @@ GLIB_AVAILABLE_IN_ALL
void g_app_launch_context_launch_failed (GAppLaunchContext *context,
const char * startup_notify_id);
#define G_TYPE_APP_INFO_MONITOR (g_app_info_monitor_get_type ())
#define G_APP_INFO_MONITOR(inst) (G_TYPE_CHECK_INSTANCE_CAST ((inst), \
G_TYPE_APP_INFO_MONITOR, GAppInfoMonitor))
#define G_IS_APP_INFO_MONITOR(inst) (G_TYPE_CHECK_INSTANCE_TYPE ((inst), \
G_TYPE_APP_INFO_MONITOR))
typedef struct _GAppInfoMonitor GAppInfoMonitor;
GLIB_AVAILABLE_IN_2_40
GType g_app_info_monitor_get_type (void);
GLIB_AVAILABLE_IN_2_40
GAppInfoMonitor * g_app_info_monitor_get (void);
G_END_DECLS
#endif /* __G_APP_INFO_H__ */

28
gio/gappinfoprivate.h Normal file
View File

@ -0,0 +1,28 @@
/* GIO - GLib Input, Output and Streaming Library
*
* Copyright © 2013 Canonical Limited
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General
* Public License along with this library; if not, write to the
* Free Software Foundation, Inc., 59 Temple Place, Suite 330,
* Boston, MA 02111-1307, USA.
*
* Author: Ryan Lortie <desrt@desrt.ca>
*/
#ifndef __G_APP_INFO_PRIVATE_H__
#define __G_APP_INFO_PRIVATE_H__
void g_app_info_monitor_fire (void);
#endif /* __G_APP_INFO_PRIVATE_H__ */

View File

@ -48,6 +48,7 @@
#include "glibintl.h"
#include "giomodule-priv.h"
#include "gappinfo.h"
#include "gappinfoprivate.h"
#include "glocaldirectorymonitor.h"
#include "dfi-reader.h"
@ -330,6 +331,9 @@ desktop_file_dir_changed (GFileMonitor *monitor,
desktop_file_dir_reset (dir);
g_mutex_unlock (&desktop_file_dir_lock);
/* Notify anyone else who may be interested */
g_app_info_monitor_fire ();
}
/* Internal utility functions {{{2 */