Skip to main content

coven_storage/
provider_ops.rs

1//! Provider calls beneath the common storage contract.
2
3use crate::*;
4use coven_foundation::id_source::{DeviceId, StoreId};
5
6/// Native calls used by [`StorageConnection`]. Consumers use [`Storage`];
7/// implementations handle provider protocols after the connection checks paths,
8/// request sizes and recorded sessions. Construction receives validated settings.
9#[async_trait::async_trait]
10pub trait ProviderOps: Send + Sync {
11    /// This provider's location.
12    fn config(&self) -> StorageConfig;
13    /// Largest complete body accepted by `create` or `replace`.
14    fn single_request_limit(&self) -> u64;
15    /// The size of the parts a ranged read moves whole
16    /// ([`crate::Storage::read_part_size`]); `None` when a range moves only
17    /// its own bytes.
18    fn read_part_size(&self) -> Option<std::num::NonZeroU64> {
19        None
20    }
21    /// Create D10's location marker at the location's root with the
22    /// provider's create-once request. Only first setup at a location that
23    /// holds one store calls this operation. Anything it stages sits in
24    /// `device`'s reserved folder (D10).
25    async fn create_location_marker(
26        &self,
27        _store: coven_foundation::id_source::StoreId,
28        _device: DeviceId,
29    ) -> Result<(), StorageError> {
30        Err(StorageFailure::ProviderUnavailable.into())
31    }
32    /// Read the complete location marker, bounded to D10's UUID byte count.
33    /// Only a location that holds one store calls this operation. A marker
34    /// read that finds nothing confirms the location is still there first.
35    async fn read_location_marker(&self) -> Result<Vec<u8>, StorageError> {
36        Err(StorageFailure::ProviderUnavailable.into())
37    }
38    /// The store's folder's name and web address as this connection's
39    /// account names them now (§20.2). Only a Dropbox shared folder or a
40    /// OneDrive folder has them; nothing asks another location.
41    async fn folder_label(&self) -> Result<FolderLabel, StorageError> {
42        Err(StorageFailure::ProviderUnavailable.into())
43    }
44    /// Signed-in account; coven shares nothing on S3, so it has none.
45    async fn account(&self) -> Result<String, StorageError> {
46        Err(StorageFailure::InvalidConfiguration.with_source("provider has no sharing account"))
47    }
48    /// Replace tokens after credential custody commits them.
49    async fn set_oauth_tokens(&self, _tokens: OAuthTokens) -> Result<(), StorageError> {
50        Err(StorageFailure::InvalidConfiguration.with_source("provider does not use OAuth"))
51    }
52    /// Replace the S3 signing key after credential custody commits it.
53    async fn set_s3_credentials(&self, _credentials: S3Credentials) -> Result<(), StorageError> {
54        Err(StorageFailure::InvalidConfiguration.with_source("provider does not use S3 keys"))
55    }
56    /// Create in one request, telling `sent` the body's bytes as the
57    /// provider's client hands them over. A temporary it stages sits in
58    /// `device`'s reserved folder (D10).
59    /// Returns the created object's metadata when the reply carries it.
60    async fn create(
61        &self,
62        publication: &PublicationRequest,
63        path: &ObjectPath,
64        bytes: &[u8],
65        sent: &ByteProgress,
66        device: DeviceId,
67    ) -> Result<Option<StoredObject>, StorageError>;
68    /// Replace the observed object in one atomic conditional publication. A
69    /// temporary it stages sits in `device`'s reserved folder (D10).
70    async fn replace(
71        &self,
72        publication: &PublicationRequest,
73        previous: &StoredObject,
74        bytes: &[u8],
75        device: DeviceId,
76    ) -> Result<(), StorageError>;
77    /// Exact native lookup; only a confirmed missing object returns None.
78    async fn status(&self, path: &ObjectPath) -> Result<Option<StoredObject>, StorageError>;
79    /// Read complete bytes or a range, retaining metadata for those exact bytes.
80    /// The connection hears the stream's reads itself; a client that fetches
81    /// bytes before the stream yields them tells `received` how far through
82    /// the object they reach as they arrive, counted from the object's start.
83    async fn stream_object(
84        &self,
85        path: &ObjectPath,
86        range: Option<ByteRange>,
87        received: &ByteProgress,
88    ) -> Result<crate::ObjectStream<'_>, StorageError>;
89    /// Collect a bounded response through the streaming implementation. Large
90    /// writes and snapshots consume `stream_object` directly. This convenience
91    /// reader holds at most 32 MiB, regardless of provider metadata.
92    async fn read_object(
93        &self,
94        path: &ObjectPath,
95        range: Option<ByteRange>,
96    ) -> Result<ObjectRead, StorageError> {
97        self.stream_object(path, range, &ByteProgress::unread())
98            .await?
99            .collect(crate::object_stream::BUFFERED_READ_LIMIT)
100            .await
101    }
102    /// Consume native pages into the connection's checked listing.
103    async fn list(&self, listing: &mut ObjectListing) -> Result<(), StorageError>;
104
105    /// Delete, accepting an already absent object.
106    async fn delete(&self, path: &ObjectPath) -> Result<(), StorageError>;
107    /// Grant account access, or return S3's console instructions.
108    async fn grant_access(&self, account: &str) -> Result<AccessGrant, StorageError>;
109    /// Accept native sharing material. The connection checks location and reads
110    /// the exact floor status afterwards to verify readability.
111    async fn join(&self, invitation: &StorageInvitation) -> Result<(), StorageError> {
112        if !matches!(invitation.acceptance, crate::invitation::InvitationAcceptance::Granted) {
113            return Err(
114                StorageFailure::InvalidConfiguration.with_source("provider acceptance required")
115            );
116        }
117        Ok(())
118    }
119    /// Undo [`Self::join`]'s acceptance so the invite stays usable
120    /// ([`crate::Storage::leave`]). Access an invite grants without native
121    /// acceptance has nothing to undo.
122    async fn leave(&self, invitation: &StorageInvitation) -> Result<(), StorageError> {
123        if !matches!(invitation.acceptance, crate::invitation::InvitationAcceptance::Granted) {
124            return Err(
125                StorageFailure::InvalidConfiguration.with_source("provider acceptance required")
126            );
127        }
128        Ok(())
129    }
130    /// Remove exclusive grants and return grants that still reach the account.
131    async fn revoke_access(&self, account: &str) -> Result<Vec<RetainedAccess>, StorageError>;
132    /// Begin a nonempty upload with its original publication condition, for
133    /// `device`, whose reserved folder holds its temporaries (D10).
134    async fn begin_upload(
135        &self,
136        publication: &PublicationRequest,
137        target: &UploadTarget,
138        total: u64,
139        device: DeviceId,
140    ) -> Result<UploadSession, StorageError>;
141    /// Recover native progress of a validated, incomplete session.
142    async fn resume_upload(
143        &self,
144        publication: &PublicationRequest,
145        session: &mut UploadSession,
146    ) -> Result<(), StorageError>;
147    /// Send a checked part ending at `end`. The connection validates its length,
148    /// boundary and session; implementations enforce the native protocol.
149    /// `sent` already counts from the part's start in the object.
150    async fn upload_part(
151        &self,
152        publication: &PublicationRequest,
153        session: &mut UploadSession,
154        bytes: &[u8],
155        end: u64,
156        sent: &ByteProgress,
157    ) -> Result<(), StorageError>;
158    /// Send part `offset..` of a session whose parts are independent
159    /// ([`UploadSession::parts_independent`]), leaving the session as it
160    /// is: the provider's confirmation of the part comes back, for the
161    /// caller to add. Several may be in flight at once. The connection
162    /// checks the part is a whole one the session lacks; `sent` already
163    /// counts from the part's start in the object. A provider whose parts
164    /// go in order has none to send this way.
165    async fn send_independent_part(
166        &self,
167        _publication: &PublicationRequest,
168        _session: &UploadSession,
169        _offset: u64,
170        _bytes: &[u8],
171        _sent: &ByteProgress,
172    ) -> Result<ConfirmedPart, StorageError> {
173        Err(StorageFailure::InvalidPart.into())
174    }
175    /// Publish an incomplete session, checking native progress where required.
176    async fn finish_upload(
177        &self,
178        publication: &PublicationRequest,
179        session: &mut UploadSession,
180    ) -> Result<(), StorageError>;
181    /// Abandon an incomplete session without deleting published bytes.
182    async fn abort_upload(
183        &self,
184        publication: &PublicationRequest,
185        session: &UploadSession,
186    ) -> Result<(), StorageError>;
187    /// The uploads `device` left in `store` that this sign-in can see: the
188    /// sessions and temporaries in its reserved folder (D10), and, where the
189    /// provider puts a session at its destination, those under the device's
190    /// file and snapshot folders. Every page of each listing is followed.
191    async fn device_uploads(
192        &self,
193        store: StoreId,
194        device: DeviceId,
195    ) -> Result<Vec<LeftUpload>, StorageError>;
196    /// The unfinished sessions at slots 1 to `through` of `store`, whoever
197    /// began them: where a claim's session sits at its slot's path, one at
198    /// a slot this device has applied can never publish, since create-once
199    /// refuses it there. A provider that puts no session at a slot's path
200    /// returns none.
201    async fn settled_claim_uploads(
202        &self,
203        store: StoreId,
204        through: u64,
205    ) -> Result<Vec<LeftUpload>, StorageError>;
206    /// Whether this provider dates an unfinished floor session's start so
207    /// that one begun before the current floor's publication can be told
208    /// apart (Appendix E.10). Asking sends nothing.
209    fn dates_floor_sessions(&self) -> bool {
210        false
211    }
212    /// The unfinished sessions at the floor's path begun before `floor`, the
213    /// current floor, was published: each observed an older revision, so
214    /// none can publish. Asked only where [`Self::dates_floor_sessions`].
215    async fn superseded_floor_uploads(
216        &self,
217        _floor: &StoredObject,
218    ) -> Result<Vec<LeftUpload>, StorageError> {
219        Ok(Vec::new())
220    }
221    /// What S3 detection run `run` left in `store`'s `.coven-check` prefix
222    /// (§20.7): its unfinished multipart uploads, then its probes. Only S3
223    /// detects, so elsewhere no run left anything.
224    async fn detection_leftovers(
225        &self,
226        _store: StoreId,
227        _run: uuid::Uuid,
228    ) -> Result<Vec<LeftUpload>, StorageError> {
229        Ok(Vec::new())
230    }
231    /// End one upload another device left, as this provider listed it: abort
232    /// the session, or delete what it staged. One already ended succeeds,
233    /// and a published object is never touched.
234    async fn end_left_upload(&self, upload: &LeftUpload) -> Result<(), StorageError>;
235}