use crate::ScreenshotResult;
use chrono::Local;
use std::fs;
use std::path::PathBuf;
use std::sync::atomic::{AtomicBool, Ordering};
use tracing::{debug, info, warn};
static LOGGING_ENABLED: AtomicBool = AtomicBool::new(true);
static INITIALIZED: AtomicBool = AtomicBool::new(false);
pub fn get_executions_dir() -> PathBuf {
let base = dirs::data_local_dir().unwrap_or_else(std::env::temp_dir);
match std::env::var("COMPUTERUSE_WORKFLOW_ID") {
Ok(wf_id) => {
debug!("[screenshot_logger] Using workflow folder: {}", wf_id);
base.join("mediar")
.join("workflows")
.join(wf_id)
.join("executions")
}
Err(_) => base.join("mediar").join("executions"),
}
}
pub fn init() {
if INITIALIZED.swap(true, Ordering::Relaxed) {
return; }
if std::env::var("COMPUTERUSE_DISABLE_EXECUTION_LOGS")
.map(|v| v == "1" || v.to_lowercase() == "true")
.unwrap_or(false)
{
LOGGING_ENABLED.store(false, Ordering::Relaxed);
info!(
"[screenshot_logger] Screenshot logging disabled via COMPUTERUSE_DISABLE_EXECUTION_LOGS"
);
return;
}
let dir = get_executions_dir();
if let Err(e) = fs::create_dir_all(&dir) {
warn!("[screenshot_logger] Failed to create executions dir: {}", e);
LOGGING_ENABLED.store(false, Ordering::Relaxed);
return;
}
info!(
"[screenshot_logger] Screenshots will be saved to: {}",
dir.display()
);
}
pub fn is_enabled() -> bool {
LOGGING_ENABLED.load(Ordering::Relaxed)
}
pub fn generate_prefix(context: Option<&str>, operation: &str) -> String {
let timestamp = Local::now().format("%Y%m%d_%H%M%S").to_string();
let ctx = context.unwrap_or("sdk");
let clean_op = operation
.replace("::", "_")
.replace(" ", "_")
.chars()
.filter(|c| c.is_alphanumeric() || *c == '_')
.collect::<String>();
format!("{}_{}_{}", timestamp, ctx, clean_op)
}
#[derive(Debug, Clone)]
pub struct SavedScreenshot {
pub path: PathBuf,
pub filename: String,
}
pub fn save_screenshot(
screenshot: &ScreenshotResult,
prefix: &str,
suffix: &str,
max_dimension: Option<u32>,
) -> Option<SavedScreenshot> {
if !is_enabled() {
return None;
}
if !INITIALIZED.load(Ordering::Relaxed) {
init();
}
let dir = get_executions_dir();
let filename = format!("{}_{}.jpg", prefix, suffix);
let path = dir.join(&filename);
let jpeg_bytes = match screenshot.to_jpeg_resized(max_dimension, None) {
Ok(bytes) => bytes,
Err(e) => {
warn!("[screenshot_logger] Failed to encode JPEG: {}", e);
return None;
}
};
if let Err(e) = fs::write(&path, &jpeg_bytes) {
warn!(
"[screenshot_logger] Failed to save {}: {}",
path.display(),
e
);
return None;
}
info!(
"[screenshot_logger] Saved screenshot: {} ({}KB)",
filename,
jpeg_bytes.len() / 1024
);
Some(SavedScreenshot { path, filename })
}
pub fn save_monitor_screenshots(
screenshots: &[(crate::Monitor, ScreenshotResult)],
prefix: &str,
max_dimension: Option<u32>,
) -> Vec<SavedScreenshot> {
let mut saved = Vec::new();
for (i, (monitor, screenshot)) in screenshots.iter().enumerate() {
let suffix = if screenshots.len() == 1 {
"monitor".to_string()
} else {
format!("monitor_{}", i + 1)
};
if let Some(result) = save_screenshot(screenshot, prefix, &suffix, max_dimension) {
info!(
"[screenshot_logger] Saved monitor '{}' screenshot",
monitor.name
);
saved.push(result);
}
}
saved
}
pub fn save_window_screenshot(
screenshot: &ScreenshotResult,
prefix: &str,
max_dimension: Option<u32>,
) -> Option<SavedScreenshot> {
save_screenshot(screenshot, prefix, "window", max_dimension)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_generate_prefix() {
let prefix = generate_prefix(Some("workflow123"), "get_window_tree");
assert!(prefix.contains("workflow123"));
assert!(prefix.contains("get_window_tree"));
let prefix_sdk = generate_prefix(None, "capture");
assert!(prefix_sdk.contains("sdk"));
assert!(prefix_sdk.contains("capture"));
}
#[test]
fn test_get_executions_dir() {
let dir = get_executions_dir();
assert!(dir.to_string_lossy().contains("mediar"));
assert!(dir.to_string_lossy().contains("executions"));
}
}