feat: basic music backend
This commit is contained in:
120
src/library.rs
Normal file
120
src/library.rs
Normal file
@@ -0,0 +1,120 @@
|
||||
use rayon::prelude::*;
|
||||
use std::{collections::HashMap, fs, path::PathBuf, time::Instant};
|
||||
|
||||
use r2d2::{Pool, PooledConnection};
|
||||
use r2d2_sqlite::SqliteConnectionManager;
|
||||
use walkdir::{DirEntry, WalkDir};
|
||||
|
||||
use crate::{
|
||||
checksum::generate_hash,
|
||||
database::tracks::{get_tracks, insert_tracks},
|
||||
music::metadata::{extract_track_data, TrackMetadata},
|
||||
proto::{self, library_server::Library},
|
||||
state::GrooveState,
|
||||
};
|
||||
|
||||
pub struct LibraryService {
|
||||
#[allow(dead_code)]
|
||||
state: GrooveState,
|
||||
pool: Pool<SqliteConnectionManager>,
|
||||
}
|
||||
|
||||
impl LibraryService {
|
||||
pub fn new(state: GrooveState, pool: Pool<SqliteConnectionManager>) -> Self {
|
||||
Self { state, pool }
|
||||
}
|
||||
}
|
||||
|
||||
#[tonic::async_trait]
|
||||
impl Library for LibraryService {
|
||||
async fn list_tracks(
|
||||
&self,
|
||||
_request: tonic::Request<()>,
|
||||
) -> Result<tonic::Response<proto::TrackList>, tonic::Status> {
|
||||
let Ok(db) = self.pool.get() else {
|
||||
return Err(tonic::Status::internal(""));
|
||||
};
|
||||
|
||||
let Ok(tracks) = get_tracks(&db) else {
|
||||
return Err(tonic::Status::internal(""));
|
||||
};
|
||||
|
||||
let response = proto::TrackList {
|
||||
tracks: tracks
|
||||
.iter()
|
||||
.map(|t| proto::Track {
|
||||
hash: t.hash.clone(),
|
||||
name: t.name.clone(),
|
||||
artist_name: t.artist_name.clone(),
|
||||
artist_id: t.artist_id,
|
||||
})
|
||||
.collect::<Vec<proto::Track>>(),
|
||||
};
|
||||
|
||||
Ok(tonic::Response::new(response))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn index_path(
|
||||
path: PathBuf,
|
||||
db: PooledConnection<SqliteConnectionManager>,
|
||||
path_id: u64,
|
||||
) -> Result<(), rusqlite::Error> {
|
||||
let home = home::home_dir().unwrap();
|
||||
|
||||
let correct_path = path.to_str().unwrap().replace("~", home.to_str().unwrap());
|
||||
let path_offset = correct_path.len() + 1;
|
||||
|
||||
let now = Instant::now();
|
||||
|
||||
let entries: Vec<DirEntry> = WalkDir::new(correct_path)
|
||||
.into_iter()
|
||||
.filter_map(Result::ok)
|
||||
.collect();
|
||||
|
||||
let hashmaps: Vec<HashMap<String, TrackMetadata>> = entries
|
||||
.par_iter()
|
||||
.fold(
|
||||
|| HashMap::new(),
|
||||
|mut acc: HashMap<String, TrackMetadata>, entry| {
|
||||
if entry.file_type().is_file()
|
||||
&& entry.path().extension().is_some_and(|ext| ext == "mp3")
|
||||
{
|
||||
let file_path = entry.path();
|
||||
let content = fs::read(file_path).unwrap();
|
||||
|
||||
let hash = generate_hash(&content);
|
||||
|
||||
let relative_path =
|
||||
file_path.to_str().unwrap().to_string()[path_offset..].to_string();
|
||||
|
||||
if let Some(metadata) = extract_track_data(content, relative_path) {
|
||||
acc.insert(hash, metadata);
|
||||
}
|
||||
}
|
||||
|
||||
acc
|
||||
},
|
||||
)
|
||||
.collect();
|
||||
|
||||
let mut tracks = HashMap::<String, TrackMetadata>::new();
|
||||
|
||||
for tracks_chunk in hashmaps {
|
||||
tracks.extend(tracks_chunk);
|
||||
}
|
||||
|
||||
let elapsed = now.elapsed();
|
||||
|
||||
println!("indexing took {:.2?}", elapsed);
|
||||
|
||||
let now = Instant::now();
|
||||
|
||||
insert_tracks(db, tracks, path_id)?;
|
||||
|
||||
let elapsed = now.elapsed();
|
||||
|
||||
println!("inserting took {:.2?}", elapsed);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
Reference in New Issue
Block a user