void recurse_dir(MediaScan *s, const char *path, int recurse_count) { char *dir, *p; char tmp_full_path[MAX_PATH_STR_LEN]; DIR *dirp; struct dirent *dp; struct dirq *subdirq; // list of subdirs of the current directory struct dirq_entry *parent_entry = NULL; // entry for current dir in s->_dirq char redirect_dir[MAX_PATH_STR_LEN]; if (recurse_count > RECURSE_LIMIT) { LOG_ERROR("Hit recurse limit of %d scanning path %s\n", RECURSE_LIMIT, path); return; } if (path[0] != '/') { // XXX Win32 // Get full path char *buf = (char *)malloc((size_t)MAX_PATH_STR_LEN); if (buf == NULL) { FATAL("Out of memory for directory scan\n"); return; } dir = getcwd(buf, (size_t)MAX_PATH_STR_LEN); strcat(dir, "/"); strcat(dir, path); } else { #ifdef USING_TCMALLOC // strdup will cause tcmalloc to crash on free dir = (char *)malloc((size_t)MAX_PATH_STR_LEN); strcpy(dir, path); #else dir = strdup(path); #endif } // Strip trailing slash if any p = &dir[0]; while (*p != 0) { if (p[1] == 0 && *p == '/') *p = 0; p++; } LOG_INFO("Recursed into %s\n", dir); #if defined(__APPLE__) if (isAlias(dir)) { if (CheckMacAlias(dir, redirect_dir)) { LOG_INFO("Resolving Alias %s to %s\n", dir, redirect_dir); strcpy(dir, redirect_dir); } else { LOG_ERROR("Failure to follow symlink or alias, skipping directory\n"); goto out; } } #elif defined(__linux__) if (isAlias(dir)) { FollowLink(dir, redirect_dir); LOG_INFO("Resolving symlink %s to %s\n", dir, redirect_dir); strcpy(dir, redirect_dir); } #endif if ((dirp = opendir(dir)) == NULL) { LOG_ERROR("Unable to open directory %s: %s\n", dir, strerror(errno)); goto out; } subdirq = malloc(sizeof(struct dirq)); SIMPLEQ_INIT(subdirq); while ((dp = readdir(dirp)) != NULL) { char *name = dp->d_name; // skip all dot files if (name[0] != '.') { // Check if scan should be aborted if (unlikely(s->_want_abort)) break; // XXX some platforms may be missing d_type/DT_DIR if (dp->d_type == DT_DIR) { // Add to list of subdirectories we need to recurse into struct dirq_entry *subdir_entry = malloc(sizeof(struct dirq_entry)); // Construct full path //*tmp_full_path = 0; strcpy(tmp_full_path, dir); strcat(tmp_full_path, "/"); strcat(tmp_full_path, name); if (_should_scan_dir(s, tmp_full_path)) { subdir_entry->dir = strdup(tmp_full_path); SIMPLEQ_INSERT_TAIL(subdirq, subdir_entry, entries); LOG_INFO(" subdir: %s\n", tmp_full_path); } else { LOG_INFO(" skipping subdir: %s\n", tmp_full_path); } } else { enum media_type type = _should_scan(s, name); LOG_INFO("name %s = type %d\n", name, type); if (type) { struct fileq_entry *entry; // Check if this file is a shortcut and if so resolve it #if defined(__APPLE__) if (isAlias(name)) { char full_name[MAX_PATH_STR_LEN]; LOG_INFO("Mac Alias detected\n"); strcpy(full_name, dir); strcat(full_name, "\\"); strcat(full_name, name); parse_lnk(full_name, redirect_dir, MAX_PATH_STR_LEN); if (PathIsDirectory(redirect_dir)) { struct dirq_entry *subdir_entry = malloc(sizeof(struct dirq_entry)); subdir_entry->dir = strdup(redirect_dir); SIMPLEQ_INSERT_TAIL(subdirq, subdir_entry, entries); LOG_INFO(" subdir: %s\n", tmp_full_path); type = 0; } } #elif defined(__linux__) if (isAlias(name)) { char full_name[MAX_PATH_STR_LEN]; printf("Linux Alias detected\n"); strcpy(full_name, dir); strcat(full_name, "\\"); strcat(full_name, name); FollowLink(full_name, redirect_dir); if (PathIsDirectory(redirect_dir)) { struct dirq_entry *subdir_entry = malloc(sizeof(struct dirq_entry)); subdir_entry->dir = strdup(redirect_dir); SIMPLEQ_INSERT_TAIL(subdirq, subdir_entry, entries); LOG_INFO(" subdir: %s\n", tmp_full_path); type = 0; } } #endif if (parent_entry == NULL) { // Add parent directory to list of dirs with files parent_entry = malloc(sizeof(struct dirq_entry)); parent_entry->dir = strdup(dir); parent_entry->files = malloc(sizeof(struct fileq)); SIMPLEQ_INIT(parent_entry->files); SIMPLEQ_INSERT_TAIL((struct dirq *)s->_dirq, parent_entry, entries); } // Add scannable file to this directory list entry = malloc(sizeof(struct fileq_entry)); entry->file = strdup(name); entry->type = type; SIMPLEQ_INSERT_TAIL(parent_entry->files, entry, entries); s->progress->total++; LOG_INFO(" [%5d] file: %s\n", s->progress->total, entry->file); } } } } closedir(dirp); // Send progress update if (s->on_progress && !s->_want_abort) if (progress_update(s->progress, dir)) send_progress(s); // process subdirs while (!SIMPLEQ_EMPTY(subdirq)) { struct dirq_entry *subdir_entry = SIMPLEQ_FIRST(subdirq); SIMPLEQ_REMOVE_HEAD(subdirq, entries); if (!s->_want_abort) recurse_dir(s, subdir_entry->dir, recurse_count); free(subdir_entry); } free(subdirq); out: free(dir); }
///------------------------------------------------------------------------------------------------- /// Scan a single file. Everything that applies to ms_scan also applies to this function. If /// you know the type of the file, set the type paramter to one of TYPE_AUDIO, TYPE_VIDEO, or /// TYPE_IMAGE. Set it to TYPE_UNKNOWN to have it determined automatically. /// /// @author Andy Grundman /// @date 03/15/2011 /// /// @param [in,out] s If non-null, the. /// @param full_path Full pathname of the full file. /// /// ### remarks . ///------------------------------------------------------------------------------------------------- void ms_scan_file(MediaScan *s, const char *full_path, enum media_type type) { MediaScanError *e = NULL; MediaScanResult *r = NULL; int ret; uint32_t hash; int mtime = 0; uint64_t size = 0; DBT key, data; char tmp_full_path[MAX_PATH_STR_LEN]; #ifdef WIN32 char *ext = strrchr(full_path, '.'); #endif if (s == NULL) { ms_errno = MSENO_NULLSCANOBJ; LOG_ERROR("MediaScan = NULL, aborting scan\n"); return; } if (s->on_result == NULL) { ms_errno = MSENO_NORESULTCALLBACK; LOG_ERROR("Result callback not set, aborting scan\n"); return; } #if (defined(__APPLE__) && defined(__MACH__)) if (isAlias(full_path)) { LOG_INFO("File %s is a mac alias\n", full_path); // Check if this file is a shortcut and if so resolve it if (!CheckMacAlias(full_path, tmp_full_path)) { LOG_ERROR("Failure to follow symlink or alias, skipping file\n"); return; } } else { strcpy(tmp_full_path, full_path); } #elif defined(__unix__) || defined(__unix) if (isAlias(full_path)) { LOG_INFO("File %s is a unix symlink\n", full_path); // Check if this file is a shortcut and if so resolve it FollowLink(full_path, tmp_full_path); } else { strcpy(tmp_full_path, full_path); } #elif defined(WIN32) if (strcasecmp(ext, ".lnk") == 0) { // Check if this file is a shortcut and if so resolve it parse_lnk(full_path, tmp_full_path, MAX_PATH_STR_LEN); if (PathIsDirectory(tmp_full_path)) return; } else { strcpy(tmp_full_path, full_path); } #endif // Check if the file has been recently scanned hash = HashFile(tmp_full_path, &mtime, &size); // Skip 0-byte files if (unlikely(size == 0)) { LOG_WARN("Skipping 0-byte file: %s\n", tmp_full_path); return; } // Setup DBT values memset(&key, 0, sizeof(DBT)); memset(&data, 0, sizeof(DBT)); key.data = (char *)full_path; key.size = strlen(full_path) + 1; data.data = &hash; data.size = sizeof(uint32_t); if ((s->flags & MS_RESCAN) || (s->flags & MS_FULL_SCAN)) { // s->dbp will be null if this function is called directly, if not check if this file is // already scanned. if (s->dbp != NULL) { // DB_GET_BOTH will only return OK if both key and data match, this avoids the need to check // the returned data against hash int ret = s->dbp->get(s->dbp, NULL, &key, &data, DB_GET_BOTH); if (ret != DB_NOTFOUND) { // LOG_INFO("File %s already scanned, skipping\n", tmp_full_path); return; } } } LOG_INFO("Scanning file %s\n", tmp_full_path); if (type == TYPE_UNKNOWN || type == TYPE_LNK) { // auto-detect type type = _should_scan(s, tmp_full_path); if (!type) { if (s->on_error) { ms_errno = MSENO_SCANERROR; e = error_create(tmp_full_path, MS_ERROR_TYPE_UNKNOWN, "Unrecognized file extension"); send_error(s, e); return; } } } r = result_create(s); if (r == NULL) return; r->type = type; r->path = strdup(full_path); if (result_scan(r)) { // These were determined by HashFile r->mtime = mtime; r->size = size; r->hash = hash; // Store path -> hash data in cache if (s->dbp != NULL) { memset(&data, 0, sizeof(DBT)); data.data = &hash; data.size = sizeof(uint32_t); ret = s->dbp->put(s->dbp, NULL, &key, &data, 0); if (ret != 0) { s->dbp->err(s->dbp, ret, "Cache store failed: %s", db_strerror(ret)); } } send_result(s, r); } else { if (s->on_error && r->error) { // Copy the error, because the original will be cleaned up by result_destroy below MediaScanError *ecopy = error_copy(r->error); send_error(s, ecopy); } result_destroy(r); } } /* ms_scan_file() */