source: trunk/daemon/watch.c @ 10783

Last change on this file since 10783 was 10783, checked in by charles, 12 years ago

(trunk) #2983: add command-line utilities for creating .torrent files, for editing passkeys, for adding/removing trackers, etc.

  • Property svn:keywords set to Date Rev Author Id
File size: 6.4 KB
Line 
1/*
2 * This file Copyright (C) 2009-2010 Mnemosyne LLC
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License version 2
6 * as published by the Free Software Foundation.
7 *
8 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
9 *
10 * $Id: watch.c 10783 2010-06-16 14:27:24Z 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 <event.h> /* evbuffer */
20#endif
21
22#include <errno.h>
23#include <string.h> /* strstr */
24
25#include <dirent.h> /* readdir */
26
27#include <libtransmission/transmission.h>
28#include <libtransmission/utils.h> /* tr_buildPath(), tr_inf() */
29#include "watch.h"
30
31struct dtr_watchdir
32{
33    tr_session * session;
34    char * dir;
35    dtr_watchdir_callback * callback;
36#ifdef WITH_INOTIFY
37    int inotify_fd;
38#else /* readdir implementation */
39    time_t lastTimeChecked;
40    struct evbuffer * lastFiles;
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_CLOSE_WRITE|IN_MOVED_TO|IN_ONLYDIR)
58
59static void
60watchdir_new_impl( dtr_watchdir * w )
61{
62    int i;
63    DIR * odir;
64    w->inotify_fd = inotify_init( );
65
66    if( w->inotify_fd < 0 )
67    {
68        i = -1;
69    }
70    else
71    {
72        tr_inf( "Using inotify to watch directory \"%s\"", w->dir );
73        i = inotify_add_watch( w->inotify_fd, w->dir, DTR_INOTIFY_MASK );
74    }
75
76    if( i < 0 )
77    {
78        tr_err( "Unable to watch \"%s\": %s", w->dir, strerror( errno ) );
79    }
80    else if(( odir = opendir( w->dir )))
81    {
82        struct dirent * d;
83
84        while(( d = readdir( odir )))
85        {
86            const char * name = d->d_name;
87
88            if( !tr_str_has_suffix( name, ".torrent" ) ) /* skip non-torrents */
89                continue;
90
91            tr_inf( "Found new .torrent file \"%s\" in watchdir \"%s\"", name, w->dir );
92            w->callback( w->session, w->dir, name );
93        }
94
95        closedir( odir );
96    }
97
98}
99static void
100watchdir_free_impl( dtr_watchdir * w )
101{
102    if( w->inotify_fd >= 0 )
103    {
104        inotify_rm_watch( w->inotify_fd, DTR_INOTIFY_MASK );
105
106        close( w->inotify_fd );
107    }
108}
109static void
110watchdir_update_impl( dtr_watchdir * w )
111{
112    int ret;
113    fd_set rfds;
114    struct timeval time;
115    const int fd = w->inotify_fd;
116
117    /* timeout after one second */
118    time.tv_sec = 1;
119    time.tv_usec = 0;
120
121    /* make the fd_set hold the inotify fd */
122    FD_ZERO( &rfds );
123    FD_SET( fd, &rfds );
124
125    /* check for added files */
126    ret = select( fd+1, &rfds, NULL, NULL, &time );
127    if( ret < 0 ) {
128        perror( "select" );
129    } else if( !ret ) {
130        /* timed out! */
131    } else if( FD_ISSET( fd, &rfds ) ) {
132        int i = 0;
133        char buf[BUF_LEN];
134        int len = read( fd, buf, sizeof( buf ) );
135        while (i < len) {
136            struct inotify_event * event = (struct inotify_event *) &buf[i];
137            const char * name = event->name;
138            if( tr_str_has_suffix( name, ".torrent" ) )
139            {
140                tr_inf( "Found new .torrent file \"%s\" in watchdir \"%s\"", name, w->dir );
141                w->callback( w->session, w->dir, name );
142            }
143            i += EVENT_SIZE +  event->len;
144        }
145    }
146}
147
148#else /* WITH_INOTIFY */
149
150/***
151****  READDIR IMPLEMENTATION
152***/
153
154#define WATCHDIR_POLL_INTERVAL_SECS 10
155
156#define FILE_DELIMITER '\0'
157
158static void
159watchdir_new_impl( dtr_watchdir * w UNUSED )
160{
161    tr_inf( "Using readdir to watch directory \"%s\"", w->dir );
162    w->lastFiles = evbuffer_new( );
163}
164static void
165watchdir_free_impl( dtr_watchdir * w )
166{
167    evbuffer_free( w->lastFiles );
168}
169static void
170add_file_to_list( struct evbuffer * buf, const char * filename, size_t len )
171{
172    const char delimiter = FILE_DELIMITER;
173    evbuffer_add( buf, &delimiter, 1 );
174    evbuffer_add( buf, filename, len );
175    evbuffer_add( buf, &delimiter, 1 );
176}
177static tr_bool
178is_file_in_list( struct evbuffer * buf, const char * filename, size_t len )
179{
180    tr_bool in_list;
181    struct evbuffer * test = evbuffer_new( );
182    add_file_to_list( test, filename, len );
183    in_list = evbuffer_find( buf, EVBUFFER_DATA( test ), EVBUFFER_LENGTH( test ) ) != NULL;
184    evbuffer_free( test );
185    return in_list;
186}
187static void
188watchdir_update_impl( dtr_watchdir * w )
189{
190    struct stat sb;
191    DIR * odir;
192    const time_t oldTime = w->lastTimeChecked;
193    const char * dirname = w->dir;
194    struct evbuffer * curFiles = evbuffer_new( );
195
196    if ( ( oldTime + WATCHDIR_POLL_INTERVAL_SECS < time( NULL ) )
197         && !stat( dirname, &sb )
198         && S_ISDIR( sb.st_mode )
199         && (( odir = opendir( dirname ))) )
200    {
201        struct dirent * d;
202
203        for( d = readdir( odir ); d != NULL; d = readdir( odir ) )
204        {
205            size_t len;
206            const char * name = d->d_name;
207
208            if( !name || *name=='.' ) /* skip dotfiles */
209                continue;
210            if( !tr_str_has_suffix( name, ".torrent" ) ) /* skip non-torrents */
211                continue;
212
213            len = strlen( name );
214            add_file_to_list( curFiles, name, len );
215
216            /* if this file wasn't here last time, try adding it */
217            if( !is_file_in_list( w->lastFiles, name, len ) ) {
218                tr_inf( "Found new .torrent file \"%s\" in watchdir \"%s\"", name, w->dir );
219                w->callback( w->session, w->dir, name );
220            }
221        }
222
223        closedir( odir );
224        w->lastTimeChecked = time( NULL );
225        evbuffer_free( w->lastFiles );
226        w->lastFiles = curFiles;
227    }
228}
229
230#endif
231
232/***
233****
234***/
235
236dtr_watchdir*
237dtr_watchdir_new( tr_session * session, const char * dir, dtr_watchdir_callback * callback )
238{
239    dtr_watchdir * w = tr_new0( dtr_watchdir, 1 );
240
241    w->session = session;
242    w->dir = tr_strdup( dir );
243    w->callback = callback;
244
245    watchdir_new_impl( w );
246
247    return w;
248}
249
250void
251dtr_watchdir_update( dtr_watchdir * w )
252{
253    if( w != NULL )
254        watchdir_update_impl( w );
255}
256
257void
258dtr_watchdir_free( dtr_watchdir * w )
259{
260    if( w != NULL )
261    {
262        watchdir_free_impl( w );
263        tr_free( w->dir );
264        tr_free( w );
265    }
266}
Note: See TracBrowser for help on using the repository browser.