gled-core 2.32.1

The engine of gled without a user interface: inputs, timing, rendering, Art-Net/DMX output and storage
Documentation
//! The storage thread: opens the git repository, then works through the
//! storage actions one at a time, reporting progress and errors in the
//! storage statics.

use super::{
    BRANCHES, Branches, ERROR, Loading, STAGED_FILES, STORAGE_DIR, STORAGE_VERSION_WRITTEN,
    WORKING,
    action::{self, StorageAction},
    asset::{
        animation::Animation, curve::Curve, midi_controller::MidiController,
        output_device::OutputDevice, palette::Palette, project::Project, scene::Scene,
    },
    collections::Collections,
    files::{asset_path, load_collection, write_storage_version_file},
    git, migration,
};
use crate::{action::UiAction, runtime::persistent_state::PersistentState};
use gled_model::patching::definition::FixtureDefinition;
use std::{
    fs::{read_to_string, remove_dir_all},
    sync::atomic::Ordering::Relaxed,
    thread::sleep,
    time::Duration,
};

pub(crate) fn start_thread() {
    let actions = action::init();

    std::thread::Builder::new()
        .name("gled:storage".to_string())
        .spawn(move || {
            #[cfg(feature = "profiling")]
            profiling::register_thread!("storage");

            let mut retry_wait = std::time::Duration::from_secs(0);
            loop {
                sleep(retry_wait);
                retry_wait = std::time::Duration::from_secs(2);

                ERROR.lock().take();

                // Clear the queue
                while let Ok(Some(_)) = actions.try_recv() {}

                Loading::GitRepository.set();
                let mut git = match git::Git::open(PersistentState::default().git_url()) {
                    Ok(git) => git,
                    Err(err) => {
                        let err: String = format!("Could not open git: {err}");
                        tracing::error!("{err}");
                        ERROR.lock().replace(err);
                        continue;
                    }
                };

                StorageAction::LoadBranches.enqueue();
                StorageAction::CountStagedFiles.enqueue();
                StorageAction::LoadAssets.enqueue();

                while let Ok(action) = actions.recv() {
                    WORKING.store(true, Relaxed);

                    match action {
                        StorageAction::Nuke => {
                            ERROR.lock().take();
                            Loading::Nuking.set();
                            remove_dir_all(&*STORAGE_DIR).ok();
                            StorageAction::Restart.enqueue();
                        }
                        StorageAction::Restart => {
                            break;
                        }
                        StorageAction::Stop => {
                            return;
                        }
                        StorageAction::CountStagedFiles => match git.count_staged_files() {
                            Err(err) => {
                                ERROR
                                    .lock()
                                    .replace(format!("Error counting staged files: {err}"));
                                sleep(Duration::from_secs(1));
                                StorageAction::CountStagedFiles.enqueue();
                            }
                            Ok(count) => {
                                STAGED_FILES.store(count, Relaxed);
                                ERROR.lock().take();
                            }
                        },
                        StorageAction::Pull => {
                            if let Err(err) = git.pull() {
                                ERROR.lock().replace(format!("Error pulling: {err}"));
                                sleep(Duration::from_secs(1));
                                StorageAction::Pull.enqueue();
                            } else {
                                ERROR.lock().take();
                            }
                        }
                        StorageAction::Push => {
                            if let Err(err) = git.push() {
                                ERROR.lock().replace(format!("Error pushing: {err}"));
                                sleep(Duration::from_secs(1));
                                StorageAction::Push.enqueue();
                            } else {
                                ERROR.lock().take();
                            }
                        }
                        StorageAction::Commit { message } => {
                            if let Err(err) = git.commit(&message) {
                                ERROR
                                    .lock()
                                    .replace(format!("Error committing and pushing: {err}"));
                                sleep(Duration::from_secs(1));
                                StorageAction::Commit { message }.enqueue();
                            } else {
                                ERROR.lock().take();
                            }
                            StorageAction::CountStagedFiles.enqueue();
                        }
                        StorageAction::LoadBranches => {
                            BRANCHES.lock().take();

                            Loading::GitBranches.set();
                            let branches = match git.branches() {
                                Ok(branches) => branches,
                                Err(err) => {
                                    let err: String = format!("Could not get branches: {err}");
                                    tracing::error!("{err}");
                                    ERROR.lock().replace(err);
                                    continue;
                                }
                            };

                            Loading::GitBranch.set();
                            let current_branch = match git.current_branch() {
                                Ok(current_branch) => current_branch,
                                Err(err) => {
                                    let err: String = format!("Could not get current_branch: {err}");
                                    tracing::error!("{err}");
                                    ERROR.lock().replace(err);
                                    continue;
                                }
                            };

                            *BRANCHES.lock() = Some(Branches {
                                available: branches,
                                current: current_branch,
                            });

                            ERROR.lock().take();
                        }
                        StorageAction::LoadAssets => {
                            STORAGE_VERSION_WRITTEN.store(false, Relaxed);
                            let version_file = STORAGE_DIR.join("version");
                            match read_to_string(version_file).ok().and_then(|version| {
                                semver::Version::parse(version.trim())
                                    .map_err(|err| tracing::error!("Could not parse version file: {err:?}"))
                                    .ok()
                            }) {
                                Some(version) => {
                                    if version
                                        > semver::Version::parse(env!("CARGO_PKG_VERSION"))
                                            .expect("Could not parse cargo pkg version")
                                    {
                                        tracing::error!(
                                            "Gled version is too old. Please update to the latest version."
                                        );
                                        ERROR.lock().replace("Gled version is too old. Please update to the latest version.".to_string());
                                        continue;
                                    }
                                }
                                None => {
                                    write_storage_version_file(&mut git);
                                }
                            }

                            migration::run(&mut git);

                            let mut collections = Collections::default();

                            Loading::Animations.set();
                            collections.insert::<Animation>(load_collection::<Animation>());

                            Loading::Curves.set();
                            collections.insert::<Curve>(load_collection::<Curve>());

                            Loading::FixtureDefinitions.set();
                            collections.insert::<FixtureDefinition>(load_collection::<
                                FixtureDefinition,
                            >());

                            Loading::OutputDevices.set();
                            collections.insert::<OutputDevice>(load_collection::<OutputDevice>());

                            Loading::MidiControllers.set();
                            collections.insert::<MidiController>(load_collection::<MidiController>());

                            Loading::Palettes.set();
                            collections.insert::<Palette>(load_collection::<Palette>());

                            Loading::Projects.set();
                            collections.insert::<Project>(load_collection::<Project>());

                            Loading::Scenes.set();
                            collections.insert::<Scene>(load_collection::<Scene>());

                            Loading::Synchronizing.set();
                            collections.save();

                            Loading::No.set();
                        }
                        StorageAction::SwitchBranch(branch) => match git.switch_branch(&branch) {
                            Ok(_) => {
                                StorageAction::Restart.enqueue();
                                StorageAction::LoadBranches.enqueue();
                                StorageAction::CountStagedFiles.enqueue();
                                StorageAction::LoadAssets.enqueue();
                            }
                            Err(err) => {
                                ERROR
                                    .lock()
                                    .replace(format!("Error switching branch: {err}"));
                            }
                        },
                        StorageAction::SaveAsset {
                            dir_name,
                            uuid,
                            json,
                        } => {
                            if let Err(err) = git.write_asset(&asset_path(uuid, dir_name), json) {
                                UiAction::Error(format!("Could not write asset: {err}")).enqueue();
                            }

                            write_storage_version_file(&mut git);

                            StorageAction::CountStagedFiles.enqueue();
                        }
                        StorageAction::SavePicture { path, bytes } => {
                            if let Err(err) = git.write_asset(&STORAGE_DIR.join(&path), &*bytes.0)
                            {
                                UiAction::Error(format!(
                                    "Could not store the picture {}: {err}",
                                    path.display()
                                ))
                                .enqueue();
                            }
                            StorageAction::CountStagedFiles.enqueue();
                        }
                        StorageAction::DeleteAsset { uuid, dir_name } => {
                            if let Err(err) = git.delete_asset(&asset_path(uuid, dir_name)) {
                                tracing::error!("Could not delete asset: {err:?}");
                            }

                            StorageAction::CountStagedFiles.enqueue();
                        }
                    }

                    WORKING.store(false, Relaxed);
                }
            }
        })
    .expect("Could not spawn storage thread");
}