Skip to main content

leyline/core/device/
autosave.rs

1use std::path::{Path, PathBuf};
2use std::sync::{Arc, Mutex};
3use std::time::Duration;
4
5use crate::core::error::Result;
6use crate::core::{Session, SessionState, Tab};
7use crate::util::autosave::{Autosave, SaveJob, SaveTarget, Schedule};
8use crate::util::lock;
9
10use super::Device;
11
12#[derive(Clone, Copy, Debug, PartialEq, Eq)]
13#[non_exhaustive]
14pub struct DeviceAutosaveOptions {
15    pub interval: Duration,
16    pub state_interval: Duration,
17}
18
19impl DeviceAutosaveOptions {
20    #[must_use]
21    pub const fn new(interval: Duration) -> Self {
22        Self {
23            interval,
24            state_interval: Duration::from_secs(300),
25        }
26    }
27
28    #[must_use]
29    pub const fn state_interval(mut self, state_interval: Duration) -> Self {
30        self.state_interval = state_interval;
31        self
32    }
33}
34
35impl From<Duration> for DeviceAutosaveOptions {
36    fn from(interval: Duration) -> Self {
37        Self::new(interval)
38    }
39}
40
41pub struct DeviceAutosave {
42    path: Arc<Path>,
43    device: Arc<Mutex<Device>>,
44    tab: Arc<Mutex<Option<Tab>>>,
45    autosave: Autosave,
46}
47
48struct DeviceTarget {
49    device: Arc<Mutex<Device>>,
50    tab: Arc<Mutex<Option<Tab>>>,
51    session: Session,
52    path: Arc<Path>,
53    saved: Option<u64>,
54    writing: Option<SessionState>,
55    saved_state: Option<SessionState>,
56}
57
58impl Device {
59    pub fn autosave(
60        &self,
61        session: &Session,
62        path: impl Into<PathBuf>,
63        options: impl Into<DeviceAutosaveOptions>,
64    ) -> DeviceAutosave {
65        let options = options.into();
66        let path: Arc<Path> = Arc::from(path.into());
67        let device = Arc::new(Mutex::new(self.clone()));
68        let tab = Arc::new(Mutex::new(None));
69        let target = DeviceTarget {
70            device: Arc::clone(&device),
71            tab: Arc::clone(&tab),
72            session: session.clone(),
73            path: Arc::clone(&path),
74            saved: None,
75            writing: None,
76            saved_state: None,
77        };
78        let schedule = Schedule {
79            interval: options.interval,
80            periodic: Some(options.state_interval),
81            label: "device",
82        };
83        DeviceAutosave {
84            path,
85            device,
86            tab,
87            autosave: Autosave::spawn(target, session.cookies().changes(), schedule),
88        }
89    }
90}
91
92impl DeviceAutosave {
93    pub fn update(&self, change: impl FnOnce(&mut Device)) {
94        change(&mut lock(&self.device));
95        self.autosave.touch();
96    }
97
98    pub fn track(&self, tab: &Tab) {
99        *lock(&self.tab) = Some(tab.clone());
100        self.autosave.touch();
101    }
102
103    #[must_use]
104    pub fn device(&self) -> Device {
105        snapshot(&self.device, &self.tab)
106    }
107
108    pub async fn flush(&self) -> Result<()> {
109        self.autosave.flush().await
110    }
111
112    pub async fn shutdown(self) -> Result<()> {
113        self.autosave.shutdown().await
114    }
115}
116
117impl std::fmt::Debug for DeviceAutosave {
118    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
119        f.debug_struct("DeviceAutosave")
120            .field("path", &self.path)
121            .finish_non_exhaustive()
122    }
123}
124
125impl SaveTarget for DeviceTarget {
126    fn prepare(&mut self) -> Option<SaveJob> {
127        let live = self.session.cookies();
128        let generation = *live.changes().borrow();
129        let jar = live.snapshot();
130        let jar_dirty = self.saved != Some(generation);
131        let mut device = snapshot(&self.device, &self.tab);
132        device.state = self.session.state();
133        self.writing = Some(device.state.clone());
134        let jar_path = device.jar_path.clone();
135        device.jar = jar_path.is_none().then(|| jar.clone());
136        let path = Arc::clone(&self.path);
137        Some(SaveJob {
138            generation,
139            write: Box::new(move || {
140                if let Some(jar_path) = jar_path.filter(|_| jar_dirty) {
141                    jar.save_to(jar_path)?;
142                }
143                device.save_to(&*path)
144            }),
145        })
146    }
147
148    fn saved(&mut self, generation: u64) {
149        self.saved = Some(generation);
150        self.saved_state = self.writing.take();
151    }
152
153    fn unsaved(&self) -> bool {
154        self.saved_state.as_ref() != Some(&self.session.state())
155    }
156}
157
158fn snapshot(device: &Mutex<Device>, tab: &Mutex<Option<Tab>>) -> Device {
159    let mut device = lock(device).clone();
160    if let Some(tab) = lock(tab).as_ref() {
161        device.page = tab.current();
162    }
163    device
164}