A custom OS for the xteink x4 ebook reader
at main 105 lines 3.7 kB view raw
1// Cover thumbnail cache: shared helpers for reading and writing 2// persistent 1-bit cover thumbnails on SD. 3// 4// Format is identical to the reader's inline image cache: 5// 4-byte header: u16 width (LE), u16 height (LE) 6// packed 1-bit pixel payload (stride = ceil(width/8)) 7// 8// Thumbnails live in the per-book `_PULP/_XXXXXXX/` directory 9// alongside chapter/image caches, under a fixed filename. 10 11use alloc::vec::Vec; 12 13use smol_epub::cache; 14 15use crate::kernel::KernelHandle; 16use crate::kernel::work_queue::DecodedImage; 17 18/// Fixed filename for the cover thumbnail inside the per-book cache directory. 19pub const COVER_THUMB_FILE: &str = "COVER.BIN"; 20 21/// Maximum width for the cover thumbnail (fits inside the home-screen card). 22pub const COVER_THUMB_MAX_W: u16 = 200; 23 24/// Maximum height for the cover thumbnail (fits inside the home-screen card). 25pub const COVER_THUMB_MAX_H: u16 = 240; 26 27/// Compute the per-book cache directory name from a book filename. 28/// 29/// Returns the 8-byte directory name buffer (e.g. `_1A2B3C4D`) that 30/// can be passed to `cache::dir_name_str()`. 31fn cache_dir_for_filename(filename: &[u8]) -> [u8; 8] { 32 let hash = cache::fnv1a(filename); 33 cache::dir_name_for_hash(hash) 34} 35 36/// Save a 1-bit cover thumbnail to the per-book cache directory. 37pub fn save_cover_thumb( 38 k: &mut KernelHandle<'_>, 39 dir: &str, 40 img: &DecodedImage, 41) -> crate::error::Result<()> { 42 let mut header = [0u8; 4]; 43 header[0..2].copy_from_slice(&img.width.to_le_bytes()); 44 header[2..4].copy_from_slice(&img.height.to_le_bytes()); 45 k.sd().write_in_plump_subdir(dir, COVER_THUMB_FILE, &header)?; 46 k.sd().append_in_plump_subdir(dir, COVER_THUMB_FILE, &img.data)?; 47 Ok(()) 48} 49 50/// Load a 1-bit cover thumbnail from the per-book cache directory. 51/// 52/// Returns `None` if the file doesn't exist or is invalid. 53pub fn load_cover_thumb(k: &mut KernelHandle<'_>, dir: &str) -> Option<DecodedImage> { 54 let size = k.sd().file_size_in_plump_subdir(dir, COVER_THUMB_FILE).ok()?; 55 if size < 5 { 56 return None; 57 } 58 let mut header = [0u8; 4]; 59 k.sd().read_chunk_in_plump_subdir(dir, COVER_THUMB_FILE, 0, &mut header) 60 .ok()?; 61 let width = u16::from_le_bytes([header[0], header[1]]); 62 let height = u16::from_le_bytes([header[2], header[3]]); 63 if width == 0 || height == 0 { 64 return None; 65 } 66 let stride = (width as usize).div_ceil(8); 67 let data_len = stride * height as usize; 68 if size as usize != 4 + data_len { 69 return None; 70 } 71 let mut data = Vec::new(); 72 data.try_reserve_exact(data_len).ok()?; 73 data.resize(data_len, 0); 74 k.sd().read_chunk_in_plump_subdir(dir, COVER_THUMB_FILE, 4, &mut data) 75 .ok()?; 76 Some(DecodedImage { 77 width, 78 height, 79 data, 80 stride, 81 }) 82} 83 84/// Check whether a cover thumbnail already exists for the given cache dir. 85pub fn has_cover_thumb(k: &mut KernelHandle<'_>, dir: &str) -> bool { 86 k.sd().file_size_in_plump_subdir(dir, COVER_THUMB_FILE) 87 .map(|s| s >= 5) 88 .unwrap_or(false) 89} 90 91// ── convenience helpers (filename → hash → dir → operation) ───────── 92 93/// Load a cover thumbnail by book filename (internalizes hash→dir). 94pub fn load_cover_for(k: &mut KernelHandle<'_>, filename: &[u8]) -> Option<DecodedImage> { 95 let dir_buf = cache_dir_for_filename(filename); 96 let dir = cache::dir_name_str(&dir_buf); 97 load_cover_thumb(k, dir) 98} 99 100/// Check whether a cover thumbnail exists by book filename. 101pub fn has_cover_for(k: &mut KernelHandle<'_>, filename: &[u8]) -> bool { 102 let dir_buf = cache_dir_for_filename(filename); 103 let dir = cache::dir_name_str(&dir_buf); 104 has_cover_thumb(k, dir) 105}