Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 15 additions & 2 deletions src-tauri/src/events/frontend/instances.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,17 +26,20 @@ pub async fn create_instance(app: AppHandle, action: Action, context: Context) -
action: action.clone(),
context: ActionContext::from_context(context.clone(), index),
states: action.states.clone(),
persisted_states: action.states.clone(),
current_state: 0,
settings: serde_json::Value::Object(serde_json::Map::new()),
children: None,
};
children.push(instance.clone());

if parent.action.uuid == "opendeck.toggleaction" && parent.states.len() < children.len() {
parent.states.push(crate::shared::ActionState {
let state = crate::shared::ActionState {
image: "opendeck/toggle-action.png".to_owned(),
..Default::default()
});
};
parent.states.push(state.clone());
parent.persisted_states.push(state);
let _ = update_state(&app, parent.context.clone(), &mut locks).await;
}

Expand All @@ -52,6 +55,7 @@ pub async fn create_instance(app: AppHandle, action: Action, context: Context) -
action: action.clone(),
context: ActionContext::from_context(context.clone(), 0),
states: action.states.clone(),
persisted_states: action.states.clone(),
current_state: 0,
settings: serde_json::Value::Object(serde_json::Map::new()),
children: if matches!(action.uuid.as_str(), "opendeck.multiaction" | "opendeck.toggleaction") {
Expand Down Expand Up @@ -110,6 +114,7 @@ pub async fn move_instance(source: Context, destination: Context, retain: bool)
state.image = instance.action.icon.clone();
}
}
instance.persisted_states = instance.states.clone();
}
}

Expand All @@ -127,6 +132,12 @@ pub async fn move_instance(source: Context, destination: Context, retain: bool)
state.image = new_dir.join(path.strip_prefix(&old_dir).unwrap()).to_string_lossy().into_owned();
}
}
for state in new.persisted_states.iter_mut() {
let path = std::path::Path::new(&state.image);
if path.starts_with(&old_dir) {
state.image = new_dir.join(path.strip_prefix(&old_dir).unwrap()).to_string_lossy().into_owned();
}
}

let dst = get_slot_mut(&destination, &mut locks).await?;
*dst = Some(new.clone());
Expand Down Expand Up @@ -181,6 +192,7 @@ pub async fn remove_instance(context: ActionContext) -> Result<(), Error> {
}
if !children.is_empty() {
instance.states.pop();
instance.persisted_states.pop();
let _ = update_state(crate::APP_HANDLE.get().unwrap(), instance.context.clone(), &mut locks).await;
}
}
Expand Down Expand Up @@ -214,6 +226,7 @@ pub async fn set_state(context: ActionContext, index: u16, state: ActionState) -
let mut locks = acquire_locks_mut().await;
let reference = get_instance_mut(&context, &mut locks).await?.unwrap();
reference.states[index as usize] = state;
reference.persisted_states[index as usize] = reference.states[index as usize].clone();
let clone = reference.clone();
save_profile(&context.device, &mut locks).await?;
crate::events::outbound::states::title_parameters_did_change(&clone, index).await?;
Expand Down
22 changes: 15 additions & 7 deletions src-tauri/src/events/inbound/settings.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use crate::events::outbound::settings as outbound;
use crate::shared::ActionContext;
use crate::store::profiles::{acquire_locks, acquire_locks_mut, get_instance, get_instance_mut, save_profile};
use crate::store::profiles::{acquire_locks, acquire_locks_mut, debounce_profile_save, get_instance, get_instance_mut};

