etebase/
fs_cache.rs

1// SPDX-FileCopyrightText: © 2020 Etebase Authors
2// SPDX-License-Identifier: LGPL-2.1-only
3
4use super::{
5    error::Result,
6    http_client::Client,
7    service::{Account, Collection, CollectionManager, Item, ItemManager},
8};
9use remove_dir_all::remove_dir_all;
10use std::fs;
11use std::path::{Path, PathBuf};
12
13/*
14File structure:
15cache_dir/
16    user1/ <-- the name of the user
17        account <-- the account cache
18        stoken <-- the stokens of the collection fetch
19        cols/
20            UID1/ <-- The uid of the first col
21                ...
22            UID2/ <-- The uid of the second col
23                col <-- the col itself
24                stoken <-- the stoken of the items fetch
25                items/
26                    item_uid1 <-- the item with uid 1
27                    item_uid2
28                    ...
29 */
30pub struct FileSystemCache {
31    user_dir: PathBuf,
32    cols_dir: PathBuf,
33}
34
35impl FileSystemCache {
36    /// Initialize a file system cache object
37    ///
38    /// # Arguments:
39    /// * `path` - the path to a directory to store cache in
40    /// * `username` - username of the user to cache data for
41    pub fn new(path: &Path, username: &str) -> Result<Self> {
42        let mut user_dir = PathBuf::from(path);
43        user_dir.push(username);
44        let cols_dir = user_dir.join("cols");
45        fs::create_dir_all(&cols_dir)?;
46
47        Ok(Self { user_dir, cols_dir })
48    }
49
50    fn get_collection_items_dir(&self, col_uid: &str) -> PathBuf {
51        self.cols_dir.join(col_uid).join("items")
52    }
53
54    /// Clear all cache for the user
55    pub fn clear_user_cache(&self) -> Result<()> {
56        let user_dir = &self.user_dir;
57        remove_dir_all(user_dir)?;
58        Ok(())
59    }
60
61    /// Save the user account
62    ///
63    /// Load it later using [`load_account`](Self::load_account)
64    ///
65    /// # Arguments:
66    /// * `etebase` - the account to save
67    /// * `encryption_key` - used to encrypt the saved account string to enhance security
68    pub fn save_account(&self, etebase: &Account, encryption_key: Option<&[u8]>) -> Result<()> {
69        let account_file = self.user_dir.join("account");
70        let account = etebase.save(encryption_key)?;
71        fs::write(account_file, account)?;
72        Ok(())
73    }
74
75    /// Load the [`Account`] object from cache
76    ///
77    /// # Arguments:
78    /// * `client` - the already setup [`Client`] object
79    /// * `encryption_key` - the same encryption key passed to [`save_account`](Self::save_account)
80    ///    while saving the account
81    pub fn load_account(&self, client: &Client, encryption_key: Option<&[u8]>) -> Result<Account> {
82        let account_file = self.user_dir.join("account");
83        let data = fs::read_to_string(account_file)?;
84        Account::restore(client.clone(), &data, encryption_key)
85    }
86
87    /// Save the collection list sync token
88    ///
89    /// # Arguments:
90    /// * `stoken` - the sync token to be saved
91    pub fn save_stoken(&self, stoken: &str) -> Result<()> {
92        let stoken_file = self.user_dir.join("stoken");
93        fs::write(stoken_file, stoken)?;
94        Ok(())
95    }
96
97    /// Load the collection list sync token from cache
98    pub fn load_stoken(&self) -> Result<Option<String>> {
99        let stoken_file = self.user_dir.join("stoken");
100        let ret = fs::read_to_string(stoken_file);
101        match ret {
102            Err(_) => Ok(None),
103            Ok(ret) => Ok(Some(ret)),
104        }
105    }
106
107    /// Save a collection's sync token
108    ///
109    /// # Arguments:
110    /// * `col_uid` - the UID of the collection
111    /// * `stoken` - the sync token to be saved
112    pub fn collection_save_stoken(&self, col_uid: &str, stoken: &str) -> Result<()> {
113        let stoken_file = self.cols_dir.join(col_uid).join("stoken");
114        fs::write(stoken_file, stoken)?;
115        Ok(())
116    }
117
118    /// Load the sync token for a collection
119    ///
120    /// # Arguments:
121    /// * `col_uid` - the UID of the collection
122    pub fn collection_load_stoken(&self, col_uid: &str) -> Result<Option<String>> {
123        let stoken_file = self.cols_dir.join(col_uid).join("stoken");
124        let ret = fs::read_to_string(stoken_file);
125        match ret {
126            Err(_) => Ok(None),
127            Ok(ret) => Ok(Some(ret)),
128        }
129    }
130
131    /// Raw data of all cached collections as a list response
132    pub fn collection_list_raw(&self) -> Result<ListRawCacheResponse> {
133        let ret = fs::read_dir(&self.cols_dir)?;
134        Ok(ListRawCacheResponse {
135            inner_iter: ret,
136            is_collection: true,
137        })
138    }
139
140    /// Load a collection from cache
141    ///
142    /// # Arguments:
143    /// * `col_mgr` - collection manager for the account
144    /// * `col_uid` - the UID of the collection
145    pub fn collection(&self, col_mgr: &CollectionManager, col_uid: &str) -> Result<Collection> {
146        let col_file = self.cols_dir.join(col_uid).join("col");
147        let content = fs::read(col_file)?;
148        col_mgr.cache_load(&content)
149    }
150
151    /// Save a collection to cache
152    ///
153    /// # Arguments:
154    /// * `col_mgr` - collection manager for the account
155    /// * `collection` - the collection to be saved
156    pub fn collection_set(
157        &self,
158        col_mgr: &CollectionManager,
159        collection: &Collection,
160    ) -> Result<()> {
161        let mut col_file = self.cols_dir.join(collection.uid());
162        fs::create_dir_all(&col_file)?;
163        col_file.push("col");
164
165        let content = col_mgr.cache_save(collection)?;
166        fs::write(col_file, content)?;
167
168        let items_dir = self.get_collection_items_dir(collection.uid());
169        fs::create_dir_all(items_dir)?;
170
171        Ok(())
172    }
173
174    /// Save a collection and its content to cache
175    ///
176    /// # Arguments:
177    /// * `col_mgr` - collection manager for the account
178    /// * `collection` - the collection to be saved
179    pub fn collection_set_with_content(
180        &self,
181        col_mgr: &CollectionManager,
182        collection: &Collection,
183    ) -> Result<()> {
184        let mut col_file = self.cols_dir.join(collection.uid());
185        fs::create_dir_all(&col_file)?;
186        col_file.push("col");
187
188        let content = col_mgr.cache_save_with_content(collection)?;
189        fs::write(col_file, content)?;
190
191        let items_dir = self.get_collection_items_dir(collection.uid());
192        fs::create_dir_all(items_dir)?;
193
194        Ok(())
195    }
196
197    /// Remove a collection from cache
198    ///
199    /// # Arguments:
200    /// * `_col_mgr` - collection manager for the account
201    /// * `col_uid` - the UID of the collection to be unset
202    pub fn collection_unset(&self, _col_mgr: &CollectionManager, col_uid: &str) -> Result<()> {
203        let col_dir = self.cols_dir.join(col_uid);
204        remove_dir_all(col_dir)?;
205        Ok(())
206    }
207
208    /// Return a list of cached items in a collection
209    ///
210    /// # Arguments:
211    /// * `col_uid` - the UID of the parent collection
212    pub fn item_list_raw(&self, col_uid: &str) -> Result<ListRawCacheResponse> {
213        let items_dir = self.get_collection_items_dir(col_uid);
214        let ret = fs::read_dir(items_dir)?;
215        Ok(ListRawCacheResponse {
216            inner_iter: ret,
217            is_collection: false,
218        })
219    }
220
221    /// Load an item from cache
222    ///
223    /// # Arguments:
224    /// * `item_mgr` - item manager for the parent collection
225    /// * `col_uid` - the UID of the parent collection
226    /// * `item_uid` - the UID of the item
227    pub fn item(&self, item_mgr: &ItemManager, col_uid: &str, item_uid: &str) -> Result<Item> {
228        let item_file = self.get_collection_items_dir(col_uid).join(item_uid);
229        let content = fs::read(item_file)?;
230        item_mgr.cache_load(&content)
231    }
232
233    /// Save an item to cache
234    ///
235    /// # Arguments:
236    /// * `item_mgr` - item manager for the parent collection
237    /// * `col_uid` - the UID of the parent collection
238    /// * `item` - the item to be saved
239    pub fn item_set(&self, item_mgr: &ItemManager, col_uid: &str, item: &Item) -> Result<()> {
240        let item_file = self.get_collection_items_dir(col_uid).join(item.uid());
241        let content = item_mgr.cache_save(item)?;
242        fs::write(item_file, content)?;
243        Ok(())
244    }
245
246    /// Save an item and its content to cache
247    ///
248    /// # Arguments:
249    /// * `item_mgr` - item manager for the parent collection
250    /// * `col_uid` - the UID of the parent collection
251    /// * `item` - the item to be saved
252    pub fn item_set_with_content(
253        &self,
254        item_mgr: &ItemManager,
255        col_uid: &str,
256        item: &Item,
257    ) -> Result<()> {
258        let item_file = self.get_collection_items_dir(col_uid).join(item.uid());
259        let content = item_mgr.cache_save_with_content(item)?;
260        fs::write(item_file, content)?;
261        Ok(())
262    }
263
264    /// Remove an item from cache
265    ///
266    /// # Arguments:
267    /// * `_item_mgr` - item manager for the parent collection
268    /// * `col_uid` - the UID of the parent collection
269    /// * `item_uid` - the UID of the item
270    pub fn item_unset(&self, _item_mgr: &ItemManager, col_uid: &str, item_uid: &str) -> Result<()> {
271        let item_file = self.get_collection_items_dir(col_uid).join(item_uid);
272        fs::remove_file(item_file)?;
273        Ok(())
274    }
275}
276
277/// An iterator used for cache list responses
278pub struct ListRawCacheResponse {
279    inner_iter: fs::ReadDir,
280    is_collection: bool,
281}
282
283impl Iterator for ListRawCacheResponse {
284    type Item = Result<Vec<u8>>;
285
286    fn next(&mut self) -> Option<Self::Item> {
287        self.inner_iter.next().map(|x| -> Result<Vec<u8>> {
288            let mut col_file = x?.path();
289            if self.is_collection {
290                col_file.push("col");
291            }
292            Ok(fs::read(col_file)?)
293        })
294    }
295}