2 * This file Copyright (C) 2009-2014 Mnemosyne LLC
4 * It may be used under the GNU GPL versions 2 or 3
5 * or any future license endorsed by Mnemosyne LLC.
7 * $Id: watch.c 14241 2014-01-21 03:10:30Z jordan $
11 #include <sys/inotify.h>
12 #include <sys/select.h>
13 #include <unistd.h> /* close */
15 #include <sys/types.h> /* stat */
16 #include <sys/stat.h> /* stat */
17 #include <event2/buffer.h> /* evbuffer */
21 #include <string.h> /* strlen () */
22 #include <stdio.h> /* perror () */
24 #include <dirent.h> /* readdir */
26 #include <libtransmission/transmission.h>
27 #include <libtransmission/log.h>
28 #include <libtransmission/utils.h> /* tr_buildPath (), tr_logAddInfo () */
35 dtr_watchdir_callback
* callback
;
38 #else /* readdir implementation */
39 time_t lastTimeChecked
;
40 struct evbuffer
* lastFiles
;
45 **** INOTIFY IMPLEMENTATION
48 #if defined (WITH_INOTIFY)
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)
57 #define DTR_INOTIFY_MASK (IN_CLOSE_WRITE|IN_MOVED_TO|IN_CREATE|IN_ONLYDIR)
60 watchdir_new_impl (dtr_watchdir
* w
)
64 w
->inotify_fd
= inotify_init ();
66 if (w
->inotify_fd
< 0)
72 tr_logAddInfo ("Using inotify to watch directory \"%s\"", w
->dir
);
73 i
= inotify_add_watch (w
->inotify_fd
, w
->dir
, DTR_INOTIFY_MASK
);
78 tr_logAddError ("Unable to watch \"%s\": %s", w
->dir
, tr_strerror (errno
));
80 else if ((odir
= opendir (w
->dir
)))
84 while ((d
= readdir (odir
)))
86 const char * name
= d
->d_name
;
88 if (!tr_str_has_suffix (name
, ".torrent")) /* skip non-torrents */
91 tr_logAddInfo ("Found new .torrent file \"%s\" in watchdir \"%s\"", name
, w
->dir
);
92 w
->callback (w
->session
, w
->dir
, name
);
100 watchdir_free_impl (dtr_watchdir
* w
)
102 if (w
->inotify_fd
>= 0)
104 inotify_rm_watch (w
->inotify_fd
, DTR_INOTIFY_MASK
);
106 close (w
->inotify_fd
);
110 watchdir_update_impl (dtr_watchdir
* w
)
115 const int fd
= w
->inotify_fd
;
117 /* timeout after one second */
121 /* make the fd_set hold the inotify fd */
125 /* check for added files */
126 ret
= select (fd
+1, &rfds
, NULL
, NULL
, &time
);
131 } else if (FD_ISSET (fd
, &rfds
)) {
134 int len
= read (fd
, buf
, sizeof (buf
));
136 struct inotify_event
* event
= (struct inotify_event
*) &buf
[i
];
137 const char * name
= event
->name
;
138 if (tr_str_has_suffix (name
, ".torrent"))
140 tr_logAddInfo ("Found new .torrent file \"%s\" in watchdir \"%s\"", name
, w
->dir
);
141 w
->callback (w
->session
, w
->dir
, name
);
143 i
+= EVENT_SIZE
+ event
->len
;
148 #else /* WITH_INOTIFY */
151 **** READDIR IMPLEMENTATION
154 #define WATCHDIR_POLL_INTERVAL_SECS 10
156 #define FILE_DELIMITER '\t'
159 watchdir_new_impl (dtr_watchdir
* w UNUSED
)
161 tr_logAddInfo ("Using readdir to watch directory \"%s\"", w
->dir
);
162 w
->lastFiles
= evbuffer_new ();
165 watchdir_free_impl (dtr_watchdir
* w
)
167 evbuffer_free (w
->lastFiles
);
171 get_key_from_file (const char * filename
, const size_t len
)
173 return tr_strdup_printf ("%c%*.*s%d", FILE_DELIMITER
, (int)len
, (int)len
, filename
, FILE_DELIMITER
);
177 add_file_to_list (struct evbuffer
* buf
, const char * filename
, size_t len
)
179 char * key
= get_key_from_file (filename
, len
);
180 evbuffer_add (buf
, key
, strlen (key
));
184 is_file_in_list (struct evbuffer
* buf
, const char * filename
, size_t len
)
187 struct evbuffer_ptr ptr
;
188 char * key
= get_key_from_file (filename
, len
);
190 ptr
= evbuffer_search (buf
, key
, strlen (key
), NULL
);
191 in_list
= ptr
.pos
!= -1;
197 watchdir_update_impl (dtr_watchdir
* w
)
201 const time_t oldTime
= w
->lastTimeChecked
;
202 const char * dirname
= w
->dir
;
203 struct evbuffer
* curFiles
= evbuffer_new ();
205 if ((oldTime
+ WATCHDIR_POLL_INTERVAL_SECS
< time (NULL
))
206 && !stat (dirname
, &sb
)
207 && S_ISDIR (sb
.st_mode
)
208 && ((odir
= opendir (dirname
))))
212 for (d
= readdir (odir
); d
!= NULL
; d
= readdir (odir
))
215 const char * name
= d
->d_name
;
217 if (!name
|| *name
=='.') /* skip dotfiles */
219 if (!tr_str_has_suffix (name
, ".torrent")) /* skip non-torrents */
223 add_file_to_list (curFiles
, name
, len
);
225 /* if this file wasn't here last time, try adding it */
226 if (!is_file_in_list (w
->lastFiles
, name
, len
)) {
227 tr_logAddInfo ("Found new .torrent file \"%s\" in watchdir \"%s\"", name
, w
->dir
);
228 w
->callback (w
->session
, w
->dir
, name
);
233 w
->lastTimeChecked
= time (NULL
);
234 evbuffer_free (w
->lastFiles
);
235 w
->lastFiles
= curFiles
;
246 dtr_watchdir_new (tr_session
* session
, const char * dir
, dtr_watchdir_callback
* callback
)
248 dtr_watchdir
* w
= tr_new0 (dtr_watchdir
, 1);
250 w
->session
= session
;
251 w
->dir
= tr_strdup (dir
);
252 w
->callback
= callback
;
254 watchdir_new_impl (w
);
260 dtr_watchdir_update (dtr_watchdir
* w
)
263 watchdir_update_impl (w
);
267 dtr_watchdir_free (dtr_watchdir
* w
)
271 watchdir_free_impl (w
);