1 /* MiniDLNA media server
2 * Copyright (C) 2008-2010 Justin Maggard
4 * This file is part of MiniDLNA.
6 * MiniDLNA is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License version 2 as
8 * published by the Free Software Foundation.
10 * MiniDLNA is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with MiniDLNA. If not, see <http://www.gnu.org/licenses/>.
27 #include <sys/types.h>
30 #include <sys/resource.h>
33 #include <sys/inotify.h>
35 #include "linux/inotify.h"
36 #include "linux/inotify-syscalls.h"
39 #include "upnpglobalvars.h"
49 #define EVENT_SIZE ( sizeof (struct inotify_event) )
50 #define BUF_LEN ( 1024 * ( EVENT_SIZE + 16 ) )
51 #define DESIRED_WATCH_LIMIT 65536
53 #define PATH_BUF_SIZE PATH_MAX
57 int wd
; /* watch descriptor */
58 char *path
; /* watched path */
62 static struct watch
*watches
;
63 static struct watch
*lastwatch
= NULL
;
64 static time_t next_pl_fill
= 0;
66 char *get_path_from_wd(int wd
)
68 struct watch
*w
= watches
;
81 add_watch(int fd
, const char * path
)
86 wd
= inotify_add_watch(fd
, path
, IN_CREATE
|IN_CLOSE_WRITE
|IN_DELETE
|IN_MOVE
);
89 DPRINTF(E_ERROR
, L_INOTIFY
, "inotify_add_watch(%s) [%s]\n", path
, strerror(errno
));
93 nw
= malloc(sizeof(struct watch
));
96 DPRINTF(E_ERROR
, L_INOTIFY
, "malloc() error\n");
101 nw
->path
= strdup(path
);
103 if( watches
== NULL
)
108 if( lastwatch
!= NULL
)
110 lastwatch
->next
= nw
;
118 remove_watch(int fd
, const char * path
)
122 for( w
= watches
; w
; w
= w
->next
)
124 if( strcmp(path
, w
->path
) == 0 )
125 return(inotify_rm_watch(fd
, w
->wd
));
132 next_highest(unsigned int num
)
143 inotify_create_watches(int fd
)
146 unsigned int num_watches
, watch_limit
= 8192;
149 struct media_dir_s
* media_path
;
151 i
= sql_get_int_field(db
, "SELECT count(*) from DETAILS where SIZE is NULL and PATH is not NULL");
152 num_watches
= (i
< 0) ? 0 : i
;
154 max_watches
= fopen("/proc/sys/fs/inotify/max_user_watches", "r");
157 fscanf(max_watches
, "%10u", &watch_limit
);
159 if( (watch_limit
< DESIRED_WATCH_LIMIT
) || (watch_limit
< (num_watches
*3/4)) )
161 max_watches
= fopen("/proc/sys/fs/inotify/max_user_watches", "w");
164 if( DESIRED_WATCH_LIMIT
>= (num_watches
*3/4) )
166 fprintf(max_watches
, "%u", DESIRED_WATCH_LIMIT
);
168 else if( next_highest(num_watches
) >= (num_watches
*3/4) )
170 fprintf(max_watches
, "%u", next_highest(num_watches
));
174 fprintf(max_watches
, "%u", next_highest(next_highest(num_watches
)));
180 DPRINTF(E_WARN
, L_INOTIFY
, "WARNING: Inotify max_user_watches [%u] is low or close to the number of used watches [%u] "
181 "and I do not have permission to increase this limit. Please do so manually by "
182 "writing a higher value into /proc/sys/fs/inotify/max_user_watches.\n", watch_limit
, num_watches
);
188 DPRINTF(E_WARN
, L_INOTIFY
, "WARNING: Could not read inotify max_user_watches! "
189 "Hopefully it is enough to cover %u current directories plus any new ones added.\n", num_watches
);
192 for( media_path
= media_dirs
; media_path
!= NULL
; media_path
= media_path
->next
)
194 DPRINTF(E_DEBUG
, L_INOTIFY
, "Add watch to %s\n", media_path
->path
);
195 add_watch(fd
, media_path
->path
);
197 sql_get_table(db
, "SELECT PATH from DETAILS where SIZE is NULL and PATH is not NULL", &result
, &rows
, NULL
);
198 for( i
=1; i
<= rows
; i
++ )
200 DPRINTF(E_DEBUG
, L_INOTIFY
, "Add watch to %s\n", result
[i
]);
201 add_watch(fd
, result
[i
]);
203 sqlite3_free_table(result
);
209 inotify_remove_watches(int fd
)
211 struct watch
*w
= watches
;
212 struct watch
*last_w
;
218 inotify_rm_watch(fd
, w
->wd
);
229 int add_dir_watch(int fd
, char * path
, char * filename
)
238 asprintf(&buf
, "%s/%s", path
, filename
);
242 wd
= add_watch(fd
, buf
);
245 DPRINTF(E_ERROR
, L_INOTIFY
, "add_watch() [%s]\n", strerror(errno
));
249 DPRINTF(E_INFO
, L_INOTIFY
, "Added watch to %s [%d]\n", buf
, wd
);
255 while( (e
= readdir(ds
)) )
257 if( strcmp(e
->d_name
, ".") == 0 ||
258 strcmp(e
->d_name
, "..") == 0 )
260 if( (e
->d_type
== DT_DIR
) ||
261 (e
->d_type
== DT_UNKNOWN
&& resolve_unknown_type(buf
, NO_MEDIA
) == TYPE_DIR
) )
262 i
+= add_dir_watch(fd
, buf
, e
->d_name
);
267 DPRINTF(E_ERROR
, L_INOTIFY
, "Opendir error! [%s]\n", strerror(errno
));
277 inotify_insert_file(char * name
, const char * path
)
284 char * parent_buf
= NULL
;
288 enum media_types type
= ALL_MEDIA
;
289 struct media_dir_s
* media_path
= media_dirs
;
292 /* Is it cover art for another file? */
294 update_if_album_art(path
);
295 else if( ends_with(path
, ".srt") )
296 check_for_captions(path
, 0);
298 /* Check if we're supposed to be scanning for this file type in this directory */
301 if( strncmp(path
, media_path
->path
, strlen(media_path
->path
)) == 0 )
303 type
= media_path
->type
;
306 media_path
= media_path
->next
;
311 if( !is_image(path
) &&
318 if( !is_audio(path
) &&
323 if( !is_video(path
) )
327 if( !is_image(path
) )
335 /* If it's already in the database and hasn't been modified, skip it. */
336 if( stat(path
, &st
) != 0 )
339 ts
= sql_get_int_field(db
, "SELECT TIMESTAMP from DETAILS where PATH = '%q'", path
);
340 if( !ts
&& is_playlist(path
) && (sql_get_int_field(db
, "SELECT ID from PLAYLISTS where PATH = '%q'", path
) > 0) )
342 DPRINTF(E_DEBUG
, L_INOTIFY
, "Re-reading modified playlist.\n", path
);
343 inotify_remove_file(path
);
346 else if( ts
< st
.st_mtime
)
349 DPRINTF(E_DEBUG
, L_INOTIFY
, "%s is newer than the last db entry.\n", path
);
350 inotify_remove_file(path
);
353 /* Find the parentID. If it's not found, create all necessary parents. */
354 len
= strlen(path
)+1;
355 if( !(path_buf
= malloc(len
)) ||
356 !(last_dir
= malloc(len
)) ||
357 !(base_name
= malloc(len
)) )
359 base_copy
= base_name
;
363 strcpy(path_buf
, path
);
364 parent_buf
= dirname(path_buf
);
368 //DEBUG DPRINTF(E_DEBUG, L_INOTIFY, "Checking %s\n", parent_buf);
369 id
= sql_get_text_field(db
, "SELECT OBJECT_ID from OBJECTS o left join DETAILS d on (d.ID = o.DETAIL_ID)"
370 " where d.PATH = '%q' and REF_ID is NULL", parent_buf
);
375 DPRINTF(E_DEBUG
, L_INOTIFY
, "Found first known parentID: %s [%s]\n", id
, parent_buf
);
376 /* Insert newly-found directory */
377 strcpy(base_name
, last_dir
);
378 base_copy
= basename(base_name
);
379 insert_directory(base_copy
, last_dir
, BROWSEDIR_ID
, id
+2, get_next_available_id("OBJECTS", id
));
384 strcpy(last_dir
, parent_buf
);
385 parent_buf
= dirname(parent_buf
);
387 while( strcmp(parent_buf
, "/") != 0 );
389 if( strcmp(parent_buf
, "/") == 0 )
391 id
= sqlite3_mprintf("%s", BROWSEDIR_ID
);
395 strcpy(path_buf
, path
);
403 //DEBUG DPRINTF(E_DEBUG, L_INOTIFY, "Inserting %s\n", name);
404 insert_file(name
, path
, id
+2, get_next_available_id("OBJECTS", id
));
406 if( (is_audio(path
) || is_playlist(path
)) && next_pl_fill
!= 1 )
408 next_pl_fill
= time(NULL
) + 120; // Schedule a playlist scan for 2 minutes from now.
409 //DEBUG DPRINTF(E_WARN, L_INOTIFY, "Playlist scan scheduled for %s", ctime(&next_pl_fill));
416 inotify_insert_directory(int fd
, char *name
, const char * path
)
420 char *id
, *path_buf
, *parent_buf
, *esc_name
;
422 enum file_types type
= TYPE_UNKNOWN
;
423 enum media_types dir_type
= ALL_MEDIA
;
424 struct media_dir_s
* media_path
;
427 if( access(path
, R_OK
|X_OK
) != 0 )
429 DPRINTF(E_WARN
, L_INOTIFY
, "Could not access %s [%s]\n", path
, strerror(errno
));
432 if( sql_get_int_field(db
, "SELECT ID from DETAILS where PATH = '%q'", path
) > 0 )
434 DPRINTF(E_DEBUG
, L_INOTIFY
, "%s already exists\n", path
);
438 parent_buf
= strdup(path
);
439 id
= sql_get_text_field(db
, "SELECT OBJECT_ID from OBJECTS o left join DETAILS d on (d.ID = o.DETAIL_ID)"
440 " where d.PATH = '%q' and REF_ID is NULL", dirname(parent_buf
));
442 id
= sqlite3_mprintf("%s", BROWSEDIR_ID
);
443 insert_directory(name
, path
, BROWSEDIR_ID
, id
+2, get_next_available_id("OBJECTS", id
));
447 wd
= add_watch(fd
, path
);
450 DPRINTF(E_ERROR
, L_INOTIFY
, "add_watch() failed\n");
454 DPRINTF(E_INFO
, L_INOTIFY
, "Added watch to %s [%d]\n", path
, wd
);
457 media_path
= media_dirs
;
460 if( strncmp(path
, media_path
->path
, strlen(media_path
->path
)) == 0 )
462 dir_type
= media_path
->type
;
465 media_path
= media_path
->next
;
471 DPRINTF(E_ERROR
, L_INOTIFY
, "opendir failed! [%s]\n", strerror(errno
));
474 while( (e
= readdir(ds
)) )
476 if( e
->d_name
[0] == '.' )
478 esc_name
= escape_tag(e
->d_name
, 1);
479 asprintf(&path_buf
, "%s/%s", path
, e
->d_name
);
486 type
= resolve_unknown_type(path_buf
, dir_type
);
490 if( type
== TYPE_DIR
)
492 inotify_insert_directory(fd
, esc_name
, path_buf
);
494 else if( type
== TYPE_FILE
)
496 if( (stat(path_buf
, &st
) == 0) && (st
.st_blocks
<<9 >= st
.st_size
) )
498 inotify_insert_file(esc_name
, path_buf
);
510 inotify_remove_file(const char * path
)
516 sqlite_int64 detailID
;
519 /* Invalidate the scanner cache so we don't insert files into non-existent containers */
521 playlist
= is_playlist(path
);
522 sql
= sql_get_text_field(db
, "SELECT ID from %s where PATH = '%q'", playlist
?"PLAYLISTS":"DETAILS", path
);
525 detailID
= strtoll(sql
, NULL
, 10);
529 sql_exec(db
, "DELETE from PLAYLISTS where ID = %lld", detailID
);
530 sql_exec(db
, "DELETE from DETAILS where ID ="
531 " (SELECT DETAIL_ID from OBJECTS where OBJECT_ID = '%s$%lld')",
532 MUSIC_PLIST_ID
, detailID
);
533 sql_exec(db
, "DELETE from OBJECTS where OBJECT_ID = '%s$%lld' or PARENT_ID = '%s$%lld'",
534 MUSIC_PLIST_ID
, detailID
, MUSIC_PLIST_ID
, detailID
);
538 /* Delete the parent containers if we are about to empty them. */
539 asprintf(&sql
, "SELECT PARENT_ID from OBJECTS where DETAIL_ID = %lld", detailID
);
540 if( (sql_get_table(db
, sql
, &result
, &rows
, NULL
) == SQLITE_OK
) )
543 for( i
=1; i
<= rows
; i
++ )
545 /* If it's a playlist item, adjust the item count of the playlist */
546 if( strncmp(result
[i
], MUSIC_PLIST_ID
, strlen(MUSIC_PLIST_ID
)) == 0 )
548 sql_exec(db
, "UPDATE PLAYLISTS set FOUND = (FOUND-1) where ID = %d",
549 atoi(strrchr(result
[i
], '$') + 1));
552 children
= sql_get_int_field(db
, "SELECT count(*) from OBJECTS where PARENT_ID = '%s'", result
[i
]);
557 sql_exec(db
, "DELETE from DETAILS where ID ="
558 " (SELECT DETAIL_ID from OBJECTS where OBJECT_ID = '%s')", result
[i
]);
559 sql_exec(db
, "DELETE from OBJECTS where OBJECT_ID = '%s'", result
[i
]);
561 ptr
= strrchr(result
[i
], '$');
564 if( sql_get_int_field(db
, "SELECT count(*) from OBJECTS where PARENT_ID = '%s'", result
[i
]) == 0 )
566 sql_exec(db
, "DELETE from DETAILS where ID ="
567 " (SELECT DETAIL_ID from OBJECTS where OBJECT_ID = '%s')", result
[i
]);
568 sql_exec(db
, "DELETE from OBJECTS where OBJECT_ID = '%s'", result
[i
]);
572 sqlite3_free_table(result
);
575 /* Now delete the actual objects */
576 sql_exec(db
, "DELETE from DETAILS where ID = %lld", detailID
);
577 sql_exec(db
, "DELETE from OBJECTS where DETAIL_ID = %lld", detailID
);
579 asprintf(&art_cache
, "%s/art_cache%s", db_path
, path
);
587 inotify_remove_directory(int fd
, const char * path
)
591 sqlite_int64 detailID
= 0;
592 int rows
, i
, ret
= 1;
594 remove_watch(fd
, path
);
595 sql
= sqlite3_mprintf("SELECT ID from DETAILS where PATH glob '%q/*'"
596 " UNION ALL SELECT ID from DETAILS where PATH = '%q'", path
, path
);
597 if( (sql_get_table(db
, sql
, &result
, &rows
, NULL
) == SQLITE_OK
) )
601 for( i
=1; i
<= rows
; i
++ )
603 detailID
= strtoll(result
[i
], NULL
, 10);
604 sql_exec(db
, "DELETE from DETAILS where ID = %lld", detailID
);
605 sql_exec(db
, "DELETE from OBJECTS where DETAIL_ID = %lld", detailID
);
609 sqlite3_free_table(result
);
612 /* Clean up any album art entries in the deleted directory */
613 sql_exec(db
, "DELETE from ALBUM_ART where PATH glob '%q/*'", path
);
621 struct pollfd pollfds
[1];
623 char buffer
[BUF_LEN
];
624 char path_buf
[PATH_MAX
];
626 char * esc_name
= NULL
;
629 pollfds
[0].fd
= inotify_init();
630 pollfds
[0].events
= POLLIN
;
632 if ( pollfds
[0].fd
< 0 )
633 DPRINTF(E_ERROR
, L_INOTIFY
, "inotify_init() failed!\n");
641 inotify_create_watches(pollfds
[0].fd
);
642 if (setpriority(PRIO_PROCESS
, 0, 19) == -1)
643 DPRINTF(E_WARN
, L_INOTIFY
, "Failed to reduce inotify thread priority\n");
647 length
= poll(pollfds
, 1, timeout
);
650 if( next_pl_fill
&& (time(NULL
) >= next_pl_fill
) )
657 else if( length
< 0 )
659 if( (errno
== EINTR
) || (errno
== EAGAIN
) )
662 DPRINTF(E_ERROR
, L_INOTIFY
, "read failed!\n");
666 length
= read(pollfds
[0].fd
, buffer
, BUF_LEN
);
672 struct inotify_event
* event
= (struct inotify_event
*) &buffer
[i
];
675 if( *(event
->name
) == '.' )
677 i
+= EVENT_SIZE
+ event
->len
;
680 esc_name
= modifyString(strdup(event
->name
), "&", "&amp;", 0);
681 sprintf(path_buf
, "%s/%s", get_path_from_wd(event
->wd
), event
->name
);
682 if ( event
->mask
& IN_ISDIR
&& (event
->mask
& (IN_CREATE
|IN_MOVED_TO
)) )
684 DPRINTF(E_DEBUG
, L_INOTIFY
, "The directory %s was %s.\n",
685 path_buf
, (event
->mask
& IN_MOVED_TO
? "moved here" : "created"));
688 if ( wait_for_mount(path_buf
) >= 0 )
689 inotify_insert_directory(pollfds
[0].fd
, esc_name
, path_buf
);
692 else if ( (event
->mask
& (IN_CLOSE_WRITE
|IN_MOVED_TO
|IN_CREATE
)) &&
693 (lstat(path_buf
, &st
) == 0) )
695 if( S_ISLNK(st
.st_mode
) )
697 DPRINTF(E_DEBUG
, L_INOTIFY
, "The symbolic link %s was %s.\n",
698 path_buf
, (event
->mask
& IN_MOVED_TO
? "moved here" : "created"));
699 if( stat(path_buf
, &st
) == 0 && S_ISDIR(st
.st_mode
) )
700 inotify_insert_directory(pollfds
[0].fd
, esc_name
, path_buf
);
702 inotify_insert_file(esc_name
, path_buf
);
704 else if( event
->mask
& (IN_CLOSE_WRITE
|IN_MOVED_TO
) && st
.st_size
> 0 )
706 if( (event
->mask
& IN_MOVED_TO
) ||
707 (sql_get_int_field(db
, "SELECT TIMESTAMP from DETAILS where PATH = '%q'", path_buf
) != st
.st_mtime
) )
709 DPRINTF(E_DEBUG
, L_INOTIFY
, "The file %s was %s.\n",
710 path_buf
, (event
->mask
& IN_MOVED_TO
? "moved here" : "changed"));
711 inotify_insert_file(esc_name
, path_buf
);
715 else if ( event
->mask
& (IN_DELETE
|IN_MOVED_FROM
) )
717 DPRINTF(E_DEBUG
, L_INOTIFY
, "The %s %s was %s.\n",
718 (event
->mask
& IN_ISDIR
? "directory" : "file"),
719 path_buf
, (event
->mask
& IN_MOVED_FROM
? "moved away" : "deleted"));
720 if ( event
->mask
& IN_ISDIR
)
721 inotify_remove_directory(pollfds
[0].fd
, path_buf
);
723 inotify_remove_file(path_buf
);
727 i
+= EVENT_SIZE
+ event
->len
;
730 inotify_remove_watches(pollfds
[0].fd
);
732 close(pollfds
[0].fd
);