musicbrainz-light 0.1.3

A lightweight MusicBrainz database importer and processor
Documentation
use crate::error::MbLightResult;
use crate::progress::get_progress_bar;
use crate::settings::MbLightSettingsExt;
use crate::{MbLight, MbLightError};
use futures_util::future::join_all;
use indicatif::MultiProgress;
use std::fs;
use std::path::PathBuf;
use tempfile::env::temp_dir;
use tracing::error;

impl<S: MbLightSettingsExt> MbLight<S> {
    pub async fn download_musicbrainz_sql(&self) -> MbLightResult<PathBuf> {
        let owner = "metabrainz";
        let repo = "musicbrainz-server";
        let path = "admin/sql";
        let local_dir = temp_dir();
        let local_dir = local_dir.join("musicbrainz-sql");

        let mp = MultiProgress::new();
        self.download_dir(
            owner.into(),
            repo.into(),
            path.into(),
            local_dir.clone(),
            mp.clone(),
        )
        .await?;

        mp.clear()?;
        Ok(local_dir)
    }

    pub async fn download_schema_update(&self, target_sequence: i32) -> MbLightResult<PathBuf> {
        let owner = "metabrainz";
        let repo = "musicbrainz-server";
        let path = format!("admin/sql/update/schema-change/{}.all.sql", target_sequence);
        let local_dir = temp_dir();
        let local_dir = local_dir.join("musicbrainz-sql");

        let mp = MultiProgress::new();
        let path_clone = PathBuf::from(&path);
        self.download_dir(
            owner.into(),
            repo.into(),
            path,
            local_dir.clone(),
            mp.clone(),
        )
        .await?;

        mp.clear()?;
        Ok(path_clone)
    }

    async fn download_dir(
        &self,
        owner: String,
        repo: String,
        path: String,
        local_path: PathBuf,
        mp: MultiProgress,
    ) -> MbLightResult<()> {
        fs::create_dir_all(&local_path)?;

        let contents = self
            .github_client
            .repos(&owner, &repo)
            .get_content()
            .path(path)
            .send()
            .await?
            .items;

        let pb = mp.add(get_progress_bar(contents.len() as u64)?);
        pb.set_message(format!(
            "Dir {}",
            local_path.file_name().unwrap_or_default().to_string_lossy()
        ));

        let mut files = vec![];
        for item in contents {
            let item_path = local_path.join(&item.name);
            let mp = mp.clone();
            let pb = pb.clone();

            match item.r#type.as_str() {
                "dir" => {
                    let owner = owner.clone();
                    let repo = repo.clone();
                    let path = item.path.clone();
                    let local_path = item_path.clone();

                    Box::pin(self.download_dir(owner, repo, path, local_path, mp)).await?;
                    pb.inc(1);
                }
                "file" => {
                    if let Some(download_url) = item.download_url {
                        let fut = async move {
                            let file_path = item_path.clone();
                            let bytes = self
                                .http_client
                                .get(&download_url)
                                .send()
                                .await?
                                .bytes()
                                .await?;
                            tokio::fs::write(&file_path, &bytes).await?;

                            pb.inc(1);
                            Ok::<(), MbLightError>(())
                        };

                        files.push(fut);
                    } else {
                        pb.inc(1);
                    }
                }
                _ => {}
            }
        }

        let results = join_all(files).await;
        for r in results {
            if let Err(e) = r {
                error!("Error: {}", e);
            }
        }
        pb.finish_with_message("Download complete");

        Ok(())
    }
}