Fixed up the indexing a bit
This commit is contained in:
@@ -28,8 +28,12 @@ drift index /path/to/music
|
|||||||
|
|
||||||
Scans for tagged files, fetches similar artists and top tracks from Last.fm, and stores everything in a local SQLite database (`~/.local/share/drift/drift.db`).
|
Scans for tagged files, fetches similar artists and top tracks from Last.fm, and stores everything in a local SQLite database (`~/.local/share/drift/drift.db`).
|
||||||
|
|
||||||
|
Stale tracks (files previously indexed but no longer on disk) are automatically removed.
|
||||||
|
|
||||||
Flags:
|
Flags:
|
||||||
- `-v` — print progress
|
- `-v` — print new artists indexed + summary
|
||||||
|
- `-vv` — also print each track added/removed
|
||||||
|
- `-vvv` — also print skipped artists
|
||||||
- `-f` — re-index artists that were already indexed
|
- `-f` — re-index artists that were already indexed
|
||||||
|
|
||||||
### Build a playlist
|
### Build a playlist
|
||||||
|
|||||||
17
src/db.rs
17
src/db.rs
@@ -145,6 +145,23 @@ pub fn insert_track(
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_tracks_in_directory(conn: &Connection, dir_prefix: &str) -> Result<Vec<String>, rusqlite::Error> {
|
||||||
|
let pattern = format!("{dir_prefix}%");
|
||||||
|
let mut stmt = conn.prepare("SELECT path FROM tracks WHERE path LIKE ?1")?;
|
||||||
|
let rows = stmt.query_map([pattern], |row| row.get(0))?;
|
||||||
|
rows.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn delete_tracks(conn: &Connection, paths: &[String]) -> Result<usize, rusqlite::Error> {
|
||||||
|
let tx = conn.unchecked_transaction()?;
|
||||||
|
let mut deleted = 0;
|
||||||
|
for path in paths {
|
||||||
|
deleted += tx.execute("DELETE FROM tracks WHERE path = ?1", [path])?;
|
||||||
|
}
|
||||||
|
tx.commit()?;
|
||||||
|
Ok(deleted)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn insert_artist_with_similar(
|
pub fn insert_artist_with_similar(
|
||||||
conn: &Connection,
|
conn: &Connection,
|
||||||
mbid: &str,
|
mbid: &str,
|
||||||
|
|||||||
78
src/main.rs
78
src/main.rs
@@ -7,8 +7,9 @@ mod mpd;
|
|||||||
mod playlist;
|
mod playlist;
|
||||||
mod tui;
|
mod tui;
|
||||||
|
|
||||||
|
use std::collections::HashSet;
|
||||||
use std::env;
|
use std::env;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use clap::{Parser, Subcommand};
|
use clap::{Parser, Subcommand};
|
||||||
use rand::prelude::*;
|
use rand::prelude::*;
|
||||||
@@ -26,9 +27,9 @@ struct Cli {
|
|||||||
enum Command {
|
enum Command {
|
||||||
/// Index a music directory
|
/// Index a music directory
|
||||||
Index {
|
Index {
|
||||||
/// Print progress
|
/// Verbosity level (-v, -vv, -vvv)
|
||||||
#[arg(short)]
|
#[arg(short, action = clap::ArgAction::Count)]
|
||||||
verbose: bool,
|
verbose: u8,
|
||||||
/// Re-index already indexed artists
|
/// Re-index already indexed artists
|
||||||
#[arg(short)]
|
#[arg(short)]
|
||||||
force: bool,
|
force: bool,
|
||||||
@@ -109,7 +110,7 @@ fn main() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn cmd_index(verbose: bool, force: bool, directory: &str) {
|
fn cmd_index(verbose: u8, force: bool, directory: &str) {
|
||||||
dotenvy::dotenv().ok();
|
dotenvy::dotenv().ok();
|
||||||
|
|
||||||
let api_key = env::var("LASTFM_API_KEY").unwrap_or_default();
|
let api_key = env::var("LASTFM_API_KEY").unwrap_or_default();
|
||||||
@@ -120,9 +121,30 @@ fn cmd_index(verbose: bool, force: bool, directory: &str) {
|
|||||||
|
|
||||||
let conn = db::open(&db_path()).expect("failed to open database");
|
let conn = db::open(&db_path()).expect("failed to open database");
|
||||||
let lastfm = lastfm::LastfmClient::new(api_key);
|
let lastfm = lastfm::LastfmClient::new(api_key);
|
||||||
let dir = Path::new(directory);
|
|
||||||
|
|
||||||
for path in filesystem::walk_music_files(dir) {
|
let dir = std::fs::canonicalize(directory).unwrap_or_else(|e| {
|
||||||
|
eprintln!("Error: cannot resolve directory {directory}: {e}");
|
||||||
|
std::process::exit(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
let dir_prefix = format!("{}/", dir.display());
|
||||||
|
|
||||||
|
let mut stale_paths: HashSet<String> = match db::get_tracks_in_directory(&conn, &dir_prefix) {
|
||||||
|
Ok(paths) => paths.into_iter().collect(),
|
||||||
|
Err(e) => {
|
||||||
|
eprintln!("DB error loading existing tracks: {e}");
|
||||||
|
HashSet::new()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut artists_indexed: usize = 0;
|
||||||
|
let mut artists_skipped: usize = 0;
|
||||||
|
let mut tracks_added: usize = 0;
|
||||||
|
|
||||||
|
for path in filesystem::walk_music_files(&dir) {
|
||||||
|
let path_str = path.to_string_lossy().into_owned();
|
||||||
|
stale_paths.remove(&path_str);
|
||||||
|
|
||||||
let tags = match metadata::read_tags(&path, &[
|
let tags = match metadata::read_tags(&path, &[
|
||||||
metadata::Tag::ArtistMbid,
|
metadata::Tag::ArtistMbid,
|
||||||
metadata::Tag::TrackMbid,
|
metadata::Tag::TrackMbid,
|
||||||
@@ -152,7 +174,7 @@ fn cmd_index(verbose: bool, force: bool, directory: &str) {
|
|||||||
let display_name = artist_name.as_deref().unwrap_or(&artist_mbid);
|
let display_name = artist_name.as_deref().unwrap_or(&artist_mbid);
|
||||||
|
|
||||||
if !already_indexed || force {
|
if !already_indexed || force {
|
||||||
if verbose {
|
if verbose >= 1 {
|
||||||
println!("Indexing {display_name}...");
|
println!("Indexing {display_name}...");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -184,14 +206,50 @@ fn cmd_index(verbose: bool, force: bool, directory: &str) {
|
|||||||
eprintln!("Last.fm top tracks error for {display_name}: {e}");
|
eprintln!("Last.fm top tracks error for {display_name}: {e}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if verbose {
|
|
||||||
|
artists_indexed += 1;
|
||||||
|
} else {
|
||||||
|
if verbose >= 3 {
|
||||||
println!("Skipping {display_name} (already indexed)");
|
println!("Skipping {display_name} (already indexed)");
|
||||||
}
|
}
|
||||||
|
artists_skipped += 1;
|
||||||
|
}
|
||||||
|
|
||||||
let path_str = path.to_string_lossy();
|
|
||||||
if let Err(e) = db::insert_track(&conn, &path_str, &artist_mbid, recording_mbid.as_deref(), track_title.as_deref()) {
|
if let Err(e) = db::insert_track(&conn, &path_str, &artist_mbid, recording_mbid.as_deref(), track_title.as_deref()) {
|
||||||
eprintln!("DB error inserting track {}: {e}", path.display());
|
eprintln!("DB error inserting track {}: {e}", path.display());
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
tracks_added += 1;
|
||||||
|
|
||||||
|
if verbose >= 2 {
|
||||||
|
let label = track_title.as_deref().unwrap_or(&path_str);
|
||||||
|
println!(" + {label}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sweep stale tracks
|
||||||
|
let stale: Vec<String> = stale_paths.into_iter().collect();
|
||||||
|
let tracks_stale = if !stale.is_empty() {
|
||||||
|
if verbose >= 2 {
|
||||||
|
for p in &stale {
|
||||||
|
println!(" - {p}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
match db::delete_tracks(&conn, &stale) {
|
||||||
|
Ok(n) => n,
|
||||||
|
Err(e) => {
|
||||||
|
eprintln!("DB error removing stale tracks: {e}");
|
||||||
|
0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
};
|
||||||
|
|
||||||
|
if verbose >= 1 {
|
||||||
|
println!(
|
||||||
|
"Done: {artists_indexed} artists indexed, {artists_skipped} skipped, {tracks_added} tracks added, {tracks_stale} stale tracks removed"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user