coven/read_handle.rs
1//! The read-only application surface caches files under a shared deletion guard.
2
3use crate::*;
4use coven_database::{Database, ReadOnly};
5
6/// A handle that reads synced rows and maintains its local file cache (§4, E1).
7/// Its shared store lock prevents deletion until all its connections close.
8/// Its SQL may not read `coven_sync_status`: it can't tell whether the writer
9/// that last wrote the row still runs (E5).
10///
11/// ```compile_fail
12/// async fn no_writes(handle: &coven::CovenReadHandle) {
13/// handle.write(|_| Ok::<_, coven::CovenError>(())).await;
14/// }
15/// ```
16/// ```compile_fail
17/// fn no_live_query(handle: &coven::CovenReadHandle) {
18/// handle.subscribe(|_| Ok::<_, coven::CovenError>(()));
19/// }
20/// ```
21#[derive(Clone)]
22pub struct CovenReadHandle {
23 database: Database<ReadOnly>,
24 files: coven_sync::Files<ReadOnly>,
25}
26
27impl CovenReadHandle {
28 pub(crate) fn new(database: Database<ReadOnly>, files: coven_sync::Files<ReadOnly>) -> Self {
29 Self { database, files }
30 }
31 /// The store's current name from this device's committed state (E9).
32 pub async fn store_name(&self) -> CovenResult<String> {
33 self.database.store_name().await.map_err(CovenError::from)
34 }
35
36 /// A read of one consistent snapshot, run when awaited.
37 pub fn read<F, R, E>(&self, read: F) -> Read<'_, F, ReadOnly>
38 where
39 F: FnOnce(SqlReadContext<'_>) -> Result<R, E> + Send + 'static,
40 R: Send + 'static,
41 E: From<DbError> + task::CarriesBug + Send + 'static,
42 {
43 self.database.read(read)
44 }
45 /// The SQLite settings this handle's connections have in effect (§4).
46 pub async fn sqlite_settings(&self) -> Result<SqliteSettingsInEffect, DbError> {
47 self.database.sqlite_settings().await
48 }
49 /// The file a row references, by the id the row holds.
50 pub async fn file_ref(&self, file: FileId) -> Result<FileRef, DbError> {
51 self.database.file_ref(file).await
52 }
53 /// The path, size and modification time recorded for a file's original.
54 pub async fn user_file(&self, file: FileId) -> Result<Option<UserFile>, DbError> {
55 self.database.user_file(file).await
56 }
57 /// Reads a whole file. A stored file is checked against its content hash.
58 pub async fn read_file(&self, file: &FileRef) -> Result<Vec<u8>, FileReadError> {
59 let stream = self.open_file_stream(file).await?;
60 stream.read_at(0, stream.plaintext_size()).await
61 }
62 /// Opens a file for reading ranges (§17.7), as its record stands when it
63 /// opens; keep the stream for as long as the file is read. Its shared
64 /// store lock prevents deletion until it and its I/O finish.
65 pub async fn open_file_stream(
66 &self,
67 file: &FileRef,
68 ) -> Result<FileStream<ReadOnly>, FileReadError> {
69 self.files.open_file_stream(file).await
70 }
71 /// Closes read and cache connections on all clones and releases their
72 /// storage client and shared store lock, while the app still holds the
73 /// handle. From its first poll every clone refuses new calls with
74 /// `StoreClosed`; the closing itself runs as its own task, so dropping
75 /// this future doesn't stop it (E1, E15). File streams retain their own
76 /// locks.
77 pub async fn close(&self) -> Result<(), DbError> {
78 self.database.begin_close();
79 let handle = self.clone();
80 let bugs = self.database.bugs().clone();
81 let closing = async move {
82 handle.files.close().await;
83 handle.database.close().await
84 };
85 crate::task::spawn_completing("close the read-only store", bugs, closing).await?
86 }
87}