1use super::{
8 FilesystemCallContext, FilesystemDescriptor, FilesystemEntry, FilesystemEntryPage,
9 FilesystemFuture, FilesystemMutation, FilesystemMutationContext, FilesystemPageRequest,
10 IFilesystem, SynchronousFileProvider,
11};
12use crate::file::{CopyOptions, DeleteOptions, FileError, MkdirOptions, MoveOptions, WriteOptions};
13use std::cell::Cell;
14use std::rc::Rc;
15
16#[cfg(any(not(target_arch = "wasm32"), target_os = "wasi"))]
17impl SynchronousFileProvider for crate::file::NativeFileProvider {}
18impl SynchronousFileProvider for crate::file::MemoryFileProvider {}
19impl SynchronousFileProvider for crate::file::UnsupportedFileProvider {}
20
21pub(super) struct LegacyFilesystem<P> {
22 provider: P,
23 descriptor: FilesystemDescriptor,
24 closed: Rc<Cell<bool>>,
25}
26
27impl<P> LegacyFilesystem<P> {
28 pub fn new(provider: P, descriptor: FilesystemDescriptor) -> Self {
29 Self {
30 provider,
31 descriptor,
32 closed: Rc::new(Cell::new(false)),
33 }
34 }
35
36 fn before_call(&self, context: &FilesystemCallContext) -> Result<(), FileError> {
37 if self.closed.get() {
38 return Err(FileError::Io("filesystem provider is closed".into()));
39 }
40 context.check()
41 }
42
43 fn require_no_revision_expectation(
44 mutation: &FilesystemMutationContext,
45 ) -> Result<(), FileError> {
46 if mutation.required() {
47 Err(FileError::Unsupported)
48 } else {
49 Ok(())
50 }
51 }
52}
53
54impl<P: SynchronousFileProvider> IFilesystem for LegacyFilesystem<P> {
55 fn descriptor(&self) -> FilesystemDescriptor {
56 self.descriptor.clone()
57 }
58
59 fn stat<'a>(
60 &'a self,
61 context: FilesystemCallContext,
62 path: String,
63 ) -> FilesystemFuture<'a, FilesystemEntry> {
64 Box::pin(async move {
65 self.before_call(&context)?;
66 self.provider.stat_entry(&path).map(FilesystemEntry::from)
67 })
68 }
69
70 fn read<'a>(
71 &'a self,
72 context: FilesystemCallContext,
73 path: String,
74 ) -> FilesystemFuture<'a, Vec<u8>> {
75 Box::pin(async move {
76 self.before_call(&context)?;
77 self.provider.read_bytes(&path)
78 })
79 }
80
81 fn write<'a>(
82 &'a self,
83 context: FilesystemCallContext,
84 path: String,
85 bytes: Vec<u8>,
86 options: WriteOptions,
87 mutation: FilesystemMutationContext,
88 ) -> FilesystemFuture<'a, FilesystemMutation> {
89 Box::pin(async move {
90 self.before_call(&context)?;
91 Self::require_no_revision_expectation(&mutation)?;
92 self.provider
93 .write_bytes(&path, bytes, options)
94 .map(FilesystemMutation::path)
95 })
96 }
97
98 fn entries_page<'a>(
99 &'a self,
100 context: FilesystemCallContext,
101 path: String,
102 request: FilesystemPageRequest,
103 ) -> FilesystemFuture<'a, FilesystemEntryPage> {
104 Box::pin(async move {
105 self.before_call(&context)?;
106 if request.limit == 0 {
107 return Err(FileError::InvalidPath(
108 "filesystem page limit must be positive".into(),
109 ));
110 }
111 let start = request
112 .token
113 .as_deref()
114 .unwrap_or("0")
115 .parse::<usize>()
116 .map_err(|_| FileError::InvalidPath("invalid filesystem page token".into()))?;
117 let mut entries = self.provider.entries_values(&path)?;
118 entries.sort_by(|left, right| left.path.cmp(&right.path));
119 if start > entries.len() {
120 return Err(FileError::InvalidPath(
121 "filesystem page token is outside the directory".into(),
122 ));
123 }
124 let end = start.saturating_add(request.limit).min(entries.len());
125 let next_token = (end < entries.len()).then(|| end.to_string());
126 Ok(FilesystemEntryPage {
127 entries: entries[start..end]
128 .iter()
129 .cloned()
130 .map(FilesystemEntry::from)
131 .collect(),
132 next_token,
133 })
134 })
135 }
136
137 fn mkdir<'a>(
138 &'a self,
139 context: FilesystemCallContext,
140 path: String,
141 options: MkdirOptions,
142 mutation: FilesystemMutationContext,
143 ) -> FilesystemFuture<'a, FilesystemMutation> {
144 Box::pin(async move {
145 self.before_call(&context)?;
146 Self::require_no_revision_expectation(&mutation)?;
147 self.provider
148 .mkdir_path(&path, options)
149 .map(FilesystemMutation::path)
150 })
151 }
152
153 fn delete<'a>(
154 &'a self,
155 context: FilesystemCallContext,
156 path: String,
157 options: DeleteOptions,
158 mutation: FilesystemMutationContext,
159 ) -> FilesystemFuture<'a, FilesystemMutation> {
160 Box::pin(async move {
161 self.before_call(&context)?;
162 Self::require_no_revision_expectation(&mutation)?;
163 self.provider
164 .delete_path(&path, options)
165 .map(FilesystemMutation::path)
166 })
167 }
168
169 fn copy<'a>(
170 &'a self,
171 context: FilesystemCallContext,
172 source: String,
173 target: String,
174 options: CopyOptions,
175 mutation: FilesystemMutationContext,
176 ) -> FilesystemFuture<'a, FilesystemMutation> {
177 Box::pin(async move {
178 self.before_call(&context)?;
179 Self::require_no_revision_expectation(&mutation)?;
180 self.provider
181 .copy_path(&source, &target, options)
182 .map(FilesystemMutation::path)
183 })
184 }
185
186 fn move_entry<'a>(
187 &'a self,
188 context: FilesystemCallContext,
189 source: String,
190 target: String,
191 options: MoveOptions,
192 mutation: FilesystemMutationContext,
193 ) -> FilesystemFuture<'a, FilesystemMutation> {
194 Box::pin(async move {
195 self.before_call(&context)?;
196 Self::require_no_revision_expectation(&mutation)?;
197 self.provider
198 .move_path(&source, &target, options)
199 .map(FilesystemMutation::path)
200 })
201 }
202
203 fn close<'a>(&'a self, context: FilesystemCallContext) -> FilesystemFuture<'a, ()> {
204 Box::pin(async move {
205 context.check()?;
206 self.closed.set(true);
207 Ok(())
208 })
209 }
210}
211
212#[cfg(test)]
213mod tests {
214 use super::*;
215
216 #[test]
217 fn close_marks_the_legacy_adapter_closed() {
218 let filesystem = LegacyFilesystem::new(
219 crate::file::MemoryFileProvider::new("/"),
220 FilesystemDescriptor::legacy("memory", "memory fixture"),
221 );
222 assert!(!filesystem.closed.get());
223 filesystem.closed.set(true);
224 assert!(matches!(
225 filesystem.before_call(&FilesystemCallContext::default()),
226 Err(FileError::Io(_))
227 ));
228 }
229}