lr2-oxytabler 0.10.2

Table manager for Lunatic Rave 2
Documentation
use crate::{output::OutputFolder, output::OutputFolderKey, time::UnixEpochTs};
use anyhow::{Context as _, Result, ensure};
use std::path::Path;

pub mod changelog;
pub mod db;
pub mod fetch;
pub mod lr2folder;
pub mod mass_edit;
pub mod migrations;
pub mod output;
pub mod table;
pub mod time;
pub mod url;

pub fn build_reqwest() -> Result<reqwest::Client> {
    const APP_USER_AGENT: &str = concat!(env!("CARGO_PKG_NAME"), "/", env!("CARGO_PKG_VERSION"));
    reqwest::ClientBuilder::new()
        // jounin.jp, where new overjoy and insane 2 tables are hosted, blocks requests with no
        // user-agent.
        .user_agent(APP_USER_AGENT)
        .build()
        .context("failed to build reqwest client")
}

pub fn open_db(
    path: &Path,
    outputs: &[OutputFolder],
) -> Result<(rusqlite::Connection, Vec<table::Table>)> {
    fn validate_good_outputs(outputs: &[OutputFolder]) -> Result<()> {
        if let Some(p) = OutputFolder::is_any_parent_of_another(outputs) {
            anyhow::bail!(
                "intersecting output paths found: '{}:{}' and '{}:{}'",
                p.0.0,
                p.0.1.display(),
                p.1.0,
                p.1.1.display()
            );
        }

        for OutputFolder(_key, path) in outputs {
            lr2folder::validate_good_playlists_folder(path)
                .context("bad playlists folder selected")?;
        }

        Ok(())
    }

    fn validate_outputs_for_tables(
        tables: &[table::Table],
        outputs: &[OutputFolder],
    ) -> Result<()> {
        for table::Table(table, add_data) in tables {
            ensure!(
                outputs.iter().any(|o| o.0 == add_data.output),
                "missing --output '{}' required for table '{}'",
                add_data.output.0,
                table.name,
            );
        }
        Ok(())
    }

    validate_good_outputs(outputs)?;
    let (db, tables) = db::open_from_file(path)?;
    validate_outputs_for_tables(&tables, outputs)?;
    Ok((db, tables))
}

pub fn save_db(
    db: &mut rusqlite::Connection,
    outputs: &[OutputFolder],
    tables: &mut [table::Table],
    write_tags: bool,
) -> Result<()> {
    let tx = db
        .transaction()
        .context("failed to start transaction for updating db")?;
    let table_ids = db::save_db_raw(&tx, tables)?;
    if write_tags {
        db::update_tags_inplace(&tx)?;
    }
    lr2folder::write_files(outputs, tables, &table_ids)?;
    tx.commit()
        .context("failed to commit after writing playlists")?;
    // === No errors from this point
    for (table, id) in tables.iter_mut().zip(table_ids) {
        table.1.playlist_id = Some(id);
    }
    for table in tables.iter_mut() {
        table.1.entry_diff_to_save_to_db.clear();
    }
    Ok(())
}