pub mod action;
pub mod asset;
pub mod asset_id;
pub mod collection;
pub mod collections;
pub mod git;
pub mod serde;
use self::{action::StorageAction, asset::*, asset_id::AssetId};
use crate::{
app::persistent_state::PersistentState, storage::collections::Collections, ui::action::UiAction,
};
use animation::Animation;
use collection::Collection;
use curve::Curve;
use directories::BaseDirs;
use egui::mutex::Mutex;
use git::Git;
use midi_controller::MidiController;
use once_cell::sync::Lazy;
use output_device::OutputDevice;
use palette::Palette;
use project::Project;
use scene::Scene;
use std::{
fmt::Debug,
fs::{read_to_string, remove_dir_all},
path::PathBuf,
sync::atomic::{AtomicBool, AtomicUsize, Ordering::Relaxed},
thread::sleep,
time::Duration,
};
use strum::Display;
use uuid::Uuid;
pub static STORAGE_DIR: Lazy<PathBuf> = Lazy::new(|| {
BaseDirs::new()
.expect("Could not get base dirs")
.data_dir()
.join("gled2")
});
static STORAGE_VERSION_FILE: Lazy<PathBuf> = Lazy::new(|| STORAGE_DIR.join("version"));
static WORKING: AtomicBool = AtomicBool::new(true);
static ERROR: Lazy<Mutex<Option<String>>> = Lazy::new(|| Mutex::new(None));
static LOADING: AtomicLoading = AtomicLoading::new(Loading::No);
static BRANCHES: Lazy<Mutex<Option<Branches>>> = Lazy::new(|| Mutex::new(None));
static STAGED_FILES: AtomicUsize = AtomicUsize::new(0);
#[derive(Debug, Clone)]
pub struct Branches {
pub available: Vec<String>,
pub current: String,
}
#[derive(Display, Default, PartialEq, Eq)]
#[atomic_enum::atomic_enum]
pub enum Loading {
#[default]
#[strum(serialize = "Idling")]
No,
#[strum(serialize = "Nuking storage")]
Nuking,
#[strum(serialize = "Loading git repository")]
GitRepository,
#[strum(serialize = "Loading git branches")]
GitBranches,
#[strum(serialize = "Loading current git branch")]
GitBranch,
#[strum(serialize = "Loading animations")]
Animations,
#[strum(serialize = "Loading curves")]
Curves,
#[strum(serialize = "Loading output devices")]
OutputDevices,
#[strum(serialize = "Loading MIDI controllers")]
MidiControllers,
#[strum(serialize = "Loading palettes")]
Palettes,
#[strum(serialize = "Loading projects")]
Projects,
#[strum(serialize = "Loading scenes")]
Scenes,
#[strum(serialize = "Synchronizing")]
Synchronizing,
}
impl Loading {
pub fn set(self) {
LOADING.store(self, Relaxed);
}
}
pub fn working() -> bool {
WORKING.load(Relaxed)
}
pub fn staged_files() -> usize {
STAGED_FILES.load(Relaxed)
}
pub fn loading() -> Loading {
LOADING.load(Relaxed)
}
pub fn is_loading() -> bool {
loading() != Loading::No
}
pub fn error() -> Option<String> {
ERROR.lock().clone()
}
pub fn branches() -> Option<Branches> {
BRANCHES.lock().clone()
}
pub 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();
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 => {
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);
}
}
if let Err(err) = mkdirp::mkdirp(STORAGE_DIR.join("svg")) {
ERROR
.lock()
.replace(format!("Could not create svg templates folder: {err}"));
continue;
}
let mut collections = Collections::default();
Loading::Animations.set();
collections.insert::<Animation>(Collection::<Animation>::load());
Loading::Curves.set();
collections.insert::<Curve>(Collection::<Curve>::load());
Loading::OutputDevices.set();
collections.insert::<OutputDevice>(Collection::<OutputDevice>::load());
Loading::MidiControllers.set();
collections.insert::<MidiController>(Collection::<MidiController>::load());
Loading::Palettes.set();
collections.insert::<Palette>(Collection::<Palette>::load());
Loading::Projects.set();
collections.insert::<Project>(Collection::<Project>::load());
Loading::Scenes.set();
collections.insert::<Scene>(Collection::<Scene>::load());
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::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");
}
pub fn asset_path(id: Uuid, dir_name: &str) -> PathBuf {
STORAGE_DIR.join(dir_name).join(format!("{id}.json"))
}
pub fn write_storage_version_file(git: &mut Git) {
static STORAGE_VERSION_WRITTEN: AtomicBool = AtomicBool::new(false);
if STORAGE_VERSION_WRITTEN.load(Relaxed) {
return;
}
if let Err(err) = std::fs::write(
STORAGE_VERSION_FILE.as_path(),
format!("{}\n", env!("CARGO_PKG_VERSION")),
) {
tracing::error!("Could not write storage version file: {err}");
ERROR
.lock()
.replace(format!("Could not write storage version file: {err}"));
StorageAction::Stop.enqueue();
return;
}
if let Err(err) = git.add(STORAGE_VERSION_FILE.as_path()) {
tracing::error!("Could not add storage version file to git: {err}");
ERROR
.lock()
.replace(format!("Could not add storage version file to git: {err}"));
StorageAction::Stop.enqueue();
return;
}
STORAGE_VERSION_WRITTEN.store(true, Relaxed);
}