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 */; };
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 */; };
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; };
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>"; };
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>";
C1F690FE1AD0628400D95CF0 /* daemon.h */,
C1F690FC1AD0627500D95CF0 /* daemon-posix.c */,
BEFC1C140C07756200B0BB3C /* remote.c */,
- A2D597280F5AE49E0001AB3C /* watch.c */,
- A2D597290F5AE49E0001AB3C /* watch.h */,
);
name = daemon;
sourceTree = "<group>";
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 */,
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 */,
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 */,
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 */,
buildActionMask = 2147483647;
files = (
BEFC1C1A0C07756200B0BB3C /* daemon.c in Sources */,
- A2D5972A0F5AE49E0001AB3C /* watch.c in Sources */,
C1F690FD1AD0627500D95CF0 /* daemon-posix.c in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
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])
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()
daemon.c
daemon-posix.c
daemon-win32.c
- watch.c
)
if(WIN32)
set(${PROJECT_NAME}_HEADERS
daemon.h
- watch.h
)
tr_win32_app_info(${PROJECT_NAME}_WIN32_RC_FILE
@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
#include <unistd.h> /* getpid */
#endif
-#include <event2/buffer.h>
#include <event2/event.h>
#include <libtransmission/transmission.h>
#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>
#endif
#include "daemon.h"
-#include "watch.h"
#define MY_NAME "transmission-daemon"
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);
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);
}
}
}
+ else
+ {
+ err = TR_PARSE_ERR;
+ }
tr_ctorFree (ctor);
tr_free (filename);
+
+ return err == TR_PARSE_ERR ? TR_WATCHDIR_RETRY : TR_WATCHDIR_ACCEPT;
}
static 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 ();
}
{
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;
&& *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;
}
}
/* 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));
sd_notify( 0, "STATUS=Closing transmission session...\n" );
printf ("Closing transmission session...");
+ tr_watchdir_free (watchdir);
+
if (status_ev)
{
event_del(status_ev);
event_base_free(ev_base);
tr_sessionSaveSettings (mySession, configDir, settings);
- dtr_watchdir_free (watchdir);
tr_sessionClose (mySession);
pumpLogMessages (logfile);
printf (" done.\n");
+++ /dev/null
-/*
- * 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);
- }
-}
+++ /dev/null
-/*
- * 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
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
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
transmission.h
utils.h
variant.h
+ watchdir.h
web.h
${PROJECT_BINARY_DIR}/version.h
)
variant-common.h
verify.h
version.h
+ watchdir-common.h
webseed.h
)
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)
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
variant-common.h \
verify.h \
version.h \
+ watchdir.h \
+ watchdir-common.h \
web.h \
webseed.h
session-test \
tr-getopt-test \
utils-test \
- variant-test
+ variant-test \
+ watchdir-test
noinst_PROGRAMS = $(TESTS)
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}
--- /dev/null
+/*
+ * 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 */
--- /dev/null
+/*
+ * 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;
+}
--- /dev/null
+/*
+ * 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;
+}
--- /dev/null
+/*
+ * 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;
+}
--- /dev/null
+/*
+ * 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));
+}
--- /dev/null
+/*
+ * 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;
+}
--- /dev/null
+/*
+ * 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;
+ }
+}
--- /dev/null
+/*
+ * 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 */