238 lines
6.7 KiB
Rust
238 lines
6.7 KiB
Rust
use deadpool_sqlite::Pool;
|
|
use std::{collections::HashMap, path::PathBuf, sync::Arc, time::Instant};
|
|
use tokio::{sync::Mutex, task::JoinSet};
|
|
|
|
use tonic::{Request, Response, Status};
|
|
use walkdir::{DirEntry, WalkDir};
|
|
|
|
use crate::{
|
|
checksum::generate_hash,
|
|
database::tracks::{
|
|
add_track_to_playlist, create_playlist, delete_playlist, get_playlist, get_playlists,
|
|
get_tracks, insert_tracks, remove_track_from_playlist, swap_playlist_tracks,
|
|
},
|
|
music::metadata::{extract_track_data, TrackMetadata},
|
|
proto::library::{
|
|
library_server::Library, AddTrackToPlaylistRequest, CreatePlaylistRequest,
|
|
CreatePlaylistResponse, DeletePlaylistRequest, ListPlaylistsResponse,
|
|
RemoveTrackFromPlaylistRequest, SwapTracksRequest, Track, TrackList,
|
|
},
|
|
state::GrooveState,
|
|
};
|
|
|
|
pub struct LibraryService {
|
|
#[allow(dead_code)]
|
|
state: GrooveState,
|
|
pool: Pool,
|
|
}
|
|
|
|
impl LibraryService {
|
|
pub fn new(state: GrooveState, pool: Pool) -> Self {
|
|
Self { state, pool }
|
|
}
|
|
}
|
|
|
|
#[tonic::async_trait]
|
|
impl Library for LibraryService {
|
|
async fn list_tracks(&self, _request: Request<()>) -> Result<Response<TrackList>, Status> {
|
|
let Ok(tracks) = get_tracks(&self.pool).await else {
|
|
return Err(Status::internal(""));
|
|
};
|
|
|
|
let response = TrackList {
|
|
tracks: tracks
|
|
.into_iter()
|
|
.map(|t| Track {
|
|
hash: t.hash.clone(),
|
|
name: t.name.clone(),
|
|
artist_name: t.artist_name.clone(),
|
|
artist_id: t.artist_id,
|
|
duration: t.duration,
|
|
})
|
|
.collect::<Vec<Track>>(),
|
|
};
|
|
|
|
Ok(Response::new(response))
|
|
}
|
|
|
|
async fn list_playlists(
|
|
&self,
|
|
_request: Request<()>,
|
|
) -> Result<Response<ListPlaylistsResponse>, Status> {
|
|
let Ok(playlists) = get_playlists(&self.pool).await else {
|
|
return Err(Status::internal(""));
|
|
};
|
|
|
|
let response = ListPlaylistsResponse { playlists };
|
|
|
|
Ok(Response::new(response))
|
|
}
|
|
|
|
async fn create_playlist(
|
|
&self,
|
|
request: Request<CreatePlaylistRequest>,
|
|
) -> Result<Response<CreatePlaylistResponse>, Status> {
|
|
let input = request.get_ref();
|
|
|
|
let Ok(playlist) = create_playlist(&self.pool, &input.name).await else {
|
|
return Err(Status::internal(""));
|
|
};
|
|
|
|
let response = CreatePlaylistResponse {
|
|
playlist: Some(playlist),
|
|
};
|
|
|
|
Ok(Response::new(response))
|
|
}
|
|
|
|
async fn delete_playlist(
|
|
&self,
|
|
request: Request<DeletePlaylistRequest>,
|
|
) -> Result<Response<()>, Status> {
|
|
let input = request.get_ref();
|
|
|
|
let Ok(success) = delete_playlist(&self.pool, input.id).await else {
|
|
return Err(Status::internal(""));
|
|
};
|
|
|
|
if !success {
|
|
return Err(Status::internal(""));
|
|
}
|
|
|
|
Ok(Response::new(()))
|
|
}
|
|
|
|
async fn add_track_to_playlist(
|
|
&self,
|
|
request: Request<AddTrackToPlaylistRequest>,
|
|
) -> Result<Response<TrackList>, Status> {
|
|
let input = request.get_ref();
|
|
|
|
let Ok(success) =
|
|
add_track_to_playlist(&self.pool, input.playlist_id, &input.track_hash).await
|
|
else {
|
|
return Err(Status::internal(""));
|
|
};
|
|
|
|
if !success {
|
|
return Err(Status::internal(""));
|
|
}
|
|
|
|
let Ok(playlist) = get_playlist(&self.pool, input.playlist_id).await else {
|
|
return Err(Status::internal(""));
|
|
};
|
|
|
|
let response = TrackList {
|
|
tracks: playlist.tracks,
|
|
};
|
|
|
|
Ok(Response::new(response))
|
|
}
|
|
|
|
async fn remove_track_from_playlist(
|
|
&self,
|
|
request: Request<RemoveTrackFromPlaylistRequest>,
|
|
) -> Result<Response<TrackList>, Status> {
|
|
let input = request.get_ref();
|
|
|
|
let Ok(success) =
|
|
remove_track_from_playlist(&self.pool, input.playlist_id, input.track_rank).await
|
|
else {
|
|
return Err(Status::internal(""));
|
|
};
|
|
|
|
if !success {
|
|
return Err(Status::internal(""));
|
|
}
|
|
|
|
let Ok(playlist) = get_playlist(&self.pool, input.playlist_id).await else {
|
|
return Err(Status::internal(""));
|
|
};
|
|
|
|
let response = TrackList {
|
|
tracks: playlist.tracks,
|
|
};
|
|
|
|
Ok(Response::new(response))
|
|
}
|
|
|
|
async fn swap_tracks(
|
|
&self,
|
|
request: Request<SwapTracksRequest>,
|
|
) -> Result<Response<TrackList>, Status> {
|
|
let input = request.get_ref();
|
|
|
|
let Ok(success) =
|
|
swap_playlist_tracks(&self.pool, input.playlist_id, input.a_rank, input.b_rank).await
|
|
else {
|
|
return Err(Status::internal(""));
|
|
};
|
|
|
|
if !success {
|
|
return Err(Status::internal(""));
|
|
}
|
|
|
|
let Ok(playlist) = get_playlist(&self.pool, input.playlist_id).await else {
|
|
return Err(Status::internal(""));
|
|
};
|
|
|
|
let response = TrackList {
|
|
tracks: playlist.tracks,
|
|
};
|
|
|
|
Ok(Response::new(response))
|
|
}
|
|
}
|
|
|
|
pub async fn index_path(path: PathBuf, db: &Pool, 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 tracks: Arc<Mutex<HashMap<String, TrackMetadata>>> = Arc::new(Mutex::new(HashMap::new()));
|
|
let mut set = JoinSet::new();
|
|
|
|
for entry in entries.into_iter() {
|
|
if entry.file_type().is_file() && entry.path().extension().is_some_and(|ext| ext == "mp3") {
|
|
let tracks = tracks.clone();
|
|
set.spawn(async move {
|
|
let file_path = entry.path();
|
|
if let Ok(content) = std::fs::read(file_path) {
|
|
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) {
|
|
tracks.lock().await.insert(hash, metadata);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
set.join_all().await;
|
|
|
|
let elapsed = now.elapsed();
|
|
|
|
println!("indexing took {:.2?}", elapsed);
|
|
|
|
let now = Instant::now();
|
|
|
|
insert_tracks(db, Arc::try_unwrap(tracks).unwrap().into_inner(), path_id).await?;
|
|
|
|
let elapsed = now.elapsed();
|
|
|
|
println!("inserting took {:.2?}", elapsed);
|
|
|
|
Ok(())
|
|
}
|