diff --git a/Makefile.am b/Makefile.am index 74fc901..0b68e47 100644 --- a/Makefile.am +++ b/Makefile.am @@ -25,7 +25,7 @@ minidlnad_SOURCES = minidlna.c upnphttp.c upnpdescgen.c upnpsoap.c \ upnpreplyparse.c minixml.c clients.c \ getifaddr.c process.c upnpglobalvars.c \ options.c minissdp.c uuid.c upnpevents.c \ - sql.c utils.c metadata.c scanner.c inotify.c \ + sql.c utils.c metadata.c scanner.c monitor.c \ tivo_utils.c tivo_beacon.c tivo_commands.c \ playlist.c image_utils.c albumart.c log.c \ containers.c avahi.c tagutils/tagutils.c diff --git a/inotify.h b/inotify.h deleted file mode 100644 index 6be4e4f..0000000 --- a/inotify.h +++ /dev/null @@ -1,4 +0,0 @@ -#ifdef HAVE_INOTIFY -void * -start_inotify(); -#endif diff --git a/minidlna.c b/minidlna.c index 16c27b9..4fa23f6 100644 --- a/minidlna.c +++ b/minidlna.c @@ -90,7 +90,7 @@ #include "process.h" #include "upnpevents.h" #include "scanner.h" -#include "inotify.h" +#include "monitor.h" #include "log.h" #include "tivo_beacon.h" #include "tivo_utils.h" @@ -328,12 +328,13 @@ check_db(sqlite3 *db, int new_db, pid_t *scanner_pid) if (ret != 0) { rescan: + rescan_db = 0; if (ret < 0) DPRINTF(E_WARN, L_GENERAL, "Creating new database at %s/files.db\n", db_path); else if (ret == 1) - DPRINTF(E_WARN, L_GENERAL, "New media_dir detected; rescanning...\n"); + DPRINTF(E_WARN, L_GENERAL, "New media_dir detected; rebuilding...\n"); else if (ret == 2) - DPRINTF(E_WARN, L_GENERAL, "Removed media_dir detected; rescanning...\n"); + DPRINTF(E_WARN, L_GENERAL, "Removed media_dir detected; rebuilding...\n"); else DPRINTF(E_WARN, L_GENERAL, "Database version mismatch (%d=>%d); need to recreate...\n", ret, DB_VERSION); @@ -346,6 +347,9 @@ rescan: open_db(&db); if (CreateDatabase() != 0) DPRINTF(E_FATAL, L_GENERAL, "ERROR: Failed to create sqlite database! Exiting...\n"); + } + if (ret || rescan_db) + { #if USE_FORK scanning = 1; sqlite3_close(db); @@ -834,6 +838,9 @@ init(int argc, char **argv) case 'h': runtime_vars.port = -1; // triggers help display break; + case 'r': + rescan_db = 1; + break; case 'R': snprintf(buf, sizeof(buf), "rm -rf %s/files.db %s/art_cache", db_path, db_path); if (system(buf) != 0) @@ -880,9 +887,9 @@ init(int argc, char **argv) "\t\t[-t notify_interval] [-P pid_filename]\n" "\t\t[-s serial] [-m model_number]\n" #ifdef __linux__ - "\t\t[-w url] [-R] [-L] [-S] [-V] [-h]\n" + "\t\t[-w url] [-r] [-R] [-L] [-S] [-V] [-h]\n" #else - "\t\t[-w url] [-R] [-L] [-V] [-h]\n" + "\t\t[-w url] [-r] [-R] [-L] [-V] [-h]\n" #endif "\nNotes:\n\tNotify interval is in seconds. Default is 895 seconds.\n" "\tDefault pid file is %s.\n" @@ -890,7 +897,8 @@ init(int argc, char **argv) "\t-w sets the presentation url. Default is http address on port 80\n" "\t-v enables verbose output\n" "\t-h displays this text\n" - "\t-R forces a full rescan\n" + "\t-r forces a rescan\n" + "\t-R forces a rebuild\n" "\t-L do not create playlists\n" #ifdef __linux__ "\t-S changes behaviour for systemd\n" diff --git a/inotify.c b/monitor.c similarity index 89% rename from inotify.c rename to monitor.c index ab251c7..ebe8095 100644 --- a/inotify.c +++ b/monitor.c @@ -17,7 +17,6 @@ */ #include "config.h" -#ifdef HAVE_INOTIFY #include #include #include @@ -30,6 +29,7 @@ #include #include #include +#ifdef HAVE_INOTIFY #include #include #ifdef HAVE_SYS_INOTIFY_H @@ -38,10 +38,11 @@ #include "linux/inotify.h" #include "linux/inotify-syscalls.h" #endif +#endif #include "libav.h" #include "upnpglobalvars.h" -#include "inotify.h" +#include "monitor.h" #include "utils.h" #include "sql.h" #include "scanner.h" @@ -50,6 +51,9 @@ #include "playlist.h" #include "log.h" +static time_t next_pl_fill = 0; + +#ifdef HAVE_INOTIFY #define EVENT_SIZE ( sizeof (struct inotify_event) ) #define BUF_LEN ( 1024 * ( EVENT_SIZE + 16 ) ) #define DESIRED_WATCH_LIMIT 65536 @@ -65,7 +69,6 @@ struct watch static struct watch *watches; static struct watch *lastwatch = NULL; -static time_t next_pl_fill = 0; static char * get_path_from_wd(int wd) @@ -244,9 +247,10 @@ inotify_remove_watches(int fd) return rm_watches; } +#endif -static int -inotify_remove_file(const char * path) +int +monitor_remove_file(const char * path) { char sql[128]; char art_cache[PATH_MAX]; @@ -323,8 +327,8 @@ inotify_remove_file(const char * path) return 0; } -static int -inotify_insert_file(char * name, const char * path) +int +monitor_insert_file(char * name, const char * path) { int len; char * last_dir; @@ -406,14 +410,24 @@ inotify_insert_file(char * name, const char * path) if( !ts && is_playlist(path) && (sql_get_int_field(db, "SELECT ID from PLAYLISTS where PATH = '%q'", path) > 0) ) { DPRINTF(E_DEBUG, L_INOTIFY, "Re-reading modified playlist (%s).\n", path); - inotify_remove_file(path); + monitor_remove_file(path); next_pl_fill = 1; } - else if( ts < st.st_mtime ) + else if( !ts ) { - if( ts > 0 ) - DPRINTF(E_DEBUG, L_INOTIFY, "%s is newer than the last db entry.\n", path); - inotify_remove_file(path); + DPRINTF(E_DEBUG, L_INOTIFY, "Adding: %s\n", path); + } + else if( ts != st.st_mtime ) + { + DPRINTF(E_DEBUG, L_INOTIFY, "%s is %s than the last db entry.\n", + path, (ts < st.st_mtime) ? "older" : "newer"); + monitor_remove_file(path); + } + else + { + if( ts == st.st_mtime ) + DPRINTF(E_DEBUG, L_INOTIFY, "%s already exists\n", path); + return 0; } /* Find the parentID. If it's not found, create all necessary parents. */ @@ -478,14 +492,13 @@ inotify_insert_file(char * name, const char * path) return depth; } -static int -inotify_insert_directory(int fd, char *name, const char * path) +int +monitor_insert_directory(int fd, char *name, const char * path) { DIR * ds; struct dirent * e; char *id, *parent_buf, *esc_name; char path_buf[PATH_MAX]; - int wd; enum file_types type = TYPE_UNKNOWN; media_types dir_types = ALL_MEDIA; struct media_dir_s* media_path; @@ -498,27 +511,34 @@ inotify_insert_directory(int fd, char *name, const char * path) } if( sql_get_int_field(db, "SELECT ID from DETAILS where PATH = '%q'", path) > 0 ) { + fd = 0; DPRINTF(E_DEBUG, L_INOTIFY, "%s already exists\n", path); - return 0; - } - - parent_buf = strdup(path); - id = sql_get_text_field(db, "SELECT OBJECT_ID from OBJECTS o left join DETAILS d on (d.ID = o.DETAIL_ID)" - " where d.PATH = '%q' and REF_ID is NULL", dirname(parent_buf)); - if( !id ) - id = sqlite3_mprintf("%s", BROWSEDIR_ID); - insert_directory(name, path, BROWSEDIR_ID, id+2, get_next_available_id("OBJECTS", id)); - sqlite3_free(id); - free(parent_buf); - - wd = add_watch(fd, path); - if( wd == -1 ) - { - DPRINTF(E_ERROR, L_INOTIFY, "add_watch() failed\n"); } else { - DPRINTF(E_INFO, L_INOTIFY, "Added watch to %s [%d]\n", path, wd); + parent_buf = strdup(path); + id = sql_get_text_field(db, "SELECT OBJECT_ID from OBJECTS o left join DETAILS d on (d.ID = o.DETAIL_ID)" + " WHERE d.PATH = '%q' and REF_ID is NULL", dirname(parent_buf)); + if( !id ) + id = sqlite3_mprintf("%s", BROWSEDIR_ID); + insert_directory(name, path, BROWSEDIR_ID, id+2, get_next_available_id("OBJECTS", id)); + sqlite3_free(id); + free(parent_buf); + } + + if( fd > 0 ) + { + #ifdef HAVE_INOTIFY + int wd = add_watch(fd, path); + if( wd == -1 ) + { + DPRINTF(E_ERROR, L_INOTIFY, "add_watch() failed\n"); + } + else + { + DPRINTF(E_INFO, L_INOTIFY, "Added watch to %s [%d]\n", path, wd); + } + #endif } media_path = media_dirs; @@ -556,13 +576,13 @@ inotify_insert_directory(int fd, char *name, const char * path) } if( type == TYPE_DIR ) { - inotify_insert_directory(fd, esc_name, path_buf); + monitor_insert_directory(fd, esc_name, path_buf); } else if( type == TYPE_FILE ) { if( (stat(path_buf, &st) == 0) && (st.st_blocks<<9 >= st.st_size) ) { - inotify_insert_file(esc_name, path_buf); + monitor_insert_file(esc_name, path_buf); } } free(esc_name); @@ -572,8 +592,8 @@ inotify_insert_directory(int fd, char *name, const char * path) return 0; } -static int -inotify_remove_directory(int fd, const char * path) +int +monitor_remove_directory(int fd, const char * path) { char * sql; char **result; @@ -582,7 +602,12 @@ inotify_remove_directory(int fd, const char * path) /* Invalidate the scanner cache so we don't insert files into non-existent containers */ valid_cache = 0; - remove_watch(fd, path); + if( fd > 0 ) + { + #ifdef HAVE_INOTIFY + remove_watch(fd, path); + #endif + } sql = sqlite3_mprintf("SELECT ID from DETAILS where (PATH > '%q/' and PATH <= '%q/%c')" " or PATH = '%q'", path, path, 0xFF, path); if( (sql_get_table(db, sql, &result, &rows, NULL) == SQLITE_OK) ) @@ -606,6 +631,7 @@ inotify_remove_directory(int fd, const char * path) return ret; } +#ifdef HAVE_INOTIFY void * start_inotify(void) { @@ -620,7 +646,7 @@ start_inotify(void) sigfillset(&set); pthread_sigmask(SIG_BLOCK, &set, NULL); - + pollfds[0].fd = inotify_init(); pollfds[0].events = POLLIN; @@ -638,10 +664,10 @@ start_inotify(void) DPRINTF(E_WARN, L_INOTIFY, "Failed to reduce inotify thread priority\n"); sqlite3_release_memory(1<<31); av_register_all(); - + while( !quitting ) { - length = poll(pollfds, 1, timeout); + length = poll(pollfds, 1, timeout); if( !length ) { if( next_pl_fill && (time(NULL) >= next_pl_fill) ) @@ -653,9 +679,9 @@ start_inotify(void) } else if( length < 0 ) { - if( (errno == EINTR) || (errno == EAGAIN) ) - continue; - else + if( (errno == EINTR) || (errno == EAGAIN) ) + continue; + else DPRINTF(E_ERROR, L_INOTIFY, "read failed!\n"); } else @@ -681,7 +707,7 @@ start_inotify(void) { DPRINTF(E_DEBUG, L_INOTIFY, "The directory %s was %s.\n", path_buf, (event->mask & IN_MOVED_TO ? "moved here" : "created")); - inotify_insert_directory(pollfds[0].fd, esc_name, path_buf); + monitor_insert_directory(pollfds[0].fd, esc_name, path_buf); } else if ( (event->mask & (IN_CLOSE_WRITE|IN_MOVED_TO|IN_CREATE)) && (lstat(path_buf, &st) == 0) ) @@ -692,9 +718,9 @@ start_inotify(void) (S_ISLNK(st.st_mode) ? "symbolic" : "hard"), path_buf, (event->mask & IN_MOVED_TO ? "moved here" : "created")); if( stat(path_buf, &st) == 0 && S_ISDIR(st.st_mode) ) - inotify_insert_directory(pollfds[0].fd, esc_name, path_buf); + monitor_insert_directory(pollfds[0].fd, esc_name, path_buf); else - inotify_insert_file(esc_name, path_buf); + monitor_insert_file(esc_name, path_buf); } else if( event->mask & (IN_CLOSE_WRITE|IN_MOVED_TO) && st.st_size > 0 ) { @@ -703,7 +729,7 @@ start_inotify(void) { DPRINTF(E_DEBUG, L_INOTIFY, "The file %s was %s.\n", path_buf, (event->mask & IN_MOVED_TO ? "moved here" : "changed")); - inotify_insert_file(esc_name, path_buf); + monitor_insert_file(esc_name, path_buf); } } } @@ -713,9 +739,9 @@ start_inotify(void) (event->mask & IN_ISDIR ? "directory" : "file"), path_buf, (event->mask & IN_MOVED_FROM ? "moved away" : "deleted")); if ( event->mask & IN_ISDIR ) - inotify_remove_directory(pollfds[0].fd, path_buf); + monitor_remove_directory(pollfds[0].fd, path_buf); else - inotify_remove_file(path_buf); + monitor_remove_file(path_buf); } free(esc_name); } diff --git a/monitor.h b/monitor.h new file mode 100644 index 0000000..92ae41a --- /dev/null +++ b/monitor.h @@ -0,0 +1,16 @@ +int +monitor_insert_file(char * name, const char * path); + +int +monitor_insert_directory(int fd, char *name, const char * path); + +int +monitor_remove_file(const char * path); + +int +monitor_remove_directory(int fd, const char * path); + +#ifdef HAVE_INOTIFY +void * +start_inotify(); +#endif diff --git a/scanner.c b/scanner.c index 52ac256..96127a7 100644 --- a/scanner.c +++ b/scanner.c @@ -46,6 +46,7 @@ #include "albumart.h" #include "containers.h" #include "log.h" +#include "monitor.h" #if SCANDIR_CONST typedef const struct dirent scan_filter; @@ -837,6 +838,68 @@ _notify_stop(void) #endif } +/* rescan functions added by shrimpkin@sourceforge.net */ +static int +cb_orphans(void *args, int argc, char **argv, char **azColName) +{ + const char *path = argv[0]; + const char *mime = argv[1]; + + /* If we can't access the path, remove it */ + if (access(path, R_OK) != 0) + { + DPRINTF(E_DEBUG, L_SCANNER, "Removing %s [%s]\n", path, mime ? "file" : "dir"); + if (mime) + monitor_remove_file(path); + else + monitor_remove_directory(0, path); + } + + return 0; +} + +void +start_rescan(void) +{ + struct media_dir_s *media_path; + char *esc_name = NULL; + char *zErrMsg; + const char *sql_files = "SELECT path, mime FROM details WHERE path NOT NULL AND mime IS NOT NULL;"; + const char *sql_dir = "SELECT path, mime FROM details WHERE path NOT NULL AND mime IS NULL;"; + int ret; + + DPRINTF(E_INFO, L_SCANNER, "Starting rescan\n"); + + /* Find and remove any dead directory links */ + ret = sqlite3_exec(db, sql_dir, cb_orphans, NULL, &zErrMsg); + if (ret != SQLITE_OK) + { + DPRINTF(E_MAXDEBUG, L_SCANNER, "SQL error: %s\nBAD SQL: %s\n", zErrMsg, sql_dir); + sqlite3_free(zErrMsg); + } + + /* Find and remove any dead file links */ + ret = sqlite3_exec(db, sql_files, cb_orphans, NULL, &zErrMsg); + if (ret != SQLITE_OK) + { + DPRINTF(E_MAXDEBUG, L_SCANNER, "SQL error: %s\nBAD SQL: %s\n", zErrMsg, sql_files); + sqlite3_free(zErrMsg); + } + + /* Rescan media_paths for new and/or modified files */ + for (media_path = media_dirs; media_path != NULL; media_path = media_path->next) + { + char path[MAXPATHLEN], buf[MAXPATHLEN]; + strncpyt(path, media_path->path, sizeof(path)); + strncpyt(buf, media_path->path, sizeof(buf)); + esc_name = escape_tag(basename(buf), 1); + monitor_insert_directory(0, esc_name, path); + free(esc_name); + } + DPRINTF(E_INFO, L_SCANNER, "Rescan completed\n"); +} +/* end rescan functions */ + void start_scanner() { @@ -845,12 +908,17 @@ start_scanner() if (setpriority(PRIO_PROCESS, 0, 15) == -1) DPRINTF(E_WARN, L_INOTIFY, "Failed to reduce scanner thread priority\n"); - _notify_start(); setlocale(LC_COLLATE, ""); av_register_all(); av_log_set_level(AV_LOG_PANIC); + if( rescan_db ) + { + start_rescan(); + return; + } + _notify_start(); for( media_path = media_dirs; media_path != NULL; media_path = media_path->next ) { int64_t id; diff --git a/upnpglobalvars.c b/upnpglobalvars.c index 17d4dff..f221619 100644 --- a/upnpglobalvars.c +++ b/upnpglobalvars.c @@ -89,3 +89,4 @@ short int scanning = 0; volatile short int quitting = 0; volatile uint32_t updateID = 0; const char *force_sort_criteria = NULL; +short int rescan_db = 0; diff --git a/upnpglobalvars.h b/upnpglobalvars.h index 0c9664d..9773b0d 100644 --- a/upnpglobalvars.h +++ b/upnpglobalvars.h @@ -233,5 +233,6 @@ extern short int scanning; extern volatile short int quitting; extern volatile uint32_t updateID; extern const char *force_sort_criteria; +extern short int rescan_db; #endif