cadmus_core/task/
thumbnail.rs1use std::sync::mpsc::Sender;
4
5use crate::db::Database;
6use crate::device::CURRENT_DEVICE;
7use crate::document::open;
8use crate::library::Library;
9use crate::settings::Settings;
10use crate::task::{BackgroundTask, ShutdownSignal, TaskId};
11use crate::unit::scale_by_dpi;
12use crate::view::Event;
13use crate::view::BIG_BAR_HEIGHT;
14
15pub struct ThumbnailExtractionTask {
17 database: Database,
18 settings: Settings,
19 library_index: Option<usize>,
21}
22
23impl ThumbnailExtractionTask {
24 pub fn new(database: Database, settings: Settings, library_index: Option<usize>) -> Self {
25 Self {
26 database,
27 settings,
28 library_index,
29 }
30 }
31
32 #[cfg_attr(feature = "tracing", tracing::instrument(skip(hub, shutdown, self)))]
33 fn run_for_index(&self, index: usize, hub: &Sender<Event>, shutdown: &ShutdownSignal) {
34 let lib_settings = match self.settings.libraries.get(index) {
35 Some(s) => s,
36 None => {
37 tracing::warn!(
38 library_index = index,
39 "library index out of range, skipping"
40 );
41 return;
42 }
43 };
44
45 let library = match Library::new(&lib_settings.path, &self.database, &lib_settings.name) {
46 Ok(lib) => lib,
47 Err(e) => {
48 tracing::error!(error = %e, library_index = index, "failed to open library for thumbnail extraction");
49 return;
50 }
51 };
52
53 let books = match library.db.books_without_thumbnails(library.library_id) {
54 Ok(books) => books,
55 Err(e) => {
56 tracing::error!(error = %e, library_id = library.library_id, "failed to query books without thumbnails");
57 return;
58 }
59 };
60
61 if books.is_empty() {
62 tracing::debug!(
63 library_id = library.library_id,
64 "no missing thumbnails for library"
65 );
66 return;
67 }
68
69 tracing::info!(
70 library_id = library.library_id,
71 count = books.len(),
72 "starting thumbnail extraction for library"
73 );
74
75 let dpi = CURRENT_DEVICE.dpi;
76 let big_height = scale_by_dpi(BIG_BAR_HEIGHT, dpi) as i32;
77 let th = big_height;
78 let tw = 3 * th / 4;
79
80 for (fp, path) in books {
81 if shutdown.should_stop() {
82 tracing::info!("thumbnail extraction task shutdown requested, stopping");
83 return;
84 }
85
86 let full_path = library.home.join(&path);
87 tracing::debug!(path = %path.display(), "extracting thumbnail");
88
89 match open(&full_path)
90 .and_then(|mut doc| {
91 doc.preview_pixmap(tw as f32, th as f32, CURRENT_DEVICE.color_samples())
92 })
93 .and_then(|pixmap| pixmap.to_png_bytes().ok())
94 {
95 Some(bytes) => {
96 if let Err(e) = library.db.save_thumbnail(fp, &bytes) {
97 tracing::error!(error = %e, path = %path.display(), "failed to save thumbnail to database");
98 } else {
99 hub.send(Event::RefreshBookPreview(path)).ok();
100 }
101 }
102 None => {
103 tracing::warn!(path = %path.display(), "failed to extract preview for book");
104 }
105 }
106 }
107 }
108}
109
110impl BackgroundTask for ThumbnailExtractionTask {
111 fn id(&self) -> TaskId {
112 TaskId::ThumbnailExtraction
113 }
114
115 #[cfg_attr(feature = "tracing", tracing::instrument(skip_all))]
116 fn run(&mut self, hub: &Sender<Event>, shutdown: &ShutdownSignal) {
117 match self.library_index {
118 Some(index) => {
119 self.run_for_index(index, hub, shutdown);
120 }
121 None => {
122 for index in 0..self.settings.libraries.len() {
123 if shutdown.should_stop() {
124 return;
125 }
126 self.run_for_index(index, hub, shutdown);
127 }
128 }
129 }
130 }
131
132 fn finished_event(&self) -> Option<Event> {
133 Some(Event::ThumbnailExtractionFinished {
134 library_index: self.library_index,
135 })
136 }
137}