generic_functions/
generic_functions.rs

1//! Writing generic functions with anyfs-backend trait bounds.
2//!
3//! This example demonstrates how to write reusable code that works
4//! with ANY filesystem backend by using trait bounds.
5//!
6//! Run with: `cargo run --example generic_functions`
7
8use anyfs_backend::*;
9use std::collections::HashMap;
10use std::ffi::OsStr;
11use std::io::{Read, Write};
12use std::path::{Path, PathBuf};
13use std::sync::atomic::{AtomicU64, Ordering};
14use std::sync::RwLock;
15use std::time::SystemTime;
16
17// =============================================================================
18// Pattern 1: Generic functions with `Fs` bound (most common)
19// =============================================================================
20
21/// Copy a file's contents, works with ANY backend implementing `Fs`.
22///
23/// This is the most common pattern - just require `Fs` for basic operations.
24fn copy_file<B: Fs>(fs: &B, from: &Path, to: &Path) -> Result<(), FsError> {
25    let data = fs.read(from)?;
26    fs.write(to, &data)?;
27    Ok(())
28}
29
30/// Count all files recursively in a directory.
31fn count_files_recursive<B: Fs>(fs: &B, dir: &Path) -> Result<usize, FsError> {
32    let mut count = 0;
33    for entry in fs.read_dir(dir)? {
34        let entry = entry?;
35        match entry.file_type {
36            FileType::File => count += 1,
37            FileType::Directory => {
38                count += count_files_recursive(fs, &entry.path)?;
39            }
40            FileType::Symlink => {} // Skip symlinks in basic count
41        }
42    }
43    Ok(count)
44}
45
46/// Find files matching a predicate.
47fn find_files<B: Fs>(
48    fs: &B,
49    dir: &Path,
50    predicate: &dyn Fn(&DirEntry) -> bool,
51) -> Result<Vec<PathBuf>, FsError> {
52    let mut results = Vec::new();
53    for entry in fs.read_dir(dir)? {
54        let entry = entry?;
55        if predicate(&entry) {
56            results.push(entry.path.clone());
57        }
58        if entry.file_type == FileType::Directory {
59            results.extend(find_files(fs, &entry.path, predicate)?);
60        }
61    }
62    Ok(results)
63}
64
65// =============================================================================
66// Pattern 2: Multiple trait bounds (Fs + FsLink)
67// =============================================================================
68
69/// Create a backup with a symlink to the original.
70///
71/// Requires both `Fs` (for read/write) and `FsLink` (for symlinks).
72fn backup_with_link<B: Fs + FsLink>(fs: &B, path: &Path) -> Result<PathBuf, FsError> {
73    let backup_path = PathBuf::from(format!("{}.bak", path.display()));
74
75    // Copy the file
76    let data = fs.read(path)?;
77    fs.write(&backup_path, &data)?;
78
79    // Create a symlink pointing to the backup
80    let link_path = PathBuf::from(format!("{}.latest", path.display()));
81    // Remove old link if exists (ignore errors)
82    let _ = fs.remove_file(&link_path);
83    fs.symlink(&backup_path, &link_path)?;
84
85    Ok(backup_path)
86}
87
88/// Resolve all symlinks in a path and return the real path.
89fn resolve_symlinks<B: Fs + FsLink>(fs: &B, path: &Path) -> Result<PathBuf, FsError> {
90    let mut current = path.to_path_buf();
91    let mut seen = std::collections::HashSet::new();
92
93    loop {
94        if !seen.insert(current.clone()) {
95            // Detected a symlink loop
96            return Err(FsError::InvalidData {
97                path: current,
98                details: "symlink loop detected".into(),
99            });
100        }
101
102        match fs.symlink_metadata(&current) {
103            Ok(meta) if meta.file_type == FileType::Symlink => {
104                let target = fs.read_link(&current)?;
105                current = if target.is_absolute() {
106                    target
107                } else {
108                    current.parent().unwrap_or(Path::new("/")).join(target)
109                };
110            }
111            Ok(_) => return Ok(current),
112            Err(e) => return Err(e),
113        }
114    }
115}
116
117// =============================================================================
118// Pattern 3: Using composite traits (FsFull, FsFuse, FsPosix)
119// =============================================================================
120
121/// Get filesystem statistics and report usage.
122///
123/// Requires `FsFull` which includes `FsStats`.
124fn report_usage<B: FsFull>(fs: &B) -> Result<String, FsError> {
125    let stats = fs.statfs()?;
126    let used_percent = (stats.used_bytes as f64 / stats.total_bytes as f64) * 100.0;
127
128    Ok(format!(
129        "Disk usage: {:.1}% ({} / {} bytes)",
130        used_percent, stats.used_bytes, stats.total_bytes
131    ))
132}
133
134/// Navigate filesystem by inode (FUSE-style).
135///
136/// Requires `FsFuse` which includes `FsInode`.
137fn list_by_inode<B: FsFuse>(fs: &B, inode: u64) -> Result<Vec<(String, u64)>, FsError> {
138    let path = fs.inode_to_path(inode)?;
139    let mut entries = Vec::new();
140
141    for entry in fs.read_dir(&path)? {
142        let entry = entry?;
143        let child_inode = fs.path_to_inode(&entry.path)?;
144        entries.push((entry.name, child_inode));
145    }
146
147    Ok(entries)
148}
149
150/// Perform atomic file write with locking.
151///
152/// Requires `FsPosix` which includes `FsHandles` and `FsLock`.
153fn atomic_write<B: FsPosix>(fs: &B, path: &Path, data: &[u8]) -> Result<(), FsError> {
154    // Open with write flag
155    let handle = fs.open(path, OpenFlags::WRITE)?;
156
157    // Lock exclusively
158    fs.lock(handle, LockType::Exclusive)?;
159
160    // Write data
161    fs.write_at(handle, data, 0)?;
162
163    // Unlock and close
164    fs.unlock(handle)?;
165    fs.close(handle)?;
166
167    Ok(())
168}
169
170// =============================================================================
171// Pattern 4: Trait objects for runtime polymorphism
172// =============================================================================
173
174/// Process files using a trait object.
175///
176/// Useful when the backend type isn't known at compile time.
177fn process_with_trait_object(fs: &dyn Fs, files: &[&Path]) -> Result<u64, FsError> {
178    let mut total_size = 0;
179    for path in files {
180        let meta = fs.metadata(path)?;
181        total_size += meta.size;
182    }
183    Ok(total_size)
184}
185
186/// Store multiple backends with different types.
187struct MultiBackend {
188    backends: Vec<Box<dyn Fs>>,
189}
190
191impl MultiBackend {
192    fn new() -> Self {
193        Self {
194            backends: Vec::new(),
195        }
196    }
197
198    fn add<B: Fs + 'static>(&mut self, backend: B) {
199        self.backends.push(Box::new(backend));
200    }
201
202    fn find_file(&self, name: &str) -> Option<(usize, PathBuf)> {
203        for (i, fs) in self.backends.iter().enumerate() {
204            if let Ok(entries) = fs.read_dir(Path::new("/")) {
205                for entry in entries.flatten() {
206                    if entry.name == name {
207                        return Some((i, entry.path));
208                    }
209                }
210            }
211        }
212        None
213    }
214}
215
216// =============================================================================
217// Pattern 5: Extension traits for custom functionality
218// =============================================================================
219
220// Note: This pattern is shown in the crate's FsExtJson trait.
221// To use it, enable the `serde` feature and use the FsExtJson extension trait.
222//
223// Example (with serde feature):
224// ```
225// use anyfs_backend::ext::FsExtJson;
226// let config: MyConfig = fs.read_json(Path::new("/config.json"))?;
227// fs.write_json(Path::new("/output.json"), &my_data)?;
228// ```
229
230// =============================================================================
231// Demo implementation (same as basic_usage.rs, but more complete)
232// =============================================================================
233
234struct DemoFs {
235    files: RwLock<HashMap<PathBuf, Vec<u8>>>,
236    dirs: RwLock<std::collections::HashSet<PathBuf>>,
237    symlinks: RwLock<HashMap<PathBuf, PathBuf>>,
238    inodes: RwLock<HashMap<PathBuf, u64>>,
239    inode_to_path: RwLock<HashMap<u64, PathBuf>>,
240    handles: RwLock<HashMap<u64, (PathBuf, OpenFlags)>>,
241    next_inode: AtomicU64,
242    next_handle: AtomicU64,
243}
244
245impl DemoFs {
246    fn new() -> Self {
247        let fs = Self {
248            files: RwLock::new(HashMap::new()),
249            dirs: RwLock::new(std::collections::HashSet::new()),
250            symlinks: RwLock::new(HashMap::new()),
251            inodes: RwLock::new(HashMap::new()),
252            inode_to_path: RwLock::new(HashMap::new()),
253            handles: RwLock::new(HashMap::new()),
254            next_inode: AtomicU64::new(2),
255            next_handle: AtomicU64::new(1),
256        };
257        fs.dirs.write().unwrap().insert(PathBuf::from("/"));
258        fs.assign_inode(Path::new("/"));
259        fs
260    }
261
262    fn assign_inode(&self, path: &Path) -> u64 {
263        let mut inodes = self.inodes.write().unwrap();
264        if let Some(&inode) = inodes.get(path) {
265            return inode;
266        }
267        let inode = if path == Path::new("/") {
268            ROOT_INODE
269        } else {
270            self.next_inode.fetch_add(1, Ordering::SeqCst)
271        };
272        inodes.insert(path.to_path_buf(), inode);
273        self.inode_to_path
274            .write()
275            .unwrap()
276            .insert(inode, path.to_path_buf());
277        inode
278    }
279
280    fn get_file_type(&self, path: &Path) -> Option<FileType> {
281        if self.symlinks.read().unwrap().contains_key(path) {
282            Some(FileType::Symlink)
283        } else if self.dirs.read().unwrap().contains(path) {
284            Some(FileType::Directory)
285        } else if self.files.read().unwrap().contains_key(path) {
286            Some(FileType::File)
287        } else {
288            None
289        }
290    }
291}
292
293// Implement all required traits (abbreviated for clarity)
294impl FsRead for DemoFs {
295    fn read(&self, path: &Path) -> Result<Vec<u8>, FsError> {
296        self.files
297            .read()
298            .unwrap()
299            .get(path)
300            .cloned()
301            .ok_or_else(|| FsError::NotFound {
302                path: path.to_path_buf(),
303            })
304    }
305    fn read_to_string(&self, path: &Path) -> Result<String, FsError> {
306        String::from_utf8(self.read(path)?).map_err(|_| FsError::InvalidData {
307            path: path.to_path_buf(),
308            details: "not UTF-8".into(),
309        })
310    }
311    fn read_range(&self, path: &Path, offset: u64, len: usize) -> Result<Vec<u8>, FsError> {
312        let data = self.read(path)?;
313        let start = offset as usize;
314        Ok(if start >= data.len() {
315            vec![]
316        } else {
317            data[start..(start + len).min(data.len())].to_vec()
318        })
319    }
320    fn exists(&self, path: &Path) -> Result<bool, FsError> {
321        Ok(self.get_file_type(path).is_some())
322    }
323    fn metadata(&self, path: &Path) -> Result<Metadata, FsError> {
324        let ft = self.get_file_type(path).ok_or_else(|| FsError::NotFound {
325            path: path.to_path_buf(),
326        })?;
327        let size = if ft == FileType::File {
328            self.files
329                .read()
330                .unwrap()
331                .get(path)
332                .map(|d| d.len() as u64)
333                .unwrap_or(0)
334        } else {
335            0
336        };
337        Ok(Metadata {
338            file_type: ft,
339            size,
340            permissions: Permissions::default_file(),
341            created: SystemTime::UNIX_EPOCH,
342            modified: SystemTime::UNIX_EPOCH,
343            accessed: SystemTime::UNIX_EPOCH,
344            inode: self.inodes.read().unwrap().get(path).copied().unwrap_or(0),
345            nlink: 1,
346        })
347    }
348    fn open_read(&self, path: &Path) -> Result<Box<dyn Read + Send>, FsError> {
349        Ok(Box::new(std::io::Cursor::new(self.read(path)?)))
350    }
351}
352
353impl FsWrite for DemoFs {
354    fn write(&self, path: &Path, data: &[u8]) -> Result<(), FsError> {
355        self.assign_inode(path);
356        self.files
357            .write()
358            .unwrap()
359            .insert(path.to_path_buf(), data.to_vec());
360        Ok(())
361    }
362    fn append(&self, path: &Path, data: &[u8]) -> Result<(), FsError> {
363        self.files
364            .write()
365            .unwrap()
366            .entry(path.to_path_buf())
367            .or_default()
368            .extend_from_slice(data);
369        self.assign_inode(path);
370        Ok(())
371    }
372    fn remove_file(&self, path: &Path) -> Result<(), FsError> {
373        self.files
374            .write()
375            .unwrap()
376            .remove(path)
377            .map(|_| ())
378            .ok_or_else(|| FsError::NotFound {
379                path: path.to_path_buf(),
380            })
381    }
382    fn rename(&self, from: &Path, to: &Path) -> Result<(), FsError> {
383        let data = self
384            .files
385            .write()
386            .unwrap()
387            .remove(from)
388            .ok_or_else(|| FsError::NotFound {
389                path: from.to_path_buf(),
390            })?;
391        self.files.write().unwrap().insert(to.to_path_buf(), data);
392        self.assign_inode(to);
393        Ok(())
394    }
395    fn copy(&self, from: &Path, to: &Path) -> Result<(), FsError> {
396        let data = self.read(from)?;
397        self.write(to, &data)
398    }
399    fn truncate(&self, path: &Path, size: u64) -> Result<(), FsError> {
400        self.files
401            .write()
402            .unwrap()
403            .get_mut(path)
404            .ok_or_else(|| FsError::NotFound {
405                path: path.to_path_buf(),
406            })?
407            .resize(size as usize, 0);
408        Ok(())
409    }
410    fn open_write(&self, _path: &Path) -> Result<Box<dyn Write + Send>, FsError> {
411        Ok(Box::new(std::io::Cursor::new(Vec::new())))
412    }
413}
414
415impl FsDir for DemoFs {
416    fn read_dir(&self, path: &Path) -> Result<ReadDirIter, FsError> {
417        if !self.dirs.read().unwrap().contains(path) {
418            return Err(FsError::NotFound {
419                path: path.to_path_buf(),
420            });
421        }
422        let mut entries = Vec::new();
423        for (fp, data) in self.files.read().unwrap().iter() {
424            if fp.parent() == Some(path) {
425                if let Some(name) = fp.file_name() {
426                    entries.push(Ok(DirEntry {
427                        name: name.to_string_lossy().into(),
428                        path: fp.clone(),
429                        file_type: FileType::File,
430                        size: data.len() as u64,
431                        inode: 0,
432                    }));
433                }
434            }
435        }
436        for dp in self.dirs.read().unwrap().iter() {
437            if dp.parent() == Some(path) && dp != path {
438                if let Some(name) = dp.file_name() {
439                    entries.push(Ok(DirEntry {
440                        name: name.to_string_lossy().into(),
441                        path: dp.clone(),
442                        file_type: FileType::Directory,
443                        size: 0,
444                        inode: 0,
445                    }));
446                }
447            }
448        }
449        Ok(ReadDirIter::from_vec(entries))
450    }
451    fn create_dir(&self, path: &Path) -> Result<(), FsError> {
452        if self.dirs.read().unwrap().contains(path) {
453            return Err(FsError::AlreadyExists {
454                path: path.to_path_buf(),
455                operation: "create_dir",
456            });
457        }
458        self.dirs.write().unwrap().insert(path.to_path_buf());
459        self.assign_inode(path);
460        Ok(())
461    }
462    fn create_dir_all(&self, path: &Path) -> Result<(), FsError> {
463        let mut current = PathBuf::new();
464        for c in path.components() {
465            current.push(c);
466            self.dirs.write().unwrap().insert(current.clone());
467            self.assign_inode(&current);
468        }
469        Ok(())
470    }
471    fn remove_dir(&self, path: &Path) -> Result<(), FsError> {
472        if !self.dirs.write().unwrap().remove(path) {
473            return Err(FsError::NotFound {
474                path: path.to_path_buf(),
475            });
476        }
477        Ok(())
478    }
479    fn remove_dir_all(&self, path: &Path) -> Result<(), FsError> {
480        self.dirs.write().unwrap().remove(path);
481        self.files
482            .write()
483            .unwrap()
484            .retain(|p, _| !p.starts_with(path));
485        Ok(())
486    }
487}
488
489impl FsLink for DemoFs {
490    fn symlink(&self, target: &Path, link: &Path) -> Result<(), FsError> {
491        self.symlinks
492            .write()
493            .unwrap()
494            .insert(link.to_path_buf(), target.to_path_buf());
495        self.assign_inode(link);
496        Ok(())
497    }
498    fn hard_link(&self, original: &Path, link: &Path) -> Result<(), FsError> {
499        let data = self.read(original)?;
500        self.write(link, &data)
501    }
502    fn read_link(&self, path: &Path) -> Result<PathBuf, FsError> {
503        self.symlinks
504            .read()
505            .unwrap()
506            .get(path)
507            .cloned()
508            .ok_or_else(|| FsError::InvalidData {
509                path: path.to_path_buf(),
510                details: "not a symlink".into(),
511            })
512    }
513    fn symlink_metadata(&self, path: &Path) -> Result<Metadata, FsError> {
514        let ft = self.get_file_type(path).ok_or_else(|| FsError::NotFound {
515            path: path.to_path_buf(),
516        })?;
517        Ok(Metadata {
518            file_type: ft,
519            size: 0,
520            permissions: Permissions::default_file(),
521            created: SystemTime::UNIX_EPOCH,
522            modified: SystemTime::UNIX_EPOCH,
523            accessed: SystemTime::UNIX_EPOCH,
524            inode: 0,
525            nlink: 1,
526        })
527    }
528}
529
530impl FsPermissions for DemoFs {
531    fn set_permissions(&self, path: &Path, _perm: Permissions) -> Result<(), FsError> {
532        if self.get_file_type(path).is_none() {
533            return Err(FsError::NotFound {
534                path: path.to_path_buf(),
535            });
536        }
537        Ok(())
538    }
539}
540
541impl FsSync for DemoFs {
542    fn sync(&self) -> Result<(), FsError> {
543        Ok(())
544    }
545    fn fsync(&self, path: &Path) -> Result<(), FsError> {
546        if self.get_file_type(path).is_none() {
547            return Err(FsError::NotFound {
548                path: path.to_path_buf(),
549            });
550        }
551        Ok(())
552    }
553}
554
555impl FsStats for DemoFs {
556    fn statfs(&self) -> Result<StatFs, FsError> {
557        Ok(StatFs {
558            total_bytes: 100 * 1024 * 1024,
559            used_bytes: 50 * 1024 * 1024,
560            available_bytes: 50 * 1024 * 1024,
561            total_inodes: 10000,
562            used_inodes: 1000,
563            available_inodes: 9000,
564            block_size: 4096,
565            max_name_len: 255,
566        })
567    }
568}
569
570impl FsInode for DemoFs {
571    fn path_to_inode(&self, path: &Path) -> Result<u64, FsError> {
572        self.inodes
573            .read()
574            .unwrap()
575            .get(path)
576            .copied()
577            .ok_or_else(|| FsError::NotFound {
578                path: path.to_path_buf(),
579            })
580    }
581    fn inode_to_path(&self, inode: u64) -> Result<PathBuf, FsError> {
582        self.inode_to_path
583            .read()
584            .unwrap()
585            .get(&inode)
586            .cloned()
587            .ok_or(FsError::InodeNotFound { inode })
588    }
589    fn lookup(&self, parent: u64, name: &OsStr) -> Result<u64, FsError> {
590        let parent_path = self.inode_to_path(parent)?;
591        self.path_to_inode(&parent_path.join(name))
592    }
593    fn metadata_by_inode(&self, inode: u64) -> Result<Metadata, FsError> {
594        self.metadata(&self.inode_to_path(inode)?)
595    }
596}
597
598impl FsHandles for DemoFs {
599    fn open(&self, path: &Path, flags: OpenFlags) -> Result<Handle, FsError> {
600        if flags.create && !self.files.read().unwrap().contains_key(path) {
601            self.write(path, &[])?;
602        } else if !self.files.read().unwrap().contains_key(path) {
603            return Err(FsError::NotFound {
604                path: path.to_path_buf(),
605            });
606        }
607        let id = self.next_handle.fetch_add(1, Ordering::SeqCst);
608        self.handles
609            .write()
610            .unwrap()
611            .insert(id, (path.to_path_buf(), flags));
612        Ok(Handle(id))
613    }
614    fn read_at(&self, handle: Handle, buf: &mut [u8], offset: u64) -> Result<usize, FsError> {
615        let handles = self.handles.read().unwrap();
616        let (path, flags) = handles
617            .get(&handle.0)
618            .ok_or(FsError::InvalidHandle { handle })?;
619        if !flags.read {
620            return Err(FsError::PermissionDenied {
621                path: path.clone(),
622                operation: "read",
623            });
624        }
625        let data = self
626            .files
627            .read()
628            .unwrap()
629            .get(path)
630            .cloned()
631            .unwrap_or_default();
632        let start = offset as usize;
633        if start >= data.len() {
634            return Ok(0);
635        }
636        let n = buf.len().min(data.len() - start);
637        buf[..n].copy_from_slice(&data[start..start + n]);
638        Ok(n)
639    }
640    fn write_at(&self, handle: Handle, data: &[u8], offset: u64) -> Result<usize, FsError> {
641        let handles = self.handles.read().unwrap();
642        let (path, flags) = handles
643            .get(&handle.0)
644            .ok_or(FsError::InvalidHandle { handle })?;
645        if !flags.write {
646            return Err(FsError::PermissionDenied {
647                path: path.clone(),
648                operation: "write",
649            });
650        }
651        let path = path.clone();
652        drop(handles);
653        let mut files = self.files.write().unwrap();
654        let file_data = files.entry(path).or_default();
655        let start = offset as usize;
656        if start + data.len() > file_data.len() {
657            file_data.resize(start + data.len(), 0);
658        }
659        file_data[start..start + data.len()].copy_from_slice(data);
660        Ok(data.len())
661    }
662    fn close(&self, handle: Handle) -> Result<(), FsError> {
663        self.handles
664            .write()
665            .unwrap()
666            .remove(&handle.0)
667            .map(|_| ())
668            .ok_or(FsError::InvalidHandle { handle })
669    }
670}
671
672impl FsLock for DemoFs {
673    fn lock(&self, handle: Handle, _lock_type: LockType) -> Result<(), FsError> {
674        if !self.handles.read().unwrap().contains_key(&handle.0) {
675            return Err(FsError::InvalidHandle { handle });
676        }
677        Ok(())
678    }
679    fn try_lock(&self, handle: Handle, _lock_type: LockType) -> Result<bool, FsError> {
680        if !self.handles.read().unwrap().contains_key(&handle.0) {
681            return Err(FsError::InvalidHandle { handle });
682        }
683        Ok(true)
684    }
685    fn unlock(&self, handle: Handle) -> Result<(), FsError> {
686        if !self.handles.read().unwrap().contains_key(&handle.0) {
687            return Err(FsError::InvalidHandle { handle });
688        }
689        Ok(())
690    }
691}
692
693impl FsXattr for DemoFs {
694    fn get_xattr(&self, path: &Path, _name: &str) -> Result<Vec<u8>, FsError> {
695        Err(FsError::NotFound {
696            path: path.to_path_buf(),
697        })
698    }
699    fn set_xattr(&self, _path: &Path, _name: &str, _value: &[u8]) -> Result<(), FsError> {
700        Ok(())
701    }
702    fn remove_xattr(&self, path: &Path, name: &str) -> Result<(), FsError> {
703        Err(FsError::XattrNotFound {
704            path: path.to_path_buf(),
705            name: name.to_string(),
706        })
707    }
708    fn list_xattr(&self, _path: &Path) -> Result<Vec<String>, FsError> {
709        Ok(vec![])
710    }
711}
712
713// =============================================================================
714// Main: Demonstrate all patterns
715// =============================================================================
716
717fn main() {
718    println!("=== Generic Functions Example ===\n");
719
720    let fs = DemoFs::new();
721
722    // Setup: Create some test files
723    fs.create_dir_all(Path::new("/project/src")).unwrap();
724    fs.write(Path::new("/project/README.md"), b"# My Project")
725        .unwrap();
726    fs.write(Path::new("/project/src/main.rs"), b"fn main() {}")
727        .unwrap();
728    fs.write(Path::new("/project/src/lib.rs"), b"pub fn hello() {}")
729        .unwrap();
730
731    // Pattern 1: Basic Fs operations
732    println!("Pattern 1: Functions with Fs bound");
733    copy_file(
734        &fs,
735        Path::new("/project/README.md"),
736        Path::new("/project/README.bak"),
737    )
738    .unwrap();
739    println!("  Copied README.md to README.bak");
740
741    let count = count_files_recursive(&fs, Path::new("/project")).unwrap();
742    println!("  Total files in /project: {count}");
743
744    let rs_files = find_files(&fs, Path::new("/project"), &|e| e.name.ends_with(".rs")).unwrap();
745    println!("  Rust files found: {rs_files:?}");
746
747    // Pattern 2: Fs + FsLink
748    println!("\nPattern 2: Functions with Fs + FsLink bounds");
749    let backup = backup_with_link(&fs, Path::new("/project/README.md")).unwrap();
750    println!("  Created backup at: {}", backup.display());
751
752    // Also demonstrate symlink resolution (the .latest link was created above)
753    let resolved = resolve_symlinks(&fs, Path::new("/project/README.md.latest")).unwrap();
754    println!("  Resolved symlink to: {}", resolved.display());
755
756    // Pattern 3: FsFull (includes FsStats)
757    println!("\nPattern 3: Functions with FsFull bound");
758    let usage = report_usage(&fs).unwrap();
759    println!("  {usage}");
760
761    // Pattern 3: FsFuse (includes FsInode)
762    println!("\nPattern 4: Functions with FsFuse bound");
763    let entries = list_by_inode(&fs, ROOT_INODE).unwrap();
764    println!("  Root directory contents by inode:");
765    for (name, inode) in entries {
766        println!("    {name}: inode {inode}");
767    }
768
769    // Pattern 3: FsPosix (includes FsHandles + FsLock)
770    println!("\nPattern 5: Functions with FsPosix bound");
771    atomic_write(&fs, Path::new("/project/config.txt"), b"key=value").unwrap();
772    println!("  Wrote config.txt atomically with locking");
773
774    // Pattern 4: Trait objects
775    println!("\nPattern 6: Trait objects for runtime polymorphism");
776    let files = [
777        Path::new("/project/README.md"),
778        Path::new("/project/src/main.rs"),
779    ];
780    let total = process_with_trait_object(&fs, &files).unwrap();
781    println!("  Total size of selected files: {total} bytes");
782
783    // Pattern 4b: Storing multiple backends
784    let mut multi = MultiBackend::new();
785    multi.add(DemoFs::new()); // Could add different backend types here
786    println!("  MultiBackend can store {} backends", multi.backends.len());
787    if let Some((idx, path)) = multi.find_file("README.md") {
788        println!("  Found file in backend {idx}: {}", path.display());
789    }
790
791    // Pattern 5: Extension traits
792    // With the `serde` feature enabled, you can use FsExtJson for JSON operations.
793    // See the crate documentation for FsExtJson examples.
794
795    println!("\n=== All patterns demonstrated! ===");
796}