void scan_itunes_itml(const char *file, time_t mtime, int dir_id) { struct playlist_info *pli; struct stat sb; char buf[PATH_MAX]; char *itml_xml; plist_t itml; plist_t node; int fd; int ret; // This is special playlist that is disabled and only used for saving a timestamp pli = db_pl_fetch_bytitlepath(file, file); if (pli) { // mtime == db_timestamp is also treated as a modification because some editors do // stuff like 1) close the file with no changes (leading us to update db_timestamp), // 2) copy over a modified version from a tmp file (which may result in a mtime that // is equal to the newly updated db_timestamp) if (mtime && (pli->db_timestamp > mtime)) { DPRINTF(E_LOG, L_SCAN, "Unchanged iTunes XML found, not processing '%s'\n", file); // TODO Protect the radio stations from purge after scan db_pl_ping_bymatch(file, 0); free_pli(pli, 0); return; } DPRINTF(E_LOG, L_SCAN, "Modified iTunes XML found, processing '%s'\n", file); // Clear out everything, we will recreate db_pl_delete_bypath(file); free_pli(pli, 0); } else { DPRINTF(E_LOG, L_SCAN, "New iTunes XML found, processing: '%s'\n", file); } CHECK_NULL(L_SCAN, pli = calloc(1, sizeof(struct playlist_info))); pli->type = PL_PLAIN; pli->title = strdup(file); pli->path = strdup(file); snprintf(buf, sizeof(buf), "/file:%s", file); pli->virtual_path = strip_extension(buf); pli->directory_id = dir_id; ret = db_pl_add(pli, (int *)&pli->id); if (ret < 0) { DPRINTF(E_LOG, L_SCAN, "Error adding iTunes XML meta playlist '%s'\n", file); free_pli(pli, 0); return; } // Disable, only used for saving timestamp db_pl_disable_bypath(file, STRIP_NONE, 0); free_pli(pli, 0); fd = open(file, O_RDONLY); if (fd < 0) { DPRINTF(E_LOG, L_SCAN, "Could not open iTunes library '%s': %s\n", file, strerror(errno)); return; } ret = fstat(fd, &sb); if (ret < 0) { DPRINTF(E_LOG, L_SCAN, "Could not stat iTunes library '%s': %s\n", file, strerror(errno)); close(fd); return; } itml_xml = mmap(NULL, sb.st_size, PROT_READ, MAP_SHARED, fd, 0); if (itml_xml == MAP_FAILED) { DPRINTF(E_LOG, L_SCAN, "Could not map iTunes library '%s': %s\n", file, strerror(errno)); close(fd); return; } itml = NULL; plist_from_xml(itml_xml, sb.st_size, &itml); ret = munmap(itml_xml, sb.st_size); if (ret < 0) DPRINTF(E_LOG, L_SCAN, "Could not unmap iTunes library '%s': %s\n", file, strerror(errno)); close(fd); if (!itml) { DPRINTF(E_LOG, L_SCAN, "iTunes XML playlist '%s' failed to parse\n", file); return; } if (plist_get_node_type(itml) != PLIST_DICT) { DPRINTF(E_LOG, L_SCAN, "Malformed iTunes XML playlist '%s'\n", file); plist_free(itml); return; } /* Meta data */ ret = check_meta(itml); if (ret < 0) { plist_free(itml); return; } /* Tracks */ ret = get_dictval_dict_from_key(itml, "Tracks", &node); if (ret < 0) { DPRINTF(E_LOG, L_SCAN, "Could not find Tracks dict in '%s'\n", file); plist_free(itml); return; } id_map = calloc(ID_MAP_SIZE, sizeof(struct itml_to_db_map *)); if (!id_map) { DPRINTF(E_FATAL, L_SCAN, "iTunes library parser could not allocate ID map\n"); plist_free(itml); return; } ret = process_tracks(node); if (ret <= 0) { DPRINTF(E_LOG, L_SCAN, "No tracks loaded from iTunes XML '%s'\n", file); id_map_free(); plist_free(itml); return; } DPRINTF(E_LOG, L_SCAN, "Loaded %d tracks from iTunes XML '%s'\n", ret, file); /* Playlists */ ret = get_dictval_array_from_key(itml, "Playlists", &node); if (ret < 0) { DPRINTF(E_LOG, L_SCAN, "Could not find Playlists dict in '%s'\n", file); id_map_free(); plist_free(itml); return; } process_pls(node, file); id_map_free(); plist_free(itml); }
/* Thread: scan */ static void bulk_scan(int flags) { cfg_t *lib; int ndirs; char *path; char *deref; time_t start; time_t end; int i; start = time(NULL); playlists = NULL; dirstack = NULL; lib = cfg_getsec(cfg, "library"); ndirs = cfg_size(lib, "directories"); for (i = 0; i < ndirs; i++) { path = cfg_getnstr(lib, "directories", i); deref = m_realpath(path); if (!deref) { DPRINTF(E_LOG, L_SCAN, "Skipping library directory %s, could not dereference: %s\n", path, strerror(errno)); /* Assume dir is mistakenly not mounted, so just disable everything and update timestamps */ db_file_disable_bymatch(path, "", 0); db_pl_disable_bymatch(path, "", 0); db_file_ping_bymatch(path, 1); db_pl_ping_bymatch(path, 1); continue; } counter = 0; db_transaction_begin(); process_directories(deref, flags); db_transaction_end(); free(deref); if (scan_exit) return; } if (!(flags & F_SCAN_FAST) && playlists) process_deferred_playlists(); if (scan_exit) return; if (dirstack) DPRINTF(E_LOG, L_SCAN, "WARNING: unhandled leftover directories\n"); end = time(NULL); if (flags & F_SCAN_FAST) { DPRINTF(E_LOG, L_SCAN, "Bulk library scan completed in %.f sec (with file scan disabled)\n", difftime(end, start)); } else { /* Protect spotify from the imminent purge if rescanning */ if (flags & F_SCAN_RESCAN) { db_file_ping_bymatch("spotify:", 0); db_pl_ping_bymatch("spotify:", 0); } DPRINTF(E_DBG, L_SCAN, "Purging old database content\n"); db_purge_cruft(start); DPRINTF(E_LOG, L_SCAN, "Bulk library scan completed in %.f sec\n", difftime(end, start)); DPRINTF(E_DBG, L_SCAN, "Running post library scan jobs\n"); db_hook_post_scan(); } }