1105 lines
32 KiB
C
1105 lines
32 KiB
C
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4; coding: utf-8 -*-
|
|
* moofilewatch.h
|
|
*
|
|
* Copyright (C) 2004-2006 by Yevgen Muntyan <muntyan@math.tamu.edu>
|
|
*
|
|
* 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.
|
|
*
|
|
* See COPYING file that comes with this distribution.
|
|
*/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include "config.h"
|
|
#endif
|
|
|
|
#ifdef MOO_USE_FAM
|
|
#include <fam.h>
|
|
#endif
|
|
|
|
#ifdef __WIN32__
|
|
#include <windows.h>
|
|
#include <io.h>
|
|
#include <fcntl.h>
|
|
#endif
|
|
|
|
#ifndef __WIN32__
|
|
#include <time.h>
|
|
#endif
|
|
|
|
#include <sys/stat.h>
|
|
#include <errno.h>
|
|
#include <string.h>
|
|
#include "mooutils/moofilewatch.h"
|
|
#include "mooutils/moomarshals.h"
|
|
#include "mooutils/moocompat.h"
|
|
|
|
|
|
#if 0
|
|
#define DEBUG_PRINT g_message
|
|
#else
|
|
static void DEBUG_PRINT (G_GNUC_UNUSED const char *format, ...)
|
|
{
|
|
}
|
|
#endif
|
|
|
|
static const char *event_code_strings[] = {
|
|
NULL,
|
|
"CHANGED",
|
|
"DELETED",
|
|
"CREATED",
|
|
"MOVED"
|
|
};
|
|
|
|
typedef struct _Monitor Monitor;
|
|
|
|
typedef enum {
|
|
MONITOR_DIR,
|
|
MONITOR_FILE
|
|
} MonitorType;
|
|
|
|
typedef struct {
|
|
gboolean (*start) (MooFileWatch *watch,
|
|
GError **error);
|
|
gboolean (*shutdown)(MooFileWatch *watch,
|
|
GError **error);
|
|
|
|
Monitor *(*create) (MooFileWatch *watch,
|
|
MonitorType type,
|
|
const char *filename,
|
|
gpointer data,
|
|
int *request,
|
|
GError **error);
|
|
void (*suspend) (Monitor *monitor);
|
|
void (*resume) (Monitor *monitor);
|
|
void (*delete) (Monitor *monitor);
|
|
} WatchFuncs;
|
|
|
|
|
|
struct _MooFileWatchPrivate {
|
|
guint stat_timeout;
|
|
#ifdef MOO_USE_FAM
|
|
FAMConnection fam_connection;
|
|
guint fam_connection_watch;
|
|
#endif
|
|
WatchFuncs funcs;
|
|
MooFileWatchMethod method;
|
|
GSList *monitors;
|
|
GHashTable *requests; /* int -> Monitor* */
|
|
guint alive : 1;
|
|
};
|
|
|
|
/* stat and win32 assign id themselves */
|
|
static int last_monitor_id = 0;
|
|
|
|
|
|
#ifdef MOO_USE_FAM
|
|
static gboolean watch_fam_start (MooFileWatch *watch,
|
|
GError **error);
|
|
static gboolean watch_fam_shutdown (MooFileWatch *watch,
|
|
GError **error);
|
|
static Monitor *monitor_fam_create (MooFileWatch *watch,
|
|
MonitorType type,
|
|
const char *filename,
|
|
gpointer data,
|
|
int *request,
|
|
GError **error);
|
|
static void monitor_fam_suspend (Monitor *monitor);
|
|
static void monitor_fam_resume (Monitor *monitor);
|
|
static void monitor_fam_delete (Monitor *monitor);
|
|
#endif /* MOO_USE_FAM */
|
|
|
|
static gboolean watch_stat_start (MooFileWatch *watch,
|
|
GError **error);
|
|
static gboolean watch_stat_shutdown (MooFileWatch *watch,
|
|
GError **error);
|
|
static Monitor *monitor_stat_create (MooFileWatch *watch,
|
|
MonitorType type,
|
|
const char *filename,
|
|
gpointer data,
|
|
int *request,
|
|
GError **error);
|
|
static void monitor_stat_suspend (Monitor *monitor);
|
|
static void monitor_stat_resume (Monitor *monitor);
|
|
static void monitor_stat_delete (Monitor *monitor);
|
|
|
|
#ifdef __WIN32__
|
|
static gboolean watch_win32_start (MooFileWatch *watch,
|
|
GError **error);
|
|
static gboolean watch_win32_shutdown (MooFileWatch *watch,
|
|
GError **error);
|
|
static Monitor *monitor_win32_create (MooFileWatch *watch,
|
|
MonitorType type,
|
|
const char *filename,
|
|
gpointer data,
|
|
int *request,
|
|
GError **error);
|
|
static void monitor_win32_suspend (Monitor *monitor);
|
|
static void monitor_win32_resume (Monitor *monitor);
|
|
static void monitor_win32_delete (Monitor *monitor);
|
|
#endif /* __WIN32__ */
|
|
|
|
|
|
/* MOO_TYPE_FILE_WATCH */
|
|
G_DEFINE_TYPE (MooFileWatch, moo_file_watch, G_TYPE_OBJECT)
|
|
|
|
enum {
|
|
PROP_0,
|
|
PROP_METHOD
|
|
};
|
|
|
|
enum {
|
|
EVENT,
|
|
ERROR_SIGNAL,
|
|
LAST_SIGNAL
|
|
};
|
|
|
|
static guint signals[LAST_SIGNAL];
|
|
|
|
static void moo_file_watch_finalize (GObject *object);
|
|
|
|
static void moo_file_watch_class_init (MooFileWatchClass *klass)
|
|
{
|
|
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
|
|
|
|
gobject_class->finalize = moo_file_watch_finalize;
|
|
|
|
signals[EVENT] =
|
|
g_signal_new ("event",
|
|
G_OBJECT_CLASS_TYPE (klass),
|
|
G_SIGNAL_RUN_LAST,
|
|
G_STRUCT_OFFSET (MooFileWatchClass, event),
|
|
NULL, NULL,
|
|
_moo_marshal_VOID__POINTER,
|
|
G_TYPE_NONE, 1,
|
|
G_TYPE_POINTER);
|
|
|
|
signals[ERROR_SIGNAL] =
|
|
g_signal_new ("error",
|
|
G_OBJECT_CLASS_TYPE (klass),
|
|
G_SIGNAL_RUN_LAST,
|
|
G_STRUCT_OFFSET (MooFileWatchClass, error),
|
|
NULL, NULL,
|
|
_moo_marshal_VOID__POINTER,
|
|
G_TYPE_NONE, 1,
|
|
G_TYPE_POINTER);
|
|
}
|
|
|
|
|
|
static void moo_file_watch_init (MooFileWatch *watch)
|
|
{
|
|
watch->priv = g_new0 (MooFileWatchPrivate, 1);
|
|
watch->priv->requests = g_hash_table_new (g_direct_hash, g_direct_equal);
|
|
|
|
#if defined (__WIN32__)
|
|
watch->priv->method = MOO_FILE_WATCH_WIN32;
|
|
#elif defined (MOO_USE_FAM)
|
|
watch->priv->method = MOO_FILE_WATCH_FAM;
|
|
#else
|
|
watch->priv->method = MOO_FILE_WATCH_STAT;
|
|
#endif
|
|
|
|
#if defined (__WIN32__)
|
|
g_assert (watch->priv->method == MOO_FILE_WATCH_WIN32);
|
|
watch->priv->funcs.start = watch_win32_start;
|
|
watch->priv->funcs.shutdown = watch_win32_shutdown;
|
|
watch->priv->funcs.create = monitor_win32_create;
|
|
watch->priv->funcs.suspend = monitor_win32_suspend;
|
|
watch->priv->funcs.resume = monitor_win32_resume;
|
|
watch->priv->funcs.delete = monitor_win32_delete;
|
|
#else /* !__WIN32__ */
|
|
if (watch->priv->method == MOO_FILE_WATCH_STAT)
|
|
{
|
|
watch->priv->funcs.start = watch_stat_start;
|
|
watch->priv->funcs.shutdown = watch_stat_shutdown;
|
|
watch->priv->funcs.create = monitor_stat_create;
|
|
watch->priv->funcs.suspend = monitor_stat_suspend;
|
|
watch->priv->funcs.resume = monitor_stat_resume;
|
|
watch->priv->funcs.delete = monitor_stat_delete;
|
|
}
|
|
#ifdef MOO_USE_FAM
|
|
else
|
|
{
|
|
watch->priv->funcs.start = watch_fam_start;
|
|
watch->priv->funcs.shutdown = watch_fam_shutdown;
|
|
watch->priv->funcs.create = monitor_fam_create;
|
|
watch->priv->funcs.suspend = monitor_fam_suspend;
|
|
watch->priv->funcs.resume = monitor_fam_resume;
|
|
watch->priv->funcs.delete = monitor_fam_delete;
|
|
}
|
|
#else /* !MOO_USE_FAM */
|
|
else
|
|
{
|
|
g_assert_not_reached ();
|
|
}
|
|
#endif /* !MOO_USE_FAM */
|
|
#endif /* !__WIN32__ */
|
|
}
|
|
|
|
|
|
static void moo_file_watch_finalize (GObject *object)
|
|
{
|
|
GError *error = NULL;
|
|
MooFileWatch *watch = MOO_FILE_WATCH (object);
|
|
|
|
if (watch->priv->alive)
|
|
{
|
|
g_warning ("%s: finalizing open watch", G_STRLOC);
|
|
if (!moo_file_watch_close (watch, &error))
|
|
{
|
|
g_warning ("%s: error in moo_file_watch_close()", G_STRLOC);
|
|
g_warning ("%s: %s", G_STRLOC, error->message);
|
|
g_error_free (error);
|
|
}
|
|
}
|
|
|
|
g_hash_table_destroy (watch->priv->requests);
|
|
|
|
g_free (watch->priv);
|
|
watch->priv = NULL;
|
|
|
|
G_OBJECT_CLASS(moo_file_watch_parent_class)->finalize (object);
|
|
}
|
|
|
|
|
|
GQuark moo_file_watch_error_quark (void)
|
|
{
|
|
static GQuark quark = 0;
|
|
if (!quark)
|
|
quark = g_quark_from_static_string ("moo-file-watch-error");
|
|
return quark;
|
|
}
|
|
|
|
|
|
MooFileWatchMethod
|
|
moo_file_watch_get_method (MooFileWatch *watch)
|
|
{
|
|
g_return_val_if_fail (MOO_IS_FILE_WATCH (watch), -1);
|
|
return watch->priv->method;
|
|
}
|
|
|
|
|
|
GType
|
|
moo_file_watch_method_get_type (void)
|
|
{
|
|
static GType type = 0;
|
|
|
|
if (!type)
|
|
{
|
|
static const GEnumValue values[] = {
|
|
{MOO_FILE_WATCH_STAT, (char*) "MOO_FILE_WATCH_STAT", (char*) "stat"},
|
|
{MOO_FILE_WATCH_FAM, (char*) "MOO_FILE_WATCH_FAM", (char*) "fam"},
|
|
{MOO_FILE_WATCH_WIN32, (char*) "MOO_FILE_WATCH_WIN32", (char*) "win32"},
|
|
{ 0, NULL, NULL }
|
|
};
|
|
|
|
type = g_enum_register_static ("MooFileWatchMethod", values);
|
|
}
|
|
|
|
return type;
|
|
}
|
|
|
|
|
|
GType
|
|
moo_file_watch_event_code_get_type (void)
|
|
{
|
|
static GType type = 0;
|
|
|
|
if (!type)
|
|
{
|
|
static const GEnumValue values[] = {
|
|
{MOO_FILE_WATCH_CHANGED, (char*) "MOO_FILE_WATCH_CHANGED", (char*) "changed"},
|
|
{MOO_FILE_WATCH_DELETED, (char*) "MOO_FILE_WATCH_DELETED", (char*) "deleted"},
|
|
{MOO_FILE_WATCH_CREATED, (char*) "MOO_FILE_WATCH_CREATED", (char*) "created"},
|
|
{MOO_FILE_WATCH_MOVED, (char*) "MOO_FILE_WATCH_MOVED", (char*) "moved"},
|
|
{ 0, NULL, NULL }
|
|
};
|
|
|
|
type = g_enum_register_static ("MooFileWatchEventCode", values);
|
|
}
|
|
|
|
return type;
|
|
}
|
|
|
|
|
|
static gboolean
|
|
moo_file_watch_create_monitor (MooFileWatch *watch,
|
|
MonitorType type,
|
|
const char *filename,
|
|
gpointer data,
|
|
int *request,
|
|
GError **error)
|
|
{
|
|
Monitor *monitor;
|
|
int req;
|
|
|
|
g_return_val_if_fail (MOO_IS_FILE_WATCH (watch), FALSE);
|
|
g_return_val_if_fail (filename != NULL, FALSE);
|
|
|
|
if (!watch->priv->alive)
|
|
{
|
|
g_set_error (error, MOO_FILE_WATCH_ERROR,
|
|
MOO_FILE_WATCH_ERROR_CLOSED,
|
|
"MooFileWatch is closed");
|
|
return FALSE;
|
|
}
|
|
|
|
monitor = watch->priv->funcs.create (watch, type, filename, data, &req, error);
|
|
|
|
if (!monitor)
|
|
return FALSE;
|
|
|
|
watch->priv->monitors = g_slist_prepend (watch->priv->monitors, monitor);
|
|
g_hash_table_insert (watch->priv->requests, GINT_TO_POINTER (req), monitor);
|
|
|
|
if (request)
|
|
*request = req;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
gboolean moo_file_watch_monitor_directory (MooFileWatch *watch,
|
|
const char *filename,
|
|
gpointer data,
|
|
int *monitor_id,
|
|
GError **error)
|
|
{
|
|
return moo_file_watch_create_monitor (watch, MONITOR_DIR, filename,
|
|
data, monitor_id, error);
|
|
}
|
|
|
|
|
|
gboolean moo_file_watch_monitor_file (MooFileWatch *watch,
|
|
const char *filename,
|
|
gpointer data,
|
|
int *monitor_id,
|
|
GError **error)
|
|
{
|
|
return moo_file_watch_create_monitor (watch, MONITOR_FILE, filename,
|
|
data, monitor_id, error);
|
|
}
|
|
|
|
|
|
void moo_file_watch_suspend_monitor (MooFileWatch *watch,
|
|
int monitor_id)
|
|
{
|
|
Monitor *monitor;
|
|
|
|
g_return_if_fail (MOO_IS_FILE_WATCH (watch));
|
|
|
|
monitor = g_hash_table_lookup (watch->priv->requests,
|
|
GINT_TO_POINTER (monitor_id));
|
|
g_return_if_fail (monitor != NULL);
|
|
|
|
watch->priv->funcs.suspend (monitor);
|
|
}
|
|
|
|
|
|
void moo_file_watch_resume_monitor (MooFileWatch *watch,
|
|
int monitor_id)
|
|
{
|
|
Monitor *monitor;
|
|
|
|
g_return_if_fail (MOO_IS_FILE_WATCH (watch));
|
|
|
|
monitor = g_hash_table_lookup (watch->priv->requests,
|
|
GINT_TO_POINTER (monitor_id));
|
|
g_return_if_fail (monitor != NULL);
|
|
|
|
watch->priv->funcs.resume (monitor);
|
|
}
|
|
|
|
|
|
void moo_file_watch_cancel_monitor (MooFileWatch *watch,
|
|
int monitor_id)
|
|
{
|
|
Monitor *monitor;
|
|
|
|
g_return_if_fail (MOO_IS_FILE_WATCH (watch));
|
|
|
|
monitor = g_hash_table_lookup (watch->priv->requests,
|
|
GINT_TO_POINTER (monitor_id));
|
|
g_return_if_fail (monitor != NULL);
|
|
|
|
watch->priv->funcs.delete (monitor);
|
|
watch->priv->monitors = g_slist_remove (watch->priv->monitors, monitor);
|
|
g_hash_table_remove (watch->priv->requests,
|
|
GINT_TO_POINTER (monitor_id));
|
|
}
|
|
|
|
|
|
static void emit_event (MooFileWatch *watch,
|
|
MooFileWatchEvent *event)
|
|
{
|
|
DEBUG_PRINT ("watch %p, monitor %d: event %s for %s",
|
|
watch, event->monitor_id,
|
|
event_code_strings[event->code],
|
|
event->filename);
|
|
g_signal_emit (watch, signals[EVENT], 0, event);
|
|
}
|
|
|
|
|
|
MooFileWatch *moo_file_watch_new (GError **error)
|
|
{
|
|
MooFileWatch *watch;
|
|
|
|
watch = MOO_FILE_WATCH (g_object_new (MOO_TYPE_FILE_WATCH, NULL));
|
|
|
|
if (!watch->priv->funcs.start (watch, error))
|
|
{
|
|
g_object_unref (watch);
|
|
return NULL;
|
|
}
|
|
|
|
watch->priv->alive = TRUE;
|
|
return watch;
|
|
}
|
|
|
|
|
|
gboolean moo_file_watch_close (MooFileWatch *watch,
|
|
GError **error)
|
|
{
|
|
g_return_val_if_fail (MOO_IS_FILE_WATCH (watch), FALSE);
|
|
|
|
if (!watch->priv->alive)
|
|
return TRUE;
|
|
|
|
watch->priv->alive = FALSE;
|
|
|
|
g_slist_foreach (watch->priv->monitors,
|
|
(GFunc) watch->priv->funcs.delete, NULL);
|
|
g_slist_free (watch->priv->monitors);
|
|
watch->priv->monitors = NULL;
|
|
|
|
return watch->priv->funcs.shutdown (watch, error);
|
|
}
|
|
|
|
|
|
struct _Monitor {
|
|
MooFileWatch *parent;
|
|
MonitorType type;
|
|
char *filename;
|
|
gpointer user_data;
|
|
int request;
|
|
guint suspended : 1;
|
|
struct stat statbuf;
|
|
};
|
|
|
|
|
|
/*****************************************************************************/
|
|
/* FAM
|
|
*/
|
|
#ifdef MOO_USE_FAM
|
|
|
|
#define SET_FAM_ERROR(func,error) \
|
|
G_STMT_START { \
|
|
g_set_error (error, MOO_FILE_WATCH_ERROR, \
|
|
MOO_FILE_WATCH_ERROR_FAILED, \
|
|
#func " failed: %s", \
|
|
((FAMErrno && FamErrlist[FAMErrno]) ? \
|
|
FamErrlist[FAMErrno] : \
|
|
"unknown error")); \
|
|
} G_STMT_END
|
|
|
|
#define RETURN_FAM_ERROR(func,error,ret) \
|
|
G_STMT_START { \
|
|
SET_FAM_ERROR (func,error); \
|
|
return ret; \
|
|
} G_STMT_END
|
|
|
|
|
|
#define MOO_FAM_SOCKET_WATCH_PRIORITY G_PRIORITY_DEFAULT
|
|
|
|
|
|
static gboolean read_fam_events (GIOChannel *source,
|
|
GIOCondition condition,
|
|
MooFileWatch *watch);
|
|
|
|
static gboolean watch_fam_start (MooFileWatch *watch,
|
|
GError **error)
|
|
{
|
|
GIOChannel *fam_socket;
|
|
|
|
if (FAMOpen (&watch->priv->fam_connection))
|
|
RETURN_FAM_ERROR (FAMOpen, error, FALSE);
|
|
|
|
fam_socket = g_io_channel_unix_new (watch->priv->fam_connection.fd);
|
|
watch->priv->fam_connection_watch =
|
|
g_io_add_watch_full (fam_socket, MOO_FAM_SOCKET_WATCH_PRIORITY,
|
|
G_IO_IN | G_IO_PRI | G_IO_PRI | G_IO_HUP,
|
|
(GIOFunc) read_fam_events, watch, NULL);
|
|
g_io_channel_unref (fam_socket);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
static gboolean watch_fam_shutdown (MooFileWatch *watch,
|
|
GError **error)
|
|
{
|
|
if (watch->priv->fam_connection_watch)
|
|
g_source_remove (watch->priv->fam_connection_watch);
|
|
|
|
if (FAMClose (&watch->priv->fam_connection))
|
|
RETURN_FAM_ERROR (FAMOpen, error, FALSE);
|
|
else
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
static Monitor *monitor_fam_create (MooFileWatch *watch,
|
|
MonitorType type,
|
|
const char *filename,
|
|
gpointer data,
|
|
int *request,
|
|
GError **error)
|
|
{
|
|
FAMRequest fr;
|
|
int result;
|
|
Monitor *monitor;
|
|
|
|
g_return_val_if_fail (filename != NULL, FALSE);
|
|
|
|
if (type == MONITOR_DIR)
|
|
result = FAMMonitorDirectory (&watch->priv->fam_connection,
|
|
filename, &fr, data);
|
|
else
|
|
result = FAMMonitorFile (&watch->priv->fam_connection,
|
|
filename, &fr, data);
|
|
|
|
if (result)
|
|
{
|
|
DEBUG_PRINT ("Connection %d: creating monitor for '%s' failed",
|
|
watch->priv->fam_connection.fd, filename);
|
|
if (type == MONITOR_DIR)
|
|
RETURN_FAM_ERROR (FAMMonitorDirectory, error, NULL);
|
|
else
|
|
RETURN_FAM_ERROR (FAMMonitorFile, error, NULL);
|
|
}
|
|
else
|
|
{
|
|
DEBUG_PRINT ("Connection %d: created monitor %d for %s '%s'",
|
|
watch->priv->fam_connection.fd, fr.reqnum,
|
|
type == MONITOR_DIR ? "directory" : "file",
|
|
filename);
|
|
}
|
|
|
|
if (request)
|
|
*request = fr.reqnum;
|
|
|
|
monitor = g_new0 (Monitor, 1);
|
|
|
|
monitor->parent = watch;
|
|
monitor->type = type;
|
|
monitor->filename = g_strdup (filename);
|
|
monitor->user_data = data;
|
|
monitor->request = fr.reqnum;
|
|
monitor->suspended = FALSE;
|
|
|
|
return monitor;
|
|
}
|
|
|
|
|
|
static void monitor_fam_suspend (Monitor *monitor)
|
|
{
|
|
FAMRequest fr;
|
|
int result;
|
|
|
|
g_return_if_fail (monitor != NULL);
|
|
|
|
if (monitor->suspended)
|
|
return;
|
|
|
|
fr.reqnum = monitor->request;
|
|
|
|
result = FAMSuspendMonitor (&monitor->parent->priv->fam_connection, &fr);
|
|
|
|
if (result)
|
|
{
|
|
DEBUG_PRINT ("Connection %d: FAMSuspendMonitor for %s '%s' failed",
|
|
monitor->parent->priv->fam_connection.fd,
|
|
monitor->type == MONITOR_DIR ? "directory" : "file",
|
|
monitor->filename);
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
DEBUG_PRINT ("Connection %d: suspended monitor %d for %s '%s'",
|
|
monitor->parent->priv->fam_connection.fd,
|
|
monitor->request,
|
|
monitor->type == MONITOR_DIR ? "directory" : "file",
|
|
monitor->filename);
|
|
}
|
|
|
|
monitor->suspended = TRUE;
|
|
}
|
|
|
|
|
|
static void monitor_fam_resume (Monitor *monitor)
|
|
{
|
|
FAMRequest fr;
|
|
int result;
|
|
|
|
g_return_if_fail (monitor != NULL);
|
|
|
|
if (!monitor->suspended)
|
|
return;
|
|
|
|
fr.reqnum = monitor->request;
|
|
|
|
result = FAMResumeMonitor (&monitor->parent->priv->fam_connection, &fr);
|
|
|
|
if (result)
|
|
{
|
|
DEBUG_PRINT ("Connection %d: FAMResumeMonitor for %s '%s' failed",
|
|
monitor->parent->priv->fam_connection.fd,
|
|
monitor->type == MONITOR_DIR ? "directory" : "file",
|
|
monitor->filename);
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
DEBUG_PRINT ("Connection %d: resumed monitor %d for %s '%s'",
|
|
monitor->parent->priv->fam_connection.fd,
|
|
monitor->request,
|
|
monitor->type == MONITOR_DIR ? "directory" : "file",
|
|
monitor->filename);
|
|
}
|
|
|
|
monitor->suspended = FALSE;
|
|
}
|
|
|
|
|
|
static void monitor_fam_delete (Monitor *monitor)
|
|
{
|
|
FAMRequest fr;
|
|
int result;
|
|
|
|
g_return_if_fail (monitor != NULL);
|
|
|
|
fr.reqnum = monitor->request;
|
|
|
|
result = FAMCancelMonitor (&monitor->parent->priv->fam_connection, &fr);
|
|
|
|
if (result)
|
|
{
|
|
DEBUG_PRINT ("Connection %d: FAMCancelMonitor for %s '%s' failed",
|
|
monitor->parent->priv->fam_connection.fd,
|
|
monitor->type == MONITOR_DIR ? "directory" : "file",
|
|
monitor->filename);
|
|
}
|
|
|
|
g_free (monitor->filename);
|
|
g_free (monitor);
|
|
}
|
|
|
|
|
|
static gboolean read_fam_events (G_GNUC_UNUSED GIOChannel *source,
|
|
GIOCondition condition,
|
|
MooFileWatch *watch)
|
|
{
|
|
GError *error = NULL;
|
|
int result;
|
|
FAMConnection *connection = &watch->priv->fam_connection;
|
|
gboolean retval = TRUE;
|
|
|
|
g_object_ref (watch);
|
|
|
|
if (condition & (G_IO_ERR | G_IO_HUP))
|
|
{
|
|
DEBUG_PRINT ("Connection %d: FAM socket broke",
|
|
connection->fd);
|
|
|
|
g_set_error (&error, MOO_FILE_WATCH_ERROR,
|
|
MOO_FILE_WATCH_ERROR_FAILED,
|
|
"Connection to FAM is broken");
|
|
g_signal_emit (watch, signals[ERROR_SIGNAL], 0, error);
|
|
g_error_free (error);
|
|
error = NULL;
|
|
|
|
if (!moo_file_watch_close (watch, &error))
|
|
{
|
|
DEBUG_PRINT ("%s: error in moo_file_watch_close()", G_STRLOC);
|
|
DEBUG_PRINT ("%s: %s", G_STRLOC, error->message);
|
|
g_error_free (error);
|
|
}
|
|
|
|
retval = FALSE;
|
|
goto out;
|
|
}
|
|
|
|
while ((result = FAMPending (connection)))
|
|
{
|
|
FAMEvent fe;
|
|
MooFileWatchEvent event;
|
|
gboolean emit = TRUE;
|
|
|
|
if (result < 0)
|
|
{
|
|
SET_FAM_ERROR (FAMPending, &error);
|
|
DEBUG_PRINT ("Connection %d: FAMPending failed", connection->fd);
|
|
}
|
|
else if (FAMNextEvent (connection, &fe) != 1)
|
|
{
|
|
SET_FAM_ERROR (FAMNextEvent, &error);
|
|
DEBUG_PRINT ("Connection %d: FAMNextEvent failed", connection->fd);
|
|
}
|
|
|
|
if (error)
|
|
{
|
|
g_signal_emit (watch, signals[ERROR_SIGNAL], 0, error);
|
|
g_error_free (error);
|
|
error = NULL;
|
|
|
|
if (!moo_file_watch_close (watch, &error))
|
|
{
|
|
DEBUG_PRINT ("%s: error in moo_file_watch_close()", G_STRLOC);
|
|
DEBUG_PRINT ("%s: %s", G_STRLOC, error->message);
|
|
g_error_free (error);
|
|
}
|
|
|
|
retval = FALSE;
|
|
goto out;
|
|
}
|
|
|
|
/* TODO: check monitor here */
|
|
event.monitor_id = fe.fr.reqnum;
|
|
event.filename = fe.filename;
|
|
event.data = fe.userdata;
|
|
|
|
switch (fe.code)
|
|
{
|
|
case FAMChanged:
|
|
event.code = MOO_FILE_WATCH_CHANGED;
|
|
break;
|
|
case FAMDeleted:
|
|
event.code = MOO_FILE_WATCH_DELETED;
|
|
break;
|
|
case FAMCreated:
|
|
event.code = MOO_FILE_WATCH_CREATED;
|
|
break;
|
|
case FAMMoved:
|
|
event.code = MOO_FILE_WATCH_MOVED;
|
|
break;
|
|
|
|
case FAMStartExecuting:
|
|
case FAMStopExecuting:
|
|
case FAMAcknowledge:
|
|
case FAMExists:
|
|
case FAMEndExist:
|
|
emit = FALSE;
|
|
break;
|
|
|
|
default:
|
|
emit = FALSE;
|
|
g_warning ("%s: unknown FAM code %d", G_STRLOC, fe.code);
|
|
}
|
|
|
|
if (emit)
|
|
{
|
|
emit_event (watch, &event);
|
|
}
|
|
}
|
|
|
|
out:
|
|
g_object_unref (watch);
|
|
return retval;
|
|
}
|
|
|
|
|
|
#endif /* MOO_USE_FAM */
|
|
|
|
|
|
/*****************************************************************************/
|
|
/* stat()
|
|
*/
|
|
|
|
#define MOO_STAT_PRIORITY G_PRIORITY_DEFAULT
|
|
#define MOO_STAT_TIMEOUT 500
|
|
|
|
static MooFileWatchError errno_to_file_error (int code);
|
|
static gboolean do_stat (MooFileWatch *watch);
|
|
|
|
|
|
static gboolean watch_stat_start (MooFileWatch *watch,
|
|
G_GNUC_UNUSED GError **error)
|
|
{
|
|
watch->priv->stat_timeout =
|
|
g_timeout_add_full (MOO_STAT_PRIORITY,
|
|
MOO_STAT_TIMEOUT,
|
|
(GSourceFunc) do_stat,
|
|
watch, NULL);
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
static gboolean watch_stat_shutdown (MooFileWatch *watch,
|
|
G_GNUC_UNUSED GError **error)
|
|
{
|
|
if (watch->priv->stat_timeout)
|
|
g_source_remove (watch->priv->stat_timeout);
|
|
watch->priv->stat_timeout = 0;
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
static Monitor *monitor_stat_create (MooFileWatch *watch,
|
|
MonitorType type,
|
|
const char *filename,
|
|
gpointer data,
|
|
int *request,
|
|
GError **error)
|
|
{
|
|
Monitor *monitor;
|
|
struct stat buf;
|
|
|
|
g_return_val_if_fail (MOO_IS_FILE_WATCH (watch), NULL);
|
|
g_return_val_if_fail (filename != NULL, NULL);
|
|
|
|
if (stat (filename, &buf))
|
|
{
|
|
int saved_errno = errno;
|
|
g_set_error (error, MOO_FILE_WATCH_ERROR,
|
|
errno_to_file_error (saved_errno),
|
|
"stat: %s", g_strerror (saved_errno));
|
|
return NULL;
|
|
}
|
|
|
|
if (type == MONITOR_DIR && !S_ISDIR (buf.st_mode))
|
|
{
|
|
char *display_name = g_filename_display_name (filename);
|
|
g_set_error (error, MOO_FILE_WATCH_ERROR,
|
|
MOO_FILE_WATCH_ERROR_NOT_DIR,
|
|
"%s is not a directory", display_name);
|
|
g_free (display_name);
|
|
return NULL;
|
|
}
|
|
else if (type == MONITOR_FILE && S_ISDIR (buf.st_mode)) /* it's fatal on windows */
|
|
{
|
|
char *display_name = g_filename_display_name (filename);
|
|
g_set_error (error, MOO_FILE_WATCH_ERROR,
|
|
MOO_FILE_WATCH_ERROR_IS_DIR,
|
|
"%s is a directory", display_name);
|
|
g_free (display_name);
|
|
return NULL;
|
|
}
|
|
|
|
DEBUG_PRINT ("created monitor for '%s'", filename);
|
|
|
|
monitor = g_new0 (Monitor, 1);
|
|
monitor->parent = watch;
|
|
monitor->type = type;
|
|
monitor->filename = g_strdup (filename);
|
|
monitor->user_data = data;
|
|
monitor->request = ++last_monitor_id;
|
|
monitor->suspended = FALSE;
|
|
|
|
memcpy (&monitor->statbuf, &buf, sizeof (buf));
|
|
|
|
if (request)
|
|
*request = monitor->request;
|
|
|
|
return monitor;
|
|
}
|
|
|
|
|
|
static void monitor_stat_suspend (Monitor *monitor)
|
|
{
|
|
g_return_if_fail (monitor != NULL);
|
|
monitor->suspended = TRUE;
|
|
}
|
|
|
|
|
|
static void monitor_stat_resume (Monitor *monitor)
|
|
{
|
|
g_return_if_fail (monitor != NULL);
|
|
monitor->suspended = FALSE;
|
|
}
|
|
|
|
|
|
static void monitor_stat_delete (Monitor *monitor)
|
|
{
|
|
g_return_if_fail (monitor != NULL);
|
|
DEBUG_PRINT ("removing monitor for '%s'", monitor->filename);
|
|
g_free (monitor->filename);
|
|
g_free (monitor);
|
|
}
|
|
|
|
|
|
static gboolean do_stat (MooFileWatch *watch)
|
|
{
|
|
GSList *l, *list, *to_remove = NULL;
|
|
gboolean result = TRUE;
|
|
|
|
g_return_val_if_fail (MOO_IS_FILE_WATCH (watch), FALSE);
|
|
|
|
g_object_ref (watch);
|
|
|
|
if (!watch->priv->monitors)
|
|
goto out;
|
|
|
|
list = g_slist_copy (watch->priv->monitors);
|
|
|
|
for (l = list; l != NULL; l = l->next)
|
|
{
|
|
MooFileWatchEvent event;
|
|
Monitor *monitor = l->data;
|
|
time_t old;
|
|
|
|
g_assert (monitor != NULL);
|
|
|
|
if (!g_hash_table_lookup (watch->priv->requests,
|
|
GINT_TO_POINTER (monitor->request)))
|
|
continue;
|
|
|
|
if (monitor->suspended)
|
|
continue;
|
|
|
|
old = monitor->statbuf.st_mtime;
|
|
|
|
if (stat (monitor->filename, &monitor->statbuf))
|
|
{
|
|
event.code = MOO_FILE_WATCH_DELETED;
|
|
event.monitor_id = monitor->request;
|
|
event.filename = monitor->filename;
|
|
event.data = monitor->user_data;
|
|
|
|
DEBUG_PRINT ("'%s' deleted", monitor->filename);
|
|
|
|
to_remove = g_slist_prepend (to_remove,
|
|
GINT_TO_POINTER (monitor->request));
|
|
|
|
emit_event (watch, &event);
|
|
}
|
|
else if (monitor->statbuf.st_mtime > old)
|
|
{
|
|
event.code = MOO_FILE_WATCH_CHANGED;
|
|
event.monitor_id = monitor->request;
|
|
event.filename = monitor->filename;
|
|
event.data = monitor->user_data;
|
|
|
|
DEBUG_PRINT ("'%s' changed", monitor->filename);
|
|
|
|
emit_event (watch, &event);
|
|
}
|
|
}
|
|
|
|
for (l = to_remove; l != NULL; l = l->next)
|
|
if (g_hash_table_lookup (watch->priv->requests, GINT_TO_POINTER (l->data)))
|
|
moo_file_watch_cancel_monitor (watch, GPOINTER_TO_INT (l->data));
|
|
|
|
g_slist_free (to_remove);
|
|
g_slist_free (list);
|
|
|
|
out:
|
|
g_object_unref (watch);
|
|
return result;
|
|
}
|
|
|
|
|
|
static MooFileWatchError errno_to_file_error (int code)
|
|
{
|
|
MooFileWatchError fcode = MOO_FILE_WATCH_ERROR_FAILED;
|
|
|
|
switch (code)
|
|
{
|
|
case EACCES:
|
|
fcode = MOO_FILE_WATCH_ERROR_ACCESS_DENIED;
|
|
break;
|
|
case ENAMETOOLONG:
|
|
fcode = MOO_FILE_WATCH_ERROR_BAD_FILENAME;
|
|
break;
|
|
case ENOENT:
|
|
fcode = MOO_FILE_WATCH_ERROR_NONEXISTENT;
|
|
break;
|
|
case ENOTDIR:
|
|
fcode = MOO_FILE_WATCH_ERROR_NOT_DIR;
|
|
break;
|
|
}
|
|
|
|
return fcode;
|
|
}
|
|
|
|
|
|
|
|
/*****************************************************************************/
|
|
/* win32
|
|
*/
|
|
#ifdef __WIN32__
|
|
|
|
static gboolean watch_win32_start (MooFileWatch *watch,
|
|
GError **error)
|
|
{
|
|
return watch_stat_start (watch, error);
|
|
}
|
|
|
|
|
|
static gboolean watch_win32_shutdown (MooFileWatch *watch,
|
|
GError **error)
|
|
{
|
|
return watch_stat_shutdown (watch, error);
|
|
}
|
|
|
|
|
|
static Monitor *monitor_win32_create (MooFileWatch *watch,
|
|
MonitorType type,
|
|
const char *filename,
|
|
gpointer data,
|
|
int *request,
|
|
GError **error)
|
|
{
|
|
g_return_val_if_fail (filename != NULL, FALSE);
|
|
|
|
if (type == MONITOR_FILE)
|
|
return monitor_stat_create (watch, type, filename, data, request, error);
|
|
|
|
#warning "Implement me"
|
|
g_set_error (error, MOO_FILE_WATCH_ERROR,
|
|
MOO_FILE_WATCH_ERROR_NOT_IMPLEMENTED,
|
|
"watching folders is not implemented on win32");
|
|
return NULL;
|
|
}
|
|
|
|
|
|
static void monitor_win32_suspend (Monitor *monitor)
|
|
{
|
|
g_return_if_fail (monitor != NULL);
|
|
|
|
if (monitor->suspended)
|
|
return;
|
|
|
|
if (monitor->type == MONITOR_FILE)
|
|
return monitor_stat_suspend (monitor);
|
|
|
|
g_return_if_reached ();
|
|
}
|
|
|
|
|
|
static void monitor_win32_resume (Monitor *monitor)
|
|
{
|
|
g_return_if_fail (monitor != NULL);
|
|
|
|
if (!monitor->suspended)
|
|
return;
|
|
|
|
if (monitor->type == MONITOR_FILE)
|
|
return monitor_stat_resume (monitor);
|
|
|
|
g_return_if_reached ();
|
|
}
|
|
|
|
|
|
static void monitor_win32_delete (Monitor *monitor)
|
|
{
|
|
g_return_if_fail (monitor != NULL);
|
|
|
|
if (monitor->type == MONITOR_FILE)
|
|
return monitor_stat_delete (monitor);
|
|
|
|
g_return_if_reached ();
|
|
}
|
|
#endif /* __WIN32__ */
|