Skip to main content

coven_storage/
memory_storage_builder.rs

1use super::*;
2use coven_foundation::clock::FixedClock;
3
4/// Shared storage fixture choices; production providers do not use these defaults.
5pub struct MemoryStorageBuilder {
6    config: StorageConfig,
7    clock: ClockRef,
8    time_resolution: Duration,
9    single_limit: u64,
10    part_size: usize,
11    independent_parts: bool,
12    whole_part_reads: bool,
13    listing_page: usize,
14}
15
16impl MemoryStorageBuilder {
17    pub(super) fn new() -> Self {
18        Self {
19            config: location(CloudProvider::S3),
20            clock: Arc::new(FixedClock::new(std::time::UNIX_EPOCH)),
21            time_resolution: Duration::from_nanos(1),
22            single_limit: 4 * 1024 * 1024,
23            part_size: 64 * 1024,
24            independent_parts: false,
25            whole_part_reads: false,
26            listing_page: usize::MAX,
27        }
28    }
29
30    /// Use the canonical test location for this provider.
31    pub fn provider(mut self, provider: CloudProvider) -> Self {
32        self.config = location(provider);
33        self
34    }
35    /// Choose an exact location when comparing or reconnecting locations.
36    pub fn location(mut self, config: StorageConfig) -> Self {
37        self.config = config;
38        self
39    }
40    /// Control publication times and OAuth expiry with this clock.
41    pub fn clock(mut self, clock: ClockRef) -> Self {
42        self.clock = clock;
43        self
44    }
45    /// Choose the reported provider tick. Set the fixture clock on this grid.
46    pub fn time_resolution(mut self, resolution: Duration) -> Self {
47        self.time_resolution = resolution;
48        self
49    }
50    /// Choose the single-request threshold and resumable part size.
51    pub fn transfer_limits(mut self, single_limit: u64, part_size: usize) -> Self {
52        self.single_limit = single_limit;
53        self.part_size = part_size;
54        self
55    }
56    /// Keep each session's parts by index, whatever order they arrive in,
57    /// as an S3 multipart upload does, so they may go together (§17.9);
58    /// otherwise the provider takes them in order.
59    pub fn independent_parts(mut self) -> Self {
60        self.independent_parts = true;
61        self
62    }
63    /// Move every part a read reaches whole, as CloudKit's reads do
64    /// (Appendix E.1): an object of up to the part size is one part, a
65    /// larger one a part per part size from its start, the last shorter. A
66    /// body moves each part once it delivers a byte of it, as CloudKit
67    /// fetches a part only once its stream reaches it.
68    /// [`MemoryStorage::moved_bytes`] counts what each read moved.
69    pub fn reads_whole_parts(mut self) -> Self {
70        self.whole_part_reads = true;
71        self
72    }
73    /// Return listings in native pages of this many entries, each page its own
74    /// request in the trace, as a provider's continuation requests are (§21.1).
75    pub fn listing_page(mut self, entries: std::num::NonZeroUsize) -> Self {
76        self.listing_page = entries.get();
77        self
78    }
79    /// Validate the choices and construct an isolated storage backend.
80    pub fn build(self) -> Result<MemoryStorage, StorageError> {
81        let Self {
82            config,
83            clock,
84            time_resolution,
85            single_limit,
86            part_size,
87            independent_parts,
88            whole_part_reads,
89            listing_page,
90        } = self;
91        config.validate()?;
92        if time_resolution.is_zero() {
93            return Err(StorageFailure::InvalidConfiguration.with_source("zero provider time unit"));
94        }
95        if single_limit == 0 || part_size == 0 {
96            return Err(StorageFailure::InvalidPart.into());
97        }
98        Ok(MemoryStorage::from_memory(Arc::new(MemoryProvider {
99            config,
100            online: Arc::new(std::sync::atomic::AtomicBool::new(true)),
101            single_limit,
102            part_size,
103            independent_parts,
104            whole_part_reads,
105            listing_page,
106            requests: Arc::new(tokio::sync::watch::channel(0).0),
107            request_trace: Arc::new(std::sync::Mutex::new(Vec::new())),
108            active_requests: Arc::new(std::sync::atomic::AtomicUsize::new(0)),
109            peak_requests: Arc::new(std::sync::atomic::AtomicUsize::new(0)),
110            clock,
111            time_resolution,
112            tokens: Arc::new(Mutex::new(None)),
113            s3_key: Arc::new(Mutex::new(None)),
114            account: Account::Owner,
115            state: Arc::new(Mutex::new(MemoryState {
116                location_marker: None,
117                folder_name: "Household".into(),
118                objects: super::memory_objects::MemoryObjects::new(),
119                uploads: BTreeMap::new(),
120                next: 1,
121                accounts: BTreeMap::new(),
122                retained_access: BTreeMap::new(),
123                faults: Faults::none(),
124                creation_barrier: None,
125                creation_gate: None,
126                part_gate: None,
127                read_gate: None,
128                ranges: Vec::new(),
129                reads: Vec::new(),
130                moved: BTreeMap::new(),
131                received_buffers: Vec::new(),
132                sent_bytes: 0,
133                largest_part: 0,
134                held_listing: None,
135                held_read: None,
136                held_body: None,
137                held_creation: None,
138                held_request: None,
139                held_access: None,
140                rejected_tokens: Default::default(),
141            })),
142        })))
143    }
144}
145
146fn location(provider: CloudProvider) -> StorageConfig {
147    match provider {
148        CloudProvider::S3 => StorageConfig::S3 {
149            bucket: "test".into(),
150            region: "us-east-1".into(),
151            endpoint: None,
152            profile: None,
153        },
154        CloudProvider::Dropbox => StorageConfig::Dropbox {
155            account_label: None,
156            location: crate::DropboxLocation::SharedFolder { namespace_id: "store".into() },
157            folder_label: None,
158        },
159        CloudProvider::OneDrive => StorageConfig::OneDrive {
160            account_label: None,
161            drive_id: "drive".into(),
162            folder_id: "store".into(),
163            folder_label: None,
164        },
165        CloudProvider::CloudKit => StorageConfig::CloudKit {
166            container: "container".into(),
167            owner: "owner".into(),
168            zone: "store".into(),
169        },
170    }
171}