use std::io::Write;
use std::str::FromStr;
Expand All @@ -9,9 +9,13 @@ pub async fn set_settings(event: super::ContextAndPayloadEvent<serde_json::Value
let mut locks = acquire_locks_mut().await;

if let Some(instance) = get_instance_mut(&event.context, &mut locks).await? {
if instance.settings == event.payload {
outbound::did_receive_settings(instance, !from_property_inspector).await?;
return Ok(());
}
instance.settings = event.payload;
outbound::did_receive_settings(instance, !from_property_inspector).await?;
save_profile(&event.context.device, &mut locks).await?;
debounce_profile_save(event.context);
}

Ok(())
Expand Down Expand Up @@ -41,12 +45,16 @@ pub async fn set_global_settings(event: super::ContextAndPayloadEvent<serde_json
{
let settings_dir = crate::shared::config_dir().join("settings");
tokio::fs::create_dir_all(&settings_dir).await?;
let path = settings_dir.join(uuid.clone() + ".json");
let contents = event.payload.to_string();

let mut file = std::fs::OpenOptions::new().write(true).truncate(true).create(true).open(settings_dir.join(uuid.clone() + ".json"))?;
file.lock()?;
file.write_all(event.payload.to_string().as_bytes())?;
file.sync_data()?;
file.unlock()?;
if !std::fs::read_to_string(&path).map(|existing| existing == contents).unwrap_or(false) {
let mut file = std::fs::OpenOptions::new().write(true).truncate(true).create(true).open(path)?;
file.lock()?;
file.write_all(contents.as_bytes())?;
file.sync_data()?;
file.unlock()?;
}
}

outbound::did_receive_global_settings(&uuid, !from_property_inspector).await?;
Expand Down
16 changes: 5 additions & 11 deletions src-tauri/src/events/inbound/states.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use super::ContextAndPayloadEvent;

use crate::events::frontend::instances::update_state;
use crate::store::profiles::{acquire_locks_mut, debounce_profile_save, get_instance_mut, save_profile};
use crate::store::profiles::{acquire_locks_mut, debounce_profile_save, get_instance_mut};

use serde::Deserialize;

Expand Down Expand Up @@ -52,7 +52,6 @@ pub async fn set_title(event: ContextAndPayloadEvent<SetTitlePayload>) -> Result
}
update_state(crate::APP_HANDLE.get().unwrap(), instance.context.clone(), &mut locks).await?;
}
save_profile(&event.context.device, &mut locks).await?;

Ok(())
}
Expand Down Expand Up @@ -90,14 +89,6 @@ pub async fn set_image(mut event: ContextAndPayloadEvent<SetImagePayload>) -> Re
update_state(crate::APP_HANDLE.get().unwrap(), instance.context.clone(), &mut locks).await?;
}

if let Some(image) = &event.payload.image
&& image.trim().starts_with("data:")
{
debounce_profile_save(event.context);
} else {
save_profile(&event.context.device, &mut locks).await?;
}

Ok(())
}

Expand All @@ -108,10 +99,13 @@ pub async fn set_state(event: ContextAndPayloadEvent<SetStatePayload>) -> Result
if event.payload.state >= instance.states.len() as u16 {
return Ok(());
}
if instance.current_state == event.payload.state {
return Ok(());
}
instance.current_state = event.payload.state;
update_state(crate::APP_HANDLE.get().unwrap(), instance.context.clone(), &mut locks).await?;
debounce_profile_save(event.context);
}
save_profile(&event.context.device, &mut locks).await?;

Ok(())
}
9 changes: 5 additions & 4 deletions src-tauri/src/events/outbound/keypad.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use super::{GenericInstancePayload, send_to_plugin};

use crate::events::frontend::instances::{key_moved, update_state};
use crate::shared::{ActionContext, Context};
use crate::store::profiles::{acquire_locks_mut, get_slot_mut, save_profile};
use crate::store::profiles::{acquire_locks_mut, debounce_profile_save, get_slot_mut};

use std::sync::LazyLock;
use std::time::Duration;
Expand Down Expand Up @@ -75,7 +75,7 @@ pub async fn key_down(device: &str, key: u8) -> Result<(), anyhow::Error> {
let _ = update_state(crate::APP_HANDLE.get().unwrap(), child, &mut locks).await;
}

