1use 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?; }
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 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 } else { 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
626pub async fn exists<P: AsRef<Path>>(filename: P) -> Result<bool> {
628 exists_with_options(filename, Options::default()).await
629}
630
631pub fn exists_sync<P: AsRef<Path>>(filename: P) -> Result<bool> {
633 exists_with_options_sync(filename, Options::default())
634}
635
636pub async fn read_to_string(filename: &Path) -> Result<String> {
640 read_to_string_with_options(filename, Options::default()).await
641}
642
643pub fn read_to_string_sync(filename: &Path) -> Result<String> {
647 read_to_string_with_options_sync(filename, Options::default())
648}
649
650pub async fn read(filename: &Path) -> Result<Vec<u8>> {
654 read_binary_with_options(filename, Options::default()).await
655}
656
657pub fn read_sync(filename: &Path) -> Result<Vec<u8>> {
661 read_binary_with_options_sync(filename, Options::default())
662}
663
664pub async fn write_string(filename: &Path, text: &str) -> Result<()> {
668 write_string_with_options(filename, Options::default(), text).await
669}
670
671pub fn write_string_sync(filename: &Path, text: &str) -> Result<()> {
675 write_string_with_options_sync(filename, Options::default(), text)
676}
677
678pub async fn write(filename: &Path, data: &[u8]) -> Result<()> {
682 write_binary_with_options(filename, Options::default(), data).await
683}
684
685pub async fn write_sync(filename: &Path, data: &[u8]) -> Result<()> {
689 write_binary_with_options_sync(filename, Options::default(), data)
690}
691
692pub async fn remove(filename: &Path) -> Result<()> {
696 remove_with_options(filename, Options::default()).await
697}
698
699pub fn remove_sync(filename: &Path) -> Result<()> {
703 remove_with_options_sync(filename, Options::default())
704}
705
706pub 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
715pub 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
724pub 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
734pub 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
744pub 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
752pub 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
760pub 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
768pub 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
776pub 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
799pub 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
819pub 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
853pub 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}