atagen

macltok

public
Star0
Watch0
9bd34e25bb861f0d2a6f19a39cbceeac12e36f93
3
ssh://brewery@pls.lobotomise.me:33/atagen/macltok.git

Blame: src/store_lock.rs

a250c7fa43fbatagen2026-09-26 13:35 UTC1
use crate::{AccountError, account::account_error};
a250c7fa43fbatagen2026-09-26 13:35 UTC2
a250c7fa43fbatagen2026-09-26 13:35 UTC3
/// the platform's own exclusive lock on a store. matrix-sdk 0.18's cross process lease drops
a250c7fa43fbatagen2026-09-26 13:35 UTC4
/// itself silently after one failed renewal or a stall past 500ms, so it can't be what keeps a
a250c7fa43fbatagen2026-09-26 13:35 UTC5
/// second engine out
a250c7fa43fbatagen2026-09-26 13:35 UTC6
pub(crate) struct StoreLock {
a250c7fa43fbatagen2026-09-26 13:35 UTC7
    #[cfg(feature = "sqlite")]
a250c7fa43fbatagen2026-09-26 13:35 UTC8
    _file: std::fs::File,
a250c7fa43fbatagen2026-09-26 13:35 UTC9
    #[cfg(feature = "indexeddb")]
a250c7fa43fbatagen2026-09-26 13:35 UTC10
    release: js_sys::Function,
a250c7fa43fbatagen2026-09-26 13:35 UTC11
}
a250c7fa43fbatagen2026-09-26 13:35 UTC12
a250c7fa43fbatagen2026-09-26 13:35 UTC13
#[cfg(feature = "sqlite")]
a250c7fa43fbatagen2026-09-26 13:35 UTC14
pub(crate) const LOCK_FILE: &str = "macltok.lock";
a250c7fa43fbatagen2026-09-26 13:35 UTC15
a250c7fa43fbatagen2026-09-26 13:35 UTC16
#[cfg(feature = "sqlite")]
a250c7fa43fbatagen2026-09-26 13:35 UTC17
impl StoreLock {
a250c7fa43fbatagen2026-09-26 13:35 UTC18
    pub(crate) fn file(directory: &std::path::Path) -> Result<Self, AccountError> {
a250c7fa43fbatagen2026-09-26 13:35 UTC19
        use std::fs::{self, File, TryLockError};
a250c7fa43fbatagen2026-09-26 13:35 UTC20
a250c7fa43fbatagen2026-09-26 13:35 UTC21
        fs::create_dir_all(directory)
a250c7fa43fbatagen2026-09-26 13:35 UTC22
            .map_err(|error| account_error("creating the Matrix store directory", error))?;
a250c7fa43fbatagen2026-09-26 13:35 UTC23
        let file = File::options()
a250c7fa43fbatagen2026-09-26 13:35 UTC24
            .create(true)
a250c7fa43fbatagen2026-09-26 13:35 UTC25
            .truncate(false)
a250c7fa43fbatagen2026-09-26 13:35 UTC26
            .write(true)
a250c7fa43fbatagen2026-09-26 13:35 UTC27
            .open(directory.join(LOCK_FILE))
a250c7fa43fbatagen2026-09-26 13:35 UTC28
            .map_err(|error| account_error("opening the Matrix store's lock file", error))?;
a250c7fa43fbatagen2026-09-26 13:35 UTC29
        match file.try_lock() {
a250c7fa43fbatagen2026-09-26 13:35 UTC30
            Ok(()) => Ok(Self { _file: file }),
a250c7fa43fbatagen2026-09-26 13:35 UTC31
            Err(TryLockError::WouldBlock) => Err(AccountError::StoreInUse),
a250c7fa43fbatagen2026-09-26 13:35 UTC32
            Err(TryLockError::Error(error)) => {
a250c7fa43fbatagen2026-09-26 13:35 UTC33
                Err(account_error("locking the Matrix store", error))
a250c7fa43fbatagen2026-09-26 13:35 UTC34
            }
a250c7fa43fbatagen2026-09-26 13:35 UTC35
        }
a250c7fa43fbatagen2026-09-26 13:35 UTC36
    }
a250c7fa43fbatagen2026-09-26 13:35 UTC37
}
a250c7fa43fbatagen2026-09-26 13:35 UTC38
a250c7fa43fbatagen2026-09-26 13:35 UTC39
#[cfg(feature = "indexeddb")]
a250c7fa43fbatagen2026-09-26 13:35 UTC40
#[derive(Debug, thiserror::Error)]
a250c7fa43fbatagen2026-09-26 13:35 UTC41
enum WebLockError {
a250c7fa43fbatagen2026-09-26 13:35 UTC42
    #[error("this global scope has no Web Locks, they need a secure context")]
a250c7fa43fbatagen2026-09-26 13:35 UTC43
    Unavailable,
a250c7fa43fbatagen2026-09-26 13:35 UTC44
    #[error("requesting the Web Lock failed, {0}")]
a250c7fa43fbatagen2026-09-26 13:35 UTC45
    Failed(String),
a250c7fa43fbatagen2026-09-26 13:35 UTC46
}
a250c7fa43fbatagen2026-09-26 13:35 UTC47
a250c7fa43fbatagen2026-09-26 13:35 UTC48
#[cfg(feature = "indexeddb")]
a250c7fa43fbatagen2026-09-26 13:35 UTC49
impl StoreLock {
a250c7fa43fbatagen2026-09-26 13:35 UTC50
    /// `ifAvailable` answers at once. the lock is ours until the promise we hand back settles
a250c7fa43fbatagen2026-09-26 13:35 UTC51
    pub(crate) async fn web(name: &str) -> Result<Self, AccountError> {
a250c7fa43fbatagen2026-09-26 13:35 UTC52
        use futures_util::future::{Either, select};
a250c7fa43fbatagen2026-09-26 13:35 UTC53
        use js_sys::{Function, Object, Promise, Reflect};
a250c7fa43fbatagen2026-09-26 13:35 UTC54
        use tokio::sync::oneshot;
a250c7fa43fbatagen2026-09-26 13:35 UTC55
        use wasm_bindgen::{JsCast, JsValue, closure::Closure};
a250c7fa43fbatagen2026-09-26 13:35 UTC56
a250c7fa43fbatagen2026-09-26 13:35 UTC57
        let failed = |error: JsValue| {
a250c7fa43fbatagen2026-09-26 13:35 UTC58
            account_error(
a250c7fa43fbatagen2026-09-26 13:35 UTC59
                "locking the Matrix store",
a250c7fa43fbatagen2026-09-26 13:35 UTC60
                WebLockError::Failed(format!("{error:?}")),
a250c7fa43fbatagen2026-09-26 13:35 UTC61
            )
a250c7fa43fbatagen2026-09-26 13:35 UTC62
        };
a250c7fa43fbatagen2026-09-26 13:35 UTC63
        let locks = Reflect::get(&js_sys::global(), &JsValue::from_str("navigator"))
a250c7fa43fbatagen2026-09-26 13:35 UTC64
            .and_then(|navigator| Reflect::get(&navigator, &JsValue::from_str("locks")))
a250c7fa43fbatagen2026-09-26 13:35 UTC65
            .map_err(failed)?;
a250c7fa43fbatagen2026-09-26 13:35 UTC66
        if locks.is_undefined() {
a250c7fa43fbatagen2026-09-26 13:35 UTC67
            return Err(account_error(
a250c7fa43fbatagen2026-09-26 13:35 UTC68
                "locking the Matrix store",
a250c7fa43fbatagen2026-09-26 13:35 UTC69
                WebLockError::Unavailable,
a250c7fa43fbatagen2026-09-26 13:35 UTC70
            ));
a250c7fa43fbatagen2026-09-26 13:35 UTC71
        }
a250c7fa43fbatagen2026-09-26 13:35 UTC72
        let request: Function = Reflect::get(&locks, &JsValue::from_str("request"))
a250c7fa43fbatagen2026-09-26 13:35 UTC73
            .map_err(failed)?
a250c7fa43fbatagen2026-09-26 13:35 UTC74
            .dyn_into()
a250c7fa43fbatagen2026-09-26 13:35 UTC75
            .map_err(failed)?;
a250c7fa43fbatagen2026-09-26 13:35 UTC76
        let options = Object::new();
a250c7fa43fbatagen2026-09-26 13:35 UTC77
        Reflect::set(&options, &JsValue::from_str("ifAvailable"), &JsValue::TRUE)
a250c7fa43fbatagen2026-09-26 13:35 UTC78
            .map_err(failed)?;
a250c7fa43fbatagen2026-09-26 13:35 UTC79
a250c7fa43fbatagen2026-09-26 13:35 UTC80
        let (granted, grant) = oneshot::channel();
a250c7fa43fbatagen2026-09-26 13:35 UTC81
        let callback = Closure::once(move |lock: JsValue| -> JsValue {
a250c7fa43fbatagen2026-09-26 13:35 UTC82
            if lock.is_null() {
a250c7fa43fbatagen2026-09-26 13:35 UTC83
                let _ = granted.send(None);
a250c7fa43fbatagen2026-09-26 13:35 UTC84
                return JsValue::UNDEFINED;
a250c7fa43fbatagen2026-09-26 13:35 UTC85
            }
a250c7fa43fbatagen2026-09-26 13:35 UTC86
            let mut release = None;
a250c7fa43fbatagen2026-09-26 13:35 UTC87
            let held = Promise::new(&mut |resolve, _| release = Some(resolve));
a250c7fa43fbatagen2026-09-26 13:35 UTC88
            let _ = granted.send(release);
a250c7fa43fbatagen2026-09-26 13:35 UTC89
            held.into()
a250c7fa43fbatagen2026-09-26 13:35 UTC90
        });
a250c7fa43fbatagen2026-09-26 13:35 UTC91
        let outcome: Promise = request
a250c7fa43fbatagen2026-09-26 13:35 UTC92
            .call3(
a250c7fa43fbatagen2026-09-26 13:35 UTC93
                &locks,
a250c7fa43fbatagen2026-09-26 13:35 UTC94
                &JsValue::from_str(&format!("macltok:{name}")),
a250c7fa43fbatagen2026-09-26 13:35 UTC95
                &options,
a250c7fa43fbatagen2026-09-26 13:35 UTC96
                callback.as_ref(),
a250c7fa43fbatagen2026-09-26 13:35 UTC97
            )
a250c7fa43fbatagen2026-09-26 13:35 UTC98
            .map_err(failed)?
a250c7fa43fbatagen2026-09-26 13:35 UTC99
            .dyn_into()
a250c7fa43fbatagen2026-09-26 13:35 UTC100
            .map_err(failed)?;
a250c7fa43fbatagen2026-09-26 13:35 UTC101
        match select(grant, wasm_bindgen_futures::JsFuture::from(outcome)).await {
a250c7fa43fbatagen2026-09-26 13:35 UTC102
            Either::Left((Ok(Some(release)), _)) => Ok(Self { release }),
a250c7fa43fbatagen2026-09-26 13:35 UTC103
            Either::Left((Ok(None), _)) => Err(AccountError::StoreInUse),
a250c7fa43fbatagen2026-09-26 13:35 UTC104
            Either::Left((Err(_), _)) | Either::Right((Ok(_), _)) => {
a250c7fa43fbatagen2026-09-26 13:35 UTC105
                Err(failed(JsValue::from_str("the lock callback never ran")))
a250c7fa43fbatagen2026-09-26 13:35 UTC106
            }
a250c7fa43fbatagen2026-09-26 13:35 UTC107
            Either::Right((Err(error), _)) => Err(failed(error)),
a250c7fa43fbatagen2026-09-26 13:35 UTC108
        }
a250c7fa43fbatagen2026-09-26 13:35 UTC109
    }
a250c7fa43fbatagen2026-09-26 13:35 UTC110
}
a250c7fa43fbatagen2026-09-26 13:35 UTC111
a250c7fa43fbatagen2026-09-26 13:35 UTC112
#[cfg(feature = "indexeddb")]
a250c7fa43fbatagen2026-09-26 13:35 UTC113
impl Drop for StoreLock {
a250c7fa43fbatagen2026-09-26 13:35 UTC114
    fn drop(&mut self) {
a250c7fa43fbatagen2026-09-26 13:35 UTC115
        let _ = self.release.call0(&wasm_bindgen::JsValue::UNDEFINED);
a250c7fa43fbatagen2026-09-26 13:35 UTC116
    }
a250c7fa43fbatagen2026-09-26 13:35 UTC117
}