feat: recursively scan through directory, refactor library and import functions, add more basic information to schema
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,2 +1,3 @@
|
||||
/target
|
||||
/test/fixtures/
|
||||
.env
|
||||
96
Cargo.lock
generated
96
Cargo.lock
generated
@@ -118,6 +118,17 @@ dependencies = [
|
||||
"syn 2.0.119",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "diesel_migrations"
|
||||
version = "2.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "28d0f4a98124ba6d4ca75da535f65984badec16a003b6e2f94a01e31a79490b8"
|
||||
dependencies = [
|
||||
"diesel",
|
||||
"migrations_internals",
|
||||
"migrations_macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "diesel_table_macro_syntax"
|
||||
version = "0.3.0"
|
||||
@@ -261,6 +272,28 @@ version = "0.4.34"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f9f8bd3e56ce4dfc153cf470fffbfa98c7620958b312ca5c3a4b8d5181fd13c6"
|
||||
|
||||
[[package]]
|
||||
name = "migrations_internals"
|
||||
version = "2.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "36c791ecdf977c99f45f23280405d7723727470f6689a5e6dbf513ac547ae10d"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"toml",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "migrations_macros"
|
||||
version = "2.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9423d6affd681fb4d25d35885d0d0262c52f8ade16e96f87c064c442e5aa5d47"
|
||||
dependencies = [
|
||||
"migrations_internals",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 3.0.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "miniz_oxide"
|
||||
version = "0.9.1"
|
||||
@@ -344,6 +377,16 @@ version = "1.0.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.229"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba"
|
||||
dependencies = [
|
||||
"serde_core",
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_core"
|
||||
version = "1.0.229"
|
||||
@@ -364,6 +407,15 @@ dependencies = [
|
||||
"syn 3.0.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_spanned"
|
||||
version = "1.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4e7523beb55eece201a2356bee0bbca0d1ab466c14c07703b2e0ee6d42cb0c2c"
|
||||
dependencies = [
|
||||
"serde_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "shlex"
|
||||
version = "2.0.1"
|
||||
@@ -466,11 +518,43 @@ dependencies = [
|
||||
"time-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml"
|
||||
version = "0.9.12+spec-1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf92845e79fc2e2def6a5d828f0801e29a2f8acc037becc5ab08595c7d5e9863"
|
||||
dependencies = [
|
||||
"serde_core",
|
||||
"serde_spanned",
|
||||
"toml_datetime",
|
||||
"toml_parser",
|
||||
"winnow 0.7.15",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_datetime"
|
||||
version = "0.7.5+spec-1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347"
|
||||
dependencies = [
|
||||
"serde_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_parser"
|
||||
version = "1.1.5+spec-1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baa693a8032d7e1cada7d0041e96126df243179ff061456783ac7f12bda4744c"
|
||||
dependencies = [
|
||||
"winnow 1.0.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tuneparty_pre1"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"diesel",
|
||||
"diesel_migrations",
|
||||
"dotenvy",
|
||||
"lofty",
|
||||
]
|
||||
@@ -532,6 +616,18 @@ dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winnow"
|
||||
version = "0.7.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945"
|
||||
|
||||
[[package]]
|
||||
name = "winnow"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "23b97319f7b8343df12cc98938e5c3eb436064524c8d2b4e30a1d3a36eecdf81"
|
||||
|
||||
[[package]]
|
||||
name = "zlib-rs"
|
||||
version = "0.6.8"
|
||||
|
||||
@@ -5,5 +5,6 @@ edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
diesel = { version = "2.3.14", features = ["sqlite", "returning_clauses_for_sqlite_3_35"] }
|
||||
diesel_migrations = { version = "2.3.2", features = ["sqlite"] }
|
||||
dotenvy = "0.15.7"
|
||||
lofty = "0.25.4"
|
||||
|
||||
@@ -1,19 +1,26 @@
|
||||
CREATE TABLE artists (
|
||||
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||
original_name TEXT NOT NULL,
|
||||
display_name TEXT NOT NULL
|
||||
display_name TEXT NOT NULL,
|
||||
UNIQUE (original_name)
|
||||
);
|
||||
|
||||
CREATE TABLE collections (
|
||||
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||
artist_id INTEGER,
|
||||
collection_type TEXT NOT NULL,
|
||||
original_name TEXT NOT NULL,
|
||||
display_name TEXT NOT NULL
|
||||
display_name TEXT NOT NULL,
|
||||
UNIQUE (original_name),
|
||||
CONSTRAINT fk_collections_artist
|
||||
FOREIGN KEY (artist_id)
|
||||
REFERENCES artists (id)
|
||||
);
|
||||
|
||||
CREATE TABLE tracks (
|
||||
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||
artist_id INTEGER NOT NULL,
|
||||
file_path TEXT NOT NULL,
|
||||
artist_id INTEGER,
|
||||
collection_id INTEGER,
|
||||
original_title TEXT,
|
||||
display_title TEXT,
|
||||
|
||||
257
src/library.rs
Normal file
257
src/library.rs
Normal file
@@ -0,0 +1,257 @@
|
||||
use std::{
|
||||
error::Error,
|
||||
fs::{self, File},
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
|
||||
use diesel::{prelude::*, sqlite::SqliteConnection};
|
||||
use lofty::{prelude::*, read_from};
|
||||
|
||||
use crate::{
|
||||
models::{Artist, Collection, NewArtist, NewCollection, NewTrack, Track},
|
||||
schema,
|
||||
types::{
|
||||
errors::{ScanError, TrackScanError},
|
||||
library::{ScannedTrack, ScannedTrackMetadata},
|
||||
},
|
||||
};
|
||||
|
||||
pub struct Library {
|
||||
conn: SqliteConnection,
|
||||
}
|
||||
|
||||
impl Library {
|
||||
pub fn new(conn: SqliteConnection) -> Self {
|
||||
Self { conn }
|
||||
}
|
||||
|
||||
pub fn print(&mut self) -> QueryResult<()> {
|
||||
let artists = schema::artists::table
|
||||
.order(schema::artists::id.asc())
|
||||
.load::<Artist>(&mut self.conn)?;
|
||||
|
||||
let collections = schema::collections::table
|
||||
.order(schema::collections::id.asc())
|
||||
.load::<Collection>(&mut self.conn)?;
|
||||
|
||||
let tracks = schema::tracks::table
|
||||
.order(schema::tracks::id.asc())
|
||||
.load::<Track>(&mut self.conn)?;
|
||||
|
||||
println!("Artists:\n{artists:#?}");
|
||||
println!("Collections:\n{collections:#?}");
|
||||
println!("Tracks:\n{tracks:#?}");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Importer;
|
||||
|
||||
impl Importer {
|
||||
pub fn scan(path: &Path) -> Result<Vec<ScannedTrack>, ScanError> {
|
||||
println!("Scanning library...");
|
||||
|
||||
if !path.is_dir() {
|
||||
return Err(ScanError::NotDirectory(path.to_path_buf()));
|
||||
}
|
||||
|
||||
let mut tracks = Vec::new();
|
||||
Self::scan_directory(path, &mut tracks)?;
|
||||
Ok(tracks)
|
||||
}
|
||||
|
||||
fn scan_directory(path: &Path, tracks: &mut Vec<ScannedTrack>) -> Result<(), ScanError> {
|
||||
let entries = fs::read_dir(path).map_err(|source| ScanError::ReadDir {
|
||||
path: path.to_path_buf(),
|
||||
source,
|
||||
})?;
|
||||
|
||||
for entry in entries {
|
||||
let entry = match entry {
|
||||
Ok(entry) => entry,
|
||||
Err(error) => {
|
||||
eprintln!("Could not read an entry in {}: {error}", path.display());
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
let entry_path = entry.path();
|
||||
let file_type = match entry.file_type() {
|
||||
Ok(file_type) => file_type,
|
||||
Err(error) => {
|
||||
eprintln!("Could not inspect {}: {error}", entry_path.display());
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
if file_type.is_dir() {
|
||||
Self::scan_directory(&entry_path, tracks)?;
|
||||
} else if file_type.is_file() {
|
||||
tracks.push(ScannedTrack {
|
||||
metadata: Self::read_metadata(&entry_path),
|
||||
path: entry_path,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn read_metadata(path: &Path) -> Result<ScannedTrackMetadata, TrackScanError> {
|
||||
let mut file = File::open(path).map_err(|source| TrackScanError::FileOpen {
|
||||
path: path.to_path_buf(),
|
||||
source,
|
||||
})?;
|
||||
|
||||
let tagged_file = read_from(&mut file).map_err(|source| TrackScanError::FileParse {
|
||||
path: path.to_path_buf(),
|
||||
source,
|
||||
})?;
|
||||
|
||||
let length_sec =
|
||||
i32::try_from(tagged_file.properties().duration().as_secs()).unwrap_or(i32::MAX);
|
||||
|
||||
let Some(tags) = tagged_file.primary_tag() else {
|
||||
return Ok(ScannedTrackMetadata {
|
||||
artist: None,
|
||||
collection: None,
|
||||
title: None,
|
||||
number: None,
|
||||
genre: None,
|
||||
datetime: None,
|
||||
length_sec,
|
||||
});
|
||||
};
|
||||
|
||||
Ok(ScannedTrackMetadata {
|
||||
artist: tags.artist().map(String::from),
|
||||
collection: tags.album().map(String::from),
|
||||
title: tags.title().map(String::from),
|
||||
number: tags
|
||||
.track()
|
||||
.map(|number| i32::try_from(number).unwrap_or(i32::MAX)),
|
||||
genre: tags.genre().map(String::from),
|
||||
datetime: tags.date(),
|
||||
length_sec,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn import_scan(library: &mut Library, path: &Path) -> Result<(), Box<dyn Error>> {
|
||||
for track in Self::scan(path)? {
|
||||
let file_path = track.path.to_string_lossy();
|
||||
|
||||
println!("-- {file_path}");
|
||||
|
||||
match track.metadata {
|
||||
Ok(metadata) => {
|
||||
Self::import_tagged_track(library, &file_path, &metadata)?;
|
||||
|
||||
println!(
|
||||
"artist: {}",
|
||||
metadata.artist.as_deref().unwrap_or("unknown")
|
||||
);
|
||||
println!(
|
||||
"collection: {}",
|
||||
metadata.collection.as_deref().unwrap_or("unknown")
|
||||
);
|
||||
println!("title: {}", metadata.title.as_deref().unwrap_or("unknown"));
|
||||
println!(
|
||||
"number: {}",
|
||||
metadata
|
||||
.number
|
||||
.map(|value| value.to_string())
|
||||
.unwrap_or_else(|| "unknown".to_owned())
|
||||
);
|
||||
println!("genre: {}", metadata.genre.as_deref().unwrap_or("unknown"));
|
||||
println!(
|
||||
"datetime: {}",
|
||||
metadata
|
||||
.datetime
|
||||
.as_ref()
|
||||
.map(ToString::to_string)
|
||||
.unwrap_or_else(|| "unknown".to_owned())
|
||||
);
|
||||
println!("length_sec: {}", metadata.length_sec);
|
||||
}
|
||||
Err(error) => eprintln!("{error}"),
|
||||
}
|
||||
|
||||
println!();
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_or_create_artist(conn: &mut SqliteConnection, name: &str) -> QueryResult<i32> {
|
||||
diesel::insert_into(schema::artists::table)
|
||||
.values(NewArtist {
|
||||
original_name: name,
|
||||
display_name: name,
|
||||
})
|
||||
.on_conflict(schema::artists::original_name)
|
||||
.do_nothing()
|
||||
.execute(conn)?;
|
||||
|
||||
schema::artists::table
|
||||
.filter(schema::artists::original_name.eq(name))
|
||||
.select(schema::artists::id)
|
||||
.first(conn)
|
||||
}
|
||||
|
||||
fn get_or_create_collection(
|
||||
conn: &mut SqliteConnection,
|
||||
artist_id: Option<i32>,
|
||||
name: &str,
|
||||
) -> QueryResult<i32> {
|
||||
diesel::insert_into(schema::collections::table)
|
||||
.values(NewCollection {
|
||||
artist_id,
|
||||
collection_type: "album",
|
||||
original_name: name,
|
||||
display_name: name,
|
||||
})
|
||||
.on_conflict(schema::collections::original_name)
|
||||
.do_nothing()
|
||||
.execute(conn)?;
|
||||
|
||||
schema::collections::table
|
||||
.filter(schema::collections::original_name.eq(name))
|
||||
.select(schema::collections::id)
|
||||
.first(conn)
|
||||
}
|
||||
|
||||
fn import_tagged_track(
|
||||
library: &mut Library,
|
||||
path: &str,
|
||||
metadata: &ScannedTrackMetadata,
|
||||
) -> QueryResult<usize> {
|
||||
let artist_id = metadata
|
||||
.artist
|
||||
.as_deref()
|
||||
.map(|name| Self::get_or_create_artist(&mut library.conn, name))
|
||||
.transpose()?;
|
||||
|
||||
let collection_id = metadata
|
||||
.collection
|
||||
.as_deref()
|
||||
.map(|name| Self::get_or_create_collection(&mut library.conn, artist_id, name))
|
||||
.transpose()?;
|
||||
|
||||
let track_datetime = metadata.datetime.as_ref().map(ToString::to_string);
|
||||
|
||||
diesel::insert_into(schema::tracks::table)
|
||||
.values(NewTrack {
|
||||
file_path: path,
|
||||
artist_id,
|
||||
collection_id,
|
||||
original_title: metadata.title.as_deref(),
|
||||
display_title: metadata.title.as_deref(),
|
||||
track_number: metadata.number,
|
||||
genre: metadata.genre.as_deref(),
|
||||
track_datetime: track_datetime.as_deref(),
|
||||
length_sec: metadata.length_sec,
|
||||
})
|
||||
.execute(&mut library.conn)
|
||||
}
|
||||
}
|
||||
@@ -1,92 +0,0 @@
|
||||
use std::error::Error;
|
||||
use std::fmt;
|
||||
use std::fs;
|
||||
use std::fs::File;
|
||||
use std::io;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use lofty::file;
|
||||
use lofty::prelude::*;
|
||||
use lofty::read_from;
|
||||
|
||||
use crate::types::errors::{ScanError, TrackScanError};
|
||||
use crate::types::library::ScannedTrack;
|
||||
use crate::types::library::ScannedTrackMetadata;
|
||||
|
||||
pub fn scan(path: &Path) -> Result<Vec<ScannedTrack>, ScanError> {
|
||||
println!("Scanning library...");
|
||||
|
||||
if !path.is_dir() {
|
||||
return Err(ScanError::NotDirectory(path.to_path_buf()));
|
||||
}
|
||||
|
||||
let entries = fs::read_dir(path).map_err(|source| ScanError::ReadDir {
|
||||
path: path.to_path_buf(),
|
||||
source,
|
||||
})?;
|
||||
|
||||
let mut tracks: Vec<ScannedTrack> = Vec::new();
|
||||
|
||||
// iterate through entries in directory (depth of one)
|
||||
for entry in entries {
|
||||
// validate if entry is even valid (this annoys me)
|
||||
if let Ok(entry) = entry {
|
||||
let entry_path = entry.path();
|
||||
|
||||
// try to read file and come up with metadata
|
||||
let metadata = || -> Result<ScannedTrackMetadata, TrackScanError> {
|
||||
let mut file =
|
||||
File::open(&entry_path).map_err(|source| TrackScanError::FileOpen {
|
||||
path: entry_path.clone(),
|
||||
source,
|
||||
})?;
|
||||
|
||||
let tagged_file =
|
||||
read_from(&mut file).map_err(|source| TrackScanError::FileParse {
|
||||
path: entry_path.clone(),
|
||||
source,
|
||||
})?;
|
||||
|
||||
let props = tagged_file.properties();
|
||||
|
||||
let track_length_sec = u32::try_from(props.duration().as_secs())
|
||||
.unwrap_or(u32::MAX);
|
||||
|
||||
if let Some(tags) = tagged_file.primary_tag() {
|
||||
Ok(ScannedTrackMetadata {
|
||||
artist: tags.artist().as_deref().map(|tag| String::from(tag)),
|
||||
collection: tags.album().as_deref().map(|tag| String::from(tag)),
|
||||
title: tags.title().as_deref().map(|tag| String::from(tag)),
|
||||
number: tags.track(),
|
||||
genre: tags.genre().as_deref().map(|tag| String::from(tag)),
|
||||
datetime: tags.date(),
|
||||
length_sec: track_length_sec
|
||||
})
|
||||
} else {
|
||||
Ok(ScannedTrackMetadata {
|
||||
artist: None,
|
||||
collection: None,
|
||||
title: None,
|
||||
number: None,
|
||||
genre: None,
|
||||
datetime: None,
|
||||
length_sec: track_length_sec
|
||||
})
|
||||
}
|
||||
}();
|
||||
|
||||
tracks.push(ScannedTrack {
|
||||
path: entry_path,
|
||||
metadata,
|
||||
});
|
||||
} else {
|
||||
eprintln!(
|
||||
"there was an error reading some file in {}",
|
||||
path.to_str().unwrap()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(tracks)
|
||||
}
|
||||
|
||||
74
src/main.rs
74
src/main.rs
@@ -1,72 +1,50 @@
|
||||
use std::{fs::metadata, path::Path};
|
||||
use std::{
|
||||
fs::metadata,
|
||||
path::{Path, PathBuf},
|
||||
string,
|
||||
};
|
||||
|
||||
use diesel::prelude::*;
|
||||
use dotenvy::dotenv;
|
||||
use std::env;
|
||||
|
||||
mod library_scan;
|
||||
mod types;
|
||||
mod library;
|
||||
mod models;
|
||||
mod schema;
|
||||
mod types;
|
||||
|
||||
use library::{Importer, Library};
|
||||
|
||||
fn open_db_file() -> SqliteConnection {
|
||||
dotenv().ok();
|
||||
|
||||
let database_url = env::var("DATABASE_URL")
|
||||
.expect("DATABASE_URL missing");
|
||||
let database_url = env::var("DATABASE_URL").expect("DATABASE_URL missing");
|
||||
|
||||
SqliteConnection::establish(&database_url)
|
||||
.unwrap_or_else(|_| panic!("Error connecting to {}", database_url))
|
||||
}
|
||||
|
||||
fn open_db_in_memory() -> SqliteConnection {
|
||||
SqliteConnection::establish(":memory:")
|
||||
.unwrap_or_else(|_| panic!("Error starting in-memory database"))
|
||||
}
|
||||
use diesel_migrations::{EmbeddedMigrations, MigrationHarness, embed_migrations};
|
||||
|
||||
fn import_scan(conn: &mut SqliteConnection) {
|
||||
for track in library_scan::scan(Path::new("./test/fixtures")).expect("oh no") {
|
||||
println!("-- {}", track.path.into_string().unwrap());
|
||||
const MIGRATIONS: EmbeddedMigrations = embed_migrations!();
|
||||
|
||||
match track.metadata {
|
||||
Ok(metadata) => {
|
||||
println!("artist: {}", metadata.artist.as_deref().unwrap_or("unknown"));
|
||||
println!(
|
||||
"collection: {}",
|
||||
metadata.collection.as_deref().unwrap_or("unknown")
|
||||
);
|
||||
println!("title: {}", metadata.title.as_deref().unwrap_or("unknown"));
|
||||
println!(
|
||||
"number: {}",
|
||||
metadata
|
||||
.number
|
||||
.map(|value| value.to_string())
|
||||
.unwrap_or_else(|| "unknown".to_owned())
|
||||
);
|
||||
println!("genre: {}", metadata.genre.as_deref().unwrap_or("unknown"));
|
||||
println!(
|
||||
"datetime: {}",
|
||||
metadata
|
||||
.datetime
|
||||
.as_ref()
|
||||
.map(|value| format!("{value:?}"))
|
||||
.unwrap_or_else(|| "unknown".to_owned())
|
||||
);
|
||||
println!("length_sec: {}", metadata.length_sec);
|
||||
}
|
||||
Err(e) => {
|
||||
eprint!("{e}");
|
||||
}
|
||||
}
|
||||
println!("");
|
||||
}
|
||||
}
|
||||
let mut conn = SqliteConnection::establish(":memory:")
|
||||
.unwrap_or_else(|_| panic!("Error starting in-memory database"));
|
||||
|
||||
conn.run_pending_migrations(MIGRATIONS)
|
||||
.unwrap_or_else(|e| panic!("Error applying in-memory migrations: {}", e));
|
||||
|
||||
conn
|
||||
}
|
||||
fn main() {
|
||||
println!("Hello, world!");
|
||||
dotenv().ok();
|
||||
|
||||
let conn = &mut open_db_in_memory();
|
||||
let mut library_instance = Library::new(open_db_in_memory());
|
||||
|
||||
import_scan(conn);
|
||||
let import_path = env::var("IMPORT_PATH").expect("IMPORT_PATH not set!");
|
||||
|
||||
Importer::import_scan(&mut library_instance, Path::new(&import_path))
|
||||
.expect("Error importing files");
|
||||
|
||||
library_instance.print().expect("Error printing library");
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ pub struct Artist {
|
||||
#[diesel(check_for_backend(diesel::sqlite::Sqlite))]
|
||||
pub struct Collection {
|
||||
pub id: i32,
|
||||
pub artist_id: Option<i32>,
|
||||
pub collection_type: String,
|
||||
pub original_name: String,
|
||||
pub display_name: String,
|
||||
@@ -28,7 +29,8 @@ pub struct Collection {
|
||||
#[diesel(check_for_backend(diesel::sqlite::Sqlite))]
|
||||
pub struct Track {
|
||||
pub id: i32,
|
||||
pub artist_id: i32,
|
||||
pub file_path: String,
|
||||
pub artist_id: Option<i32>,
|
||||
pub collection_id: Option<i32>,
|
||||
pub original_title: Option<String>,
|
||||
pub display_title: Option<String>,
|
||||
@@ -48,6 +50,7 @@ pub struct NewArtist<'a> {
|
||||
#[derive(Debug, Clone, Insertable)]
|
||||
#[diesel(table_name = collections)]
|
||||
pub struct NewCollection<'a> {
|
||||
pub artist_id: Option<i32>,
|
||||
pub collection_type: &'a str,
|
||||
pub original_name: &'a str,
|
||||
pub display_name: &'a str,
|
||||
@@ -56,7 +59,8 @@ pub struct NewCollection<'a> {
|
||||
#[derive(Debug, Clone, Insertable)]
|
||||
#[diesel(table_name = tracks)]
|
||||
pub struct NewTrack<'a> {
|
||||
pub artist_id: i32,
|
||||
pub file_path: &'a str,
|
||||
pub artist_id: Option<i32>,
|
||||
pub collection_id: Option<i32>,
|
||||
pub original_title: Option<&'a str>,
|
||||
pub display_title: Option<&'a str>,
|
||||
|
||||
@@ -11,6 +11,7 @@ diesel::table! {
|
||||
diesel::table! {
|
||||
collections (id) {
|
||||
id -> Integer,
|
||||
artist_id -> Nullable<Integer>,
|
||||
collection_type -> Text,
|
||||
original_name -> Text,
|
||||
display_name -> Text,
|
||||
@@ -20,7 +21,8 @@ diesel::table! {
|
||||
diesel::table! {
|
||||
tracks (id) {
|
||||
id -> Integer,
|
||||
artist_id -> Integer,
|
||||
file_path -> Text,
|
||||
artist_id -> Nullable<Integer>,
|
||||
collection_id -> Nullable<Integer>,
|
||||
original_title -> Nullable<Text>,
|
||||
display_title -> Nullable<Text>,
|
||||
@@ -31,6 +33,7 @@ diesel::table! {
|
||||
}
|
||||
}
|
||||
|
||||
diesel::joinable!(collections -> artists (artist_id));
|
||||
diesel::joinable!(tracks -> artists (artist_id));
|
||||
diesel::joinable!(tracks -> collections (collection_id));
|
||||
|
||||
|
||||
@@ -22,10 +22,10 @@ use crate::types::errors;
|
||||
pub artist: Option<String>,
|
||||
pub collection: Option<String>,
|
||||
pub title: Option<String>,
|
||||
pub number: Option<u32>,
|
||||
pub number: Option<i32>,
|
||||
pub genre: Option<String>,
|
||||
pub datetime: Option<Timestamp>,
|
||||
pub length_sec: u32
|
||||
pub length_sec: i32,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
|
||||
Reference in New Issue
Block a user