1use alloc::string::String;
7use alloc::vec::Vec;
8use core::fmt;
9use azul_css::{AzString, U8Vec, EmptyStruct, impl_result, impl_result_inner, impl_vec, impl_vec_clone, impl_vec_debug, impl_vec_mut, impl_option, impl_option_inner};
10
11#[cfg(feature = "std")]
12use std::path::Path;
13
14#[derive(Debug, Clone, PartialEq)]
20#[repr(C)]
21pub struct FileError {
22 pub message: AzString,
24 pub kind: FileErrorKind,
26}
27
28#[derive(Debug, Clone, Copy, PartialEq, Eq)]
30#[repr(C)]
31pub enum FileErrorKind {
32 NotFound,
34 PermissionDenied,
36 AlreadyExists,
38 InvalidPath,
40 IoError,
42 DirectoryNotEmpty,
44 IsDirectory,
46 IsFile,
48 Other,
50}
51
52impl FileError {
53 pub fn new(kind: FileErrorKind, message: impl Into<String>) -> Self {
54 Self {
55 message: AzString::from(message.into()),
56 kind,
57 }
58 }
59
60 #[cfg(feature = "std")]
61 pub fn from_io_error(e: std::io::Error) -> Self {
62 use std::io::ErrorKind;
63
64 let kind = match e.kind() {
65 ErrorKind::NotFound => FileErrorKind::NotFound,
66 ErrorKind::PermissionDenied => FileErrorKind::PermissionDenied,
67 ErrorKind::AlreadyExists => FileErrorKind::AlreadyExists,
68 ErrorKind::IsADirectory => FileErrorKind::IsDirectory,
69 ErrorKind::DirectoryNotEmpty => FileErrorKind::DirectoryNotEmpty,
70 _ => FileErrorKind::IoError,
71 };
72
73 Self {
74 message: AzString::from(e.to_string()),
75 kind,
76 }
77 }
78}
79
80impl fmt::Display for FileError {
81 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
82 write!(f, "{}", self.message.as_str())
83 }
84}
85
86#[cfg(feature = "std")]
87impl std::error::Error for FileError {}
88
89impl_result!(
91 EmptyStruct,
92 FileError,
93 ResultEmptyStructFileError,
94 copy = false,
95 [Debug, Clone, PartialEq]
96);
97
98impl_result!(
99 U8Vec,
100 FileError,
101 ResultU8VecFileError,
102 copy = false,
103 [Debug, Clone, PartialEq]
104);
105
106impl_result!(
107 AzString,
108 FileError,
109 ResultStringFileError,
110 copy = false,
111 [Debug, Clone, PartialEq]
112);
113
114impl_result!(
115 u64,
116 FileError,
117 Resultu64FileError,
118 copy = false,
119 [Debug, Clone, PartialEq]
120);
121
122#[derive(Debug, Clone, Copy, PartialEq, Eq)]
131#[repr(C)]
132pub enum FileType {
133 File,
135 Directory,
137 Symlink,
139 Other,
141}
142
143#[derive(Debug, Clone, PartialEq)]
145#[repr(C)]
146pub struct FileMetadata {
147 pub size: u64,
149 pub file_type: FileType,
151 pub is_readonly: bool,
153 pub modified_secs: u64,
155 pub created_secs: u64,
157}
158
159#[derive(Debug, Clone)]
161#[repr(C)]
162pub struct DirEntry {
163 pub name: AzString,
165 pub path: AzString,
167 pub file_type: FileType,
169}
170
171impl_option!(DirEntry, OptionDirEntry, copy = false, [Debug, Clone]);
173impl_vec!(DirEntry, DirEntryVec, DirEntryVecDestructor, DirEntryVecDestructorType, DirEntryVecSlice, OptionDirEntry);
174impl_vec_clone!(DirEntry, DirEntryVec, DirEntryVecDestructor);
175impl_vec_debug!(DirEntry, DirEntryVec);
176impl_vec_mut!(DirEntry, DirEntryVec);
177
178impl_result!(
180 FileMetadata,
181 FileError,
182 ResultFileMetadataFileError,
183 copy = false,
184 [Debug, Clone, PartialEq]
185);
186
187impl_result!(
188 DirEntryVec,
189 FileError,
190 ResultDirEntryVecFileError,
191 copy = false,
192 clone = false,
193 [Debug, Clone]
194);
195
196#[cfg(feature = "std")]
202pub fn file_read(path: &str) -> Result<U8Vec, FileError> {
203 let data = std::fs::read(path)
204 .map_err(FileError::from_io_error)?;
205 Ok(U8Vec::from(data))
206}
207
208#[cfg(feature = "std")]
210pub fn file_read_string(path: &str) -> Result<AzString, FileError> {
211 let data = std::fs::read_to_string(path)
212 .map_err(FileError::from_io_error)?;
213 Ok(AzString::from(data))
214}
215
216#[cfg(feature = "std")]
218pub fn file_write(path: &str, data: &[u8]) -> Result<EmptyStruct, FileError> {
219 std::fs::write(path, data)
220 .map(|_| EmptyStruct::default()).map_err(FileError::from_io_error)
221}
222
223#[cfg(feature = "std")]
225pub fn file_write_string(path: &str, data: &str) -> Result<EmptyStruct, FileError> {
226 std::fs::write(path, data.as_bytes())
227 .map(|_| EmptyStruct::default()).map_err(FileError::from_io_error)
228}
229
230#[cfg(feature = "std")]
232pub fn file_append(path: &str, data: &[u8]) -> Result<EmptyStruct, FileError> {
233 use std::fs::OpenOptions;
234 use std::io::Write;
235
236 let mut file = OpenOptions::new()
237 .create(true)
238 .append(true)
239 .open(path)
240 .map_err(FileError::from_io_error)?;
241
242 file.write_all(data)
243 .map(|_| EmptyStruct::default())
244 .map_err(FileError::from_io_error)
245}
246
247#[cfg(feature = "std")]
249pub fn file_copy(from: &str, to: &str) -> Result<u64, FileError> {
250 std::fs::copy(from, to)
251 .map_err(FileError::from_io_error)
252}
253
254#[cfg(feature = "std")]
256pub fn file_rename(from: &str, to: &str) -> Result<EmptyStruct, FileError> {
257 std::fs::rename(from, to)
258 .map(|_| EmptyStruct::default()).map_err(FileError::from_io_error)
259}
260
261#[cfg(feature = "std")]
263pub fn file_delete(path: &str) -> Result<EmptyStruct, FileError> {
264 std::fs::remove_file(path)
265 .map(|_| EmptyStruct::default()).map_err(FileError::from_io_error)
266}
267
268#[cfg(feature = "std")]
270pub fn path_exists(path: &str) -> bool {
271 Path::new(path).exists()
272}
273
274#[cfg(feature = "std")]
276pub fn path_is_file(path: &str) -> bool {
277 Path::new(path).is_file()
278}
279
280#[cfg(feature = "std")]
282pub fn path_is_dir(path: &str) -> bool {
283 Path::new(path).is_dir()
284}
285
286#[cfg(feature = "std")]
288pub fn file_metadata(path: &str) -> Result<FileMetadata, FileError> {
289 let meta = std::fs::metadata(path)
290 .map_err(FileError::from_io_error)?;
291
292 let file_type = if meta.is_file() {
293 FileType::File
294 } else if meta.is_dir() {
295 FileType::Directory
296 } else if meta.is_symlink() {
297 FileType::Symlink
298 } else {
299 FileType::Other
300 };
301
302 let modified_secs = meta.modified()
303 .ok()
304 .and_then(|t| t.duration_since(std::time::UNIX_EPOCH).ok())
305 .map(|d| d.as_secs())
306 .unwrap_or(0);
307
308 let created_secs = meta.created()
309 .ok()
310 .and_then(|t| t.duration_since(std::time::UNIX_EPOCH).ok())
311 .map(|d| d.as_secs())
312 .unwrap_or(0);
313
314 Ok(FileMetadata {
315 size: meta.len(),
316 file_type,
317 is_readonly: meta.permissions().readonly(),
318 modified_secs,
319 created_secs,
320 })
321}
322
323#[cfg(feature = "std")]
329pub fn dir_create(path: &str) -> Result<EmptyStruct, FileError> {
330 std::fs::create_dir(path)
331 .map(|_| EmptyStruct::default()).map_err(FileError::from_io_error)
332}
333
334#[cfg(feature = "std")]
336pub fn dir_create_all(path: &str) -> Result<EmptyStruct, FileError> {
337 std::fs::create_dir_all(path)
338 .map(|_| EmptyStruct::default()).map_err(FileError::from_io_error)
339}
340
341#[cfg(feature = "std")]
343pub fn dir_delete(path: &str) -> Result<EmptyStruct, FileError> {
344 std::fs::remove_dir(path)
345 .map(|_| EmptyStruct::default()).map_err(FileError::from_io_error)
346}
347
348#[cfg(feature = "std")]
350pub fn dir_delete_all(path: &str) -> Result<EmptyStruct, FileError> {
351 std::fs::remove_dir_all(path)
352 .map(|_| EmptyStruct::default()).map_err(FileError::from_io_error)
353}
354
355#[cfg(feature = "std")]
357pub fn dir_list(path: &str) -> Result<DirEntryVec, FileError> {
358 let entries = std::fs::read_dir(path)
359 .map_err(FileError::from_io_error)?;
360
361 let mut result = Vec::new();
362
363 for entry in entries {
364 let entry = entry.map_err(FileError::from_io_error)?;
365 let file_type = entry.file_type()
366 .map(|ft| {
367 if ft.is_file() {
368 FileType::File
369 } else if ft.is_dir() {
370 FileType::Directory
371 } else if ft.is_symlink() {
372 FileType::Symlink
373 } else {
374 FileType::Other
375 }
376 })
377 .unwrap_or(FileType::Other);
378
379 result.push(DirEntry {
380 name: AzString::from(entry.file_name().to_string_lossy().to_string()),
381 path: AzString::from(entry.path().to_string_lossy().to_string()),
382 file_type,
383 });
384 }
385
386 Ok(DirEntryVec::from_vec(result))
387}
388
389#[cfg(feature = "std")]
395pub fn path_join(base: &str, path: &str) -> AzString {
396 let joined = Path::new(base).join(path);
397 AzString::from(joined.to_string_lossy().to_string())
398}
399
400#[cfg(feature = "std")]
402pub fn path_parent(path: &str) -> Option<AzString> {
403 Path::new(path).parent()
404 .map(|p| AzString::from(p.to_string_lossy().to_string()))
405}
406
407#[cfg(feature = "std")]
409pub fn path_file_name(path: &str) -> Option<AzString> {
410 Path::new(path).file_name()
411 .map(|n| AzString::from(n.to_string_lossy().to_string()))
412}
413
414#[cfg(feature = "std")]
416pub fn path_extension(path: &str) -> Option<AzString> {
417 Path::new(path).extension()
418 .map(|e| AzString::from(e.to_string_lossy().to_string()))
419}
420
421#[cfg(feature = "std")]
423pub fn path_canonicalize(path: &str) -> Result<AzString, FileError> {
424 let canonical = std::fs::canonicalize(path)
425 .map_err(FileError::from_io_error)?;
426 Ok(AzString::from(canonical.to_string_lossy().to_string()))
427}
428
429#[cfg(feature = "std")]
435pub fn temp_dir() -> AzString {
436 AzString::from(std::env::temp_dir().to_string_lossy().to_string())
437}
438
439#[derive(Debug, Clone, PartialEq, Eq, Hash)]
447#[repr(C)]
448pub struct FilePath {
449 pub inner: AzString,
450}
451
452impl_result!(
454 FilePath,
455 FileError,
456 ResultFilePathFileError,
457 copy = false,
458 [Debug, Clone, PartialEq]
459);
460
461impl_option!(FilePath, OptionFilePath, copy = false, [Clone, Debug, PartialEq]);
463
464impl Default for FilePath {
465 fn default() -> Self {
466 Self { inner: AzString::from_const_str("") }
467 }
468}
469
470impl FilePath {
471 pub fn new(path: AzString) -> Self {
473 Self { inner: path }
474 }
475
476 pub fn empty() -> Self {
478 Self::default()
479 }
480
481 pub fn from_str(s: &str) -> Self {
483 Self { inner: AzString::from(String::from(s)) }
484 }
485
486 #[cfg(feature = "std")]
488 pub fn get_temp_dir() -> Self {
489 Self { inner: temp_dir() }
490 }
491
492 #[cfg(feature = "std")]
494 pub fn get_current_dir() -> Result<FilePath, FileError> {
495 match std::env::current_dir() {
496 Ok(p) => Ok(Self { inner: AzString::from(p.to_string_lossy().into_owned()) }),
497 Err(e) => Err(FileError::from_io_error(e)),
498 }
499 }
500
501 #[cfg(all(feature = "std", feature = "extra"))]
503 pub fn get_home_dir() -> Option<FilePath> {
504 dirs::home_dir().map(|p| Self { inner: AzString::from(p.to_string_lossy().into_owned()) })
505 }
506
507 #[cfg(all(feature = "std", feature = "extra"))]
509 pub fn get_cache_dir() -> Option<FilePath> {
510 dirs::cache_dir().map(|p| Self { inner: AzString::from(p.to_string_lossy().into_owned()) })
511 }
512
513 #[cfg(all(feature = "std", feature = "extra"))]
515 pub fn get_config_dir() -> Option<FilePath> {
516 dirs::config_dir().map(|p| Self { inner: AzString::from(p.to_string_lossy().into_owned()) })
517 }
518
519 #[cfg(all(feature = "std", feature = "extra"))]
521 pub fn get_config_local_dir() -> Option<FilePath> {
522 dirs::config_local_dir().map(|p| Self { inner: AzString::from(p.to_string_lossy().into_owned()) })
523 }
524
525 #[cfg(all(feature = "std", feature = "extra"))]
527 pub fn get_data_dir() -> Option<FilePath> {
528 dirs::data_dir().map(|p| Self { inner: AzString::from(p.to_string_lossy().into_owned()) })
529 }
530
531 #[cfg(all(feature = "std", feature = "extra"))]
533 pub fn get_data_local_dir() -> Option<FilePath> {
534 dirs::data_local_dir().map(|p| Self { inner: AzString::from(p.to_string_lossy().into_owned()) })
535 }
536
537 #[cfg(all(feature = "std", feature = "extra"))]
539 pub fn get_desktop_dir() -> Option<FilePath> {
540 dirs::desktop_dir().map(|p| Self { inner: AzString::from(p.to_string_lossy().into_owned()) })
541 }
542
543 #[cfg(all(feature = "std", feature = "extra"))]
545 pub fn get_document_dir() -> Option<FilePath> {
546 dirs::document_dir().map(|p| Self { inner: AzString::from(p.to_string_lossy().into_owned()) })
547 }
548
549 #[cfg(all(feature = "std", feature = "extra"))]
551 pub fn get_download_dir() -> Option<FilePath> {
552 dirs::download_dir().map(|p| Self { inner: AzString::from(p.to_string_lossy().into_owned()) })
553 }
554
555 #[cfg(all(feature = "std", feature = "extra"))]
557 pub fn get_executable_dir() -> Option<FilePath> {
558 dirs::executable_dir().map(|p| Self { inner: AzString::from(p.to_string_lossy().into_owned()) })
559 }
560
561 #[cfg(all(feature = "std", feature = "extra"))]
563 pub fn get_font_dir() -> Option<FilePath> {
564 dirs::font_dir().map(|p| Self { inner: AzString::from(p.to_string_lossy().into_owned()) })
565 }
566
567 #[cfg(all(feature = "std", feature = "extra"))]
569 pub fn get_picture_dir() -> Option<FilePath> {
570 dirs::picture_dir().map(|p| Self { inner: AzString::from(p.to_string_lossy().into_owned()) })
571 }
572
573 #[cfg(all(feature = "std", feature = "extra"))]
575 pub fn get_preference_dir() -> Option<FilePath> {
576 dirs::preference_dir().map(|p| Self { inner: AzString::from(p.to_string_lossy().into_owned()) })
577 }
578
579 #[cfg(all(feature = "std", feature = "extra"))]
581 pub fn get_public_dir() -> Option<FilePath> {
582 dirs::public_dir().map(|p| Self { inner: AzString::from(p.to_string_lossy().into_owned()) })
583 }
584
585 #[cfg(all(feature = "std", feature = "extra"))]
587 pub fn get_runtime_dir() -> Option<FilePath> {
588 dirs::runtime_dir().map(|p| Self { inner: AzString::from(p.to_string_lossy().into_owned()) })
589 }
590
591 #[cfg(all(feature = "std", feature = "extra"))]
593 pub fn get_state_dir() -> Option<FilePath> {
594 dirs::state_dir().map(|p| Self { inner: AzString::from(p.to_string_lossy().into_owned()) })
595 }
596
597 #[cfg(all(feature = "std", feature = "extra"))]
599 pub fn get_audio_dir() -> Option<FilePath> {
600 dirs::audio_dir().map(|p| Self { inner: AzString::from(p.to_string_lossy().into_owned()) })
601 }
602
603 #[cfg(all(feature = "std", feature = "extra"))]
605 pub fn get_video_dir() -> Option<FilePath> {
606 dirs::video_dir().map(|p| Self { inner: AzString::from(p.to_string_lossy().into_owned()) })
607 }
608
609 #[cfg(all(feature = "std", feature = "extra"))]
611 pub fn get_template_dir() -> Option<FilePath> {
612 dirs::template_dir().map(|p| Self { inner: AzString::from(p.to_string_lossy().into_owned()) })
613 }
614
615 #[cfg(feature = "std")]
617 pub fn join(&self, other: &FilePath) -> FilePath {
618 FilePath { inner: path_join(self.inner.as_str(), other.inner.as_str()) }
619 }
620
621 #[cfg(feature = "std")]
623 pub fn join_str(&self, component: &AzString) -> FilePath {
624 FilePath { inner: path_join(self.inner.as_str(), component.as_str()) }
625 }
626
627 #[cfg(feature = "std")]
629 pub fn parent(&self) -> Option<FilePath> {
630 path_parent(self.inner.as_str()).map(|p| FilePath { inner: p })
631 }
632
633 #[cfg(feature = "std")]
635 pub fn file_name(&self) -> Option<AzString> {
636 path_file_name(self.inner.as_str())
637 }
638
639 #[cfg(feature = "std")]
641 pub fn extension(&self) -> Option<AzString> {
642 path_extension(self.inner.as_str())
643 }
644
645 #[cfg(feature = "std")]
647 pub fn exists(&self) -> bool {
648 path_exists(self.inner.as_str())
649 }
650
651 #[cfg(feature = "std")]
653 pub fn is_file(&self) -> bool {
654 path_is_file(self.inner.as_str())
655 }
656
657 #[cfg(feature = "std")]
659 pub fn is_dir(&self) -> bool {
660 path_is_dir(self.inner.as_str())
661 }
662
663 #[cfg(feature = "std")]
665 pub fn is_absolute(&self) -> bool {
666 Path::new(self.inner.as_str()).is_absolute()
667 }
668
669 #[cfg(feature = "std")]
671 pub fn create_dir_all(&self) -> Result<EmptyStruct, FileError> {
672 dir_create_all(self.inner.as_str())
673 }
674
675 #[cfg(feature = "std")]
677 pub fn create_dir(&self) -> Result<EmptyStruct, FileError> {
678 dir_create(self.inner.as_str())
679 }
680
681 #[cfg(feature = "std")]
683 pub fn remove_file(&self) -> Result<EmptyStruct, FileError> {
684 file_delete(self.inner.as_str())
685 }
686
687 #[cfg(feature = "std")]
689 pub fn remove_dir(&self) -> Result<EmptyStruct, FileError> {
690 dir_delete(self.inner.as_str())
691 }
692
693 #[cfg(feature = "std")]
695 pub fn remove_dir_all(&self) -> Result<EmptyStruct, FileError> {
696 dir_delete_all(self.inner.as_str())
697 }
698
699 #[cfg(feature = "std")]
701 pub fn read_bytes(&self) -> Result<U8Vec, FileError> {
702 file_read(self.inner.as_str())
703 }
704
705 #[cfg(feature = "std")]
707 pub fn read_string(&self) -> Result<AzString, FileError> {
708 file_read_string(self.inner.as_str())
709 }
710
711 #[cfg(feature = "std")]
713 pub fn write_bytes(&self, data: &U8Vec) -> Result<EmptyStruct, FileError> {
714 file_write(self.inner.as_str(), data.as_ref())
715 }
716
717 #[cfg(feature = "std")]
719 pub fn write_string(&self, data: &AzString) -> Result<EmptyStruct, FileError> {
720 file_write_string(self.inner.as_str(), data.as_str())
721 }
722
723 #[cfg(feature = "std")]
725 pub fn copy_to(&self, dest: &FilePath) -> Result<u64, FileError> {
726 file_copy(self.inner.as_str(), dest.inner.as_str())
727 }
728
729 #[cfg(feature = "std")]
731 pub fn rename_to(&self, dest: &FilePath) -> Result<EmptyStruct, FileError> {
732 file_rename(self.inner.as_str(), dest.inner.as_str())
733 }
734
735 pub fn as_str(&self) -> &str {
737 self.inner.as_str()
738 }
739
740 pub fn as_string(&self) -> AzString {
742 self.inner.clone()
743 }
744
745 #[cfg(feature = "std")]
747 pub fn read_dir(&self) -> Result<DirEntryVec, FileError> {
748 dir_list(self.inner.as_str())
749 }
750
751 #[cfg(feature = "std")]
753 pub fn metadata(&self) -> Result<FileMetadata, FileError> {
754 file_metadata(self.inner.as_str())
755 }
756
757 #[cfg(feature = "std")]
759 pub fn canonicalize(&self) -> Result<FilePath, FileError> {
760 path_canonicalize(self.inner.as_str()).map(|p| FilePath { inner: p })
761 }
762}
763
764impl From<String> for FilePath {
765 fn from(s: String) -> Self {
766 Self { inner: AzString::from(s) }
767 }
768}
769
770impl From<&str> for FilePath {
771 fn from(s: &str) -> Self {
772 Self { inner: AzString::from(String::from(s)) }
773 }
774}
775
776impl From<AzString> for FilePath {
777 fn from(s: AzString) -> Self {
778 Self { inner: s }
779 }
780}
781
782#[cfg(test)]
783mod tests {
784 use super::*;
785
786 #[test]
787 #[cfg(feature = "std")]
788 fn test_temp_dir() {
789 let temp = temp_dir();
790 assert!(!temp.as_str().is_empty());
791 }
792
793 #[test]
794 #[cfg(feature = "std")]
795 fn test_path_join() {
796 let joined = path_join("/home/user", "file.txt");
797 assert!(joined.as_str().contains("file.txt"));
798 }
799}