librmo 0.4.4

A library to manage media files and play them
Documentation
use crate::app::settings::get_db_connection;
use crate::entity::{
    album::{self, ActiveModel as AlbumAModel},
    album_group::{self, ActiveModel as AlbumGroupAModel},
    media::{self},
    music::{self, ActiveModel as MusicAModel, Entity as MusicEntity, Model as MusicModel},
};
use crate::error::error::CustomError;
use crate::media_checker::albums::check_albums_paginate;
use crate::media_manager::MusicManager;
use crate::media_validator::reports::delete_reports;
#[allow(unused_imports)]
use log::info;
use sea_orm::{entity::*, DatabaseConnection, PaginatorTrait, QueryOrder};
use sea_orm::{ActiveValue, EntityTrait, Iterable, QueryFilter, Set};

use sea_query;
use std::sync::mpsc::Sender;
use std::time::Instant;

/// MusicChecker is the link between valid media files and valid music files
///
/// Create via ::new
///
/// Run via .execute
pub struct MusicChecker {
    sender: Option<Sender<String>>,
}

impl MusicChecker {
    /// Creates a new [`MusicChecker`].
    ///
    /// Provide an optional message channel for learning the progress through scanning your library
    pub fn new(tx: Option<Sender<String>>) -> Self {
        Self { sender: tx }
    }

    /// Returns all the music records from your library
    pub async fn get_music() -> Result<Vec<MusicModel>, CustomError> {
        info!("MusicChecker::get_music");
        let db = get_db_connection().await?;
        let music = MusicEntity::find().all(&db).await?;
        Ok(music)
    }

    fn send(&self, message: String) {
        if self.sender.is_some() {
            let _ = self.sender.as_ref().unwrap().send(message);
        }
    }

    /// Scans all the media files and generates music records and further album/tracks
    ///
    /// Sends out a message every 100 files scanned or on the last file
    pub async fn execute(&self) -> Result<(), CustomError> {
        info!("MusicChecker.execute");

        let _before = Instant::now();
        let db = get_db_connection().await?;

        let media = MusicManager::get_filtered_media().await?;
        let mut inserted_music: Vec<music::ActiveModel> = Vec::new();
        let mut updated_music: Vec<music::ActiveModel> = Vec::new();
        let mut i = 0;
        let tot_media = media.len();
        info!("MusicChecker.execute: media to scan: {}", tot_media);
        for m in media {
            i = i + 1;
            if i % 100 == 0 || i == tot_media {
                let message = format!("Scanning media - {}/{}", i, tot_media);
                info!("{}", message);
                self.send(message);
            }
            let music = music::Entity::find()
                .filter(music::Column::MediaId.eq(m.id.to_string()))
                .one(&db)
                .await?;
            match music {
                Some(x) => updated_music.push(MusicChecker::update_music(x, m)),
                None => inserted_music.push(MusicChecker::new_music(m)),
            };
        }

        for chunk in inserted_music.chunks(
            (u16::MAX / (<music::Entity as EntityTrait>::Column::iter().count() as u16 * 10))
                as usize,
        ) {
            let _ = MusicChecker::insert_music(chunk.to_vec(), &db).await?;
        }

        for chunk in updated_music.chunks(
            (u16::MAX / (<music::Entity as EntityTrait>::Column::iter().count() as u16 * 10))
                as usize,
        ) {
            let _ = MusicChecker::insert_music(chunk.to_vec(), &db).await?;
        }
        info!("MusicChecker.execute: Timing Complete in {:.2?}", _before.elapsed());
        self.check_albums().await?;
        Ok(())
    }

    #[allow(dead_code)]
    async fn check_albums(&self) -> Result<(), CustomError> {
        info!("MusicChecker.check_albums");

        let _before = Instant::now();
        let db = get_db_connection().await?;

        let mut music_pages = music::Entity::find()
            .order_by_asc(music::Column::Id)
            .paginate(&db, 50);
        let mut i = 0;
        delete_reports().await?;
        while let Some(music) = music_pages.fetch_and_next().await? {
            i = i + music.len();
            let message = format!("Scanning Albums - {}", i);
            info!("{}", message);
            self.send(message);

            check_albums_paginate(music, &db).await?;
        }
        info!("MusicChecker.check_albums: Timing Complete in {:.2?}", _before.elapsed());
        Ok(())
    }

    pub async fn insert_album_group(
        album_group: Vec<AlbumGroupAModel>,
        db: &DatabaseConnection,
    ) -> Result<String, CustomError> {
        let _ = album_group::Entity::insert_many(album_group)
            .on_empty_do_nothing()
            .on_conflict(
                sea_query::OnConflict::column(album_group::Column::MbReleaseGroupId)
                    .update_column(album_group::Column::ReleaseYear)
                    .to_owned(),
            )
            .exec(db)
            .await?;

        Ok("Ok".to_string())
    }

    pub async fn insert_album(
        album: Vec<AlbumAModel>,
        db: &DatabaseConnection,
    ) -> Result<String, CustomError> {
        let _ = album::Entity::insert_many(album)
            .on_empty_do_nothing()
            .on_conflict(
                sea_query::OnConflict::column(album::Column::MbReleaseId)
                    .do_nothing()
                    .to_owned(),
            )
            .exec(db)
            .await?;

        Ok("Ok".to_string())
    }

    pub async fn insert_music(
        music: Vec<MusicAModel>,
        db: &DatabaseConnection,
    ) -> Result<String, CustomError> {
        let _ = music::Entity::insert_many(music)
            .on_empty_do_nothing()
            .on_conflict(
                sea_query::OnConflict::column(music::Column::MediaId)
                    .update_column(music::Column::Mtime)
                    .to_owned(),
            )
            .exec(db)
            .await?;
        Ok("Ok".to_string())
    }

    fn update_music(music: music::Model, media: media::Model) -> MusicAModel {
        let media_mtime = media.mtime.to_owned();
        let active_media: media::ActiveModel = media.into();
        let mut active_music: music::ActiveModel = music.into();
        if active_music.mtime != active_media.mtime {
            active_music.mtime = Set(media_mtime);
        }
        active_music
    }

    fn new_music(m: media::Model) -> MusicAModel {
        music::ActiveModel {
            id: ActiveValue::not_set(),
            media_id: ActiveValue::set(m.id),
            mtime: ActiveValue::set(m.mtime),
            processed_mtime: ActiveValue::set(String::from("0")),
        }
    }
}