source: trunk/daemon/watch.c @ 7979

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

(trunk daemon) #1882: take KyleK's suggestion of using inotify when possible. As he writes, "the opendir() approach will basically prevent my NAS drives to go to standby."

File size: 4.7 KB
Line 
1/*
2 * This file Copyright (C) 2009 Charles Kerr <charles@transmissionbt.com>
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:$
11 */
12
13#undef WITH_INOTIFY
14
15#ifdef WITH_INOTIFY
16  #include <sys/inotify.h>
17  #include <sys/select.h>
18  #include <unistd.h> /* close */
19#else
20  #include <sys/types.h> /* stat */
21  #include <sys/stat.h> /* stat */
22  #include <dirent.h> /* readdir */
23#endif
24
25#include <errno.h>
26#include <string.h> /* strstr */
27
28#include <libtransmission/transmission.h>
29#include <libtransmission/utils.h> /* tr_buildPath */
30#include "watch.h"
31
32struct dtr_watchdir
33{
34    tr_session * session;
35    char * dir;
36    dtr_watchdir_callback * callback;
37#ifdef WITH_INOTIFY
38    int inotify_fd;
39#else /* readdir implementation */
40    time_t lastTimeChecked;
41#endif
42};
43
44/***
45****  INOTIFY IMPLEMENTATION
46***/
47
48#if defined(WITH_INOTIFY)
49
50/* how many inotify events to try to batch into a single read */
51#define EVENT_BATCH_COUNT 50
52/* size of the event structure, not counting name */
53#define EVENT_SIZE  (sizeof (struct inotify_event))
54/* reasonable guess as to size of 50 events */
55#define BUF_LEN (EVENT_BATCH_COUNT * (EVENT_SIZE + 16) + 2048)
56
57#define DTR_INOTIFY_MASK (IN_CREATE|IN_CLOSE_WRITE|IN_MOVED_TO)
58
59static void
60watchdir_new_impl( dtr_watchdir * w )
61{
62    int i;
63    w->inotify_fd = inotify_init( );
64    tr_inf( "Using inotify to watch directory \"%s\"", w->dir );
65    i = inotify_add_watch( w->inotify_fd, w->dir, DTR_INOTIFY_MASK );
66    if( i < 0 )
67        tr_err( "Unable to watch \"%s\": %s", w->dir, strerror (errno) );
68}
69static void
70watchdir_free_impl( dtr_watchdir * w )
71{
72    inotify_rm_watch( w->inotify_fd, DTR_INOTIFY_MASK );
73    close( w->inotify_fd );
74}
75static void
76watchdir_update_impl( dtr_watchdir * w )
77{
78    int ret;
79    fd_set rfds;
80    struct timeval time;
81    const int fd = w->inotify_fd;
82
83    /* timeout after one second */
84    time.tv_sec = 1;
85    time.tv_usec = 0;
86
87    /* make the fd_set hold the inotify fd */
88    FD_ZERO( &rfds );
89    FD_SET( fd, &rfds );
90
91    /* check for added files */
92    ret = select( fd+1, &rfds, NULL, NULL, &time );
93    if( ret < 0 ) {
94        perror( "select" );
95    } else if( !ret ) {
96        /* timed out! */
97    } else if( FD_ISSET( fd, &rfds ) ) {
98        int i = 0;
99        char buf[BUF_LEN];
100        int len = read( fd, buf, sizeof( buf ) );
101        while (i < len) {
102            struct inotify_event * event = (struct inotify_event *) &buf[i];
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
117static void
118watchdir_new_impl( dtr_watchdir * w UNUSED )
119{
120    tr_inf( "Using readdir to watch directory \"%s\"", w->dir );
121}
122static void
123watchdir_free_impl( dtr_watchdir * w UNUSED )
124{
125    /* NOOP */
126}
127static void
128watchdir_update_impl( dtr_watchdir * w )
129{
130    struct stat sb;
131    DIR * odir;
132    const time_t oldTime = w->lastTimeChecked;
133    const char * dirname = w->dir;
134
135    if ( ( oldTime + WATCHDIR_POLL_INTERVAL_SECS < time( NULL ) )
136         && !stat( dirname, &sb )
137         && S_ISDIR( sb.st_mode )
138         && (( odir = opendir( dirname ))) )
139    {
140        struct dirent * d;
141
142        for( d = readdir( odir ); d != NULL; d = readdir( odir ) )
143        {
144            char * filename;
145
146            if( !d->d_name || *d->d_name=='.' ) /* skip dotfiles */
147                continue;
148            if( !strstr( d->d_name, ".torrent" ) ) /* skip non-torrents */
149                continue;
150
151            /* if the file's changed since our last pass, try adding it */
152            filename = tr_buildPath( dirname, d->d_name, NULL );
153            if( !stat( filename, &sb ) && sb.st_mtime >= oldTime )
154                w->callback( w->session, w->dir, d->d_name );
155            tr_free( filename );
156        }
157
158        closedir( odir );
159
160        w->lastTimeChecked = time( NULL );
161    }
162}
163
164#endif
165
166/***
167****
168***/
169
170dtr_watchdir*
171dtr_watchdir_new( tr_session * session, const char * dir, dtr_watchdir_callback * callback )
172{
173    dtr_watchdir * w = tr_new0( dtr_watchdir, 1 );
174
175    w->session = session;
176    w->dir = tr_strdup( dir );
177    w->callback = callback;
178
179    watchdir_new_impl( w );
180
181    return w;
182}
183
184void
185dtr_watchdir_update( dtr_watchdir * w )
186{
187    if( w != NULL )
188        watchdir_update_impl( w );
189}
190
191void
192dtr_watchdir_free( dtr_watchdir * w )
193{
194    if( w != NULL )
195    {
196        watchdir_free_impl( w );
197        tr_free( w->dir );
198        tr_free( w );
199    }
200}
Note: See TracBrowser for help on using the repository browser.