Skip to main content

ferroui_base/platform/storage/
fallback_storage_provider.rs

1use super::{
2    FilePickerOpenOptions, FilePickerSaveOptions, FolderPickerOpenOptions, IStorageBookmarkFile,
3    IStorageBookmarkFolder, IStorageFile, IStorageFolder, IStorageProvider, OpenFilePickerResult,
4    SaveFilePickerResult, WellKnownFolder,
5};
6use crate::input::LocalBoxFuture;
7use crate::utilities::Uri;
8use std::cell::{Cell, RefCell};
9use std::rc::Rc;
10
11/// Creates a storage provider, or resolves to `None` when the provider is
12/// not available.
13pub type StorageProviderFactory = Rc<dyn Fn() -> LocalBoxFuture<Option<Rc<dyn IStorageProvider>>>>;
14
15struct State {
16    factories: RefCell<Rc<[StorageProviderFactory]>>,
17    providers: RefCell<Vec<Rc<dyn IStorageProvider>>>,
18    next_provider_factory: Cell<usize>,
19}
20
21impl State {
22    /// The provider at `index` of the sequence of available providers: the
23    /// ones created so far, followed by the ones the remaining factories
24    /// create. `None` once the factories are exhausted.
25    async fn get_provider(&self, index: usize) -> Option<Rc<dyn IStorageProvider>> {
26        loop {
27            if let Some(p) = self.providers.borrow().get(index) {
28                return Some(p.clone());
29            }
30
31            let factory = {
32                let factories = self.factories.borrow();
33                factories.get(self.next_provider_factory.get())?.clone()
34            };
35            let p = factory().await;
36            self.next_provider_factory.set(self.next_provider_factory.get() + 1);
37            if let Some(p) = p {
38                self.providers.borrow_mut().push(p);
39            }
40        }
41    }
42
43    async fn get_for(&self, filter: fn(&dyn IStorageProvider) -> bool) -> std::io::Result<Rc<dyn IStorageProvider>> {
44        let mut index = 0;
45        while let Some(p) = self.get_provider(index).await {
46            if filter(&*p) {
47                return Ok(p);
48            }
49            index += 1;
50        }
51        Err(std::io::Error::other("Unable to select a suitable storage provider"))
52    }
53
54    async fn first_not_null<TResult, F>(&self, cb: F) -> Option<TResult>
55    where
56        F: Fn(&dyn IStorageProvider) -> LocalBoxFuture<Option<TResult>>,
57    {
58        let mut index = 0;
59        while let Some(p) = self.get_provider(index).await {
60            let res = cb(&*p).await;
61            if res.is_some() {
62                return res;
63            }
64            index += 1;
65        }
66        None
67    }
68}
69
70/// A storage provider that forwards each request to the first of a list of
71/// lazily created providers that can serve it.
72///
73/// This is an implementation detail of the platform backends.
74pub struct FallbackStorageProvider {
75    state: Rc<State>,
76}
77
78impl FallbackStorageProvider {
79    /// Creates a provider over the given factories, which are tried in
80    /// order and at most once each.
81    pub fn new(factories: Vec<StorageProviderFactory>) -> Self {
82        Self {
83            state: Rc::new(State {
84                factories: RefCell::new(factories.into()),
85                providers: RefCell::new(Vec::new()),
86                next_provider_factory: Cell::new(0),
87            }),
88        }
89    }
90
91    /// Replaces the factories and forgets the providers created so far.
92    pub fn reset(&self, factories: Vec<StorageProviderFactory>) {
93        *self.state.factories.borrow_mut() = factories.into();
94        self.state.providers.borrow_mut().clear();
95        self.state.next_provider_factory.set(0);
96    }
97}
98
99impl IStorageProvider for FallbackStorageProvider {
100    // Those should _really_ have been asynchronous,
101    // but this class is expected to fall back to the managed implementation anyway
102    fn can_open(&self) -> bool {
103        true
104    }
105
106    fn can_save(&self) -> bool {
107        true
108    }
109
110    fn can_pick_folder(&self) -> bool {
111        true
112    }
113
114    fn open_file_picker_async(
115        &self,
116        options: FilePickerOpenOptions,
117    ) -> LocalBoxFuture<std::io::Result<Vec<Rc<dyn IStorageFile>>>> {
118        let state = self.state.clone();
119        Box::pin(async move { state.get_for(|p| p.can_open()).await?.open_file_picker_async(options).await })
120    }
121
122    fn open_file_picker_with_result_async(
123        &self,
124        options: FilePickerOpenOptions,
125    ) -> LocalBoxFuture<std::io::Result<OpenFilePickerResult>> {
126        let state = self.state.clone();
127        Box::pin(
128            async move { state.get_for(|p| p.can_open()).await?.open_file_picker_with_result_async(options).await },
129        )
130    }
131
132    fn save_file_picker_async(
133        &self,
134        options: FilePickerSaveOptions,
135    ) -> LocalBoxFuture<std::io::Result<Option<Rc<dyn IStorageFile>>>> {
136        let state = self.state.clone();
137        Box::pin(async move { state.get_for(|p| p.can_save()).await?.save_file_picker_async(options).await })
138    }
139
140    fn save_file_picker_with_result_async(
141        &self,
142        options: FilePickerSaveOptions,
143    ) -> LocalBoxFuture<std::io::Result<SaveFilePickerResult>> {
144        let state = self.state.clone();
145        Box::pin(
146            async move { state.get_for(|p| p.can_save()).await?.save_file_picker_with_result_async(options).await },
147        )
148    }
149
150    fn open_folder_picker_async(
151        &self,
152        options: FolderPickerOpenOptions,
153    ) -> LocalBoxFuture<std::io::Result<Vec<Rc<dyn IStorageFolder>>>> {
154        let state = self.state.clone();
155        Box::pin(async move { state.get_for(|p| p.can_pick_folder()).await?.open_folder_picker_async(options).await })
156    }
157
158    fn open_file_bookmark_async(&self, bookmark: &str) -> LocalBoxFuture<Option<Rc<dyn IStorageBookmarkFile>>> {
159        let state = self.state.clone();
160        let bookmark = bookmark.to_owned();
161        Box::pin(async move { state.first_not_null(|p| p.open_file_bookmark_async(&bookmark)).await })
162    }
163
164    fn open_folder_bookmark_async(&self, bookmark: &str) -> LocalBoxFuture<Option<Rc<dyn IStorageBookmarkFolder>>> {
165        let state = self.state.clone();
166        let bookmark = bookmark.to_owned();
167        Box::pin(async move { state.first_not_null(|p| p.open_folder_bookmark_async(&bookmark)).await })
168    }
169
170    fn try_get_file_from_path_async(&self, file_path: &Uri) -> LocalBoxFuture<Option<Rc<dyn IStorageFile>>> {
171        let state = self.state.clone();
172        let file_path = file_path.clone();
173        Box::pin(async move { state.first_not_null(|p| p.try_get_file_from_path_async(&file_path)).await })
174    }
175
176    fn try_get_folder_from_path_async(&self, folder_path: &Uri) -> LocalBoxFuture<Option<Rc<dyn IStorageFolder>>> {
177        let state = self.state.clone();
178        let folder_path = folder_path.clone();
179        Box::pin(async move { state.first_not_null(|p| p.try_get_folder_from_path_async(&folder_path)).await })
180    }
181
182    fn try_get_well_known_folder_async(
183        &self,
184        well_known_folder: WellKnownFolder,
185    ) -> LocalBoxFuture<Option<Rc<dyn IStorageFolder>>> {
186        let state = self.state.clone();
187        Box::pin(async move { state.first_not_null(|p| p.try_get_well_known_folder_async(well_known_folder)).await })
188    }
189}