A custom OS for the xteink x4 ebook reader
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}