1996 lines
59 KiB
C
1996 lines
59 KiB
C
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4; coding: utf-8 -*-
|
|
* moofilewatch.h
|
|
*
|
|
* Copyright (C) 2004-2005 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 "moofilewatch.h"
|
|
#include MOO_MARSHALS_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;
|
|
#ifdef __WIN32__
|
|
typedef struct _WinFamConnection WinFamConnection;
|
|
#endif
|
|
|
|
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
|
|
#ifdef __WIN32__
|
|
WinFamConnection *fam_connection;
|
|
guint fam_timeout;
|
|
#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;
|
|
}
|
|
|
|
|
|
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__
|
|
|
|
/* For directories it uses FindFirstChangeNotification and friends; for
|
|
files - stat() method. */
|
|
|
|
typedef MooFileWatchEvent WinFamEvent;
|
|
|
|
typedef struct {
|
|
WinFamConnection *fc;
|
|
int reqnum;
|
|
gboolean suspended;
|
|
gpointer user_data;
|
|
char *filename;
|
|
} Request;
|
|
|
|
static WinFamEvent *win_fam_event_new (MooFileWatchEventCode code,
|
|
Request *request);
|
|
static void win_fam_event_free (WinFamEvent *fe);
|
|
|
|
struct _WinFamConnection {
|
|
GAsyncQueue *events;
|
|
};
|
|
|
|
|
|
/* Starts WinFam and creates new connection to it. Returned structure
|
|
must not be modified or freed, it must be passed to win_fam_close(). */
|
|
static WinFamConnection *win_fam_open (GError **error);
|
|
|
|
/* Removes given connection and stops WinFam if it's the last connection.
|
|
fc structure is freed, so it must not be accessed after win_fam_close()
|
|
call, regardless of returned status. */
|
|
static gboolean win_fam_close (WinFamConnection *fc,
|
|
GError **error);
|
|
|
|
static gboolean win_fam_monitor_directory (WinFamConnection *fc,
|
|
const char *filename,
|
|
int *request,
|
|
gpointer user_data,
|
|
GError **error);
|
|
|
|
static gboolean win_fam_suspend_monitor (WinFamConnection *fc,
|
|
int request,
|
|
GError **error);
|
|
static gboolean win_fam_resume_monitor (WinFamConnection *fc,
|
|
int request,
|
|
GError **error);
|
|
static gboolean win_fam_cancel_monitor (WinFamConnection *fc,
|
|
int request,
|
|
GError **error);
|
|
|
|
|
|
/*****************************************************************************/
|
|
/* MooFileWatch
|
|
*/
|
|
|
|
#define MOO_WIN_FAM_WATCH_PRIORITY G_PRIORITY_DEFAULT
|
|
#define MOO_WIN_FAM_WATCH_TIMEOUT 500
|
|
|
|
|
|
static gboolean check_fam_events (MooFileWatch *watch);
|
|
|
|
static gboolean watch_win32_start (MooFileWatch *watch,
|
|
GError **error)
|
|
{
|
|
watch->priv->fam_connection = win_fam_open (error);
|
|
|
|
if (!watch->priv->fam_connection)
|
|
return FALSE;
|
|
|
|
watch->priv->fam_timeout =
|
|
g_timeout_add_full (MOO_WIN_FAM_WATCH_TIMEOUT,
|
|
MOO_WIN_FAM_WATCH_PRIORITY,
|
|
(GSourceFunc) check_fam_events, watch, NULL);
|
|
|
|
return watch_stat_start (watch, error);
|
|
}
|
|
|
|
|
|
static gboolean watch_win32_shutdown (MooFileWatch *watch,
|
|
GError **error)
|
|
{
|
|
WinFamConnection *fc = watch->priv->fam_connection;
|
|
gboolean result;
|
|
|
|
watch->priv->fam_connection = NULL;
|
|
|
|
if (watch->priv->fam_timeout)
|
|
g_source_remove (watch->priv->fam_timeout);
|
|
|
|
result = win_fam_close (fc, error);
|
|
watch_stat_shutdown (watch, NULL);
|
|
return result;
|
|
}
|
|
|
|
|
|
static Monitor *monitor_win32_create (MooFileWatch *watch,
|
|
MonitorType type,
|
|
const char *filename,
|
|
gpointer data,
|
|
int *request,
|
|
GError **error)
|
|
{
|
|
int reqnum;
|
|
gboolean result;
|
|
Monitor *monitor;
|
|
|
|
g_return_val_if_fail (filename != NULL, FALSE);
|
|
|
|
if (type == MONITOR_FILE)
|
|
return monitor_stat_create (watch, type, filename, data, request, error);
|
|
|
|
result = win_fam_monitor_directory (watch->priv->fam_connection,
|
|
filename, &reqnum, data, error);
|
|
|
|
if (!result)
|
|
{
|
|
DEBUG_PRINT ("Creating monitor for '%s' failed", filename);
|
|
return FALSE;
|
|
}
|
|
else
|
|
{
|
|
DEBUG_PRINT ("Created monitor %d for %s '%s'", reqnum,
|
|
type == MONITOR_DIR ? "directory" : "file",
|
|
filename);
|
|
}
|
|
|
|
if (request)
|
|
*request = reqnum;
|
|
|
|
monitor = g_new0 (Monitor, 1);
|
|
|
|
monitor->parent = watch;
|
|
monitor->type = type;
|
|
monitor->filename = g_strdup (filename);
|
|
monitor->user_data = data;
|
|
monitor->request = reqnum;
|
|
monitor->suspended = FALSE;
|
|
|
|
return monitor;
|
|
}
|
|
|
|
|
|
static void monitor_win32_suspend (Monitor *monitor)
|
|
{
|
|
int result;
|
|
|
|
g_return_if_fail (monitor != NULL);
|
|
|
|
if (monitor->suspended)
|
|
return;
|
|
|
|
if (monitor->type == MONITOR_FILE)
|
|
return monitor_stat_suspend (monitor);
|
|
|
|
result = win_fam_suspend_monitor (monitor->parent->priv->fam_connection,
|
|
monitor->request, NULL);
|
|
|
|
if (!result)
|
|
{
|
|
DEBUG_PRINT ("win_fam_suspend_monitor for %s '%s' failed",
|
|
monitor->type == MONITOR_DIR ? "directory" : "file",
|
|
monitor->filename);
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
DEBUG_PRINT ("Suspended monitor %d for %s '%s'", monitor->request,
|
|
monitor->type == MONITOR_DIR ? "directory" : "file",
|
|
monitor->filename);
|
|
}
|
|
|
|
monitor->suspended = TRUE;
|
|
}
|
|
|
|
|
|
static void monitor_win32_resume (Monitor *monitor)
|
|
{
|
|
gboolean result;
|
|
|
|
g_return_if_fail (monitor != NULL);
|
|
|
|
if (!monitor->suspended)
|
|
return;
|
|
|
|
if (monitor->type == MONITOR_FILE)
|
|
return monitor_stat_resume (monitor);
|
|
|
|
result = win_fam_resume_monitor (monitor->parent->priv->fam_connection,
|
|
monitor->request, NULL);
|
|
|
|
if (!result)
|
|
{
|
|
DEBUG_PRINT ("win_fam_resume_monitor for %s '%s' failed",
|
|
monitor->type == MONITOR_DIR ? "directory" : "file",
|
|
monitor->filename);
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
DEBUG_PRINT ("Resumed monitor %d for %s '%s'", monitor->request,
|
|
monitor->type == MONITOR_DIR ? "directory" : "file",
|
|
monitor->filename);
|
|
}
|
|
|
|
monitor->suspended = FALSE;
|
|
}
|
|
|
|
|
|
static void monitor_win32_delete (Monitor *monitor)
|
|
{
|
|
int result;
|
|
|
|
g_return_if_fail (monitor != NULL);
|
|
|
|
if (monitor->type == MONITOR_FILE)
|
|
return monitor_stat_delete (monitor);
|
|
|
|
result = win_fam_cancel_monitor (monitor->parent->priv->fam_connection,
|
|
monitor->request, NULL);
|
|
|
|
if (!result)
|
|
{
|
|
DEBUG_PRINT ("win_fam_cancel_monitor for %s '%s' failed",
|
|
monitor->type == MONITOR_DIR ? "directory" : "file",
|
|
monitor->filename);
|
|
}
|
|
|
|
g_free (monitor->filename);
|
|
g_free (monitor);
|
|
}
|
|
|
|
|
|
static gboolean check_fam_events (MooFileWatch *watch)
|
|
{
|
|
GError *error = NULL;
|
|
WinFamConnection *connection = watch->priv->fam_connection;
|
|
GAsyncQueue *queue = connection->events;
|
|
gboolean retval = TRUE;
|
|
|
|
g_object_ref (watch);
|
|
|
|
while (g_async_queue_length (queue))
|
|
{
|
|
WinFamEvent *event = g_async_queue_pop (queue);
|
|
|
|
if (!event)
|
|
{
|
|
g_set_error (&error, MOO_FILE_WATCH_ERROR,
|
|
MOO_FILE_WATCH_ERROR_FAILED, "Failed");
|
|
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;
|
|
}
|
|
|
|
emit_event (watch, event);
|
|
win_fam_event_free (event);
|
|
}
|
|
|
|
out:
|
|
g_object_unref (watch);
|
|
return retval;
|
|
}
|
|
|
|
|
|
|
|
/*****************************************************************************/
|
|
/* WinFam - aka FAM on windows
|
|
*/
|
|
|
|
typedef enum {
|
|
SIGNAL_NEW_CONNECTION = 1,
|
|
SIGNAL_CLOSE_CONNECTION = 2,
|
|
SIGNAL_MONITOR = 3,
|
|
SIGNAL_SUSPEND_MONITOR = 4,
|
|
SIGNAL_RESUME_MONITOR = 5,
|
|
SIGNAL_CANCEL_MONITOR = 6
|
|
} SignalCode;
|
|
|
|
typedef struct {
|
|
GError **error;
|
|
} SignalNew;
|
|
|
|
typedef struct {
|
|
GError **error;
|
|
WinFamConnection *fc;
|
|
} SignalClose;
|
|
|
|
typedef struct {
|
|
GError **error;
|
|
WinFamConnection *fc;
|
|
const char *filename;
|
|
int request;
|
|
gpointer user_data;
|
|
} SignalMonitor;
|
|
|
|
typedef struct {
|
|
GError **error;
|
|
WinFamConnection *fc;
|
|
int request;
|
|
} SignalModifyMonitor;
|
|
|
|
typedef union {
|
|
SignalNew new;
|
|
SignalClose close;
|
|
SignalMonitor monitor;
|
|
SignalModifyMonitor modify_monitor;
|
|
} Signal;
|
|
|
|
typedef struct {
|
|
HANDLE signal;
|
|
SignalCode signal_code;
|
|
Signal signal_data;
|
|
GAsyncQueue *answer;
|
|
} WinFam;
|
|
|
|
/* Single WinFam instance */
|
|
static WinFam *fam_instance = NULL;
|
|
// static GMutex *fam_mutex = NULL;
|
|
|
|
static gpointer fam_thread_main (WinFam *fam);
|
|
|
|
/* checks if fam is started */
|
|
static gboolean win_fam_start (GError **error)
|
|
{
|
|
GThread *thread;
|
|
|
|
if (fam_instance != NULL)
|
|
return TRUE;
|
|
|
|
fam_instance = g_new0 (WinFam, 1);
|
|
|
|
fam_instance->signal = CreateEvent (NULL, FALSE, FALSE, NULL);
|
|
|
|
if (!fam_instance->signal)
|
|
{
|
|
int err = GetLastError ();
|
|
char *msg = g_win32_error_message (err);
|
|
g_set_error (error, MOO_FILE_WATCH_ERROR,
|
|
MOO_FILE_WATCH_ERROR_FAILED,
|
|
"CreateEvent: %s", msg);
|
|
g_free (msg);
|
|
goto error;
|
|
}
|
|
|
|
fam_instance->answer = g_async_queue_new ();
|
|
|
|
thread = g_thread_create ((GThreadFunc) fam_thread_main,
|
|
fam_instance, FALSE, error);
|
|
if (!thread)
|
|
goto error;
|
|
|
|
return TRUE;
|
|
|
|
error:
|
|
if (fam_instance)
|
|
{
|
|
if (fam_instance->signal)
|
|
CloseHandle (fam_instance->signal);
|
|
if (fam_instance->answer)
|
|
g_async_queue_unref (fam_instance->answer);
|
|
g_free (fam_instance);
|
|
fam_instance = NULL;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
static gboolean win_fam_signal (GError **error)
|
|
{
|
|
g_assert (fam_instance != NULL);
|
|
|
|
if (!SetEvent (fam_instance->signal))
|
|
{
|
|
int err = GetLastError ();
|
|
char *msg = g_win32_error_message (err);
|
|
fam_instance->signal_code = 0;
|
|
g_set_error (error, MOO_FILE_WATCH_ERROR,
|
|
MOO_FILE_WATCH_ERROR_FAILED,
|
|
"SetEvent: %s", msg);
|
|
g_free (msg);
|
|
return FALSE;
|
|
}
|
|
else
|
|
{
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
|
|
static WinFamConnection *win_fam_open (GError **error)
|
|
{
|
|
if (!win_fam_start (error))
|
|
return NULL;
|
|
|
|
fam_instance->signal_code = SIGNAL_NEW_CONNECTION;
|
|
fam_instance->signal_data.new.error = error;
|
|
|
|
if (!win_fam_signal (error))
|
|
return NULL;
|
|
|
|
return g_async_queue_pop (fam_instance->answer);
|
|
}
|
|
|
|
|
|
static gboolean win_fam_close (WinFamConnection *fc,
|
|
GError **error)
|
|
{
|
|
gpointer result;
|
|
|
|
g_return_val_if_fail (fc != NULL, FALSE);
|
|
g_return_val_if_fail (fam_instance != NULL, FALSE);
|
|
|
|
fam_instance->signal_code = SIGNAL_CLOSE_CONNECTION;
|
|
fam_instance->signal_data.close.error = error;
|
|
fam_instance->signal_data.close.fc = fc;
|
|
|
|
if (!win_fam_signal (error))
|
|
return FALSE;
|
|
|
|
result = g_async_queue_pop (fam_instance->answer);
|
|
return GPOINTER_TO_INT (result);
|
|
}
|
|
|
|
|
|
static gboolean win_fam_monitor_directory (WinFamConnection *fc,
|
|
const char *filename,
|
|
int *request,
|
|
gpointer user_data,
|
|
GError **error)
|
|
{
|
|
gpointer result;
|
|
|
|
g_return_val_if_fail (fc != NULL, FALSE);
|
|
g_return_val_if_fail (fam_instance != NULL, FALSE);
|
|
|
|
fam_instance->signal_code = SIGNAL_MONITOR;
|
|
fam_instance->signal_data.monitor.error = error;
|
|
fam_instance->signal_data.monitor.fc = fc;
|
|
fam_instance->signal_data.monitor.filename = filename;
|
|
fam_instance->signal_data.monitor.user_data = user_data;
|
|
|
|
if (!win_fam_signal (error))
|
|
return FALSE;
|
|
|
|
result = g_async_queue_pop (fam_instance->answer);
|
|
|
|
if (result)
|
|
{
|
|
if (request)
|
|
*request = fam_instance->signal_data.monitor.request;
|
|
return TRUE;
|
|
}
|
|
else
|
|
{
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
|
|
static gboolean win_fam_modify_monitor (WinFamConnection *fc,
|
|
int request,
|
|
SignalCode code,
|
|
GError **error)
|
|
{
|
|
gpointer result;
|
|
|
|
g_return_val_if_fail (fc != NULL, FALSE);
|
|
g_return_val_if_fail (fam_instance != NULL, FALSE);
|
|
|
|
fam_instance->signal_code = code;
|
|
fam_instance->signal_data.modify_monitor.error = error;
|
|
fam_instance->signal_data.modify_monitor.fc = fc;
|
|
fam_instance->signal_data.modify_monitor.request = request;
|
|
|
|
if (!win_fam_signal (error))
|
|
return FALSE;
|
|
|
|
result = g_async_queue_pop (fam_instance->answer);
|
|
return GPOINTER_TO_INT (result);
|
|
}
|
|
|
|
|
|
static gboolean win_fam_suspend_monitor (WinFamConnection *fc,
|
|
int request,
|
|
GError **error)
|
|
{
|
|
return win_fam_modify_monitor (fc, request, SIGNAL_SUSPEND_MONITOR, error);
|
|
}
|
|
|
|
|
|
static gboolean win_fam_resume_monitor (WinFamConnection *fc,
|
|
int request,
|
|
GError **error)
|
|
{
|
|
return win_fam_modify_monitor (fc, request, SIGNAL_RESUME_MONITOR, error);
|
|
}
|
|
|
|
|
|
static gboolean win_fam_cancel_monitor (WinFamConnection *fc,
|
|
int request,
|
|
GError **error)
|
|
{
|
|
return win_fam_modify_monitor (fc, request, SIGNAL_CANCEL_MONITOR, error);
|
|
}
|
|
|
|
|
|
/*****************************************************************************/
|
|
/* Monitoring thread
|
|
*/
|
|
|
|
typedef struct {
|
|
GHashTable *handles;
|
|
GSList *connections;
|
|
GPtrArray *objects;
|
|
} WinFamData;
|
|
|
|
static void fam_thread_process_signal (WinFam *fam,
|
|
WinFamData *data);
|
|
static void fam_thread_process_event (WinFam *fam,
|
|
WinFamData *data,
|
|
int obj_no);
|
|
|
|
static gpointer fam_thread_main (WinFam *fam)
|
|
{
|
|
WinFamData data;
|
|
|
|
data.objects = g_ptr_array_new ();
|
|
g_ptr_array_add (data.objects, fam->signal);
|
|
|
|
data.handles = g_hash_table_new (g_direct_hash, g_direct_equal);
|
|
data.connections = NULL;
|
|
|
|
while (TRUE)
|
|
{
|
|
int ret = WaitForMultipleObjects (data.objects->len,
|
|
data.objects->pdata,
|
|
FALSE, INFINITE);
|
|
|
|
if (ret == WAIT_FAILED)
|
|
{
|
|
/* die */
|
|
guint i;
|
|
for (i = 1; i < data.objects->len; ++i)
|
|
CloseHandle (data.objects->pdata[i]);
|
|
g_ptr_array_set_size (data.objects, 1);
|
|
return NULL;
|
|
}
|
|
|
|
if (ret >= WAIT_OBJECT_0 + data.objects->len)
|
|
{
|
|
/* ??? */
|
|
guint i;
|
|
for (i = 1; i < data.objects->len; ++i)
|
|
CloseHandle (data.objects->pdata[i]);
|
|
g_ptr_array_set_size (data.objects, 1);
|
|
return NULL;
|
|
}
|
|
|
|
if (ret == WAIT_OBJECT_0)
|
|
fam_thread_process_signal (fam, &data);
|
|
else
|
|
fam_thread_process_event (fam, &data, ret - WAIT_OBJECT_0);
|
|
}
|
|
}
|
|
|
|
|
|
// typedef enum {
|
|
// SIGNAL_NEW_CONNECTION = 1,
|
|
// SIGNAL_CLOSE_CONNECTION = 2,
|
|
// SIGNAL_MONITOR = 3,
|
|
// SIGNAL_SUSPEND_MONITOR = 4,
|
|
// SIGNAL_RESUME_MONITOR = 5,
|
|
// SIGNAL_CANCEL_MONITOR = 6
|
|
// } SignalCode;
|
|
//
|
|
// typedef struct {
|
|
// GError **error;
|
|
// } SignalNew;
|
|
//
|
|
// typedef struct {
|
|
// GError **error;
|
|
// WinFamConnection *fc;
|
|
// } SignalClose;
|
|
//
|
|
// typedef struct {
|
|
// GError **error;
|
|
// MonitorType type;
|
|
// WinFamConnection *fc;
|
|
// const char *filename;
|
|
// int *request;
|
|
// gpointer user_data;
|
|
// } SignalMonitor;
|
|
//
|
|
// typedef struct {
|
|
// GError **error;
|
|
// WinFamConnection *fc;
|
|
// int request;
|
|
// } SignalModifyMonitor;
|
|
//
|
|
// typedef union {
|
|
// SignalNew new;
|
|
// SignalClose close;
|
|
// SignalMonitor monitor;
|
|
// SignalModifyMonitor modify_monitor;
|
|
// } Signal;
|
|
//
|
|
// typedef struct {
|
|
// HANDLE signal;
|
|
// SignalCode signal_code;
|
|
// Signal signal_data;
|
|
// GAsyncQueue *answer;
|
|
// } WinFam;
|
|
|
|
static void fam_thread_delete_handle (WinFamData *data,
|
|
HANDLE monitor);
|
|
static void fam_thread_new_connection (WinFamData *data,
|
|
SignalNew *signal,
|
|
GAsyncQueue *answer);
|
|
static void fam_thread_close_connection (WinFamData *data,
|
|
SignalClose *signal,
|
|
GAsyncQueue *answer);
|
|
static void fam_thread_create_monitor (WinFamData *data,
|
|
SignalMonitor *signal,
|
|
GAsyncQueue *answer);
|
|
static void fam_thread_suspend_monitor (WinFamData *data,
|
|
SignalModifyMonitor *signal,
|
|
GAsyncQueue *answer);
|
|
static void fam_thread_resume_monitor (WinFamData *data,
|
|
SignalModifyMonitor *signal,
|
|
GAsyncQueue *answer);
|
|
static void fam_thread_cancel_monitor (WinFamData *data,
|
|
SignalModifyMonitor *signal,
|
|
GAsyncQueue *answer);
|
|
|
|
|
|
static void fam_thread_process_signal (WinFam *fam,
|
|
WinFamData *data)
|
|
{
|
|
switch (fam->signal_code)
|
|
{
|
|
case SIGNAL_NEW_CONNECTION:
|
|
return fam_thread_new_connection (data, &fam->signal_data.new,
|
|
fam->answer);
|
|
|
|
case SIGNAL_CLOSE_CONNECTION:
|
|
return fam_thread_close_connection (data, &fam->signal_data.close,
|
|
fam->answer);
|
|
|
|
case SIGNAL_MONITOR:
|
|
return fam_thread_create_monitor (data, &fam->signal_data.monitor,
|
|
fam->answer);
|
|
|
|
case SIGNAL_SUSPEND_MONITOR:
|
|
return fam_thread_suspend_monitor (data, &fam->signal_data.modify_monitor,
|
|
fam->answer);
|
|
|
|
case SIGNAL_RESUME_MONITOR:
|
|
return fam_thread_resume_monitor (data, &fam->signal_data.modify_monitor,
|
|
fam->answer);
|
|
|
|
case SIGNAL_CANCEL_MONITOR:
|
|
return fam_thread_cancel_monitor (data, &fam->signal_data.modify_monitor,
|
|
fam->answer);
|
|
|
|
default:
|
|
g_warning ("%s: unknown signal code %d",
|
|
G_STRLOC, fam->signal_code);
|
|
g_async_queue_push (fam->answer, NULL);
|
|
}
|
|
}
|
|
|
|
|
|
static void fam_thread_new_connection (WinFamData *data,
|
|
SignalNew *signal,
|
|
GAsyncQueue *answer)
|
|
{
|
|
WinFamConnection *fc = g_new0 (WinFamConnection, 1);
|
|
fc->events = g_async_queue_new ();
|
|
data->connections = g_slist_prepend (data->connections, fc);
|
|
g_clear_error (signal->error);
|
|
g_async_queue_push (answer, fc);
|
|
}
|
|
|
|
|
|
static void fam_thread_close_connection (WinFamData *data,
|
|
SignalClose *signal,
|
|
GAsyncQueue *answer)
|
|
{
|
|
guint i;
|
|
GSList *list = NULL, *l;
|
|
WinFamConnection *fc = signal->fc;
|
|
|
|
if (!g_slist_find (data->connections, fc))
|
|
{
|
|
g_set_error (signal->error,
|
|
MOO_FILE_WATCH_ERROR,
|
|
MOO_FILE_WATCH_ERROR_FAILED,
|
|
"connection not found");
|
|
g_async_queue_push (answer, GINT_TO_POINTER (FALSE));
|
|
return;
|
|
}
|
|
|
|
data->connections = g_slist_remove (data->connections, fc);
|
|
|
|
for (i = 1; i < data->objects->len; i++)
|
|
{
|
|
Request *req = g_hash_table_lookup (data->handles,
|
|
data->objects->pdata[i]);
|
|
g_assert (req != NULL);
|
|
if (req->fc == fc)
|
|
list = g_slist_prepend (list, data->objects->pdata[i]);
|
|
}
|
|
|
|
if (list)
|
|
{
|
|
g_warning ("%s: closing FAM connection with open requests",
|
|
G_STRLOC);
|
|
|
|
for (l = list; l != NULL; l = l->next)
|
|
fam_thread_delete_handle (data, l->data);
|
|
g_slist_free (list);
|
|
}
|
|
|
|
while (g_async_queue_length (fc->events))
|
|
win_fam_event_free (g_async_queue_pop (fc->events));
|
|
|
|
g_async_queue_unref (fc->events);
|
|
g_free (fc);
|
|
g_clear_error (signal->error);
|
|
g_async_queue_push (answer, GINT_TO_POINTER (TRUE));
|
|
}
|
|
|
|
|
|
static void fam_thread_create_monitor (WinFamData *data,
|
|
SignalMonitor *signal,
|
|
GAsyncQueue *answer)
|
|
{
|
|
HANDLE handle;
|
|
Request *request;
|
|
WinFamConnection *fc = signal->fc;
|
|
|
|
if (!g_slist_find (data->connections, fc))
|
|
{
|
|
g_set_error (signal->error,
|
|
MOO_FILE_WATCH_ERROR,
|
|
MOO_FILE_WATCH_ERROR_FAILED,
|
|
"connection not found");
|
|
g_async_queue_push (answer, GINT_TO_POINTER (FALSE));
|
|
return;
|
|
}
|
|
|
|
/* TODO: this really sucks */
|
|
if (data->objects->len == MAXIMUM_WAIT_OBJECTS)
|
|
{
|
|
g_set_error (signal->error,
|
|
MOO_FILE_WATCH_ERROR,
|
|
MOO_FILE_WATCH_ERROR_TOO_MANY,
|
|
"maximum number of monitored files reached");
|
|
g_async_queue_push (answer, GINT_TO_POINTER (FALSE));
|
|
return;
|
|
}
|
|
|
|
/* TODO: filename is utf8 */
|
|
handle = FindFirstChangeNotification (signal->filename, FALSE,
|
|
FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_DIR_NAME |
|
|
FILE_NOTIFY_CHANGE_SIZE | FILE_NOTIFY_CHANGE_LAST_WRITE);
|
|
|
|
if (handle == INVALID_HANDLE_VALUE)
|
|
{
|
|
int err = GetLastError ();
|
|
char *msg = g_win32_error_message (err);
|
|
g_set_error (signal->error,
|
|
MOO_FILE_WATCH_ERROR,
|
|
MOO_FILE_WATCH_ERROR_FAILED,
|
|
"FindFirstChangeNotification: %s", msg);
|
|
g_async_queue_push (answer, GINT_TO_POINTER (FALSE));
|
|
g_free (msg);
|
|
return;
|
|
}
|
|
|
|
request = g_new0 (Request, 1);
|
|
request->fc = fc;
|
|
request->reqnum = ++last_monitor_id;
|
|
request->suspended = FALSE;
|
|
request->user_data = signal->user_data;
|
|
request->filename = g_strdup (signal->filename);
|
|
|
|
g_hash_table_insert (data->handles, handle, request);
|
|
g_ptr_array_add (data->objects, handle);
|
|
|
|
g_clear_error (signal->error);
|
|
signal->request = request->reqnum;
|
|
|
|
g_async_queue_push (answer, GINT_TO_POINTER (TRUE));
|
|
}
|
|
|
|
|
|
static gboolean find_request (WinFamData *data,
|
|
WinFamConnection *fc,
|
|
int reqnum,
|
|
Request **request,
|
|
HANDLE *handle)
|
|
{
|
|
guint i;
|
|
|
|
for (i = 1; i < data->objects->len; ++i)
|
|
{
|
|
HANDLE h = data->objects->pdata[i];
|
|
Request *req = g_hash_table_lookup (data->handles, h);
|
|
g_assert (req != NULL);
|
|
if (req->fc == fc && req->reqnum == reqnum)
|
|
{
|
|
*request = req;
|
|
*handle = h;
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
static void fam_thread_suspend_monitor (WinFamData *data,
|
|
SignalModifyMonitor *signal,
|
|
GAsyncQueue *answer)
|
|
{
|
|
Request *request;
|
|
HANDLE handle;
|
|
|
|
if (!find_request (data, signal->fc, signal->request, &request, &handle))
|
|
{
|
|
g_set_error (signal->error,
|
|
MOO_FILE_WATCH_ERROR,
|
|
MOO_FILE_WATCH_ERROR_FAILED,
|
|
"No monitor with id %d",
|
|
signal->request);
|
|
g_async_queue_push (answer, GINT_TO_POINTER (FALSE));
|
|
return;
|
|
}
|
|
|
|
g_set_error (signal->error,
|
|
MOO_FILE_WATCH_ERROR,
|
|
MOO_FILE_WATCH_ERROR_NOT_IMPLEMENTED,
|
|
"suspend_monitor not implemented for folders");
|
|
g_async_queue_push (answer, GINT_TO_POINTER (FALSE));
|
|
}
|
|
|
|
|
|
static void fam_thread_resume_monitor (WinFamData *data,
|
|
SignalModifyMonitor *signal,
|
|
GAsyncQueue *answer)
|
|
{
|
|
Request *request;
|
|
HANDLE handle;
|
|
|
|
if (!find_request (data, signal->fc, signal->request, &request, &handle))
|
|
{
|
|
g_set_error (signal->error,
|
|
MOO_FILE_WATCH_ERROR,
|
|
MOO_FILE_WATCH_ERROR_FAILED,
|
|
"No monitor with id %d",
|
|
signal->request);
|
|
g_async_queue_push (answer, GINT_TO_POINTER (FALSE));
|
|
return;
|
|
}
|
|
|
|
g_set_error (signal->error,
|
|
MOO_FILE_WATCH_ERROR,
|
|
MOO_FILE_WATCH_ERROR_NOT_IMPLEMENTED,
|
|
"resume_monitor not implemented for folders");
|
|
g_async_queue_push (answer, GINT_TO_POINTER (FALSE));
|
|
}
|
|
|
|
|
|
static void fam_thread_cancel_monitor (WinFamData *data,
|
|
SignalModifyMonitor *signal,
|
|
GAsyncQueue *answer)
|
|
{
|
|
Request *request;
|
|
HANDLE handle;
|
|
|
|
if (!find_request (data, signal->fc, signal->request, &request, &handle))
|
|
{
|
|
g_set_error (signal->error,
|
|
MOO_FILE_WATCH_ERROR,
|
|
MOO_FILE_WATCH_ERROR_FAILED,
|
|
"No monitor with id %d",
|
|
signal->request);
|
|
g_async_queue_push (answer, GINT_TO_POINTER (FALSE));
|
|
return;
|
|
}
|
|
|
|
fam_thread_delete_handle (data, handle);
|
|
|
|
g_clear_error (signal->error);
|
|
g_async_queue_push (answer, GINT_TO_POINTER (TRUE));
|
|
}
|
|
|
|
|
|
static void fam_thread_delete_handle (WinFamData *data,
|
|
HANDLE handle)
|
|
{
|
|
guint i;
|
|
Request *request;
|
|
|
|
for (i = 1; i < data->objects->len; ++i)
|
|
if (data->objects->pdata[i] == handle)
|
|
break;
|
|
|
|
g_return_if_fail (i < data->objects->len);
|
|
|
|
g_ptr_array_remove_index (data->objects, i);
|
|
|
|
request = g_hash_table_lookup (data->handles, handle);
|
|
g_assert (request != NULL);
|
|
g_free (request->filename);
|
|
g_free (request);
|
|
|
|
g_hash_table_remove (data->handles, handle);
|
|
}
|
|
|
|
|
|
#define SOME_NUMBER 16
|
|
|
|
static void fam_thread_process_event (WinFam *fam,
|
|
WinFamData *data,
|
|
int obj_no)
|
|
{
|
|
HANDLE handle;
|
|
Request *request;
|
|
gboolean result;
|
|
// FILE_NOTIFY_INFORMATION info[SOME_NUMBER];
|
|
// DWORD bytes_returned;
|
|
WinFamEvent *event;
|
|
|
|
handle = data->objects->pdata[obj_no];
|
|
request = g_hash_table_lookup (data->handles, handle);
|
|
g_assert (request != NULL);
|
|
|
|
// result = ReadDirectoryChangesW (request->dir_handle,
|
|
// info, sizeof (info),
|
|
// FALSE,
|
|
// FILE_NOTIFY_CHANGE_FILE_NAME |
|
|
// FILE_NOTIFY_CHANGE_DIR_NAME |
|
|
// FILE_NOTIFY_CHANGE_SIZE |
|
|
// FILE_NOTIFY_CHANGE_LAST_WRITE,
|
|
// &bytes_returned, NULL, NULL);
|
|
|
|
// if (!result)
|
|
// {
|
|
// g_warning ("%s: ReadDirectoryChangesW failed", G_STRLOC);
|
|
// fam_thread_delete_handle (data, handle);
|
|
// return;
|
|
// }
|
|
|
|
if (!g_file_test (request->filename, G_FILE_TEST_IS_DIR))
|
|
{
|
|
g_message ("folder '%s' deleted", request->filename);
|
|
event = win_fam_event_new (MOO_FILE_WATCH_DELETED, request);
|
|
fam_thread_delete_handle (data, handle);
|
|
}
|
|
else
|
|
{
|
|
event = win_fam_event_new (MOO_FILE_WATCH_CHANGED, request);
|
|
/* WIN32_XXX */
|
|
result = FindNextChangeNotification (handle);
|
|
if (!result)
|
|
{
|
|
g_warning ("%s: FindNextChangeNotification failed", G_STRLOC);
|
|
fam_thread_delete_handle (data, handle);
|
|
}
|
|
}
|
|
|
|
g_async_queue_push (request->fc->events, event);
|
|
}
|
|
|
|
|
|
static WinFamEvent *win_fam_event_new (MooFileWatchEventCode code,
|
|
Request *request)
|
|
{
|
|
WinFamEvent *event = g_new (WinFamEvent, 1);
|
|
event->code = code;
|
|
event->monitor_id = request->reqnum;
|
|
event->filename = g_strdup (request->filename);
|
|
event->data = request->user_data;
|
|
return event;
|
|
}
|
|
|
|
|
|
static void win_fam_event_free (WinFamEvent *event)
|
|
{
|
|
if (event)
|
|
{
|
|
g_free (event->filename);
|
|
g_free (event);
|
|
}
|
|
}
|
|
|
|
|
|
#endif /* __WIN32__ */
|