static void process_pl_items(plist_t items, int pl_id, const char *name) { plist_t trk; uint64_t itml_id; uint32_t db_id; uint32_t alen; uint32_t i; int ntracks; int ret; db_transaction_begin(); ntracks = 0; alen = plist_array_get_size(items); for (i = 0; i < alen; i++) { trk = plist_array_get_item(items, i); if (plist_get_node_type(trk) != PLIST_DICT) continue; ret = get_dictval_int_from_key(trk, "Track ID", &itml_id); if (ret < 0) { DPRINTF(E_WARN, L_SCAN, "No Track ID found for playlist item %u in '%s'\n", i, name); continue; } db_id = id_map_get(itml_id); if (!db_id) { DPRINTF(E_INFO, L_SCAN, "Did not find a match for track ID %" PRIu64 " in '%s'\n", itml_id, name); continue; } ret = db_pl_add_item_byid(pl_id, db_id); if (ret < 0) DPRINTF(E_WARN, L_SCAN, "Could not add ID %d to playlist '%s'\n", db_id, name); ntracks++; if (ntracks % 200 == 0) { DPRINTF(E_LOG, L_SCAN, "Processed %d tracks from playlist '%s'...\n", ntracks, name); db_transaction_end(); db_transaction_begin(); } } db_transaction_end(); }
void scan_playlist(const char *file, time_t mtime, int dir_id) { FILE *fp; struct media_file_info mfi; struct playlist_info *pli; struct stat sb; char buf[PATH_MAX]; char *path; const char *filename; char *ptr; size_t len; int extinf; int pl_id; int pl_format; int ntracks; int nadded; int ret; ptr = strrchr(file, '.'); if (!ptr) return; if (strcasecmp(ptr, ".m3u") == 0) pl_format = PLAYLIST_M3U; else if (strcasecmp(ptr, ".pls") == 0) pl_format = PLAYLIST_PLS; else return; filename = filename_from_path(file); /* Fetch or create playlist */ pli = db_pl_fetch_bypath(file); if (pli) { db_pl_ping(pli->id); if (mtime && (pli->db_timestamp >= mtime)) { DPRINTF(E_LOG, L_SCAN, "Unchanged playlist found, not processing '%s'\n", file); // Protect this playlist's radio stations from purge after scan db_pl_ping_items_bymatch("http://", pli->id); free_pli(pli, 0); return; } DPRINTF(E_LOG, L_SCAN, "Modified playlist found, processing '%s'\n", file); pl_id = pli->id; db_pl_clear_items(pl_id); } else { DPRINTF(E_LOG, L_SCAN, "New playlist found, processing '%s'\n", file); CHECK_NULL(L_SCAN, pli = calloc(1, sizeof(struct playlist_info))); pli->type = PL_PLAIN; /* Get only the basename, to be used as the playlist title */ pli->title = strip_extension(filename); 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, &pl_id); if (ret < 0) { DPRINTF(E_LOG, L_SCAN, "Error adding playlist '%s'\n", file); free_pli(pli, 0); return; } DPRINTF(E_INFO, L_SCAN, "Added new playlist as id %d\n", pl_id); } free_pli(pli, 0); ret = stat(file, &sb); if (ret < 0) { DPRINTF(E_LOG, L_SCAN, "Could not stat() '%s': %s\n", file, strerror(errno)); return; } fp = fopen(file, "r"); if (!fp) { DPRINTF(E_LOG, L_SCAN, "Could not open playlist '%s': %s\n", file, strerror(errno)); return; } db_transaction_begin(); extinf = 0; memset(&mfi, 0, sizeof(struct media_file_info)); ntracks = 0; nadded = 0; while (fgets(buf, sizeof(buf), fp) != NULL) { len = strlen(buf); /* rtrim and check that length is sane (ignore blank lines) */ while ((len > 0) && isspace(buf[len - 1])) { len--; buf[len] = '\0'; } if (len < 1) continue; /* Saves metadata in mfi if EXTINF metadata line */ if ((pl_format == PLAYLIST_M3U) && extinf_get(buf, &mfi, &extinf)) continue; /* For pls files we are only interested in the part after the FileX= entry */ path = NULL; if ((pl_format == PLAYLIST_PLS) && (strncasecmp(buf, "file", strlen("file")) == 0)) path = strchr(buf, '=') + 1; else if (pl_format == PLAYLIST_M3U) path = buf; if (!path) continue; /* Check that first char is sane for a path */ if ((!isalnum(path[0])) && (path[0] != '/') && (path[0] != '.')) continue; /* Check if line is an URL, will be added to library, otherwise it should already be there */ if (strncasecmp(path, "http://", 7) == 0) ret = process_url(pl_id, path, sb.st_mtime, extinf, &mfi); else ret = process_regular_file(pl_id, path); ntracks++; if (ntracks % 200 == 0) { DPRINTF(E_LOG, L_SCAN, "Processed %d items...\n", ntracks); db_transaction_end(); db_transaction_begin(); } if (ret == 0) nadded++; /* Clean up in preparation for next item */ extinf = 0; free_mfi(&mfi, 1); } db_transaction_end(); /* We had some extinf that we never got to use, free it now */ if (extinf) free_mfi(&mfi, 1); if (!feof(fp)) DPRINTF(E_LOG, L_SCAN, "Error reading playlist '%s' (only added %d tracks): %s\n", file, nadded, strerror(errno)); else DPRINTF(E_LOG, L_SCAN, "Done processing playlist, added/modified %d items\n", nadded); fclose(fp); }
static int process_tracks(plist_t tracks) { plist_t trk; plist_dict_iter iter; char *str; uint64_t trk_id; uint8_t disabled; int ntracks; int nloaded; int mfi_id; int ret; if (plist_dict_get_size(tracks) == 0) { DPRINTF(E_WARN, L_SCAN, "No tracks in iTunes library\n"); return 0; } db_transaction_begin(); ntracks = 0; nloaded = 0; iter = NULL; plist_dict_new_iter(tracks, &iter); plist_dict_next_item(tracks, iter, NULL, &trk); while (trk) { if (plist_get_node_type(trk) != PLIST_DICT) { plist_dict_next_item(tracks, iter, NULL, &trk); continue; } ret = get_dictval_int_from_key(trk, "Track ID", &trk_id); if (ret < 0) { DPRINTF(E_WARN, L_SCAN, "Track ID not found!\n"); plist_dict_next_item(tracks, iter, NULL, &trk); continue; } ret = get_dictval_bool_from_key(trk, "Disabled", &disabled); if (ret < 0) { DPRINTF(E_WARN, L_SCAN, "Malformed track record (id %" PRIu64 ")\n", trk_id); plist_dict_next_item(tracks, iter, NULL, &trk); continue; } if (disabled) { DPRINTF(E_INFO, L_SCAN, "Track %" PRIu64 " disabled; skipping\n", trk_id); plist_dict_next_item(tracks, iter, NULL, &trk); continue; } ret = get_dictval_string_from_key(trk, "Track Type", &str); if (ret < 0) { DPRINTF(E_WARN, L_SCAN, "Track %" PRIu64 " has no track type\n", trk_id); plist_dict_next_item(tracks, iter, NULL, &trk); continue; } if (strcmp(str, "URL") == 0) mfi_id = process_track_stream(trk); else if (strcmp(str, "File") == 0) mfi_id = process_track_file(trk); else { DPRINTF(E_LOG, L_SCAN, "Unknown track type: '%s'\n", str); free(str); plist_dict_next_item(tracks, iter, NULL, &trk); continue; } free(str); ntracks++; if (ntracks % 200 == 0) { DPRINTF(E_LOG, L_SCAN, "Processed %d tracks...\n", ntracks); db_transaction_end(); db_transaction_begin(); } if (mfi_id <= 0) { plist_dict_next_item(tracks, iter, NULL, &trk); continue; } ret = id_map_add(trk_id, mfi_id); if (ret < 0) DPRINTF(E_LOG, L_SCAN, "Out of memory for itml -> db mapping\n"); nloaded++; plist_dict_next_item(tracks, iter, NULL, &trk); } free(iter); db_transaction_end(); return nloaded; }
/* 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(); } }
/* Thread: scan */ static void process_file(char *file, time_t mtime, off_t size, int type, int flags) { switch (file_type_get(file)) { case FILE_REGULAR: filescanner_process_media(file, mtime, size, type, NULL); counter++; /* When in bulk mode, split transaction in pieces of 200 */ if ((flags & F_SCAN_BULK) && (counter % 200 == 0)) { DPRINTF(E_LOG, L_SCAN, "Scanned %d files...\n", counter); db_transaction_end(); db_transaction_begin(); } break; case FILE_PLAYLIST: case FILE_ITUNES: if (flags & F_SCAN_BULK) defer_playlist(file, mtime); else process_playlist(file, mtime); break; case FILE_CTRL_REMOTE: remote_pairing_read_pin(file); break; #ifdef LASTFM case FILE_CTRL_LASTFM: lastfm_login(file); break; #endif #ifdef HAVE_SPOTIFY_H case FILE_CTRL_SPOTIFY: spotify_login(file); break; #endif case FILE_CTRL_INITSCAN: if (flags & F_SCAN_BULK) break; DPRINTF(E_LOG, L_SCAN, "Startup rescan triggered, found init-rescan file: %s\n", file); inofd_event_unset(); // Clears all inotify watches db_watch_clear(); inofd_event_set(); bulk_scan(F_SCAN_BULK | F_SCAN_RESCAN); break; case FILE_CTRL_FULLSCAN: if (flags & F_SCAN_BULK) break; DPRINTF(E_LOG, L_SCAN, "Full rescan triggered, found full-rescan file: %s\n", file); player_playback_stop(); player_queue_clear(); inofd_event_unset(); // Clears all inotify watches db_purge_all(); // Clears files, playlists, playlistitems, inotify and groups inofd_event_set(); bulk_scan(F_SCAN_BULK); break; default: DPRINTF(E_WARN, L_SCAN, "Ignoring file: %s\n", file); } }
/* Thread: scan */ static void process_file(char *file, time_t mtime, off_t size, int type, int flags, int dir_id) { int is_bulkscan; int ret; is_bulkscan = (flags & F_SCAN_BULK); switch (file_type_get(file)) { case FILE_REGULAR: filescanner_process_media(file, mtime, size, type, NULL, dir_id); cache_artwork_ping(file, mtime, !is_bulkscan); // TODO [artworkcache] If entry in artwork cache exists for no artwork available, delete the entry if media file has embedded artwork counter++; /* When in bulk mode, split transaction in pieces of 200 */ if ((flags & F_SCAN_BULK) && (counter % 200 == 0)) { DPRINTF(E_LOG, L_SCAN, "Scanned %d files...\n", counter); db_transaction_end(); db_transaction_begin(); } break; case FILE_PLAYLIST: case FILE_ITUNES: if (flags & F_SCAN_BULK) defer_playlist(file, mtime, dir_id); else process_playlist(file, mtime, dir_id); break; case FILE_SMARTPL: DPRINTF(E_DBG, L_SCAN, "Smart playlist file: %s\n", file); scan_smartpl(file, mtime, dir_id); break; case FILE_ARTWORK: DPRINTF(E_DBG, L_SCAN, "Artwork file: %s\n", file); cache_artwork_ping(file, mtime, !is_bulkscan); // TODO [artworkcache] If entry in artwork cache exists for no artwork available for a album with files in the same directory, delete the entry break; case FILE_CTRL_REMOTE: remote_pairing_read_pin(file); break; case FILE_CTRL_LASTFM: #ifdef LASTFM lastfm_login(file); #else DPRINTF(E_LOG, L_SCAN, "Detected LastFM file, but this version was built without LastFM support\n"); #endif break; case FILE_CTRL_SPOTIFY: #ifdef HAVE_SPOTIFY_H spotify_login(file); #else DPRINTF(E_LOG, L_SCAN, "Detected Spotify file, but this version was built without Spotify support\n"); #endif break; case FILE_CTRL_INITSCAN: if (flags & F_SCAN_BULK) break; DPRINTF(E_LOG, L_SCAN, "Startup rescan triggered, found init-rescan file: %s\n", file); filescanner_initscan(NULL, &ret); break; case FILE_CTRL_FULLSCAN: if (flags & F_SCAN_BULK) break; DPRINTF(E_LOG, L_SCAN, "Full rescan triggered, found full-rescan file: %s\n", file); filescanner_fullrescan(NULL, &ret); break; default: DPRINTF(E_WARN, L_SCAN, "Ignoring file: %s\n", file); } }