/* Thread: scan */ static void process_inotify_file(struct watch_info *wi, char *path, struct inotify_event *ie) { struct stat sb; char *deref = NULL; char *file = path; int type; int ret; DPRINTF(E_SPAM, L_SCAN, "File event: 0x%x, cookie 0x%x, wd %d\n", ie->mask, ie->cookie, wi->wd); if (ie->mask & IN_DELETE) { DPRINTF(E_DBG, L_SCAN, "File deleted: %s\n", path); db_file_delete_bypath(path); db_pl_delete_bypath(path); } if (ie->mask & IN_MOVED_FROM) { DPRINTF(E_DBG, L_SCAN, "File moved from: %s\n", path); db_file_disable_bypath(path, path, ie->cookie); db_pl_disable_bypath(path, path, ie->cookie); } if (ie->mask & IN_ATTRIB) { DPRINTF(E_DBG, L_SCAN, "File permissions changed: %s\n", path); #ifdef HAVE_EUIDACCESS if (euidaccess(path, R_OK) < 0) #else if (access(path, R_OK) < 0) #endif { DPRINTF(E_LOG, L_SCAN, "File access to '%s' failed: %s\n", path, strerror(errno)); db_file_delete_bypath(path);; } else if ((file_type_get(path) == FILE_REGULAR) && (db_file_id_bypath(path) <= 0)) // TODO Playlists { DPRINTF(E_LOG, L_SCAN, "File access to '%s' achieved\n", path); ie->mask |= IN_CLOSE_WRITE; } } if (ie->mask & IN_MOVED_TO) { DPRINTF(E_DBG, L_SCAN, "File moved to: %s\n", path); ret = db_file_enable_bycookie(ie->cookie, path); if (ret <= 0) { /* It's not a known media file, so it's either a new file * or a playlist, known or not. * We want to scan the new file and we want to rescan the * playlist to update playlist items (relative items). */ ie->mask |= IN_CLOSE_WRITE; db_pl_enable_bycookie(ie->cookie, path); } } if (ie->mask & IN_CREATE) { DPRINTF(E_DBG, L_SCAN, "File created: %s\n", path); ret = lstat(path, &sb); if (ret < 0) { DPRINTF(E_LOG, L_SCAN, "Could not lstat() '%s': %s\n", path, strerror(errno)); return; } if (S_ISFIFO(sb.st_mode)) ie->mask |= IN_CLOSE_WRITE; } if (ie->mask & IN_CLOSE_WRITE) { DPRINTF(E_DBG, L_SCAN, "File closed: %s\n", path); ret = lstat(path, &sb); if (ret < 0) { DPRINTF(E_LOG, L_SCAN, "Could not lstat() '%s': %s\n", path, strerror(errno)); return; } if (S_ISLNK(sb.st_mode)) { deref = m_realpath(path); if (!deref) { DPRINTF(E_LOG, L_SCAN, "Could not dereference symlink '%s': %s\n", path, strerror(errno)); return; } file = deref; ret = stat(deref, &sb); if (ret < 0) { DPRINTF(E_LOG, L_SCAN, "Could not stat() '%s': %s\n", file, strerror(errno)); free(deref); return; } if (S_ISDIR(sb.st_mode)) { process_inotify_dir(wi, deref, ie); free(deref); return; } } type = 0; if (check_speciallib(path, "compilations")) type |= F_SCAN_TYPE_COMPILATION; if (check_speciallib(path, "podcasts")) type |= F_SCAN_TYPE_PODCAST; if (check_speciallib(path, "audiobooks")) type |= F_SCAN_TYPE_AUDIOBOOK; if (S_ISREG(sb.st_mode)) process_file(file, sb.st_mtime, sb.st_size, F_SCAN_TYPE_FILE | type, 0); else if (S_ISFIFO(sb.st_mode)) process_file(file, sb.st_mtime, sb.st_size, F_SCAN_TYPE_PIPE | type, 0); if (deref) free(deref); } }
/* Thread: scan */ static void process_inotify_dir(struct watch_info *wi, char *path, struct inotify_event *ie) { struct watch_enum we; uint32_t rm_wd; char *s; int flags = 0; int ret; DPRINTF(E_SPAM, L_SCAN, "Directory event: 0x%x, cookie 0x%x, wd %d\n", ie->mask, ie->cookie, wi->wd); if (ie->mask & IN_UNMOUNT) { db_file_disable_bymatch(path, "", 0); db_pl_disable_bymatch(path, "", 0); } if (ie->mask & IN_MOVE_SELF) { /* A directory we know about, that got moved from a place * we know about to a place we know nothing about */ if (wi->cookie) { memset(&we, 0, sizeof(struct watch_enum)); we.cookie = wi->cookie; ret = db_watch_enum_start(&we); if (ret < 0) return; while ((db_watch_enum_fetchwd(&we, &rm_wd) == 0) && (rm_wd)) { inotify_rm_watch(inofd, rm_wd); } db_watch_enum_end(&we); db_watch_delete_bycookie(wi->cookie); } else { /* If the directory exists, it has been moved and we've * kept track of it successfully, so we're done */ ret = access(path, F_OK); if (ret == 0) return; /* Most probably a top-level dir is getting moved, * and we can't tell where it's going */ ret = watches_clear(ie->wd, path); if (ret < 0) return; db_file_disable_bymatch(path, "", 0); db_pl_disable_bymatch(path, "", 0); } } if (ie->mask & IN_MOVED_FROM) { db_watch_mark_bypath(path, path, ie->cookie); db_watch_mark_bymatch(path, path, ie->cookie); db_file_disable_bymatch(path, path, ie->cookie); db_pl_disable_bymatch(path, path, ie->cookie); } if (ie->mask & IN_MOVED_TO) { if (db_watch_cookie_known(ie->cookie)) { db_watch_move_bycookie(ie->cookie, path); db_file_enable_bycookie(ie->cookie, path); db_pl_enable_bycookie(ie->cookie, path); /* We'll rescan the directory tree to update playlists */ flags |= F_SCAN_MOVED; } ie->mask |= IN_CREATE; } if (ie->mask & IN_ATTRIB) { DPRINTF(E_DBG, L_SCAN, "Directory permissions changed (%s): %s\n", wi->path, path); // Find out if we are already watching the dir (ret will be 0) s = wi->path; wi->path = path; ret = db_watch_get_bypath(wi); if (ret == 0) free(wi->path); wi->path = s; #ifdef HAVE_EUIDACCESS if (euidaccess(path, (R_OK | X_OK)) < 0) #else if (access(path, (R_OK | X_OK)) < 0) #endif { DPRINTF(E_LOG, L_SCAN, "Directory access to '%s' failed: %s\n", path, strerror(errno)); if (ret == 0) watches_clear(wi->wd, path); db_file_disable_bymatch(path, "", 0); db_pl_disable_bymatch(path, "", 0); } else if (ret < 0) { DPRINTF(E_LOG, L_SCAN, "Directory access to '%s' achieved\n", path); ie->mask |= IN_CREATE; } else { DPRINTF(E_INFO, L_SCAN, "Directory event, but '%s' already being watched\n", path); } } if (ie->mask & IN_CREATE) { process_directories(path, flags); if (dirstack) DPRINTF(E_LOG, L_SCAN, "WARNING: unhandled leftover directories\n"); } }
/* Thread: scan */ static void process_inotify_dir(struct watch_info *wi, char *path, struct inotify_event *ie) { struct watch_enum we; uint32_t rm_wd; int flags = 0; int ret; DPRINTF(E_DBG, L_SCAN, "Directory event: 0x%x, cookie 0x%x, wd %d\n", ie->mask, ie->cookie, wi->wd); if (ie->mask & IN_UNMOUNT) { db_file_disable_bymatch(path, "", 0); db_pl_disable_bymatch(path, "", 0); } if (ie->mask & IN_MOVE_SELF) { /* A directory we know about, that got moved from a place * we know about to a place we know nothing about */ if (wi->cookie) { memset(&we, 0, sizeof(struct watch_enum)); we.cookie = wi->cookie; ret = db_watch_enum_start(&we); if (ret < 0) return; while ((db_watch_enum_fetchwd(&we, &rm_wd) == 0) && (rm_wd)) { inotify_rm_watch(inofd, rm_wd); } db_watch_enum_end(&we); db_watch_delete_bycookie(wi->cookie); } else { /* If the directory exists, it has been moved and we've * kept track of it successfully, so we're done */ ret = access(path, F_OK); if (ret == 0) return; /* Most probably a top-level dir is getting moved, * and we can't tell where it's going */ inotify_rm_watch(inofd, ie->wd); db_watch_delete_bywd(ie->wd); memset(&we, 0, sizeof(struct watch_enum)); we.match = path; ret = db_watch_enum_start(&we); if (ret < 0) return; while ((db_watch_enum_fetchwd(&we, &rm_wd) == 0) && (rm_wd)) { inotify_rm_watch(inofd, rm_wd); } db_watch_enum_end(&we); db_watch_delete_bymatch(path); db_file_disable_bymatch(path, "", 0); db_pl_disable_bymatch(path, "", 0); } } if (ie->mask & IN_MOVED_FROM) { db_watch_mark_bypath(path, path, ie->cookie); db_watch_mark_bymatch(path, path, ie->cookie); db_file_disable_bymatch(path, path, ie->cookie); db_pl_disable_bymatch(path, path, ie->cookie); } if (ie->mask & IN_MOVED_TO) { if (db_watch_cookie_known(ie->cookie)) { db_watch_move_bycookie(ie->cookie, path); db_file_enable_bycookie(ie->cookie, path); db_pl_enable_bycookie(ie->cookie, path); /* We'll rescan the directory tree to update playlists */ flags |= F_SCAN_RESCAN; } ie->mask |= IN_CREATE; } if (ie->mask & IN_CREATE) { process_directories(path, flags); if (dirstack) DPRINTF(E_LOG, L_SCAN, "WARNING: unhandled leftover directories\n"); } }
/* Thread: scan */ static void process_inotify_file(struct watch_info *wi, char *path, struct inotify_event *ie) { struct stat sb; char *deref = NULL; char *file = path; int compilation; int ret; DPRINTF(E_DBG, L_SCAN, "File event: 0x%x, cookie 0x%x, wd %d\n", ie->mask, ie->cookie, wi->wd); if (ie->mask & IN_DELETE) { db_file_delete_bypath(path); db_pl_delete_bypath(path); } if (ie->mask & IN_MOVED_FROM) { db_file_disable_bypath(path, wi->path, ie->cookie); db_pl_disable_bypath(path, wi->path, ie->cookie); } if (ie->mask & IN_MOVED_TO) { ret = db_file_enable_bycookie(ie->cookie, wi->path); if (ret <= 0) { /* It's not a known media file, so it's either a new file * or a playlist, known or not. * We want to scan the new file and we want to rescan the * playlist to update playlist items (relative items). */ ie->mask |= IN_CREATE; db_pl_enable_bycookie(ie->cookie, wi->path); } } if (ie->mask & (IN_MODIFY | IN_CREATE | IN_CLOSE_WRITE)) { ret = lstat(path, &sb); if (ret < 0) { DPRINTF(E_LOG, L_SCAN, "Could not lstat() '%s': %s\n", path, strerror(errno)); return; } if (S_ISLNK(sb.st_mode)) { deref = m_realpath(path); if (!deref) { DPRINTF(E_LOG, L_SCAN, "Could not dereference symlink '%s': %s\n", path, strerror(errno)); return; } file = deref; ret = stat(deref, &sb); if (ret < 0) { DPRINTF(E_LOG, L_SCAN, "Could not stat() '%s': %s\n", file, strerror(errno)); free(deref); return; } if (S_ISDIR(sb.st_mode)) { process_inotify_dir(wi, deref, ie); free(deref); return; } } compilation = check_compilation(path); process_file(file, sb.st_mtime, sb.st_size, compilation, 0); if (deref) free(deref); } }
/* Thread: scan */ static void process_inotify_file(struct watch_info *wi, char *path, struct inotify_event *ie) { struct stat sb; uint32_t path_hash; char *deref = NULL; char *file = path; char *dir; char dir_vpath[PATH_MAX]; int type; int i; int dir_id; char *ptr; int ret; DPRINTF(E_DBG, L_SCAN, "File event: 0x%x, cookie 0x%x, wd %d\n", ie->mask, ie->cookie, wi->wd); path_hash = djb_hash(path, strlen(path)); if (ie->mask & IN_DELETE) { DPRINTF(E_DBG, L_SCAN, "File deleted: %s\n", path); db_file_delete_bypath(path); db_pl_delete_bypath(path); cache_artwork_delete_by_path(path); } if (ie->mask & IN_MOVED_FROM) { DPRINTF(E_DBG, L_SCAN, "File moved from: %s\n", path); db_file_disable_bypath(path, path, ie->cookie); db_pl_disable_bypath(path, path, ie->cookie); } if (ie->mask & IN_ATTRIB) { DPRINTF(E_DBG, L_SCAN, "File attributes changed: %s\n", path); // Ignore the IN_ATTRIB if we just got an IN_CREATE for (i = 0; i < INCOMINGFILES_BUFFER_SIZE; i++) { if (incomingfiles_buffer[i] == path_hash) return; } #ifdef HAVE_EUIDACCESS if (euidaccess(path, R_OK) < 0) #else if (access(path, R_OK) < 0) #endif { DPRINTF(E_LOG, L_SCAN, "File access to '%s' failed: %s\n", path, strerror(errno)); db_file_delete_bypath(path); cache_artwork_delete_by_path(path); } else if ((file_type_get(path) == FILE_REGULAR) && (db_file_id_bypath(path) <= 0)) // TODO Playlists { DPRINTF(E_LOG, L_SCAN, "File access to '%s' achieved\n", path); ie->mask |= IN_CLOSE_WRITE; } } if (ie->mask & IN_MOVED_TO) { DPRINTF(E_DBG, L_SCAN, "File moved to: %s\n", path); ret = db_file_enable_bycookie(ie->cookie, path); if (ret > 0) { // If file was successfully enabled, update the directory id dir = strdup(path); ptr = strrchr(dir, '/'); dir[(ptr - dir)] = '\0'; ret = create_virtual_path(dir, dir_vpath, sizeof(dir_vpath)); if (ret >= 0) { dir_id = db_directory_id_byvirtualpath(dir_vpath); if (dir_id > 0) { ret = db_file_update_directoryid(path, dir_id); if (ret < 0) DPRINTF(E_LOG, L_SCAN, "Error updating directory id for file: %s\n", path); } } free(dir); } else { /* It's not a known media file, so it's either a new file * or a playlist, known or not. * We want to scan the new file and we want to rescan the * playlist to update playlist items (relative items). */ ie->mask |= IN_CLOSE_WRITE; db_pl_enable_bycookie(ie->cookie, path); } } if (ie->mask & IN_CREATE) { DPRINTF(E_DBG, L_SCAN, "File created: %s\n", path); ret = lstat(path, &sb); if (ret < 0) { DPRINTF(E_LOG, L_SCAN, "Could not lstat() '%s': %s\n", path, strerror(errno)); return; } // Add to the list of files where we ignore IN_ATTRIB until the file is closed again if (S_ISREG(sb.st_mode)) { DPRINTF(E_SPAM, L_SCAN, "Incoming file created '%s' (%d), index %d\n", path, (int)path_hash, incomingfiles_idx); incomingfiles_buffer[incomingfiles_idx] = path_hash; incomingfiles_idx = (incomingfiles_idx + 1) % INCOMINGFILES_BUFFER_SIZE; } else if (S_ISFIFO(sb.st_mode)) ie->mask |= IN_CLOSE_WRITE; } if (ie->mask & IN_CLOSE_WRITE) { DPRINTF(E_DBG, L_SCAN, "File closed: %s\n", path); // File has been closed so remove from the IN_ATTRIB ignore list for (i = 0; i < INCOMINGFILES_BUFFER_SIZE; i++) if (incomingfiles_buffer[i] == path_hash) { DPRINTF(E_SPAM, L_SCAN, "Incoming file closed '%s' (%d), index %d\n", path, (int)path_hash, i); incomingfiles_buffer[i] = 0; } ret = lstat(path, &sb); if (ret < 0) { DPRINTF(E_LOG, L_SCAN, "Could not lstat() '%s': %s\n", path, strerror(errno)); return; } if (S_ISLNK(sb.st_mode)) { deref = m_realpath(path); if (!deref) { DPRINTF(E_LOG, L_SCAN, "Could not dereference symlink '%s': %s\n", path, strerror(errno)); return; } file = deref; ret = stat(deref, &sb); if (ret < 0) { DPRINTF(E_LOG, L_SCAN, "Could not stat() '%s': %s\n", file, strerror(errno)); free(deref); return; } if (S_ISDIR(sb.st_mode)) { process_inotify_dir(wi, deref, ie); free(deref); return; } } type = 0; if (check_speciallib(path, "compilations")) type |= F_SCAN_TYPE_COMPILATION; if (check_speciallib(path, "podcasts")) type |= F_SCAN_TYPE_PODCAST; if (check_speciallib(path, "audiobooks")) type |= F_SCAN_TYPE_AUDIOBOOK; dir_id = get_parent_dir_id(file); if (S_ISREG(sb.st_mode)) { process_file(file, sb.st_mtime, sb.st_size, F_SCAN_TYPE_FILE | type, 0, dir_id); } else if (S_ISFIFO(sb.st_mode)) process_file(file, sb.st_mtime, sb.st_size, F_SCAN_TYPE_PIPE | type, 0, dir_id); if (deref) free(deref); } }