1use alloc::string::String;
7use alloc::vec::Vec;
8use core::fmt;
9use azul_css::{AzString, U8Vec, Void, impl_result, impl_result_inner, impl_vec, impl_vec_clone, impl_vec_debug, impl_vec_partialeq, 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 Void,
92 FileError,
93 ResultVoidFileError,
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")]
208pub fn file_read(path: &str) -> Result<U8Vec, FileError> {
209 let data = std::fs::read(path)
210 .map_err(FileError::from_io_error)?;
211 Ok(U8Vec::from(data))
212}
213
214#[cfg(feature = "std")]
222pub fn file_read_string(path: &str) -> Result<AzString, FileError> {
223 let data = std::fs::read_to_string(path)
224 .map_err(FileError::from_io_error)?;
225 Ok(AzString::from(data))
226}
227
228#[cfg(feature = "std")]
237pub fn file_write(path: &str, data: &[u8]) -> Result<Void, FileError> {
238 std::fs::write(path, data)
239 .map(|_| Void::default()).map_err(FileError::from_io_error)
240}
241
242#[cfg(feature = "std")]
251pub fn file_write_string(path: &str, data: &str) -> Result<Void, FileError> {
252 std::fs::write(path, data.as_bytes())
253 .map(|_| Void::default()).map_err(FileError::from_io_error)
254}
255
256#[cfg(feature = "std")]
265pub fn file_append(path: &str, data: &[u8]) -> Result<Void, FileError> {
266 use std::fs::OpenOptions;
267 use std::io::Write;
268
269 let mut file = OpenOptions::new()
270 .create(true)
271 .append(true)
272 .open(path)
273 .map_err(FileError::from_io_error)?;
274
275 file.write_all(data)
276 .map(|_| Void::default())
277 .map_err(FileError::from_io_error)
278}
279
280#[cfg(feature = "std")]
289pub fn file_copy(from: &str, to: &str) -> Result<u64, FileError> {
290 std::fs::copy(from, to)
291 .map_err(FileError::from_io_error)
292}
293
294#[cfg(feature = "std")]
303pub fn file_rename(from: &str, to: &str) -> Result<Void, FileError> {
304 std::fs::rename(from, to)
305 .map(|_| Void::default()).map_err(FileError::from_io_error)
306}
307
308#[cfg(feature = "std")]
316pub fn file_delete(path: &str) -> Result<Void, FileError> {
317 std::fs::remove_file(path)
318 .map(|_| Void::default()).map_err(FileError::from_io_error)
319}
320
321#[cfg(feature = "std")]
329pub fn path_exists(path: &str) -> bool {
330 Path::new(path).exists()
331}
332
333#[cfg(feature = "std")]
341pub fn path_is_file(path: &str) -> bool {
342 Path::new(path).is_file()
343}
344
345#[cfg(feature = "std")]
353pub fn path_is_dir(path: &str) -> bool {
354 Path::new(path).is_dir()
355}
356
357#[cfg(feature = "std")]
365pub fn file_metadata(path: &str) -> Result<FileMetadata, FileError> {
366 let meta = std::fs::metadata(path)
367 .map_err(FileError::from_io_error)?;
368
369 let file_type = if meta.is_file() {
370 FileType::File
371 } else if meta.is_dir() {
372 FileType::Directory
373 } else if meta.is_symlink() {
374 FileType::Symlink
375 } else {
376 FileType::Other
377 };
378
379 let modified_secs = meta.modified()
380 .ok()
381 .and_then(|t| t.duration_since(std::time::UNIX_EPOCH).ok())
382 .map(|d| d.as_secs())
383 .unwrap_or(0);
384
385 let created_secs = meta.created()
386 .ok()
387 .and_then(|t| t.duration_since(std::time::UNIX_EPOCH).ok())
388 .map(|d| d.as_secs())
389 .unwrap_or(0);
390
391 Ok(FileMetadata {
392 size: meta.len(),
393 file_type,
394 is_readonly: meta.permissions().readonly(),
395 modified_secs,
396 created_secs,
397 })
398}
399
400#[cfg(feature = "std")]
412pub fn dir_create(path: &str) -> Result<Void, FileError> {
413 std::fs::create_dir(path)
414 .map(|_| Void::default()).map_err(FileError::from_io_error)
415}
416
417#[cfg(feature = "std")]
425pub fn dir_create_all(path: &str) -> Result<Void, FileError> {
426 std::fs::create_dir_all(path)
427 .map(|_| Void::default()).map_err(FileError::from_io_error)
428}
429
430#[cfg(feature = "std")]
438pub fn dir_delete(path: &str) -> Result<Void, FileError> {
439 std::fs::remove_dir(path)
440 .map(|_| Void::default()).map_err(FileError::from_io_error)
441}
442
443#[cfg(feature = "std")]
451pub fn dir_delete_all(path: &str) -> Result<Void, FileError> {
452 std::fs::remove_dir_all(path)
453 .map(|_| Void::default()).map_err(FileError::from_io_error)
454}
455
456#[cfg(feature = "std")]
464pub fn dir_list(path: &str) -> Result<DirEntryVec, FileError> {
465 let entries = std::fs::read_dir(path)
466 .map_err(FileError::from_io_error)?;
467
468 let mut result = Vec::new();
469
470 for entry in entries {
471 let entry = entry.map_err(FileError::from_io_error)?;
472 let file_type = entry.file_type()
473 .map(|ft| {
474 if ft.is_file() {
475 FileType::File
476 } else if ft.is_dir() {
477 FileType::Directory
478 } else if ft.is_symlink() {
479 FileType::Symlink
480 } else {
481 FileType::Other
482 }
483 })
484 .unwrap_or(FileType::Other);
485
486 result.push(DirEntry {
487 name: AzString::from(entry.file_name().to_string_lossy().to_string()),
488 path: AzString::from(entry.path().to_string_lossy().to_string()),
489 file_type,
490 });
491 }
492
493 Ok(DirEntryVec::from_vec(result))
494}
495
496#[cfg(feature = "std")]
509pub fn path_join(base: &str, path: &str) -> AzString {
510 let joined = Path::new(base).join(path);
511 AzString::from(joined.to_string_lossy().to_string())
512}
513
514#[cfg(feature = "std")]
522pub fn path_parent(path: &str) -> Option<AzString> {
523 Path::new(path).parent()
524 .map(|p| AzString::from(p.to_string_lossy().to_string()))
525}
526
527#[cfg(feature = "std")]
535pub fn path_file_name(path: &str) -> Option<AzString> {
536 Path::new(path).file_name()
537 .map(|n| AzString::from(n.to_string_lossy().to_string()))
538}
539
540#[cfg(feature = "std")]
548pub fn path_extension(path: &str) -> Option<AzString> {
549 Path::new(path).extension()
550 .map(|e| AzString::from(e.to_string_lossy().to_string()))
551}
552
553#[cfg(feature = "std")]
561pub fn path_canonicalize(path: &str) -> Result<AzString, FileError> {
562 let canonical = std::fs::canonicalize(path)
563 .map_err(FileError::from_io_error)?;
564 Ok(AzString::from(canonical.to_string_lossy().to_string()))
565}
566
567#[cfg(feature = "std")]
576pub fn temp_dir() -> AzString {
577 AzString::from(std::env::temp_dir().to_string_lossy().to_string())
578}
579
580#[derive(Debug, Clone, PartialEq, Eq, Hash)]
588#[repr(C)]
589pub struct FilePath {
590 pub inner: AzString,
591}
592
593impl_result!(
595 FilePath,
596 FileError,
597 ResultFilePathFileError,
598 copy = false,
599 [Debug, Clone, PartialEq]
600);
601
602impl_option!(FilePath, OptionFilePath, copy = false, [Clone, Debug, PartialEq]);
604
605impl Default for FilePath {
606 fn default() -> Self {
607 Self { inner: AzString::from_const_str("") }
608 }
609}
610
611impl FilePath {
612 pub fn new(path: AzString) -> Self {
614 Self { inner: path }
615 }
616
617 pub fn empty() -> Self {
619 Self::default()
620 }
621
622 pub fn from_str(s: &str) -> Self {
624 Self { inner: AzString::from(String::from(s)) }
625 }
626
627 #[cfg(feature = "std")]
629 pub fn get_temp_dir() -> Self {
630 Self { inner: temp_dir() }
631 }
632
633 #[cfg(feature = "std")]
635 pub fn get_current_dir() -> Result<FilePath, FileError> {
636 match std::env::current_dir() {
637 Ok(p) => Ok(Self { inner: AzString::from(p.to_string_lossy().into_owned()) }),
638 Err(e) => Err(FileError::from_io_error(e)),
639 }
640 }
641
642 #[cfg(all(feature = "std", feature = "extra"))]
644 pub fn get_home_dir() -> Option<FilePath> {
645 dirs::home_dir().map(|p| Self { inner: AzString::from(p.to_string_lossy().into_owned()) })
646 }
647
648 #[cfg(all(feature = "std", feature = "extra"))]
650 pub fn get_cache_dir() -> Option<FilePath> {
651 dirs::cache_dir().map(|p| Self { inner: AzString::from(p.to_string_lossy().into_owned()) })
652 }
653
654 #[cfg(all(feature = "std", feature = "extra"))]
656 pub fn get_config_dir() -> Option<FilePath> {
657 dirs::config_dir().map(|p| Self { inner: AzString::from(p.to_string_lossy().into_owned()) })
658 }
659
660 #[cfg(all(feature = "std", feature = "extra"))]
662 pub fn get_config_local_dir() -> Option<FilePath> {
663 dirs::config_local_dir().map(|p| Self { inner: AzString::from(p.to_string_lossy().into_owned()) })
664 }
665
666 #[cfg(all(feature = "std", feature = "extra"))]
668 pub fn get_data_dir() -> Option<FilePath> {
669 dirs::data_dir().map(|p| Self { inner: AzString::from(p.to_string_lossy().into_owned()) })
670 }
671
672 #[cfg(all(feature = "std", feature = "extra"))]
674 pub fn get_data_local_dir() -> Option<FilePath> {
675 dirs::data_local_dir().map(|p| Self { inner: AzString::from(p.to_string_lossy().into_owned()) })
676 }
677
678 #[cfg(all(feature = "std", feature = "extra"))]
680 pub fn get_desktop_dir() -> Option<FilePath> {
681 dirs::desktop_dir().map(|p| Self { inner: AzString::from(p.to_string_lossy().into_owned()) })
682 }
683
684 #[cfg(all(feature = "std", feature = "extra"))]
686 pub fn get_document_dir() -> Option<FilePath> {
687 dirs::document_dir().map(|p| Self { inner: AzString::from(p.to_string_lossy().into_owned()) })
688 }
689
690 #[cfg(all(feature = "std", feature = "extra"))]
692 pub fn get_download_dir() -> Option<FilePath> {
693 dirs::download_dir().map(|p| Self { inner: AzString::from(p.to_string_lossy().into_owned()) })
694 }
695
696 #[cfg(all(feature = "std", feature = "extra"))]
698 pub fn get_executable_dir() -> Option<FilePath> {
699 dirs::executable_dir().map(|p| Self { inner: AzString::from(p.to_string_lossy().into_owned()) })
700 }
701
702 #[cfg(all(feature = "std", feature = "extra"))]
704 pub fn get_font_dir() -> Option<FilePath> {
705 dirs::font_dir().map(|p| Self { inner: AzString::from(p.to_string_lossy().into_owned()) })
706 }
707
708 #[cfg(all(feature = "std", feature = "extra"))]
710 pub fn get_picture_dir() -> Option<FilePath> {
711 dirs::picture_dir().map(|p| Self { inner: AzString::from(p.to_string_lossy().into_owned()) })
712 }
713
714 #[cfg(all(feature = "std", feature = "extra"))]
716 pub fn get_preference_dir() -> Option<FilePath> {
717 dirs::preference_dir().map(|p| Self { inner: AzString::from(p.to_string_lossy().into_owned()) })
718 }
719
720 #[cfg(all(feature = "std", feature = "extra"))]
722 pub fn get_public_dir() -> Option<FilePath> {
723 dirs::public_dir().map(|p| Self { inner: AzString::from(p.to_string_lossy().into_owned()) })
724 }
725
726 #[cfg(all(feature = "std", feature = "extra"))]
728 pub fn get_runtime_dir() -> Option<FilePath> {
729 dirs::runtime_dir().map(|p| Self { inner: AzString::from(p.to_string_lossy().into_owned()) })
730 }
731
732 #[cfg(all(feature = "std", feature = "extra"))]
734 pub fn get_state_dir() -> Option<FilePath> {
735 dirs::state_dir().map(|p| Self { inner: AzString::from(p.to_string_lossy().into_owned()) })
736 }
737
738 #[cfg(all(feature = "std", feature = "extra"))]
740 pub fn get_audio_dir() -> Option<FilePath> {
741 dirs::audio_dir().map(|p| Self { inner: AzString::from(p.to_string_lossy().into_owned()) })
742 }
743
744 #[cfg(all(feature = "std", feature = "extra"))]
746 pub fn get_video_dir() -> Option<FilePath> {
747 dirs::video_dir().map(|p| Self { inner: AzString::from(p.to_string_lossy().into_owned()) })
748 }
749
750 #[cfg(all(feature = "std", feature = "extra"))]
752 pub fn get_template_dir() -> Option<FilePath> {
753 dirs::template_dir().map(|p| Self { inner: AzString::from(p.to_string_lossy().into_owned()) })
754 }
755
756 pub fn join(&self, other: &FilePath) -> FilePath {
758 FilePath { inner: path_join(self.inner.as_str(), other.inner.as_str()) }
759 }
760
761 pub fn join_str(&self, component: &AzString) -> FilePath {
763 FilePath { inner: path_join(self.inner.as_str(), component.as_str()) }
764 }
765
766 #[cfg(feature = "std")]
768 pub fn parent(&self) -> Option<FilePath> {
769 path_parent(self.inner.as_str()).map(|p| FilePath { inner: p })
770 }
771
772 #[cfg(feature = "std")]
774 pub fn file_name(&self) -> Option<AzString> {
775 path_file_name(self.inner.as_str())
776 }
777
778 #[cfg(feature = "std")]
780 pub fn extension(&self) -> Option<AzString> {
781 path_extension(self.inner.as_str())
782 }
783
784 #[cfg(feature = "std")]
786 pub fn exists(&self) -> bool {
787 path_exists(self.inner.as_str())
788 }
789
790 #[cfg(feature = "std")]
792 pub fn is_file(&self) -> bool {
793 path_is_file(self.inner.as_str())
794 }
795
796 #[cfg(feature = "std")]
798 pub fn is_dir(&self) -> bool {
799 path_is_dir(self.inner.as_str())
800 }
801
802 #[cfg(feature = "std")]
804 pub fn is_absolute(&self) -> bool {
805 Path::new(self.inner.as_str()).is_absolute()
806 }
807
808 #[cfg(feature = "std")]
810 pub fn create_dir_all(&self) -> Result<Void, FileError> {
811 dir_create_all(self.inner.as_str())
812 }
813
814 #[cfg(feature = "std")]
816 pub fn create_dir(&self) -> Result<Void, FileError> {
817 dir_create(self.inner.as_str())
818 }
819
820 #[cfg(feature = "std")]
822 pub fn remove_file(&self) -> Result<Void, FileError> {
823 file_delete(self.inner.as_str())
824 }
825
826 #[cfg(feature = "std")]
828 pub fn remove_dir(&self) -> Result<Void, FileError> {
829 dir_delete(self.inner.as_str())
830 }
831
832 #[cfg(feature = "std")]
834 pub fn remove_dir_all(&self) -> Result<Void, FileError> {
835 dir_delete_all(self.inner.as_str())
836 }
837
838 #[cfg(feature = "std")]
840 pub fn read_bytes(&self) -> Result<U8Vec, FileError> {
841 file_read(self.inner.as_str())
842 }
843
844 #[cfg(feature = "std")]
846 pub fn read_string(&self) -> Result<AzString, FileError> {
847 file_read_string(self.inner.as_str())
848 }
849
850 #[cfg(feature = "std")]
852 pub fn write_bytes(&self, data: &U8Vec) -> Result<Void, FileError> {
853 file_write(self.inner.as_str(), data.as_ref())
854 }
855
856 #[cfg(feature = "std")]
858 pub fn write_string(&self, data: &AzString) -> Result<Void, FileError> {
859 file_write_string(self.inner.as_str(), data.as_str())
860 }
861
862 #[cfg(feature = "std")]
864 pub fn copy_to(&self, dest: &FilePath) -> Result<u64, FileError> {
865 file_copy(self.inner.as_str(), dest.inner.as_str())
866 }
867
868 #[cfg(feature = "std")]
870 pub fn rename_to(&self, dest: &FilePath) -> Result<Void, FileError> {
871 file_rename(self.inner.as_str(), dest.inner.as_str())
872 }
873
874 pub fn as_str(&self) -> &str {
876 self.inner.as_str()
877 }
878
879 pub fn as_string(&self) -> AzString {
881 self.inner.clone()
882 }
883
884 #[cfg(feature = "std")]
886 pub fn read_dir(&self) -> Result<DirEntryVec, FileError> {
887 dir_list(self.inner.as_str())
888 }
889
890 #[cfg(feature = "std")]
892 pub fn metadata(&self) -> Result<FileMetadata, FileError> {
893 file_metadata(self.inner.as_str())
894 }
895
896 #[cfg(feature = "std")]
898 pub fn canonicalize(&self) -> Result<FilePath, FileError> {
899 path_canonicalize(self.inner.as_str()).map(|p| FilePath { inner: p })
900 }
901}
902
903impl From<String> for FilePath {
904 fn from(s: String) -> Self {
905 Self { inner: AzString::from(s) }
906 }
907}
908
909impl From<&str> for FilePath {
910 fn from(s: &str) -> Self {
911 Self { inner: AzString::from(String::from(s)) }
912 }
913}
914
915impl From<AzString> for FilePath {
916 fn from(s: AzString) -> Self {
917 Self { inner: s }
918 }
919}
920
921#[cfg(test)]
922mod tests {
923 use super::*;
924
925 #[test]
926 #[cfg(feature = "std")]
927 fn test_temp_dir() {
928 let temp = temp_dir();
929 assert!(!temp.as_str().is_empty());
930 }
931
932 #[test]
933 #[cfg(feature = "std")]
934 fn test_path_join() {
935 let joined = path_join("/home/user", "file.txt");
936 assert!(joined.as_str().contains("file.txt"));
937 }
938}