mirror of
https://github.com/MindWorkAI/AI-Studio.git
synced 2026-10-05 00:49:40 +00:00
Improve Linux clipboard support and file handling (#872)
This commit is contained in:
1 parent
f666760415
commit
8fa0c4db01
16 files changed
+343
-166
No files matched your search
@@ -1,9 +1,9 @@
|
||||
use std::fmt::Display;
|
||||
use std::sync::Mutex;
|
||||
use arboard::Clipboard;
|
||||
use axum::Json;
|
||||
use log::{debug, error, warn};
|
||||
use once_cell::sync::Lazy;
|
||||
use axum::Json;
|
||||
use serde::Serialize;
|
||||
use crate::api_token::APIToken;
|
||||
use crate::encryption::{EncryptedText, ENCRYPTION};
|
||||
@@ -26,17 +26,32 @@ impl ClipboardBackend for Clipboard {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
enum ClipboardOperationError<E> {
|
||||
Initialization(E),
|
||||
Write(E),
|
||||
}
|
||||
|
||||
impl<E: Display> Display for ClipboardOperationError<E> {
|
||||
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::Initialization(error) => write!(formatter, "Failed to initialize the clipboard backend: {error}"),
|
||||
Self::Write(error) => write!(formatter, "Failed to write to the clipboard: {error}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn set_text_with_retry<B, F>(
|
||||
clipboard: &mut Option<B>,
|
||||
text: String,
|
||||
mut create_clipboard: F,
|
||||
) -> Result<(), B::Error>
|
||||
) -> Result<(), ClipboardOperationError<B::Error>>
|
||||
where
|
||||
B: ClipboardBackend,
|
||||
F: FnMut() -> Result<B, B::Error>,
|
||||
{
|
||||
if clipboard.is_none() {
|
||||
*clipboard = Some(create_clipboard()?);
|
||||
*clipboard = Some(create_clipboard().map_err(ClipboardOperationError::Initialization)?);
|
||||
}
|
||||
|
||||
let first_result = clipboard.as_mut().unwrap().set_text(text.clone());
|
||||
@@ -44,10 +59,10 @@ where
|
||||
warn!(Source = "Clipboard"; "Failed to set text using the current clipboard backend; reinitializing it once: {first_error}.");
|
||||
*clipboard = None;
|
||||
|
||||
let mut retry_clipboard = create_clipboard()?;
|
||||
let mut retry_clipboard = create_clipboard().map_err(ClipboardOperationError::Initialization)?;
|
||||
if let Err(retry_error) = retry_clipboard.set_text(text) {
|
||||
error!(Source = "Clipboard"; "Failed to set text after reinitializing the clipboard backend: {retry_error}.");
|
||||
return Err(retry_error);
|
||||
return Err(ClipboardOperationError::Write(retry_error));
|
||||
}
|
||||
|
||||
*clipboard = Some(retry_clipboard);
|
||||
@@ -87,7 +102,7 @@ pub async fn set_clipboard(_token: APIToken, encrypted_text: String) -> Json<Set
|
||||
},
|
||||
|
||||
Err(e) => {
|
||||
error!(Source = "Clipboard"; "Failed to set text to the clipboard: {e}.");
|
||||
error!(Source = "Clipboard"; "Clipboard operation failed: {e}.");
|
||||
Json(SetClipboardResponse {
|
||||
success: false,
|
||||
issue: e.to_string(),
|
||||
@@ -116,7 +131,7 @@ mod tests {
|
||||
use std::collections::VecDeque;
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
use std::sync::{Arc, Mutex};
|
||||
use super::{release_clipboard, set_text_with_retry, ClipboardBackend};
|
||||
use super::{ClipboardOperationError, release_clipboard, set_text_with_retry, ClipboardBackend};
|
||||
|
||||
struct MockClipboard {
|
||||
id: usize,
|
||||
@@ -186,6 +201,30 @@ mod tests {
|
||||
assert!(clipboard.is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reports_initialization_failures_and_retries_on_the_next_request() {
|
||||
let mut clipboard: Option<MockClipboard> = None;
|
||||
let mut factory = MockFactory::new([false]);
|
||||
let mut fail_initialization = true;
|
||||
|
||||
let error = set_text_with_retry(&mut clipboard, "first".to_string(), || {
|
||||
if fail_initialization {
|
||||
fail_initialization = false;
|
||||
Err("initialization failed".to_string())
|
||||
} else {
|
||||
factory.create()
|
||||
}
|
||||
}).unwrap_err();
|
||||
|
||||
assert_eq!(error, ClipboardOperationError::Initialization("initialization failed".to_string()));
|
||||
assert!(clipboard.is_none());
|
||||
|
||||
set_text_with_retry(&mut clipboard, "second".to_string(), || factory.create()).unwrap();
|
||||
|
||||
assert_eq!(factory.created, 1);
|
||||
assert!(clipboard.is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reuses_the_same_instance_for_multiple_writes() {
|
||||
let mut clipboard = None;
|
||||
@@ -210,6 +249,26 @@ mod tests {
|
||||
assert_eq!(*factory.writes.lock().unwrap(), vec![(0, "text".to_string()), (1, "text".to_string())]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reports_reinitialization_failures_and_discards_the_failed_instance() {
|
||||
let mut clipboard = None;
|
||||
let mut factory = MockFactory::new([true]);
|
||||
let mut initialization_attempts = 0;
|
||||
|
||||
let error = set_text_with_retry(&mut clipboard, "text".to_string(), || {
|
||||
initialization_attempts += 1;
|
||||
if initialization_attempts == 1 {
|
||||
factory.create()
|
||||
} else {
|
||||
Err("reinitialization failed".to_string())
|
||||
}
|
||||
}).unwrap_err();
|
||||
|
||||
assert_eq!(error, ClipboardOperationError::Initialization("reinitialization failed".to_string()));
|
||||
assert_eq!(initialization_attempts, 2);
|
||||
assert!(clipboard.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn returns_the_retry_error_and_discards_the_failed_instance() {
|
||||
let mut clipboard = None;
|
||||
@@ -217,7 +276,7 @@ mod tests {
|
||||
|
||||
let error = set_text_with_retry(&mut clipboard, "text".to_string(), || factory.create()).unwrap_err();
|
||||
|
||||
assert_eq!(error, "backend 1 failed");
|
||||
assert_eq!(error, ClipboardOperationError::Write("backend 1 failed".to_string()));
|
||||
assert_eq!(factory.created, 2);
|
||||
assert!(clipboard.is_none());
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user