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, 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// ============================================================================
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    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// 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_vec!(DirEntry, DirEntryVec, DirEntryVecDestructor, DirEntryVecDestructorType);
173impl_vec_clone!(DirEntry, DirEntryVec, DirEntryVecDestructor);
174impl_vec_debug!(DirEntry, DirEntryVec);
175impl_vec_mut!(DirEntry, DirEntryVec);
176
177// Additional FFI-safe Result types for complex types
178impl_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// ============================================================================
196// File operations
197// ============================================================================
198
199/// Read a file to bytes
200/// 
201/// # Arguments
202/// * `path` - Path to the file
203/// 
204/// # Returns
205/// * `Result<U8Vec, FileError>` - File contents or error
206#[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/// Read a file to string (UTF-8)
214/// 
215/// # Arguments
216/// * `path` - Path to the file
217/// 
218/// # Returns
219/// * `Result<AzString, FileError>` - File contents or error
220#[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/// Write bytes to a file (creates or overwrites)
228/// 
229/// # Arguments
230/// * `path` - Path to the file
231/// * `data` - Data to write
232/// 
233/// # Returns
234/// * `Result<Void, FileError>` - Success or error
235#[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/// Write string to a file (creates or overwrites)
242/// 
243/// # Arguments
244/// * `path` - Path to the file
245/// * `data` - String to write
246/// 
247/// # Returns
248/// * `Result<Void, FileError>` - Success or error
249#[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/// Append bytes to a file
256/// 
257/// # Arguments
258/// * `path` - Path to the file
259/// * `data` - Data to append
260/// 
261/// # Returns
262/// * `Result<Void, FileError>` - Success or error
263#[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/// Copy a file
280/// 
281/// # Arguments
282/// * `from` - Source path
283/// * `to` - Destination path
284/// 
285/// # Returns
286/// * `Result<u64, FileError>` - Bytes copied or error
287#[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/// Rename/move a file
294/// 
295/// # Arguments
296/// * `from` - Source path
297/// * `to` - Destination path
298/// 
299/// # Returns
300/// * `Result<Void, FileError>` - Success or error
301#[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/// Delete a file
308/// 
309/// # Arguments
310/// * `path` - Path to the file
311/// 
312/// # Returns
313/// * `Result<Void, FileError>` - Success or error
314#[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/// Check if a file or directory exists
321/// 
322/// # Arguments
323/// * `path` - Path to check
324/// 
325/// # Returns
326/// * `bool` - True if exists
327#[cfg(feature = "std")]
328pub fn path_exists(path: &str) -> bool {
329    Path::new(path).exists()
330}
331
332/// Check if path is a file
333/// 
334/// # Arguments
335/// * `path` - Path to check
336/// 
337/// # Returns
338/// * `bool` - True if is a file
339#[cfg(feature = "std")]
340pub fn path_is_file(path: &str) -> bool {
341    Path::new(path).is_file()
342}
343
344/// Check if path is a directory
345/// 
346/// # Arguments
347/// * `path` - Path to check
348/// 
349/// # Returns
350/// * `bool` - True if is a directory
351#[cfg(feature = "std")]
352pub fn path_is_dir(path: &str) -> bool {
353    Path::new(path).is_dir()
354}
355
356/// Get file metadata
357/// 
358/// # Arguments
359/// * `path` - Path to the file
360/// 
361/// # Returns
362/// * `Result<FileMetadata, FileError>` - Metadata or error
363#[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// ============================================================================
400// Directory operations
401// ============================================================================
402
403/// Create a directory
404/// 
405/// # Arguments
406/// * `path` - Path to create
407/// 
408/// # Returns
409/// * `Result<Void, FileError>` - Success or error
410#[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/// Create a directory and all parent directories
417/// 
418/// # Arguments
419/// * `path` - Path to create
420/// 
421/// # Returns
422/// * `Result<Void, FileError>` - Success or error
423#[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/// Delete an empty directory
430/// 
431/// # Arguments
432/// * `path` - Path to delete
433/// 
434/// # Returns
435/// * `Result<Void, FileError>` - Success or error
436#[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/// Delete a directory and all its contents
443/// 
444/// # Arguments
445/// * `path` - Path to delete
446/// 
447/// # Returns
448/// * `Result<Void, FileError>` - Success or error
449#[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/// List directory contents
456/// 
457/// # Arguments
458/// * `path` - Path to the directory
459/// 
460/// # Returns
461/// * `Result<DirEntryVec, FileError>` - Directory entries or error
462#[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// ============================================================================
496// Path operations
497// ============================================================================
498
499/// Join two paths
500/// 
501/// # Arguments
502/// * `base` - Base path
503/// * `path` - Path to join
504/// 
505/// # Returns
506/// * `AzString` - Joined path
507#[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/// Get the parent directory of a path
514/// 
515/// # Arguments
516/// * `path` - Path to get parent of
517/// 
518/// # Returns
519/// * `Option<AzString>` - Parent path or None
520#[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/// Get the file name from a path
527/// 
528/// # Arguments
529/// * `path` - Path to get file name from
530/// 
531/// # Returns
532/// * `Option<AzString>` - File name or None
533#[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/// Get the file extension from a path
540/// 
541/// # Arguments
542/// * `path` - Path to get extension from
543/// 
544/// # Returns
545/// * `Option<AzString>` - Extension or None
546#[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/// Canonicalize a path (resolve symlinks, make absolute)
553/// 
554/// # Arguments
555/// * `path` - Path to canonicalize
556/// 
557/// # Returns
558/// * `Result<AzString, FileError>` - Canonical path or error
559#[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// ============================================================================
567// Temporary files
568// ============================================================================
569
570/// Get the system temporary directory
571/// 
572/// # Returns
573/// * `AzString` - Path to temp directory
574#[cfg(feature = "std")]
575pub fn temp_dir() -> AzString {
576    AzString::from(std::env::temp_dir().to_string_lossy().to_string())
577}
578
579// ============================================================================
580// OOP-style Path wrapper
581// ============================================================================
582
583/// FFI-safe path type with OOP-style methods
584/// 
585/// This wraps a string path and provides method-based access to file operations.
586#[derive(Debug, Clone, PartialEq, Eq, Hash)]
587#[repr(C)]
588pub struct FilePath {
589    pub inner: AzString,
590}
591
592// Result type for FilePath operations (must be after FilePath definition)
593impl_result!(
594    FilePath,
595    FileError,
596    ResultFilePathFileError,
597    copy = false,
598    [Debug, Clone, PartialEq]
599);
600
601// Option type for FilePath
602impl_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    /// Creates a new path from a string
612    pub fn new(path: AzString) -> Self {
613        Self { inner: path }
614    }
615
616    /// Creates an empty path
617    pub fn empty() -> Self {
618        Self::default()
619    }
620
621    /// Creates a path from a string slice
622    pub fn from_str(s: &str) -> Self {
623        Self { inner: AzString::from(String::from(s)) }
624    }
625
626    /// Returns the system temporary directory
627    #[cfg(feature = "std")]
628    pub fn get_temp_dir() -> Self {
629        Self { inner: temp_dir() }
630    }
631
632    /// Returns the current working directory
633    #[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    /// Returns the user's home directory (e.g., /home/username on Linux, C:\Users\username on Windows)
642    #[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    /// Returns the user's cache directory (e.g., ~/.cache on Linux, ~/Library/Caches on macOS)
648    #[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    /// Returns the user's config directory (e.g., ~/.config on Linux, ~/Library/Application Support on macOS)
654    #[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    /// Returns the user's local config directory (e.g., ~/.config on Linux, ~/Library/Application Support on macOS)
660    #[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    /// Returns the user's data directory (e.g., ~/.local/share on Linux, ~/Library/Application Support on macOS)
666    #[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    /// Returns the user's local data directory (e.g., ~/.local/share on Linux, ~/Library/Application Support on macOS)
672    #[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    /// Returns the user's desktop directory (e.g., ~/Desktop)
678    #[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    /// Returns the user's documents directory (e.g., ~/Documents)
684    #[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    /// Returns the user's downloads directory (e.g., ~/Downloads)
690    #[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    /// Returns the user's executable directory (e.g., ~/.local/bin on Linux)
696    #[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    /// Returns the user's font directory (e.g., ~/.local/share/fonts on Linux, ~/Library/Fonts on macOS)
702    #[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    /// Returns the user's pictures directory (e.g., ~/Pictures)
708    #[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    /// Returns the user's preference directory (e.g., ~/.config on Linux, ~/Library/Preferences on macOS)
714    #[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    /// Returns the user's public directory (e.g., ~/Public)
720    #[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    /// Returns the user's runtime directory (e.g., /run/user/1000 on Linux)
726    #[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    /// Returns the user's state directory (e.g., ~/.local/state on Linux)
732    #[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    /// Returns the user's audio directory (e.g., ~/Music)
738    #[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    /// Returns the user's video directory (e.g., ~/Videos)
744    #[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    /// Returns the user's templates directory
750    #[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    /// Joins this path with another path component
756    pub fn join(&self, other: &FilePath) -> FilePath {
757        FilePath { inner: path_join(self.inner.as_str(), other.inner.as_str()) }
758    }
759
760    /// Joins this path with a string component  
761    pub fn join_str(&self, component: &AzString) -> FilePath {
762        FilePath { inner: path_join(self.inner.as_str(), component.as_str()) }
763    }
764
765    /// Returns the parent directory of this path
766    #[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    /// Returns the file name component of this path
772    #[cfg(feature = "std")]
773    pub fn file_name(&self) -> Option<AzString> {
774        path_file_name(self.inner.as_str())
775    }
776
777    /// Returns the file extension of this path
778    #[cfg(feature = "std")]
779    pub fn extension(&self) -> Option<AzString> {
780        path_extension(self.inner.as_str())
781    }
782
783    /// Checks if the path exists on the filesystem
784    #[cfg(feature = "std")]
785    pub fn exists(&self) -> bool {
786        path_exists(self.inner.as_str())
787    }
788
789    /// Checks if the path is a file
790    #[cfg(feature = "std")]
791    pub fn is_file(&self) -> bool {
792        path_is_file(self.inner.as_str())
793    }
794
795    /// Checks if the path is a directory
796    #[cfg(feature = "std")]
797    pub fn is_dir(&self) -> bool {
798        path_is_dir(self.inner.as_str())
799    }
800
801    /// Checks if the path is absolute
802    #[cfg(feature = "std")]
803    pub fn is_absolute(&self) -> bool {
804        Path::new(self.inner.as_str()).is_absolute()
805    }
806
807    /// Creates this directory and all parent directories
808    #[cfg(feature = "std")]
809    pub fn create_dir_all(&self) -> Result<Void, FileError> {
810        dir_create_all(self.inner.as_str())
811    }
812
813    /// Creates this directory (parent must exist)
814    #[cfg(feature = "std")]
815    pub fn create_dir(&self) -> Result<Void, FileError> {
816        dir_create(self.inner.as_str())
817    }
818
819    /// Removes this file
820    #[cfg(feature = "std")]
821    pub fn remove_file(&self) -> Result<Void, FileError> {
822        file_delete(self.inner.as_str())
823    }
824
825    /// Removes this directory (must be empty)
826    #[cfg(feature = "std")]
827    pub fn remove_dir(&self) -> Result<Void, FileError> {
828        dir_delete(self.inner.as_str())
829    }
830
831    /// Removes this directory and all contents
832    #[cfg(feature = "std")]
833    pub fn remove_dir_all(&self) -> Result<Void, FileError> {
834        dir_delete_all(self.inner.as_str())
835    }
836
837    /// Reads the entire file at this path as bytes
838    #[cfg(feature = "std")]
839    pub fn read_bytes(&self) -> Result<U8Vec, FileError> {
840        file_read(self.inner.as_str())
841    }
842
843    /// Reads the entire file at this path as a string
844    #[cfg(feature = "std")]
845    pub fn read_string(&self) -> Result<AzString, FileError> {
846        file_read_string(self.inner.as_str())
847    }
848
849    /// Writes bytes to the file at this path
850    #[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    /// Writes a string to the file at this path
856    #[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    /// Copies a file from this path to another path
862    #[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    /// Renames/moves a file from this path to another path
868    #[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    /// Returns the path as a string reference
874    pub fn as_str(&self) -> &str {
875        self.inner.as_str()
876    }
877
878    /// Returns the path as an AzString
879    pub fn as_string(&self) -> AzString {
880        self.inner.clone()
881    }
882
883    /// Lists directory contents
884    #[cfg(feature = "std")]
885    pub fn read_dir(&self) -> Result<DirEntryVec, FileError> {
886        dir_list(self.inner.as_str())
887    }
888
889    /// Returns metadata about the file/directory
890    #[cfg(feature = "std")]
891    pub fn metadata(&self) -> Result<FileMetadata, FileError> {
892        file_metadata(self.inner.as_str())
893    }
894
895    /// Makes the path canonical (absolute, with no `.` or `..` components)
896    #[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/// Create a temporary file and return its path
921/// 
922/// # Arguments
923/// * `prefix` - Prefix for the temp file name
924/// 
925/// # Returns
926/// * `Result<AzString, FileError>` - Path to created temp file
927#[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    // Create empty file
940    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}