workflow_store/
fs.rs

1//!
2//! File system abstraction layer. Currently supporting storage on the filesystem
3//! and the browser domain-associated local storage ([Web Storage API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Storage_API)).
4//!
5//! Storage APIs abstracted:
6//! - Rust std file I/O (fs::xxx)
7//! - NodeJS file I/O (fs::read_file_sync)
8//! - Browser local storage
9//!
10//! By default, all I/O functions will use the name of the file as a key
11//! for localstorage. If you want to manually specify the localstorage key.
12//!
13
14use crate::error::Error;
15use crate::result::Result;
16use cfg_if::cfg_if;
17use js_sys::Reflect;
18use js_sys::Uint8Array;
19use serde::de::DeserializeOwned;
20use serde::Serialize;
21use std::path::{Path, PathBuf};
22use wasm_bindgen::prelude::*;
23use workflow_core::dirs;
24use workflow_core::runtime;
25
26#[wasm_bindgen]
27extern "C" {
28    #[wasm_bindgen(extends = Uint8Array)]
29    #[derive(Clone, Debug)]
30    pub type Buffer;
31
32    #[wasm_bindgen(static_method_of = Buffer, js_name = from)]
33    pub fn from_uint8_array(array: &Uint8Array) -> Buffer;
34
35}
36
37pub fn local_storage() -> web_sys::Storage {
38    web_sys::window()
39        .unwrap()
40        .local_storage()
41        .ok()
42        .flatten()
43        .expect("localStorage is not available")
44}
45
46#[derive(Default)]
47pub struct Options {
48    pub local_storage_key: Option<String>,
49}
50
51impl Options {
52    pub fn with_local_storage_key(key: &str) -> Self {
53        Options {
54            local_storage_key: Some(key.to_string()),
55        }
56    }
57
58    pub fn local_storage_key(&self, filename: &Path) -> String {
59        self.local_storage_key
60            .clone()
61            .unwrap_or(filename.file_name().unwrap().to_str().unwrap().to_string())
62    }
63}
64
65cfg_if! {
66    if #[cfg(target_arch = "wasm32")] {
67        use workflow_core::hex::*;
68        use workflow_wasm::jserror::*;
69        use workflow_node as node;
70        use js_sys::Object;
71        use workflow_chrome::storage::LocalStorage as ChromeStorage;
72
73
74        pub async fn exists_with_options<P : AsRef<Path>>(filename: P, options : Options) -> Result<bool> {
75            if runtime::is_node() || runtime::is_nw() {
76                let filename = filename.as_ref().to_platform_string();
77                Ok(node::fs::exists_sync(filename.as_ref())?)
78            } else {
79                let key_name = options.local_storage_key(filename.as_ref());
80                if runtime::is_chrome_extension(){
81                    Ok(ChromeStorage::get_item(&key_name).await?.is_some())
82                }else{
83                    Ok(local_storage().get_item(&key_name)?.is_some())
84                }
85            }
86        }
87
88        pub fn exists_with_options_sync<P : AsRef<Path>>(filename: P, options : Options) -> Result<bool> {
89            if runtime::is_node() || runtime::is_nw() {
90                let filename = filename.as_ref().to_platform_string();
91                Ok(node::fs::exists_sync(filename.as_ref())?)
92            } else {
93                let key_name = options.local_storage_key(filename.as_ref());
94                if runtime::is_chrome_extension(){
95                    Err(Error::Custom("localStorage api is unavailable, you can use exists_with_options() for chrome.storage.local api.".to_string()))
96                }else{
97                    Ok(local_storage().get_item(&key_name)?.is_some())
98                }
99            }
100        }
101
102        pub async fn read_to_string_with_options<P : AsRef<Path>>(filename: P, options : Options) -> Result<String> {
103            if runtime::is_node() || runtime::is_nw() {
104                let filename = filename.as_ref().to_platform_string();
105                let options = Object::new();
106                Reflect::set(&options, &"encoding".into(), &"utf-8".into())?;
107                let js_value = node::fs::read_file_sync(&filename, options)?;
108                let text = js_value.as_string().ok_or(Error::DataIsNotAString(filename))?;
109                Ok(text)
110            } else {
111                let key_name = options.local_storage_key(filename.as_ref());
112                if runtime::is_chrome_extension(){
113                    if let Some(text) = ChromeStorage::get_item(&key_name).await?{
114                        Ok(text)
115                    }else {
116                        Err(Error::NotFound(filename.as_ref().to_string_lossy().to_string()))
117                    }
118                }else if let Some(text) = local_storage().get_item(&key_name)? {
119                    Ok(text)
120                } else {
121                    Err(Error::NotFound(filename.as_ref().to_string_lossy().to_string()))
122                }
123            }
124        }
125
126        pub fn read_to_string_with_options_sync<P : AsRef<Path>>(filename: P, options : Options) -> Result<String> {
127            if runtime::is_node() || runtime::is_nw() {
128                let filename = filename.as_ref().to_platform_string();
129                let options = Object::new();
130                Reflect::set(&options, &"encoding".into(), &"utf-8".into())?;
131                let js_value = node::fs::read_file_sync(&filename, options)?;
132                let text = js_value.as_string().ok_or(Error::DataIsNotAString(filename))?;
133                Ok(text)
134            } else {
135                let key_name = options.local_storage_key(filename.as_ref());
136                if runtime::is_chrome_extension(){
137                    Err(Error::Custom("localStorage api is unavailable, you can use exists_with_options() for chrome.storage.local api.".to_string()))
138                }else if let Some(text) = local_storage().get_item(&key_name)? {
139                    Ok(text)
140                } else {
141                    Err(Error::NotFound(filename.as_ref().to_string_lossy().to_string()))
142                }
143            }
144        }
145
146        pub async fn read_binary_with_options<P : AsRef<Path>>(filename: P, options : Options) -> Result<Vec<u8>> {
147            if runtime::is_node() || runtime::is_nw() {
148                let filename = filename.as_ref().to_platform_string();
149                let options = Object::new();
150                let buffer = node::fs::read_file_sync(&filename, options)?;
151                let data = buffer.dyn_into::<Uint8Array>()?;
152                Ok(data.to_vec())
153            } else {
154                let key_name = options.local_storage_key(filename.as_ref());
155                let data = if runtime::is_chrome_extension(){
156                    ChromeStorage::get_item(&key_name).await?
157                }else{
158                    local_storage().get_item(&key_name)?
159                };
160
161                if let Some(text) = data{
162                    let data = Vec::<u8>::from_hex(&text)?;
163                    Ok(data)
164                } else {
165                    Err(Error::NotFound(filename.as_ref().to_string_lossy().to_string()))
166                }
167            }
168        }
169
170        pub fn read_binary_with_options_sync<P : AsRef<Path>>(filename: P, options : Options) -> Result<Vec<u8>> {
171            if runtime::is_node() || runtime::is_nw() {
172                let filename = filename.as_ref().to_platform_string();
173                let options = Object::new();
174                let buffer = node::fs::read_file_sync(&filename, options)?;
175                let data = buffer.dyn_into::<Uint8Array>()?;
176                Ok(data.to_vec())
177            } else if runtime::is_chrome_extension(){
178                    Err(Error::Custom("localStorage api is unavailable, you can use read_binary_with_options() for chrome.storage.local api.".to_string()))
179            } else {
180                let key_name = options.local_storage_key(filename.as_ref());
181                if let Some(text) = local_storage().get_item(&key_name)? {
182                    let data = Vec::<u8>::from_hex(&text)?;
183                    Ok(data)
184                } else {
185                    Err(Error::NotFound(filename.as_ref().to_string_lossy().to_string()))
186                }
187            }
188        }
189
190        pub async fn write_string_with_options<P : AsRef<Path>>(filename: P, options: Options, text : &str) -> Result<()> {
191            if runtime::is_node() || runtime::is_nw() {
192                let filename = filename.as_ref().to_platform_string();
193                let options = Object::new();
194                Reflect::set(&options, &"encoding".into(), &"utf-8".into())?;
195                let data = JsValue::from(text);
196                node::fs::write_file_sync(&filename, data, options)?;
197            } else {
198                let key_name = options.local_storage_key(filename.as_ref());
199                if runtime::is_chrome_extension(){
200                    ChromeStorage::set_item(&key_name, text).await?;
201                }else{
202                    local_storage().set_item(&key_name, text)?;
203                }
204            }
205
206            Ok(())
207        }
208
209        pub fn write_string_with_options_sync<P : AsRef<Path>>(filename: P, options: Options, text : &str) -> Result<()> {
210            if runtime::is_node() || runtime::is_nw() {
211                let filename = filename.as_ref().to_platform_string();
212                let options = Object::new();
213                Reflect::set(&options, &"encoding".into(), &"utf-8".into())?;
214                let data = JsValue::from(text);
215                node::fs::write_file_sync(&filename, data, options)?;
216            } else if runtime::is_chrome_extension(){
217                return Err(Error::Custom("localStorage api is unavailable, you can use write_string_with_options() for chrome.storage.local api.".to_string()));
218            }else{
219                let key_name = options.local_storage_key(filename.as_ref());
220                local_storage().set_item(&key_name, text)?;
221            }
222            Ok(())
223        }
224
225        pub async fn write_binary_with_options<P : AsRef<Path>>(filename: P, options: Options, data : &[u8]) -> Result<()> {
226            if runtime::is_node() || runtime::is_nw() {
227                let filename = filename.as_ref().to_platform_string();
228                let options = Object::new();
229                let uint8_array = Uint8Array::from(data);
230                let buffer = Buffer::from_uint8_array(&uint8_array);
231                node::fs::write_file_sync(&filename, buffer.into(), options)?;
232            } else {
233                let key_name = options.local_storage_key(filename.as_ref());
234                if runtime::is_chrome_extension(){
235                    ChromeStorage::set_item(&key_name, data.to_hex().as_str()).await?;
236                }else{
237                    local_storage().set_item(&key_name, data.to_hex().as_str())?;
238                }
239            }
240            Ok(())
241        }
242
243        pub fn write_binary_with_options_sync<P : AsRef<Path>>(filename: P, options: Options, data : &[u8]) -> Result<()> {
244            if runtime::is_node() || runtime::is_nw() {
245                let filename = filename.as_ref().to_platform_string();
246                let options = Object::new();
247                let uint8_array = Uint8Array::from(data);
248                let buffer = Buffer::from_uint8_array(&uint8_array);
249                node::fs::write_file_sync(&filename, buffer.into(), options)?;
250            } else if runtime::is_chrome_extension(){
251                return Err(Error::Custom("localStorage api is unavailable, you can use write_binary_with_options() for chrome.storage.local api.".to_string()));
252            }else{
253                let key_name = options.local_storage_key(filename.as_ref());
254                local_storage().set_item(&key_name, data.to_hex().as_str())?;
255            }
256
257            Ok(())
258        }
259
260        pub async fn remove_with_options<P : AsRef<Path>>(filename: P, options: Options) -> Result<()> {
261            if runtime::is_node() || runtime::is_nw() {
262                let filename = filename.as_ref().to_platform_string();
263                node::fs::unlink_sync(&filename)?;
264            } else {
265                let key_name = options.local_storage_key(filename.as_ref());
266                if runtime::is_chrome_extension(){
267                    ChromeStorage::remove_item(&key_name).await?;
268                }else{
269                    local_storage().remove_item(&key_name)?;
270                }
271            }
272            Ok(())
273        }
274
275        pub fn remove_with_options_sync<P : AsRef<Path>>(filename: P, options: Options) -> Result<()> {
276            if runtime::is_node() || runtime::is_nw() {
277                let filename = filename.as_ref().to_platform_string();
278                node::fs::unlink_sync(&filename)?;
279            } else if runtime::is_chrome_extension(){
280                return Err(Error::Custom("localStorage api is unavailable, you can use remove_with_options() for chrome.storage.local api.".to_string()));
281            }else{
282                let key_name = options.local_storage_key(filename.as_ref());
283                local_storage().remove_item(&key_name)?;
284            }
285            Ok(())
286        }
287
288        pub async fn rename<P : AsRef<Path>>(from: P, to: P) -> Result<()> {
289            if runtime::is_node() || runtime::is_nw() {
290                let from = from.as_ref().to_platform_string();
291                let to = to.as_ref().to_platform_string();
292                node::fs::rename_sync(&from,&to)?;
293                Ok(())
294            } else {
295                Err(Error::NotSupported)
296            }
297        }
298
299        pub fn rename_sync<P : AsRef<Path>>(from: P, to: P) -> Result<()> {
300            if runtime::is_node() || runtime::is_nw() {
301                let from = from.as_ref().to_platform_string();
302                let to = to.as_ref().to_platform_string();
303                node::fs::rename_sync(&from,&to)?;
304                Ok(())
305            } else {
306                Err(Error::NotSupported)
307            }
308        }
309
310        pub async fn create_dir_all<P : AsRef<Path>>(filename: P) -> Result<()> {
311            create_dir_all_sync(filename)
312        }
313
314        pub fn create_dir_all_sync<P : AsRef<Path>>(filename: P) -> Result<()> {
315            if runtime::is_node() || runtime::is_nw() {
316                let options = Object::new();
317                Reflect::set(&options, &JsValue::from("recursive"), &JsValue::from_bool(true))?;
318                let filename = filename.as_ref().to_platform_string();
319                node::fs::mkdir_sync(&filename, options)?;
320            }
321
322            Ok(())
323        }
324
325
326        async fn fetch_metadata(path: &str, entries : &mut [DirEntry]) -> std::result::Result<(),JsErrorData> {
327            for entry in entries.iter_mut() {
328                let path = format!("{}/{}",path, entry.file_name());
329                let metadata = node::fs::stat_sync(&path).unwrap();
330                entry.metadata = metadata.try_into().ok();
331            }
332
333            Ok(())
334        }
335
336        async fn readdir_impl(path: &Path, metadata : bool) -> std::result::Result<Vec<DirEntry>,JsErrorData> {
337            let path_string = path.to_string_lossy().to_string();
338            let files = node::fs::readdir(&path_string).await?;
339            let list = files.dyn_into::<js_sys::Array>().expect("readdir: expecting resulting entries to be an array");
340            let mut entries = list.to_vec().into_iter().map(|s| s.into()).collect::<Vec<DirEntry>>();
341
342            if metadata {
343                fetch_metadata(&path_string, &mut entries).await?; //.map_err(|e|e.to_string())?;
344            }
345
346            Ok(entries)
347        }
348
349        pub async fn readdir<P>(path: P, metadata : bool) -> Result<Vec<DirEntry>>
350        where P : AsRef<Path> + Send + 'static
351        {
352            // this is a hack to bypass JsFuture being !Send
353            // until I had a chance to setup a proper infrastructure
354            // to relay JS promises within Send contexts.
355            // we want to use async version of readdir to ensure
356            // our executor is not blocked.
357
358            use workflow_core::sendable::Sendable;
359            use workflow_core::task::dispatch;
360            use workflow_core::channel::oneshot;
361
362            if runtime::is_node() || runtime::is_nw() {
363
364                let (sender, receiver) = oneshot();
365                dispatch(async move {
366                    let path = path.as_ref();
367                    let result = readdir_impl(path, metadata).await;
368                    sender.send(Sendable(result)).await.unwrap();
369                });
370
371                Ok(receiver.recv().await.unwrap().unwrap()?)
372            } else if runtime::is_chrome_extension(){
373                let entries = ChromeStorage::keys().await?
374                    .into_iter()
375                    .map(DirEntry::from)
376                    .collect::<Vec<_>>();
377                Ok(entries)
378            } else{
379                let local_storage = local_storage();
380
381                let mut entries = vec![];
382                let length = local_storage.length().unwrap();
383                for i in 0..length {
384                    let key = local_storage.key(i)?;
385                    if let Some(key) = key {
386                        entries.push(DirEntry::from(key));
387                    }
388                }
389                Ok(entries)
390            }
391        }
392
393        // -----------------------------------------
394
395    } else {  // cfg_if - native platforms
396
397        // -----------------------------------------
398
399        pub async fn exists_with_options<P : AsRef<Path>>(filename: P, _options: Options) -> Result<bool> {
400            Ok(filename.as_ref().exists())
401        }
402
403        pub fn exists_with_options_sync<P : AsRef<Path>>(filename: P, _options: Options) -> Result<bool> {
404            Ok(filename.as_ref().exists())
405        }
406
407        pub async fn read_to_string_with_options<P : AsRef<Path>>(filename: P, _options: Options) -> Result<String> {
408            Ok(std::fs::read_to_string(filename)?)
409        }
410
411        pub fn read_to_string_with_options_sync<P : AsRef<Path>>(filename: P, _options: Options) -> Result<String> {
412            Ok(std::fs::read_to_string(filename)?)
413        }
414
415        pub async fn read_binary_with_options<P : AsRef<Path>>(filename: P, _options: Options) -> Result<Vec<u8>> {
416            Ok(std::fs::read(filename)?)
417        }
418
419        pub fn read_binary_with_options_sync<P : AsRef<Path>>(filename: P, _options: Options) -> Result<Vec<u8>> {
420            Ok(std::fs::read(filename)?)
421        }
422
423        pub async fn write_string_with_options<P : AsRef<Path>>(filename: P, _options: Options, text : &str) -> Result<()> {
424            Ok(std::fs::write(filename, text)?)
425        }
426
427        pub fn write_string_with_options_sync<P : AsRef<Path>>(filename: P, _options: Options, text : &str) -> Result<()> {
428            Ok(std::fs::write(filename, text)?)
429        }
430
431        pub async fn write_binary_with_options<P : AsRef<Path>>(filename: P, _options: Options, data : &[u8]) -> Result<()> {
432            Ok(std::fs::write(filename, data)?)
433        }
434
435        pub fn write_binary_with_options_sync<P : AsRef<Path>>(filename: P, _options: Options, data : &[u8]) -> Result<()> {
436            Ok(std::fs::write(filename, data)?)
437        }
438
439        pub async fn remove_with_options<P : AsRef<Path>>(filename: P, _options: Options) -> Result<()> {
440            std::fs::remove_file(filename)?;
441            Ok(())
442        }
443
444        pub fn remove_with_options_sync<P : AsRef<Path>>(filename: P, _options: Options) -> Result<()> {
445            std::fs::remove_file(filename)?;
446            Ok(())
447        }
448
449        pub async fn rename<P : AsRef<Path>>(from: P, to: P) -> Result<()> {
450            std::fs::rename(from,to)?;
451            Ok(())
452        }
453
454        pub fn rename_sync<P : AsRef<Path>>(from: P, to: P) -> Result<()> {
455            std::fs::rename(from,to)?;
456            Ok(())
457        }
458
459        pub async fn create_dir_all<P : AsRef<Path>>(dir: P) -> Result<()> {
460            std::fs::create_dir_all(dir)?;
461            Ok(())
462        }
463
464        pub fn create_dir_all_sync<P : AsRef<Path>>(dir: P) -> Result<()> {
465            std::fs::create_dir_all(dir)?;
466            Ok(())
467        }
468
469        pub async fn readdir<P : AsRef<Path>>(path: P, metadata : bool) -> Result<Vec<DirEntry>> {
470            let entries = std::fs::read_dir(path.as_ref())?;
471
472            if metadata {
473                let mut list = Vec::new();
474                for de in entries {
475                    let de = de?;
476                    let metadata = std::fs::metadata(de.path())?;
477                    let dir_entry = DirEntry::from((de,metadata));
478                    list.push(dir_entry);
479                }
480                Ok(list)
481            } else {
482                Ok(entries.map(|r|r.map(|e|e.into())).collect::<std::result::Result<Vec<_>,_>>()?)
483            }
484        }
485
486    }
487
488}
489
490#[derive(Clone, Debug)]
491pub struct Metadata {
492    created: Option<u64>,
493    modified: Option<u64>,
494    accessed: Option<u64>,
495    len: Option<u64>,
496}
497
498impl Metadata {
499    pub fn created(&self) -> Option<u64> {
500        self.created
501    }
502
503    pub fn modified(&self) -> Option<u64> {
504        self.modified
505    }
506
507    pub fn accessed(&self) -> Option<u64> {
508        self.accessed
509    }
510
511    pub fn len(&self) -> Option<u64> {
512        self.len
513    }
514
515    pub fn is_empty(&self) -> Option<bool> {
516        self.len.map(|len| len == 0)
517    }
518}
519
520impl From<std::fs::Metadata> for Metadata {
521    fn from(metadata: std::fs::Metadata) -> Self {
522        Metadata {
523            created: metadata.created().ok().map(|created| {
524                created
525                    .duration_since(std::time::UNIX_EPOCH)
526                    .unwrap()
527                    .as_secs()
528            }),
529            modified: metadata.modified().ok().map(|modified| {
530                modified
531                    .duration_since(std::time::UNIX_EPOCH)
532                    .unwrap()
533                    .as_secs()
534            }),
535            accessed: metadata.accessed().ok().map(|accessed| {
536                accessed
537                    .duration_since(std::time::UNIX_EPOCH)
538                    .unwrap()
539                    .as_secs()
540            }),
541            len: Some(metadata.len()),
542        }
543    }
544}
545
546impl TryFrom<JsValue> for Metadata {
547    type Error = Error;
548    fn try_from(metadata: JsValue) -> Result<Self> {
549        if metadata.is_undefined() {
550            return Err(Error::Metadata);
551        }
552        let created = Reflect::get(&metadata, &"birthtimeMs".into())
553            .ok()
554            .map(|v| (v.as_f64().unwrap() / 1000.0) as u64);
555        let modified = Reflect::get(&metadata, &"mtimeMs".into())
556            .ok()
557            .map(|v| (v.as_f64().unwrap() / 1000.0) as u64);
558        let accessed = Reflect::get(&metadata, &"atimeMs".into())
559            .ok()
560            .map(|v| (v.as_f64().unwrap() / 1000.0) as u64);
561        let len = Reflect::get(&metadata, &"size".into())
562            .ok()
563            .map(|v| v.as_f64().unwrap() as u64);
564
565        Ok(Metadata {
566            created,
567            modified,
568            accessed,
569            len,
570        })
571    }
572}
573
574#[derive(Clone, Debug)]
575pub struct DirEntry {
576    file_name: String,
577    metadata: Option<Metadata>,
578}
579
580impl DirEntry {
581    pub fn file_name(&self) -> &str {
582        &self.file_name
583    }
584
585    pub fn metadata(&self) -> Option<&Metadata> {
586        self.metadata.as_ref()
587    }
588}
589
590impl From<std::fs::DirEntry> for DirEntry {
591    fn from(de: std::fs::DirEntry) -> Self {
592        DirEntry {
593            file_name: de.file_name().to_string_lossy().to_string(),
594            metadata: None,
595        }
596    }
597}
598
599impl From<(std::fs::DirEntry, std::fs::Metadata)> for DirEntry {
600    fn from((de, metadata): (std::fs::DirEntry, std::fs::Metadata)) -> Self {
601        DirEntry {
602            file_name: de.file_name().to_string_lossy().to_string(),
603            metadata: Some(metadata.into()),
604        }
605    }
606}
607
608impl From<JsValue> for DirEntry {
609    fn from(de: JsValue) -> Self {
610        DirEntry {
611            file_name: de.as_string().unwrap(),
612            metadata: None,
613        }
614    }
615}
616
617impl From<String> for DirEntry {
618    fn from(s: String) -> Self {
619        DirEntry {
620            file_name: s,
621            metadata: None,
622        }
623    }
624}
625
626/// Check if a file exists
627pub async fn exists<P: AsRef<Path>>(filename: P) -> Result<bool> {
628    exists_with_options(filename, Options::default()).await
629}
630
631/// Check if a file exists
632pub fn exists_sync<P: AsRef<Path>>(filename: P) -> Result<bool> {
633    exists_with_options_sync(filename, Options::default())
634}
635
636/// Read file contents to a string. If using within the web browser
637/// environment, a local storage key with the name of the file
638/// will be used.
639pub async fn read_to_string(filename: &Path) -> Result<String> {
640    read_to_string_with_options(filename, Options::default()).await
641}
642
643/// Read file contents to a string. If using within the web browser
644/// environment, a local storage key with the name of the file
645/// will be used.
646pub fn read_to_string_sync(filename: &Path) -> Result<String> {
647    read_to_string_with_options_sync(filename, Options::default())
648}
649
650/// Read binary file contents to a `Vec<u8>`. If using within the web browser
651/// environment, a local storage key with the name of the file
652/// will be used and the data is assumed to be hex-encoded.
653pub async fn read(filename: &Path) -> Result<Vec<u8>> {
654    read_binary_with_options(filename, Options::default()).await
655}
656
657/// Read binary file contents to a `Vec<u8>`. If using within the web browser
658/// environment, a local storage key with the name of the file
659/// will be used and the data is assumed to be hex-encoded.
660pub fn read_sync(filename: &Path) -> Result<Vec<u8>> {
661    read_binary_with_options_sync(filename, Options::default())
662}
663
664/// Write a string to a text file. If using within the web browser
665/// environment, a local storage key with the name of the file
666/// will be used.
667pub async fn write_string(filename: &Path, text: &str) -> Result<()> {
668    write_string_with_options(filename, Options::default(), text).await
669}
670
671/// Write a string to a text file. If using within the web browser
672/// environment, a local storage key with the name of the file
673/// will be used.
674pub fn write_string_sync(filename: &Path, text: &str) -> Result<()> {
675    write_string_with_options_sync(filename, Options::default(), text)
676}
677
678/// Write a `Vec<u8>` to a binary file. If using within the web browser
679/// environment, a local storage key with the name of the file
680/// will be used and the data will be hex-encoded.
681pub async fn write(filename: &Path, data: &[u8]) -> Result<()> {
682    write_binary_with_options(filename, Options::default(), data).await
683}
684
685/// Write a `Vec<u8>` to a binary file. If using within the web browser
686/// environment, a local storage key with the name of the file
687/// will be used and the data will be hex-encoded.
688pub async fn write_sync(filename: &Path, data: &[u8]) -> Result<()> {
689    write_binary_with_options_sync(filename, Options::default(), data)
690}
691
692/// Remove the file at the given path. If using within the web browser
693/// environment, a local storage key with the name of the file
694/// will be removed.
695pub async fn remove(filename: &Path) -> Result<()> {
696    remove_with_options(filename, Options::default()).await
697}
698
699/// Remove the file at the given path. If using within the web browser
700/// environment, a local storage key with the name of the file
701/// will be removed.
702pub fn remove_sync(filename: &Path) -> Result<()> {
703    remove_with_options_sync(filename, Options::default())
704}
705
706/// Read text file and deserialized it using `serde-json`.
707pub async fn read_json_with_options<T>(filename: &Path, options: Options) -> Result<T>
708where
709    T: DeserializeOwned,
710{
711    let text = read_to_string_with_options(filename, options).await?;
712    Ok(serde_json::from_str(&text)?)
713}
714
715/// Read text file and deserialized it using `serde-json`.
716pub fn read_json_with_options_sync<T>(filename: &Path, options: Options) -> Result<T>
717where
718    T: DeserializeOwned,
719{
720    let text = read_to_string_with_options_sync(filename, options)?;
721    Ok(serde_json::from_str(&text)?)
722}
723
724/// Write a serializable value to a text file using `serde-json`.
725pub async fn write_json_with_options<T>(filename: &Path, options: Options, value: &T) -> Result<()>
726where
727    T: Serialize,
728{
729    let json = serde_json::to_string(value)?;
730    write_string_with_options(filename, options, &json).await?;
731    Ok(())
732}
733
734/// Write a serializable value to a text file using `serde-json`.
735pub fn write_json_with_options_sync<T>(filename: &Path, options: Options, value: &T) -> Result<()>
736where
737    T: Serialize,
738{
739    let json = serde_json::to_string(value)?;
740    write_string_with_options_sync(filename, options, &json)?;
741    Ok(())
742}
743
744/// Read text file and deserialized it using `serde-json`.
745pub async fn read_json<T>(filename: &Path) -> Result<T>
746where
747    T: DeserializeOwned,
748{
749    read_json_with_options(filename, Options::default()).await
750}
751
752/// Read text file and deserialized it using `serde-json`.
753pub fn read_json_sync<T>(filename: &Path) -> Result<T>
754where
755    T: DeserializeOwned,
756{
757    read_json_with_options_sync(filename, Options::default())
758}
759
760/// Write a serializable value to a text file using `serde-json`.
761pub async fn write_json<T>(filename: &Path, value: &T) -> Result<()>
762where
763    T: Serialize,
764{
765    write_json_with_options(filename, Options::default(), value).await
766}
767
768/// Write a serializable value to a text file using `serde-json`.
769pub fn write_json_sync<T>(filename: &Path, value: &T) -> Result<()>
770where
771    T: Serialize,
772{
773    write_json_with_options_sync(filename, Options::default(), value)
774}
775
776/// Parses the supplied path resolving `~/` to the home directory.
777pub fn resolve_path(path: &str) -> Result<PathBuf> {
778    if let Some(_stripped) = path.strip_prefix("~/") {
779        if runtime::is_web() {
780            Ok(PathBuf::from(path))
781        } else if runtime::is_node() || runtime::is_nw() {
782            Ok(dirs::home_dir()
783                .ok_or_else(|| Error::HomeDir(path.to_string()))?
784                .join(_stripped))
785        } else {
786            cfg_if! {
787                if #[cfg(target_arch = "wasm32")] {
788                    Ok(PathBuf::from(path))
789                } else {
790                    Ok(home::home_dir().ok_or_else(||Error::HomeDir(path.to_string()))?.join(_stripped))
791                }
792            }
793        }
794    } else {
795        Ok(PathBuf::from(path))
796    }
797}
798
799/// Normalizes path, dereferencing relative references `.` and `..`
800/// and converting path separators to current platform separators.
801/// (detects platform natively or via NodeJS if operating in WASM32
802/// environment)
803pub trait NormalizePath {
804    fn normalize(&self) -> Result<PathBuf>;
805}
806
807impl NormalizePath for Path {
808    fn normalize(&self) -> Result<PathBuf> {
809        normalize(self)
810    }
811}
812
813impl NormalizePath for PathBuf {
814    fn normalize(&self) -> Result<PathBuf> {
815        normalize(self)
816    }
817}
818
819/// Convert path separators to unix or to current platform.
820/// Detects platform natively or using NodeJS if operating
821/// under WASM32 environment. Since in WASM32 paths default
822/// to forward slashes, when running WASM32 in Windows paths
823/// needs to be converted back and forth for various path-related
824/// functions to work.
825pub trait ToPlatform {
826    fn to_platform(&self) -> PathBuf;
827    fn to_platform_string(&self) -> String;
828    fn to_unix(&self) -> PathBuf;
829}
830
831impl ToPlatform for Path {
832    fn to_platform(&self) -> PathBuf {
833        if runtime::is_windows() {
834            convert_path_separators(self, "/", "\\")
835        } else {
836            self.to_path_buf()
837        }
838    }
839
840    fn to_platform_string(&self) -> String {
841        self.to_platform().to_string_lossy().to_string()
842    }
843
844    fn to_unix(&self) -> PathBuf {
845        if runtime::is_windows() {
846            convert_path_separators(self, "\\", "/")
847        } else {
848            self.to_path_buf()
849        }
850    }
851}
852
853/// Normalizes path, dereferencing relative references `.` and `..`
854/// and converting path separators to current platform separators.
855/// (detects platform natively or via NodeJS if operating in WASM32
856/// environment). Uses [`ToPlatform`] to perform path conversion.
857pub fn normalize<P>(path: P) -> Result<PathBuf>
858where
859    P: AsRef<Path>,
860{
861    let path = path.as_ref().to_unix();
862    let mut result = PathBuf::new();
863
864    for component in path.components() {
865        if let Some(c) = component.as_os_str().to_str() {
866            if c == "." {
867                continue;
868            } else if c == ".." {
869                result.pop();
870            } else {
871                result.push(c);
872            }
873        } else {
874            return Err(Error::InvalidPath(path.to_string_lossy().to_string()));
875        }
876    }
877
878    Ok(result.to_platform())
879}
880
881fn convert_path_separators<P>(path: P, from: &str, to: &str) -> PathBuf
882where
883    P: AsRef<Path>,
884{
885    let path = path.as_ref().to_string_lossy();
886    let path = path.replace(from, to);
887    PathBuf::from(path)
888}