use super::structs::MediaFile;
use crate::app::settings::get_db_connection;
use crate::entity::{
library::{self, Model as LibraryModel},
media::{self, ActiveModel as MediaAModel, Model as MediaModel},
};
use crate::error::error::{CustomError, QueryError};
use crate::media_checker::albums::validate_albums;
use log::info;
use sea_orm::entity::*;
use sea_orm::{
ActiveModelTrait, ActiveValue, DatabaseConnection, EntityTrait, Iterable, ModelTrait,
QueryFilter, Set,
};
use sea_query;
use std::str::FromStr;
use std::sync::mpsc::Sender;
use std::time::{Instant, UNIX_EPOCH};
use walkdir::WalkDir;
pub struct MusicManager {
sender: Option<Sender<String>>,
}
impl MusicManager {
pub fn new(tx: Option<Sender<String>>) -> Self {
Self { sender: tx }
}
#[deprecated(note = "Use remove_library_by_id instead")]
pub async fn remove_library(library_id: &str) -> Result<(), CustomError> {
info!("MusicManager::remove_library");
let db = get_db_connection().await?;
let library = library::Entity::find_by_id(i32::from_str(library_id).unwrap())
.one(&db)
.await?;
let mut ret_val: Result<(), CustomError> = Ok(());
match library {
Some(x) => {
x.delete(&db).await?;
}
None => {
ret_val = Err(CustomError::QueryErr(QueryError {
query: format!("Failed to find library with id {}", library_id),
}));
}
}
validate_albums().await?;
ret_val
}
pub async fn remove_library_by_id(library_id: i32) -> Result<(), CustomError> {
info!("MusicManager::remove_library");
let db = get_db_connection().await?;
let library = library::Entity::find_by_id(library_id).one(&db).await?;
let mut ret_val: Result<(), CustomError> = Ok(());
match library {
Some(x) => {
x.delete(&db).await?;
}
None => {
ret_val = Err(CustomError::QueryErr(QueryError {
query: format!("Failed to find library with id {}", library_id),
}));
}
}
validate_albums().await?;
ret_val
}
pub async fn get_media_by_library(library_id: i32) -> Result<Vec<MediaModel>, CustomError> {
info!("MusicManager::get_media_by_library");
let db = get_db_connection().await?;
let libraries = media::Entity::find()
.filter(media::Column::LibraryId.eq(library_id))
.all(&db)
.await
.expect("get_media_by_library::Failed to retrieve all libraries");
Ok(libraries)
}
async fn cleanup_media(
new_set: &Vec<MediaFile>,
library_id: i32,
db: &DatabaseConnection,
) -> Result<(), CustomError> {
let media: Vec<MediaModel> = MusicManager::get_media_by_library(library_id).await?;
let res_paths: Vec<String> = new_set.iter().map(|x| x.path.clone()).collect();
let removed_media: Vec<MediaModel> = media
.into_iter()
.filter(|x| !res_paths.contains(&x.path))
.collect();
for r in removed_media.iter() {
media::Entity::delete_by_id(r.id).exec(db).await?;
}
validate_albums().await?;
Ok(())
}
pub async fn execute(&self) -> Result<(), CustomError> {
info!("MusicManager.execute");
let _before = Instant::now();
let db = get_db_connection().await?;
let libraries = MusicManager::get_libraries().await?;
info!(
"MusicManager.execute: Libraries to scan: {}",
libraries.len()
);
for l in libraries.iter() {
self.send(format!(
"MusicManager.execute: Scanning library: {}",
l.name
));
let res = MusicManager::walk_dir(&l.path);
MusicManager::cleanup_media(&res, l.id, &db).await?;
let mut media_inserts = vec![];
let mut i = 0;
let tot_files = res.len();
for r in res.iter() {
i += 1;
if i % 100 == 0 || i == tot_files {
let message = format!("Scanning file - {}/{}", i, tot_files);
info!("{}", message);
self.send(message);
}
let media = media::ActiveModel {
id: ActiveValue::not_set(),
path: ActiveValue::set(r.path.to_owned()),
mtime: ActiveValue::set(
r.mtime
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs()
.to_string(),
),
library_id: ActiveValue::set(l.id),
file_type: ActiveValue::set(r.file_type.to_string()),
};
media_inserts.push(media);
}
for chunk in media_inserts.chunks(
(u16::MAX / (<library::Entity as EntityTrait>::Column::iter().count() as u16 * 5))
as usize,
) {
MusicManager::insert_media(chunk.to_vec(), &db).await?;
}
let _ = MusicManager::calculate_library_files(l.id.clone(), res.len(), &db).await;
self.send(format!(
"MusicManager.execute: Completed library: {}",
l.name
));
}
info!(
"MusicManager.execute: Timing Complete in {:.2?}",
_before.elapsed()
);
self.send(format!("Complete MusicManager Scan"));
Ok(())
}
async fn calculate_library_files(
library_id: i32,
tot_files: usize,
db: &DatabaseConnection,
) -> Result<(), CustomError> {
let updated_library = library::Entity::find_by_id(library_id).one(db).await?;
let mut updated_library: library::ActiveModel = match updated_library {
Some(x) => x.into(),
None => {
return Err(CustomError::QueryErr(QueryError {
query: format!("Failed to find library with id {}", library_id),
}))
}
};
updated_library.files = Set(tot_files.to_string());
updated_library.update(db).await?;
Ok(())
}
pub async fn insert_media(
media: Vec<MediaAModel>,
db: &DatabaseConnection,
) -> Result<String, CustomError> {
let _ = media::Entity::insert_many(media)
.on_empty_do_nothing()
.on_conflict(
sea_query::OnConflict::column(media::Column::Path)
.update_column(media::Column::Mtime)
.to_owned(),
)
.exec(db)
.await?;
Ok("Ok".to_string())
}
fn send(&self, message: String) {
match self.sender.as_ref() {
Some(x) => {
let _ = x.send(message);
}
None => {}
};
}
fn walk_dir(root_path: &String) -> Vec<MediaFile> {
let files = WalkDir::new(root_path)
.into_iter()
.filter_map(|e| e.ok())
.filter(|file| file.path().is_file());
let mut media_files: Vec<MediaFile> = Vec::new();
for f in files {
let media_file = MediaFile::build(&f);
match media_file {
Err(_) => continue,
Ok(x) => media_files.push(x),
}
}
media_files
}
pub async fn get_libraries() -> Result<Vec<LibraryModel>, CustomError> {
let db = get_db_connection().await?;
let libraries = library::Entity::find().all(&db).await?;
Ok(libraries)
}
pub async fn get_media() -> Result<Vec<MediaModel>, CustomError> {
let db = get_db_connection().await?;
let media = media::Entity::find().all(&db).await?;
Ok(media)
}
pub async fn get_filtered_media() -> Result<Vec<MediaModel>, CustomError> {
let db = get_db_connection().await?;
let media = media::Entity::find()
.filter(
media::Column::FileType
.contains("MPEG")
.or(media::Column::FileType.contains("XFLAC")),
)
.all(&db)
.await
.expect("get_filtered_media::Failed to find relevant media");
Ok(media)
}
pub async fn insert_library(name: &str, path: &str) -> Result<(), CustomError> {
let db = get_db_connection().await?;
let library = library::ActiveModel {
id: ActiveValue::not_set(),
name: ActiveValue::set(name.to_owned()),
path: ActiveValue::set(path.to_owned()),
files: ActiveValue::set(0.to_string()),
};
let _ = library::Entity::insert(library)
.on_conflict(
sea_query::OnConflict::column(library::Column::Name)
.update_column(library::Column::Path)
.to_owned(),
)
.exec(&db)
.await;
Ok(())
}
}