mirror of
https://github.com/transmission/transmission
synced 2024-12-26 09:37:56 +00:00
11d60307cf
When inotify isn't available, watch.c uses readdir() to look for new .torrent files. It keeps a list of old .torrent files internally so that it doesn't try to keep re-adding the same file. This list is stored in an evbuffer. As part of the libevent2 upgrade (#3836), r11594 changed how the buffer is searched by replacing the (deprecated) event_find() call with libevent2's evbuffer_search(). However the latter's semantics are different in that searching stops when '\0' is reached, so '\0' is no longer a good filename delimiter. Fixed by changing watch.c's internal delimiter from '\0' to '\t' ... so all those torrents with tabs in their filenames had better watch out.
275 lines
6.5 KiB
C
275 lines
6.5 KiB
C
/*
|
|
* This file Copyright (C) Mnemosyne LLC
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License version 2
|
|
* as published by the Free Software Foundation.
|
|
*
|
|
* http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
|
|
*
|
|
* $Id$
|
|
*/
|
|
#ifdef WITH_INOTIFY
|
|
#include <sys/inotify.h>
|
|
#include <sys/select.h>
|
|
#include <unistd.h> /* close */
|
|
#else
|
|
#include <sys/types.h> /* stat */
|
|
#include <sys/stat.h> /* stat */
|
|
#include <event2/buffer.h> /* evbuffer */
|
|
#endif
|
|
|
|
#include <errno.h>
|
|
#include <string.h> /* strstr */
|
|
|
|
#include <dirent.h> /* readdir */
|
|
|
|
#include <libtransmission/transmission.h>
|
|
#include <libtransmission/utils.h> /* tr_buildPath(), tr_inf() */
|
|
#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_ONLYDIR)
|
|
|
|
static void
|
|
watchdir_new_impl( dtr_watchdir * w )
|
|
{
|
|
int i;
|
|
DIR * odir;
|
|
w->inotify_fd = inotify_init( );
|
|
|
|
if( w->inotify_fd < 0 )
|
|
{
|
|
i = -1;
|
|
}
|
|
else
|
|
{
|
|
tr_inf( "Using inotify to watch directory \"%s\"", w->dir );
|
|
i = inotify_add_watch( w->inotify_fd, w->dir, DTR_INOTIFY_MASK );
|
|
}
|
|
|
|
if( i < 0 )
|
|
{
|
|
tr_err( "Unable to watch \"%s\": %s", w->dir, strerror( errno ) );
|
|
}
|
|
else if(( odir = opendir( w->dir )))
|
|
{
|
|
struct dirent * d;
|
|
|
|
while(( d = readdir( odir )))
|
|
{
|
|
const char * name = d->d_name;
|
|
|
|
if( !tr_str_has_suffix( name, ".torrent" ) ) /* skip non-torrents */
|
|
continue;
|
|
|
|
tr_inf( "Found new .torrent file \"%s\" in watchdir \"%s\"", name, w->dir );
|
|
w->callback( w->session, w->dir, name );
|
|
}
|
|
|
|
closedir( odir );
|
|
}
|
|
|
|
}
|
|
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_inf( "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_inf( "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 tr_bool
|
|
is_file_in_list( struct evbuffer * buf, const char * filename, size_t len )
|
|
{
|
|
tr_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 )
|
|
{
|
|
struct stat sb;
|
|
DIR * 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 ) )
|
|
&& !stat( dirname, &sb )
|
|
&& S_ISDIR( sb.st_mode )
|
|
&& (( odir = opendir( dirname ))) )
|
|
{
|
|
struct dirent * d;
|
|
|
|
for( d = readdir( odir ); d != NULL; d = readdir( odir ) )
|
|
{
|
|
size_t len;
|
|
const char * name = d->d_name;
|
|
|
|
if( !name || *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_inf( "Found new .torrent file \"%s\" in watchdir \"%s\"", name, w->dir );
|
|
w->callback( w->session, w->dir, name );
|
|
}
|
|
}
|
|
|
|
closedir( odir );
|
|
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 );
|
|
}
|
|
}
|