1use crate::error::Error;
15use crate::result::Result;
16use cfg_if::cfg_if;
17use js_sys::Reflect;
18use js_sys::Uint8Array;
19use serde::Serialize;
20use serde::de::DeserializeOwned;
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)]
31 #[derive(Clone, Debug)]
32 pub type Buffer;
33
34 #[wasm_bindgen(static_method_of = Buffer, js_name = from)]
36 pub fn from_uint8_array(array: &Uint8Array) -> Buffer;
37
38}
39
40pub fn local_storage() -> web_sys::Storage {
43 web_sys::window()
44 .unwrap()
45 .local_storage()
46 .ok()
47 .flatten()
48 .expect("localStorage is not available")
49}
50
51#[derive(Default)]
54pub struct Options {
55 pub local_storage_key: Option<String>,
57}
58
59impl Options {
60 pub fn with_local_storage_key(key: &str) -> Self {
62 Options {
63 local_storage_key: Some(key.to_string()),
64 }
65 }
66
67 pub fn local_storage_key(&self, filename: &Path) -> String {
70 self.local_storage_key
71 .clone()
72 .unwrap_or(filename.file_name().unwrap().to_str().unwrap().to_string())
73 }
74}
75
76cfg_if! {
77 if #[cfg(target_arch = "wasm32")] {
78 use workflow_core::hex::*;
79 use workflow_wasm::jserror::*;
80 use workflow_node as node;
81 use js_sys::Object;
82 use workflow_chrome::storage::LocalStorage as ChromeStorage;
83
84
85 pub async fn exists_with_options<P : AsRef<Path>>(filename: P, options : Options) -> Result<bool> {
86 if runtime::is_node() || runtime::is_nw() {
87 let filename = filename.as_ref().to_platform_string();
88 Ok(node::fs::exists_sync(filename.as_ref())?)
89 } else {
90 let key_name = options.local_storage_key(filename.as_ref());
91 if runtime::is_chrome_extension(){
92 Ok(ChromeStorage::get_item(&key_name).await?.is_some())
93 }else{
94 Ok(local_storage().get_item(&key_name)?.is_some())
95 }
96 }
97 }
98
99 pub fn exists_with_options_sync<P : AsRef<Path>>(filename: P, options : Options) -> Result<bool> {
100 if runtime::is_node() || runtime::is_nw() {
101 let filename = filename.as_ref().to_platform_string();
102 Ok(node::fs::exists_sync(filename.as_ref())?)
103 } else {
104 let key_name = options.local_storage_key(filename.as_ref());
105 if runtime::is_chrome_extension(){
106 Err(Error::Custom("localStorage api is unavailable, you can use exists_with_options() for chrome.storage.local api.".to_string()))
107 }else{
108 Ok(local_storage().get_item(&key_name)?.is_some())
109 }
110 }
111 }
112
113 pub async fn read_to_string_with_options<P : AsRef<Path>>(filename: P, options : Options) -> Result<String> {
114 if runtime::is_node() || runtime::is_nw() {
115 let filename = filename.as_ref().to_platform_string();
116 let options = Object::new();
117 Reflect::set(&options, &"encoding".into(), &"utf-8".into())?;
118 let js_value = node::fs::read_file_sync(&filename, options)?;
119 let text = js_value.as_string().ok_or(Error::DataIsNotAString(filename))?;
120 Ok(text)
121 } else {
122 let key_name = options.local_storage_key(filename.as_ref());
123 if runtime::is_chrome_extension(){
124 if let Some(text) = ChromeStorage::get_item(&key_name).await?{
125 Ok(text)
126 }else {
127 Err(Error::NotFound(filename.as_ref().to_string_lossy().to_string()))
128 }
129 }else if let Some(text) = local_storage().get_item(&key_name)? {
130 Ok(text)
131 } else {
132 Err(Error::NotFound(filename.as_ref().to_string_lossy().to_string()))
133 }
134 }
135 }
136
137 pub fn read_to_string_with_options_sync<P : AsRef<Path>>(filename: P, options : Options) -> Result<String> {
138 if runtime::is_node() || runtime::is_nw() {
139 let filename = filename.as_ref().to_platform_string();
140 let options = Object::new();
141 Reflect::set(&options, &"encoding".into(), &"utf-8".into())?;
142 let js_value = node::fs::read_file_sync(&filename, options)?;
143 let text = js_value.as_string().ok_or(Error::DataIsNotAString(filename))?;
144 Ok(text)
145 } else {
146 let key_name = options.local_storage_key(filename.as_ref());
147 if runtime::is_chrome_extension(){
148 Err(Error::Custom("localStorage api is unavailable, you can use exists_with_options() for chrome.storage.local api.".to_string()))
149 }else if let Some(text) = local_storage().get_item(&key_name)? {
150 Ok(text)
151 } else {
152 Err(Error::NotFound(filename.as_ref().to_string_lossy().to_string()))
153 }
154 }
155 }
156
157 pub async fn read_binary_with_options<P : AsRef<Path>>(filename: P, options : Options) -> Result<Vec<u8>> {
158 if runtime::is_node() || runtime::is_nw() {
159 let filename = filename.as_ref().to_platform_string();
160 let options = Object::new();
161 let buffer = node::fs::read_file_sync(&filename, options)?;
162 let data = buffer.dyn_into::<Uint8Array>()?;
163 Ok(data.to_vec())
164 } else {
165 let key_name = options.local_storage_key(filename.as_ref());
166 let data = if runtime::is_chrome_extension(){
167 ChromeStorage::get_item(&key_name).await?
168 }else{
169 local_storage().get_item(&key_name)?
170 };
171
172 if let Some(text) = data{
173 let data = Vec::<u8>::from_hex(&text)?;
174 Ok(data)
175 } else {
176 Err(Error::NotFound(filename.as_ref().to_string_lossy().to_string()))
177 }
178 }
179 }
180
181 pub fn read_binary_with_options_sync<P : AsRef<Path>>(filename: P, options : Options) -> Result<Vec<u8>> {
182 if runtime::is_node() || runtime::is_nw() {
183 let filename = filename.as_ref().to_platform_string();
184 let options = Object::new();
185 let buffer = node::fs::read_file_sync(&filename, options)?;
186 let data = buffer.dyn_into::<Uint8Array>()?;
187 Ok(data.to_vec())
188 } else if runtime::is_chrome_extension(){
189 Err(Error::Custom("localStorage api is unavailable, you can use read_binary_with_options() for chrome.storage.local api.".to_string()))
190 } else {
191 let key_name = options.local_storage_key(filename.as_ref());
192 if let Some(text) = local_storage().get_item(&key_name)? {
193 let data = Vec::<u8>::from_hex(&text)?;
194 Ok(data)
195 } else {
196 Err(Error::NotFound(filename.as_ref().to_string_lossy().to_string()))
197 }
198 }
199 }
200
201 pub async fn write_string_with_options<P : AsRef<Path>>(filename: P, options: Options, text : &str) -> Result<()> {
202 if runtime::is_node() || runtime::is_nw() {
203 let filename = filename.as_ref().to_platform_string();
204 let options = Object::new();
205 Reflect::set(&options, &"encoding".into(), &"utf-8".into())?;
206 let data = JsValue::from(text);
207 node::fs::write_file_sync(&filename, data, options)?;
208 } else {
209 let key_name = options.local_storage_key(filename.as_ref());
210 if runtime::is_chrome_extension(){
211 ChromeStorage::set_item(&key_name, text).await?;
212 }else{
213 local_storage().set_item(&key_name, text)?;
214 }
215 }
216
217 Ok(())
218 }
219
220 pub fn write_string_with_options_sync<P : AsRef<Path>>(filename: P, options: Options, text : &str) -> Result<()> {
221 if runtime::is_node() || runtime::is_nw() {
222 let filename = filename.as_ref().to_platform_string();
223 let options = Object::new();
224 Reflect::set(&options, &"encoding".into(), &"utf-8".into())?;
225 let data = JsValue::from(text);
226 node::fs::write_file_sync(&filename, data, options)?;
227 } else if runtime::is_chrome_extension(){
228 return Err(Error::Custom("localStorage api is unavailable, you can use write_string_with_options() for chrome.storage.local api.".to_string()));
229 }else{
230 let key_name = options.local_storage_key(filename.as_ref());
231 local_storage().set_item(&key_name, text)?;
232 }
233 Ok(())
234 }
235
236 pub async fn write_binary_with_options<P : AsRef<Path>>(filename: P, options: Options, data : &[u8]) -> Result<()> {
237 if runtime::is_node() || runtime::is_nw() {
238 let filename = filename.as_ref().to_platform_string();
239 let options = Object::new();
240 let uint8_array = Uint8Array::from(data);
241 let buffer = Buffer::from_uint8_array(&uint8_array);
242 node::fs::write_file_sync(&filename, buffer.into(), options)?;
243 } else {
244 let key_name = options.local_storage_key(filename.as_ref());
245 if runtime::is_chrome_extension(){
246 ChromeStorage::set_item(&key_name, data.to_hex().as_str()).await?;
247 }else{
248 local_storage().set_item(&key_name, data.to_hex().as_str())?;
249 }
250 }
251 Ok(())
252 }
253
254 pub fn write_binary_with_options_sync<P : AsRef<Path>>(filename: P, options: Options, data : &[u8]) -> Result<()> {
255 if runtime::is_node() || runtime::is_nw() {
256 let filename = filename.as_ref().to_platform_string();
257 let options = Object::new();
258 let uint8_array = Uint8Array::from(data);
259 let buffer = Buffer::from_uint8_array(&uint8_array);
260 node::fs::write_file_sync(&filename, buffer.into(), options)?;
261 } else if runtime::is_chrome_extension(){
262 return Err(Error::Custom("localStorage api is unavailable, you can use write_binary_with_options() for chrome.storage.local api.".to_string()));
263 }else{
264 let key_name = options.local_storage_key(filename.as_ref());
265 local_storage().set_item(&key_name, data.to_hex().as_str())?;
266 }
267
268 Ok(())
269 }
270
271 pub async fn remove_with_options<P : AsRef<Path>>(filename: P, options: Options) -> Result<()> {
272 if runtime::is_node() || runtime::is_nw() {
273 let filename = filename.as_ref().to_platform_string();
274 node::fs::unlink_sync(&filename)?;
275 } else {
276 let key_name = options.local_storage_key(filename.as_ref());
277 if runtime::is_chrome_extension(){
278 ChromeStorage::remove_item(&key_name).await?;
279 }else{
280 local_storage().remove_item(&key_name)?;
281 }
282 }
283 Ok(())
284 }
285
286 pub fn remove_with_options_sync<P : AsRef<Path>>(filename: P, options: Options) -> Result<()> {
287 if runtime::is_node() || runtime::is_nw() {
288 let filename = filename.as_ref().to_platform_string();
289 node::fs::unlink_sync(&filename)?;
290 } else if runtime::is_chrome_extension(){
291 return Err(Error::Custom("localStorage api is unavailable, you can use remove_with_options() for chrome.storage.local api.".to_string()));
292 }else{
293 let key_name = options.local_storage_key(filename.as_ref());
294 local_storage().remove_item(&key_name)?;
295 }
296 Ok(())
297 }
298
299 pub async fn rename<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 fn rename_sync<P : AsRef<Path>>(from: P, to: P) -> Result<()> {
311 if runtime::is_node() || runtime::is_nw() {
312 let from = from.as_ref().to_platform_string();
313 let to = to.as_ref().to_platform_string();
314 node::fs::rename_sync(&from,&to)?;
315 Ok(())
316 } else {
317 Err(Error::NotSupported)
318 }
319 }
320
321 pub async fn create_dir_all<P : AsRef<Path>>(filename: P) -> Result<()> {
322 create_dir_all_sync(filename)
323 }
324
325 pub fn create_dir_all_sync<P : AsRef<Path>>(filename: P) -> Result<()> {
326 if runtime::is_node() || runtime::is_nw() {
327 let options = Object::new();
328 Reflect::set(&options, &JsValue::from("recursive"), &JsValue::from_bool(true))?;
329 let filename = filename.as_ref().to_platform_string();
330 node::fs::mkdir_sync(&filename, options)?;
331 }
332
333 Ok(())
334 }
335
336
337 async fn fetch_metadata(path: &str, entries : &mut [DirEntry]) -> std::result::Result<(),JsErrorData> {
338 for entry in entries.iter_mut() {
339 let path = format!("{}/{}",path, entry.file_name());
340 let metadata = node::fs::stat_sync(&path).unwrap();
341 entry.metadata = metadata.try_into().ok();
342 }
343
344 Ok(())
345 }
346
347 async fn readdir_impl(path: &Path, metadata : bool) -> std::result::Result<Vec<DirEntry>,JsErrorData> {
348 let path_string = path.to_string_lossy().to_string();
349 let files = node::fs::readdir(&path_string).await?;
350 let list = files.dyn_into::<js_sys::Array>().expect("readdir: expecting resulting entries to be an array");
351 let mut entries = list.to_vec().into_iter().map(|s| s.into()).collect::<Vec<DirEntry>>();
352
353 if metadata {
354 fetch_metadata(&path_string, &mut entries).await?; }
356
357 Ok(entries)
358 }
359
360 pub async fn readdir<P>(path: P, metadata : bool) -> Result<Vec<DirEntry>>
361 where P : AsRef<Path> + Send + 'static
362 {
363 use workflow_core::sendable::Sendable;
370 use workflow_core::task::dispatch;
371 use workflow_core::channel::oneshot;
372
373 if runtime::is_node() || runtime::is_nw() {
374
375 let (sender, receiver) = oneshot();
376 dispatch(async move {
377 let path = path.as_ref();
378 let result = readdir_impl(path, metadata).await;
379 sender.send(Sendable(result)).await.unwrap();
380 });
381
382 Ok(receiver.recv().await.unwrap().unwrap()?)
383 } else if runtime::is_chrome_extension(){
384 let entries = ChromeStorage::keys().await?
385 .into_iter()
386 .map(DirEntry::from)
387 .collect::<Vec<_>>();
388 Ok(entries)
389 } else{
390 let local_storage = local_storage();
391
392 let mut entries = vec![];
393 let length = local_storage.length().unwrap();
394 for i in 0..length {
395 let key = local_storage.key(i)?;
396 if let Some(key) = key {
397 entries.push(DirEntry::from(key));
398 }
399 }
400 Ok(entries)
401 }
402 }
403
404 } else { pub async fn exists_with_options<P : AsRef<Path>>(filename: P, _options: Options) -> Result<bool> {
412 Ok(filename.as_ref().exists())
413 }
414
415 pub fn exists_with_options_sync<P : AsRef<Path>>(filename: P, _options: Options) -> Result<bool> {
417 Ok(filename.as_ref().exists())
418 }
419
420 pub async fn read_to_string_with_options<P : AsRef<Path>>(filename: P, _options: Options) -> Result<String> {
422 Ok(std::fs::read_to_string(filename)?)
423 }
424
425 pub fn read_to_string_with_options_sync<P : AsRef<Path>>(filename: P, _options: Options) -> Result<String> {
427 Ok(std::fs::read_to_string(filename)?)
428 }
429
430 pub async fn read_binary_with_options<P : AsRef<Path>>(filename: P, _options: Options) -> Result<Vec<u8>> {
432 Ok(std::fs::read(filename)?)
433 }
434
435 pub fn read_binary_with_options_sync<P : AsRef<Path>>(filename: P, _options: Options) -> Result<Vec<u8>> {
437 Ok(std::fs::read(filename)?)
438 }
439
440 pub async fn write_string_with_options<P : AsRef<Path>>(filename: P, _options: Options, text : &str) -> Result<()> {
442 Ok(std::fs::write(filename, text)?)
443 }
444
445 pub fn write_string_with_options_sync<P : AsRef<Path>>(filename: P, _options: Options, text : &str) -> Result<()> {
447 Ok(std::fs::write(filename, text)?)
448 }
449
450 pub async fn write_binary_with_options<P : AsRef<Path>>(filename: P, _options: Options, data : &[u8]) -> Result<()> {
452 Ok(std::fs::write(filename, data)?)
453 }
454
455 pub fn write_binary_with_options_sync<P : AsRef<Path>>(filename: P, _options: Options, data : &[u8]) -> Result<()> {
457 Ok(std::fs::write(filename, data)?)
458 }
459
460 pub async fn remove_with_options<P : AsRef<Path>>(filename: P, _options: Options) -> Result<()> {
462 std::fs::remove_file(filename)?;
463 Ok(())
464 }
465
466 pub fn remove_with_options_sync<P : AsRef<Path>>(filename: P, _options: Options) -> Result<()> {
468 std::fs::remove_file(filename)?;
469 Ok(())
470 }
471
472 pub async fn rename<P : AsRef<Path>>(from: P, to: P) -> Result<()> {
474 std::fs::rename(from,to)?;
475 Ok(())
476 }
477
478 pub fn rename_sync<P : AsRef<Path>>(from: P, to: P) -> Result<()> {
480 std::fs::rename(from,to)?;
481 Ok(())
482 }
483
484 pub async fn create_dir_all<P : AsRef<Path>>(dir: P) -> Result<()> {
486 std::fs::create_dir_all(dir)?;
487 Ok(())
488 }
489
490 pub fn create_dir_all_sync<P : AsRef<Path>>(dir: P) -> Result<()> {
492 std::fs::create_dir_all(dir)?;
493 Ok(())
494 }
495
496 pub async fn readdir<P : AsRef<Path>>(path: P, metadata : bool) -> Result<Vec<DirEntry>> {
499 let entries = std::fs::read_dir(path.as_ref())?;
500
501 if metadata {
502 let mut list = Vec::new();
503 for de in entries {
504 let de = de?;
505 let metadata = std::fs::metadata(de.path())?;
506 let dir_entry = DirEntry::from((de,metadata));
507 list.push(dir_entry);
508 }
509 Ok(list)
510 } else {
511 Ok(entries.map(|r|r.map(|e|e.into())).collect::<std::result::Result<Vec<_>,_>>()?)
512 }
513 }
514
515 }
516
517}
518
519#[derive(Clone, Debug)]
522pub struct Metadata {
523 created: Option<u64>,
524 modified: Option<u64>,
525 accessed: Option<u64>,
526 len: Option<u64>,
527}
528
529impl Metadata {
530 pub fn created(&self) -> Option<u64> {
532 self.created
533 }
534
535 pub fn modified(&self) -> Option<u64> {
537 self.modified
538 }
539
540 pub fn accessed(&self) -> Option<u64> {
542 self.accessed
543 }
544
545 pub fn len(&self) -> Option<u64> {
547 self.len
548 }
549
550 pub fn is_empty(&self) -> Option<bool> {
552 self.len.map(|len| len == 0)
553 }
554}
555
556impl From<std::fs::Metadata> for Metadata {
557 fn from(metadata: std::fs::Metadata) -> Self {
558 Metadata {
559 created: metadata.created().ok().map(|created| {
560 created
561 .duration_since(std::time::UNIX_EPOCH)
562 .unwrap()
563 .as_secs()
564 }),
565 modified: metadata.modified().ok().map(|modified| {
566 modified
567 .duration_since(std::time::UNIX_EPOCH)
568 .unwrap()
569 .as_secs()
570 }),
571 accessed: metadata.accessed().ok().map(|accessed| {
572 accessed
573 .duration_since(std::time::UNIX_EPOCH)
574 .unwrap()
575 .as_secs()
576 }),
577 len: Some(metadata.len()),
578 }
579 }
580}
581
582impl TryFrom<JsValue> for Metadata {
583 type Error = Error;
584 fn try_from(metadata: JsValue) -> Result<Self> {
585 if metadata.is_undefined() {
586 return Err(Error::Metadata);
587 }
588 let created = Reflect::get(&metadata, &"birthtimeMs".into())
589 .ok()
590 .map(|v| (v.as_f64().unwrap() / 1000.0) as u64);
591 let modified = Reflect::get(&metadata, &"mtimeMs".into())
592 .ok()
593 .map(|v| (v.as_f64().unwrap() / 1000.0) as u64);
594 let accessed = Reflect::get(&metadata, &"atimeMs".into())
595 .ok()
596 .map(|v| (v.as_f64().unwrap() / 1000.0) as u64);
597 let len = Reflect::get(&metadata, &"size".into())
598 .ok()
599 .map(|v| v.as_f64().unwrap() as u64);
600
601 Ok(Metadata {
602 created,
603 modified,
604 accessed,
605 len,
606 })
607 }
608}
609
610#[derive(Clone, Debug)]
612pub struct DirEntry {
613 file_name: String,
614 metadata: Option<Metadata>,
615}
616
617impl DirEntry {
618 pub fn file_name(&self) -> &str {
620 &self.file_name
621 }
622
623 pub fn metadata(&self) -> Option<&Metadata> {
625 self.metadata.as_ref()
626 }
627}
628
629impl From<std::fs::DirEntry> for DirEntry {
630 fn from(de: std::fs::DirEntry) -> Self {
631 DirEntry {
632 file_name: de.file_name().to_string_lossy().to_string(),
633 metadata: None,
634 }
635 }
636}
637
638impl From<(std::fs::DirEntry, std::fs::Metadata)> for DirEntry {
639 fn from((de, metadata): (std::fs::DirEntry, std::fs::Metadata)) -> Self {
640 DirEntry {
641 file_name: de.file_name().to_string_lossy().to_string(),
642 metadata: Some(metadata.into()),
643 }
644 }
645}
646
647impl From<JsValue> for DirEntry {
648 fn from(de: JsValue) -> Self {
649 DirEntry {
650 file_name: de.as_string().unwrap(),
651 metadata: None,
652 }
653 }
654}
655
656impl From<String> for DirEntry {
657 fn from(s: String) -> Self {
658 DirEntry {
659 file_name: s,
660 metadata: None,
661 }
662 }
663}
664
665pub async fn exists<P: AsRef<Path>>(filename: P) -> Result<bool> {
667 exists_with_options(filename, Options::default()).await
668}
669
670pub fn exists_sync<P: AsRef<Path>>(filename: P) -> Result<bool> {
672 exists_with_options_sync(filename, Options::default())
673}
674
675pub async fn read_to_string(filename: &Path) -> Result<String> {
679 read_to_string_with_options(filename, Options::default()).await
680}
681
682pub fn read_to_string_sync(filename: &Path) -> Result<String> {
686 read_to_string_with_options_sync(filename, Options::default())
687}
688
689pub async fn read(filename: &Path) -> Result<Vec<u8>> {
693 read_binary_with_options(filename, Options::default()).await
694}
695
696pub fn read_sync(filename: &Path) -> Result<Vec<u8>> {
700 read_binary_with_options_sync(filename, Options::default())
701}
702
703pub async fn write_string(filename: &Path, text: &str) -> Result<()> {
707 write_string_with_options(filename, Options::default(), text).await
708}
709
710pub fn write_string_sync(filename: &Path, text: &str) -> Result<()> {
714 write_string_with_options_sync(filename, Options::default(), text)
715}
716
717pub async fn write(filename: &Path, data: &[u8]) -> Result<()> {
721 write_binary_with_options(filename, Options::default(), data).await
722}
723
724pub async fn write_sync(filename: &Path, data: &[u8]) -> Result<()> {
728 write_binary_with_options_sync(filename, Options::default(), data)
729}
730
731pub async fn remove(filename: &Path) -> Result<()> {
735 remove_with_options(filename, Options::default()).await
736}
737
738pub fn remove_sync(filename: &Path) -> Result<()> {
742 remove_with_options_sync(filename, Options::default())
743}
744
745pub async fn read_json_with_options<T>(filename: &Path, options: Options) -> Result<T>
747where
748 T: DeserializeOwned,
749{
750 let text = read_to_string_with_options(filename, options).await?;
751 Ok(serde_json::from_str(&text)?)
752}
753
754pub fn read_json_with_options_sync<T>(filename: &Path, options: Options) -> Result<T>
756where
757 T: DeserializeOwned,
758{
759 let text = read_to_string_with_options_sync(filename, options)?;
760 Ok(serde_json::from_str(&text)?)
761}
762
763pub async fn write_json_with_options<T>(filename: &Path, options: Options, value: &T) -> Result<()>
765where
766 T: Serialize,
767{
768 let json = serde_json::to_string(value)?;
769 write_string_with_options(filename, options, &json).await?;
770 Ok(())
771}
772
773pub fn write_json_with_options_sync<T>(filename: &Path, options: Options, value: &T) -> Result<()>
775where
776 T: Serialize,
777{
778 let json = serde_json::to_string(value)?;
779 write_string_with_options_sync(filename, options, &json)?;
780 Ok(())
781}
782
783pub async fn read_json<T>(filename: &Path) -> Result<T>
785where
786 T: DeserializeOwned,
787{
788 read_json_with_options(filename, Options::default()).await
789}
790
791pub fn read_json_sync<T>(filename: &Path) -> Result<T>
793where
794 T: DeserializeOwned,
795{
796 read_json_with_options_sync(filename, Options::default())
797}
798
799pub async fn write_json<T>(filename: &Path, value: &T) -> Result<()>
801where
802 T: Serialize,
803{
804 write_json_with_options(filename, Options::default(), value).await
805}
806
807pub fn write_json_sync<T>(filename: &Path, value: &T) -> Result<()>
809where
810 T: Serialize,
811{
812 write_json_with_options_sync(filename, Options::default(), value)
813}
814
815pub fn resolve_path(path: &str) -> Result<PathBuf> {
817 if let Some(_stripped) = path.strip_prefix("~/") {
818 if runtime::is_web() {
819 Ok(PathBuf::from(path))
820 } else if runtime::is_node() || runtime::is_nw() {
821 Ok(dirs::home_dir()
822 .ok_or_else(|| Error::HomeDir(path.to_string()))?
823 .join(_stripped))
824 } else {
825 cfg_if! {
826 if #[cfg(target_arch = "wasm32")] {
827 Ok(PathBuf::from(path))
828 } else {
829 Ok(home::home_dir().ok_or_else(||Error::HomeDir(path.to_string()))?.join(_stripped))
830 }
831 }
832 }
833 } else {
834 Ok(PathBuf::from(path))
835 }
836}
837
838pub trait NormalizePath {
843 fn normalize(&self) -> Result<PathBuf>;
846}
847
848impl NormalizePath for Path {
849 fn normalize(&self) -> Result<PathBuf> {
850 normalize(self)
851 }
852}
853
854impl NormalizePath for PathBuf {
855 fn normalize(&self) -> Result<PathBuf> {
856 normalize(self)
857 }
858}
859
860pub trait ToPlatform {
867 fn to_platform(&self) -> PathBuf;
869 fn to_platform_string(&self) -> String;
871 fn to_unix(&self) -> PathBuf;
873}
874
875impl ToPlatform for Path {
876 fn to_platform(&self) -> PathBuf {
877 if runtime::is_windows() {
878 convert_path_separators(self, "/", "\\")
879 } else {
880 self.to_path_buf()
881 }
882 }
883
884 fn to_platform_string(&self) -> String {
885 self.to_platform().to_string_lossy().to_string()
886 }
887
888 fn to_unix(&self) -> PathBuf {
889 if runtime::is_windows() {
890 convert_path_separators(self, "\\", "/")
891 } else {
892 self.to_path_buf()
893 }
894 }
895}
896
897pub fn normalize<P>(path: P) -> Result<PathBuf>
902where
903 P: AsRef<Path>,
904{
905 let path = path.as_ref().to_unix();
906 let mut result = PathBuf::new();
907
908 for component in path.components() {
909 if let Some(c) = component.as_os_str().to_str() {
910 if c == "." {
911 continue;
912 } else if c == ".." {
913 result.pop();
914 } else {
915 result.push(c);
916 }
917 } else {
918 return Err(Error::InvalidPath(path.to_string_lossy().to_string()));
919 }
920 }
921
922 Ok(result.to_platform())
923}
924
925fn convert_path_separators<P>(path: P, from: &str, to: &str) -> PathBuf
926where
927 P: AsRef<Path>,
928{
929 let path = path.as_ref().to_string_lossy();
930 let path = path.replace(from, to);
931 PathBuf::from(path)
932}