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_vec!(DirEntry, DirEntryVec, DirEntryVecDestructor, DirEntryVecDestructorType);
173impl_vec_clone!(DirEntry, DirEntryVec, DirEntryVecDestructor);
174impl_vec_debug!(DirEntry, DirEntryVec);
175impl_vec_mut!(DirEntry, DirEntryVec);
176
177impl_result!(
179 FileMetadata,
180 FileError,
181 ResultFileMetadataFileError,
182 copy = false,
183 [Debug, Clone, PartialEq]
184);
185
186impl_result!(
187 DirEntryVec,
188 FileError,
189 ResultDirEntryVecFileError,
190 copy = false,
191 clone = false,
192 [Debug, Clone]
193);
194
195#[cfg(feature = "std")]
207pub fn file_read(path: &str) -> Result<U8Vec, FileError> {
208 let data = std::fs::read(path)
209 .map_err(FileError::from_io_error)?;
210 Ok(U8Vec::from(data))
211}
212
213#[cfg(feature = "std")]
221pub fn file_read_string(path: &str) -> Result<AzString, FileError> {
222 let data = std::fs::read_to_string(path)
223 .map_err(FileError::from_io_error)?;
224 Ok(AzString::from(data))
225}
226
227#[cfg(feature = "std")]
236pub fn file_write(path: &str, data: &[u8]) -> Result<Void, FileError> {
237 std::fs::write(path, data)
238 .map(|_| Void::default()).map_err(FileError::from_io_error)
239}
240
241#[cfg(feature = "std")]
250pub fn file_write_string(path: &str, data: &str) -> Result<Void, FileError> {
251 std::fs::write(path, data.as_bytes())
252 .map(|_| Void::default()).map_err(FileError::from_io_error)
253}
254
255#[cfg(feature = "std")]
264pub fn file_append(path: &str, data: &[u8]) -> Result<Void, FileError> {
265 use std::fs::OpenOptions;
266 use std::io::Write;
267
268 let mut file = OpenOptions::new()
269 .create(true)
270 .append(true)
271 .open(path)
272 .map_err(FileError::from_io_error)?;
273
274 file.write_all(data)
275 .map(|_| Void::default())
276 .map_err(FileError::from_io_error)
277}
278
279#[cfg(feature = "std")]
288pub fn file_copy(from: &str, to: &str) -> Result<u64, FileError> {
289 std::fs::copy(from, to)
290 .map_err(FileError::from_io_error)
291}
292
293#[cfg(feature = "std")]
302pub fn file_rename(from: &str, to: &str) -> Result<Void, FileError> {
303 std::fs::rename(from, to)
304 .map(|_| Void::default()).map_err(FileError::from_io_error)
305}
306
307#[cfg(feature = "std")]
315pub fn file_delete(path: &str) -> Result<Void, FileError> {
316 std::fs::remove_file(path)
317 .map(|_| Void::default()).map_err(FileError::from_io_error)
318}
319
320#[cfg(feature = "std")]
328pub fn path_exists(path: &str) -> bool {
329 Path::new(path).exists()
330}
331
332#[cfg(feature = "std")]
340pub fn path_is_file(path: &str) -> bool {
341 Path::new(path).is_file()
342}
343
344#[cfg(feature = "std")]
352pub fn path_is_dir(path: &str) -> bool {
353 Path::new(path).is_dir()
354}
355
356#[cfg(feature = "std")]
364pub fn file_metadata(path: &str) -> Result<FileMetadata, FileError> {
365 let meta = std::fs::metadata(path)
366 .map_err(FileError::from_io_error)?;
367
368 let file_type = if meta.is_file() {
369 FileType::File
370 } else if meta.is_dir() {
371 FileType::Directory
372 } else if meta.is_symlink() {
373 FileType::Symlink
374 } else {
375 FileType::Other
376 };
377
378 let modified_secs = meta.modified()
379 .ok()
380 .and_then(|t| t.duration_since(std::time::UNIX_EPOCH).ok())
381 .map(|d| d.as_secs())
382 .unwrap_or(0);
383
384 let created_secs = meta.created()
385 .ok()
386 .and_then(|t| t.duration_since(std::time::UNIX_EPOCH).ok())
387 .map(|d| d.as_secs())
388 .unwrap_or(0);
389
390 Ok(FileMetadata {
391 size: meta.len(),
392 file_type,
393 is_readonly: meta.permissions().readonly(),
394 modified_secs,
395 created_secs,
396 })
397}
398
399#[cfg(feature = "std")]
411pub fn dir_create(path: &str) -> Result<Void, FileError> {
412 std::fs::create_dir(path)
413 .map(|_| Void::default()).map_err(FileError::from_io_error)
414}
415
416#[cfg(feature = "std")]
424pub fn dir_create_all(path: &str) -> Result<Void, FileError> {
425 std::fs::create_dir_all(path)
426 .map(|_| Void::default()).map_err(FileError::from_io_error)
427}
428
429#[cfg(feature = "std")]
437pub fn dir_delete(path: &str) -> Result<Void, FileError> {
438 std::fs::remove_dir(path)
439 .map(|_| Void::default()).map_err(FileError::from_io_error)
440}
441
442#[cfg(feature = "std")]
450pub fn dir_delete_all(path: &str) -> Result<Void, FileError> {
451 std::fs::remove_dir_all(path)
452 .map(|_| Void::default()).map_err(FileError::from_io_error)
453}
454
455#[cfg(feature = "std")]
463pub fn dir_list(path: &str) -> Result<DirEntryVec, FileError> {
464 let entries = std::fs::read_dir(path)
465 .map_err(FileError::from_io_error)?;
466
467 let mut result = Vec::new();
468
469 for entry in entries {
470 let entry = entry.map_err(FileError::from_io_error)?;
471 let file_type = entry.file_type()
472 .map(|ft| {
473 if ft.is_file() {
474 FileType::File
475 } else if ft.is_dir() {
476 FileType::Directory
477 } else if ft.is_symlink() {
478 FileType::Symlink
479 } else {
480 FileType::Other
481 }
482 })
483 .unwrap_or(FileType::Other);
484
485 result.push(DirEntry {
486 name: AzString::from(entry.file_name().to_string_lossy().to_string()),
487 path: AzString::from(entry.path().to_string_lossy().to_string()),
488 file_type,
489 });
490 }
491
492 Ok(DirEntryVec::from_vec(result))
493}
494
495#[cfg(feature = "std")]
508pub fn path_join(base: &str, path: &str) -> AzString {
509 let joined = Path::new(base).join(path);
510 AzString::from(joined.to_string_lossy().to_string())
511}
512
513#[cfg(feature = "std")]
521pub fn path_parent(path: &str) -> Option<AzString> {
522 Path::new(path).parent()
523 .map(|p| AzString::from(p.to_string_lossy().to_string()))
524}
525
526#[cfg(feature = "std")]
534pub fn path_file_name(path: &str) -> Option<AzString> {
535 Path::new(path).file_name()
536 .map(|n| AzString::from(n.to_string_lossy().to_string()))
537}
538
539#[cfg(feature = "std")]
547pub fn path_extension(path: &str) -> Option<AzString> {
548 Path::new(path).extension()
549 .map(|e| AzString::from(e.to_string_lossy().to_string()))
550}
551
552#[cfg(feature = "std")]
560pub fn path_canonicalize(path: &str) -> Result<AzString, FileError> {
561 let canonical = std::fs::canonicalize(path)
562 .map_err(FileError::from_io_error)?;
563 Ok(AzString::from(canonical.to_string_lossy().to_string()))
564}
565
566#[cfg(feature = "std")]
575pub fn temp_dir() -> AzString {
576 AzString::from(std::env::temp_dir().to_string_lossy().to_string())
577}
578
579#[derive(Debug, Clone, PartialEq, Eq, Hash)]
587#[repr(C)]
588pub struct FilePath {
589 pub inner: AzString,
590}
591
592impl_result!(
594 FilePath,
595 FileError,
596 ResultFilePathFileError,
597 copy = false,
598 [Debug, Clone, PartialEq]
599);
600
601impl_option!(FilePath, OptionFilePath, copy = false, [Clone, Debug, PartialEq]);
603
604impl Default for FilePath {
605 fn default() -> Self {
606 Self { inner: AzString::from_const_str("") }
607 }
608}
609
610impl FilePath {
611 pub fn new(path: AzString) -> Self {
613 Self { inner: path }
614 }
615
616 pub fn empty() -> Self {
618 Self::default()
619 }
620
621 pub fn from_str(s: &str) -> Self {
623 Self { inner: AzString::from(String::from(s)) }
624 }
625
626 #[cfg(feature = "std")]
628 pub fn get_temp_dir() -> Self {
629 Self { inner: temp_dir() }
630 }
631
632 #[cfg(feature = "std")]
634 pub fn get_current_dir() -> Result<FilePath, FileError> {
635 match std::env::current_dir() {
636 Ok(p) => Ok(Self { inner: AzString::from(p.to_string_lossy().into_owned()) }),
637 Err(e) => Err(FileError::from_io_error(e)),
638 }
639 }
640
641 #[cfg(all(feature = "std", feature = "extra"))]
643 pub fn get_home_dir() -> Option<FilePath> {
644 dirs::home_dir().map(|p| Self { inner: AzString::from(p.to_string_lossy().into_owned()) })
645 }
646
647 #[cfg(all(feature = "std", feature = "extra"))]
649 pub fn get_cache_dir() -> Option<FilePath> {
650 dirs::cache_dir().map(|p| Self { inner: AzString::from(p.to_string_lossy().into_owned()) })
651 }
652
653 #[cfg(all(feature = "std", feature = "extra"))]
655 pub fn get_config_dir() -> Option<FilePath> {
656 dirs::config_dir().map(|p| Self { inner: AzString::from(p.to_string_lossy().into_owned()) })
657 }
658
659 #[cfg(all(feature = "std", feature = "extra"))]
661 pub fn get_config_local_dir() -> Option<FilePath> {
662 dirs::config_local_dir().map(|p| Self { inner: AzString::from(p.to_string_lossy().into_owned()) })
663 }
664
665 #[cfg(all(feature = "std", feature = "extra"))]
667 pub fn get_data_dir() -> Option<FilePath> {
668 dirs::data_dir().map(|p| Self { inner: AzString::from(p.to_string_lossy().into_owned()) })
669 }
670
671 #[cfg(all(feature = "std", feature = "extra"))]
673 pub fn get_data_local_dir() -> Option<FilePath> {
674 dirs::data_local_dir().map(|p| Self { inner: AzString::from(p.to_string_lossy().into_owned()) })
675 }
676
677 #[cfg(all(feature = "std", feature = "extra"))]
679 pub fn get_desktop_dir() -> Option<FilePath> {
680 dirs::desktop_dir().map(|p| Self { inner: AzString::from(p.to_string_lossy().into_owned()) })
681 }
682
683 #[cfg(all(feature = "std", feature = "extra"))]
685 pub fn get_document_dir() -> Option<FilePath> {
686 dirs::document_dir().map(|p| Self { inner: AzString::from(p.to_string_lossy().into_owned()) })
687 }
688
689 #[cfg(all(feature = "std", feature = "extra"))]
691 pub fn get_download_dir() -> Option<FilePath> {
692 dirs::download_dir().map(|p| Self { inner: AzString::from(p.to_string_lossy().into_owned()) })
693 }
694
695 #[cfg(all(feature = "std", feature = "extra"))]
697 pub fn get_executable_dir() -> Option<FilePath> {
698 dirs::executable_dir().map(|p| Self { inner: AzString::from(p.to_string_lossy().into_owned()) })
699 }
700
701 #[cfg(all(feature = "std", feature = "extra"))]
703 pub fn get_font_dir() -> Option<FilePath> {
704 dirs::font_dir().map(|p| Self { inner: AzString::from(p.to_string_lossy().into_owned()) })
705 }
706
707 #[cfg(all(feature = "std", feature = "extra"))]
709 pub fn get_picture_dir() -> Option<FilePath> {
710 dirs::picture_dir().map(|p| Self { inner: AzString::from(p.to_string_lossy().into_owned()) })
711 }
712
713 #[cfg(all(feature = "std", feature = "extra"))]
715 pub fn get_preference_dir() -> Option<FilePath> {
716 dirs::preference_dir().map(|p| Self { inner: AzString::from(p.to_string_lossy().into_owned()) })
717 }
718
719 #[cfg(all(feature = "std", feature = "extra"))]
721 pub fn get_public_dir() -> Option<FilePath> {
722 dirs::public_dir().map(|p| Self { inner: AzString::from(p.to_string_lossy().into_owned()) })
723 }
724
725 #[cfg(all(feature = "std", feature = "extra"))]
727 pub fn get_runtime_dir() -> Option<FilePath> {
728 dirs::runtime_dir().map(|p| Self { inner: AzString::from(p.to_string_lossy().into_owned()) })
729 }
730
731 #[cfg(all(feature = "std", feature = "extra"))]
733 pub fn get_state_dir() -> Option<FilePath> {
734 dirs::state_dir().map(|p| Self { inner: AzString::from(p.to_string_lossy().into_owned()) })
735 }
736
737 #[cfg(all(feature = "std", feature = "extra"))]
739 pub fn get_audio_dir() -> Option<FilePath> {
740 dirs::audio_dir().map(|p| Self { inner: AzString::from(p.to_string_lossy().into_owned()) })
741 }
742
743 #[cfg(all(feature = "std", feature = "extra"))]
745 pub fn get_video_dir() -> Option<FilePath> {
746 dirs::video_dir().map(|p| Self { inner: AzString::from(p.to_string_lossy().into_owned()) })
747 }
748
749 #[cfg(all(feature = "std", feature = "extra"))]
751 pub fn get_template_dir() -> Option<FilePath> {
752 dirs::template_dir().map(|p| Self { inner: AzString::from(p.to_string_lossy().into_owned()) })
753 }
754
755 pub fn join(&self, other: &FilePath) -> FilePath {
757 FilePath { inner: path_join(self.inner.as_str(), other.inner.as_str()) }
758 }
759
760 pub fn join_str(&self, component: &AzString) -> FilePath {
762 FilePath { inner: path_join(self.inner.as_str(), component.as_str()) }
763 }
764
765 #[cfg(feature = "std")]
767 pub fn parent(&self) -> Option<FilePath> {
768 path_parent(self.inner.as_str()).map(|p| FilePath { inner: p })
769 }
770
771 #[cfg(feature = "std")]
773 pub fn file_name(&self) -> Option<AzString> {
774 path_file_name(self.inner.as_str())
775 }
776
777 #[cfg(feature = "std")]
779 pub fn extension(&self) -> Option<AzString> {
780 path_extension(self.inner.as_str())
781 }
782
783 #[cfg(feature = "std")]
785 pub fn exists(&self) -> bool {
786 path_exists(self.inner.as_str())
787 }
788
789 #[cfg(feature = "std")]
791 pub fn is_file(&self) -> bool {
792 path_is_file(self.inner.as_str())
793 }
794
795 #[cfg(feature = "std")]
797 pub fn is_dir(&self) -> bool {
798 path_is_dir(self.inner.as_str())
799 }
800
801 #[cfg(feature = "std")]
803 pub fn is_absolute(&self) -> bool {
804 Path::new(self.inner.as_str()).is_absolute()
805 }
806
807 #[cfg(feature = "std")]
809 pub fn create_dir_all(&self) -> Result<Void, FileError> {
810 dir_create_all(self.inner.as_str())
811 }
812
813 #[cfg(feature = "std")]
815 pub fn create_dir(&self) -> Result<Void, FileError> {
816 dir_create(self.inner.as_str())
817 }
818
819 #[cfg(feature = "std")]
821 pub fn remove_file(&self) -> Result<Void, FileError> {
822 file_delete(self.inner.as_str())
823 }
824
825 #[cfg(feature = "std")]
827 pub fn remove_dir(&self) -> Result<Void, FileError> {
828 dir_delete(self.inner.as_str())
829 }
830
831 #[cfg(feature = "std")]
833 pub fn remove_dir_all(&self) -> Result<Void, FileError> {
834 dir_delete_all(self.inner.as_str())
835 }
836
837 #[cfg(feature = "std")]
839 pub fn read_bytes(&self) -> Result<U8Vec, FileError> {
840 file_read(self.inner.as_str())
841 }
842
843 #[cfg(feature = "std")]
845 pub fn read_string(&self) -> Result<AzString, FileError> {
846 file_read_string(self.inner.as_str())
847 }
848
849 #[cfg(feature = "std")]
851 pub fn write_bytes(&self, data: &U8Vec) -> Result<Void, FileError> {
852 file_write(self.inner.as_str(), data.as_ref())
853 }
854
855 #[cfg(feature = "std")]
857 pub fn write_string(&self, data: &AzString) -> Result<Void, FileError> {
858 file_write_string(self.inner.as_str(), data.as_str())
859 }
860
861 #[cfg(feature = "std")]
863 pub fn copy_to(&self, dest: &FilePath) -> Result<u64, FileError> {
864 file_copy(self.inner.as_str(), dest.inner.as_str())
865 }
866
867 #[cfg(feature = "std")]
869 pub fn rename_to(&self, dest: &FilePath) -> Result<Void, FileError> {
870 file_rename(self.inner.as_str(), dest.inner.as_str())
871 }
872
873 pub fn as_str(&self) -> &str {
875 self.inner.as_str()
876 }
877
878 pub fn as_string(&self) -> AzString {
880 self.inner.clone()
881 }
882
883 #[cfg(feature = "std")]
885 pub fn read_dir(&self) -> Result<DirEntryVec, FileError> {
886 dir_list(self.inner.as_str())
887 }
888
889 #[cfg(feature = "std")]
891 pub fn metadata(&self) -> Result<FileMetadata, FileError> {
892 file_metadata(self.inner.as_str())
893 }
894
895 #[cfg(feature = "std")]
897 pub fn canonicalize(&self) -> Result<FilePath, FileError> {
898 path_canonicalize(self.inner.as_str()).map(|p| FilePath { inner: p })
899 }
900}
901
902impl From<String> for FilePath {
903 fn from(s: String) -> Self {
904 Self { inner: AzString::from(s) }
905 }
906}
907
908impl From<&str> for FilePath {
909 fn from(s: &str) -> Self {
910 Self { inner: AzString::from(String::from(s)) }
911 }
912}
913
914impl From<AzString> for FilePath {
915 fn from(s: AzString) -> Self {
916 Self { inner: s }
917 }
918}
919
920#[cfg(feature = "std")]
928pub fn temp_file_create(prefix: &str) -> Result<AzString, FileError> {
929 use std::time::{SystemTime, UNIX_EPOCH};
930
931 let timestamp = SystemTime::now()
932 .duration_since(UNIX_EPOCH)
933 .map(|d| d.as_nanos())
934 .unwrap_or(0);
935
936 let temp_path = std::env::temp_dir()
937 .join(format!("{}_{}", prefix, timestamp));
938
939 std::fs::File::create(&temp_path)
941 .map_err(FileError::from_io_error)?;
942
943 Ok(AzString::from(temp_path.to_string_lossy().to_string()))
944}
945
946#[cfg(test)]
947mod tests {
948 use super::*;
949
950 #[test]
951 #[cfg(feature = "std")]
952 fn test_temp_dir() {
953 let temp = temp_dir();
954 assert!(!temp.as_str().is_empty());
955 }
956
957 #[test]
958 #[cfg(feature = "std")]
959 fn test_path_join() {
960 let joined = path_join("/home/user", "file.txt");
961 assert!(joined.as_str().contains("file.txt"));
962 }
963}