init
This commit is contained in:
216
src-tauri/src/engine.rs
Normal file
216
src-tauri/src/engine.rs
Normal file
@@ -0,0 +1,216 @@
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::sync::Arc;
|
||||
use tokio::time::{interval, Duration};
|
||||
use tauri::{AppHandle, Manager};
|
||||
use xcap::Monitor;
|
||||
use chrono::Local;
|
||||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
use tauri_plugin_store::StoreExt;
|
||||
|
||||
pub struct AppState {
|
||||
pub is_paused: Arc<AtomicBool>,
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn toggle_pause(state: tauri::State<'_, AppState>) -> bool {
|
||||
let current = state.is_paused.load(Ordering::SeqCst);
|
||||
state.is_paused.store(!current, Ordering::SeqCst);
|
||||
!current
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn get_pause_state(state: tauri::State<'_, AppState>) -> bool {
|
||||
state.is_paused.load(Ordering::SeqCst)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn get_timeline(date: String, base_dir: String) -> Vec<serde_json::Value> {
|
||||
let mut results = Vec::new();
|
||||
let dir_path = PathBuf::from(base_dir).join(date);
|
||||
|
||||
if !dir_path.exists() || !dir_path.is_dir() {
|
||||
return results;
|
||||
}
|
||||
|
||||
if let Ok(entries) = fs::read_dir(dir_path) {
|
||||
let mut paths: Vec<_> = entries
|
||||
.filter_map(|e| e.ok())
|
||||
.map(|e| e.path())
|
||||
.filter(|p| p.is_file() && p.extension().and_then(|s| s.to_str()) == Some("jpg"))
|
||||
.collect();
|
||||
|
||||
paths.sort();
|
||||
|
||||
for path in paths {
|
||||
if let Some(file_name) = path.file_stem().and_then(|s| s.to_str()) {
|
||||
// file_name format: "14-30-00" or "14-30-00_0"
|
||||
let time_str = file_name.replace("-", ":").replace("_", " ");
|
||||
results.push(serde_json::json!({
|
||||
"time": time_str,
|
||||
"path": path.to_string_lossy().to_string()
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
results
|
||||
}
|
||||
|
||||
pub fn start_engine(app: AppHandle) {
|
||||
let state = app.state::<AppState>();
|
||||
let is_paused = state.is_paused.clone();
|
||||
|
||||
// Start Cleanup routine
|
||||
let app_clone = app.clone();
|
||||
tauri::async_runtime::spawn(async move {
|
||||
// Run once on startup, then every 24 hours
|
||||
let mut ticker = interval(Duration::from_secs(60 * 60 * 24));
|
||||
loop {
|
||||
ticker.tick().await;
|
||||
if let Err(e) = run_cleanup(&app_clone).await {
|
||||
eprintln!("Cleanup error: {}", e);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
tauri::async_runtime::spawn(async move {
|
||||
// Every 60 seconds
|
||||
let mut ticker = interval(Duration::from_secs(60));
|
||||
|
||||
loop {
|
||||
ticker.tick().await;
|
||||
|
||||
if is_paused.load(Ordering::SeqCst) {
|
||||
continue;
|
||||
}
|
||||
|
||||
println!("Tick: capturing screen...");
|
||||
if let Err(e) = capture_screens(&app).await {
|
||||
eprintln!("Failed to capture screens: {}", e);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async fn run_cleanup(app: &AppHandle) -> anyhow::Result<()> {
|
||||
let store = match app.store("config.json") {
|
||||
Ok(s) => s,
|
||||
Err(_) => return Ok(()),
|
||||
};
|
||||
|
||||
let save_path_value = store.get("savePath");
|
||||
let save_path_str = match save_path_value {
|
||||
Some(serde_json::Value::String(s)) => s,
|
||||
_ => return Ok(()),
|
||||
};
|
||||
|
||||
let base_dir = PathBuf::from(save_path_str);
|
||||
if !base_dir.exists() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let retain_days = store
|
||||
.get("retainDays")
|
||||
.and_then(|v| v.as_u64())
|
||||
.unwrap_or(30) as i64;
|
||||
|
||||
let now = chrono::Local::now().naive_local().date();
|
||||
|
||||
if let Ok(entries) = fs::read_dir(&base_dir) {
|
||||
for entry in entries.flatten() {
|
||||
let path = entry.path();
|
||||
if path.is_dir() {
|
||||
if let Some(folder_name) = path.file_name().and_then(|n| n.to_str()) {
|
||||
if let Ok(folder_date) = chrono::NaiveDate::parse_from_str(folder_name, "%Y-%m-%d") {
|
||||
let duration = now.signed_duration_since(folder_date);
|
||||
if duration.num_days() > retain_days {
|
||||
let _ = fs::remove_dir_all(&path);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn capture_screens(app: &AppHandle) -> anyhow::Result<()> {
|
||||
let store = match app.store("config.json") {
|
||||
Ok(s) => s,
|
||||
Err(_) => return Ok(()),
|
||||
};
|
||||
|
||||
let save_path_value = store.get("savePath");
|
||||
let save_path_str = match save_path_value {
|
||||
Some(serde_json::Value::String(s)) => s,
|
||||
_ => return Ok(()),
|
||||
};
|
||||
|
||||
let base_dir = PathBuf::from(save_path_str);
|
||||
if !base_dir.exists() {
|
||||
fs::create_dir_all(&base_dir)?;
|
||||
}
|
||||
|
||||
let merge_screens = store
|
||||
.get("mergeScreens")
|
||||
.and_then(|v| v.as_bool())
|
||||
.unwrap_or(false);
|
||||
|
||||
let monitors = Monitor::all()?;
|
||||
let now = Local::now();
|
||||
let timestamp = now.format("%H-%M-%S").to_string();
|
||||
let date_folder = now.format("%Y-%m-%d").to_string();
|
||||
let save_dir = base_dir.join(date_folder);
|
||||
|
||||
if !save_dir.exists() {
|
||||
fs::create_dir_all(&save_dir)?;
|
||||
}
|
||||
|
||||
if merge_screens {
|
||||
let mut min_x = i32::MAX;
|
||||
let mut min_y = i32::MAX;
|
||||
let mut max_x = i32::MIN;
|
||||
let mut max_y = i32::MIN;
|
||||
|
||||
for m in &monitors {
|
||||
let x = m.x()?;
|
||||
let y = m.y()?;
|
||||
let width = m.width()? as i32;
|
||||
let height = m.height()? as i32;
|
||||
|
||||
min_x = min_x.min(x);
|
||||
min_y = min_y.min(y);
|
||||
max_x = max_x.max(x + width);
|
||||
max_y = max_y.max(y + height);
|
||||
}
|
||||
|
||||
let total_width = (max_x - min_x) as u32;
|
||||
let total_height = (max_y - min_y) as u32;
|
||||
|
||||
let mut combined_image = image::RgbaImage::new(total_width, total_height);
|
||||
|
||||
for m in &monitors {
|
||||
let capture = m.capture_image()?;
|
||||
let offset_x = (m.x()? - min_x) as u32;
|
||||
let offset_y = (m.y()? - min_y) as u32;
|
||||
image::imageops::overlay(&mut combined_image, &capture, offset_x as i64, offset_y as i64);
|
||||
}
|
||||
|
||||
let file_path = save_dir.join(format!("{}.jpg", timestamp));
|
||||
let dynamic_image = image::DynamicImage::ImageRgba8(combined_image);
|
||||
let rgb_image = dynamic_image.into_rgb8();
|
||||
rgb_image.save_with_format(file_path, image::ImageFormat::Jpeg)?;
|
||||
} else {
|
||||
for (i, m) in monitors.iter().enumerate() {
|
||||
let capture = m.capture_image()?;
|
||||
let file_path = save_dir.join(format!("{}_{}.jpg", timestamp, i));
|
||||
let dynamic_image = image::DynamicImage::ImageRgba8(capture);
|
||||
let rgb_image = dynamic_image.into_rgb8();
|
||||
rgb_image.save_with_format(file_path, image::ImageFormat::Jpeg)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
Reference in New Issue
Block a user