Admin a9c15b21f0
Some checks failed
Desktop Release / Windows x64 NSIS (push) Has been cancelled
Desktop Release / Linux x64 DEB (push) Has been cancelled
Desktop Release / Publish public desktop release (push) Has been cancelled
feat: initialize FieldLens Print desktop release mirror
2026-09-23 22:43:52 +00:00

124 lines
3.5 KiB
Rust

use std::{
collections::VecDeque,
fs,
path::{Path, PathBuf},
sync::Arc,
};
use anyhow::{Context, Result};
use parking_lot::Mutex;
use serde::Serialize;
use serde_json::json;
use tauri::{AppHandle, Emitter};
use crate::model::{AppConfig, AuthState, JobSummary, ScanEvent};
const MAX_JOBS: usize = 200;
#[derive(Clone)]
pub struct SharedState {
pub config: Arc<Mutex<AppConfig>>,
pub jobs: Arc<Mutex<VecDeque<JobSummary>>>,
pub pending: Arc<Mutex<Vec<ScanEvent>>>,
pub auth: Arc<Mutex<AuthState>>,
pub data_dir: PathBuf,
}
impl SharedState {
pub fn load(data_dir: PathBuf) -> Result<Self> {
fs::create_dir_all(&data_dir).context("create FieldLens Print data directory")?;
let mut config = read_json::<AppConfig>(&data_dir.join("config.json")).unwrap_or_default();
if config.spool_dir.trim().is_empty() {
config.spool_dir = data_dir.join("spool").to_string_lossy().into_owned();
}
fs::create_dir_all(&config.spool_dir).ok();
let pending = read_json::<Vec<ScanEvent>>(&data_dir.join("pending.json")).unwrap_or_default();
let state = Self {
config: Arc::new(Mutex::new(config)),
jobs: Arc::new(Mutex::new(VecDeque::new())),
pending: Arc::new(Mutex::new(pending)),
auth: Arc::new(Mutex::new(AuthState::default())),
data_dir,
};
state.persist_config()?;
Ok(state)
}
pub fn persist_config(&self) -> Result<()> {
write_json_atomic(&self.data_dir.join("config.json"), &*self.config.lock())
}
pub fn persist_pending(&self) -> Result<()> {
write_json_atomic(&self.data_dir.join("pending.json"), &*self.pending.lock())
}
pub fn replace_config(&self, next: AppConfig) -> Result<AppConfig> {
fs::create_dir_all(&next.spool_dir).ok();
*self.config.lock() = next.clone();
self.persist_config()?;
Ok(next)
}
pub fn push_job(&self, app: &AppHandle, job: JobSummary) {
{
let mut jobs = self.jobs.lock();
jobs.push_front(job.clone());
while jobs.len() > MAX_JOBS {
jobs.pop_back();
}
}
let _ = app.emit("capture-job", &job);
}
pub fn queue_scan(
&self,
raw_value: String,
format: String,
source: String,
job_id: &str,
label: &str,
) -> ScanEvent {
let mut pending = self.pending.lock();
let duplicate = pending.iter().any(|event| event.raw_value == raw_value);
let event = ScanEvent {
id: uuid::Uuid::new_v4().to_string(),
raw_value,
format,
duplicate,
captured_at: chrono::Utc::now().to_rfc3339(),
source,
source_metadata: json!({
"jobId": job_id,
"label": label,
}),
};
pending.push(event.clone());
drop(pending);
let _ = self.persist_pending();
event
}
pub fn pending_count(&self) -> usize {
self.pending.lock().len()
}
}
fn read_json<T: serde::de::DeserializeOwned>(path: &Path) -> Option<T> {
let bytes = fs::read(path).ok()?;
serde_json::from_slice(&bytes).ok()
}
fn write_json_atomic<T: Serialize + ?Sized>(path: &Path, value: &T) -> Result<()> {
let bytes = serde_json::to_vec_pretty(value)?;
let temp = path.with_extension("tmp");
fs::write(&temp, bytes)?;
if path.exists() {
fs::remove_file(path)?;
}
fs::rename(&temp, path)?;
Ok(())
}