wallswitch/
lib.rs

1mod config;
2mod dependencies;
3mod desktops;
4mod dimension;
5mod environment;
6mod error;
7mod fileinfo;
8mod monitors;
9mod orientation;
10mod pids;
11mod traits;
12mod walkdir;
13
14pub use self::{
15    config::*, dependencies::*, desktops::*, dimension::*, environment::*, error::*, fileinfo::*,
16    monitors::*, orientation::*, pids::*, traits::*, walkdir::*,
17};
18
19// https://crates.io/crates/cfg-if
20cfg_if::cfg_if! {
21    if #[cfg(feature = "args_v2")] {
22        mod args_v2;
23        pub use args_v2::*;
24    } else {
25        // default: use "clap"
26        mod args_v1;
27        pub use args_v1::*;
28    }
29}
30
31// use rayon::prelude::*;
32use std::{
33    collections::HashMap,
34    env,
35    hash::{BuildHasher, Hasher, RandomState},
36    path::PathBuf,
37    thread,
38};
39
40/// Show initial messages
41pub fn show_initial_msgs(config: &Config) -> WallSwitchResult<()> {
42    let pkg_name = ENVIRON.get_pkg_name();
43    let pkg_desc = env!("CARGO_PKG_DESCRIPTION");
44    let pkg_version = env!("CARGO_PKG_VERSION");
45    let interval = config.interval;
46    let info = format!("Interval between each wallpaper: {interval} seconds.");
47    let author = "Claudio Fernandes de Souza Rodrigues (claudiofsrodrigues@gmail.com)";
48
49    println!("{pkg_name} {pkg_desc}\n{info}\n{author}");
50    println!("version: {pkg_version}\n");
51
52    let depend1 = "imagemagick (image viewing/manipulation program)";
53    let depend2 = "feh (fast and light image viewer)";
54    let dependencies = [depend1, depend2];
55
56    println!("Dependencies:");
57    dependencies.print_with_spaces(" ");
58    println!();
59
60    config.print()?;
61
62    Ok(())
63}
64
65/// Get unique and random images/figures
66pub fn get_images(config: &Config) -> WallSwitchResult<Vec<FileInfo>> {
67    let mut images: Vec<FileInfo> = gather_files(config)?;
68
69    if images.is_empty() {
70        let directories = config.directories.clone();
71        let error = WallSwitchError::NoImages { paths: directories };
72        eprintln!("{error}");
73        //return Err(error.into());
74        std::process::exit(1);
75    }
76
77    let nimages: usize = images.len();
78
79    if nimages < config.monitors.len() {
80        let directories: Vec<PathBuf> = images.iter().map(|f| f.path.clone()).collect();
81        let error = WallSwitchError::InsufficientImages {
82            paths: directories,
83            nfiles: nimages,
84        };
85        eprintln!("{error}");
86        //return Err(error.into());
87        std::process::exit(1);
88    }
89
90    images.update_number();
91
92    if !config.sort {
93        shuffle(&mut images);
94    }
95
96    Ok(images)
97}
98
99/// Gather the files in `Vec<FileInfo>`
100///
101/// Identical files (same hash) are disregarded
102fn gather_files(config: &Config) -> WallSwitchResult<Vec<FileInfo>> {
103    let mut files: Vec<FileInfo> = Vec::new();
104    let mut group_by: HashMap<String, Vec<PathBuf>> = HashMap::new();
105
106    for dir in &config.directories {
107        // Get files from directory and update hashes
108        let mut infos: Vec<FileInfo> = get_files_from_directory(dir, config)?;
109        infos.update_hash()?;
110
111        for info in infos {
112            let hash: String = info.hash.clone();
113            let path: PathBuf = info.path.clone();
114
115            // Insert for the first time
116            if !group_by.contains_key(&hash) {
117                files.push(info);
118            }
119
120            group_by.entry(hash).or_default().push(path);
121        }
122    }
123
124    // Print identical file paths (if verbose mode is enabled)
125    if config.verbose {
126        group_by
127            .values()
128            .filter(|paths| paths.len() > 1)
129            .for_each(|paths| {
130                println!("{id}: {paths:#?}\n", id = "identical files".yellow());
131            });
132    }
133
134    Ok(files)
135}
136
137/**
138Shuffle the vector in place with the Fisher-Yates algorithm.
139
140```
141    use wallswitch::shuffle;
142
143    let mut strings = vec!["abc", "foo", "bar", "baz", "mm nn", "zzz"];
144
145    shuffle(&mut strings);
146
147    println!("strings: {:?}", strings);
148
149    let mut integers: Vec<u32> = (1..=20).collect();
150
151    shuffle(&mut integers);
152
153    println!("integers: {:?}", integers);
154```
155
156<https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle>
157
158<https://stackoverflow.com/questions/26033976/how-do-i-create-a-vec-from-a-range-and-shuffle-it>
159
160*/
161pub fn shuffle<T>(vec: &mut [T]) {
162    let n: usize = vec.len();
163    for i in 0..(n - 1) {
164        // Generate random index j, such that: i <= j < n
165        // The remainder (`%`) after division is always less than the divisor.
166        let j = (rand() as usize) % (n - i) + i;
167        vec.swap(i, j);
168    }
169}
170
171/// Generate a random integer value in the given range (min, max) inclusive.
172pub fn get_random_integer(min: u64, max: u64) -> u64 {
173    min + rand() % (max - min + 1)
174}
175
176/// Generate a random integer value in the given range (min, max) inclusive.
177///
178/// Return error if `min > max``
179pub fn get_random_integer_v2(min: u64, max: u64) -> WallSwitchResult<u64> {
180    if min > max {
181        Err(WallSwitchError::MinMax { min, max })
182    } else {
183        // The remainder (`%`) after division is always less than the divisor.
184        Ok(min + rand() % (max - min + 1))
185    }
186}
187
188/// Generate random numbers without external dependencies
189pub fn rand() -> u64 {
190    RandomState::new().build_hasher().finish()
191}
192
193/// Display found images
194pub fn display_files(files: &[FileInfo], config: &Config) {
195    let nfiles = files.len();
196    let ndigits = nfiles.count_chars();
197
198    if config.sort {
199        println!("{nfiles} images were found (sorted):");
200    } else {
201        println!("{nfiles} images were found (shuffled):");
202    }
203
204    for file in files.iter() {
205        println!(
206            "images[{n:0ndigits$}/{t}]: {p:?}",
207            n = file.number,
208            p = file.path,
209            t = file.total,
210        );
211    }
212    println!();
213}
214
215/**
216Update FileInfo images with dimension information
217
218Parallelize the computation using std::thread::scope
219
220<https://stackoverflow.com/questions/74590440/how-do-i-change-the-structure-in-the-thread>
221*/
222pub fn update_images(files: &[FileInfo], config: &Config) -> Vec<FileInfo> {
223    let mut owned_files: Vec<FileInfo> = files.to_vec();
224
225    thread::scope(|scope| {
226        for file in &mut owned_files {
227            scope.spawn(move || -> WallSwitchResult<()> {
228                //let id = thread::current().id();
229                //println!("identifier thread: {id:?}");
230                file.update_info(config)
231            });
232        }
233    });
234
235    owned_files
236}
237
238/*
239pub fn update_images_v2(
240    files: &[FileInfo],
241    config: &Config,
242) -> Vec<FileInfo> {
243    let mut owned_files: Vec<FileInfo> = files.to_vec();
244
245    thread::scope(|scope| {
246        let mut threads = Vec::new();
247
248        for file in &mut owned_files {
249            threads.push(scope.spawn(move || -> WallSwitchResult<()> {
250                //let id = thread::current().id();
251                //println!("identifier thread: {id:?}");
252                file.update_info(config)
253            }));
254        }
255
256        for thread in threads {
257            let _result = thread.join();
258        }
259    });
260
261    owned_files
262}
263
264pub fn update_images_v3(
265    files: &[FileInfo],
266    config: &Config,
267) -> WallSwitchResult<Vec<FileInfo>> {
268    files
269        .iter() // Sequential computing
270        //.par_iter() // Parallelize the computation using rayon
271        .cloned()
272        .map(|mut file| -> WallSwitchResult<FileInfo> {
273            //let thread = rayon::current_thread_index();
274            //println!("rayon thread id {:?}", thread);
275            file.update_info(config)?;
276            Ok(file)
277        })
278        .collect()
279}
280*/
281
282#[cfg(test)]
283mod test_lib {
284    use crate::*;
285
286    #[test]
287    /// `cargo test -- --show-output vec_shuffle`
288    fn vec_shuffle() {
289        let mut vec: Vec<u32> = (1..=100).collect();
290        shuffle(&mut vec);
291
292        println!("vec: {vec:?}");
293        assert_eq!(vec.len(), 100);
294    }
295
296    #[test]
297    /// `cargo test -- --show-output random_integers_v1`
298    ///
299    /// <https://stackoverflow.com/questions/48218459/how-do-i-generate-a-vector-of-random-numbers-in-a-range>
300    fn random_integers_v1() {
301        // Example: Get a random integer value in the range 1 to 20:
302        let value: u64 = get_random_integer(1, 20);
303
304        println!("integer: {value:?}");
305
306        // Generate a vector of 100 64-bit integer values in the range from 1 to 20,
307        // allowing duplicates:
308
309        let integers: Vec<u64> = (0..100).map(|_| get_random_integer(1, 20)).collect();
310
311        println!("integers: {integers:?}");
312
313        let condition_a = integers.iter().min() >= Some(&1);
314        let condition_b = integers.iter().max() <= Some(&20);
315
316        assert!(condition_a);
317        assert!(condition_b);
318        assert_eq!(integers.len(), 100);
319    }
320
321    #[test]
322    /// `cargo test -- --show-output random_integers_v2`
323    ///
324    /// <https://stackoverflow.com/questions/48218459/how-do-i-generate-a-vector-of-random-numbers-in-a-range>
325    fn random_integers_v2() -> WallSwitchResult<()> {
326        // Example: Get a random integer value in the range 1 to 20:
327        let value: u64 = get_random_integer_v2(1, 20)?;
328
329        println!("integer: {value:?}");
330
331        // Generate a vector of 100 64-bit integer values in the range from 1 to 20,
332        // allowing duplicates:
333
334        let integers: Vec<u64> = (0..100)
335            .map(|_| get_random_integer_v2(1, 20))
336            .collect::<Result<Vec<u64>, _>>()?;
337
338        println!("integers: {integers:?}");
339
340        let condition_a = integers.iter().min() >= Some(&1);
341        let condition_b = integers.iter().max() <= Some(&20);
342
343        assert!(condition_a);
344        assert!(condition_b);
345        assert_eq!(integers.len(), 100);
346
347        Ok(())
348    }
349
350    #[test]
351    /// `cargo test -- --show-output random_integers_v3`
352    fn random_integers_v3() -> WallSwitchResult<()> {
353        let result = get_random_integer_v2(21, 20).map_err(|err| {
354            eprintln!("{err}");
355            err
356        });
357        assert!(result.is_err());
358
359        let error = result.unwrap_err();
360        eprintln!("error: {error:?}");
361
362        Ok(())
363    }
364}