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