1use crate::IconCache;
4use file_lock::FileLock;
5use memmap2::Mmap;
6use std::error::Error;
7use std::ops::Deref;
8use std::os::fd::AsRawFd;
9use std::path::Path;
10
11pub mod reexports {
13 pub use file_lock;
14 pub use memmap2;
15}
16
17#[derive(Debug)]
24pub struct OwnedIconCache {
25 pub lock: FileLock,
26 pub memmap: Mmap,
27}
28
29impl OwnedIconCache {
30 pub fn open(path: impl AsRef<Path>) -> std::io::Result<Self> {
35 Self::create(path, true)
36 }
37
38 pub fn open_non_blocking(path: impl AsRef<Path>) -> std::io::Result<Self> {
41 Self::create(path, false)
42 }
43
44 pub fn icon_cache<'a>(&'a self) -> Result<IconCache<'a>, Box<dyn Error + 'a>> {
48 let bytes = self.memmap.deref();
49
50 IconCache::new_from_bytes(bytes)
51 }
52
53 fn create(path: impl AsRef<Path>, blocking: bool) -> std::io::Result<Self> {
54 let path = path.as_ref();
55 let options = file_lock::FileOptions::new().read(true).write(false); let lock = FileLock::lock(path, blocking, options)?;
57
58 Self::from_lock(lock)
59 }
60
61 pub fn from_lock(lock: FileLock) -> std::io::Result<Self> {
63 let fd = lock.file.as_raw_fd();
64 let memmap = unsafe { Mmap::map(fd)? };
67
68 Ok(Self { lock, memmap })
69 }
70}
71
72#[cfg(test)]
73mod tests {
74 use crate::file::OwnedIconCache;
75 use crate::raw;
76 use crate::raw::Offset;
77 use std::error::Error;
78 use std::ops::Deref;
79 use std::sync::LazyLock;
80 use zerocopy::U16;
81
82 use mktemp::Temp;
83
84 static SAMPLE_INDEX_FILE: &[u8] = include_bytes!("../assets/icon-theme.cache");
85 static TEMP_FILE: LazyLock<Temp> = LazyLock::new(|| create_test_cache().unwrap());
86
87 fn create_test_cache() -> std::io::Result<Temp> {
88 let temp = Temp::new_file()?;
89
90 std::fs::write(temp.as_path(), SAMPLE_INDEX_FILE)?;
91
92 Ok(temp)
93 }
94
95 #[test]
96 fn open_test_file() -> std::io::Result<()> {
97 let path = TEMP_FILE.as_path();
98 let _file = OwnedIconCache::open_non_blocking(path)?;
99
100 Ok(())
101 }
102
103 #[test]
104 fn mmap_correct() -> Result<(), Box<dyn Error>> {
105 let path = TEMP_FILE.as_path();
106 let file = OwnedIconCache::open_non_blocking(path)?;
107
108 assert_eq!(file.memmap.deref(), SAMPLE_INDEX_FILE);
109
110 let icon_cache = file.icon_cache().unwrap();
111
112 assert_eq!(
113 icon_cache.header,
114 &raw::Header {
115 major_version: U16::new(1),
116 minor_version: U16::new(0),
117 hash: Offset::new(12),
118 directory_list: Offset::new(35812)
119 }
120 );
121
122 assert_eq!(
123 icon_cache.hash.n_buckets.get() as usize,
124 icon_cache.hash.icon.len(),
125 "claimed hash len is the same as parsed len"
126 );
127
128 assert_eq!(
129 icon_cache.directory_list.raw_list.n_directories.get() as usize,
130 icon_cache.directory_list.raw_list.directory.len(),
131 "claimed directory list len is the same as parsed len"
132 );
133
134 Ok(())
135 }
136}