source: trunk/daemon/watch.c @ 12476

Last change on this file since 12476 was 12476, checked in by jordan, 12 years ago

(trunk daemon) #4301 "strerror used instead of tr_strerror" -- fixed with a patch from taem

  • Property svn:keywords set to Date Rev Author Id
File size: 6.5 KB
Line 
1/*
2 * This file Copyright (C) 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 12476 2011-05-30 15:28:55Z jordan $
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 <event2/buffer.h> /* evbuffer */
20#endif
21
22#include <errno.h>
23#include <string.h> /* strlen() */
24#include <stdio.h> /* perror() */
25
26#include <dirent.h> /* readdir */
27
28#include <libtransmission/transmission.h>
29#include <libtransmission/utils.h> /* tr_buildPath(), tr_inf() */
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    struct evbuffer * lastFiles;
42#endif
43};
44
45/***
46****  INOTIFY IMPLEMENTATION
47***/
48
49#if defined(WITH_INOTIFY)
50
51/* how many inotify events to try to batch into a single read */
52#define EVENT_BATCH_COUNT 50
53/* size of the event structure, not counting name */
54#define EVENT_SIZE  (sizeof (struct inotify_event))
55/* reasonable guess as to size of 50 events */
56#define BUF_LEN (EVENT_BATCH_COUNT * (EVENT_SIZE + 16) + 2048)
57
58#define DTR_INOTIFY_MASK (IN_CLOSE_WRITE|IN_MOVED_TO|IN_ONLYDIR)
59
60static void
61watchdir_new_impl( dtr_watchdir * w )
62{
63    int i;
64    DIR * odir;
65    w->inotify_fd = inotify_init( );
66
67    if( w->inotify_fd < 0 )
68    {
69        i = -1;
70    }
71    else
72    {
73        tr_inf( "Using inotify to watch directory \"%s\"", w->dir );
74        i = inotify_add_watch( w->inotify_fd, w->dir, DTR_INOTIFY_MASK );
75    }
76
77    if( i < 0 )
78    {
79        tr_err( "Unable to watch \"%s\": %s", w->dir, tr_strerror( errno ) );
80    }
81    else if(( odir = opendir( w->dir )))
82    {
83        struct dirent * d;
84
85        while(( d = readdir( odir )))
86        {
87            const char * name = d->d_name;
88
89            if( !tr_str_has_suffix( name, ".torrent" ) ) /* skip non-torrents */
90                continue;
91
92            tr_inf( "Found new .torrent file \"%s\" in watchdir \"%s\"", name, w->dir );
93            w->callback( w->session, w->dir, name );
94        }
95
96        closedir( odir );
97    }
98
99}
100static void
101watchdir_free_impl( dtr_watchdir * w )
102{
103    if( w->inotify_fd >= 0 )
104    {
105        inotify_rm_watch( w->inotify_fd, DTR_INOTIFY_MASK );
106
107        close( w->inotify_fd );
108    }
109}
110static void
111watchdir_update_impl( dtr_watchdir * w )
112{
113    int ret;
114    fd_set rfds;
115    struct timeval time;
116    const int fd = w->inotify_fd;
117
118    /* timeout after one second */
119    time.tv_sec = 1;
120    time.tv_usec = 0;
121
122    /* make the fd_set hold the inotify fd */
123    FD_ZERO( &rfds );
124    FD_SET( fd, &rfds );
125
126    /* check for added files */
127    ret = select( fd+1, &rfds, NULL, NULL, &time );
128    if( ret < 0 ) {
129        perror( "select" );
130    } else if( !ret ) {
131        /* timed out! */
132    } else if( FD_ISSET( fd, &rfds ) ) {
133        int i = 0;
134        char buf[BUF_LEN];
135        int len = read( fd, buf, sizeof( buf ) );
136        while (i < len) {
137            struct inotify_event * event = (struct inotify_event *) &buf[i];
138            const char * name = event->name;
139            if( tr_str_has_suffix( name, ".torrent" ) )
140            {
141                tr_inf( "Found new .torrent file \"%s\" in watchdir \"%s\"", name, w->dir );
142                w->callback( w->session, w->dir, name );
143            }
144            i += EVENT_SIZE +  event->len;
145        }
146    }
147}
148
149#else /* WITH_INOTIFY */
150
151/***
152****  READDIR IMPLEMENTATION
153***/
154
155#define WATCHDIR_POLL_INTERVAL_SECS 10
156
157#define FILE_DELIMITER '\t'
158
159static void
160watchdir_new_impl( dtr_watchdir * w UNUSED )
161{
162    tr_inf( "Using readdir to watch directory \"%s\"", w->dir );
163    w->lastFiles = evbuffer_new( );
164}
165static void
166watchdir_free_impl( dtr_watchdir * w )
167{
168    evbuffer_free( w->lastFiles );
169}
170
171static char*
172get_key_from_file( const char * filename, const size_t len )
173{
174    return tr_strdup_printf( "%c%*.*s%d", FILE_DELIMITER, (int)len, (int)len, filename, FILE_DELIMITER );
175}
176
177static void
178add_file_to_list( struct evbuffer * buf, const char * filename, size_t len )
179{
180    char * key = get_key_from_file( filename, len );
181    evbuffer_add( buf, key, strlen( key ) );
182    tr_free( key );
183}
184static bool
185is_file_in_list( struct evbuffer * buf, const char * filename, size_t len )
186{
187    bool in_list;
188    struct evbuffer_ptr ptr;
189    char * key = get_key_from_file( filename, len );
190
191    ptr = evbuffer_search( buf, key, strlen( key ), NULL );
192    in_list = ptr.pos != -1;
193
194    tr_free( key );
195    return in_list;
196}
197static void
198watchdir_update_impl( dtr_watchdir * w )
199{
200    struct stat sb;
201    DIR * odir;
202    const time_t oldTime = w->lastTimeChecked;
203    const char * dirname = w->dir;
204    struct evbuffer * curFiles = evbuffer_new( );
205
206    if ( ( oldTime + WATCHDIR_POLL_INTERVAL_SECS < time( NULL ) )
207         && !stat( dirname, &sb )
208         && S_ISDIR( sb.st_mode )
209         && (( odir = opendir( dirname ))) )
210    {
211        struct dirent * d;
212
213        for( d = readdir( odir ); d != NULL; d = readdir( odir ) )
214        {
215            size_t len;
216            const char * name = d->d_name;
217
218            if( !name || *name=='.' ) /* skip dotfiles */
219                continue;
220            if( !tr_str_has_suffix( name, ".torrent" ) ) /* skip non-torrents */
221                continue;
222
223            len = strlen( name );
224            add_file_to_list( curFiles, name, len );
225
226            /* if this file wasn't here last time, try adding it */
227            if( !is_file_in_list( w->lastFiles, name, len ) ) {
228                tr_inf( "Found new .torrent file \"%s\" in watchdir \"%s\"", name, w->dir );
229                w->callback( w->session, w->dir, name );
230            }
231        }
232
233        closedir( odir );
234        w->lastTimeChecked = time( NULL );
235        evbuffer_free( w->lastFiles );
236        w->lastFiles = curFiles;
237    }
238}
239
240#endif
241
242/***
243****
244***/
245
246dtr_watchdir*
247dtr_watchdir_new( tr_session * session, const char * dir, dtr_watchdir_callback * callback )
248{
249    dtr_watchdir * w = tr_new0( dtr_watchdir, 1 );
250
251    w->session = session;
252    w->dir = tr_strdup( dir );
253    w->callback = callback;
254
255    watchdir_new_impl( w );
256
257    return w;
258}
259
260void
261dtr_watchdir_update( dtr_watchdir * w )
262{
263    if( w != NULL )
264        watchdir_update_impl( w );
265}
266
267void
268dtr_watchdir_free( dtr_watchdir * w )
269{
270    if( w != NULL )
271    {
272        watchdir_free_impl( w );
273        tr_free( w->dir );
274        tr_free( w );
275    }
276}
Note: See TracBrowser for help on using the repository browser.