1use std::{
2 collections::VecDeque,
3 fs::create_dir_all,
4 path::{Path, PathBuf},
5 sync::{
6 atomic::{AtomicBool, Ordering},
7 Arc,
8 },
9 thread::{self, JoinHandle},
10 time::Duration,
11};
12
13use parking_lot::Mutex;
14
15use crate::modes::Thumbnail;
16use crate::{common::TMP_THUMBNAILS_DIR, log_info};
17
18#[derive(Debug)]
28pub struct ThumbnailManager {
29 queue: Arc<Mutex<VecDeque<PathBuf>>>,
30 is_empty: Arc<AtomicBool>,
31 _workers: Vec<Worker>,
32}
33
34impl Default for ThumbnailManager {
35 fn default() -> Self {
36 Self::create_thumbnail_dir_if_not_exist();
37 let num_workers = Self::default_thread_count();
38 let mut _workers = Vec::with_capacity(num_workers);
39 let queue = Arc::new(Mutex::new(VecDeque::new()));
40 let is_empty = Arc::new(AtomicBool::new(true));
41 for id in 0..num_workers {
42 _workers.push(Worker::new(id, queue.clone(), is_empty.clone()));
43 }
44
45 Self {
46 queue,
47 _workers,
48 is_empty,
49 }
50 }
51}
52
53impl ThumbnailManager {
54 fn create_thumbnail_dir_if_not_exist() {
55 if Path::new(TMP_THUMBNAILS_DIR).exists() {
56 return;
57 }
58 if let Err(error) = create_dir_all(TMP_THUMBNAILS_DIR) {
59 log_info!("Coudln't create {TMP_THUMBNAILS_DIR}. Error: {error}");
60 }
61 }
62
63 fn default_thread_count() -> usize {
64 thread::available_parallelism()
65 .map(|it| it.get().checked_sub(6).unwrap_or(1))
66 .unwrap_or(1)
67 }
68
69 pub fn enqueue(&self, mut videos: VecDeque<PathBuf>) {
73 let mut locked_queue = self.queue.lock();
74 log_info!(
76 "Enqueuing {len} videos to thumbnail queue",
77 len = videos.len()
78 );
79 locked_queue.append(&mut videos);
80 drop(locked_queue);
81 self.is_empty.store(false, Ordering::SeqCst);
82 }
83
84 pub fn clear(&self) {
88 let mut locked_queue = self.queue.lock();
89 locked_queue.clear();
90 drop(locked_queue);
91 }
92}
93
94#[derive(Debug)]
95pub struct Worker {
96 _handle: JoinHandle<()>,
97}
98
99impl Worker {
100 fn new(id: usize, queue: Arc<Mutex<VecDeque<PathBuf>>>, is_empty: Arc<AtomicBool>) -> Self {
101 let _handle = thread::spawn(move || Self::runner(id, queue, is_empty));
102 Self { _handle }
103 }
104
105 fn runner(id: usize, queue: Arc<Mutex<VecDeque<PathBuf>>>, is_empty: Arc<AtomicBool>) {
106 loop {
107 Self::advance_queue(id, &queue, &is_empty);
108 thread::sleep(Duration::from_millis(10));
109 }
110 }
111
112 fn advance_queue(id: usize, queue: &Arc<Mutex<VecDeque<PathBuf>>>, is_empty: &Arc<AtomicBool>) {
113 if is_empty.load(Ordering::SeqCst) {
114 return;
115 }
116 let mut locked_queue = queue.lock();
117 let Some(path) = locked_queue.pop_front() else {
118 return;
119 };
120 if locked_queue.is_empty() {
121 is_empty.store(true, Ordering::SeqCst);
122 }
123 drop(locked_queue);
124 log_info!("Worker {id} received task {p}", p = path.display());
125 Self::make_thumbnail(id, path);
126 }
127
128 fn make_thumbnail(id: usize, path: PathBuf) {
129 match Thumbnail::create_video(&path.to_string_lossy()) {
130 Ok(_) => log_info!("worker {id} thumbnail built successfully"),
131 Err(e) => log_info!("worker {id} error building thumbnail {e}"),
132 }
133 }
134}