use std::sync::{Arc, Mutex, atomic::{AtomicU8, AtomicU64, Ordering}};
use std::sync::mpsc::{Sender, sync_channel, Receiver};
use crate::ui::utils::format_size;
use crate::ui::state::{UiMessage, BatchAction, ProcessingState};
use crate::engine::wof::{uncompress_file, WofAlgorithm, get_real_file_size, get_wof_algorithm, smart_compress};
use crate::utils::{to_wstring, u64_to_wstring, concat_wstrings};
use windows_sys::Win32::Foundation::{HWND, INVALID_HANDLE_VALUE};
use windows_sys::Win32::UI::WindowsAndMessaging::SendMessageW;
use windows_sys::Win32::Storage::FileSystem::{
FindFirstFileExW, FindNextFileW, FindClose,
FindExInfoBasic, FindExSearchNameMatch,
FIND_FIRST_EX_LARGE_FETCH, WIN32_FIND_DATAW,
FILE_ATTRIBUTE_DIRECTORY, FILE_ATTRIBUTE_REPARSE_POINT,
};
use windows_sys::Win32::System::Power::{SetThreadExecutionState, ES_CONTINUOUS, ES_SYSTEM_REQUIRED};
fn is_critical_path(path: &str) -> bool {
let lower = path.to_lowercase();
lower.contains("windows\\system32") ||
lower.contains("windows\\syswow64") ||
lower.contains("windows\\winsxs") ||
lower.contains("boot") ||
lower.ends_with("bootmgr")
}
struct ExecutionStateGuard {
_private: (), }
impl ExecutionStateGuard {
fn new() -> Self {
unsafe {
SetThreadExecutionState(ES_CONTINUOUS | ES_SYSTEM_REQUIRED);
}
Self { _private: () }
}
}
impl Drop for ExecutionStateGuard {
fn drop(&mut self) {
unsafe {
SetThreadExecutionState(ES_CONTINUOUS);
}
}
}
#[derive(Debug, Clone)]
pub enum ProcessResult {
Success,
Skipped(String),
Failed(String),
}
#[derive(Default)]
pub struct ScanStats {
pub file_count: u64,
pub logical_size: u64,
pub file_paths: Vec<String>,
}
const SKIP_EXTENSIONS: &[&str] = &[
"zip", "7z", "rar", "gz", "bz2", "xz", "zst", "lz4", "jpg", "jpeg", "png", "gif", "webp", "avif", "heic", "mp4", "mkv", "avi", "webm", "mov", "wmv", "mp3", "flac", "aac", "ogg", "opus", "wma", "pdf", ];
fn should_skip_extension(path: &str) -> bool {
let path_obj = std::path::Path::new(path);
if let Some(ext) = path_obj.extension().and_then(|s| s.to_str()) {
let ext_lower = ext.to_lowercase();
SKIP_EXTENSIONS.iter().any(|&skip_ext| ext_lower == skip_ext)
} else {
false
}
}
fn walk_directory_generic<F>(
path: &str,
state: Option<&Arc<AtomicU8>>,
visitor: &mut F,
)
where
F: FnMut(&str, bool, &WIN32_FIND_DATAW),
{
if let Some(s) = state {
if s.load(Ordering::Relaxed) == ProcessingState::Stopped as u8 {
return;
}
}
let pattern = if path.ends_with('\\') || path.ends_with('/') {
let mut p = path.to_string();
p.push('*');
p
} else {
let mut p = path.to_string();
p.push_str("\\*");
p
};
let pattern_wide = to_wstring(&pattern);
let mut find_data: WIN32_FIND_DATAW = unsafe { std::mem::zeroed() };
unsafe {
let handle = FindFirstFileExW(
pattern_wide.as_ptr(),
FindExInfoBasic,
&mut find_data as *mut _ as *mut _,
FindExSearchNameMatch,
std::ptr::null(),
FIND_FIRST_EX_LARGE_FETCH,
);
if handle == INVALID_HANDLE_VALUE {
return;
}
loop {
if let Some(s) = state {
if s.load(Ordering::Relaxed) == ProcessingState::Stopped as u8 {
FindClose(handle);
return;
}
}
let filename_len = find_data
.cFileName
.iter()
.position(|&c| c == 0)
.unwrap_or(find_data.cFileName.len());
let filename = String::from_utf16_lossy(&find_data.cFileName[..filename_len]);
if filename != "." && filename != ".." {
let full_path = if path.ends_with('\\') || path.ends_with('/') {
let mut p = path.to_string();
p.push_str(&filename);
p
} else {
let mut p = path.to_string();
p.push('\\');
p.push_str(&filename);
p
};
let is_dir = (find_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0;
let is_reparse = (find_data.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) != 0;
visitor(&full_path, is_dir, &find_data);
if is_dir && !is_reparse {
walk_directory_generic(&full_path, state, visitor);
}
}
if FindNextFileW(handle, &mut find_data) == 0 {
break;
}
}
FindClose(handle);
}
}
fn scan_directory_optimized(
path: &str,
collect_paths: bool,
state: Option<&Arc<AtomicU8>>,
) -> ScanStats {
let mut stats = ScanStats::default();
walk_directory_generic(path, state, &mut |full_path, is_dir, find_data| {
if !is_dir {
stats.file_count += 1;
let size = ((find_data.nFileSizeHigh as u64) << 32) | (find_data.nFileSizeLow as u64);
stats.logical_size += size;
if collect_paths {
stats.file_paths.push(full_path.to_string());
}
}
});
stats
}
fn walk_directory_win32_disk_size(path: &str, total: &mut u64) {
walk_directory_generic(path, None, &mut |full_path, is_dir, _| {
if !is_dir {
*total += get_real_file_size(full_path);
}
});
}
fn walk_directory_win32_detect_algo(
path: &str,
found_algos: &mut std::collections::HashSet<u32>,
scanned: &mut usize,
max_scan: usize,
) {
walk_directory_generic(path, None, &mut |full_path, is_dir, _| {
if *scanned >= max_scan {
return;
}
if !is_dir {
if let Some(algo) = get_wof_algorithm(full_path) {
found_algos.insert(algo as u32);
}
*scanned += 1;
}
});
}
pub fn calculate_folder_logical_size(path: &str) -> u64 {
scan_directory_optimized(path, false, None).logical_size
}
pub fn calculate_folder_disk_size(path: &str) -> u64 {
let mut total = 0u64;
walk_directory_win32_disk_size(path, &mut total);
total
}
pub fn detect_folder_algorithm(path: &str) -> crate::engine::wof::CompressionState {
use crate::engine::wof::CompressionState;
let mut found_algos = std::collections::HashSet::new();
let mut scanned = 0usize;
walk_directory_win32_detect_algo(path, &mut found_algos, &mut scanned, 50);
if found_algos.is_empty() {
return CompressionState::None;
}
if found_algos.len() > 1 {
return CompressionState::Mixed;
}
let algo_val = found_algos.into_iter().next().unwrap();
match algo_val {
0 => CompressionState::Specific(WofAlgorithm::Xpress4K),
1 => CompressionState::Specific(WofAlgorithm::Lzx),
2 => CompressionState::Specific(WofAlgorithm::Xpress8K),
3 => CompressionState::Specific(WofAlgorithm::Xpress16K),
_ => CompressionState::None,
}
}
pub fn calculate_path_logical_size(path: &str) -> u64 {
let p = std::path::Path::new(path);
if p.is_file() {
std::fs::metadata(path).map(|m| m.len()).unwrap_or(0)
} else {
calculate_folder_logical_size(path)
}
}
pub fn calculate_path_disk_size(path: &str) -> u64 {
let p = std::path::Path::new(path);
if p.is_file() {
get_real_file_size(path)
} else {
calculate_folder_disk_size(path)
}
}
pub fn detect_path_algorithm(path: &str) -> crate::engine::wof::CompressionState {
use crate::engine::wof::CompressionState;
let p = std::path::Path::new(path);
if p.is_file() {
match get_wof_algorithm(path) {
Some(algo) => CompressionState::Specific(algo),
None => CompressionState::None,
}
} else {
detect_folder_algorithm(path)
}
}
fn try_compress_with_lock_handling(
path: &str,
algo: WofAlgorithm,
force: bool,
main_hwnd: usize
) -> Result<bool, String> {
match smart_compress(path, algo, force) {
Ok(res) => Ok(res),
Err(e) => {
if force && e == 32 { let blockers_res = std::panic::catch_unwind(|| {
crate::engine::process::get_file_blockers(path)
});
if let Ok(blockers) = blockers_res {
if !blockers.is_empty() {
let name = &blockers[0].name;
let name_wide = to_wstring(name);
let hwnd = main_hwnd as HWND;
let res = unsafe {
SendMessageW(hwnd, 0x8004, name_wide.as_ptr() as usize, 0)
};
if res == 1 {
for b in blockers {
let _ = crate::engine::process::kill_process(b.pid);
}
std::thread::sleep(std::time::Duration::from_millis(100));
return smart_compress(path, algo, force)
.map_err(|e2| {
let mut s = "Failed retry ".to_string();
s.push_str(path);
s.push_str(": ");
s.push_str(&e2.to_string()); s
});
}
}
}
}
let mut s = "Failed ".to_string();
s.push_str(path);
s.push_str(": ");
s.push_str(&e.to_string());
Err(s)
}
}
}
pub fn process_file_core(
path: &str,
algo: WofAlgorithm,
action: BatchAction,
force: bool,
main_hwnd: usize,
tx: &Sender<UiMessage>,
guard_enabled: bool,
) -> (ProcessResult, u64) {
match action {
BatchAction::Compress => {
let mut final_res = ProcessResult::Success;
if guard_enabled && !force && is_critical_path(path) {
let p = to_wstring(path);
let msg = concat_wstrings(&[&to_wstring("Skipped (Critical System Path): "), &p]);
let _ = tx.send(UiMessage::Log(msg));
final_res = ProcessResult::Skipped("System Path".to_string());
}
else if !force {
if let Some(current_algo) = crate::engine::wof::get_wof_algorithm(path) {
if current_algo == algo {
let p = to_wstring(path);
let msg = concat_wstrings(&[&to_wstring("Skipped (Already compressed): "), &p]);
let _ = tx.send(UiMessage::Log(msg));
final_res = ProcessResult::Skipped("Already optimal".to_string());
}
}
}
if let ProcessResult::Success = final_res {
if !force && should_skip_extension(path) {
let p = to_wstring(path);
let msg = concat_wstrings(&[&to_wstring("Skipped (filtered): "), &p]);
let _ = tx.send(UiMessage::Log(msg));
final_res = ProcessResult::Skipped("Filtered extension".to_string());
}
}
if let ProcessResult::Success = final_res {
match try_compress_with_lock_handling(path, algo, force, main_hwnd) {
Ok(true) => { final_res = ProcessResult::Success; }
Ok(false) => {
let p = to_wstring(path);
let msg = concat_wstrings(&[&to_wstring("Skipped (OS: Not Beneficial): "), &p]);
let _ = tx.send(UiMessage::Log(msg));
final_res = ProcessResult::Skipped("Not beneficial".to_string());
}
Err(msg) => {
let _ = tx.send(UiMessage::Error(to_wstring(&msg)));
final_res = ProcessResult::Failed(msg);
}
}
}
let size = get_real_file_size(path);
(final_res, size)
}
BatchAction::Decompress => {
match uncompress_file(path) {
Ok(_) => (ProcessResult::Success, get_real_file_size(path)),
Err(e) => {
let mut s = "Failed ".to_string();
s.push_str(path);
s.push_str(": ");
s.push_str(&e.to_string());
let w = to_wstring(&s);
let _ = tx.send(UiMessage::Error(w));
(ProcessResult::Failed(s), get_real_file_size(path))
}
}
}
}
}
struct FileTask {
path: String,
action: BatchAction,
row_idx: usize,
algorithm: WofAlgorithm,
}
struct SharedReceiver<T> {
rx: Mutex<Receiver<T>>,
}
impl<T> SharedReceiver<T> {
fn new(rx: Receiver<T>) -> Self {
Self { rx: Mutex::new(rx) }
}
fn recv(&self) -> Option<T> {
self.rx.lock().ok()?.recv().ok()
}
}
pub fn batch_process_worker(
items: Vec<(String, BatchAction, usize, WofAlgorithm)>,
tx: Sender<UiMessage>,
state: Arc<AtomicU8>,
force: bool,
main_hwnd: usize,
guard_enabled: bool,
) {
let _sleep_guard = ExecutionStateGuard::new();
let _ = tx.send(UiMessage::Status(to_wstring("Discovering files...")));
let mut row_totals: std::collections::HashMap<usize, u64> = std::collections::HashMap::new();
let mut row_paths: std::collections::HashMap<usize, String> = std::collections::HashMap::new();
let mut total_files = 0u64;
for (path, _, row, _) in &items {
let row_count = if std::path::Path::new(path).is_file() {
1u64
} else {
scan_directory_optimized(path, false, None).file_count
};
row_totals.insert(*row, row_count);
row_paths.insert(*row, path.clone());
total_files += row_count;
let row_cnt_w = u64_to_wstring(row_count);
let prog_str = concat_wstrings(&[&to_wstring("0/"), &row_cnt_w]);
let _ = tx.send(UiMessage::RowUpdate(*row as i32, prog_str, to_wstring("Running"), vec![0;1])); }
let _ = tx.send(UiMessage::Progress(0, total_files));
let num_threads = std::thread::available_parallelism().map(|n| n.get()).unwrap_or(4);
let total_w = u64_to_wstring(total_files);
let threads_w = u64_to_wstring(num_threads as u64);
let msg = concat_wstrings(&[
&to_wstring("Processing "), &total_w, &to_wstring(" files with "), &threads_w, &to_wstring(" threads...")
]);
let _ = tx.send(UiMessage::Status(msg));
if total_files == 0 {
let _ = tx.send(UiMessage::Status(to_wstring("No files found to process.")));
let _ = tx.send(UiMessage::Finished);
return;
}
let (file_tx, file_rx) = sync_channel::<FileTask>(1024);
let shared_rx = Arc::new(SharedReceiver::new(file_rx));
let processed = Arc::new(AtomicU64::new(0));
let success = Arc::new(AtomicU64::new(0));
let failed = Arc::new(AtomicU64::new(0));
let max_row = items.iter().map(|(_, _, r, _)| *r).max().unwrap_or(0);
let row_processed_counts: Arc<Vec<AtomicU64>> = Arc::new((0..=max_row).map(|_| AtomicU64::new(0)).collect());
let row_disk_sizes: Arc<Vec<AtomicU64>> = Arc::new((0..=max_row).map(|_| AtomicU64::new(0)).collect());
let row_totals = Arc::new(row_totals);
let row_paths = Arc::new(row_paths);
let state_producer = Arc::clone(&state);
let items_for_producer = items.clone();
let producer_handle = std::thread::spawn(move || {
for (path, action, row, algo) in items_for_producer {
if state_producer.load(Ordering::Relaxed) == ProcessingState::Stopped as u8 {
break;
}
if std::path::Path::new(&path).is_file() {
let _ = file_tx.send(FileTask { path, action, row_idx: row, algorithm: algo });
} else {
let stats = scan_directory_optimized(&path, true, Some(&state_producer));
for file_path in stats.file_paths {
if state_producer.load(Ordering::Relaxed) == ProcessingState::Stopped as u8 {
break;
}
let _ = file_tx.send(FileTask {
path: file_path,
action,
row_idx: row,
algorithm: algo,
});
}
}
}
drop(file_tx);
});
std::thread::scope(|s| {
for _ in 0..num_threads {
let shared_rx_clone = Arc::clone(&shared_rx);
let processed_ref = Arc::clone(&processed);
let success_ref = Arc::clone(&success);
let failed_ref = Arc::clone(&failed);
let row_counts_ref = Arc::clone(&row_processed_counts);
let row_sizes_ref = Arc::clone(&row_disk_sizes);
let row_totals_ref = Arc::clone(&row_totals);
let row_paths_ref = Arc::clone(&row_paths);
let tx_clone = tx.clone();
let state_ref = Arc::clone(&state);
let force_copy = force;
let hwnd_val = main_hwnd;
let guard_enabled_copy = guard_enabled;
let total_files_copy = total_files;
s.spawn(move || {
crate::engine::wof::enable_backup_privileges();
while let Some(task) = shared_rx_clone.recv() {
while state_ref.load(Ordering::Relaxed) == ProcessingState::Paused as u8 {
std::thread::sleep(std::time::Duration::from_millis(100));
}
if state_ref.load(Ordering::Relaxed) == ProcessingState::Stopped as u8 {
break;
}
let (result, size_on_disk) = process_file_core(
&task.path,
task.algorithm,
task.action,
force_copy,
hwnd_val,
&tx_clone,
guard_enabled_copy
);
match result {
ProcessResult::Success | ProcessResult::Skipped(_) => {
success_ref.fetch_add(1, Ordering::Relaxed);
}
ProcessResult::Failed(_) => {
failed_ref.fetch_add(1, Ordering::Relaxed);
}
}
let current_global = processed_ref.fetch_add(1, Ordering::Relaxed) + 1;
if let Some(counter) = row_counts_ref.get(task.row_idx) {
let current_row = counter.fetch_add(1, Ordering::Relaxed) + 1;
let total_row = *row_totals_ref.get(&task.row_idx).unwrap_or(&1);
if current_row % 5 == 0 || current_row == total_row {
let cur_w = u64_to_wstring(current_row);
let tot_w = u64_to_wstring(total_row);
let prog_w = concat_wstrings(&[&cur_w, &to_wstring("/"), &tot_w]);
if let Some(size_counter) = row_sizes_ref.get(task.row_idx) {
size_counter.fetch_add(size_on_disk, Ordering::Relaxed);
}
if current_row == total_row {
let _ = tx_clone.send(UiMessage::RowUpdate(task.row_idx as i32, prog_w, to_wstring("Finishing..."), vec![0;1]));
let final_w = to_wstring("Done");
let final_size = if let Some(sc) = row_sizes_ref.get(task.row_idx) {
sc.load(Ordering::Relaxed)
} else { 0 };
let size_w = format_size(final_size);
let algo_state = if let Some(p) = row_paths_ref.get(&task.row_idx) {
detect_path_algorithm(p)
} else {
crate::engine::wof::CompressionState::None
};
let _ = tx_clone.send(UiMessage::ItemFinished(task.row_idx as i32, final_w, size_w, algo_state));
} else {
let _ = tx_clone.send(UiMessage::RowUpdate(task.row_idx as i32, prog_w, to_wstring("Running"), vec![0;1]));
}
}
}
if current_global % 20 == 0 || current_global == total_files_copy {
let _ = tx_clone.send(UiMessage::Progress(current_global, total_files_copy));
let cur_w = u64_to_wstring(current_global);
let tot_w = u64_to_wstring(total_files_copy);
let stat_w = concat_wstrings(&[&to_wstring("Processed "), &cur_w, &to_wstring("/"), &tot_w, &to_wstring(" files...")]);
let _ = tx_clone.send(UiMessage::Status(stat_w));
}
}
});
}
});
let _ = producer_handle.join();
if state.load(Ordering::Relaxed) == ProcessingState::Stopped as u8 {
let _ = tx.send(UiMessage::Status(to_wstring("Batch processing cancelled.")));
let _ = tx.send(UiMessage::Finished);
return;
}
let s = success.load(Ordering::Relaxed);
let f = failed.load(Ordering::Relaxed);
let p = processed.load(Ordering::Relaxed);
let p_w = u64_to_wstring(p);
let s_w = u64_to_wstring(s);
let f_w = u64_to_wstring(f);
let report_w = concat_wstrings(&[
&to_wstring("Batch complete! Processed: "), &p_w,
&to_wstring(" files | Success: "), &s_w,
&to_wstring(" | Failed: "), &f_w
]);
let _ = tx.send(UiMessage::Log(report_w.clone()));
let _ = tx.send(UiMessage::Status(report_w));
let _ = tx.send(UiMessage::Progress(total_files, total_files));
let _ = tx.send(UiMessage::Finished);
}
pub fn single_item_worker(
path: String,
algo: WofAlgorithm,
action: BatchAction,
row: i32,
tx: Sender<UiMessage>,
state: Arc<AtomicU8>,
force: bool,
main_hwnd: usize,
guard_enabled: bool,
) {
let mut success = 0u64;
let mut failed = 0u64;
let is_single_file = std::path::Path::new(&path).is_file();
let mut total_files = if is_single_file {
1
} else {
scan_directory_optimized(&path, false, None).file_count
};
let _ = tx.send(UiMessage::Progress(0, total_files));
let action_str = match action {
BatchAction::Compress => "Compressing",
BatchAction::Decompress => "Decompressing",
};
let tot_w = u64_to_wstring(total_files);
let path_w = to_wstring(&path);
let stat_w = concat_wstrings(&[
&to_wstring(action_str), &to_wstring(" "), &path_w, &to_wstring(" ("), &tot_w, &to_wstring(" files)...")
]);
let _ = tx.send(UiMessage::Status(stat_w));
let start_prog = concat_wstrings(&[&to_wstring("0/"), &tot_w]);
let _ = tx.send(UiMessage::RowUpdate(row, start_prog, to_wstring("Running"), vec![0;1]));
if is_single_file {
while state.load(Ordering::Relaxed) == ProcessingState::Paused as u8 {
std::thread::sleep(std::time::Duration::from_millis(100));
}
if state.load(Ordering::Relaxed) == ProcessingState::Stopped as u8 {
let _ = tx.send(UiMessage::ItemFinished(row, to_wstring("Cancelled"), vec![0;1], crate::engine::wof::CompressionState::None));
let _ = tx.send(UiMessage::Status(to_wstring("Cancelled.")));
let _ = tx.send(UiMessage::Finished);
return;
}
let (result, final_size) = process_file_core(&path, algo, action, force, main_hwnd, &tx, guard_enabled);
match result {
ProcessResult::Success => {
success += 1;
let disk_w = format_size(final_size);
let final_state = detect_path_algorithm(&path);
let _ = tx.send(UiMessage::ItemFinished(row, to_wstring("Done"), disk_w, final_state));
}
ProcessResult::Skipped(_) => {
success += 1;
let disk_w = format_size(final_size);
let final_state = detect_path_algorithm(&path);
let _ = tx.send(UiMessage::ItemFinished(row, to_wstring("Skipped"), disk_w, final_state));
}
ProcessResult::Failed(_) => {
failed += 1;
let _ = tx.send(UiMessage::ItemFinished(row, to_wstring("Failed"), vec![0;1], crate::engine::wof::CompressionState::None));
}
}
let _ = tx.send(UiMessage::RowUpdate(row, to_wstring("1/1"), to_wstring("Running"), vec![0;1]));
let _ = tx.send(UiMessage::Progress(1, 1));
} else {
let num_threads = std::thread::available_parallelism().map(|n| n.get()).unwrap_or(4);
let (file_tx, file_rx) = sync_channel::<String>(1024);
let shared_rx = Arc::new(SharedReceiver::new(file_rx));
let processed = Arc::new(AtomicU64::new(0));
let success_atomic = Arc::new(AtomicU64::new(0));
let failed_atomic = Arc::new(AtomicU64::new(0));
let stats = scan_directory_optimized(&path, true, Some(&state));
let files = stats.file_paths;
total_files = files.len() as u64;
let state_producer = Arc::clone(&state);
let producer_handle = std::thread::spawn(move || {
for file_path in files {
if state_producer.load(Ordering::Relaxed) == ProcessingState::Stopped as u8 {
break;
}
let _ = file_tx.send(file_path);
}
drop(file_tx);
});
std::thread::scope(|s| {
for _ in 0..num_threads {
let shared_rx_clone = Arc::clone(&shared_rx);
let processed_ref = Arc::clone(&processed);
let success_ref = Arc::clone(&success_atomic);
let failed_ref = Arc::clone(&failed_atomic);
let state_ref = Arc::clone(&state);
let algo_copy = algo;
let action_copy = action;
let tx_clone = tx.clone();
let row_copy = row;
let force_copy = force;
let hwnd_val = main_hwnd;
let guard_enabled_copy = guard_enabled;
let total_files_copy = total_files;
s.spawn(move || {
crate::engine::wof::enable_backup_privileges();
while let Some(file_path) = shared_rx_clone.recv() {
while state_ref.load(Ordering::Relaxed) == ProcessingState::Paused as u8 {
std::thread::sleep(std::time::Duration::from_millis(100));
}
if state_ref.load(Ordering::Relaxed) == ProcessingState::Stopped as u8 {
break;
}
let (result, _) = process_file_core(
&file_path,
algo_copy,
action_copy,
force_copy,
hwnd_val,
&tx_clone,
guard_enabled_copy,
);
match result {
ProcessResult::Success | ProcessResult::Skipped(_) => {
success_ref.fetch_add(1, Ordering::Relaxed);
}
ProcessResult::Failed(_) => {
failed_ref.fetch_add(1, Ordering::Relaxed);
}
}
let current = processed_ref.fetch_add(1, Ordering::Relaxed) + 1;
if current % 100 == 0 || current == total_files_copy {
let cur_w = u64_to_wstring(current);
let tot_w = u64_to_wstring(total_files_copy);
let prog_w = concat_wstrings(&[&cur_w, &to_wstring("/"), &tot_w]);
let _ = tx_clone.send(UiMessage::RowUpdate(row_copy, prog_w, to_wstring("Running"), vec![0;1]));
}
let _ = tx_clone.send(UiMessage::Progress(current, total_files_copy));
}
});
}
});
let _ = producer_handle.join();
if state.load(Ordering::Relaxed) == ProcessingState::Stopped as u8 {
let _ = tx.send(UiMessage::ItemFinished(row, to_wstring("Cancelled"), vec![0;1], crate::engine::wof::CompressionState::None));
let _ = tx.send(UiMessage::Status(to_wstring("Cancelled.")));
let _ = tx.send(UiMessage::Finished);
return;
}
success = success_atomic.load(Ordering::Relaxed);
failed = failed_atomic.load(Ordering::Relaxed);
}
let size_after = calculate_folder_disk_size(&path);
let size_after_str = format_size(size_after);
let status_w = if failed > 0 {
let f_w = u64_to_wstring(failed);
concat_wstrings(&[&to_wstring("Done+"), &f_w, &to_wstring(" err")])
} else {
to_wstring("Done")
};
let final_state = detect_path_algorithm(&path);
let _ = tx.send(UiMessage::ItemFinished(row, status_w, size_after_str, final_state));
let t_w = u64_to_wstring(total_files);
let s_w = u64_to_wstring(success);
let f_w = u64_to_wstring(failed);
let report_w = concat_wstrings(&[
&to_wstring("Done! "), &t_w,
&to_wstring(" files | Success: "), &s_w,
&to_wstring(" | Failed: "), &f_w
]);
let _ = tx.send(UiMessage::Status(report_w));
let _ = tx.send(UiMessage::Progress(total_files, total_files));
let _ = tx.send(UiMessage::Finished);
}