#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:
parent
de304e8a35
commit
ea48360212
|
@ -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;
|
||||
|
|
47
configure.ac
47
configure.ac
|
@ -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])
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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");
|
||||
|
|
267
daemon/watch.c
267
daemon/watch.c
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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
|
|
@ -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)
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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 */
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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));
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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 */
|
Loading…
Reference in New Issue