Skip to content
Open
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
90 changes: 76 additions & 14 deletions apps/desktop/src-tauri/src/automation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ use crate::general_settings::PostStudioRecordingBehaviour;

const WEBHOOK_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(30);
const COMMAND_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(300);
const CLIPBOARD_WRITE_ATTEMPTS: usize = 4;
const CLIPBOARD_WRITE_RETRY_BASE_MS: u64 = 50;

#[derive(Debug, PartialEq, Eq)]
enum ClipboardImageSource {
Expand Down Expand Up @@ -57,6 +59,30 @@ fn resolve_clipboard_image_source(
}
}

fn clipboard_write_retry_delay(attempt: usize) -> Option<std::time::Duration> {
(attempt + 1 < CLIPBOARD_WRITE_ATTEMPTS)
.then(|| std::time::Duration::from_millis(CLIPBOARD_WRITE_RETRY_BASE_MS << attempt))
}

enum ClipboardImage {
File(PathBuf),
Encoded(Vec<u8>),
}

impl ClipboardImage {
fn load(&self) -> Result<clipboard_rs::RustImageData, String> {
match self {
Self::File(path) => {
let path = path.to_string_lossy().to_string();
clipboard_rs::RustImageData::from_path(&path)
.map_err(|e| format!("Failed to load image for clipboard: {e}"))
}
Self::Encoded(bytes) => clipboard_rs::RustImageData::from_bytes(bytes)
.map_err(|e| format!("Failed to load rendered screenshot for clipboard: {e}")),
}
}
}

pub struct DesktopAutomationHost {
app: AppHandle,
clipboard: Arc<RwLock<ClipboardContext>>,
Expand All @@ -66,6 +92,34 @@ impl DesktopAutomationHost {
pub fn new(app: AppHandle, clipboard: Arc<RwLock<ClipboardContext>>) -> Self {
Self { app, clipboard }
}

async fn set_clipboard_image(&self, image: &ClipboardImage) -> Result<(), String> {
for attempt in 0..CLIPBOARD_WRITE_ATTEMPTS {
let image_data = image.load()?;
let result = {
let clipboard = self.clipboard.write().await;
clipboard.set_image(image_data)
};

match result {
Ok(()) => return Ok(()),
Err(error) => {
let Some(retry_delay) = clipboard_write_retry_delay(attempt) else {
return Err(format!("Failed to set clipboard image: {error}"));
};
warn!(
attempt = attempt + 1,
retry_delay_ms = retry_delay.as_millis(),
%error,
"Clipboard image write failed; retrying"
);
tokio::time::sleep(retry_delay).await;
}
}
}

unreachable!("clipboard retry loop always returns")
}
}

impl AutomationHost for DesktopAutomationHost {
Expand Down Expand Up @@ -94,12 +148,10 @@ impl AutomationHost for DesktopAutomationHost {
source: &ClipboardSource,
) -> Result<(), String> {
let source = resolve_clipboard_image_source(ctx, *source)?;
let img_data = match source {
let image = match source {
ClipboardImageSource::File(path) => {
let path = path.to_string_lossy().to_string();
info!(%path, "Automation: copying file to clipboard");
clipboard_rs::RustImageData::from_path(&path)
.map_err(|e| format!("Failed to load image for clipboard: {e}"))?
info!(path = %path.display(), "Automation: copying file to clipboard");
ClipboardImage::File(path)
}
ClipboardImageSource::ScreenshotProject(path) => {
info!(project = %path.display(), "Automation: rendering screenshot for clipboard");
Expand All @@ -108,18 +160,11 @@ impl AutomationHost for DesktopAutomationHost {
path,
)
.await?;
clipboard_rs::RustImageData::from_bytes(&rendered.image_bytes)
.map_err(|e| format!("Failed to load rendered screenshot for clipboard: {e}"))?
ClipboardImage::Encoded(rendered.image_bytes)
}
};

self.clipboard
.write()
.await
.set_image(img_data)
.map_err(|e| format!("Failed to set clipboard image: {e}"))?;

Ok(())
self.set_clipboard_image(&image).await
}

async fn save_to_location(
Expand Down Expand Up @@ -1019,4 +1064,21 @@ mod tests {
)))
);
}

#[test]
fn clipboard_write_retries_use_bounded_exponential_backoff() {
assert_eq!(
clipboard_write_retry_delay(0),
Some(std::time::Duration::from_millis(50))
);
assert_eq!(
clipboard_write_retry_delay(1),
Some(std::time::Duration::from_millis(100))
);
assert_eq!(
clipboard_write_retry_delay(2),
Some(std::time::Duration::from_millis(200))
);
assert_eq!(clipboard_write_retry_delay(3), None);
}
}
Loading