diff --git a/Cargo.lock b/Cargo.lock index a315c47..7b423d6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -560,6 +560,8 @@ dependencies = [ "mime", "mime_guess", "paris", + "reqwest", + "rmp-serde", "sea-orm", "sea-orm-migration", "sea-query", @@ -622,6 +624,12 @@ dependencies = [ "spin", ] +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + [[package]] name = "foreign-types" version = "0.3.2" @@ -801,6 +809,25 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "h2" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ca32592cf21ac7ccab1825cd87f6c9b3d9022c44d086172ed0966bec8af30be" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + [[package]] name = "hashbrown" version = "0.12.3" @@ -861,6 +888,77 @@ dependencies = [ "digest", ] +[[package]] +name = "http" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75f43d41e26995c17e71ee126451dd3941010b0514a81a9d11f3b341debc2399" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" +dependencies = [ + "bytes", + "http", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" + +[[package]] +name = "httpdate" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" + +[[package]] +name = "hyper" +version = "0.14.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02c929dc5c39e335a03c405292728118860721b10190d98c2a0f0efd5baafbac" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", + "want", +] + +[[package]] +name = "hyper-tls" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" +dependencies = [ + "bytes", + "hyper", + "native-tls", + "tokio", + "tokio-native-tls", +] + [[package]] name = "idna" version = "0.2.3" @@ -891,6 +989,12 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "ipnet" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "879d54834c8c76457ef4293a689b2a8c59b076067ad77b15efafbb05f92a592b" + [[package]] name = "is_ci" version = "1.1.1" @@ -1563,6 +1667,65 @@ dependencies = [ "winapi", ] +[[package]] +name = "reqwest" +version = "0.11.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "431949c384f4e2ae07605ccaa56d1d9d2ecdb5cadd4f9577ccfab29f2e5149fc" +dependencies = [ + "base64", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "hyper", + "hyper-tls", + "ipnet", + "js-sys", + "log", + "mime", + "native-tls", + "once_cell", + "percent-encoding", + "pin-project-lite", + "serde", + "serde_json", + "serde_urlencoded", + "tokio", + "tokio-native-tls", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "winreg", +] + +[[package]] +name = "rmp" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44519172358fd6d58656c86ab8e7fbc9e1490c3e8f14d35ed78ca0dd07403c9f" +dependencies = [ + "byteorder", + "num-traits", + "paste", +] + +[[package]] +name = "rmp-serde" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25786b0d276110195fa3d6f3f31299900cf71dfbd6c28450f3f58a0e7f7a347e" +dependencies = [ + "byteorder", + "rmp", + "serde", +] + [[package]] name = "rust_decimal" version = "1.25.0" @@ -1829,6 +1992,18 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + [[package]] name = "sha2" version = "0.10.2" @@ -2368,6 +2543,20 @@ dependencies = [ "tokio", ] +[[package]] +name = "tokio-util" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc463cd8deddc3770d20f9852143d50bf6094e640b485cb2e189a2099085ff45" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", + "tracing", +] + [[package]] name = "toml" version = "0.5.9" @@ -2377,6 +2566,12 @@ dependencies = [ "serde", ] +[[package]] +name = "tower-service" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" + [[package]] name = "tracing" version = "0.1.36" @@ -2440,6 +2635,12 @@ dependencies = [ "tracing-log", ] +[[package]] +name = "try-lock" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" + [[package]] name = "typenum" version = "1.15.0" @@ -2570,6 +2771,16 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "want" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" +dependencies = [ + "log", + "try-lock", +] + [[package]] name = "wasi" version = "0.10.0+wasi-snapshot-preview1" @@ -2740,3 +2951,12 @@ name = "windows_x86_64_msvc" version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" + +[[package]] +name = "winreg" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d" +dependencies = [ + "winapi", +] diff --git a/Cargo.toml b/Cargo.toml index e1c909a..4ecb13a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,6 +14,8 @@ miette = { version = "5.2.0", features = ["fancy"] } mime = "0.3.16" mime_guess = "2.0.4" paris = { version = "1.5.13", features = ["macros"] } +reqwest = "0.11.12" +rmp-serde = "1.1.0" sea-orm = { version = "0.9.1", features = ["sqlx-sqlite", "runtime-tokio-native-tls", "macros"] } sea-orm-migration = "^0.9.0" sea-query = "0.26.2" diff --git a/src/backend/fetching.rs b/src/backend/fetching.rs index 512df51..7c19fa7 100644 --- a/src/backend/fetching.rs +++ b/src/backend/fetching.rs @@ -5,14 +5,17 @@ use std::{ path::Path, }; +use crate::backend::utils::get_auth_source; + use super::{ config::{Config, Source, SourceKind}, - model::library, + model::{library, library::Column}, }; use adler::Adler32; use lofty::{read_from_path, Accessor, AudioFile}; use miette::{miette, IntoDiagnostic, Result}; use paris::{success, warn}; +use reqwest::Client; use sea_orm::{ColumnTrait, DatabaseConnection, EntityTrait, QueryFilter, QuerySelect, Set}; use symphonia::{ core::{ @@ -116,7 +119,7 @@ pub async fn index_source(source: Source, mode: IndexMode, db: &DatabaseConnecti library::Entity::insert(song) .on_conflict( - sea_query::OnConflict::column(library::Column::Filename) + sea_query::OnConflict::column(Column::Hash) .do_nothing() .to_owned(), ) @@ -126,7 +129,54 @@ pub async fn index_source(source: Source, mode: IndexMode, db: &DatabaseConnecti } } SourceKind::Remote { address } => { - unimplemented!(); + let (username, password) = get_auth_source(source.id)?; + + let client = Client::new(); + + let index = client + .get(format!("{address}/")) + .basic_auth(username, Some(password)) + .send() + .await + .into_diagnostic()? + .bytes() + .await + .into_diagnostic()?; + + // Deserialize messagepack into a library model + let parsed: Vec = rmp_serde::from_slice(&index).into_diagnostic()?; + + // Use all fields except for id and source_id + let songs: Vec<_> = parsed + .into_iter() + .map(|v| { + return library::ActiveModel { + path: Set(v.path), + filename: Set(v.filename), + source_id: Set(source.id.into()), // Use local source id, not remote + hash: Set(v.hash), + artist: Set(v.artist), + album_artist: Set(v.album_artist), + name: Set(v.name), + album: Set(v.album), + genres: Set(v.genres), + track: Set(v.track), + year: Set(v.year), + duration: Set(v.duration), + ..Default::default() + }; + }) + .collect(); + + library::Entity::insert_many(songs) + .on_conflict( + sea_query::OnConflict::column(Column::Hash) + .do_nothing() + .to_owned(), + ) + .exec(db) + .await + .into_diagnostic()?; } } diff --git a/src/backend/migrator/m20220803_000001_create_library.rs b/src/backend/migrator/m20220803_000001_create_library.rs index 28f202f..54a04ac 100644 --- a/src/backend/migrator/m20220803_000001_create_library.rs +++ b/src/backend/migrator/m20220803_000001_create_library.rs @@ -19,12 +19,7 @@ impl MigrationTrait for Migration { .primary_key(), ) .col(ColumnDef::new(Song::Path).string().not_null()) - .col( - ColumnDef::new(Song::Filename) - .string() - .not_null() - .unique_key(), - ) + .col(ColumnDef::new(Song::Filename).string().not_null()) .col(ColumnDef::new(Song::SourceId).integer().not_null()) .col(ColumnDef::new(Song::Hash).integer().not_null().unique_key()) .col(ColumnDef::new(Song::Artist).string()) diff --git a/src/backend/mod.rs b/src/backend/mod.rs index 3d74525..b210e25 100644 --- a/src/backend/mod.rs +++ b/src/backend/mod.rs @@ -12,16 +12,24 @@ use migrator::Migrator; use paris::success; use sea_orm_migration::prelude::*; -use self::{config::Config, utils::config_dir}; +use self::{ + config::Config, + utils::{cache_dir, config_dir}, +}; /// Create the necessary files on first run pub fn create_app_data() -> Result<()> { - let path = config_dir().ok_or(miette!("Configuration directory does not exist"))?; + let config_path = config_dir().ok_or(miette!("Configuration directory does not exist"))?; // Create Eleanor's config directory - create_dir_all(&path).into_diagnostic()?; + create_dir_all(&config_path).into_diagnostic()?; - File::create(&path.join("eleanor.db")).into_diagnostic()?; + let cache_path = cache_dir().ok_or(miette!("Configuration directory does not exist"))?; + + // Create Eleanor's cache directory + create_dir_all(&cache_path).into_diagnostic()?; + + File::create(&config_path.join("eleanor.db")).into_diagnostic()?; Config::write_config(&Default::default())?; success!("Created configuration file"); diff --git a/src/backend/utils.rs b/src/backend/utils.rs index f73d205..d3fabfa 100644 --- a/src/backend/utils.rs +++ b/src/backend/utils.rs @@ -1,11 +1,10 @@ -use miette::{miette, Result}; -use std::path::PathBuf; +use miette::{miette, IntoDiagnostic, Result}; +use std::{fs::File, io::Write, path::PathBuf}; pub fn config_dir() -> Option { dirs::config_dir().map(|v| v.join("eleanor")) } -#[allow(dead_code)] pub fn cache_dir() -> Option { dirs::cache_dir().map(|v| v.join("eleanor")) } @@ -16,3 +15,29 @@ pub fn is_first_run() -> Result { Ok(!path.exists()) } + +/// Stores credentials for a remote source +pub fn store_auth_source(username: String, password: String, source: u8) -> Result<()> { + let contents = rmp_serde::to_vec(&(username, password)).into_diagnostic()?; + + let path = cache_dir() + .ok_or(miette!("Cache directory does not exist"))? + .join(format!("{source}.auth")); + + File::create(path) + .and_then(|mut v| v.write_all(&contents)) + .into_diagnostic() +} + +/// Returns the stored credentials for a remote source +pub fn get_auth_source(source: u8) -> Result<(String, String)> { + let path = cache_dir() + .ok_or(miette!("Cache directory does not exist"))? + .join(format!("{source}.auth")); + + let file = std::fs::read(path).into_diagnostic()?; + + let contents = rmp_serde::from_slice(&file).into_diagnostic()?; + + Ok(contents) +}