2022-01-06 10:47:27 +13:00
|
|
|
use std::collections::{BTreeMap, HashMap};
|
2022-01-06 04:43:26 +13:00
|
|
|
use std::fs::File;
|
2020-11-03 09:56:07 +13:00
|
|
|
use std::io::prelude::*;
|
2022-01-06 10:47:27 +13:00
|
|
|
use std::io::{BufReader, BufWriter};
|
|
|
|
use std::path::{Path, PathBuf};
|
2021-11-28 08:49:20 +13:00
|
|
|
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
|
|
|
|
use std::sync::Arc;
|
|
|
|
use std::thread::sleep;
|
2022-01-06 04:43:26 +13:00
|
|
|
use std::time::{Duration, SystemTime};
|
2022-05-17 04:23:07 +12:00
|
|
|
use std::{mem, panic, thread};
|
2020-11-03 09:56:07 +13:00
|
|
|
|
2021-11-28 08:49:20 +13:00
|
|
|
use crossbeam_channel::Receiver;
|
2022-12-30 05:25:01 +13:00
|
|
|
use lofty::TaggedFileExt;
|
2022-05-24 05:04:28 +12:00
|
|
|
use lofty::{read_from, AudioFile, ItemKey};
|
2021-11-28 08:49:20 +13:00
|
|
|
use rayon::prelude::*;
|
2022-01-06 10:47:27 +13:00
|
|
|
use serde::{Deserialize, Serialize};
|
2021-11-28 08:49:20 +13:00
|
|
|
|
2022-07-28 17:29:50 +12:00
|
|
|
use crate::common::{create_crash_message, AUDIO_FILES_EXTENSIONS};
|
2022-01-14 18:34:43 +13:00
|
|
|
use crate::common::{open_cache_folder, Common, LOOP_DURATION};
|
2022-01-06 04:43:26 +13:00
|
|
|
use crate::common_dir_traversal::{CheckingMethod, DirTraversalBuilder, DirTraversalResult, FileEntry, ProgressData};
|
2020-11-03 09:56:07 +13:00
|
|
|
use crate::common_directory::Directories;
|
2021-12-18 07:29:37 +13:00
|
|
|
use crate::common_extensions::Extensions;
|
2020-11-03 09:56:07 +13:00
|
|
|
use crate::common_items::ExcludedItems;
|
|
|
|
use crate::common_messages::Messages;
|
|
|
|
use crate::common_traits::*;
|
2020-12-02 22:25:27 +13:00
|
|
|
|
2023-02-19 22:21:14 +13:00
|
|
|
#[derive(Eq, PartialEq, Clone, Debug, Copy)]
|
2020-11-03 09:56:07 +13:00
|
|
|
pub enum DeleteMethod {
|
|
|
|
None,
|
|
|
|
Delete,
|
|
|
|
}
|
|
|
|
|
2023-05-02 03:48:12 +12:00
|
|
|
#[derive(Eq, PartialEq, Clone, Debug, Copy)]
|
|
|
|
pub enum AudioCheckMethod {
|
|
|
|
Tags,
|
|
|
|
Content,
|
|
|
|
}
|
|
|
|
|
2020-11-03 09:56:07 +13:00
|
|
|
bitflags! {
|
2023-04-05 18:08:43 +12:00
|
|
|
#[derive(PartialEq, Copy, Clone, Debug)]
|
2020-11-03 09:56:07 +13:00
|
|
|
pub struct MusicSimilarity : u32 {
|
|
|
|
const NONE = 0;
|
|
|
|
|
2022-02-26 06:47:25 +13:00
|
|
|
const TRACK_TITLE = 0b1;
|
|
|
|
const TRACK_ARTIST = 0b10;
|
|
|
|
const YEAR = 0b100;
|
|
|
|
const LENGTH = 0b1000;
|
|
|
|
const GENRE = 0b10000;
|
2023-01-29 06:54:02 +13:00
|
|
|
const BITRATE = 0b10_0000;
|
2020-11-03 09:56:07 +13:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-01-06 10:47:27 +13:00
|
|
|
#[derive(Clone, Debug, Deserialize, Serialize)]
|
2022-01-06 04:43:26 +13:00
|
|
|
pub struct MusicEntry {
|
2020-11-03 09:56:07 +13:00
|
|
|
pub size: u64,
|
|
|
|
|
|
|
|
pub path: PathBuf,
|
|
|
|
pub modified_date: u64,
|
|
|
|
|
2022-02-26 06:47:25 +13:00
|
|
|
pub track_title: String,
|
|
|
|
pub track_artist: String,
|
|
|
|
pub year: String,
|
|
|
|
pub length: String,
|
|
|
|
pub genre: String,
|
|
|
|
pub bitrate: u32,
|
2020-11-03 09:56:07 +13:00
|
|
|
}
|
|
|
|
|
2022-01-06 04:43:26 +13:00
|
|
|
impl FileEntry {
|
2022-01-06 10:47:27 +13:00
|
|
|
fn to_music_entry(&self) -> MusicEntry {
|
2022-01-06 04:43:26 +13:00
|
|
|
MusicEntry {
|
|
|
|
size: self.size,
|
2022-01-06 10:47:27 +13:00
|
|
|
path: self.path.clone(),
|
2022-01-06 04:43:26 +13:00
|
|
|
modified_date: self.modified_date,
|
|
|
|
|
2023-01-29 06:54:02 +13:00
|
|
|
track_title: String::new(),
|
|
|
|
track_artist: String::new(),
|
|
|
|
year: String::new(),
|
|
|
|
length: String::new(),
|
|
|
|
genre: String::new(),
|
2022-02-26 06:47:25 +13:00
|
|
|
bitrate: 0,
|
2022-01-06 04:43:26 +13:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-11-03 09:56:07 +13:00
|
|
|
/// Info struck with helpful information's about results
|
|
|
|
#[derive(Default)]
|
|
|
|
pub struct Info {
|
2021-12-24 21:18:55 +13:00
|
|
|
pub number_of_duplicates: usize,
|
|
|
|
pub number_of_groups: u64,
|
2020-11-03 09:56:07 +13:00
|
|
|
}
|
2021-11-28 08:57:10 +13:00
|
|
|
|
2020-11-03 09:56:07 +13:00
|
|
|
impl Info {
|
2023-01-29 06:54:02 +13:00
|
|
|
#[must_use]
|
2020-11-03 09:56:07 +13:00
|
|
|
pub fn new() -> Self {
|
|
|
|
Default::default()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Struct with required information's to work
|
|
|
|
pub struct SameMusic {
|
|
|
|
text_messages: Messages,
|
|
|
|
information: Info,
|
2022-01-06 10:47:27 +13:00
|
|
|
music_to_check: HashMap<String, MusicEntry>,
|
2022-01-06 04:43:26 +13:00
|
|
|
music_entries: Vec<MusicEntry>,
|
|
|
|
duplicated_music_entries: Vec<Vec<MusicEntry>>,
|
|
|
|
duplicated_music_entries_referenced: Vec<(MusicEntry, Vec<MusicEntry>)>,
|
2020-11-03 09:56:07 +13:00
|
|
|
directories: Directories,
|
2021-12-18 07:29:37 +13:00
|
|
|
allowed_extensions: Extensions,
|
2020-11-03 09:56:07 +13:00
|
|
|
excluded_items: ExcludedItems,
|
|
|
|
minimal_file_size: u64,
|
2021-08-07 09:23:11 +12:00
|
|
|
maximal_file_size: u64,
|
2020-11-03 09:56:07 +13:00
|
|
|
recursive_search: bool,
|
|
|
|
delete_method: DeleteMethod,
|
|
|
|
music_similarity: MusicSimilarity,
|
|
|
|
stopped_search: bool,
|
2021-12-04 21:29:29 +13:00
|
|
|
approximate_comparison: bool,
|
2022-01-06 10:47:27 +13:00
|
|
|
use_cache: bool,
|
|
|
|
delete_outdated_cache: bool, // TODO add this to GUI
|
2021-12-24 21:18:55 +13:00
|
|
|
use_reference_folders: bool,
|
2022-01-06 10:47:27 +13:00
|
|
|
save_also_as_json: bool,
|
2023-05-02 03:48:12 +12:00
|
|
|
check_type: AudioCheckMethod,
|
2020-11-03 09:56:07 +13:00
|
|
|
}
|
|
|
|
|
|
|
|
impl SameMusic {
|
2023-01-29 06:54:02 +13:00
|
|
|
#[must_use]
|
2020-11-03 09:56:07 +13:00
|
|
|
pub fn new() -> Self {
|
|
|
|
Self {
|
|
|
|
text_messages: Messages::new(),
|
|
|
|
information: Info::new(),
|
|
|
|
recursive_search: true,
|
|
|
|
directories: Directories::new(),
|
2021-12-18 07:29:37 +13:00
|
|
|
allowed_extensions: Extensions::new(),
|
2020-11-03 09:56:07 +13:00
|
|
|
excluded_items: ExcludedItems::new(),
|
2020-11-08 21:10:49 +13:00
|
|
|
music_entries: Vec::with_capacity(2048),
|
2020-11-03 09:56:07 +13:00
|
|
|
delete_method: DeleteMethod::None,
|
|
|
|
music_similarity: MusicSimilarity::NONE,
|
|
|
|
stopped_search: false,
|
2021-08-07 09:23:11 +12:00
|
|
|
minimal_file_size: 8192,
|
|
|
|
maximal_file_size: u64::MAX,
|
2020-11-03 09:56:07 +13:00
|
|
|
duplicated_music_entries: vec![],
|
2022-01-06 10:47:27 +13:00
|
|
|
music_to_check: Default::default(),
|
2021-12-04 21:29:29 +13:00
|
|
|
approximate_comparison: true,
|
2022-02-26 06:47:25 +13:00
|
|
|
use_cache: false,
|
2022-01-06 10:47:27 +13:00
|
|
|
delete_outdated_cache: true,
|
2021-12-24 21:18:55 +13:00
|
|
|
use_reference_folders: false,
|
|
|
|
duplicated_music_entries_referenced: vec![],
|
2022-01-06 10:47:27 +13:00
|
|
|
save_also_as_json: false,
|
2023-05-02 03:48:12 +12:00
|
|
|
check_type: AudioCheckMethod::Content,
|
2020-11-03 09:56:07 +13:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-04-14 18:23:59 +12:00
|
|
|
pub fn find_same_music(&mut self, stop_receiver: Option<&Receiver<()>>, progress_sender: Option<&futures::channel::mpsc::UnboundedSender<ProgressData>>) {
|
2020-11-03 09:56:07 +13:00
|
|
|
self.directories.optimize_directories(self.recursive_search, &mut self.text_messages);
|
2021-12-24 21:18:55 +13:00
|
|
|
self.use_reference_folders = !self.directories.reference_directories.is_empty();
|
2020-12-02 22:25:27 +13:00
|
|
|
if !self.check_files(stop_receiver, progress_sender) {
|
2020-11-03 09:56:07 +13:00
|
|
|
self.stopped_search = true;
|
|
|
|
return;
|
|
|
|
}
|
2023-05-02 03:48:12 +12:00
|
|
|
match self.check_type {
|
|
|
|
AudioCheckMethod::Tags => {
|
|
|
|
if !self.read_tags(stop_receiver, progress_sender) {
|
|
|
|
self.stopped_search = true;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if !self.check_for_duplicate_tags(stop_receiver, progress_sender) {
|
|
|
|
self.stopped_search = true;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
AudioCheckMethod::Content => {
|
|
|
|
unimplemented!();
|
|
|
|
}
|
2020-11-03 09:56:07 +13:00
|
|
|
}
|
|
|
|
self.delete_files();
|
|
|
|
self.debug_print();
|
|
|
|
}
|
|
|
|
|
2023-01-29 06:54:02 +13:00
|
|
|
#[must_use]
|
2020-11-03 09:56:07 +13:00
|
|
|
pub fn get_stopped_search(&self) -> bool {
|
|
|
|
self.stopped_search
|
|
|
|
}
|
|
|
|
|
2023-01-29 06:54:02 +13:00
|
|
|
#[must_use]
|
2022-01-06 04:43:26 +13:00
|
|
|
pub const fn get_duplicated_music_entries(&self) -> &Vec<Vec<MusicEntry>> {
|
2020-11-03 09:56:07 +13:00
|
|
|
&self.duplicated_music_entries
|
|
|
|
}
|
2023-01-29 06:54:02 +13:00
|
|
|
#[must_use]
|
2020-11-03 09:56:07 +13:00
|
|
|
pub const fn get_music_similarity(&self) -> &MusicSimilarity {
|
|
|
|
&self.music_similarity
|
|
|
|
}
|
|
|
|
|
2023-01-29 06:54:02 +13:00
|
|
|
#[must_use]
|
2020-11-03 09:56:07 +13:00
|
|
|
pub const fn get_text_messages(&self) -> &Messages {
|
|
|
|
&self.text_messages
|
|
|
|
}
|
|
|
|
|
2023-01-29 06:54:02 +13:00
|
|
|
#[must_use]
|
2020-11-03 09:56:07 +13:00
|
|
|
pub const fn get_information(&self) -> &Info {
|
|
|
|
&self.information
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn set_delete_method(&mut self, delete_method: DeleteMethod) {
|
|
|
|
self.delete_method = delete_method;
|
|
|
|
}
|
|
|
|
|
2022-01-06 10:47:27 +13:00
|
|
|
pub fn set_save_also_as_json(&mut self, save_also_as_json: bool) {
|
|
|
|
self.save_also_as_json = save_also_as_json;
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn set_use_cache(&mut self, use_cache: bool) {
|
|
|
|
self.use_cache = use_cache;
|
|
|
|
}
|
|
|
|
|
2021-12-04 21:29:29 +13:00
|
|
|
pub fn set_approximate_comparison(&mut self, approximate_comparison: bool) {
|
|
|
|
self.approximate_comparison = approximate_comparison;
|
|
|
|
}
|
|
|
|
|
2020-11-03 09:56:07 +13:00
|
|
|
pub fn set_recursive_search(&mut self, recursive_search: bool) {
|
|
|
|
self.recursive_search = recursive_search;
|
|
|
|
}
|
|
|
|
|
2022-04-24 06:21:46 +12:00
|
|
|
#[cfg(target_family = "unix")]
|
|
|
|
pub fn set_exclude_other_filesystems(&mut self, exclude_other_filesystems: bool) {
|
|
|
|
self.directories.set_exclude_other_filesystems(exclude_other_filesystems);
|
|
|
|
}
|
2022-07-20 05:09:52 +12:00
|
|
|
#[cfg(not(target_family = "unix"))]
|
|
|
|
pub fn set_exclude_other_filesystems(&mut self, _exclude_other_filesystems: bool) {}
|
2022-04-24 06:21:46 +12:00
|
|
|
|
2021-12-24 21:18:55 +13:00
|
|
|
/// Set included dir which needs to be relative, exists etc.
|
|
|
|
pub fn set_included_directory(&mut self, included_directory: Vec<PathBuf>) {
|
|
|
|
self.directories.set_included_directory(included_directory, &mut self.text_messages);
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn set_reference_directory(&mut self, reference_directory: Vec<PathBuf>) {
|
|
|
|
self.directories.set_reference_directory(reference_directory);
|
2020-11-03 09:56:07 +13:00
|
|
|
}
|
|
|
|
|
2021-01-11 08:44:10 +13:00
|
|
|
pub fn set_excluded_directory(&mut self, excluded_directory: Vec<PathBuf>) {
|
2020-11-03 09:56:07 +13:00
|
|
|
self.directories.set_excluded_directory(excluded_directory, &mut self.text_messages);
|
|
|
|
}
|
|
|
|
|
2021-01-11 08:44:10 +13:00
|
|
|
pub fn set_excluded_items(&mut self, excluded_items: Vec<String>) {
|
2020-11-03 09:56:07 +13:00
|
|
|
self.excluded_items.set_excluded_items(excluded_items, &mut self.text_messages);
|
|
|
|
}
|
2021-12-24 21:18:55 +13:00
|
|
|
|
2021-12-18 07:29:37 +13:00
|
|
|
pub fn set_allowed_extensions(&mut self, allowed_extensions: String) {
|
|
|
|
self.allowed_extensions.set_allowed_extensions(allowed_extensions, &mut self.text_messages);
|
|
|
|
}
|
2020-11-03 09:56:07 +13:00
|
|
|
|
|
|
|
pub fn set_music_similarity(&mut self, music_similarity: MusicSimilarity) {
|
|
|
|
self.music_similarity = music_similarity;
|
|
|
|
}
|
|
|
|
|
2021-08-07 09:23:11 +12:00
|
|
|
pub fn set_maximal_file_size(&mut self, maximal_file_size: u64) {
|
|
|
|
self.maximal_file_size = match maximal_file_size {
|
|
|
|
0 => 1,
|
|
|
|
t => t,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2023-01-29 06:54:02 +13:00
|
|
|
#[must_use]
|
2022-01-06 04:43:26 +13:00
|
|
|
pub fn get_similar_music_referenced(&self) -> &Vec<(MusicEntry, Vec<MusicEntry>)> {
|
2021-12-24 21:18:55 +13:00
|
|
|
&self.duplicated_music_entries_referenced
|
|
|
|
}
|
|
|
|
|
2023-01-29 06:54:02 +13:00
|
|
|
#[must_use]
|
2021-12-24 21:18:55 +13:00
|
|
|
pub fn get_number_of_base_duplicated_files(&self) -> usize {
|
|
|
|
if self.use_reference_folders {
|
|
|
|
self.duplicated_music_entries_referenced.len()
|
|
|
|
} else {
|
|
|
|
self.duplicated_music_entries.len()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-01-29 06:54:02 +13:00
|
|
|
#[must_use]
|
2021-12-24 21:18:55 +13:00
|
|
|
pub fn get_use_reference(&self) -> bool {
|
|
|
|
self.use_reference_folders
|
|
|
|
}
|
|
|
|
|
2021-04-14 18:23:59 +12:00
|
|
|
fn check_files(&mut self, stop_receiver: Option<&Receiver<()>>, progress_sender: Option<&futures::channel::mpsc::UnboundedSender<ProgressData>>) -> bool {
|
2022-01-01 10:34:24 +13:00
|
|
|
if !self.allowed_extensions.using_custom_extensions() {
|
2022-05-17 04:23:07 +12:00
|
|
|
self.allowed_extensions.extend_allowed_extensions(AUDIO_FILES_EXTENSIONS);
|
2022-05-24 05:04:28 +12:00
|
|
|
} else {
|
|
|
|
self.allowed_extensions.validate_allowed_extensions(AUDIO_FILES_EXTENSIONS);
|
|
|
|
if !self.allowed_extensions.using_custom_extensions() {
|
|
|
|
return true;
|
|
|
|
}
|
2022-01-01 10:34:24 +13:00
|
|
|
}
|
2022-05-24 05:04:28 +12:00
|
|
|
|
2022-01-06 04:43:26 +13:00
|
|
|
let result = DirTraversalBuilder::new()
|
|
|
|
.root_dirs(self.directories.included_directories.clone())
|
|
|
|
.group_by(|_fe| ())
|
|
|
|
.stop_receiver(stop_receiver)
|
|
|
|
.progress_sender(progress_sender)
|
|
|
|
.minimal_file_size(self.minimal_file_size)
|
|
|
|
.maximal_file_size(self.maximal_file_size)
|
|
|
|
.directories(self.directories.clone())
|
|
|
|
.allowed_extensions(self.allowed_extensions.clone())
|
|
|
|
.excluded_items(self.excluded_items.clone())
|
|
|
|
.recursive_search(self.recursive_search)
|
|
|
|
.max_stage(2)
|
|
|
|
.build()
|
|
|
|
.run();
|
|
|
|
match result {
|
|
|
|
DirTraversalResult::SuccessFiles {
|
|
|
|
start_time,
|
|
|
|
grouped_file_entries,
|
|
|
|
warnings,
|
|
|
|
} => {
|
|
|
|
if let Some(music_to_check) = grouped_file_entries.get(&()) {
|
2022-01-06 10:47:27 +13:00
|
|
|
for fe in music_to_check {
|
|
|
|
self.music_to_check.insert(fe.path.to_string_lossy().to_string(), fe.to_music_entry());
|
|
|
|
}
|
2020-12-02 22:25:27 +13:00
|
|
|
}
|
2021-12-18 07:29:37 +13:00
|
|
|
self.text_messages.warnings.extend(warnings);
|
2023-01-29 06:54:02 +13:00
|
|
|
Common::print_time(start_time, SystemTime::now(), "check_files");
|
2022-01-06 04:43:26 +13:00
|
|
|
true
|
|
|
|
}
|
|
|
|
DirTraversalResult::SuccessFolders { .. } => {
|
|
|
|
unreachable!()
|
2020-11-03 09:56:07 +13:00
|
|
|
}
|
2022-01-06 04:43:26 +13:00
|
|
|
DirTraversalResult::Stopped => false,
|
2020-11-03 09:56:07 +13:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-05-02 03:48:12 +12:00
|
|
|
fn read_tags(&mut self, stop_receiver: Option<&Receiver<()>>, progress_sender: Option<&futures::channel::mpsc::UnboundedSender<ProgressData>>) -> bool {
|
2020-11-08 21:10:49 +13:00
|
|
|
let start_time: SystemTime = SystemTime::now();
|
|
|
|
|
2022-01-06 10:47:27 +13:00
|
|
|
let loaded_hash_map;
|
|
|
|
|
|
|
|
let mut records_already_cached: HashMap<String, MusicEntry> = Default::default();
|
|
|
|
let mut non_cached_files_to_check: HashMap<String, MusicEntry> = Default::default();
|
|
|
|
|
|
|
|
if self.use_cache {
|
|
|
|
loaded_hash_map = match load_cache_from_file(&mut self.text_messages, self.delete_outdated_cache) {
|
|
|
|
Some(t) => t,
|
|
|
|
None => Default::default(),
|
|
|
|
};
|
|
|
|
|
|
|
|
for (name, file_entry) in &self.music_to_check {
|
|
|
|
#[allow(clippy::if_same_then_else)]
|
|
|
|
if !loaded_hash_map.contains_key(name) {
|
|
|
|
// If loaded data doesn't contains current image info
|
|
|
|
non_cached_files_to_check.insert(name.clone(), file_entry.clone());
|
|
|
|
} else if file_entry.size != loaded_hash_map.get(name).unwrap().size || file_entry.modified_date != loaded_hash_map.get(name).unwrap().modified_date {
|
|
|
|
// When size or modification date of image changed, then it is clear that is different image
|
|
|
|
non_cached_files_to_check.insert(name.clone(), file_entry.clone());
|
|
|
|
} else {
|
|
|
|
// Checking may be omitted when already there is entry with same size and modification date
|
|
|
|
records_already_cached.insert(name.clone(), loaded_hash_map.get(name).unwrap().clone());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
loaded_hash_map = Default::default();
|
|
|
|
mem::swap(&mut self.music_to_check, &mut non_cached_files_to_check);
|
|
|
|
}
|
|
|
|
|
2022-05-10 05:40:35 +12:00
|
|
|
let check_was_stopped = AtomicBool::new(false); // Used for breaking from GUI and ending check thread
|
2020-12-02 22:25:27 +13:00
|
|
|
|
|
|
|
//// PROGRESS THREAD START
|
|
|
|
let progress_thread_run = Arc::new(AtomicBool::new(true));
|
|
|
|
|
|
|
|
let atomic_file_counter = Arc::new(AtomicUsize::new(0));
|
|
|
|
|
2021-12-19 11:45:37 +13:00
|
|
|
let progress_thread_handle = if let Some(progress_sender) = progress_sender {
|
2021-04-14 18:23:59 +12:00
|
|
|
let progress_send = progress_sender.clone();
|
2020-12-02 22:25:27 +13:00
|
|
|
let progress_thread_run = progress_thread_run.clone();
|
|
|
|
let atomic_file_counter = atomic_file_counter.clone();
|
2022-01-06 10:47:27 +13:00
|
|
|
let music_to_check = non_cached_files_to_check.len();
|
2021-12-19 11:45:37 +13:00
|
|
|
thread::spawn(move || loop {
|
2020-12-02 22:25:27 +13:00
|
|
|
progress_send
|
2021-04-14 18:23:59 +12:00
|
|
|
.unbounded_send(ProgressData {
|
2022-01-06 04:43:26 +13:00
|
|
|
checking_method: CheckingMethod::None,
|
2020-12-02 22:25:27 +13:00
|
|
|
current_stage: 1,
|
|
|
|
max_stage: 2,
|
2022-11-24 08:23:17 +13:00
|
|
|
entries_checked: atomic_file_counter.load(Ordering::Relaxed),
|
2022-01-06 04:43:26 +13:00
|
|
|
entries_to_check: music_to_check,
|
2020-12-02 22:25:27 +13:00
|
|
|
})
|
|
|
|
.unwrap();
|
|
|
|
if !progress_thread_run.load(Ordering::Relaxed) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
sleep(Duration::from_millis(LOOP_DURATION as u64));
|
2021-12-19 11:45:37 +13:00
|
|
|
})
|
2020-12-02 22:25:27 +13:00
|
|
|
} else {
|
2021-12-19 11:45:37 +13:00
|
|
|
thread::spawn(|| {})
|
|
|
|
};
|
2020-12-02 22:25:27 +13:00
|
|
|
//// PROGRESS THREAD END
|
|
|
|
|
2022-01-06 04:43:26 +13:00
|
|
|
// Clean for duplicate files
|
2022-01-06 10:47:27 +13:00
|
|
|
let mut vec_file_entry = non_cached_files_to_check
|
2022-01-06 04:43:26 +13:00
|
|
|
.into_par_iter()
|
2022-01-06 10:47:27 +13:00
|
|
|
.map(|(path, mut music_entry)| {
|
2020-12-02 22:25:27 +13:00
|
|
|
atomic_file_counter.fetch_add(1, Ordering::Relaxed);
|
2020-11-08 21:10:49 +13:00
|
|
|
if stop_receiver.is_some() && stop_receiver.unwrap().try_recv().is_ok() {
|
2022-05-10 05:40:35 +12:00
|
|
|
check_was_stopped.store(true, Ordering::Relaxed);
|
2020-11-08 21:10:49 +13:00
|
|
|
return None;
|
|
|
|
}
|
|
|
|
|
2023-03-06 08:54:02 +13:00
|
|
|
let Ok(mut file) = File::open(&path) else{return Some(None)};
|
2022-05-24 05:04:28 +12:00
|
|
|
|
|
|
|
let result = panic::catch_unwind(move || {
|
2022-11-24 08:23:17 +13:00
|
|
|
match read_from(&mut file) {
|
2022-05-17 04:23:07 +12:00
|
|
|
Ok(t) => Some(t),
|
|
|
|
Err(_inspected) => {
|
|
|
|
// println!("Failed to open {}", path);
|
|
|
|
None
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2023-01-29 06:54:02 +13:00
|
|
|
let tagged_file = if let Ok(t) = result {
|
|
|
|
match t {
|
2022-05-17 04:23:07 +12:00
|
|
|
Some(r) => r,
|
|
|
|
None => {
|
|
|
|
return Some(Some(music_entry));
|
|
|
|
}
|
2022-02-26 06:47:25 +13:00
|
|
|
}
|
2023-01-29 06:54:02 +13:00
|
|
|
} else {
|
|
|
|
let message = create_crash_message("Lofty", &path, "https://github.com/image-rs/image/issues");
|
|
|
|
println!("{message}");
|
|
|
|
return Some(None);
|
2020-11-08 21:10:49 +13:00
|
|
|
};
|
|
|
|
|
2022-02-26 06:47:25 +13:00
|
|
|
let properties = tagged_file.properties();
|
|
|
|
|
2023-01-29 06:54:02 +13:00
|
|
|
let mut track_title = String::new();
|
|
|
|
let mut track_artist = String::new();
|
|
|
|
let mut year = String::new();
|
|
|
|
let mut genre = String::new();
|
2022-04-29 00:40:21 +12:00
|
|
|
|
|
|
|
let bitrate = properties.audio_bitrate().unwrap_or(0);
|
2022-05-17 04:23:07 +12:00
|
|
|
let mut length = properties.duration().as_millis().to_string();
|
2022-04-29 00:40:21 +12:00
|
|
|
|
2022-05-17 04:23:07 +12:00
|
|
|
if let Some(tag) = tagged_file.primary_tag() {
|
2022-04-29 00:40:21 +12:00
|
|
|
track_title = tag.get_string(&ItemKey::TrackTitle).unwrap_or("").to_string();
|
|
|
|
track_artist = tag.get_string(&ItemKey::TrackArtist).unwrap_or("").to_string();
|
|
|
|
year = tag.get_string(&ItemKey::Year).unwrap_or("").to_string();
|
|
|
|
genre = tag.get_string(&ItemKey::Genre).unwrap_or("").to_string();
|
|
|
|
}
|
2022-02-26 06:47:25 +13:00
|
|
|
|
2022-04-29 00:40:21 +12:00
|
|
|
for tag in tagged_file.tags() {
|
|
|
|
if track_title.is_empty() {
|
|
|
|
if let Some(tag_value) = tag.get_string(&ItemKey::TrackTitle) {
|
|
|
|
track_title = tag_value.to_string();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if track_artist.is_empty() {
|
|
|
|
if let Some(tag_value) = tag.get_string(&ItemKey::TrackArtist) {
|
|
|
|
track_artist = tag_value.to_string();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if year.is_empty() {
|
|
|
|
if let Some(tag_value) = tag.get_string(&ItemKey::Year) {
|
|
|
|
year = tag_value.to_string();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if genre.is_empty() {
|
|
|
|
if let Some(tag_value) = tag.get_string(&ItemKey::Genre) {
|
|
|
|
genre = tag_value.to_string();
|
|
|
|
}
|
|
|
|
}
|
2022-05-17 04:23:07 +12:00
|
|
|
// println!("{:?}", tag.items());
|
2022-04-29 00:40:21 +12:00
|
|
|
}
|
2022-02-26 06:47:25 +13:00
|
|
|
|
2022-05-17 04:23:07 +12:00
|
|
|
if let Ok(old_length_number) = length.parse::<u32>() {
|
|
|
|
let length_number = old_length_number / 60;
|
2022-02-26 06:47:25 +13:00
|
|
|
let minutes = length_number / 1000;
|
|
|
|
let seconds = (length_number % 1000) * 6 / 100;
|
2022-04-29 00:40:21 +12:00
|
|
|
if minutes != 0 || seconds != 0 {
|
2022-12-21 20:44:26 +13:00
|
|
|
length = format!("{minutes}:{seconds:02}");
|
2022-05-17 04:23:07 +12:00
|
|
|
} else if old_length_number > 0 {
|
|
|
|
// That means, that audio have length smaller that second, but length is properly read
|
|
|
|
length = "0:01".to_string();
|
2022-02-26 06:47:25 +13:00
|
|
|
} else {
|
2023-01-29 06:54:02 +13:00
|
|
|
length = String::new();
|
2022-02-26 06:47:25 +13:00
|
|
|
}
|
|
|
|
} else {
|
2023-01-29 06:54:02 +13:00
|
|
|
length = String::new();
|
2022-02-26 06:47:25 +13:00
|
|
|
}
|
|
|
|
|
|
|
|
music_entry.track_title = track_title;
|
|
|
|
music_entry.track_artist = track_artist;
|
|
|
|
music_entry.year = year;
|
|
|
|
music_entry.length = length;
|
|
|
|
music_entry.genre = genre;
|
|
|
|
music_entry.bitrate = bitrate;
|
2020-11-08 21:10:49 +13:00
|
|
|
|
2022-01-06 10:47:27 +13:00
|
|
|
Some(Some(music_entry))
|
2020-11-08 21:10:49 +13:00
|
|
|
})
|
|
|
|
.while_some()
|
2023-01-29 06:54:02 +13:00
|
|
|
.filter(Option::is_some)
|
|
|
|
.map(Option::unwrap)
|
2020-11-08 21:10:49 +13:00
|
|
|
.collect::<Vec<_>>();
|
|
|
|
|
2020-12-02 22:25:27 +13:00
|
|
|
// End thread which send info to gui
|
|
|
|
progress_thread_run.store(false, Ordering::Relaxed);
|
|
|
|
progress_thread_handle.join().unwrap();
|
|
|
|
|
2022-01-06 10:47:27 +13:00
|
|
|
// Just connect loaded results with already calculated
|
|
|
|
for (_name, file_entry) in records_already_cached {
|
|
|
|
vec_file_entry.push(file_entry.clone());
|
|
|
|
}
|
|
|
|
|
|
|
|
self.music_entries = vec_file_entry.clone();
|
|
|
|
|
|
|
|
if self.use_cache {
|
|
|
|
// Must save all results to file, old loaded from file with all currently counted results
|
|
|
|
let mut all_results: HashMap<String, MusicEntry> = loaded_hash_map;
|
|
|
|
|
|
|
|
for file_entry in vec_file_entry {
|
|
|
|
all_results.insert(file_entry.path.to_string_lossy().to_string(), file_entry);
|
|
|
|
}
|
|
|
|
save_cache_to_file(&all_results, &mut self.text_messages, self.save_also_as_json);
|
|
|
|
}
|
2020-11-08 21:10:49 +13:00
|
|
|
|
2022-05-10 05:40:35 +12:00
|
|
|
// Break if stop was clicked after saving to cache
|
|
|
|
if check_was_stopped.load(Ordering::Relaxed) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2023-05-02 03:48:12 +12:00
|
|
|
Common::print_time(start_time, SystemTime::now(), "read_tags");
|
2021-01-16 00:41:45 +13:00
|
|
|
|
2020-11-08 21:10:49 +13:00
|
|
|
true
|
|
|
|
}
|
2023-05-02 03:48:12 +12:00
|
|
|
fn check_for_duplicate_tags(&mut self, stop_receiver: Option<&Receiver<()>>, progress_sender: Option<&futures::channel::mpsc::UnboundedSender<ProgressData>>) -> bool {
|
2023-01-29 06:54:02 +13:00
|
|
|
assert!(MusicSimilarity::NONE != self.music_similarity, "This can't be none");
|
2020-11-03 09:56:07 +13:00
|
|
|
let start_time: SystemTime = SystemTime::now();
|
|
|
|
|
2020-12-02 22:25:27 +13:00
|
|
|
//// PROGRESS THREAD START
|
|
|
|
let progress_thread_run = Arc::new(AtomicBool::new(true));
|
|
|
|
|
|
|
|
let atomic_file_counter = Arc::new(AtomicUsize::new(0));
|
|
|
|
|
2021-12-19 11:45:37 +13:00
|
|
|
let progress_thread_handle = if let Some(progress_sender) = progress_sender {
|
2021-04-14 18:23:59 +12:00
|
|
|
let progress_send = progress_sender.clone();
|
2020-12-02 22:25:27 +13:00
|
|
|
let progress_thread_run = progress_thread_run.clone();
|
|
|
|
let atomic_file_counter = atomic_file_counter.clone();
|
|
|
|
let music_to_check = self.music_to_check.len();
|
2021-12-19 11:45:37 +13:00
|
|
|
thread::spawn(move || loop {
|
2020-12-02 22:25:27 +13:00
|
|
|
progress_send
|
2021-04-14 18:23:59 +12:00
|
|
|
.unbounded_send(ProgressData {
|
2022-01-06 04:43:26 +13:00
|
|
|
checking_method: CheckingMethod::None,
|
2020-12-02 22:25:27 +13:00
|
|
|
current_stage: 2,
|
|
|
|
max_stage: 2,
|
2022-11-24 08:23:17 +13:00
|
|
|
entries_checked: atomic_file_counter.load(Ordering::Relaxed),
|
2022-01-06 04:43:26 +13:00
|
|
|
entries_to_check: music_to_check,
|
2020-12-02 22:25:27 +13:00
|
|
|
})
|
|
|
|
.unwrap();
|
|
|
|
if !progress_thread_run.load(Ordering::Relaxed) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
sleep(Duration::from_millis(LOOP_DURATION as u64));
|
2021-12-19 11:45:37 +13:00
|
|
|
})
|
2020-12-02 22:25:27 +13:00
|
|
|
} else {
|
2021-12-19 11:45:37 +13:00
|
|
|
thread::spawn(|| {})
|
|
|
|
};
|
2020-12-02 22:25:27 +13:00
|
|
|
//// PROGRESS THREAD END
|
|
|
|
|
2022-01-06 04:43:26 +13:00
|
|
|
let mut old_duplicates: Vec<Vec<MusicEntry>> = vec![self.music_entries.clone()];
|
|
|
|
let mut new_duplicates: Vec<Vec<MusicEntry>> = Vec::new();
|
2020-11-03 09:56:07 +13:00
|
|
|
|
2022-02-26 06:47:25 +13:00
|
|
|
if (self.music_similarity & MusicSimilarity::TRACK_TITLE) == MusicSimilarity::TRACK_TITLE {
|
2020-11-03 09:56:07 +13:00
|
|
|
for vec_file_entry in old_duplicates {
|
2020-12-02 22:25:27 +13:00
|
|
|
atomic_file_counter.fetch_add(1, Ordering::Relaxed);
|
2020-11-05 06:15:06 +13:00
|
|
|
if stop_receiver.is_some() && stop_receiver.unwrap().try_recv().is_ok() {
|
2020-12-02 22:25:27 +13:00
|
|
|
// End thread which send info to gui
|
|
|
|
progress_thread_run.store(false, Ordering::Relaxed);
|
|
|
|
progress_thread_handle.join().unwrap();
|
2020-11-03 09:56:07 +13:00
|
|
|
return false;
|
|
|
|
}
|
2022-01-06 04:43:26 +13:00
|
|
|
let mut hash_map: BTreeMap<String, Vec<MusicEntry>> = Default::default();
|
2020-11-03 09:56:07 +13:00
|
|
|
for file_entry in vec_file_entry {
|
2022-07-25 06:48:02 +12:00
|
|
|
let mut thing = file_entry.track_title.trim().to_lowercase();
|
2021-12-05 02:49:43 +13:00
|
|
|
if self.approximate_comparison {
|
2022-02-26 06:47:25 +13:00
|
|
|
get_approximate_conversion(&mut thing);
|
2021-12-05 02:49:43 +13:00
|
|
|
}
|
2022-02-26 06:47:25 +13:00
|
|
|
if !thing.is_empty() {
|
2022-06-05 18:01:17 +12:00
|
|
|
hash_map.entry(thing.clone()).or_insert_with(Vec::new).push(file_entry);
|
2020-11-03 09:56:07 +13:00
|
|
|
}
|
|
|
|
}
|
|
|
|
for (_title, vec_file_entry) in hash_map {
|
|
|
|
if vec_file_entry.len() > 1 {
|
|
|
|
new_duplicates.push(vec_file_entry);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
old_duplicates = new_duplicates;
|
|
|
|
new_duplicates = Vec::new();
|
|
|
|
}
|
2022-02-26 06:47:25 +13:00
|
|
|
if (self.music_similarity & MusicSimilarity::TRACK_ARTIST) == MusicSimilarity::TRACK_ARTIST {
|
2020-11-03 09:56:07 +13:00
|
|
|
for vec_file_entry in old_duplicates {
|
2020-12-02 22:25:27 +13:00
|
|
|
atomic_file_counter.fetch_add(1, Ordering::Relaxed);
|
2020-11-05 06:15:06 +13:00
|
|
|
if stop_receiver.is_some() && stop_receiver.unwrap().try_recv().is_ok() {
|
2020-12-02 22:25:27 +13:00
|
|
|
// End thread which send info to gui
|
|
|
|
progress_thread_run.store(false, Ordering::Relaxed);
|
|
|
|
progress_thread_handle.join().unwrap();
|
2020-11-03 09:56:07 +13:00
|
|
|
return false;
|
|
|
|
}
|
2022-01-06 04:43:26 +13:00
|
|
|
let mut hash_map: BTreeMap<String, Vec<MusicEntry>> = Default::default();
|
2020-11-03 09:56:07 +13:00
|
|
|
for file_entry in vec_file_entry {
|
2022-07-25 06:48:02 +12:00
|
|
|
let mut thing = file_entry.track_artist.trim().to_lowercase();
|
2021-12-04 21:29:29 +13:00
|
|
|
if self.approximate_comparison {
|
2022-02-26 06:47:25 +13:00
|
|
|
get_approximate_conversion(&mut thing);
|
2021-12-04 21:29:29 +13:00
|
|
|
}
|
2022-02-26 06:47:25 +13:00
|
|
|
if !thing.is_empty() {
|
2022-06-05 18:01:17 +12:00
|
|
|
hash_map.entry(thing.clone()).or_insert_with(Vec::new).push(file_entry);
|
2020-11-03 09:56:07 +13:00
|
|
|
}
|
|
|
|
}
|
2022-02-26 06:47:25 +13:00
|
|
|
for (_title, vec_file_entry) in hash_map {
|
2020-11-03 09:56:07 +13:00
|
|
|
if vec_file_entry.len() > 1 {
|
|
|
|
new_duplicates.push(vec_file_entry);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
old_duplicates = new_duplicates;
|
|
|
|
new_duplicates = Vec::new();
|
|
|
|
}
|
2022-02-26 06:47:25 +13:00
|
|
|
if (self.music_similarity & MusicSimilarity::YEAR) == MusicSimilarity::YEAR {
|
2020-11-03 09:56:07 +13:00
|
|
|
for vec_file_entry in old_duplicates {
|
2020-12-02 22:25:27 +13:00
|
|
|
atomic_file_counter.fetch_add(1, Ordering::Relaxed);
|
2020-11-05 06:15:06 +13:00
|
|
|
if stop_receiver.is_some() && stop_receiver.unwrap().try_recv().is_ok() {
|
2020-12-02 22:25:27 +13:00
|
|
|
// End thread which send info to gui
|
|
|
|
progress_thread_run.store(false, Ordering::Relaxed);
|
|
|
|
progress_thread_handle.join().unwrap();
|
2020-11-03 09:56:07 +13:00
|
|
|
return false;
|
|
|
|
}
|
2022-01-06 04:43:26 +13:00
|
|
|
let mut hash_map: BTreeMap<String, Vec<MusicEntry>> = Default::default();
|
2020-11-03 09:56:07 +13:00
|
|
|
for file_entry in vec_file_entry {
|
2022-07-25 06:48:02 +12:00
|
|
|
let thing = file_entry.year.trim().to_lowercase();
|
2022-02-26 06:47:25 +13:00
|
|
|
if !thing.is_empty() {
|
2022-06-05 18:01:17 +12:00
|
|
|
hash_map.entry(thing.clone()).or_insert_with(Vec::new).push(file_entry);
|
2020-11-03 09:56:07 +13:00
|
|
|
}
|
|
|
|
}
|
2022-02-26 06:47:25 +13:00
|
|
|
for (_title, vec_file_entry) in hash_map {
|
2020-11-03 09:56:07 +13:00
|
|
|
if vec_file_entry.len() > 1 {
|
|
|
|
new_duplicates.push(vec_file_entry);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
old_duplicates = new_duplicates;
|
|
|
|
new_duplicates = Vec::new();
|
|
|
|
}
|
2022-02-26 06:47:25 +13:00
|
|
|
if (self.music_similarity & MusicSimilarity::LENGTH) == MusicSimilarity::LENGTH {
|
2020-11-03 09:56:07 +13:00
|
|
|
for vec_file_entry in old_duplicates {
|
2020-12-02 22:25:27 +13:00
|
|
|
atomic_file_counter.fetch_add(1, Ordering::Relaxed);
|
2020-11-05 06:15:06 +13:00
|
|
|
if stop_receiver.is_some() && stop_receiver.unwrap().try_recv().is_ok() {
|
2020-12-02 22:25:27 +13:00
|
|
|
// End thread which send info to gui
|
|
|
|
progress_thread_run.store(false, Ordering::Relaxed);
|
|
|
|
progress_thread_handle.join().unwrap();
|
2020-11-03 09:56:07 +13:00
|
|
|
return false;
|
|
|
|
}
|
2022-01-06 04:43:26 +13:00
|
|
|
let mut hash_map: BTreeMap<String, Vec<MusicEntry>> = Default::default();
|
2020-11-03 09:56:07 +13:00
|
|
|
for file_entry in vec_file_entry {
|
2022-07-25 06:48:02 +12:00
|
|
|
let thing = file_entry.length.trim().to_lowercase();
|
2022-02-26 06:47:25 +13:00
|
|
|
if !thing.is_empty() {
|
2022-06-05 18:01:17 +12:00
|
|
|
hash_map.entry(thing.clone()).or_insert_with(Vec::new).push(file_entry);
|
2021-12-04 21:29:29 +13:00
|
|
|
}
|
2022-02-26 06:47:25 +13:00
|
|
|
}
|
|
|
|
for (_title, vec_file_entry) in hash_map {
|
|
|
|
if vec_file_entry.len() > 1 {
|
|
|
|
new_duplicates.push(vec_file_entry);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
old_duplicates = new_duplicates;
|
|
|
|
new_duplicates = Vec::new();
|
|
|
|
}
|
|
|
|
if (self.music_similarity & MusicSimilarity::GENRE) == MusicSimilarity::GENRE {
|
|
|
|
for vec_file_entry in old_duplicates {
|
|
|
|
atomic_file_counter.fetch_add(1, Ordering::Relaxed);
|
|
|
|
if stop_receiver.is_some() && stop_receiver.unwrap().try_recv().is_ok() {
|
|
|
|
// End thread which send info to gui
|
|
|
|
progress_thread_run.store(false, Ordering::Relaxed);
|
|
|
|
progress_thread_handle.join().unwrap();
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
let mut hash_map: BTreeMap<String, Vec<MusicEntry>> = Default::default();
|
|
|
|
for file_entry in vec_file_entry {
|
2022-07-25 06:48:02 +12:00
|
|
|
let thing = file_entry.genre.trim().to_lowercase();
|
2022-02-26 06:47:25 +13:00
|
|
|
if !thing.is_empty() {
|
2023-02-19 22:21:14 +13:00
|
|
|
hash_map.entry(thing).or_insert_with(Vec::new).push(file_entry);
|
2020-11-03 09:56:07 +13:00
|
|
|
}
|
|
|
|
}
|
2022-02-26 06:47:25 +13:00
|
|
|
for (_title, vec_file_entry) in hash_map {
|
2020-11-03 09:56:07 +13:00
|
|
|
if vec_file_entry.len() > 1 {
|
|
|
|
new_duplicates.push(vec_file_entry);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
old_duplicates = new_duplicates;
|
|
|
|
new_duplicates = Vec::new();
|
|
|
|
}
|
2022-02-26 06:47:25 +13:00
|
|
|
if (self.music_similarity & MusicSimilarity::BITRATE) == MusicSimilarity::BITRATE {
|
2020-11-03 09:56:07 +13:00
|
|
|
for vec_file_entry in old_duplicates {
|
2020-12-02 22:25:27 +13:00
|
|
|
atomic_file_counter.fetch_add(1, Ordering::Relaxed);
|
2020-11-05 06:15:06 +13:00
|
|
|
if stop_receiver.is_some() && stop_receiver.unwrap().try_recv().is_ok() {
|
2020-12-02 22:25:27 +13:00
|
|
|
// End thread which send info to gui
|
|
|
|
progress_thread_run.store(false, Ordering::Relaxed);
|
|
|
|
progress_thread_handle.join().unwrap();
|
2020-11-03 09:56:07 +13:00
|
|
|
return false;
|
|
|
|
}
|
2022-02-26 06:47:25 +13:00
|
|
|
let mut hash_map: BTreeMap<String, Vec<MusicEntry>> = Default::default();
|
2020-11-03 09:56:07 +13:00
|
|
|
for file_entry in vec_file_entry {
|
2022-02-26 06:47:25 +13:00
|
|
|
if file_entry.bitrate != 0 {
|
|
|
|
let thing = file_entry.bitrate.to_string();
|
|
|
|
if !thing.is_empty() {
|
2022-06-05 18:01:17 +12:00
|
|
|
hash_map.entry(thing.clone()).or_insert_with(Vec::new).push(file_entry);
|
2022-02-26 06:47:25 +13:00
|
|
|
}
|
2020-11-03 09:56:07 +13:00
|
|
|
}
|
|
|
|
}
|
2022-02-26 06:47:25 +13:00
|
|
|
for (_title, vec_file_entry) in hash_map {
|
2020-11-03 09:56:07 +13:00
|
|
|
if vec_file_entry.len() > 1 {
|
|
|
|
new_duplicates.push(vec_file_entry);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
old_duplicates = new_duplicates;
|
|
|
|
// new_duplicates = Vec::new();
|
|
|
|
}
|
|
|
|
|
2020-12-02 22:25:27 +13:00
|
|
|
// End thread which send info to gui
|
|
|
|
progress_thread_run.store(false, Ordering::Relaxed);
|
|
|
|
progress_thread_handle.join().unwrap();
|
2020-11-03 09:56:07 +13:00
|
|
|
|
2021-12-24 21:18:55 +13:00
|
|
|
self.duplicated_music_entries = old_duplicates;
|
|
|
|
|
|
|
|
if self.use_reference_folders {
|
2022-06-01 03:52:55 +12:00
|
|
|
let mut similar_vector = Default::default();
|
|
|
|
mem::swap(&mut self.duplicated_music_entries, &mut similar_vector);
|
2021-12-24 21:18:55 +13:00
|
|
|
let reference_directories = self.directories.reference_directories.clone();
|
2022-06-01 03:52:55 +12:00
|
|
|
self.duplicated_music_entries_referenced = similar_vector
|
2021-12-24 21:18:55 +13:00
|
|
|
.into_iter()
|
|
|
|
.filter_map(|vec_file_entry| {
|
|
|
|
let mut files_from_referenced_folders = Vec::new();
|
|
|
|
let mut normal_files = Vec::new();
|
|
|
|
for file_entry in vec_file_entry {
|
2022-11-24 08:23:17 +13:00
|
|
|
if reference_directories.iter().any(|e| file_entry.path.starts_with(e)) {
|
2021-12-24 21:18:55 +13:00
|
|
|
files_from_referenced_folders.push(file_entry);
|
|
|
|
} else {
|
|
|
|
normal_files.push(file_entry);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if files_from_referenced_folders.is_empty() || normal_files.is_empty() {
|
|
|
|
None
|
|
|
|
} else {
|
|
|
|
Some((files_from_referenced_folders.pop().unwrap(), normal_files))
|
|
|
|
}
|
|
|
|
})
|
2022-01-06 04:43:26 +13:00
|
|
|
.collect::<Vec<(MusicEntry, Vec<MusicEntry>)>>();
|
2021-12-24 21:18:55 +13:00
|
|
|
}
|
|
|
|
|
|
|
|
if self.use_reference_folders {
|
|
|
|
for (_fe, vector) in &self.duplicated_music_entries_referenced {
|
|
|
|
self.information.number_of_duplicates += vector.len();
|
|
|
|
self.information.number_of_groups += 1;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
for vector in &self.duplicated_music_entries {
|
|
|
|
self.information.number_of_duplicates += vector.len() - 1;
|
|
|
|
self.information.number_of_groups += 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-05-02 03:48:12 +12:00
|
|
|
Common::print_time(start_time, SystemTime::now(), "check_for_duplicate_tags");
|
2021-01-16 00:41:45 +13:00
|
|
|
|
|
|
|
// Clear unused data
|
|
|
|
self.music_entries.clear();
|
|
|
|
|
2020-11-03 09:56:07 +13:00
|
|
|
true
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn set_minimal_file_size(&mut self, minimal_file_size: u64) {
|
|
|
|
self.minimal_file_size = match minimal_file_size {
|
|
|
|
0 => 1,
|
|
|
|
t => t,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Function to delete files, from filed Vector
|
|
|
|
fn delete_files(&mut self) {
|
|
|
|
let start_time: SystemTime = SystemTime::now();
|
|
|
|
// TODO
|
|
|
|
// match self.delete_method {
|
|
|
|
// DeleteMethod::Delete => {
|
|
|
|
// for file_entry in &self.music_entries {
|
|
|
|
// if fs::remove_file(file_entry.path.clone()).is_err() {
|
|
|
|
// self.text_messages.warnings.push(file_entry.path.display().to_string());
|
|
|
|
// }
|
|
|
|
// }
|
|
|
|
// }
|
|
|
|
// DeleteMethod::None => {
|
|
|
|
// //Just do nothing
|
|
|
|
// }
|
|
|
|
// }
|
|
|
|
|
2023-01-29 06:54:02 +13:00
|
|
|
Common::print_time(start_time, SystemTime::now(), "delete_files");
|
2020-11-03 09:56:07 +13:00
|
|
|
}
|
|
|
|
}
|
2021-11-28 08:57:10 +13:00
|
|
|
|
2022-01-06 10:47:27 +13:00
|
|
|
fn save_cache_to_file(hashmap: &HashMap<String, MusicEntry>, text_messages: &mut Messages, save_also_as_json: bool) {
|
|
|
|
if let Some(((file_handler, cache_file), (file_handler_json, cache_file_json))) = open_cache_folder(&get_cache_file(), true, save_also_as_json, &mut text_messages.warnings) {
|
|
|
|
{
|
|
|
|
let writer = BufWriter::new(file_handler.unwrap()); // Unwrap because cannot fail here
|
|
|
|
if let Err(e) = bincode::serialize_into(writer, hashmap) {
|
|
|
|
text_messages
|
|
|
|
.warnings
|
|
|
|
.push(format!("Cannot write data to cache file {}, reason {}", cache_file.display(), e));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if save_also_as_json {
|
|
|
|
if let Some(file_handler_json) = file_handler_json {
|
|
|
|
let writer = BufWriter::new(file_handler_json);
|
|
|
|
if let Err(e) = serde_json::to_writer(writer, hashmap) {
|
|
|
|
text_messages
|
|
|
|
.warnings
|
|
|
|
.push(format!("Cannot write data to cache file {}, reason {}", cache_file_json.display(), e));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
text_messages.messages.push(format!("Properly saved to file {} cache entries.", hashmap.len()));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn load_cache_from_file(text_messages: &mut Messages, delete_outdated_cache: bool) -> Option<HashMap<String, MusicEntry>> {
|
|
|
|
if let Some(((file_handler, cache_file), (file_handler_json, cache_file_json))) = open_cache_folder(&get_cache_file(), false, true, &mut text_messages.warnings) {
|
|
|
|
let mut hashmap_loaded_entries: HashMap<String, MusicEntry>;
|
|
|
|
if let Some(file_handler) = file_handler {
|
|
|
|
let reader = BufReader::new(file_handler);
|
|
|
|
hashmap_loaded_entries = match bincode::deserialize_from(reader) {
|
|
|
|
Ok(t) => t,
|
|
|
|
Err(e) => {
|
|
|
|
text_messages
|
|
|
|
.warnings
|
|
|
|
.push(format!("Failed to load data from cache file {}, reason {}", cache_file.display(), e));
|
|
|
|
return None;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
} else {
|
|
|
|
let reader = BufReader::new(file_handler_json.unwrap()); // Unwrap cannot fail, because at least one file must be valid
|
|
|
|
hashmap_loaded_entries = match serde_json::from_reader(reader) {
|
|
|
|
Ok(t) => t,
|
|
|
|
Err(e) => {
|
|
|
|
text_messages
|
|
|
|
.warnings
|
|
|
|
.push(format!("Failed to load data from cache file {}, reason {}", cache_file_json.display(), e));
|
|
|
|
return None;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
// Don't load cache data if destination file not exists
|
|
|
|
if delete_outdated_cache {
|
|
|
|
hashmap_loaded_entries.retain(|src_path, _file_entry| Path::new(src_path).exists());
|
|
|
|
}
|
|
|
|
|
|
|
|
text_messages.messages.push(format!("Properly loaded {} cache entries.", hashmap_loaded_entries.len()));
|
|
|
|
|
|
|
|
return Some(hashmap_loaded_entries);
|
|
|
|
}
|
|
|
|
None
|
|
|
|
}
|
|
|
|
|
|
|
|
fn get_cache_file() -> String {
|
|
|
|
"cache_same_music.bin".to_string()
|
|
|
|
}
|
|
|
|
|
2020-11-03 09:56:07 +13:00
|
|
|
impl Default for SameMusic {
|
|
|
|
fn default() -> Self {
|
|
|
|
Self::new()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl DebugPrint for SameMusic {
|
|
|
|
#[allow(dead_code)]
|
|
|
|
#[allow(unreachable_code)]
|
|
|
|
/// Debugging printing - only available on debug build
|
|
|
|
fn debug_print(&self) {
|
|
|
|
#[cfg(not(debug_assertions))]
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
println!("---------------DEBUG PRINT---------------");
|
|
|
|
println!("### Information's");
|
|
|
|
|
|
|
|
println!("Errors size - {}", self.text_messages.errors.len());
|
|
|
|
println!("Warnings size - {}", self.text_messages.warnings.len());
|
|
|
|
println!("Messages size - {}", self.text_messages.messages.len());
|
|
|
|
|
|
|
|
println!("### Other");
|
|
|
|
|
|
|
|
println!("Excluded items - {:?}", self.excluded_items.items);
|
|
|
|
println!("Minimum file size - {:?}", self.minimal_file_size);
|
|
|
|
println!("Found files music - {}", self.music_entries.len());
|
|
|
|
println!("Found duplicated files music - {}", self.duplicated_music_entries.len());
|
|
|
|
println!("Included directories - {:?}", self.directories.included_directories);
|
|
|
|
println!("Excluded directories - {:?}", self.directories.excluded_directories);
|
2021-11-23 23:10:24 +13:00
|
|
|
println!("Recursive search - {}", self.recursive_search);
|
2022-04-24 06:21:46 +12:00
|
|
|
#[cfg(target_family = "unix")]
|
2022-06-01 03:52:55 +12:00
|
|
|
println!("Skip other filesystems - {}", self.directories.exclude_other_filesystems());
|
2020-11-03 09:56:07 +13:00
|
|
|
println!("Delete Method - {:?}", self.delete_method);
|
|
|
|
println!("-----------------------------------------");
|
|
|
|
}
|
|
|
|
}
|
2021-11-28 08:57:10 +13:00
|
|
|
|
2020-11-03 09:56:07 +13:00
|
|
|
impl SaveResults for SameMusic {
|
|
|
|
fn save_results_to_file(&mut self, file_name: &str) -> bool {
|
|
|
|
let start_time: SystemTime = SystemTime::now();
|
|
|
|
let file_name: String = match file_name {
|
|
|
|
"" => "results.txt".to_string(),
|
|
|
|
k => k.to_string(),
|
|
|
|
};
|
|
|
|
|
2021-01-09 23:52:43 +13:00
|
|
|
let file_handler = match File::create(&file_name) {
|
2020-11-03 09:56:07 +13:00
|
|
|
Ok(t) => t,
|
2021-11-15 03:53:55 +13:00
|
|
|
Err(e) => {
|
2022-12-21 20:44:26 +13:00
|
|
|
self.text_messages.errors.push(format!("Failed to create file {file_name}, reason {e}"));
|
2020-11-03 09:56:07 +13:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
};
|
2021-01-09 23:52:43 +13:00
|
|
|
let mut writer = BufWriter::new(file_handler);
|
2020-11-03 09:56:07 +13:00
|
|
|
|
2021-11-15 03:53:55 +13:00
|
|
|
if let Err(e) = writeln!(
|
2021-01-09 23:52:43 +13:00
|
|
|
writer,
|
2020-11-03 09:56:07 +13:00
|
|
|
"Results of searching {:?} with excluded directories {:?} and excluded items {:?}",
|
|
|
|
self.directories.included_directories, self.directories.excluded_directories, self.excluded_items.items
|
2021-11-15 03:53:55 +13:00
|
|
|
) {
|
2022-12-21 20:44:26 +13:00
|
|
|
self.text_messages.errors.push(format!("Failed to save results to file {file_name}, reason {e}"));
|
2020-11-03 09:56:07 +13:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if !self.music_entries.is_empty() {
|
2021-12-24 21:18:55 +13:00
|
|
|
writeln!(writer, "Found {} same music files.", self.information.number_of_duplicates).unwrap();
|
2023-01-29 06:54:02 +13:00
|
|
|
for file_entry in &self.music_entries {
|
2021-01-09 23:52:43 +13:00
|
|
|
writeln!(writer, "{}", file_entry.path.display()).unwrap();
|
2020-11-03 09:56:07 +13:00
|
|
|
}
|
|
|
|
} else {
|
2021-01-09 23:52:43 +13:00
|
|
|
write!(writer, "Not found any empty files.").unwrap();
|
2020-11-03 09:56:07 +13:00
|
|
|
}
|
2023-01-29 06:54:02 +13:00
|
|
|
Common::print_time(start_time, SystemTime::now(), "save_results_to_file");
|
2020-11-03 09:56:07 +13:00
|
|
|
true
|
|
|
|
}
|
|
|
|
}
|
2021-11-28 08:57:10 +13:00
|
|
|
|
2020-11-03 09:56:07 +13:00
|
|
|
impl PrintResults for SameMusic {
|
|
|
|
/// Print information's about duplicated entries
|
|
|
|
/// Only needed for CLI
|
|
|
|
fn print_results(&self) {
|
|
|
|
let start_time: SystemTime = SystemTime::now();
|
|
|
|
println!("Found {} similar music files.\n", self.duplicated_music_entries.len());
|
2023-01-29 06:54:02 +13:00
|
|
|
for vec_file_entry in &self.duplicated_music_entries {
|
2020-11-03 09:56:07 +13:00
|
|
|
for file_entry in vec_file_entry {
|
|
|
|
println!(
|
2022-02-26 06:47:25 +13:00
|
|
|
"TT: {} - TA: {} - Y: {} - L: {} - G: {} - B: {} - P: {}",
|
|
|
|
file_entry.track_title,
|
|
|
|
file_entry.track_artist,
|
2021-12-05 02:49:43 +13:00
|
|
|
file_entry.year,
|
2022-02-26 06:47:25 +13:00
|
|
|
file_entry.length,
|
|
|
|
file_entry.genre,
|
|
|
|
file_entry.bitrate,
|
2020-11-03 09:56:07 +13:00
|
|
|
file_entry.path.display()
|
|
|
|
);
|
|
|
|
}
|
|
|
|
println!();
|
|
|
|
}
|
|
|
|
|
2023-01-29 06:54:02 +13:00
|
|
|
Common::print_time(start_time, SystemTime::now(), "print_entries");
|
2020-11-03 09:56:07 +13:00
|
|
|
}
|
|
|
|
}
|
2021-12-05 02:49:43 +13:00
|
|
|
|
|
|
|
fn get_approximate_conversion(what: &mut String) {
|
|
|
|
let mut new_what = String::with_capacity(what.len());
|
|
|
|
let mut tab_number = 0;
|
|
|
|
let mut space_before = true;
|
2021-12-19 11:45:37 +13:00
|
|
|
for character in what.chars() {
|
2021-12-05 02:49:43 +13:00
|
|
|
match character {
|
2022-02-28 02:35:44 +13:00
|
|
|
'(' | '[' => {
|
2021-12-05 02:49:43 +13:00
|
|
|
tab_number += 1;
|
|
|
|
}
|
2022-02-28 02:35:44 +13:00
|
|
|
')' | ']' => {
|
2021-12-05 02:49:43 +13:00
|
|
|
if tab_number == 0 {
|
|
|
|
// Nothing to do, not even save it to output
|
|
|
|
} else {
|
|
|
|
tab_number -= 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
' ' => {
|
|
|
|
if !space_before {
|
|
|
|
new_what.push(' ');
|
|
|
|
space_before = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
ch => {
|
|
|
|
if tab_number == 0 {
|
2021-12-21 21:13:35 +13:00
|
|
|
// Ignore all non alphabetic ascii characters like " or .
|
|
|
|
if !ch.is_ascii() || ch.is_ascii_alphabetic() {
|
|
|
|
space_before = false;
|
|
|
|
new_what.push(ch);
|
|
|
|
} else if !space_before {
|
|
|
|
new_what.push(' ');
|
|
|
|
space_before = true;
|
|
|
|
}
|
2021-12-05 02:49:43 +13:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if new_what.ends_with(' ') {
|
|
|
|
new_what.pop();
|
|
|
|
}
|
|
|
|
*what = new_what;
|
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
|
|
|
use crate::same_music::get_approximate_conversion;
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_strings() {
|
|
|
|
let mut what = "roman ( ziemniak ) ".to_string();
|
|
|
|
get_approximate_conversion(&mut what);
|
|
|
|
assert_eq!(what, "roman");
|
|
|
|
|
|
|
|
let mut what = " HH) ".to_string();
|
|
|
|
get_approximate_conversion(&mut what);
|
|
|
|
assert_eq!(what, "HH");
|
2021-12-21 21:13:35 +13:00
|
|
|
|
|
|
|
let mut what = " fsf.f. ".to_string();
|
|
|
|
get_approximate_conversion(&mut what);
|
|
|
|
assert_eq!(what, "fsf f");
|
2022-02-28 02:35:44 +13:00
|
|
|
|
|
|
|
let mut what = "Kekistan (feat. roman) [Mix on Mix]".to_string();
|
|
|
|
get_approximate_conversion(&mut what);
|
|
|
|
assert_eq!(what, "Kekistan");
|
2021-12-05 02:49:43 +13:00
|
|
|
}
|
|
|
|
}
|