#5663: Rework directory watching in daemon

Implement BSD/Darwin (kqueue) and Windows (ReadDirectoryChanges) mechanisms
for receiving directory change notifications. Use events instead of polling
for changes. Retry file parsing up to 3 times before giving up.

Huge thanks to missionsix for preparing first two versions of the patch.
This commit is contained in:
Mike Gelfand 2016-01-02 14:28:59 +00:00
parent de304e8a35
commit ea48360212
17 changed files with 1770 additions and 356 deletions

View File

@ -293,7 +293,6 @@
A2D22A130D65EEE700007D5F /* verify.c in Sources */ = {isa = PBXBuildFile; fileRef = A2D22A100D65EED100007D5F /* verify.c */; };
A2D307A40D9EC6870051FD27 /* BlocklistDownloader.m in Sources */ = {isa = PBXBuildFile; fileRef = A2D307A30D9EC6870051FD27 /* BlocklistDownloader.m */; };
A2D307B10D9EC9F50051FD27 /* BlocklistStatusWindow.xib in Resources */ = {isa = PBXBuildFile; fileRef = A2D307B00D9EC9F50051FD27 /* BlocklistStatusWindow.xib */; };
A2D5972A0F5AE49E0001AB3C /* watch.c in Sources */ = {isa = PBXBuildFile; fileRef = A2D597280F5AE49E0001AB3C /* watch.c */; };
A2D77451154CC25700A62B93 /* WebSeedTableView.h in Headers */ = {isa = PBXBuildFile; fileRef = A2D7744F154CC25700A62B93 /* WebSeedTableView.h */; };
A2D77452154CC25700A62B93 /* WebSeedTableView.m in Sources */ = {isa = PBXBuildFile; fileRef = A2D77450154CC25700A62B93 /* WebSeedTableView.m */; };
A2D77453154CC72B00A62B93 /* WebSeedTableView.m in Sources */ = {isa = PBXBuildFile; fileRef = A2D77450154CC25700A62B93 /* WebSeedTableView.m */; };
@ -473,6 +472,11 @@
C1639A7C1A55F57200E42033 /* cdecode.h in Headers */ = {isa = PBXBuildFile; fileRef = C1639A7A1A55F57200E42033 /* cdecode.h */; };
C1639A7D1A55F57200E42033 /* cencode.h in Headers */ = {isa = PBXBuildFile; fileRef = C1639A7B1A55F57200E42033 /* cencode.h */; };
C1F690FD1AD0627500D95CF0 /* daemon-posix.c in Sources */ = {isa = PBXBuildFile; fileRef = C1F690FC1AD0627500D95CF0 /* daemon-posix.c */; };
C1FEE5771C3223CC00D62832 /* watchdir-common.h in Headers */ = {isa = PBXBuildFile; fileRef = C1FEE5721C3223CC00D62832 /* watchdir-common.h */; };
C1FEE5781C3223CC00D62832 /* watchdir-generic.c in Sources */ = {isa = PBXBuildFile; fileRef = C1FEE5731C3223CC00D62832 /* watchdir-generic.c */; };
C1FEE5791C3223CC00D62832 /* watchdir-kqueue.c in Sources */ = {isa = PBXBuildFile; fileRef = C1FEE5741C3223CC00D62832 /* watchdir-kqueue.c */; };
C1FEE57A1C3223CC00D62832 /* watchdir.c in Sources */ = {isa = PBXBuildFile; fileRef = C1FEE5751C3223CC00D62832 /* watchdir.c */; };
C1FEE57B1C3223CC00D62832 /* watchdir.h in Headers */ = {isa = PBXBuildFile; fileRef = C1FEE5761C3223CC00D62832 /* watchdir.h */; };
D4AF3B2F0C41F7A500D46B6B /* list.c in Sources */ = {isa = PBXBuildFile; fileRef = D4AF3B2D0C41F7A500D46B6B /* list.c */; };
D4AF3B300C41F7A600D46B6B /* list.h in Headers */ = {isa = PBXBuildFile; fileRef = D4AF3B2E0C41F7A500D46B6B /* list.h */; };
E138A9780C04D88F00C5426C /* ProgressGradients.m in Sources */ = {isa = PBXBuildFile; fileRef = E138A9760C04D88F00C5426C /* ProgressGradients.m */; };
@ -1034,8 +1038,6 @@
A2D307A20D9EC6870051FD27 /* BlocklistDownloader.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = BlocklistDownloader.h; path = macosx/BlocklistDownloader.h; sourceTree = "<group>"; };
A2D307A30D9EC6870051FD27 /* BlocklistDownloader.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = BlocklistDownloader.m; path = macosx/BlocklistDownloader.m; sourceTree = "<group>"; };
A2D307B00D9EC9F50051FD27 /* BlocklistStatusWindow.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = BlocklistStatusWindow.xib; path = macosx/BlocklistStatusWindow.xib; sourceTree = "<group>"; };
A2D597280F5AE49E0001AB3C /* watch.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; name = watch.c; path = daemon/watch.c; sourceTree = "<group>"; };
A2D597290F5AE49E0001AB3C /* watch.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = watch.h; path = daemon/watch.h; sourceTree = "<group>"; };
A2D7744F154CC25700A62B93 /* WebSeedTableView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = WebSeedTableView.h; path = macosx/WebSeedTableView.h; sourceTree = "<group>"; };
A2D77450154CC25700A62B93 /* WebSeedTableView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = WebSeedTableView.m; path = macosx/WebSeedTableView.m; sourceTree = "<group>"; };
A2D8CFBF15FA177A0056E93D /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = macosx/QuickLookPlugin/ru.lproj/Localizable.strings; sourceTree = SOURCE_ROOT; };
@ -1230,6 +1232,11 @@
C1639A7B1A55F57200E42033 /* cencode.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = cencode.h; path = "third-party/libb64/b64/cencode.h"; sourceTree = "<group>"; };
C1F690FC1AD0627500D95CF0 /* daemon-posix.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; name = "daemon-posix.c"; path = "daemon/daemon-posix.c"; sourceTree = "<group>"; };
C1F690FE1AD0628400D95CF0 /* daemon.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = daemon.h; path = daemon/daemon.h; sourceTree = "<group>"; };
C1FEE5721C3223CC00D62832 /* watchdir-common.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "watchdir-common.h"; path = "libtransmission/watchdir-common.h"; sourceTree = "<group>"; };
C1FEE5731C3223CC00D62832 /* watchdir-generic.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = "watchdir-generic.c"; path = "libtransmission/watchdir-generic.c"; sourceTree = "<group>"; };
C1FEE5741C3223CC00D62832 /* watchdir-kqueue.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = "watchdir-kqueue.c"; path = "libtransmission/watchdir-kqueue.c"; sourceTree = "<group>"; };
C1FEE5751C3223CC00D62832 /* watchdir.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = watchdir.c; path = libtransmission/watchdir.c; sourceTree = "<group>"; };
C1FEE5761C3223CC00D62832 /* watchdir.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = watchdir.h; path = libtransmission/watchdir.h; sourceTree = "<group>"; };
D4AF3B2D0C41F7A500D46B6B /* list.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; name = list.c; path = libtransmission/list.c; sourceTree = "<group>"; };
D4AF3B2E0C41F7A500D46B6B /* list.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = list.h; path = libtransmission/list.h; sourceTree = "<group>"; };
E138A9750C04D88F00C5426C /* ProgressGradients.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = ProgressGradients.h; path = macosx/ProgressGradients.h; sourceTree = "<group>"; };
@ -1810,6 +1817,11 @@
A2A4EA0B0DE106E8000CE197 /* ConvertUTF.h */,
A2A4EA0A0DE106E8000CE197 /* ConvertUTF.c */,
4DB74F070E8CD75100AEB1A8 /* wildmat.c */,
C1FEE5751C3223CC00D62832 /* watchdir.c */,
C1FEE5761C3223CC00D62832 /* watchdir.h */,
C1FEE5731C3223CC00D62832 /* watchdir-generic.c */,
C1FEE5741C3223CC00D62832 /* watchdir-kqueue.c */,
C1FEE5721C3223CC00D62832 /* watchdir-common.h */,
);
name = libtransmission;
sourceTree = "<group>";
@ -2012,8 +2024,6 @@
C1F690FE1AD0628400D95CF0 /* daemon.h */,
C1F690FC1AD0627500D95CF0 /* daemon-posix.c */,
BEFC1C140C07756200B0BB3C /* remote.c */,
A2D597280F5AE49E0001AB3C /* watch.c */,
A2D597290F5AE49E0001AB3C /* watch.h */,
);
name = daemon;
sourceTree = "<group>";
@ -2122,6 +2132,7 @@
BEFC1E450C07861A00B0BB3C /* net.h in Headers */,
BEFC1E490C07861A00B0BB3C /* metainfo.h in Headers */,
BEFC1E4D0C07861A00B0BB3C /* session.h in Headers */,
C1FEE5771C3223CC00D62832 /* watchdir-common.h in Headers */,
BEFC1E4E0C07861A00B0BB3C /* inout.h in Headers */,
BEFC1E520C07861A00B0BB3C /* fdlimit.h in Headers */,
BEFC1E550C07861A00B0BB3C /* completion.h in Headers */,
@ -2140,6 +2151,7 @@
A29DF8BA0DB2544C00D04E5A /* resume.h in Headers */,
A29DF8BB0DB2544C00D04E5A /* torrent.h in Headers */,
A29DF8BE0DB2545F00D04E5A /* verify.h in Headers */,
C1FEE57B1C3223CC00D62832 /* watchdir.h in Headers */,
A2AAB6650DE0D08B00E04DDA /* blocklist.h in Headers */,
A2A4E9210DE0F7E9000CE197 /* web.h in Headers */,
A2A4EA0F0DE106EE000CE197 /* ConvertUTF.h in Headers */,
@ -2753,6 +2765,7 @@
BEFC1E4A0C07861A00B0BB3C /* metainfo.c in Sources */,
BEFC1E4F0C07861A00B0BB3C /* inout.c in Sources */,
BEFC1E530C07861A00B0BB3C /* fdlimit.c in Sources */,
C1FEE5781C3223CC00D62832 /* watchdir-generic.c in Sources */,
BEFC1E560C07861A00B0BB3C /* completion.c in Sources */,
BEFC1E580C07861A00B0BB3C /* clients.c in Sources */,
A2BE9C520C1E4AF5002D16E6 /* makemeta.c in Sources */,
@ -2786,10 +2799,12 @@
4D80185910BBC0B0008A4AF2 /* magnet.c in Sources */,
A209EE5C1144B51E002B02D1 /* history.c in Sources */,
A220EC5B118C8A060022B4BE /* tr-lpd.c in Sources */,
C1FEE57A1C3223CC00D62832 /* watchdir.c in Sources */,
A23547E211CD0B090046EAE6 /* cache.c in Sources */,
A284214412DA663E00FBDDBB /* tr-udp.c in Sources */,
A2679294130E00A000CB7464 /* tr-utp.c in Sources */,
A23F29A2132A447400E9A83B /* announcer-http.c in Sources */,
C1FEE5791C3223CC00D62832 /* watchdir-kqueue.c in Sources */,
A2AA9BE1132CAC8E00FA131E /* announcer-udp.c in Sources */,
A2D77452154CC25700A62B93 /* WebSeedTableView.m in Sources */,
A2A7B32A164F87D400B98C65 /* jsonsl.c in Sources */,
@ -2966,7 +2981,6 @@
buildActionMask = 2147483647;
files = (
BEFC1C1A0C07756200B0BB3C /* daemon.c in Sources */,
A2D5972A0F5AE49E0001AB3C /* watch.c in Sources */,
C1F690FD1AD0627500D95CF0 /* daemon-posix.c in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;

View File

@ -204,35 +204,32 @@ dnl ----------------------------------------------------------------------------
dnl
dnl file monitoring for the daemon
AC_CHECK_HEADER([sys/inotify.h],
[AC_CHECK_FUNC([inotify_init],[have_inotify="yes"],[have_inotify="no"])],
[have_inotify="no"])
AC_ARG_WITH([inotify],
[AS_HELP_STRING([--with-inotify],[Enable inotify support (default=auto)])],
[want_inotify=${withval}],
[want_inotify=${have_inotify}])
if test "x$want_inotify" = "xyes" ; then
if test "x$have_inotify" = "xyes"; then
AC_DEFINE([WITH_INOTIFY],[1])
else
AC_MSG_ERROR("inotify not found!")
fi
fi
[AS_HELP_STRING([--with-inotify], [Enable inotify support (default=auto)])],
[WANT_INOTIFY=${withval}],
[WANT_INOTIFY=auto])
HAVE_INOTIFY=0
AS_IF([test "x$WANT_INOTIFY" != "xno"],
[AC_CHECK_HEADER([sys/inotify.h],
[AC_CHECK_FUNC([inotify_init],
[HAVE_INOTIFY=1])],
[AS_IF([test "x$WANT_INOTIFY" = "xyes"],
[AC_MSG_ERROR("inotify not found!")])])])
AM_CONDITIONAL([USE_INOTIFY], [test "x$WANT_INOTIFY" != "xno" -a $HAVE_INOTIFY -eq 1])
AC_CHECK_HEADER([sys/event.h],
[AC_CHECK_FUNC([kqueue],[have_kqueue="yes"],[have_kqueue="no"])],
[have_kqueue="no"])
AC_ARG_WITH([kqueue],
[AS_HELP_STRING([--with-kqueue],[Enable kqueue support (default=auto)])],
[want_kqueue=${withval}],
[want_kqueue=${have_kqueue}])
if test "x$want_kqueue" = "xyes" ; then
if test "x$have_kqueue" = "xyes"; then
AC_DEFINE([WITH_KQUEUE],[1])
else
AC_MSG_ERROR("kqueue not found!")
fi
fi
[WITH_KQUEUE=${withval}],
[WITH_KQUEUE=auto])
HAVE_KQUEUE=0
AS_IF([test "x$WITH_KQUEUE" != "xno"],
[AC_CHECK_HEADER([sys/event.h],
[AC_CHECK_FUNC([kqueue],
[HAVE_KQUEUE=1])],
[AS_IF([test "x$WANT_KQUEUE" = "xyes"],
[AC_MSG_ERROR("kqueue not found!")])])])
AM_CONDITIONAL([USE_KQUEUE], [test "x$WITH_KQUEUE" != "xno" -a $HAVE_KQUEUE -eq 1])
AC_CHECK_HEADERS([sys/statvfs.h \
xfs/xfs.h])

View File

@ -1,13 +1,5 @@
project(trdaemon)
if(WITH_INOTIFY)
add_definitions(-DWITH_INOTIFY)
endif()
if(WITH_KQUEUE)
add_definitions(-DWITH_KQUEUE)
endif()
if(WITH_SYSTEMD)
add_definitions(-DUSE_SYSTEMD_DAEMON)
endif()
@ -22,7 +14,6 @@ set(${PROJECT_NAME}_SOURCES
daemon.c
daemon-posix.c
daemon-win32.c
watch.c
)
if(WIN32)
@ -33,7 +24,6 @@ endif()
set(${PROJECT_NAME}_HEADERS
daemon.h
watch.h
)
tr_win32_app_info(${PROJECT_NAME}_WIN32_RC_FILE

View File

@ -34,11 +34,9 @@ LDADD = \
@PTHREAD_LIBS@ \
${LIBM}
noinst_HEADERS = \
daemon.h \
watch.h
noinst_HEADERS = daemon.h
transmission_daemon_SOURCES = daemon.c watch.c
transmission_daemon_SOURCES = daemon.c
transmission_remote_SOURCES = remote.c
if WIN32

View File

@ -21,7 +21,6 @@
#include <unistd.h> /* getpid */
#endif
#include <event2/buffer.h>
#include <event2/event.h>
#include <libtransmission/transmission.h>
@ -32,6 +31,7 @@
#include <libtransmission/utils.h>
#include <libtransmission/variant.h>
#include <libtransmission/version.h>
#include <libtransmission/watchdir.h>
#ifdef USE_SYSTEMD_DAEMON
#include <systemd/sd-daemon.h>
@ -41,7 +41,6 @@
#endif
#include "daemon.h"
#include "watch.h"
#define MY_NAME "transmission-daemon"
@ -185,10 +184,17 @@ getConfigDir (int argc, const char * const * argv)
return configDir;
}
static void
onFileAdded (tr_session * session, const char * dir, const char * file)
static tr_watchdir_status
onFileAdded (tr_watchdir_t dir,
const char * name,
void * context)
{
char * filename = tr_buildPath (dir, file, NULL);
tr_session * session = context;
if (!tr_str_has_suffix (name, ".torrent"))
return TR_WATCHDIR_IGNORE;
char * filename = tr_buildPath (tr_watchdir_get_path (dir), name, NULL);
tr_ctor * ctor = tr_ctorNew (session);
int err = tr_ctorSetMetainfoFromFile (ctor, filename);
@ -197,19 +203,19 @@ onFileAdded (tr_session * session, const char * dir, const char * file)
tr_torrentNew (ctor, &err, NULL);
if (err == TR_PARSE_ERR)
tr_logAddError ("Error parsing .torrent file \"%s\"", file);
tr_logAddError ("Error parsing .torrent file \"%s\"", name);
else
{
bool trash = false;
const bool test = tr_ctorGetDeleteSource (ctor, &trash);
tr_logAddInfo ("Parsing .torrent file successful \"%s\"", file);
tr_logAddInfo ("Parsing .torrent file successful \"%s\"", name);
if (test && trash)
{
tr_error * error = NULL;
tr_logAddInfo ("Deleting input .torrent file \"%s\"", file);
tr_logAddInfo ("Deleting input .torrent file \"%s\"", name);
if (!tr_sys_path_remove (filename, &error))
{
tr_logAddError ("Error deleting .torrent file: %s", error->message);
@ -224,9 +230,15 @@ onFileAdded (tr_session * session, const char * dir, const char * file)
}
}
}
else
{
err = TR_PARSE_ERR;
}
tr_ctorFree (ctor);
tr_free (filename);
return err == TR_PARSE_ERR ? TR_WATCHDIR_RETRY : TR_WATCHDIR_ACCEPT;
}
static void
@ -293,12 +305,11 @@ reportStatus (void)
}
static void
periodicUpdate (evutil_socket_t fd UNUSED, short what UNUSED, void *watchdir)
periodicUpdate (evutil_socket_t fd UNUSED,
short what UNUSED,
void * context UNUSED)
{
dtr_watchdir_update (watchdir);
pumpLogMessages (logfile);
reportStatus ();
}
@ -479,10 +490,10 @@ daemon_start (void * raw_arg,
{
bool boolVal;
const char * pid_filename;
dtr_watchdir * watchdir = NULL;
bool pidfile_created = false;
tr_session * session = NULL;
struct event *status_ev;
struct event * status_ev = NULL;
tr_watchdir_t watchdir = NULL;
struct daemon_data * const arg = raw_arg;
tr_variant * const settings = &arg->settings;
@ -558,7 +569,8 @@ daemon_start (void * raw_arg,
&& *dir)
{
tr_logAddInfo ("Watching \"%s\" for new .torrent files", dir);
watchdir = dtr_watchdir_new (mySession, dir, onFileAdded);
if ((watchdir = tr_watchdir_new (dir, &onFileAdded, mySession, ev_base)) == NULL)
goto cleanup;
}
}
@ -581,7 +593,7 @@ daemon_start (void * raw_arg,
/* Create new timer event to report daemon status */
{
struct timeval one_sec = { 1, 0 };
status_ev = event_new(ev_base, -1, EV_PERSIST, &periodicUpdate, watchdir);
status_ev = event_new(ev_base, -1, EV_PERSIST, &periodicUpdate, NULL);
if (status_ev == NULL)
{
tr_logAddError("Failed to create status event %s", tr_strerror(errno));
@ -607,6 +619,8 @@ cleanup:
sd_notify( 0, "STATUS=Closing transmission session...\n" );
printf ("Closing transmission session...");
tr_watchdir_free (watchdir);
if (status_ev)
{
event_del(status_ev);
@ -615,7 +629,6 @@ cleanup:
event_base_free(ev_base);
tr_sessionSaveSettings (mySession, configDir, settings);
dtr_watchdir_free (watchdir);
tr_sessionClose (mySession);
pumpLogMessages (logfile);
printf (" done.\n");

View File

@ -1,267 +0,0 @@
/*
* This file Copyright (C) 2009-2014 Mnemosyne LLC
*
* It may be used under the GNU GPL versions 2 or 3
* or any future license endorsed by Mnemosyne LLC.
*
* $Id$
*/
#ifdef WITH_INOTIFY
#include <sys/inotify.h>
#include <sys/select.h>
#include <unistd.h> /* close */
#else
#include <event2/buffer.h> /* evbuffer */
#endif
#include <errno.h>
#include <string.h> /* strlen () */
#include <stdio.h> /* perror () */
#include <libtransmission/transmission.h>
#include <libtransmission/file.h>
#include <libtransmission/log.h>
#include <libtransmission/utils.h> /* tr_buildPath (), tr_logAddInfo () */
#include "watch.h"
struct dtr_watchdir
{
tr_session * session;
char * dir;
dtr_watchdir_callback * callback;
#ifdef WITH_INOTIFY
int inotify_fd;
#else /* readdir implementation */
time_t lastTimeChecked;
struct evbuffer * lastFiles;
#endif
};
/***
**** INOTIFY IMPLEMENTATION
***/
#if defined (WITH_INOTIFY)
/* how many inotify events to try to batch into a single read */
#define EVENT_BATCH_COUNT 50
/* size of the event structure, not counting name */
#define EVENT_SIZE (sizeof (struct inotify_event))
/* reasonable guess as to size of 50 events */
#define BUF_LEN (EVENT_BATCH_COUNT * (EVENT_SIZE + 16) + 2048)
#define DTR_INOTIFY_MASK (IN_CLOSE_WRITE|IN_MOVED_TO|IN_CREATE|IN_ONLYDIR)
static void
watchdir_new_impl (dtr_watchdir * w)
{
int i;
tr_sys_dir_t odir;
w->inotify_fd = inotify_init ();
if (w->inotify_fd < 0)
{
i = -1;
}
else
{
tr_logAddInfo ("Using inotify to watch directory \"%s\"", w->dir);
i = inotify_add_watch (w->inotify_fd, w->dir, DTR_INOTIFY_MASK);
}
if (i < 0)
{
tr_logAddError ("Unable to watch \"%s\": %s", w->dir, tr_strerror (errno));
}
else if ((odir = tr_sys_dir_open (w->dir, NULL)) != TR_BAD_SYS_DIR)
{
const char * name;
while ((name = tr_sys_dir_read_name (odir, NULL)) != NULL)
{
if (!tr_str_has_suffix (name, ".torrent")) /* skip non-torrents */
continue;
tr_logAddInfo ("Found new .torrent file \"%s\" in watchdir \"%s\"", name, w->dir);
w->callback (w->session, w->dir, name);
}
tr_sys_dir_close (odir, NULL);
}
}
static void
watchdir_free_impl (dtr_watchdir * w)
{
if (w->inotify_fd >= 0)
{
inotify_rm_watch (w->inotify_fd, DTR_INOTIFY_MASK);
close (w->inotify_fd);
}
}
static void
watchdir_update_impl (dtr_watchdir * w)
{
int ret;
fd_set rfds;
struct timeval time;
const int fd = w->inotify_fd;
/* timeout after one second */
time.tv_sec = 1;
time.tv_usec = 0;
/* make the fd_set hold the inotify fd */
FD_ZERO (&rfds);
FD_SET (fd, &rfds);
/* check for added files */
ret = select (fd+1, &rfds, NULL, NULL, &time);
if (ret < 0) {
perror ("select");
} else if (!ret) {
/* timed out! */
} else if (FD_ISSET (fd, &rfds)) {
int i = 0;
char buf[BUF_LEN];
int len = read (fd, buf, sizeof (buf));
while (i < len) {
struct inotify_event * event = (struct inotify_event *) &buf[i];
const char * name = event->name;
if (tr_str_has_suffix (name, ".torrent"))
{
tr_logAddInfo ("Found new .torrent file \"%s\" in watchdir \"%s\"", name, w->dir);
w->callback (w->session, w->dir, name);
}
i += EVENT_SIZE + event->len;
}
}
}
#else /* WITH_INOTIFY */
/***
**** READDIR IMPLEMENTATION
***/
#define WATCHDIR_POLL_INTERVAL_SECS 10
#define FILE_DELIMITER '\t'
static void
watchdir_new_impl (dtr_watchdir * w UNUSED)
{
tr_logAddInfo ("Using readdir to watch directory \"%s\"", w->dir);
w->lastFiles = evbuffer_new ();
}
static void
watchdir_free_impl (dtr_watchdir * w)
{
evbuffer_free (w->lastFiles);
}
static char*
get_key_from_file (const char * filename, const size_t len)
{
return tr_strdup_printf ("%c%*.*s%d", FILE_DELIMITER, (int)len, (int)len, filename, FILE_DELIMITER);
}
static void
add_file_to_list (struct evbuffer * buf, const char * filename, size_t len)
{
char * key = get_key_from_file (filename, len);
evbuffer_add (buf, key, strlen (key));
tr_free (key);
}
static bool
is_file_in_list (struct evbuffer * buf, const char * filename, size_t len)
{
bool in_list;
struct evbuffer_ptr ptr;
char * key = get_key_from_file (filename, len);
ptr = evbuffer_search (buf, key, strlen (key), NULL);
in_list = ptr.pos != -1;
tr_free (key);
return in_list;
}
static void
watchdir_update_impl (dtr_watchdir * w)
{
tr_sys_path_info info;
tr_sys_dir_t odir;
const time_t oldTime = w->lastTimeChecked;
const char * dirname = w->dir;
struct evbuffer * curFiles = evbuffer_new ();
if (oldTime + WATCHDIR_POLL_INTERVAL_SECS < time (NULL) &&
tr_sys_path_get_info (dirname, 0, &info, NULL) &&
info.type == TR_SYS_PATH_IS_DIRECTORY &&
(odir = tr_sys_dir_open (dirname, NULL)) != TR_BAD_SYS_DIR)
{
const char * name;
while ((name = tr_sys_dir_read_name (odir, NULL)) != NULL)
{
size_t len;
if (*name == '.') /* skip dotfiles */
continue;
if (!tr_str_has_suffix (name, ".torrent")) /* skip non-torrents */
continue;
len = strlen (name);
add_file_to_list (curFiles, name, len);
/* if this file wasn't here last time, try adding it */
if (!is_file_in_list (w->lastFiles, name, len)) {
tr_logAddInfo ("Found new .torrent file \"%s\" in watchdir \"%s\"", name, w->dir);
w->callback (w->session, w->dir, name);
}
}
tr_sys_dir_close (odir, NULL);
w->lastTimeChecked = time (NULL);
evbuffer_free (w->lastFiles);
w->lastFiles = curFiles;
}
}
#endif
/***
****
***/
dtr_watchdir*
dtr_watchdir_new (tr_session * session, const char * dir, dtr_watchdir_callback callback)
{
dtr_watchdir * w = tr_new0 (dtr_watchdir, 1);
w->session = session;
w->dir = tr_strdup (dir);
w->callback = callback;
watchdir_new_impl (w);
return w;
}
void
dtr_watchdir_update (dtr_watchdir * w)
{
if (w != NULL)
watchdir_update_impl (w);
}
void
dtr_watchdir_free (dtr_watchdir * w)
{
if (w != NULL)
{
watchdir_free_impl (w);
tr_free (w->dir);
tr_free (w);
}
}

View File

@ -1,23 +0,0 @@
/*
* This file Copyright (C) 2009-2014 Mnemosyne LLC
*
* It may be used under the GNU GPL versions 2 or 3
* or any future license endorsed by Mnemosyne LLC.
*
* $Id$
*/
#ifndef DTR_WATCH_H
#define DTR_WATCH_H
typedef struct dtr_watchdir dtr_watchdir;
typedef void (dtr_watchdir_callback)(tr_session * session, const char * dir, const char * file);
dtr_watchdir* dtr_watchdir_new (tr_session * session, const char * dir, dtr_watchdir_callback cb);
void dtr_watchdir_update (dtr_watchdir * w);
void dtr_watchdir_free (dtr_watchdir * w);
#endif

View File

@ -62,6 +62,11 @@ set(${PROJECT_NAME}_SOURCES
variant.c
variant-json.c
verify.c
watchdir.c
watchdir-generic.c
watchdir-inotify.c
watchdir-kqueue.c
watchdir-win32.c
web.c
webseed.c
wildmat.c
@ -74,10 +79,22 @@ foreach(CP cyassl openssl polarssl)
endif()
endforeach()
if(WITH_INOTIFY)
add_definitions(-DWITH_INOTIFY)
else()
set_source_files_properties(watchdir-inotify.c PROPERTIES HEADER_FILE_ONLY ON)
endif()
if(WITH_KQUEUE)
add_definitions(-DWITH_KQUEUE)
else()
set_source_files_properties(watchdir-kqueue.c PROPERTIES HEADER_FILE_ONLY ON)
endif()
if(WIN32)
set_source_files_properties(file-posix.c PROPERTIES HEADER_FILE_ONLY ON)
else()
set_source_files_properties(file-win32.c PROPERTIES HEADER_FILE_ONLY ON)
set_source_files_properties(file-win32.c watchdir-win32.c PROPERTIES HEADER_FILE_ONLY ON)
endif()
set(${PROJECT_NAME}_PUBLIC_HEADERS
@ -92,6 +109,7 @@ set(${PROJECT_NAME}_PUBLIC_HEADERS
transmission.h
utils.h
variant.h
watchdir.h
web.h
${PROJECT_BINARY_DIR}/version.h
)
@ -140,6 +158,7 @@ set(${PROJECT_NAME}_PRIVATE_HEADERS
variant-common.h
verify.h
version.h
watchdir-common.h
webseed.h
)
@ -240,7 +259,8 @@ if(ENABLE_TESTS)
set(crypto-test_ADD_SOURCES crypto-test-ref.h)
foreach(T bitfield blocklist clients crypto error file history json magnet metainfo move peer-msgs quark rename rpc session tr-getopt utils variant)
foreach(T bitfield blocklist clients crypto error file history json magnet metainfo move peer-msgs quark rename rpc session
tr-getopt utils variant watchdir)
set(TP ${TR_NAME}-test-${T})
add_executable(${TP} ${T}-test.c ${${T}-test_ADD_SOURCES})
target_link_libraries(${TP} ${TR_NAME} ${TR_NAME}-test)

View File

@ -72,12 +72,22 @@ libtransmission_a_SOURCES = \
variant-benc.c \
variant-json.c \
verify.c \
watchdir.c \
watchdir-generic.c \
web.c \
webseed.c \
wildmat.c
if USE_INOTIFY
libtransmission_a_SOURCES += watchdir-inotify.c
endif
if USE_KQUEUE
libtransmission_a_SOURCES += watchdir-kqueue.c
endif
if WIN32
libtransmission_a_SOURCES += file-win32.c
libtransmission_a_SOURCES += file-win32.c watchdir-win32.c
else
libtransmission_a_SOURCES += file-posix.c
endif
@ -150,6 +160,8 @@ noinst_HEADERS = \
variant-common.h \
verify.h \
version.h \
watchdir.h \
watchdir-common.h \
web.h \
webseed.h
@ -173,7 +185,8 @@ TESTS = \
session-test \
tr-getopt-test \
utils-test \
variant-test
variant-test \
watchdir-test
noinst_PROGRAMS = $(TESTS)
@ -270,6 +283,10 @@ variant_test_SOURCES = variant-test.c $(TEST_SOURCES)
variant_test_LDADD = ${apps_ldadd}
variant_test_LDFLAGS = ${apps_ldflags}
watchdir_test_SOURCES = watchdir-test.c $(TEST_SOURCES)
watchdir_test_LDADD = ${apps_ldadd}
watchdir_test_LDFLAGS = ${apps_ldflags}
rename_test_SOURCES = rename-test.c $(TEST_SOURCES)
rename_test_LDADD = ${apps_ldadd}
rename_test_LDFLAGS = ${apps_ldflags}

View File

@ -0,0 +1,55 @@
/*
* This file Copyright (C) 2015-2016 Mnemosyne LLC
*
* It may be used under the GNU GPL versions 2 or 3
* or any future license endorsed by Mnemosyne LLC.
*
* $Id$
*/
#ifndef TR_WATCHDIR_COMMON_H
#define TR_WATCHDIR_COMMON_H
#ifndef __LIBTRANSMISSION_WATCHDIR_MODULE__
#error only the libtransmission watchdir module should #include this header.
#endif
struct tr_ptrArray;
typedef struct tr_watchdir_backend
{
void (* free_func) (struct tr_watchdir_backend *);
}
tr_watchdir_backend;
#define BACKEND_DOWNCAST(b) ((tr_watchdir_backend *) (b))
/* ... */
tr_watchdir_backend * tr_watchdir_get_backend (tr_watchdir_t handle);
struct event_base * tr_watchdir_get_event_base (tr_watchdir_t handle);
/* ... */
void tr_watchdir_process (tr_watchdir_t handle,
const char * name);
void tr_watchdir_scan (tr_watchdir_t handle,
struct tr_ptrArray * dir_entries);
/* ... */
tr_watchdir_backend * tr_watchdir_generic_new (tr_watchdir_t handle);
#ifdef WITH_INOTIFY
tr_watchdir_backend * tr_watchdir_inotify_new (tr_watchdir_t handle);
#endif
#ifdef WITH_KQUEUE
tr_watchdir_backend * tr_watchdir_kqueue_new (tr_watchdir_t handle);
#endif
#ifdef _WIN32
tr_watchdir_backend * tr_watchdir_win32_new (tr_watchdir_t handle);
#endif
#endif /* TR_WATCHDIR_COMMON_H */

View File

@ -0,0 +1,114 @@
/*
* This file Copyright (C) 2015-2016 Mnemosyne LLC
*
* It may be used under the GNU GPL versions 2 or 3
* or any future license endorsed by Mnemosyne LLC.
*
* $Id$
*/
#include <assert.h>
#include <errno.h>
#include <event2/event.h>
#define __LIBTRANSMISSION_WATCHDIR_MODULE__
#include "transmission.h"
#include "log.h"
#include "ptrarray.h"
#include "utils.h"
#include "watchdir.h"
#include "watchdir-common.h"
/***
****
***/
#define log_error(...) (!tr_logLevelIsActive (TR_LOG_ERROR) ? (void) 0 : \
tr_logAddMessage (__FILE__, __LINE__, TR_LOG_ERROR, "watchdir:generic", __VA_ARGS__))
/***
****
***/
typedef struct tr_watchdir_generic
{
tr_watchdir_backend base;
struct event * event;
tr_ptrArray dir_entries;
}
tr_watchdir_generic;
#define BACKEND_UPCAST(b) ((tr_watchdir_generic *) (b))
/* Non-static and mutable for unit tests */
struct timeval tr_watchdir_generic_interval = { 10, 0 };
/***
****
***/
static void
tr_watchdir_generic_on_event (evutil_socket_t fd UNUSED,
short type UNUSED,
void * context)
{
const tr_watchdir_t handle = context;
tr_watchdir_generic * const backend = BACKEND_UPCAST (tr_watchdir_get_backend (handle));
tr_watchdir_scan (handle, &backend->dir_entries);
}
static void
tr_watchdir_generic_free (tr_watchdir_backend * backend_base)
{
tr_watchdir_generic * const backend = BACKEND_UPCAST (backend_base);
if (backend == NULL)
return;
assert (backend->base.free_func == &tr_watchdir_generic_free);
if (backend->event != NULL)
{
event_del (backend->event);
event_free (backend->event);
}
tr_ptrArrayDestruct (&backend->dir_entries, &tr_free);
tr_free (backend);
}
tr_watchdir_backend *
tr_watchdir_generic_new (tr_watchdir_t handle)
{
tr_watchdir_generic * backend;
backend = tr_new0 (tr_watchdir_generic, 1);
backend->base.free_func = &tr_watchdir_generic_free;
if ((backend->event = event_new (tr_watchdir_get_event_base (handle), -1, EV_PERSIST,
&tr_watchdir_generic_on_event, handle)) == NULL)
{
log_error ("Failed to create event: %s", tr_strerror (errno));
goto fail;
}
if (event_add (backend->event, &tr_watchdir_generic_interval) == -1)
{
log_error ("Failed to add event: %s", tr_strerror (errno));
goto fail;
}
/* Run initial scan on startup */
event_active (backend->event, EV_READ, 0);
return BACKEND_DOWNCAST (backend);
fail:
tr_watchdir_generic_free (BACKEND_DOWNCAST (backend));
return NULL;
}

View File

@ -0,0 +1,203 @@
/*
* This file Copyright (C) 2015-2016 Mnemosyne LLC
*
* It may be used under the GNU GPL versions 2 or 3
* or any future license endorsed by Mnemosyne LLC.
*
* $Id$
*/
#include <assert.h>
#include <errno.h>
#include <limits.h> /* NAME_MAX */
#include <stdlib.h> /* realloc () */
#include <unistd.h> /* close () */
#include <sys/inotify.h>
#include <event2/bufferevent.h>
#include <event2/event.h>
#define __LIBTRANSMISSION_WATCHDIR_MODULE__
#include "transmission.h"
#include "log.h"
#include "utils.h"
#include "watchdir.h"
#include "watchdir-common.h"
/***
****
***/
#define log_error(...) (!tr_logLevelIsActive (TR_LOG_ERROR) ? (void) 0 : \
tr_logAddMessage (__FILE__, __LINE__, TR_LOG_ERROR, "watchdir:inotify", __VA_ARGS__))
/***
****
***/
typedef struct tr_watchdir_inotify
{
tr_watchdir_backend base;
int infd;
int inwd;
struct bufferevent * event;
}
tr_watchdir_inotify;
#define BACKEND_UPCAST(b) ((tr_watchdir_inotify *) (b))
#define INOTIFY_WATCH_MASK (IN_CLOSE_WRITE | IN_MOVED_TO | IN_CREATE)
/***
****
***/
static void
tr_watchdir_inotify_on_first_scan (evutil_socket_t fd UNUSED,
short type UNUSED,
void * context)
{
const tr_watchdir_t handle = context;
tr_watchdir_scan (handle, NULL);
}
static void
tr_watchdir_inotify_on_event (struct bufferevent * event,
void * context)
{
assert (context != NULL);
const tr_watchdir_t handle = context;
tr_watchdir_inotify * const backend = BACKEND_UPCAST (tr_watchdir_get_backend (handle));
struct inotify_event ev;
size_t nread;
size_t name_size = NAME_MAX + 1;
char * name = tr_new (char, name_size);
/* Read the size of the struct excluding name into buf. Guaranteed to have at
least sizeof (ev) available */
while ((nread = bufferevent_read (event, &ev, sizeof (ev))) != 0)
{
if (nread == (size_t) -1)
{
log_error ("Failed to read inotify event: %s", tr_strerror (errno));
break;
}
if (nread != sizeof (ev))
{
log_error ("Failed to read inotify event: expected %zu, got %zu bytes.",
sizeof (ev), nread);
break;
}
assert (ev.wd == backend->inwd);
assert ((ev.mask & INOTIFY_WATCH_MASK) != 0);
assert (ev.len > 0);
if (ev.len > name_size)
{
name_size = ev.len;
name = tr_renew (char, name, name_size);
}
/* Consume entire name into buffer */
if ((nread = bufferevent_read (event, name, ev.len)) == (size_t) -1)
{
log_error ("Failed to read inotify name: %s", tr_strerror (errno));
break;
}
if (nread != ev.len)
{
log_error ("Failed to read inotify name: expected %" PRIu32 ", got %zu bytes.",
ev.len, nread);
break;
}
tr_watchdir_process (handle, name);
}
tr_free (name);
}
static void
tr_watchdir_inotify_free (tr_watchdir_backend * backend_base)
{
tr_watchdir_inotify * const backend = BACKEND_UPCAST (backend_base);
if (backend == NULL)
return;
assert (backend->base.free_func == &tr_watchdir_inotify_free);
if (backend->event != NULL)
{
bufferevent_disable (backend->event, EV_READ);
bufferevent_free (backend->event);
}
if (backend->infd != -1)
{
if (backend->inwd != -1)
inotify_rm_watch (backend->infd, backend->inwd);
close (backend->infd);
}
tr_free (backend);
}
tr_watchdir_backend *
tr_watchdir_inotify_new (tr_watchdir_t handle)
{
const char * const path = tr_watchdir_get_path (handle);
tr_watchdir_inotify * backend;
backend = tr_new0 (tr_watchdir_inotify, 1);
backend->base.free_func = &tr_watchdir_inotify_free;
backend->infd = -1;
backend->inwd = -1;
if ((backend->infd = inotify_init ()) == -1)
{
log_error ("Unable to inotify_init: %s", tr_strerror (errno));
goto fail;
}
if ((backend->inwd = inotify_add_watch (backend->infd, path,
INOTIFY_WATCH_MASK | IN_ONLYDIR)) == -1)
{
log_error ("Failed to setup watchdir \"%s\": %s (%d)", path,
tr_strerror (errno), errno);
goto fail;
}
if ((backend->event = bufferevent_socket_new (tr_watchdir_get_event_base (handle),
backend->infd, 0)) == NULL)
{
log_error ("Failed to create event buffer: %s", tr_strerror (errno));
goto fail;
}
/* Guarantees at least the sizeof an inotify event will be available in the
event buffer */
bufferevent_setwatermark (backend->event, EV_READ, sizeof (struct inotify_event), 0);
bufferevent_setcb (backend->event, &tr_watchdir_inotify_on_event, NULL, NULL, handle);
bufferevent_enable (backend->event, EV_READ);
/* Perform an initial scan on the directory */
if (event_base_once (tr_watchdir_get_event_base (handle), -1, EV_TIMEOUT,
&tr_watchdir_inotify_on_first_scan, handle, NULL) == -1)
log_error ("Failed to perform initial scan: %s", tr_strerror (errno));
return BACKEND_DOWNCAST (backend);
fail:
tr_watchdir_inotify_free (BACKEND_DOWNCAST (backend));
return NULL;
}

View File

@ -0,0 +1,170 @@
/*
* This file Copyright (C) 2015-2016 Mnemosyne LLC
*
* It may be used under the GNU GPL versions 2 or 3
* or any future license endorsed by Mnemosyne LLC.
*
* $Id$
*/
#include <assert.h>
#include <errno.h>
#include <string.h> /* strcmp () */
#include <fcntl.h> /* open () */
#include <unistd.h> /* close () */
#include <sys/types.h>
#include <sys/event.h>
#ifndef O_EVTONLY
#define O_EVTONLY O_RDONLY
#endif
#include <event2/event.h>
#define __LIBTRANSMISSION_WATCHDIR_MODULE__
#include "transmission.h"
#include "log.h"
#include "ptrarray.h"
#include "utils.h"
#include "watchdir.h"
#include "watchdir-common.h"
/***
****
***/
#define log_error(...) (!tr_logLevelIsActive (TR_LOG_ERROR) ? (void) 0 : \
tr_logAddMessage (__FILE__, __LINE__, TR_LOG_ERROR, "watchdir:kqueue", __VA_ARGS__))
/***
****
***/
typedef struct tr_watchdir_kqueue
{
tr_watchdir_backend base;
int kq;
int dirfd;
struct event * event;
tr_ptrArray dir_entries;
}
tr_watchdir_kqueue;
#define BACKEND_UPCAST(b) ((tr_watchdir_kqueue *) (b))
#define KQUEUE_WATCH_MASK (NOTE_WRITE | NOTE_EXTEND)
/***
****
***/
static void
tr_watchdir_kqueue_on_event (evutil_socket_t fd UNUSED,
short type UNUSED,
void * context)
{
const tr_watchdir_t handle = context;
tr_watchdir_kqueue * const backend = BACKEND_UPCAST (tr_watchdir_get_backend (handle));
struct kevent ke;
const struct timespec ts = { 0, 0 };
if (kevent (backend->kq, NULL, 0, &ke, 1, &ts) == -1)
{
log_error ("Failed to fetch kevent: %s", tr_strerror (errno));
return;
}
/* Read directory with generic scan */
tr_watchdir_scan (handle, &backend->dir_entries);
}
static void
tr_watchdir_kqueue_free (tr_watchdir_backend * backend_base)
{
tr_watchdir_kqueue * const backend = BACKEND_UPCAST (backend_base);
if (backend == NULL)
return;
assert (backend->base.free_func == &tr_watchdir_kqueue_free);
if (backend->event != NULL)
{
event_del (backend->event);
event_free (backend->event);
}
if (backend->kq != -1)
close (backend->kq);
if (backend->dirfd != -1)
close (backend->dirfd);
tr_ptrArrayDestruct (&backend->dir_entries, &tr_free);
tr_free (backend);
}
tr_watchdir_backend *
tr_watchdir_kqueue_new (tr_watchdir_t handle)
{
const char * const path = tr_watchdir_get_path (handle);
struct kevent ke;
tr_watchdir_kqueue * backend;
backend = tr_new0 (tr_watchdir_kqueue, 1);
backend->base.free_func = &tr_watchdir_kqueue_free;
backend->kq = -1;
backend->dirfd = -1;
if ((backend->kq = kqueue ()) == -1)
{
log_error ("Failed to start kqueue");
goto fail;
}
/* Open fd for watching */
if ((backend->dirfd = open (path, O_RDONLY | O_EVTONLY)) == -1)
{
log_error ("Failed to passively watch directory \"%s\": %s", path,
tr_strerror (errno));
goto fail;
}
/* Register kevent filter with kqueue descriptor */
EV_SET (&ke, backend->dirfd, EVFILT_VNODE, EV_ADD | EV_ENABLE | EV_CLEAR,
KQUEUE_WATCH_MASK, 0, NULL);
if (kevent (backend->kq, &ke, 1, NULL, 0, NULL) == -1)
{
log_error ("Failed to set directory event filter with fd %d: %s", backend->kq,
tr_strerror (errno));
goto fail;
}
/* Create libevent task for event descriptor */
if ((backend->event = event_new (tr_watchdir_get_event_base (handle), backend->kq,
EV_READ | EV_ET | EV_PERSIST,
&tr_watchdir_kqueue_on_event, handle)) == NULL)
{
log_error ("Failed to create event: %s", tr_strerror (errno));
goto fail;
}
if (event_add (backend->event, NULL) == -1)
{
log_error ("Failed to add event: %s", tr_strerror (errno));
goto fail;
}
/* Trigger one event for the initial scan */
event_active (backend->event, EV_READ, 0);
return BACKEND_DOWNCAST (backend);
fail:
tr_watchdir_kqueue_free (BACKEND_DOWNCAST (backend));
return NULL;
}

View File

@ -0,0 +1,395 @@
/*
* This file Copyright (C) 2015-2016 Mnemosyne LLC
*
* It may be used under the GNU GPL versions 2 or 3
* or any future license endorsed by Mnemosyne LLC.
*
* $Id$
*/
#include <event2/event.h>
#include "transmission.h"
#include "file.h"
#include "net.h"
#include "utils.h"
#include "watchdir.h"
#include "libtransmission-test.h"
/***
****
***/
typedef struct callback_data
{
tr_watchdir_t dir;
char * name;
tr_watchdir_status result;
}
callback_data;
#define CB_DATA_STATIC_INIT { NULL, NULL, 0 }
struct event_base * ev_base = NULL;
extern struct timeval tr_watchdir_generic_interval;
extern unsigned int tr_watchdir_retry_limit;
extern struct timeval tr_watchdir_retry_start_interval;
extern struct timeval tr_watchdir_retry_max_interval;
const struct timeval FIFTY_MSEC = { 0, 50000 };
const struct timeval ONE_HUNDRED_MSEC = { 0, 100000 };
const struct timeval TWO_HUNDRED_MSEC = { 0, 200000 };
static void
process_events (void)
{
event_base_loopexit (ev_base, &TWO_HUNDRED_MSEC);
event_base_dispatch (ev_base);
}
static tr_watchdir_status
callback (tr_watchdir_t dir,
const char * name,
void * context)
{
callback_data * const data = context;
if (data->result != TR_WATCHDIR_RETRY)
{
data->dir = dir;
if (data->name != NULL)
tr_free (data->name);
data->name = tr_strdup (name);
}
return data->result;
}
static void
reset_callback_data (callback_data * data,
tr_watchdir_status result)
{
tr_free (data->name);
data->dir = NULL;
data->name = NULL;
data->result = result;
}
static void
create_file (const char * parent_dir,
const char * name)
{
char * const path = tr_buildPath (parent_dir, name, NULL);
libtest_create_file_with_string_contents (path, "");
tr_free (path);
}
static void
create_dir (const char * parent_dir,
const char * name)
{
char * const path = tr_buildPath (parent_dir, name, NULL);
tr_sys_dir_create (path, 0, 0700, NULL);
tr_free (path);
}
/***
****
***/
static int
test_construct (void)
{
char * const test_dir = libtest_sandbox_create ();
tr_watchdir_t wd;
ev_base = event_base_new();
wd = tr_watchdir_new (test_dir, &callback, NULL, ev_base);
check (wd != NULL);
check (tr_sys_path_is_same (test_dir, tr_watchdir_get_path (wd), NULL));
tr_watchdir_free (wd);
event_base_free (ev_base);
libtest_sandbox_destroy (test_dir);
tr_free (test_dir);
return 0;
}
static int
test_initial_scan (void)
{
char * const test_dir = libtest_sandbox_create ();
ev_base = event_base_new();
/* Speed up generic implementation */
tr_watchdir_generic_interval = ONE_HUNDRED_MSEC;
{
callback_data wd_data = CB_DATA_STATIC_INIT;
reset_callback_data (&wd_data, TR_WATCHDIR_ACCEPT);
tr_watchdir_t wd = tr_watchdir_new (test_dir, &callback, &wd_data, ev_base);
check (wd != NULL);
process_events ();
check_ptr_eq (NULL, wd_data.dir);
check_ptr_eq (NULL, wd_data.name);
tr_watchdir_free (wd);
reset_callback_data (&wd_data, TR_WATCHDIR_ACCEPT);
}
create_file (test_dir, "test");
{
callback_data wd_data = CB_DATA_STATIC_INIT;
reset_callback_data (&wd_data, TR_WATCHDIR_ACCEPT);
tr_watchdir_t wd = tr_watchdir_new (test_dir, &callback, &wd_data, ev_base);
check (wd != NULL);
process_events ();
check_ptr_eq (wd, wd_data.dir);
check_streq ("test", wd_data.name);
tr_watchdir_free (wd);
reset_callback_data (&wd_data, TR_WATCHDIR_ACCEPT);
}
event_base_free (ev_base);
libtest_sandbox_destroy (test_dir);
tr_free (test_dir);
return 0;
}
static int
test_watch (void)
{
char * const test_dir = libtest_sandbox_create ();
callback_data wd_data = CB_DATA_STATIC_INIT;
tr_watchdir_t wd;
ev_base = event_base_new();
/* Speed up generic implementation */
tr_watchdir_generic_interval = ONE_HUNDRED_MSEC;
reset_callback_data (&wd_data, TR_WATCHDIR_ACCEPT);
wd = tr_watchdir_new (test_dir, &callback, &wd_data, ev_base);
check (wd != NULL);
process_events ();
check_ptr_eq (NULL, wd_data.dir);
check_ptr_eq (NULL, wd_data.name);
create_file (test_dir, "test");
process_events ();
check_ptr_eq (wd, wd_data.dir);
check_streq ("test", wd_data.name);
reset_callback_data (&wd_data, TR_WATCHDIR_IGNORE);
create_file (test_dir, "test2");
process_events ();
check_ptr_eq (wd, wd_data.dir);
check_streq ("test2", wd_data.name);
reset_callback_data (&wd_data, TR_WATCHDIR_IGNORE);
create_dir (test_dir, "test3");
process_events ();
check_ptr_eq (NULL, wd_data.dir);
check_ptr_eq (NULL, wd_data.name);
tr_watchdir_free (wd);
reset_callback_data (&wd_data, TR_WATCHDIR_ACCEPT);
event_base_free (ev_base);
libtest_sandbox_destroy (test_dir);
tr_free (test_dir);
return 0;
}
static int
test_watch_two_dirs (void)
{
char * const test_dir = libtest_sandbox_create ();
char * const dir1 = tr_buildPath (test_dir, "a", NULL);
char * const dir2 = tr_buildPath (test_dir, "b", NULL);
callback_data wd1_data = CB_DATA_STATIC_INIT, wd2_data = CB_DATA_STATIC_INIT;
tr_watchdir_t wd1, wd2;
ev_base = event_base_new();
/* Speed up generic implementation */
tr_watchdir_generic_interval = ONE_HUNDRED_MSEC;
create_dir (dir1, NULL);
create_dir (dir2, NULL);
reset_callback_data (&wd1_data, TR_WATCHDIR_ACCEPT);
wd1 = tr_watchdir_new (dir1, &callback, &wd1_data, ev_base);
check (wd1 != NULL);
reset_callback_data (&wd2_data, TR_WATCHDIR_ACCEPT);
wd2 = tr_watchdir_new (dir2, &callback, &wd2_data, ev_base);
check (wd2 != NULL);
process_events ();
check_ptr_eq (NULL, wd1_data.dir);
check_ptr_eq (NULL, wd1_data.name);
check_ptr_eq (NULL, wd2_data.dir);
check_ptr_eq (NULL, wd2_data.name);
create_file (dir1, "test");
process_events ();
check_ptr_eq (wd1, wd1_data.dir);
check_streq ("test", wd1_data.name);
check_ptr_eq (NULL, wd2_data.dir);
check_ptr_eq (NULL, wd2_data.name);
reset_callback_data (&wd1_data, TR_WATCHDIR_ACCEPT);
reset_callback_data (&wd2_data, TR_WATCHDIR_ACCEPT);
create_file (dir2, "test2");
process_events ();
check_ptr_eq (NULL, wd1_data.dir);
check_ptr_eq (NULL, wd1_data.name);
check_ptr_eq (wd2, wd2_data.dir);
check_streq ("test2", wd2_data.name);
reset_callback_data (&wd1_data, TR_WATCHDIR_IGNORE);
reset_callback_data (&wd2_data, TR_WATCHDIR_IGNORE);
create_file (dir1, "test3");
create_file (dir2, "test4");
process_events ();
check_ptr_eq (wd1, wd1_data.dir);
check_streq ("test3", wd1_data.name);
check_ptr_eq (wd2, wd2_data.dir);
check_streq ("test4", wd2_data.name);
reset_callback_data (&wd1_data, TR_WATCHDIR_ACCEPT);
reset_callback_data (&wd2_data, TR_WATCHDIR_ACCEPT);
create_file (dir1, "test5");
create_dir (dir2, "test5");
process_events ();
check_ptr_eq (wd1, wd1_data.dir);
check_streq ("test5", wd1_data.name);
check_ptr_eq (NULL, wd2_data.dir);
check_ptr_eq (NULL, wd2_data.name);
reset_callback_data (&wd1_data, TR_WATCHDIR_ACCEPT);
reset_callback_data (&wd2_data, TR_WATCHDIR_ACCEPT);
create_dir (dir1, "test6");
create_file (dir2, "test6");
process_events ();
check_ptr_eq (NULL, wd1_data.dir);
check_ptr_eq (NULL, wd1_data.name);
check_ptr_eq (wd2, wd2_data.dir);
check_streq ("test6", wd2_data.name);
reset_callback_data (&wd1_data, TR_WATCHDIR_ACCEPT);
reset_callback_data (&wd2_data, TR_WATCHDIR_ACCEPT);
create_dir (dir1, "test7");
create_dir (dir2, "test7");
process_events ();
check_ptr_eq (NULL, wd1_data.dir);
check_ptr_eq (NULL, wd1_data.name);
check_ptr_eq (NULL, wd2_data.dir);
check_ptr_eq (NULL, wd2_data.name);
tr_watchdir_free (wd2);
reset_callback_data (&wd2_data, TR_WATCHDIR_ACCEPT);
tr_watchdir_free (wd1);
reset_callback_data (&wd1_data, TR_WATCHDIR_ACCEPT);
event_base_free (ev_base);
tr_free (dir2);
tr_free (dir1);
libtest_sandbox_destroy (test_dir);
tr_free (test_dir);
return 0;
}
static int
test_retry (void)
{
char * const test_dir = libtest_sandbox_create ();
callback_data wd_data = CB_DATA_STATIC_INIT;
tr_watchdir_t wd;
ev_base = event_base_new();
/* Speed up generic implementation */
tr_watchdir_generic_interval = ONE_HUNDRED_MSEC;
/* Tune retry logic */
tr_watchdir_retry_limit = 10;
tr_watchdir_retry_start_interval = FIFTY_MSEC;
tr_watchdir_retry_max_interval = tr_watchdir_retry_start_interval;
reset_callback_data (&wd_data, TR_WATCHDIR_RETRY);
wd = tr_watchdir_new (test_dir, &callback, &wd_data, ev_base);
check (wd != NULL);
process_events ();
check_ptr_eq (NULL, wd_data.dir);
check_ptr_eq (NULL, wd_data.name);
create_file (test_dir, "test");
process_events ();
check_ptr_eq (NULL, wd_data.dir);
check_ptr_eq (NULL, wd_data.name);
reset_callback_data (&wd_data, TR_WATCHDIR_ACCEPT);
process_events ();
check_ptr_eq (wd, wd_data.dir);
check_streq ("test", wd_data.name);
tr_watchdir_free (wd);
reset_callback_data (&wd_data, TR_WATCHDIR_ACCEPT);
event_base_free (ev_base);
libtest_sandbox_destroy (test_dir);
tr_free (test_dir);
return 0;
}
/***
****
***/
int
main (void)
{
const testFunc tests[] = { test_construct,
test_initial_scan,
test_watch,
test_watch_two_dirs,
test_retry };
tr_net_init ();
return runTests (tests, NUM_TESTS (tests));
}

View File

@ -0,0 +1,286 @@
/*
* This file Copyright (C) 2015-2016 Mnemosyne LLC
*
* It may be used under the GNU GPL versions 2 or 3
* or any future license endorsed by Mnemosyne LLC.
*
* $Id$
*/
#include <assert.h>
#include <errno.h>
#include <stddef.h> /* offsetof */
#include <stdlib.h> /* realloc () */
#include <process.h> /* _beginthreadex () */
#include <windows.h>
#include <event2/bufferevent.h>
#include <event2/event.h>
#include <event2/util.h>
#define __LIBTRANSMISSION_WATCHDIR_MODULE__
#include "transmission.h"
#include "log.h"
#include "net.h"
#include "utils.h"
#include "watchdir.h"
#include "watchdir-common.h"
/***
****
***/
#define log_error(...) (!tr_logLevelIsActive (TR_LOG_ERROR) ? (void) 0 : \
tr_logAddMessage (__FILE__, __LINE__, TR_LOG_ERROR, "watchdir:win32", __VA_ARGS__))
/***
****
***/
typedef struct tr_watchdir_win32
{
tr_watchdir_backend base;
HANDLE fd;
OVERLAPPED overlapped;
DWORD buffer[8 * 1024 / sizeof (DWORD)];
evutil_socket_t notify_pipe[2];
struct bufferevent * event;
HANDLE thread;
}
tr_watchdir_win32;
#define BACKEND_UPCAST(b) ((tr_watchdir_win32 *) (b))
#define WIN32_WATCH_MASK (FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_SIZE | FILE_NOTIFY_CHANGE_LAST_WRITE)
/***
****
***/
static unsigned int __stdcall
tr_watchdir_win32_thread (void * context)
{
const tr_watchdir_t handle = context;
tr_watchdir_win32 * const backend = BACKEND_UPCAST (tr_watchdir_get_backend (handle));
DWORD bytes_transferred;
while (GetOverlappedResultEx (backend->fd, &backend->overlapped, &bytes_transferred,
INFINITE, FALSE))
{
PFILE_NOTIFY_INFORMATION info = (PFILE_NOTIFY_INFORMATION) backend->buffer;
while (info->NextEntryOffset != 0)
*((BYTE **) &info) += info->NextEntryOffset;
info->NextEntryOffset = bytes_transferred - ((BYTE *) info - (BYTE *) backend->buffer);
send (backend->notify_pipe[1], (const char *) backend->buffer, bytes_transferred, 0);
if (!ReadDirectoryChangesW (backend->fd, backend->buffer, sizeof (backend->buffer), FALSE,
WIN32_WATCH_MASK, NULL, &backend->overlapped, NULL))
{
log_error ("Failed to read directory changes");
return 0;
}
}
if (GetLastError () != ERROR_OPERATION_ABORTED)
log_error ("Failed to wait for directory changes");
return 0;
}
static void
tr_watchdir_win32_on_first_scan (evutil_socket_t fd UNUSED,
short type UNUSED,
void * context)
{
const tr_watchdir_t handle = context;
tr_watchdir_scan (handle, NULL);
}
static void
tr_watchdir_win32_on_event (struct bufferevent * event,
void * context)
{
const tr_watchdir_t handle = context;
size_t nread;
size_t name_size = MAX_PATH * sizeof (WCHAR);
char * buffer = tr_malloc (sizeof (FILE_NOTIFY_INFORMATION) + name_size);
PFILE_NOTIFY_INFORMATION ev = (PFILE_NOTIFY_INFORMATION) buffer;
const size_t header_size = offsetof (FILE_NOTIFY_INFORMATION, FileName);
/* Read the size of the struct excluding name into buf. Guaranteed to have at
least sizeof (*ev) available */
while ((nread = bufferevent_read (event, ev, header_size)) != 0)
{
if (nread == (size_t) -1)
{
log_error ("Failed to read event: %s", tr_strerror (errno));
break;
}
if (nread != header_size)
{
log_error ("Failed to read event: expected %zu, got %zu bytes.",
header_size, nread);
break;
}
const size_t nleft = ev->NextEntryOffset - nread;
assert (ev->FileNameLength % sizeof (WCHAR) == 0);
assert (ev->FileNameLength > 0);
assert (ev->FileNameLength <= nleft);
if (nleft > name_size)
{
name_size = nleft;
buffer = tr_realloc (buffer, sizeof (FILE_NOTIFY_INFORMATION) + name_size);
ev = (PFILE_NOTIFY_INFORMATION) buffer;
}
/* Consume entire name into buffer */
if ((nread = bufferevent_read (event, buffer + header_size, nleft)) == (size_t) -1)
{
log_error ("Failed to read name: %s", tr_strerror (errno));
break;
}
if (nread != nleft)
{
log_error ("Failed to read name: expected %zu, got %zu bytes.", nleft, nread);
break;
}
if (ev->Action == FILE_ACTION_ADDED ||
ev->Action == FILE_ACTION_MODIFIED ||
ev->Action == FILE_ACTION_RENAMED_NEW_NAME)
{
char * name = tr_win32_native_to_utf8 (ev->FileName,
ev->FileNameLength / sizeof (WCHAR));
if (name != NULL)
{
tr_watchdir_process (handle, name);
tr_free (name);
}
}
}
tr_free (buffer);
}
static void
tr_watchdir_win32_free (tr_watchdir_backend * backend_base)
{
tr_watchdir_win32 * const backend = BACKEND_UPCAST (backend_base);
if (backend == NULL)
return;
assert (backend->base.free_func == &tr_watchdir_win32_free);
if (backend->fd != INVALID_HANDLE_VALUE)
CancelIoEx (backend->fd, &backend->overlapped);
if (backend->thread != NULL)
{
WaitForSingleObject (backend->thread, INFINITE);
CloseHandle (backend->thread);
}
if (backend->event != NULL)
bufferevent_free (backend->event);
if (backend->notify_pipe[0] != TR_BAD_SOCKET)
evutil_closesocket (backend->notify_pipe[0]);
if (backend->notify_pipe[1] != TR_BAD_SOCKET)
evutil_closesocket (backend->notify_pipe[1]);
if (backend->fd != INVALID_HANDLE_VALUE)
CloseHandle (backend->fd);
tr_free (backend);
}
tr_watchdir_backend *
tr_watchdir_win32_new (tr_watchdir_t handle)
{
const char * const path = tr_watchdir_get_path (handle);
wchar_t * wide_path;
tr_watchdir_win32 * backend;
backend = tr_new0 (tr_watchdir_win32, 1);
backend->base.free_func = &tr_watchdir_win32_free;
backend->fd = INVALID_HANDLE_VALUE;
backend->notify_pipe[0] = backend->notify_pipe[1] = TR_BAD_SOCKET;
if ((wide_path = tr_win32_utf8_to_native (path, -1)) == NULL)
{
log_error ("Failed to convert \"%s\" to native path", path);
goto fail;
}
if ((backend->fd = CreateFileW (wide_path, FILE_LIST_DIRECTORY,
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
NULL, OPEN_EXISTING,
FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED,
NULL)) == INVALID_HANDLE_VALUE)
{
log_error ("Failed to open directory \"%s\"", path);
goto fail;
}
tr_free (wide_path);
wide_path = NULL;
backend->overlapped.Pointer = handle;
if (!ReadDirectoryChangesW (backend->fd, backend->buffer, sizeof (backend->buffer), FALSE,
WIN32_WATCH_MASK, NULL, &backend->overlapped, NULL))
{
log_error ("Failed to read directory changes");
goto fail;
}
if (evutil_socketpair (AF_INET, SOCK_STREAM, 0, backend->notify_pipe) == -1)
{
log_error ("Failed to create notify pipe: %s", tr_strerror (errno));
goto fail;
}
if ((backend->event = bufferevent_socket_new (tr_watchdir_get_event_base (handle),
backend->notify_pipe[0], 0)) == NULL)
{
log_error ("Failed to create event buffer: %s", tr_strerror (errno));
goto fail;
}
bufferevent_setwatermark (backend->event, EV_READ, sizeof (FILE_NOTIFY_INFORMATION), 0);
bufferevent_setcb (backend->event, &tr_watchdir_win32_on_event, NULL, NULL, handle);
bufferevent_enable (backend->event, EV_READ);
if ((backend->thread = (HANDLE) _beginthreadex (NULL, 0, &tr_watchdir_win32_thread,
handle, 0, NULL)) == NULL)
{
log_error ("Failed to create thread");
goto fail;
}
/* Perform an initial scan on the directory */
if (event_base_once (tr_watchdir_get_event_base (handle), -1, EV_TIMEOUT,
&tr_watchdir_win32_on_first_scan, handle, NULL) == -1)
log_error ("Failed to perform initial scan: %s", tr_strerror (errno));
return BACKEND_DOWNCAST (backend);
fail:
tr_watchdir_win32_free (BACKEND_DOWNCAST (backend));
tr_free (wide_path);
return NULL;
}

384
libtransmission/watchdir.c Normal file
View File

@ -0,0 +1,384 @@
/*
* This file Copyright (C) 2015-2016 Mnemosyne LLC
*
* It may be used under the GNU GPL versions 2 or 3
* or any future license endorsed by Mnemosyne LLC.
*
* $Id$
*/
#include <assert.h>
#include <string.h> /* strcmp () */
#include <event2/event.h>
#include <event2/util.h>
#define __LIBTRANSMISSION_WATCHDIR_MODULE__
#include "transmission.h"
#include "error.h"
#include "error-types.h"
#include "file.h"
#include "log.h"
#include "ptrarray.h"
#include "utils.h"
#include "watchdir.h"
#include "watchdir-common.h"
/***
****
***/
#define log_debug(...) (!tr_logLevelIsActive (TR_LOG_DEBUG) ? (void) 0 : \
tr_logAddMessage (__FILE__, __LINE__, TR_LOG_DEBUG, "watchdir", __VA_ARGS__))
#define log_error(...) (!tr_logLevelIsActive (TR_LOG_ERROR) ? (void) 0 : \
tr_logAddMessage (__FILE__, __LINE__, TR_LOG_ERROR, "watchdir", __VA_ARGS__))
/***
****
***/
struct tr_watchdir
{
char * path;
tr_watchdir_cb callback;
void * callback_user_data;
struct event_base * event_base;
tr_watchdir_backend * backend;
tr_ptrArray active_retries;
};
/***
****
***/
static bool
is_regular_file (const char * dir,
const char * name)
{
char * const path = tr_buildPath (dir, name, NULL);
tr_sys_path_info path_info;
tr_error * error = NULL;
bool ret;
if ((ret = tr_sys_path_get_info (path, 0, &path_info, &error)))
{
ret = path_info.type == TR_SYS_PATH_IS_FILE;
}
else
{
if (!TR_ERROR_IS_ENOENT (error->code))
log_error ("Failed to get type of \"%s\" (%d): %s", path, error->code,
error->message);
tr_error_free (error);
}
tr_free (path);
return ret;
}
static const char *
watchdir_status_to_string (tr_watchdir_status status)
{
switch (status)
{
case TR_WATCHDIR_ACCEPT:
return "accept";
case TR_WATCHDIR_IGNORE:
return "ignore";
case TR_WATCHDIR_RETRY:
return "retry";
default:
return "???";
}
}
static tr_watchdir_status
tr_watchdir_process_impl (tr_watchdir_t handle,
const char * name)
{
/* File may be gone while we're retrying */
if (!is_regular_file (tr_watchdir_get_path (handle), name))
return TR_WATCHDIR_IGNORE;
const tr_watchdir_status ret = handle->callback (handle, name, handle->callback_user_data);
assert (ret == TR_WATCHDIR_ACCEPT ||
ret == TR_WATCHDIR_IGNORE ||
ret == TR_WATCHDIR_RETRY);
log_debug ("Callback decided to %s file \"%s\"", watchdir_status_to_string (ret), name);
return ret;
}
/***
****
***/
typedef struct tr_watchdir_retry
{
tr_watchdir_t handle;
char * name;
unsigned int counter;
struct event * timer;
struct timeval interval;
}
tr_watchdir_retry;
/* Non-static and mutable for unit tests */
unsigned int tr_watchdir_retry_limit = 3;
struct timeval tr_watchdir_retry_start_interval = { 1, 0 };
struct timeval tr_watchdir_retry_max_interval = { 10, 0 };
#define tr_watchdir_retries_init(r) (void) 0
#define tr_watchdir_retries_destroy(r) tr_ptrArrayDestruct ((r), (PtrArrayForeachFunc) &tr_watchdir_retry_free)
#define tr_watchdir_retries_insert(r, v) tr_ptrArrayInsertSorted ((r), (v), &compare_retry_names)
#define tr_watchdir_retries_remove(r, v) tr_ptrArrayRemoveSortedPointer ((r), (v), &compare_retry_names)
#define tr_watchdir_retries_find(r, v) tr_ptrArrayFindSorted ((r), (v), &compare_retry_names)
static int
compare_retry_names (const void * a,
const void * b)
{
return strcmp (((tr_watchdir_retry *) a)->name, ((tr_watchdir_retry *) b)->name);
}
static void
tr_watchdir_retry_free (tr_watchdir_retry * retry);
static void
tr_watchdir_on_retry_timer (evutil_socket_t fd UNUSED,
short type UNUSED,
void * context)
{
assert (context != NULL);
tr_watchdir_retry * const retry = context;
const tr_watchdir_t handle = retry->handle;
if (tr_watchdir_process_impl (handle, retry->name) == TR_WATCHDIR_RETRY)
{
if (++retry->counter < tr_watchdir_retry_limit)
{
evutil_timeradd (&retry->interval, &retry->interval, &retry->interval);
if (evutil_timercmp (&retry->interval, &tr_watchdir_retry_max_interval, >))
retry->interval = tr_watchdir_retry_max_interval;
evtimer_del (retry->timer);
evtimer_add (retry->timer, &retry->interval);
return;
}
log_error ("Failed to add (corrupted?) torrent file: %s", retry->name);
}
tr_watchdir_retries_remove (&handle->active_retries, retry);
tr_watchdir_retry_free (retry);
}
static tr_watchdir_retry *
tr_watchdir_retry_new (tr_watchdir_t handle,
const char * name)
{
tr_watchdir_retry * retry;
retry = tr_new0 (tr_watchdir_retry, 1);
retry->handle = handle;
retry->name = tr_strdup (name);
retry->timer = evtimer_new (handle->event_base, &tr_watchdir_on_retry_timer, retry);
retry->interval = tr_watchdir_retry_start_interval;
evtimer_add (retry->timer, &retry->interval);
return retry;
}
static void
tr_watchdir_retry_free (tr_watchdir_retry * retry)
{
if (retry == NULL)
return;
if (retry->timer != NULL)
{
evtimer_del (retry->timer);
event_free (retry->timer);
}
tr_free (retry->name);
tr_free (retry);
}
static void
tr_watchdir_retry_restart (tr_watchdir_retry * retry)
{
assert (retry != NULL);
evtimer_del (retry->timer);
retry->counter = 0;
retry->interval = tr_watchdir_retry_start_interval;
evtimer_add (retry->timer, &retry->interval);
}
/***
****
***/
tr_watchdir_t
tr_watchdir_new (const char * path,
tr_watchdir_cb callback,
void * callback_user_data,
struct event_base * event_base)
{
tr_watchdir_t handle;
handle = tr_new0 (struct tr_watchdir, 1);
handle->path = tr_strdup (path);
handle->callback = callback;
handle->callback_user_data = callback_user_data;
handle->event_base = event_base;
tr_watchdir_retries_init (&handle->active_retries);
#ifdef WITH_INOTIFY
if (handle->backend == NULL)
handle->backend = tr_watchdir_inotify_new (handle);
#endif
#ifdef WITH_KQUEUE
if (handle->backend == NULL)
handle->backend = tr_watchdir_kqueue_new (handle);
#endif
#ifdef _WIN32
if (handle->backend == NULL)
handle->backend = tr_watchdir_win32_new (handle);
#endif
if (handle->backend == NULL)
handle->backend = tr_watchdir_generic_new (handle);
if (handle->backend == NULL)
{
tr_watchdir_free (handle);
handle = NULL;
}
else
{
assert (handle->backend->free_func != NULL);
}
return handle;
}
void
tr_watchdir_free (tr_watchdir_t handle)
{
if (handle == NULL)
return;
tr_watchdir_retries_destroy (&handle->active_retries);
if (handle->backend != NULL)
handle->backend->free_func (handle->backend);
tr_free (handle->path);
tr_free (handle);
}
const char *
tr_watchdir_get_path (tr_watchdir_t handle)
{
assert (handle != NULL);
return handle->path;
}
tr_watchdir_backend *
tr_watchdir_get_backend (tr_watchdir_t handle)
{
assert (handle != NULL);
return handle->backend;
}
struct event_base *
tr_watchdir_get_event_base (tr_watchdir_t handle)
{
assert (handle != NULL);
return handle->event_base;
}
/***
****
***/
void
tr_watchdir_process (tr_watchdir_t handle,
const char * name)
{
const tr_watchdir_retry search_key = { .name = (char *) name };
tr_watchdir_retry * existing_retry;
assert (handle != NULL);
if ((existing_retry = tr_watchdir_retries_find (&handle->active_retries, &search_key)) != NULL)
{
tr_watchdir_retry_restart (existing_retry);
return;
}
if (tr_watchdir_process_impl (handle, name) == TR_WATCHDIR_RETRY)
{
tr_watchdir_retry * retry = tr_watchdir_retry_new (handle, name);
tr_watchdir_retries_insert (&handle->active_retries, retry);
}
}
void
tr_watchdir_scan (tr_watchdir_t handle,
tr_ptrArray * dir_entries)
{
tr_sys_dir_t dir;
const char * name;
tr_ptrArray new_dir_entries = TR_PTR_ARRAY_INIT_STATIC;
const PtrArrayCompareFunc name_compare_func = (PtrArrayCompareFunc) &strcmp;
tr_error * error = NULL;
if ((dir = tr_sys_dir_open (handle->path, &error)) == TR_BAD_SYS_DIR)
{
log_error ("Failed to open directory \"%s\" (%d): %s", handle->path,
error->code, error->message);
tr_error_free (error);
return;
}
while ((name = tr_sys_dir_read_name (dir, &error)) != NULL)
{
if (strcmp (name, ".") == 0 || strcmp (name, "..") == 0)
continue;
if (dir_entries != NULL)
{
tr_ptrArrayInsertSorted (&new_dir_entries, tr_strdup (name), name_compare_func);
if (tr_ptrArrayFindSorted (dir_entries, name, name_compare_func) != NULL)
continue;
}
tr_watchdir_process (handle, name);
}
if (error != NULL)
{
log_error ("Failed to read directory \"%s\" (%d): %s", handle->path,
error->code, error->message);
tr_error_free (error);
}
tr_sys_dir_close (dir, NULL);
if (dir_entries != NULL)
{
tr_ptrArrayDestruct (dir_entries, &tr_free);
*dir_entries = new_dir_entries;
}
}

View File

@ -0,0 +1,48 @@
/*
* This file Copyright (C) 2015-2016 Mnemosyne LLC
*
* It may be used under the GNU GPL versions 2 or 3
* or any future license endorsed by Mnemosyne LLC.
*
* $Id$
*/
#ifndef TR_WATCHDIR_H
#define TR_WATCHDIR_H
#ifdef __cplusplus
extern "C" {
#endif
struct event_base;
typedef struct tr_watchdir * tr_watchdir_t;
typedef enum
{
TR_WATCHDIR_ACCEPT,
TR_WATCHDIR_IGNORE,
TR_WATCHDIR_RETRY
}
tr_watchdir_status;
typedef tr_watchdir_status (* tr_watchdir_cb) (tr_watchdir_t handle,
const char * name,
void * user_data);
/* ... */
tr_watchdir_t tr_watchdir_new (const char * path,
tr_watchdir_cb callback,
void * callback_user_data,
struct event_base * event_base);
void tr_watchdir_free (tr_watchdir_t handle);
const char * tr_watchdir_get_path (tr_watchdir_t handle);
#ifdef __cplusplus
}
#endif
#endif /* TR_WATCHDIR_H */