save_profile(device, &mut locks).await?;
debounce_profile_save(ActionContext::from_context(context, 0));
} else if instance.action.uuid == "opendeck.toggleaction" {
let children = instance.children.as_ref().unwrap();
if children.is_empty() {
Expand Down Expand Up @@ -167,8 +167,9 @@ pub async fn key_up(device: &str, key: u8) -> Result<(), anyhow::Error> {
.await?;
};

let _ = update_state(crate::APP_HANDLE.get().unwrap(), instance.context.clone(), &mut locks).await;
save_profile(device, &mut locks).await?;
let instance_context = instance.context.clone();
let _ = update_state(crate::APP_HANDLE.get().unwrap(), instance_context.clone(), &mut locks).await;
debounce_profile_save(instance_context);

Ok(())
}
1 change: 1 addition & 0 deletions src-tauri/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -375,6 +375,7 @@ If you have already donated, thank you so much for your support!"#,

app.run(|app, event| {
if let tauri::RunEvent::Exit = event {
tauri::async_runtime::block_on(store::profiles::flush_pending_profile_saves());
#[cfg(windows)]
futures::executor::block_on(plugins::deactivate_plugins());
tokio::spawn(elgato::reset_devices());
Expand Down
2 changes: 2 additions & 0 deletions src-tauri/src/shared.rs
Original file line number Diff line number Diff line change
Expand Up @@ -302,6 +302,8 @@ pub struct ActionInstance {
pub action: Action,
pub context: ActionContext,
pub states: Vec<ActionState>,
#[serde(skip)]
pub persisted_states: Vec<ActionState>,
pub current_state: u16,
pub settings: serde_json::Value,
pub children: Option<Vec<ActionInstance>>,
Expand Down
3 changes: 3 additions & 0 deletions src-tauri/src/store/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,9 @@ where
fs::create_dir_all(self.path.parent().unwrap())?;

let contents = serde_json::to_string_pretty(&T::into_value(&self.value)?)?;
if fs::read_to_string(&self.path).map(|existing| existing == contents).unwrap_or(false) {
return Ok(());
}

let temp_path = self.path.with_extension("json.temp");
let backup_path = self.path.with_extension("json.bak");
Expand Down
25 changes: 24 additions & 1 deletion src-tauri/src/store/profiles.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use super::Store;

use crate::shared::{ActionInstance, DEVICES, DeviceInfo, Profile, config_dir, copy_dir};

use std::collections::HashMap;
use std::collections::{HashMap, HashSet};
use std::fs;
use std::path::PathBuf;
use std::sync::LazyLock;
Expand Down Expand Up @@ -359,6 +359,29 @@ pub async fn save_profile(device: &str, locks: &mut LocksMut<'_>) -> Result<(),
}

pub static PROFILE_SAVE_DEBOUNCE: LazyLock<DashMap<crate::shared::ActionContext, JoinHandle<()>>> = LazyLock::new(DashMap::new);

pub async fn flush_pending_profile_saves() {
let contexts = PROFILE_SAVE_DEBOUNCE.iter().map(|entry| entry.key().clone()).collect::<Vec<_>>();
if contexts.is_empty() {
return;
}

let mut devices = HashSet::new();
for context in contexts {
devices.insert(context.device.clone());
if let Some((_, handle)) = PROFILE_SAVE_DEBOUNCE.remove(&context) {
handle.abort();
}
}

let mut locks = acquire_locks_mut().await;
for device in devices {
if let Err(error) = save_profile(&device, &mut locks).await {
log::error!("Failed to save profile for device {device}: {error}");
}
}
}

pub fn debounce_profile_save(context: crate::shared::ActionContext) {
if let Some((_, handle)) = PROFILE_SAVE_DEBOUNCE.remove(&context) {
handle.abort();
Expand Down
10 changes: 7 additions & 3 deletions src-tauri/src/store/simplified_profile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ impl From<ActionInstance> for DiskActionInstance {
let disk_context: DiskActionContext = value.context.clone().into();
let config_dir = crate::shared::config_dir();
let image_dir = config_dir.join("images").join(&value.context.device).join(&value.context.profile).join(disk_context.to_string());
let mut states = value.persisted_states.clone();

let normalise_path = |value: &str| -> String {
let path = Path::new(value);
Expand All @@ -87,7 +88,7 @@ impl From<ActionInstance> for DiskActionInstance {
}
};

for (index, state) in value.states.iter_mut().enumerate() {
for (index, state) in states.iter_mut().enumerate() {
if state.image.trim() == "data:" {
state.image = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVQIW2NgYGD4DwABBAEAwS2OUAAAAABJRU5ErkJggg==".to_owned();
}
Expand All @@ -112,7 +113,9 @@ impl From<ActionInstance> for DiskActionInstance {
};

let filename = format!("{}.{}", index, extension);
if fs::create_dir_all(&image_dir).is_err() || fs::write(image_dir.join(&filename), data).is_err() {
let path = image_dir.join(&filename);
let image_changed = fs::read(&path).map(|existing| existing.as_slice() != data.as_slice()).unwrap_or(true);
if fs::create_dir_all(&image_dir).is_err() || (image_changed && fs::write(path, data).is_err()) {
continue;
};
state.image = filename;
Expand All @@ -129,7 +132,7 @@ impl From<ActionInstance> for DiskActionInstance {
Self {
context: disk_context,
action: value.action,
states: value.states,
states,
current_state: value.current_state,
settings: value.settings,
children: value.children.map(|c| c.into_iter().map(|v| v.into()).collect()),
Expand Down Expand Up @@ -178,6 +181,7 @@ impl DiskActionInstance {
ActionInstance {
context: self.context.into_action_context(device, profile),
action,
persisted_states: states.clone(),
states,
current_state: self.current_state,
settings: self.settings,
Expand Down