source: trunk/daemon/watch.c @ 10132

Last change on this file since 10132 was 9868, checked in by charles, 13 years ago

happy new year!

  • Property svn:keywords set to Date Rev Author Id
File size: 5.7 KB
Line 
1/*
2 * This file Copyright (C) 2009-2010 Mnemosyne LLC
3 *
4 * This file is licensed by the GPL version 2.  Works owned by the
5 * Transmission project are granted a special exemption to clause 2(b)
6 * so that the bulk of its code can remain under the MIT license.
7 * This exemption does not extend to derived works not owned by
8 * the Transmission project.
9 *
10 * $Id: watch.c 9868 2010-01-04 21:00:47Z charles $
11 */
12#ifdef WITH_INOTIFY
13  #include <sys/inotify.h>
14  #include <sys/select.h>
15  #include <unistd.h> /* close */
16#else
17  #include <sys/types.h> /* stat */
18  #include <sys/stat.h> /* stat */
19  #include <dirent.h> /* readdir */
20  #include <event.h> /* evbuffer */
21#endif
22
23#include <errno.h>
24#include <string.h> /* strstr */
25
26#include <libtransmission/transmission.h>
27#include <libtransmission/utils.h> /* tr_buildPath(), tr_inf() */
28#include "watch.h"
29
30struct dtr_watchdir
31{
32    tr_session * session;
33    char * dir;
34    dtr_watchdir_callback * callback;
35#ifdef WITH_INOTIFY
36    int inotify_fd;
37#else /* readdir implementation */
38    time_t lastTimeChecked;
39    struct evbuffer * lastFiles;
40#endif
41};
42
43/***
44****  INOTIFY IMPLEMENTATION
45***/
46
47#if defined(WITH_INOTIFY)
48
49/* how many inotify events to try to batch into a single read */
50#define EVENT_BATCH_COUNT 50
51/* size of the event structure, not counting name */
52#define EVENT_SIZE  (sizeof (struct inotify_event))
53/* reasonable guess as to size of 50 events */
54#define BUF_LEN (EVENT_BATCH_COUNT * (EVENT_SIZE + 16) + 2048)
55
56#define DTR_INOTIFY_MASK (IN_CREATE|IN_CLOSE_WRITE|IN_MOVED_TO)
57
58static void
59watchdir_new_impl( dtr_watchdir * w )
60{
61    int i;
62    w->inotify_fd = inotify_init( );
63    tr_inf( "Using inotify to watch directory \"%s\"", w->dir );
64    i = inotify_add_watch( w->inotify_fd, w->dir, DTR_INOTIFY_MASK );
65    if( i < 0 )
66        tr_err( "Unable to watch \"%s\": %s", w->dir, strerror (errno) );
67}
68static void
69watchdir_free_impl( dtr_watchdir * w )
70{
71    inotify_rm_watch( w->inotify_fd, DTR_INOTIFY_MASK );
72    close( w->inotify_fd );
73}
74static void
75watchdir_update_impl( dtr_watchdir * w )
76{
77    int ret;
78    fd_set rfds;
79    struct timeval time;
80    const int fd = w->inotify_fd;
81
82    /* timeout after one second */
83    time.tv_sec = 1;
84    time.tv_usec = 0;
85
86    /* make the fd_set hold the inotify fd */
87    FD_ZERO( &rfds );
88    FD_SET( fd, &rfds );
89
90    /* check for added files */
91    ret = select( fd+1, &rfds, NULL, NULL, &time );
92    if( ret < 0 ) {
93        perror( "select" );
94    } else if( !ret ) {
95        /* timed out! */
96    } else if( FD_ISSET( fd, &rfds ) ) {
97        int i = 0;
98        char buf[BUF_LEN];
99        int len = read( fd, buf, sizeof( buf ) );
100        while (i < len) {
101            struct inotify_event * event = (struct inotify_event *) &buf[i];
102            tr_inf( "Found new .torrent file \"%s\" in watchdir \"%s\"", event->name, w->dir );
103            w->callback( w->session, w->dir, event->name );
104            i += EVENT_SIZE +  event->len;
105        }
106    }
107}
108
109#else /* WITH_INOTIFY */
110
111/***
112****  READDIR IMPLEMENTATION
113***/
114
115#define WATCHDIR_POLL_INTERVAL_SECS 10
116
117#define FILE_DELIMITER '\0'
118
119static void
120watchdir_new_impl( dtr_watchdir * w UNUSED )
121{
122    tr_inf( "Using readdir to watch directory \"%s\"", w->dir );
123    w->lastFiles = evbuffer_new( );
124}
125static void
126watchdir_free_impl( dtr_watchdir * w )
127{
128    evbuffer_free( w->lastFiles );
129}
130static void
131add_file_to_list( struct evbuffer * buf, const char * filename, size_t len )
132{
133    const char delimiter = FILE_DELIMITER;
134    evbuffer_add( buf, &delimiter, 1 );
135    evbuffer_add( buf, filename, len );
136    evbuffer_add( buf, &delimiter, 1 );
137}
138static tr_bool
139is_file_in_list( struct evbuffer * buf, const char * filename, size_t len )
140{
141    tr_bool in_list;
142    struct evbuffer * test = evbuffer_new( );
143    add_file_to_list( test, filename, len );
144    in_list = evbuffer_find( buf, EVBUFFER_DATA( test ), EVBUFFER_LENGTH( test ) ) != NULL;
145    evbuffer_free( test );
146    return in_list;
147}
148static void
149watchdir_update_impl( dtr_watchdir * w )
150{
151    struct stat sb;
152    DIR * odir;
153    const time_t oldTime = w->lastTimeChecked;
154    const char * dirname = w->dir;
155    struct evbuffer * curFiles = evbuffer_new( );
156
157    if ( ( oldTime + WATCHDIR_POLL_INTERVAL_SECS < time( NULL ) )
158         && !stat( dirname, &sb )
159         && S_ISDIR( sb.st_mode )
160         && (( odir = opendir( dirname ))) )
161    {
162        struct dirent * d;
163
164        for( d = readdir( odir ); d != NULL; d = readdir( odir ) )
165        {
166            size_t len;
167
168            if( !d->d_name || *d->d_name=='.' ) /* skip dotfiles */
169                continue;
170            if( !strstr( d->d_name, ".torrent" ) ) /* skip non-torrents */
171                continue;
172
173            len = strlen( d->d_name );
174            add_file_to_list( curFiles, d->d_name, len );
175
176            /* if this file wasn't here last time, try adding it */
177            if( !is_file_in_list( w->lastFiles, d->d_name, len ) ) {
178                tr_inf( "Found new .torrent file \"%s\" in watchdir \"%s\"", d->d_name, w->dir );
179                w->callback( w->session, w->dir, d->d_name );
180            }
181        }
182
183        closedir( odir );
184        w->lastTimeChecked = time( NULL );
185        evbuffer_free( w->lastFiles );
186        w->lastFiles = curFiles;
187    }
188}
189
190#endif
191
192/***
193****
194***/
195
196dtr_watchdir*
197dtr_watchdir_new( tr_session * session, const char * dir, dtr_watchdir_callback * callback )
198{
199    dtr_watchdir * w = tr_new0( dtr_watchdir, 1 );
200
201    w->session = session;
202    w->dir = tr_strdup( dir );
203    w->callback = callback;
204
205    watchdir_new_impl( w );
206
207    return w;
208}
209
210void
211dtr_watchdir_update( dtr_watchdir * w )
212{
213    if( w != NULL )
214        watchdir_update_impl( w );
215}
216
217void
218dtr_watchdir_free( dtr_watchdir * w )
219{
220    if( w != NULL )
221    {
222        watchdir_free_impl( w );
223        tr_free( w->dir );
224        tr_free( w );
225    }
226}
Note: See TracBrowser for help on using the repository browser.