2007-11-26 17:13:05 +01:00
|
|
|
|
/* GIO - GLib Input, Output and Streaming Library
|
2015-01-12 20:59:35 +01:00
|
|
|
|
*
|
2007-11-26 17:13:05 +01:00
|
|
|
|
* Copyright (C) 2006-2007 Red Hat, Inc.
|
|
|
|
|
*
|
2022-05-18 10:12:45 +02:00
|
|
|
|
* SPDX-License-Identifier: LGPL-2.1-or-later
|
|
|
|
|
*
|
2007-11-26 17:13:05 +01:00
|
|
|
|
* 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
|
2017-05-27 18:21:30 +02:00
|
|
|
|
* version 2.1 of the License, or (at your option) any later version.
|
2007-11-26 17:13:05 +01:00
|
|
|
|
*
|
|
|
|
|
* 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
|
2014-01-23 12:58:29 +01:00
|
|
|
|
* Public License along with this library; if not, see <http://www.gnu.org/licenses/>.
|
2007-11-26 17:13:05 +01:00
|
|
|
|
*
|
|
|
|
|
* Author: Alexander Larsson <alexl@redhat.com>
|
|
|
|
|
*/
|
|
|
|
|
|
2008-06-22 17:10:51 +02:00
|
|
|
|
#include "config.h"
|
2007-11-26 17:13:05 +01:00
|
|
|
|
|
2010-02-18 15:43:45 +01:00
|
|
|
|
#include "gioenumtypes.h"
|
2007-11-26 17:13:05 +01:00
|
|
|
|
#include "glocalfilemonitor.h"
|
2007-12-05 12:10:21 +01:00
|
|
|
|
#include "giomodule-priv.h"
|
2008-07-01 08:32:35 +02:00
|
|
|
|
#include "gioerror.h"
|
2008-01-07 06:18:17 +01:00
|
|
|
|
#include "glibintl.h"
|
2015-01-12 20:59:35 +01:00
|
|
|
|
#include "glocalfile.h"
|
|
|
|
|
#include "glib-private.h"
|
2007-11-26 17:13:05 +01:00
|
|
|
|
|
|
|
|
|
#include <string.h>
|
|
|
|
|
|
2015-01-12 20:59:35 +01:00
|
|
|
|
#define DEFAULT_RATE_LIMIT 800 * G_TIME_SPAN_MILLISECOND
|
|
|
|
|
#define VIRTUAL_CHANGES_DONE_DELAY 2 * G_TIME_SPAN_SECOND
|
2007-11-28 13:39:07 +01:00
|
|
|
|
|
2015-01-12 20:59:35 +01:00
|
|
|
|
/* GFileMonitorSource is a GSource responsible for emitting the changed
|
|
|
|
|
* signals in the owner-context of the GFileMonitor.
|
|
|
|
|
*
|
|
|
|
|
* It contains functionality for cross-thread queuing of events. It
|
|
|
|
|
* also handles merging of CHANGED events and emission of CHANGES_DONE
|
|
|
|
|
* events.
|
|
|
|
|
*
|
|
|
|
|
* We use the "priv" pointer in the external struct to store it.
|
|
|
|
|
*/
|
|
|
|
|
struct _GFileMonitorSource {
|
|
|
|
|
GSource source;
|
|
|
|
|
|
|
|
|
|
GMutex lock;
|
2019-10-11 23:22:46 +02:00
|
|
|
|
GWeakRef instance_ref;
|
2015-01-12 20:59:35 +01:00
|
|
|
|
GFileMonitorFlags flags;
|
|
|
|
|
gchar *dirname;
|
|
|
|
|
gchar *basename;
|
|
|
|
|
gchar *filename;
|
|
|
|
|
GSequence *pending_changes; /* sorted by ready time */
|
|
|
|
|
GHashTable *pending_changes_table;
|
|
|
|
|
GQueue event_queue;
|
|
|
|
|
gint64 rate_limit;
|
2007-11-26 17:13:05 +01:00
|
|
|
|
};
|
|
|
|
|
|
2015-01-12 20:59:35 +01:00
|
|
|
|
/* PendingChange is a struct to keep track of a file that needs to have
|
|
|
|
|
* (at least) a CHANGES_DONE_HINT event sent for it in the near future.
|
|
|
|
|
*
|
|
|
|
|
* If 'dirty' is TRUE then a CHANGED event also needs to be sent.
|
|
|
|
|
*
|
|
|
|
|
* last_emission is the last time a CHANGED event was emitted. It is
|
|
|
|
|
* used to calculate the time to send the next event.
|
|
|
|
|
*/
|
|
|
|
|
typedef struct {
|
|
|
|
|
gchar *child;
|
|
|
|
|
guint64 last_emission : 63;
|
|
|
|
|
guint64 dirty : 1;
|
|
|
|
|
} PendingChange;
|
|
|
|
|
|
|
|
|
|
/* QueuedEvent is a signal that will be sent immediately, as soon as the
|
|
|
|
|
* source gets a chance to dispatch. The existence of any queued event
|
|
|
|
|
* implies that the source is ready now.
|
|
|
|
|
*/
|
|
|
|
|
typedef struct
|
|
|
|
|
{
|
|
|
|
|
GFileMonitorEvent event_type;
|
|
|
|
|
GFile *child;
|
|
|
|
|
GFile *other;
|
|
|
|
|
} QueuedEvent;
|
|
|
|
|
|
|
|
|
|
static gint64
|
|
|
|
|
pending_change_get_ready_time (const PendingChange *change,
|
|
|
|
|
GFileMonitorSource *fms)
|
|
|
|
|
{
|
|
|
|
|
if (change->dirty)
|
|
|
|
|
return change->last_emission + fms->rate_limit;
|
|
|
|
|
else
|
|
|
|
|
return change->last_emission + VIRTUAL_CHANGES_DONE_DELAY;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static int
|
|
|
|
|
pending_change_compare_ready_time (gconstpointer a_p,
|
|
|
|
|
gconstpointer b_p,
|
|
|
|
|
gpointer user_data)
|
|
|
|
|
{
|
|
|
|
|
GFileMonitorSource *fms = user_data;
|
|
|
|
|
const PendingChange *a = a_p;
|
|
|
|
|
const PendingChange *b = b_p;
|
|
|
|
|
gint64 ready_time_a;
|
|
|
|
|
gint64 ready_time_b;
|
|
|
|
|
|
|
|
|
|
ready_time_a = pending_change_get_ready_time (a, fms);
|
|
|
|
|
ready_time_b = pending_change_get_ready_time (b, fms);
|
|
|
|
|
|
|
|
|
|
if (ready_time_a < ready_time_b)
|
|
|
|
|
return -1;
|
|
|
|
|
else
|
|
|
|
|
return ready_time_a > ready_time_b;
|
|
|
|
|
}
|
2007-11-26 17:13:05 +01:00
|
|
|
|
|
|
|
|
|
static void
|
2015-01-12 20:59:35 +01:00
|
|
|
|
pending_change_free (gpointer data)
|
2007-11-26 17:13:05 +01:00
|
|
|
|
{
|
2015-01-12 20:59:35 +01:00
|
|
|
|
PendingChange *change = data;
|
|
|
|
|
|
|
|
|
|
g_free (change->child);
|
|
|
|
|
|
|
|
|
|
g_slice_free (PendingChange, change);
|
2007-11-26 17:13:05 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
2015-01-12 20:59:35 +01:00
|
|
|
|
queued_event_free (QueuedEvent *event)
|
2007-11-26 17:13:05 +01:00
|
|
|
|
{
|
2015-01-12 20:59:35 +01:00
|
|
|
|
g_object_unref (event->child);
|
|
|
|
|
if (event->other)
|
|
|
|
|
g_object_unref (event->other);
|
|
|
|
|
|
|
|
|
|
g_slice_free (QueuedEvent, event);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static gint64
|
|
|
|
|
g_file_monitor_source_get_ready_time (GFileMonitorSource *fms)
|
|
|
|
|
{
|
|
|
|
|
GSequenceIter *iter;
|
|
|
|
|
|
|
|
|
|
if (fms->event_queue.length)
|
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
|
|
iter = g_sequence_get_begin_iter (fms->pending_changes);
|
|
|
|
|
if (g_sequence_iter_is_end (iter))
|
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
|
|
return pending_change_get_ready_time (g_sequence_get (iter), fms);
|
|
|
|
|
}
|
2013-07-25 19:29:16 +02:00
|
|
|
|
|
2015-01-12 20:59:35 +01:00
|
|
|
|
static void
|
|
|
|
|
g_file_monitor_source_update_ready_time (GFileMonitorSource *fms)
|
|
|
|
|
{
|
|
|
|
|
g_source_set_ready_time ((GSource *) fms, g_file_monitor_source_get_ready_time (fms));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static GSequenceIter *
|
|
|
|
|
g_file_monitor_source_find_pending_change (GFileMonitorSource *fms,
|
|
|
|
|
const gchar *child)
|
|
|
|
|
{
|
|
|
|
|
return g_hash_table_lookup (fms->pending_changes_table, child);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
g_file_monitor_source_add_pending_change (GFileMonitorSource *fms,
|
|
|
|
|
const gchar *child,
|
|
|
|
|
gint64 now)
|
|
|
|
|
{
|
|
|
|
|
PendingChange *change;
|
|
|
|
|
GSequenceIter *iter;
|
|
|
|
|
|
|
|
|
|
change = g_slice_new (PendingChange);
|
|
|
|
|
change->child = g_strdup (child);
|
|
|
|
|
change->last_emission = now;
|
|
|
|
|
change->dirty = FALSE;
|
|
|
|
|
|
|
|
|
|
iter = g_sequence_insert_sorted (fms->pending_changes, change, pending_change_compare_ready_time, fms);
|
|
|
|
|
g_hash_table_insert (fms->pending_changes_table, change->child, iter);
|
|
|
|
|
}
|
|
|
|
|
|
2015-01-15 21:39:43 +01:00
|
|
|
|
static gboolean
|
2015-01-12 20:59:35 +01:00
|
|
|
|
g_file_monitor_source_set_pending_change_dirty (GFileMonitorSource *fms,
|
|
|
|
|
GSequenceIter *iter)
|
|
|
|
|
{
|
|
|
|
|
PendingChange *change;
|
|
|
|
|
|
|
|
|
|
change = g_sequence_get (iter);
|
|
|
|
|
|
2015-01-15 21:39:43 +01:00
|
|
|
|
/* if it was already dirty then this change is 'uninteresting' */
|
|
|
|
|
if (change->dirty)
|
|
|
|
|
return FALSE;
|
2015-01-12 20:59:35 +01:00
|
|
|
|
|
2015-01-15 21:39:43 +01:00
|
|
|
|
change->dirty = TRUE;
|
|
|
|
|
|
|
|
|
|
g_sequence_sort_changed (iter, pending_change_compare_ready_time, fms);
|
|
|
|
|
|
|
|
|
|
return TRUE;
|
2015-01-12 20:59:35 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static gboolean
|
|
|
|
|
g_file_monitor_source_get_pending_change_dirty (GFileMonitorSource *fms,
|
|
|
|
|
GSequenceIter *iter)
|
|
|
|
|
{
|
|
|
|
|
PendingChange *change;
|
|
|
|
|
|
|
|
|
|
change = g_sequence_get (iter);
|
|
|
|
|
|
|
|
|
|
return change->dirty;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
g_file_monitor_source_remove_pending_change (GFileMonitorSource *fms,
|
|
|
|
|
GSequenceIter *iter,
|
|
|
|
|
const gchar *child)
|
|
|
|
|
{
|
|
|
|
|
/* must remove the hash entry first -- its key is owned by the data
|
|
|
|
|
* which will be freed when removing the sequence iter
|
|
|
|
|
*/
|
|
|
|
|
g_hash_table_remove (fms->pending_changes_table, child);
|
|
|
|
|
g_sequence_remove (iter);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
g_file_monitor_source_queue_event (GFileMonitorSource *fms,
|
|
|
|
|
GFileMonitorEvent event_type,
|
|
|
|
|
const gchar *child,
|
|
|
|
|
GFile *other)
|
|
|
|
|
{
|
|
|
|
|
QueuedEvent *event;
|
|
|
|
|
|
|
|
|
|
event = g_slice_new (QueuedEvent);
|
|
|
|
|
event->event_type = event_type;
|
2018-02-26 14:35:07 +01:00
|
|
|
|
if (child != NULL && fms->dirname != NULL)
|
2015-01-12 20:59:35 +01:00
|
|
|
|
event->child = g_local_file_new_from_dirname_and_basename (fms->dirname, child);
|
2018-02-26 14:35:07 +01:00
|
|
|
|
else if (child != NULL)
|
|
|
|
|
{
|
|
|
|
|
gchar *dirname = g_path_get_dirname (fms->filename);
|
|
|
|
|
event->child = g_local_file_new_from_dirname_and_basename (dirname, child);
|
|
|
|
|
g_free (dirname);
|
|
|
|
|
}
|
2015-01-12 20:59:35 +01:00
|
|
|
|
else if (fms->dirname)
|
|
|
|
|
event->child = _g_local_file_new (fms->dirname);
|
|
|
|
|
else if (fms->filename)
|
|
|
|
|
event->child = _g_local_file_new (fms->filename);
|
|
|
|
|
event->other = other;
|
|
|
|
|
if (other)
|
|
|
|
|
g_object_ref (other);
|
|
|
|
|
|
|
|
|
|
g_queue_push_tail (&fms->event_queue, event);
|
|
|
|
|
}
|
|
|
|
|
|
2015-01-15 21:39:43 +01:00
|
|
|
|
static gboolean
|
2015-01-12 20:59:35 +01:00
|
|
|
|
g_file_monitor_source_file_changed (GFileMonitorSource *fms,
|
|
|
|
|
const gchar *child,
|
|
|
|
|
gint64 now)
|
|
|
|
|
{
|
|
|
|
|
GSequenceIter *pending;
|
2015-01-15 21:39:43 +01:00
|
|
|
|
gboolean interesting;
|
2015-01-12 20:59:35 +01:00
|
|
|
|
|
|
|
|
|
pending = g_file_monitor_source_find_pending_change (fms, child);
|
|
|
|
|
|
|
|
|
|
/* If there is no pending change, emit one and create a record,
|
|
|
|
|
* else: just mark the existing record as dirty.
|
|
|
|
|
*/
|
|
|
|
|
if (!pending)
|
|
|
|
|
{
|
|
|
|
|
g_file_monitor_source_queue_event (fms, G_FILE_MONITOR_EVENT_CHANGED, child, NULL);
|
|
|
|
|
g_file_monitor_source_add_pending_change (fms, child, now);
|
2015-01-15 21:39:43 +01:00
|
|
|
|
interesting = TRUE;
|
2015-01-12 20:59:35 +01:00
|
|
|
|
}
|
|
|
|
|
else
|
2015-01-15 21:39:43 +01:00
|
|
|
|
interesting = g_file_monitor_source_set_pending_change_dirty (fms, pending);
|
2015-01-12 20:59:35 +01:00
|
|
|
|
|
|
|
|
|
g_file_monitor_source_update_ready_time (fms);
|
2015-01-15 21:39:43 +01:00
|
|
|
|
|
|
|
|
|
return interesting;
|
2015-01-12 20:59:35 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
g_file_monitor_source_file_changes_done (GFileMonitorSource *fms,
|
|
|
|
|
const gchar *child)
|
|
|
|
|
{
|
|
|
|
|
GSequenceIter *pending;
|
|
|
|
|
|
|
|
|
|
pending = g_file_monitor_source_find_pending_change (fms, child);
|
|
|
|
|
if (pending)
|
|
|
|
|
{
|
|
|
|
|
/* If it is dirty, make sure we push out the last CHANGED event */
|
|
|
|
|
if (g_file_monitor_source_get_pending_change_dirty (fms, pending))
|
|
|
|
|
g_file_monitor_source_queue_event (fms, G_FILE_MONITOR_EVENT_CHANGED, child, NULL);
|
|
|
|
|
|
|
|
|
|
g_file_monitor_source_queue_event (fms, G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT, child, NULL);
|
|
|
|
|
g_file_monitor_source_remove_pending_change (fms, pending, child);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
g_file_monitor_source_file_created (GFileMonitorSource *fms,
|
|
|
|
|
const gchar *child,
|
|
|
|
|
gint64 event_time)
|
|
|
|
|
{
|
|
|
|
|
/* Unlikely, but if we have pending changes for this filename, make
|
|
|
|
|
* sure we flush those out first, before creating the new ones.
|
|
|
|
|
*/
|
|
|
|
|
g_file_monitor_source_file_changes_done (fms, child);
|
|
|
|
|
|
|
|
|
|
/* Emit CREATE and add a pending changes record */
|
|
|
|
|
g_file_monitor_source_queue_event (fms, G_FILE_MONITOR_EVENT_CREATED, child, NULL);
|
|
|
|
|
g_file_monitor_source_add_pending_change (fms, child, event_time);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
g_file_monitor_source_send_event (GFileMonitorSource *fms,
|
|
|
|
|
GFileMonitorEvent event_type,
|
|
|
|
|
const gchar *child,
|
|
|
|
|
GFile *other)
|
|
|
|
|
{
|
|
|
|
|
/* always flush any pending changes before we queue a new event */
|
|
|
|
|
g_file_monitor_source_file_changes_done (fms, child);
|
|
|
|
|
g_file_monitor_source_queue_event (fms, event_type, child, other);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
g_file_monitor_source_send_synthetic_created (GFileMonitorSource *fms,
|
|
|
|
|
const gchar *child)
|
|
|
|
|
{
|
|
|
|
|
g_file_monitor_source_file_changes_done (fms, child);
|
|
|
|
|
g_file_monitor_source_queue_event (fms, G_FILE_MONITOR_EVENT_CREATED, child, NULL);
|
|
|
|
|
g_file_monitor_source_queue_event (fms, G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT, child, NULL);
|
|
|
|
|
}
|
|
|
|
|
|
2019-03-05 13:22:51 +01:00
|
|
|
|
#ifndef G_DISABLE_ASSERT
|
2015-01-12 20:59:35 +01:00
|
|
|
|
static gboolean
|
|
|
|
|
is_basename (const gchar *name)
|
|
|
|
|
{
|
|
|
|
|
if (name[0] == '.' && ((name[1] == '.' && name[2] == '\0') || name[1] == '\0'))
|
|
|
|
|
return FALSE;
|
|
|
|
|
|
|
|
|
|
return !strchr (name, '/');
|
|
|
|
|
}
|
2019-03-05 13:22:51 +01:00
|
|
|
|
#endif /* !G_DISABLE_ASSERT */
|
2015-01-12 20:59:35 +01:00
|
|
|
|
|
2015-01-15 21:39:43 +01:00
|
|
|
|
gboolean
|
2015-01-12 20:59:35 +01:00
|
|
|
|
g_file_monitor_source_handle_event (GFileMonitorSource *fms,
|
|
|
|
|
GFileMonitorEvent event_type,
|
|
|
|
|
const gchar *child,
|
|
|
|
|
const gchar *rename_to,
|
|
|
|
|
GFile *other,
|
|
|
|
|
gint64 event_time)
|
|
|
|
|
{
|
2015-01-15 21:39:43 +01:00
|
|
|
|
gboolean interesting = TRUE;
|
|
|
|
|
|
2015-01-12 20:59:35 +01:00
|
|
|
|
g_assert (!child || is_basename (child));
|
|
|
|
|
g_assert (!rename_to || is_basename (rename_to));
|
|
|
|
|
|
2015-08-19 13:10:01 +02:00
|
|
|
|
if (fms->basename && (!child || !g_str_equal (child, fms->basename))
|
|
|
|
|
&& (!rename_to || !g_str_equal (rename_to, fms->basename)))
|
2015-01-15 21:39:43 +01:00
|
|
|
|
return TRUE;
|
2015-01-12 20:59:35 +01:00
|
|
|
|
|
|
|
|
|
g_mutex_lock (&fms->lock);
|
|
|
|
|
|
2022-05-30 18:54:18 +02:00
|
|
|
|
/* NOTE:
|
|
|
|
|
*
|
|
|
|
|
* We process events even if the file monitor has already been disposed.
|
|
|
|
|
* The reason is that we must not take a reference to the instance here as
|
|
|
|
|
* destroying it from the event handling thread will lead to a deadlock when
|
|
|
|
|
* taking the lock in _ih_sub_cancel.
|
|
|
|
|
*
|
|
|
|
|
* This results in seemingly-unbounded growth of the `event_queue` with the
|
|
|
|
|
* calls to `g_file_monitor_source_queue_event()`. However, each of those sets
|
|
|
|
|
* the ready time on the #GSource, which means that it will be dispatched in
|
|
|
|
|
* a subsequent iteration of the #GMainContext it’s attached to. At that
|
|
|
|
|
* point, `g_file_monitor_source_dispatch()` will return %FALSE, and this will
|
|
|
|
|
* trigger finalisation of the source. That will clear the `event_queue`.
|
|
|
|
|
*
|
|
|
|
|
* If the source is no longer attached, this will return early to prevent
|
|
|
|
|
* unbounded queueing.
|
glocalfilemonitor: Avoid file monitor destruction from event thread
Taking a reference to the GFileMonitor when handling events may cause
object destruction from th worker thread that calls the function. This
condition happens if the surrounding code drops the otherwise last
reference ot the GFileMonitor. The series of events causes destruction
from an unrelated worker thread and also triggers g_file_monitor_cancel
to be called from g_file_monitor_source_handle_event.
For the inotify backend, this results in a deadlock as cancellation
needs to take a lock that protects data structures from being modified
while events are dispatched.
One alternative to this approach might be to add an RCU (release, copy,
update) approach to the lists contained in the wd_dir_hash and
wd_file_hash hash tables.
Fixes: #1941
An example stack trace of this happening is:
Thread 2 (Thread 0x7fea68b1d640 (LWP 260961) "gmain"):
#0 syscall () at ../sysdeps/unix/sysv/linux/x86_64/syscall.S:38
#1 0x00007fea692215dc in g_mutex_lock_slowpath (mutex=mutex@entry=0x7fea6911e148 <g.inotify_lock_lock>) at ../glib/gthread-posix.c:1493
#2 0x00007fea69222062 in g_mutex_lock (mutex=mutex@entry=0x7fea6911e148 <g.inotify_lock_lock>) at ../glib/gthread-posix.c:1517
#3 0x00007fea6908025a in _ih_sub_cancel (sub=0x1492620) at ../gio/inotify/inotify-helper.c:131
#4 0x00007fea6907f9da in g_inotify_file_monitor_cancel (monitor=0x14a3550) at ../gio/inotify/ginotifyfilemonitor.c:75
#5 0x00007fea68fae959 in g_file_monitor_cancel (monitor=0x14a3550) at ../gio/gfilemonitor.c:241
#6 0x00007fea68fae9dc in g_file_monitor_dispose (object=0x14a3550) at ../gio/gfilemonitor.c:123
#7 0x00007fea69139341 in g_object_unref (_object=<optimized out>) at ../gobject/gobject.c:3636
#8 g_object_unref (_object=0x14a3550) at ../gobject/gobject.c:3553
#9 0x00007fea6907507a in g_file_monitor_source_handle_event (fms=0x14c3560, event_type=<optimized out>, child=0x7fea64001460 "spawned-1", rename_to=rename_to@entry=0x0, other=other@entry=0x0, event_time=<optimized out>) at ../gio/glocalfilemonitor.c:457
#10 0x00007fea6907fe0e in ih_event_callback (event=0x7fea64001420, sub=0x1492620, file_event=<optimized out>) at ../gio/inotify/inotify-helper.c:218
#11 0x00007fea6908075c in ip_event_dispatch (dir_list=dir_list@entry=0x14c14c0, file_list=0x0, event=event@entry=0x7fea64001420) at ../gio/inotify/inotify-path.c:493
#12 0x00007fea6908094e in ip_event_dispatch (event=0x7fea64001420, file_list=<optimized out>, dir_list=0x14c14c0) at ../gio/inotify/inotify-path.c:448
#13 ip_event_callback (event=0x7fea64001420) at ../gio/inotify/inotify-path.c:548
#14 ip_event_callback (event=0x7fea64001420) at ../gio/inotify/inotify-path.c:530
#15 0x00007fea69081391 in ik_source_dispatch (source=0x14a2bf0, func=0x7fea69080890 <ip_event_callback>, user_data=<optimized out>) at ../gio/inotify/inotify-kernel.c:327
#16 0x00007fea691d0824 in g_main_dispatch (context=0x14a2cc0) at ../glib/gmain.c:3417
#17 g_main_context_dispatch (context=0x14a2cc0) at ../glib/gmain.c:4135
#18 0x00007fea691d0b88 in g_main_context_iterate (context=context@entry=0x14a2cc0, block=block@entry=1, dispatch=dispatch@entry=1, self=<optimized out>) at ../glib/gmain.c:4211
#19 0x00007fea691d0c2f in g_main_context_iteration (context=0x14a2cc0, may_block=may_block@entry=1) at ../glib/gmain.c:4276
#20 0x00007fea691d0c81 in glib_worker_main (data=<optimized out>) at ../glib/gmain.c:6176
#21 0x00007fea691f9c2d in g_thread_proxy (data=0x1487cc0) at ../glib/gthread.c:827
#22 0x00007fea68d93b1a in start_thread (arg=<optimized out>) at pthread_create.c:443
#23 0x00007fea68e18650 in clone3 () at ../sysdeps/unix/sysv/linux/x86_64/clone3.S:81
2022-03-18 12:16:12 +01:00
|
|
|
|
*/
|
2022-05-30 18:54:18 +02:00
|
|
|
|
if (g_source_is_destroyed ((GSource *) fms))
|
|
|
|
|
{
|
|
|
|
|
g_mutex_unlock (&fms->lock);
|
|
|
|
|
return TRUE;
|
|
|
|
|
}
|
2015-01-12 20:59:35 +01:00
|
|
|
|
|
|
|
|
|
switch (event_type)
|
|
|
|
|
{
|
|
|
|
|
case G_FILE_MONITOR_EVENT_CREATED:
|
|
|
|
|
g_assert (!other && !rename_to);
|
|
|
|
|
g_file_monitor_source_file_created (fms, child, event_time);
|
2007-11-26 17:13:05 +01:00
|
|
|
|
break;
|
2013-07-25 19:29:16 +02:00
|
|
|
|
|
2015-01-12 20:59:35 +01:00
|
|
|
|
case G_FILE_MONITOR_EVENT_CHANGED:
|
|
|
|
|
g_assert (!other && !rename_to);
|
2015-01-15 21:39:43 +01:00
|
|
|
|
interesting = g_file_monitor_source_file_changed (fms, child, event_time);
|
2010-02-18 15:43:45 +01:00
|
|
|
|
break;
|
2013-07-25 19:29:16 +02:00
|
|
|
|
|
2015-01-12 20:59:35 +01:00
|
|
|
|
case G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT:
|
|
|
|
|
g_assert (!other && !rename_to);
|
|
|
|
|
g_file_monitor_source_file_changes_done (fms, child);
|
2007-11-26 17:13:05 +01:00
|
|
|
|
break;
|
2015-01-12 20:59:35 +01:00
|
|
|
|
|
|
|
|
|
case G_FILE_MONITOR_EVENT_MOVED_IN:
|
|
|
|
|
g_assert (!rename_to);
|
|
|
|
|
if (fms->flags & G_FILE_MONITOR_WATCH_MOVES)
|
|
|
|
|
g_file_monitor_source_send_event (fms, G_FILE_MONITOR_EVENT_MOVED_IN, child, other);
|
|
|
|
|
else
|
|
|
|
|
g_file_monitor_source_send_synthetic_created (fms, child);
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case G_FILE_MONITOR_EVENT_MOVED_OUT:
|
|
|
|
|
g_assert (!rename_to);
|
|
|
|
|
if (fms->flags & G_FILE_MONITOR_WATCH_MOVES)
|
|
|
|
|
g_file_monitor_source_send_event (fms, G_FILE_MONITOR_EVENT_MOVED_OUT, child, other);
|
2015-07-06 16:21:33 +02:00
|
|
|
|
else if (other && (fms->flags & G_FILE_MONITOR_SEND_MOVED))
|
2015-01-12 20:59:35 +01:00
|
|
|
|
g_file_monitor_source_send_event (fms, G_FILE_MONITOR_EVENT_MOVED, child, other);
|
|
|
|
|
else
|
|
|
|
|
g_file_monitor_source_send_event (fms, G_FILE_MONITOR_EVENT_DELETED, child, NULL);
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case G_FILE_MONITOR_EVENT_RENAMED:
|
|
|
|
|
g_assert (!other && rename_to);
|
2018-02-26 14:35:07 +01:00
|
|
|
|
if (fms->flags & (G_FILE_MONITOR_WATCH_MOVES | G_FILE_MONITOR_SEND_MOVED))
|
2015-01-12 20:59:35 +01:00
|
|
|
|
{
|
2022-01-19 18:45:46 +01:00
|
|
|
|
GFile *other_file;
|
2018-02-26 14:35:07 +01:00
|
|
|
|
const gchar *dirname;
|
|
|
|
|
gchar *allocated_dirname = NULL;
|
|
|
|
|
GFileMonitorEvent event;
|
2015-01-12 20:59:35 +01:00
|
|
|
|
|
2018-02-26 14:35:07 +01:00
|
|
|
|
event = (fms->flags & G_FILE_MONITOR_WATCH_MOVES) ? G_FILE_MONITOR_EVENT_RENAMED : G_FILE_MONITOR_EVENT_MOVED;
|
2015-01-12 20:59:35 +01:00
|
|
|
|
|
2018-02-26 14:35:07 +01:00
|
|
|
|
if (fms->dirname != NULL)
|
|
|
|
|
dirname = fms->dirname;
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
allocated_dirname = g_path_get_dirname (fms->filename);
|
|
|
|
|
dirname = allocated_dirname;
|
|
|
|
|
}
|
|
|
|
|
|
2022-01-19 18:45:46 +01:00
|
|
|
|
other_file = g_local_file_new_from_dirname_and_basename (dirname, rename_to);
|
2015-01-12 20:59:35 +01:00
|
|
|
|
g_file_monitor_source_file_changes_done (fms, rename_to);
|
2022-01-19 18:45:46 +01:00
|
|
|
|
g_file_monitor_source_send_event (fms, event, child, other_file);
|
2018-02-26 14:35:07 +01:00
|
|
|
|
|
2022-01-19 18:45:46 +01:00
|
|
|
|
g_object_unref (other_file);
|
2018-02-26 14:35:07 +01:00
|
|
|
|
g_free (allocated_dirname);
|
2015-01-12 20:59:35 +01:00
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
g_file_monitor_source_send_event (fms, G_FILE_MONITOR_EVENT_DELETED, child, NULL);
|
2015-03-26 04:08:38 +01:00
|
|
|
|
g_file_monitor_source_send_synthetic_created (fms, rename_to);
|
2015-01-12 20:59:35 +01:00
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case G_FILE_MONITOR_EVENT_DELETED:
|
|
|
|
|
case G_FILE_MONITOR_EVENT_ATTRIBUTE_CHANGED:
|
|
|
|
|
case G_FILE_MONITOR_EVENT_PRE_UNMOUNT:
|
|
|
|
|
case G_FILE_MONITOR_EVENT_UNMOUNTED:
|
|
|
|
|
g_assert (!other && !rename_to);
|
|
|
|
|
g_file_monitor_source_send_event (fms, event_type, child, NULL);
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case G_FILE_MONITOR_EVENT_MOVED:
|
|
|
|
|
/* was never available in this API */
|
|
|
|
|
default:
|
|
|
|
|
g_assert_not_reached ();
|
2013-07-25 19:29:16 +02:00
|
|
|
|
}
|
2015-01-12 20:59:35 +01:00
|
|
|
|
|
|
|
|
|
g_file_monitor_source_update_ready_time (fms);
|
|
|
|
|
|
|
|
|
|
g_mutex_unlock (&fms->lock);
|
2015-01-15 21:39:43 +01:00
|
|
|
|
|
|
|
|
|
return interesting;
|
2007-11-26 17:13:05 +01:00
|
|
|
|
}
|
|
|
|
|
|
2015-01-12 20:59:35 +01:00
|
|
|
|
static gint64
|
|
|
|
|
g_file_monitor_source_get_rate_limit (GFileMonitorSource *fms)
|
2007-11-26 17:13:05 +01:00
|
|
|
|
{
|
2015-01-12 20:59:35 +01:00
|
|
|
|
gint64 rate_limit;
|
|
|
|
|
|
|
|
|
|
g_mutex_lock (&fms->lock);
|
|
|
|
|
rate_limit = fms->rate_limit;
|
|
|
|
|
g_mutex_unlock (&fms->lock);
|
|
|
|
|
|
|
|
|
|
return rate_limit;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static gboolean
|
|
|
|
|
g_file_monitor_source_set_rate_limit (GFileMonitorSource *fms,
|
|
|
|
|
gint64 rate_limit)
|
|
|
|
|
{
|
|
|
|
|
gboolean changed;
|
|
|
|
|
|
|
|
|
|
g_mutex_lock (&fms->lock);
|
|
|
|
|
|
|
|
|
|
if (rate_limit != fms->rate_limit)
|
|
|
|
|
{
|
|
|
|
|
fms->rate_limit = rate_limit;
|
|
|
|
|
|
|
|
|
|
g_sequence_sort (fms->pending_changes, pending_change_compare_ready_time, fms);
|
|
|
|
|
g_file_monitor_source_update_ready_time (fms);
|
2015-05-03 13:17:10 +02:00
|
|
|
|
|
|
|
|
|
changed = TRUE;
|
2015-01-12 20:59:35 +01:00
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
changed = FALSE;
|
2007-11-26 17:13:05 +01:00
|
|
|
|
|
2015-01-12 20:59:35 +01:00
|
|
|
|
g_mutex_unlock (&fms->lock);
|
2007-11-26 17:13:05 +01:00
|
|
|
|
|
2015-01-12 20:59:35 +01:00
|
|
|
|
return changed;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static gboolean
|
|
|
|
|
g_file_monitor_source_dispatch (GSource *source,
|
|
|
|
|
GSourceFunc callback,
|
|
|
|
|
gpointer user_data)
|
|
|
|
|
{
|
|
|
|
|
GFileMonitorSource *fms = (GFileMonitorSource *) source;
|
|
|
|
|
QueuedEvent *event;
|
|
|
|
|
GQueue event_queue;
|
|
|
|
|
gint64 now;
|
2019-10-11 23:22:46 +02:00
|
|
|
|
GFileMonitor *instance = NULL;
|
2015-01-12 20:59:35 +01:00
|
|
|
|
|
|
|
|
|
/* make sure the monitor still exists */
|
2019-10-11 23:22:46 +02:00
|
|
|
|
instance = g_weak_ref_get (&fms->instance_ref);
|
|
|
|
|
if (instance == NULL)
|
2015-01-12 20:59:35 +01:00
|
|
|
|
return FALSE;
|
|
|
|
|
|
|
|
|
|
now = g_source_get_time (source);
|
|
|
|
|
|
|
|
|
|
/* Acquire the lock once and grab all events in one go, handling the
|
|
|
|
|
* queued events first. This avoids strange possibilities in cases of
|
|
|
|
|
* long delays, such as CHANGED events coming before CREATED events.
|
|
|
|
|
*
|
|
|
|
|
* We do this by converting the applicable pending changes into queued
|
|
|
|
|
* events (after the ones already queued) and then stealing the entire
|
|
|
|
|
* event queue in one go.
|
|
|
|
|
*/
|
|
|
|
|
g_mutex_lock (&fms->lock);
|
|
|
|
|
|
|
|
|
|
/* Create events for any pending changes that are due to fire */
|
2015-10-30 15:02:06 +01:00
|
|
|
|
while (!g_sequence_is_empty (fms->pending_changes))
|
2015-01-12 20:59:35 +01:00
|
|
|
|
{
|
|
|
|
|
GSequenceIter *iter = g_sequence_get_begin_iter (fms->pending_changes);
|
|
|
|
|
PendingChange *pending = g_sequence_get (iter);
|
|
|
|
|
|
|
|
|
|
/* We've gotten to a pending change that's not ready. Stop. */
|
|
|
|
|
if (pending_change_get_ready_time (pending, fms) > now)
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
if (pending->dirty)
|
|
|
|
|
{
|
|
|
|
|
/* It's time to send another CHANGED and update the record */
|
|
|
|
|
g_file_monitor_source_queue_event (fms, G_FILE_MONITOR_EVENT_CHANGED, pending->child, NULL);
|
|
|
|
|
pending->last_emission = now;
|
|
|
|
|
pending->dirty = FALSE;
|
|
|
|
|
|
|
|
|
|
g_sequence_sort_changed (iter, pending_change_compare_ready_time, fms);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
/* It's time to send CHANGES_DONE and remove the pending record */
|
|
|
|
|
g_file_monitor_source_queue_event (fms, G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT, pending->child, NULL);
|
|
|
|
|
g_file_monitor_source_remove_pending_change (fms, iter, pending->child);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Steal the queue */
|
|
|
|
|
memcpy (&event_queue, &fms->event_queue, sizeof event_queue);
|
|
|
|
|
memset (&fms->event_queue, 0, sizeof fms->event_queue);
|
|
|
|
|
|
|
|
|
|
g_file_monitor_source_update_ready_time (fms);
|
|
|
|
|
|
|
|
|
|
g_mutex_unlock (&fms->lock);
|
2021-06-14 16:02:59 +02:00
|
|
|
|
g_clear_object (&instance);
|
2015-01-12 20:59:35 +01:00
|
|
|
|
|
|
|
|
|
/* We now have our list of events to deliver */
|
|
|
|
|
while ((event = g_queue_pop_head (&event_queue)))
|
|
|
|
|
{
|
|
|
|
|
/* an event handler could destroy 'instance', so check each time */
|
2019-10-11 23:22:46 +02:00
|
|
|
|
instance = g_weak_ref_get (&fms->instance_ref);
|
|
|
|
|
if (instance != NULL)
|
|
|
|
|
g_file_monitor_emit_event (instance, event->child, event->other, event->event_type);
|
2015-01-12 20:59:35 +01:00
|
|
|
|
|
2019-10-11 23:22:46 +02:00
|
|
|
|
g_clear_object (&instance);
|
2015-01-12 20:59:35 +01:00
|
|
|
|
queued_event_free (event);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return TRUE;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
g_file_monitor_source_dispose (GFileMonitorSource *fms)
|
|
|
|
|
{
|
2019-10-11 23:22:46 +02:00
|
|
|
|
GHashTableIter iter;
|
|
|
|
|
gpointer seqiter;
|
|
|
|
|
QueuedEvent *event;
|
|
|
|
|
|
2015-01-12 20:59:35 +01:00
|
|
|
|
g_mutex_lock (&fms->lock);
|
|
|
|
|
|
2019-10-11 23:22:46 +02:00
|
|
|
|
g_hash_table_iter_init (&iter, fms->pending_changes_table);
|
|
|
|
|
while (g_hash_table_iter_next (&iter, NULL, &seqiter))
|
2015-01-12 20:59:35 +01:00
|
|
|
|
{
|
2019-10-11 23:22:46 +02:00
|
|
|
|
g_hash_table_iter_remove (&iter);
|
|
|
|
|
g_sequence_remove (seqiter);
|
|
|
|
|
}
|
2015-01-12 20:59:35 +01:00
|
|
|
|
|
2019-10-11 23:22:46 +02:00
|
|
|
|
while ((event = g_queue_pop_head (&fms->event_queue)))
|
|
|
|
|
queued_event_free (event);
|
2015-01-12 20:59:35 +01:00
|
|
|
|
|
2019-10-11 23:22:46 +02:00
|
|
|
|
g_assert (g_sequence_is_empty (fms->pending_changes));
|
|
|
|
|
g_assert (g_hash_table_size (fms->pending_changes_table) == 0);
|
|
|
|
|
g_assert (fms->event_queue.length == 0);
|
|
|
|
|
g_weak_ref_set (&fms->instance_ref, NULL);
|
2015-01-12 20:59:35 +01:00
|
|
|
|
|
2019-10-11 23:22:46 +02:00
|
|
|
|
g_file_monitor_source_update_ready_time (fms);
|
2015-01-12 20:59:35 +01:00
|
|
|
|
|
|
|
|
|
g_source_destroy ((GSource *) fms);
|
2022-05-30 18:54:18 +02:00
|
|
|
|
|
|
|
|
|
g_mutex_unlock (&fms->lock);
|
2015-01-12 20:59:35 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
g_file_monitor_source_finalize (GSource *source)
|
|
|
|
|
{
|
|
|
|
|
GFileMonitorSource *fms = (GFileMonitorSource *) source;
|
|
|
|
|
|
|
|
|
|
/* should already have been cleared in dispose of the monitor */
|
2019-10-11 23:22:46 +02:00
|
|
|
|
g_assert (g_weak_ref_get (&fms->instance_ref) == NULL);
|
|
|
|
|
g_weak_ref_clear (&fms->instance_ref);
|
|
|
|
|
|
2015-10-30 15:02:06 +01:00
|
|
|
|
g_assert (g_sequence_is_empty (fms->pending_changes));
|
2015-01-12 20:59:35 +01:00
|
|
|
|
g_assert (g_hash_table_size (fms->pending_changes_table) == 0);
|
|
|
|
|
g_assert (fms->event_queue.length == 0);
|
|
|
|
|
|
|
|
|
|
g_hash_table_unref (fms->pending_changes_table);
|
|
|
|
|
g_sequence_free (fms->pending_changes);
|
|
|
|
|
|
|
|
|
|
g_free (fms->dirname);
|
|
|
|
|
g_free (fms->basename);
|
|
|
|
|
g_free (fms->filename);
|
|
|
|
|
|
|
|
|
|
g_mutex_clear (&fms->lock);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static guint
|
|
|
|
|
str_hash0 (gconstpointer str)
|
|
|
|
|
{
|
|
|
|
|
return str ? g_str_hash (str) : 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static gboolean
|
|
|
|
|
str_equal0 (gconstpointer a,
|
|
|
|
|
gconstpointer b)
|
|
|
|
|
{
|
|
|
|
|
return g_strcmp0 (a, b) == 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static GFileMonitorSource *
|
|
|
|
|
g_file_monitor_source_new (gpointer instance,
|
|
|
|
|
const gchar *filename,
|
|
|
|
|
gboolean is_directory,
|
|
|
|
|
GFileMonitorFlags flags)
|
|
|
|
|
{
|
|
|
|
|
static GSourceFuncs source_funcs = {
|
|
|
|
|
NULL, NULL,
|
|
|
|
|
g_file_monitor_source_dispatch,
|
2020-11-17 00:59:06 +01:00
|
|
|
|
g_file_monitor_source_finalize,
|
|
|
|
|
NULL, NULL
|
2015-01-12 20:59:35 +01:00
|
|
|
|
};
|
|
|
|
|
GFileMonitorSource *fms;
|
|
|
|
|
GSource *source;
|
|
|
|
|
|
|
|
|
|
source = g_source_new (&source_funcs, sizeof (GFileMonitorSource));
|
|
|
|
|
fms = (GFileMonitorSource *) source;
|
|
|
|
|
|
2021-07-26 11:53:02 +02:00
|
|
|
|
g_source_set_static_name (source, "GFileMonitorSource");
|
2016-06-17 00:31:42 +02:00
|
|
|
|
|
2015-01-12 20:59:35 +01:00
|
|
|
|
g_mutex_init (&fms->lock);
|
2019-10-11 23:22:46 +02:00
|
|
|
|
g_weak_ref_init (&fms->instance_ref, instance);
|
2015-01-12 20:59:35 +01:00
|
|
|
|
fms->pending_changes = g_sequence_new (pending_change_free);
|
|
|
|
|
fms->pending_changes_table = g_hash_table_new (str_hash0, str_equal0);
|
|
|
|
|
fms->rate_limit = DEFAULT_RATE_LIMIT;
|
|
|
|
|
fms->flags = flags;
|
|
|
|
|
|
|
|
|
|
if (is_directory)
|
|
|
|
|
{
|
|
|
|
|
fms->dirname = g_strdup (filename);
|
|
|
|
|
fms->basename = NULL;
|
|
|
|
|
fms->filename = NULL;
|
|
|
|
|
}
|
|
|
|
|
else if (flags & G_FILE_MONITOR_WATCH_HARD_LINKS)
|
|
|
|
|
{
|
|
|
|
|
fms->dirname = NULL;
|
|
|
|
|
fms->basename = NULL;
|
|
|
|
|
fms->filename = g_strdup (filename);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
fms->dirname = g_path_get_dirname (filename);
|
|
|
|
|
fms->basename = g_path_get_basename (filename);
|
|
|
|
|
fms->filename = NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return fms;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
G_DEFINE_ABSTRACT_TYPE (GLocalFileMonitor, g_local_file_monitor, G_TYPE_FILE_MONITOR)
|
|
|
|
|
|
|
|
|
|
enum {
|
|
|
|
|
PROP_0,
|
|
|
|
|
PROP_RATE_LIMIT,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
g_local_file_monitor_get_property (GObject *object, guint prop_id,
|
|
|
|
|
GValue *value, GParamSpec *pspec)
|
|
|
|
|
{
|
|
|
|
|
GLocalFileMonitor *monitor = G_LOCAL_FILE_MONITOR (object);
|
|
|
|
|
gint64 rate_limit;
|
|
|
|
|
|
|
|
|
|
g_assert (prop_id == PROP_RATE_LIMIT);
|
|
|
|
|
|
|
|
|
|
rate_limit = g_file_monitor_source_get_rate_limit (monitor->source);
|
|
|
|
|
rate_limit /= G_TIME_SPAN_MILLISECOND;
|
|
|
|
|
|
|
|
|
|
g_value_set_int (value, rate_limit);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
g_local_file_monitor_set_property (GObject *object, guint prop_id,
|
|
|
|
|
const GValue *value, GParamSpec *pspec)
|
|
|
|
|
{
|
|
|
|
|
GLocalFileMonitor *monitor = G_LOCAL_FILE_MONITOR (object);
|
|
|
|
|
gint64 rate_limit;
|
|
|
|
|
|
|
|
|
|
g_assert (prop_id == PROP_RATE_LIMIT);
|
|
|
|
|
|
|
|
|
|
rate_limit = g_value_get_int (value);
|
|
|
|
|
rate_limit *= G_TIME_SPAN_MILLISECOND;
|
|
|
|
|
|
|
|
|
|
if (g_file_monitor_source_set_rate_limit (monitor->source, rate_limit))
|
|
|
|
|
g_object_notify (object, "rate-limit");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#ifndef G_OS_WIN32
|
|
|
|
|
static void
|
|
|
|
|
g_local_file_monitor_mounts_changed (GUnixMountMonitor *mount_monitor,
|
|
|
|
|
gpointer user_data)
|
|
|
|
|
{
|
|
|
|
|
GLocalFileMonitor *local_monitor = user_data;
|
|
|
|
|
GUnixMountEntry *mount;
|
|
|
|
|
gboolean is_mounted;
|
|
|
|
|
GFile *file;
|
|
|
|
|
|
|
|
|
|
/* Emulate unmount detection */
|
|
|
|
|
mount = g_unix_mount_at (local_monitor->source->dirname, NULL);
|
|
|
|
|
|
|
|
|
|
is_mounted = mount != NULL;
|
|
|
|
|
|
|
|
|
|
if (mount)
|
|
|
|
|
g_unix_mount_free (mount);
|
|
|
|
|
|
|
|
|
|
if (local_monitor->was_mounted != is_mounted)
|
|
|
|
|
{
|
|
|
|
|
if (local_monitor->was_mounted && !is_mounted)
|
|
|
|
|
{
|
|
|
|
|
file = g_file_new_for_path (local_monitor->source->dirname);
|
|
|
|
|
g_file_monitor_emit_event (G_FILE_MONITOR (local_monitor), file, NULL, G_FILE_MONITOR_EVENT_UNMOUNTED);
|
|
|
|
|
g_object_unref (file);
|
|
|
|
|
}
|
|
|
|
|
local_monitor->was_mounted = is_mounted;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
g_local_file_monitor_start (GLocalFileMonitor *local_monitor,
|
|
|
|
|
const gchar *filename,
|
|
|
|
|
gboolean is_directory,
|
|
|
|
|
GFileMonitorFlags flags,
|
|
|
|
|
GMainContext *context)
|
|
|
|
|
{
|
|
|
|
|
GLocalFileMonitorClass *class = G_LOCAL_FILE_MONITOR_GET_CLASS (local_monitor);
|
|
|
|
|
GFileMonitorSource *source;
|
|
|
|
|
|
|
|
|
|
g_return_if_fail (G_IS_LOCAL_FILE_MONITOR (local_monitor));
|
|
|
|
|
|
|
|
|
|
g_assert (!local_monitor->source);
|
|
|
|
|
|
2015-11-30 16:13:46 +01:00
|
|
|
|
source = g_file_monitor_source_new (local_monitor, filename, is_directory, flags);
|
|
|
|
|
local_monitor->source = source; /* owns the ref */
|
|
|
|
|
|
2015-01-12 20:59:35 +01:00
|
|
|
|
if (is_directory && !class->mount_notify && (flags & G_FILE_MONITOR_WATCH_MOUNTS))
|
|
|
|
|
{
|
|
|
|
|
#ifdef G_OS_WIN32
|
|
|
|
|
/*claim everything was mounted */
|
|
|
|
|
local_monitor->was_mounted = TRUE;
|
|
|
|
|
#else
|
|
|
|
|
GUnixMountEntry *mount;
|
|
|
|
|
|
|
|
|
|
/* Emulate unmount detection */
|
|
|
|
|
|
|
|
|
|
mount = g_unix_mount_at (local_monitor->source->dirname, NULL);
|
|
|
|
|
|
|
|
|
|
local_monitor->was_mounted = mount != NULL;
|
|
|
|
|
|
|
|
|
|
if (mount)
|
|
|
|
|
g_unix_mount_free (mount);
|
|
|
|
|
|
|
|
|
|
local_monitor->mount_monitor = g_unix_mount_monitor_get ();
|
|
|
|
|
g_signal_connect_object (local_monitor->mount_monitor, "mounts-changed",
|
|
|
|
|
G_CALLBACK (g_local_file_monitor_mounts_changed), local_monitor, 0);
|
|
|
|
|
#endif
|
|
|
|
|
}
|
|
|
|
|
|
2019-02-22 01:00:00 +01:00
|
|
|
|
g_source_attach ((GSource *) source, context);
|
|
|
|
|
|
2015-01-12 20:59:35 +01:00
|
|
|
|
G_LOCAL_FILE_MONITOR_GET_CLASS (local_monitor)->start (local_monitor,
|
|
|
|
|
source->dirname, source->basename, source->filename,
|
|
|
|
|
source);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
g_local_file_monitor_dispose (GObject *object)
|
|
|
|
|
{
|
|
|
|
|
GLocalFileMonitor *local_monitor = G_LOCAL_FILE_MONITOR (object);
|
|
|
|
|
|
|
|
|
|
g_file_monitor_source_dispose (local_monitor->source);
|
|
|
|
|
|
|
|
|
|
G_OBJECT_CLASS (g_local_file_monitor_parent_class)->dispose (object);
|
2007-11-26 17:13:05 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
g_local_file_monitor_finalize (GObject *object)
|
|
|
|
|
{
|
|
|
|
|
GLocalFileMonitor *local_monitor = G_LOCAL_FILE_MONITOR (object);
|
2013-07-25 19:29:16 +02:00
|
|
|
|
|
2015-01-12 20:59:35 +01:00
|
|
|
|
g_source_unref ((GSource *) local_monitor->source);
|
2007-11-26 17:13:05 +01:00
|
|
|
|
|
2008-06-16 11:54:04 +02:00
|
|
|
|
G_OBJECT_CLASS (g_local_file_monitor_parent_class)->finalize (object);
|
2007-11-26 17:13:05 +01:00
|
|
|
|
}
|
|
|
|
|
|
2015-01-12 20:59:35 +01:00
|
|
|
|
static void
|
|
|
|
|
g_local_file_monitor_init (GLocalFileMonitor* local_monitor)
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void g_local_file_monitor_class_init (GLocalFileMonitorClass *class)
|
2007-11-26 17:13:05 +01:00
|
|
|
|
{
|
2015-01-12 20:59:35 +01:00
|
|
|
|
GObjectClass *gobject_class = G_OBJECT_CLASS (class);
|
2007-11-26 17:13:05 +01:00
|
|
|
|
|
2015-01-12 20:59:35 +01:00
|
|
|
|
gobject_class->get_property = g_local_file_monitor_get_property;
|
2007-11-26 17:13:05 +01:00
|
|
|
|
gobject_class->set_property = g_local_file_monitor_set_property;
|
2015-01-12 20:59:35 +01:00
|
|
|
|
gobject_class->dispose = g_local_file_monitor_dispose;
|
2007-11-26 17:13:05 +01:00
|
|
|
|
gobject_class->finalize = g_local_file_monitor_finalize;
|
|
|
|
|
|
2015-01-12 20:59:35 +01:00
|
|
|
|
g_object_class_override_property (gobject_class, PROP_RATE_LIMIT, "rate-limit");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static GLocalFileMonitor *
|
|
|
|
|
g_local_file_monitor_new (gboolean is_remote_fs,
|
2018-06-15 14:29:30 +02:00
|
|
|
|
gboolean is_directory,
|
2015-01-12 20:59:35 +01:00
|
|
|
|
GError **error)
|
|
|
|
|
{
|
2013-01-19 00:44:11 +01:00
|
|
|
|
GType type = G_TYPE_INVALID;
|
2007-12-19 17:08:55 +01:00
|
|
|
|
|
2013-01-19 00:44:11 +01:00
|
|
|
|
if (is_remote_fs)
|
|
|
|
|
type = _g_io_module_get_default_type (G_NFS_FILE_MONITOR_EXTENSION_POINT_NAME,
|
|
|
|
|
"GIO_USE_FILE_MONITOR",
|
|
|
|
|
G_STRUCT_OFFSET (GLocalFileMonitorClass, is_supported));
|
|
|
|
|
|
2018-06-15 14:29:30 +02:00
|
|
|
|
/* Fallback rather to poll file monitor for remote files, see gfile.c. */
|
|
|
|
|
if (type == G_TYPE_INVALID && (!is_remote_fs || is_directory))
|
2013-01-19 00:44:11 +01:00
|
|
|
|
type = _g_io_module_get_default_type (G_LOCAL_FILE_MONITOR_EXTENSION_POINT_NAME,
|
|
|
|
|
"GIO_USE_FILE_MONITOR",
|
|
|
|
|
G_STRUCT_OFFSET (GLocalFileMonitorClass, is_supported));
|
2007-12-19 17:08:55 +01:00
|
|
|
|
|
2015-01-12 20:59:35 +01:00
|
|
|
|
if (type == G_TYPE_INVALID)
|
|
|
|
|
{
|
|
|
|
|
g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED,
|
|
|
|
|
_("Unable to find default local file monitor type"));
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return g_object_new (type, NULL);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
GFileMonitor *
|
|
|
|
|
g_local_file_monitor_new_for_path (const gchar *pathname,
|
|
|
|
|
gboolean is_directory,
|
|
|
|
|
GFileMonitorFlags flags,
|
|
|
|
|
GError **error)
|
|
|
|
|
{
|
|
|
|
|
GLocalFileMonitor *monitor;
|
|
|
|
|
gboolean is_remote_fs;
|
2007-12-19 17:08:55 +01:00
|
|
|
|
|
2020-06-15 17:44:26 +02:00
|
|
|
|
is_remote_fs = g_local_file_is_nfs_home (pathname);
|
2015-01-12 20:59:35 +01:00
|
|
|
|
|
2018-06-15 14:29:30 +02:00
|
|
|
|
monitor = g_local_file_monitor_new (is_remote_fs, is_directory, error);
|
2015-01-12 20:59:35 +01:00
|
|
|
|
|
|
|
|
|
if (monitor)
|
|
|
|
|
g_local_file_monitor_start (monitor, pathname, is_directory, flags, g_main_context_get_thread_default ());
|
|
|
|
|
|
|
|
|
|
return G_FILE_MONITOR (monitor);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
GFileMonitor *
|
|
|
|
|
g_local_file_monitor_new_in_worker (const gchar *pathname,
|
|
|
|
|
gboolean is_directory,
|
|
|
|
|
GFileMonitorFlags flags,
|
|
|
|
|
GFileMonitorCallback callback,
|
|
|
|
|
gpointer user_data,
|
2019-10-11 22:33:47 +02:00
|
|
|
|
GClosureNotify destroy_user_data,
|
2015-01-12 20:59:35 +01:00
|
|
|
|
GError **error)
|
|
|
|
|
{
|
|
|
|
|
GLocalFileMonitor *monitor;
|
|
|
|
|
gboolean is_remote_fs;
|
|
|
|
|
|
2020-06-15 17:44:26 +02:00
|
|
|
|
is_remote_fs = g_local_file_is_nfs_home (pathname);
|
2015-01-12 20:59:35 +01:00
|
|
|
|
|
2018-06-15 14:29:30 +02:00
|
|
|
|
monitor = g_local_file_monitor_new (is_remote_fs, is_directory, error);
|
2015-01-12 20:59:35 +01:00
|
|
|
|
|
|
|
|
|
if (monitor)
|
|
|
|
|
{
|
|
|
|
|
if (callback)
|
2019-10-11 22:33:47 +02:00
|
|
|
|
g_signal_connect_data (monitor, "changed", G_CALLBACK (callback),
|
|
|
|
|
user_data, destroy_user_data, 0 /* flags */);
|
2015-01-12 20:59:35 +01:00
|
|
|
|
|
|
|
|
|
g_local_file_monitor_start (monitor, pathname, is_directory, flags, GLIB_PRIVATE_CALL(g_get_worker_context) ());
|
|
|
|
|
}
|
2013-07-25 19:29:16 +02:00
|
|
|
|
|
2015-01-12 20:59:35 +01:00
|
|
|
|
return G_FILE_MONITOR (monitor);
|
2007-11-26 17:13:05 +01:00
|
|
|
|
}
|