Skip to main content

azul_layout/
file.rs

1//! File system operations module for C API
2//!
3//! Provides C-compatible wrappers around Rust's std::fs API.
4//! This allows C code to use Rust's file operations without importing stdio.h.
5
6use 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// ============================================================================
15// Error types
16// ============================================================================
17
18/// Error when performing file operations
19#[derive(Debug, Clone, PartialEq)]
20#[repr(C)]
21pub struct FileError {
22    /// Error message
23    pub message: AzString,
24    /// Error kind
25    pub kind: FileErrorKind,
26}
27
28/// Kind of file error
29#[derive(Debug, Clone, Copy, PartialEq, Eq)]
30#[repr(C)]
31pub enum FileErrorKind {
32    /// File or directory not found
33    NotFound,
34    /// Permission denied
35    PermissionDenied,
36    /// File already exists
37    AlreadyExists,
38    /// Invalid path
39    InvalidPath,
40    /// I/O error
41    IoError,
42    /// Directory not empty
43    DirectoryNotEmpty,
44    /// Is a directory (expected file)
45    IsDirectory,
46    /// Is a file (expected directory)
47    IsFile,
48    /// Other error
49    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
89// FFI-safe Result types for file operations
90impl_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// Forward declarations for result types that need later-defined types
123// (FilePath, FileMetadata, DirEntryVec are defined below)
124
125// ============================================================================
126// File metadata
127// ============================================================================
128
129/// File type (file, directory, symlink)
130#[derive(Debug, Clone, Copy, PartialEq, Eq)]
131#[repr(C)]
132pub enum FileType {
133    /// Regular file
134    File,
135    /// Directory
136    Directory,
137    /// Symbolic link
138    Symlink,
139    /// Other (device, socket, etc.)
140    Other,
141}
142
143/// Metadata about a file
144#[derive(Debug, Clone, PartialEq)]
145#[repr(C)]
146pub struct FileMetadata {
147    /// File size in bytes
148    pub size: u64,
149    /// File type
150    pub file_type: FileType,
151    /// Is read-only
152    pub is_readonly: bool,
153    /// Last modification time (Unix timestamp in seconds)
154    pub modified_secs: u64,
155    /// Creation time (Unix timestamp in seconds, 0 if unavailable)
156    pub created_secs: u64,
157}
158
159/// A directory entry
160#[derive(Debug, Clone)]
161#[repr(C)]
162pub struct DirEntry {
163    /// File name (not full path)
164    pub name: AzString,
165    /// Full path
166    pub path: AzString,
167    /// File type
168    pub file_type: FileType,
169}
170
171/// Vec of DirEntry
172impl_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
178// Additional FFI-safe Result types for complex types
179impl_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// ============================================================================
197// File operations
198// ============================================================================
199
200/// Read a file to bytes
201#[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/// Read a file to string (UTF-8)
209#[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/// Write bytes to a file (creates or overwrites)
217#[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/// Write string to a file (creates or overwrites)
224#[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/// Append bytes to a file
231#[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/// Copy a file
248#[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/// Rename/move a file
255#[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/// Delete a file
262#[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/// Check if a file or directory exists
269#[cfg(feature = "std")]
270pub fn path_exists(path: &str) -> bool {
271    Path::new(path).exists()
272}
273
274/// Check if path is a file
275#[cfg(feature = "std")]
276pub fn path_is_file(path: &str) -> bool {
277    Path::new(path).is_file()
278}
279
280/// Check if path is a directory
281#[cfg(feature = "std")]
282pub fn path_is_dir(path: &str) -> bool {
283    Path::new(path).is_dir()
284}
285
286/// Get file metadata
287#[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// ============================================================================
324// Directory operations
325// ============================================================================
326
327/// Create a directory
328#[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/// Create a directory and all parent directories
335#[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/// Delete an empty directory
342#[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/// Delete a directory and all its contents
349#[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/// List directory contents
356#[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// ============================================================================
390// Path operations
391// ============================================================================
392
393/// Join two paths
394#[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/// Get the parent directory of a path
401#[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/// Get the file name from a path
408#[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/// Get the file extension from a path
415#[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/// Canonicalize a path (resolve symlinks, make absolute)
422#[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// ============================================================================
430// Temporary files
431// ============================================================================
432
433/// Get the system temporary directory
434#[cfg(feature = "std")]
435pub fn temp_dir() -> AzString {
436    AzString::from(std::env::temp_dir().to_string_lossy().to_string())
437}
438
439// ============================================================================
440// OOP-style Path wrapper
441// ============================================================================
442
443/// FFI-safe path type with OOP-style methods
444/// 
445/// This wraps a string path and provides method-based access to file operations.
446#[derive(Debug, Clone, PartialEq, Eq, Hash)]
447#[repr(C)]
448pub struct FilePath {
449    pub inner: AzString,
450}
451
452// Result type for FilePath operations (must be after FilePath definition)
453impl_result!(
454    FilePath,
455    FileError,
456    ResultFilePathFileError,
457    copy = false,
458    [Debug, Clone, PartialEq]
459);
460
461// Option type for FilePath
462impl_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    /// Creates a new path from a string
472    pub fn new(path: AzString) -> Self {
473        Self { inner: path }
474    }
475
476    /// Creates an empty path
477    pub fn empty() -> Self {
478        Self::default()
479    }
480
481    /// Creates a path from a string slice
482    pub fn from_str(s: &str) -> Self {
483        Self { inner: AzString::from(String::from(s)) }
484    }
485
486    /// Returns the system temporary directory
487    #[cfg(feature = "std")]
488    pub fn get_temp_dir() -> Self {
489        Self { inner: temp_dir() }
490    }
491
492    /// Returns the current working directory
493    #[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    /// Returns the user's home directory (e.g., /home/username on Linux, C:\Users\username on Windows)
502    #[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    /// Returns the user's cache directory (e.g., ~/.cache on Linux, ~/Library/Caches on macOS)
508    #[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    /// Returns the user's config directory (e.g., ~/.config on Linux, ~/Library/Application Support on macOS)
514    #[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    /// Returns the user's local config directory (e.g., ~/.config on Linux, ~/Library/Application Support on macOS)
520    #[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    /// Returns the user's data directory (e.g., ~/.local/share on Linux, ~/Library/Application Support on macOS)
526    #[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    /// Returns the user's local data directory (e.g., ~/.local/share on Linux, ~/Library/Application Support on macOS)
532    #[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    /// Returns the user's desktop directory (e.g., ~/Desktop)
538    #[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    /// Returns the user's documents directory (e.g., ~/Documents)
544    #[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    /// Returns the user's downloads directory (e.g., ~/Downloads)
550    #[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    /// Returns the user's executable directory (e.g., ~/.local/bin on Linux)
556    #[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    /// Returns the user's font directory (e.g., ~/.local/share/fonts on Linux, ~/Library/Fonts on macOS)
562    #[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    /// Returns the user's pictures directory (e.g., ~/Pictures)
568    #[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    /// Returns the user's preference directory (e.g., ~/.config on Linux, ~/Library/Preferences on macOS)
574    #[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    /// Returns the user's public directory (e.g., ~/Public)
580    #[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    /// Returns the user's runtime directory (e.g., /run/user/1000 on Linux)
586    #[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    /// Returns the user's state directory (e.g., ~/.local/state on Linux)
592    #[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    /// Returns the user's audio directory (e.g., ~/Music)
598    #[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    /// Returns the user's video directory (e.g., ~/Videos)
604    #[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    /// Returns the user's templates directory
610    #[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    /// Joins this path with another path component
616    #[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    /// Joins this path with a string component
622    #[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    /// Returns the parent directory of this path
628    #[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    /// Returns the file name component of this path
634    #[cfg(feature = "std")]
635    pub fn file_name(&self) -> Option<AzString> {
636        path_file_name(self.inner.as_str())
637    }
638
639    /// Returns the file extension of this path
640    #[cfg(feature = "std")]
641    pub fn extension(&self) -> Option<AzString> {
642        path_extension(self.inner.as_str())
643    }
644
645    /// Checks if the path exists on the filesystem
646    #[cfg(feature = "std")]
647    pub fn exists(&self) -> bool {
648        path_exists(self.inner.as_str())
649    }
650
651    /// Checks if the path is a file
652    #[cfg(feature = "std")]
653    pub fn is_file(&self) -> bool {
654        path_is_file(self.inner.as_str())
655    }
656
657    /// Checks if the path is a directory
658    #[cfg(feature = "std")]
659    pub fn is_dir(&self) -> bool {
660        path_is_dir(self.inner.as_str())
661    }
662
663    /// Checks if the path is absolute
664    #[cfg(feature = "std")]
665    pub fn is_absolute(&self) -> bool {
666        Path::new(self.inner.as_str()).is_absolute()
667    }
668
669    /// Creates this directory and all parent directories
670    #[cfg(feature = "std")]
671    pub fn create_dir_all(&self) -> Result<EmptyStruct, FileError> {
672        dir_create_all(self.inner.as_str())
673    }
674
675    /// Creates this directory (parent must exist)
676    #[cfg(feature = "std")]
677    pub fn create_dir(&self) -> Result<EmptyStruct, FileError> {
678        dir_create(self.inner.as_str())
679    }
680
681    /// Removes this file
682    #[cfg(feature = "std")]
683    pub fn remove_file(&self) -> Result<EmptyStruct, FileError> {
684        file_delete(self.inner.as_str())
685    }
686
687    /// Removes this directory (must be empty)
688    #[cfg(feature = "std")]
689    pub fn remove_dir(&self) -> Result<EmptyStruct, FileError> {
690        dir_delete(self.inner.as_str())
691    }
692
693    /// Removes this directory and all contents
694    #[cfg(feature = "std")]
695    pub fn remove_dir_all(&self) -> Result<EmptyStruct, FileError> {
696        dir_delete_all(self.inner.as_str())
697    }
698
699    /// Reads the entire file at this path as bytes
700    #[cfg(feature = "std")]
701    pub fn read_bytes(&self) -> Result<U8Vec, FileError> {
702        file_read(self.inner.as_str())
703    }
704
705    /// Reads the entire file at this path as a string
706    #[cfg(feature = "std")]
707    pub fn read_string(&self) -> Result<AzString, FileError> {
708        file_read_string(self.inner.as_str())
709    }
710
711    /// Writes bytes to the file at this path
712    #[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    /// Writes a string to the file at this path
718    #[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    /// Copies a file from this path to another path
724    #[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    /// Renames/moves a file from this path to another path
730    #[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    /// Returns the path as a string reference
736    pub fn as_str(&self) -> &str {
737        self.inner.as_str()
738    }
739
740    /// Returns the path as an AzString
741    pub fn as_string(&self) -> AzString {
742        self.inner.clone()
743    }
744
745    /// Lists directory contents
746    #[cfg(feature = "std")]
747    pub fn read_dir(&self) -> Result<DirEntryVec, FileError> {
748        dir_list(self.inner.as_str())
749    }
750
751    /// Returns metadata about the file/directory
752    #[cfg(feature = "std")]
753    pub fn metadata(&self) -> Result<FileMetadata, FileError> {
754        file_metadata(self.inner.as_str())
755    }
756
757    /// Makes the path canonical (absolute, with no `.` or `..` components)
758    #[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}