1
0
Fork 0
mirror of synced 2024-05-05 21:13:41 +12:00

Add support for checking for zeroed files (#88)

This commit is contained in:
Rafał Mikrut 2020-10-31 05:29:11 -04:00 committed by GitHub
parent 7112ff6b92
commit 777ac50f07
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 769 additions and 15 deletions

View file

@ -14,11 +14,11 @@ Other tools are usually written in C/C++ for high performance but still need to
But the most important thing for me was to learn Rust and create a program useful for the open source community.
## Features
- Written in fast and memory safe Rust
- Written in memory safe Rust
- Amazingly fast - due using more or less advanced algorithms
- CLI frontend, very fast and powerful with rich help
- GUI GTK frontend - uses modern GTK 3 and looks similar to FSlint
- Light/Dark theme match the appearance of the system
- GUI Orbtk frontend(Very early WIP) - alternative GUI with reduced functionality
- Saving results to a file - allows reading entries found by the tool easily
- Rich search option - allows setting absolute included and excluded directories, set of allowed file extensions or excluded items with * wildcard
- Clean Glade file in which UI can be easily modernized
@ -29,6 +29,7 @@ But the most important thing for me was to learn Rust and create a program usefu
- Empty Files - Looks for empty files across disk
- Temporary Files - Allows finding temporary files
- Similar Files - Finds files which are not exactly the same
- Zeroed Files - Find files which are filled with zeros(usually corrupted)
## Usage and requirements
@ -49,7 +50,7 @@ cargo install czkawka_gui
```
You can update package by typing same command.
### Snap, Flatpak
### Snap, Flatpak
Maybe someday
### Debian/Ubuntu repository and PPA
@ -58,7 +59,7 @@ Tried to setup it, but for now I have problems described in this issue
https://salsa.debian.org/rust-team/debcargo-conf/-/issues/21
### AUR - Arch Linux Package(unofficial)
### AUR - Arch Linux Package (unofficial)
Czkawka is also available in Arch Linux's AUR from which it can be easily downloaded and installed on the system.
```
yay -Syu czkawka-git
@ -106,7 +107,7 @@ cargo run --bin czkawka_cli
```
![CLI](https://user-images.githubusercontent.com/41945903/93716816-0bbcfd80-fb72-11ea-8d31-4c87cc2abe6d.png)
## Speed
## Benchmarks
Since Czkawka is written in Rust and aims to be a faster alternative to FSlint (written in Python), we need to compare the speed of these tools.
I prepared a directory and performed a test without any folder exceptions(I removed all directories from FSlint and Czkawka from other tabs than Include Directory) which contained 320004 files and 36902 folders and 108844 duplicated files in 34475 groups which took 4.53 GB.
@ -121,8 +122,8 @@ DupeGuru after selecting files, froze at 45% for ~15 minutes, so I just kill it.
|:----------:|:-------------:|
| FSlint 2.4.7 (First Run)| 255s |
| FSlint 2.4.7 (Second Run)| 126s |
| Czkawka 1.2.2 (First Run) | 150s |
| Czkawka 1.2.2 (Second Run) | 107s |
| Czkawka 1.3.0 (First Run) | 150s |
| Czkawka 1.3.0 (Second Run) | 107s |
| DupeGuru 4.0.4 (First Run) | - |
| DupeGuru 4.0.4 (Second Run) | - |
@ -133,21 +134,21 @@ To not get Dupeguru crash I checked smaller directory with 217986 files and 4188
| App| Idle Ram | Max Operational Ram Usage | Stabilized after search |
|:----------:|:-------------:|:-------------:|:-------------:|
| FSlint 2.4.7 | 54 MB | 120 MB | 117 MB |
| Czkawka 1.2.2 | 8 MB | 42 MB | 41 MB |
| Czkawka 1.3.0 | 8 MB | 42 MB | 41 MB |
| DupeGuru 4.0.4 | 110 MB | 637 MB | 602 MB |
Similar Images which check 386 files which takes 1,9GB
| App| Scan time |
|:----------:|:-------------:|
| Czkawka 1.2.2 | 267s |
| Czkawka 1.3.0 | 267s |
| DupeGuru 4.0.4 | 75s |
Similar Images which check 5018 files which takes 389MB
| App| Scan time |
|:----------:|:-------------:|
| Czkawka 1.2.2 | 45s |
| Czkawka 1.3.0 | 45s |
| DupeGuru 4.0.4 | 87s |
So still is a big room for improvements.
@ -166,6 +167,7 @@ So still is a big room for improvements.
| Temporary files | X | X | |
| Big files | X | | |
| Similar images | X | | X |
| Zeroed Files| X | | |
| Checking files EXIF| | | X |
| Installed packages | | X | |
| Invalid names | | X | |

View file

@ -98,6 +98,25 @@ pub enum Commands {
#[structopt(flatten)]
not_recursive: NotRecursive,
},
#[structopt(name = "zeroed", about = "Finds zeroed files", help_message = HELP_MESSAGE, after_help = "EXAMPLE:\n czkawka zeroed -d /home/rafal -e /home/rafal/Pulpit -f results.txt")]
ZeroedFiles {
#[structopt(flatten)]
directories: Directories,
#[structopt(flatten)]
excluded_directories: ExcludedDirectories,
#[structopt(flatten)]
excluded_items: ExcludedItems,
#[structopt(flatten)]
allowed_extensions: AllowedExtensions,
#[structopt(short = "D", long, help = "Delete found files")]
delete_files: bool,
#[structopt(flatten)]
file_to_save: FileToSave,
#[structopt(flatten)]
not_recursive: NotRecursive,
#[structopt(short, long, parse(try_from_str = parse_minimal_file_size), default_value = "1024", help = "Minimum size in bytes", long_help = "Minimum size of checked files in bytes, assigning bigger value may speed up searching")]
minimal_file_size: u64,
},
}
#[derive(Debug, StructOpt)]
@ -207,4 +226,5 @@ EXAMPLES:
{bin} big -d /home/rafal/ /home/piszczal -e /home/rafal/Roman -n 25 -x VIDEO -f results.txt
{bin} empty-files -d /home/rafal /home/szczekacz -e /home/rafal/Pulpit -R -f results.txt
{bin} temp -d /home/rafal/ -E */.git */tmp* *Pulpit -f results.txt -D
{bin} image -d /home/rafal -e /home/rafal/Pulpit -f results.txt"#;
{bin} image -d /home/rafal -e /home/rafal/Pulpit -f results.txt
{bin} zeroed -d /home/rafal -e /home/rafal/Pulpit -f results.txt"#;

View file

@ -12,6 +12,7 @@ use czkawka_core::{
empty_folder::EmptyFolder,
similar_files::SimilarImages,
temporary::{self, Temporary},
zeroed::{self, ZeroedFiles},
};
use std::{path::PathBuf, process};
use structopt::StructOpt;
@ -208,5 +209,42 @@ fn main() {
sf.print_results();
sf.get_text_messages().print_messages();
}
Commands::ZeroedFiles {
directories,
excluded_directories,
excluded_items,
allowed_extensions,
delete_files,
file_to_save,
not_recursive,
minimal_file_size,
} => {
let mut zf = ZeroedFiles::new();
zf.set_included_directory(path_list_to_str(directories.directories));
zf.set_excluded_directory(path_list_to_str(excluded_directories.excluded_directories));
zf.set_excluded_items(path_list_to_str(excluded_items.excluded_items));
zf.set_allowed_extensions(allowed_extensions.allowed_extensions.join(","));
zf.set_minimal_file_size(minimal_file_size);
zf.set_recursive_search(!not_recursive.not_recursive);
if delete_files {
zf.set_delete_method(zeroed::DeleteMethod::Delete);
}
zf.find_zeroed_files(None);
if let Some(file_name) = file_to_save.file_name() {
if !zf.save_results_to_file(file_name) {
zf.get_text_messages().print_messages();
process::exit(1);
}
}
#[cfg(not(debug_assertions))] // This will show too much probably unnecessary data to debug, comment line only if needed
zf.print_results();
zf.get_text_messages().print_messages();
}
}
}

View file

@ -11,5 +11,6 @@ pub mod common_items;
pub mod common_messages;
pub mod common_traits;
pub mod similar_files;
pub mod zeroed;
pub const CZKAWKA_VERSION: &str = env!("CARGO_PKG_VERSION");

403
czkawka_core/src/zeroed.rs Normal file
View file

@ -0,0 +1,403 @@
use std::fs;
use std::fs::{File, Metadata};
use std::io::prelude::*;
use std::path::PathBuf;
use std::time::{SystemTime, UNIX_EPOCH};
use crate::common::Common;
use crate::common_directory::Directories;
use crate::common_extensions::Extensions;
use crate::common_items::ExcludedItems;
use crate::common_messages::Messages;
use crate::common_traits::*;
use crossbeam_channel::Receiver;
#[derive(Eq, PartialEq, Clone, Debug)]
pub enum DeleteMethod {
None,
Delete,
}
#[derive(Clone)]
pub struct FileEntry {
pub path: PathBuf,
pub size: u64,
pub modified_date: u64,
}
/// Info struck with helpful information's about results
#[derive(Default)]
pub struct Info {
pub number_of_checked_files: usize,
pub number_of_checked_folders: usize,
pub number_of_ignored_files: usize,
pub number_of_ignored_things: usize,
pub number_of_zeroed_files: usize,
pub number_of_removed_files: usize,
pub number_of_failed_to_remove_files: usize,
}
impl Info {
pub fn new() -> Self {
Default::default()
}
}
/// Struct with required information's to work
pub struct ZeroedFiles {
text_messages: Messages,
information: Info,
zeroed_files: Vec<FileEntry>,
directories: Directories,
allowed_extensions: Extensions,
excluded_items: ExcludedItems,
recursive_search: bool,
delete_method: DeleteMethod,
stopped_search: bool,
minimal_file_size: u64,
}
impl ZeroedFiles {
pub fn new() -> Self {
Self {
text_messages: Messages::new(),
information: Info::new(),
recursive_search: true,
allowed_extensions: Extensions::new(),
directories: Directories::new(),
excluded_items: ExcludedItems::new(),
zeroed_files: vec![],
delete_method: DeleteMethod::None,
stopped_search: false,
minimal_file_size: 1024,
}
}
pub fn find_zeroed_files(&mut self, rx: Option<&Receiver<()>>) {
self.directories.optimize_directories(self.recursive_search, &mut self.text_messages);
if !self.check_files(rx) {
self.stopped_search = true;
return;
}
self.delete_files();
self.debug_print();
}
pub fn get_stopped_search(&self) -> bool {
self.stopped_search
}
pub const fn get_zeroed_files(&self) -> &Vec<FileEntry> {
&self.zeroed_files
}
pub const fn get_text_messages(&self) -> &Messages {
&self.text_messages
}
pub const fn get_information(&self) -> &Info {
&self.information
}
pub fn set_delete_method(&mut self, delete_method: DeleteMethod) {
self.delete_method = delete_method;
}
pub fn set_minimal_file_size(&mut self, minimal_file_size: u64) {
self.minimal_file_size = match minimal_file_size {
0 => 1,
t => t,
};
}
pub fn set_recursive_search(&mut self, recursive_search: bool) {
self.recursive_search = recursive_search;
}
pub fn set_included_directory(&mut self, included_directory: String) -> bool {
self.directories.set_included_directory(included_directory, &mut self.text_messages)
}
pub fn set_excluded_directory(&mut self, excluded_directory: String) {
self.directories.set_excluded_directory(excluded_directory, &mut self.text_messages);
}
pub fn set_allowed_extensions(&mut self, allowed_extensions: String) {
self.allowed_extensions.set_allowed_extensions(allowed_extensions, &mut self.text_messages);
}
pub fn set_excluded_items(&mut self, excluded_items: String) {
self.excluded_items.set_excluded_items(excluded_items, &mut self.text_messages);
}
/// Check files for files which have 0
fn check_files(&mut self, rx: Option<&Receiver<()>>) -> bool {
let start_time: SystemTime = SystemTime::now();
let mut folders_to_check: Vec<PathBuf> = Vec::with_capacity(1024 * 2); // This should be small enough too not see to big difference and big enough to store most of paths without needing to resize vector
// Add root folders for finding
for id in &self.directories.included_directories {
folders_to_check.push(id.clone());
}
self.information.number_of_checked_folders += folders_to_check.len();
while !folders_to_check.is_empty() {
if rx.is_some() && rx.unwrap().try_recv().is_ok() {
return false;
}
let current_folder = folders_to_check.pop().unwrap();
// Read current dir, if permission are denied just go to next
let read_dir = match fs::read_dir(&current_folder) {
Ok(t) => t,
Err(_) => {
self.text_messages.warnings.push(format!("Cannot open dir {}", current_folder.display()));
continue;
} // Permissions denied
};
// Check every sub folder/file/link etc.
'dir: for entry in read_dir {
let entry_data = match entry {
Ok(t) => t,
Err(_) => {
self.text_messages.warnings.push(format!("Cannot read entry in dir {}", current_folder.display()));
continue;
} //Permissions denied
};
let metadata: Metadata = match entry_data.metadata() {
Ok(t) => t,
Err(_) => {
self.text_messages.warnings.push(format!("Cannot read metadata in dir {}", current_folder.display()));
continue;
} //Permissions denied
};
if metadata.is_dir() {
self.information.number_of_checked_folders += 1;
if !self.recursive_search {
continue;
}
let next_folder = current_folder.join(entry_data.file_name());
if self.directories.is_excluded(&next_folder) || self.excluded_items.is_excluded(&next_folder) {
continue 'dir;
}
folders_to_check.push(next_folder);
} else if metadata.is_file() {
if metadata.len() == 0 || metadata.len() < self.minimal_file_size {
self.information.number_of_ignored_files += 1;
continue 'dir;
}
let file_name_lowercase: String = match entry_data.file_name().into_string() {
Ok(t) => t,
Err(_) => continue,
}
.to_lowercase();
// Checking allowed extensions
if !self.allowed_extensions.file_extensions.is_empty() {
let allowed = self.allowed_extensions.file_extensions.iter().any(|e| file_name_lowercase.ends_with((".".to_string() + e.to_lowercase().as_str()).as_str()));
if !allowed {
// Not an allowed extension, ignore it.
self.information.number_of_ignored_files += 1;
continue 'dir;
}
}
// Checking files
let current_file_name = current_folder.join(entry_data.file_name());
if self.excluded_items.is_excluded(&current_file_name) {
continue 'dir;
}
let mut file_handler: File = match File::open(&current_file_name) {
Ok(t) => t,
Err(_) => {
continue 'dir;
}
};
let mut first_search: bool = true;
let mut n;
loop {
if first_search {
let mut buffer = [0u8; 64];
n = match file_handler.read(&mut buffer) {
Ok(t) => t,
Err(_) => {
continue 'dir;
}
};
for i in buffer[0..n].iter() {
if *i != 0 {
continue 'dir; // Not zeroed file
}
}
first_search = false;
} else {
let mut buffer = [0u8; 1024 * 32];
n = match file_handler.read(&mut buffer) {
Ok(t) => t,
Err(_) => {
continue 'dir;
}
};
for i in buffer[0..n].iter() {
if *i != 0 {
continue 'dir; // Not zeroed file
}
}
}
if n == 0 {
break;
}
}
// Creating new file entry
let fe: FileEntry = FileEntry {
path: current_file_name.clone(),
size: metadata.len(),
modified_date: match metadata.modified() {
Ok(t) => match t.duration_since(UNIX_EPOCH) {
Ok(d) => d.as_secs(),
Err(_) => {
self.text_messages.warnings.push(format!("File {} seems to be modified before Unix Epoch.", current_file_name.display()));
0
}
},
Err(_) => {
self.text_messages.warnings.push(format!("Unable to get modification date from file {}", current_file_name.display()));
continue;
} // Permissions Denied
},
};
// Adding files to Vector
self.zeroed_files.push(fe);
self.information.number_of_checked_files += 1;
} else {
// Probably this is symbolic links so we are free to ignore this
self.information.number_of_ignored_things += 1;
}
}
}
self.information.number_of_zeroed_files = self.zeroed_files.len();
Common::print_time(start_time, SystemTime::now(), "check_files".to_string());
true
}
/// Function to delete files, from filed Vector
fn delete_files(&mut self) {
let start_time: SystemTime = SystemTime::now();
match self.delete_method {
DeleteMethod::Delete => {
for file_entry in &self.zeroed_files {
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
}
}
Common::print_time(start_time, SystemTime::now(), "delete_files".to_string());
}
}
impl Default for ZeroedFiles {
fn default() -> Self {
Self::new()
}
}
impl DebugPrint for ZeroedFiles {
#[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!("Number of checked files - {}", self.information.number_of_checked_files);
println!("Number of checked folders - {}", self.information.number_of_checked_folders);
println!("Number of ignored files - {}", self.information.number_of_ignored_files);
println!("Number of ignored things(like symbolic links) - {}", self.information.number_of_ignored_things);
println!("Number of removed files - {}", self.information.number_of_removed_files);
println!("Number of failed to remove files - {}", self.information.number_of_failed_to_remove_files);
println!("### Other");
println!("Zeroed list size - {}", self.zeroed_files.len());
println!("Allowed extensions - {:?}", self.allowed_extensions.file_extensions);
println!("Excluded items - {:?}", self.excluded_items.items);
println!("Included directories - {:?}", self.directories.included_directories);
println!("Excluded directories - {:?}", self.directories.excluded_directories);
println!("Recursive search - {}", self.recursive_search.to_string());
println!("Delete Method - {:?}", self.delete_method);
println!("Minimal File Size - {:?}", self.minimal_file_size);
println!("-----------------------------------------");
}
}
impl SaveResults for ZeroedFiles {
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(),
};
let mut file = match File::create(&file_name) {
Ok(t) => t,
Err(_) => {
self.text_messages.errors.push(format!("Failed to create file {}", file_name));
return false;
}
};
if writeln!(
file,
"Results of searching {:?} with excluded directories {:?} and excluded items {:?}",
self.directories.included_directories, self.directories.excluded_directories, self.excluded_items.items
)
.is_err()
{
self.text_messages.errors.push(format!("Failed to save results to file {}", file_name));
return false;
}
if !self.zeroed_files.is_empty() {
writeln!(file, "Found {} zeroed files.", self.information.number_of_zeroed_files).unwrap();
for file_entry in self.zeroed_files.iter() {
writeln!(file, "{}", file_entry.path.display()).unwrap();
}
} else {
write!(file, "Not found any zeroed files.").unwrap();
}
Common::print_time(start_time, SystemTime::now(), "save_results_to_file".to_string());
true
}
}
impl PrintResults for ZeroedFiles {
/// Print information's about duplicated entries
/// Only needed for CLI
fn print_results(&self) {
let start_time: SystemTime = SystemTime::now();
println!("Found {} zeroed files.\n", self.information.number_of_zeroed_files);
for file_entry in self.zeroed_files.iter() {
println!("{}", file_entry.path.display());
}
Common::print_time(start_time, SystemTime::now(), "print_entries".to_string());
}
}

View file

@ -1066,9 +1066,6 @@ Author: Rafał Mikrut
<property name="position">0</property>
</packing>
</child>
<child>
<placeholder/>
</child>
<child>
<object class="GtkScrolledWindow" id="scrolled_window_similar_images_finder">
<property name="visible">True</property>
@ -1100,6 +1097,81 @@ Author: Rafał Mikrut
<property name="tab_fill">False</property>
</packing>
</child>
<child>
<object class="GtkBox" id="notebook_main_zeroed_files_finder">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="spacing">8</property>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Minimal file size(in bytes)</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkEntry" id="entry_zeroed_files_minimal_size">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="max_length">15</property>
<property name="text" translatable="yes">1024</property>
<property name="caps_lock_warning">False</property>
<property name="input_purpose">number</property>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkScrolledWindow" id="scrolled_window_zeroed_files_finder">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="shadow_type">in</property>
<child>
<placeholder/>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
</object>
<packing>
<property name="position">6</property>
</packing>
</child>
<child type="tab">
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Zeroed Files</property>
</object>
<packing>
<property name="position">6</property>
<property name="tab_fill">False</property>
</packing>
</child>
</object>
<packing>
<property name="expand">True</property>

View file

@ -251,3 +251,43 @@ pub fn create_tree_view_directories(tree_view: &mut gtk::TreeView) {
tree_view.set_headers_visible(false);
}
pub fn create_tree_view_zeroed_files(tree_view: &mut gtk::TreeView) {
let renderer = gtk::CellRendererText::new();
let column: gtk::TreeViewColumn = TreeViewColumn::new();
column.pack_start(&renderer, true);
column.set_title("Size");
column.set_resizable(true);
column.set_min_width(50);
column.add_attribute(&renderer, "text", ColumnsZeroedFiles::Size as i32);
tree_view.append_column(&column);
let renderer = gtk::CellRendererText::new();
let column: gtk::TreeViewColumn = TreeViewColumn::new();
column.pack_start(&renderer, true);
column.set_title("File Name");
column.set_resizable(true);
column.set_min_width(50);
column.add_attribute(&renderer, "text", ColumnsZeroedFiles::Name as i32);
tree_view.append_column(&column);
let renderer = gtk::CellRendererText::new();
let column: gtk::TreeViewColumn = TreeViewColumn::new();
column.pack_start(&renderer, true);
column.set_title("Path");
column.set_resizable(true);
column.set_min_width(100);
column.add_attribute(&renderer, "text", ColumnsZeroedFiles::Path as i32);
tree_view.append_column(&column);
let renderer = gtk::CellRendererText::new();
let column: gtk::TreeViewColumn = TreeViewColumn::new();
column.pack_start(&renderer, true);
column.set_title("Modification Date");
column.set_resizable(true);
column.set_min_width(100);
column.add_attribute(&renderer, "text", ColumnsZeroedFiles::Modification as i32);
tree_view.append_column(&column);
tree_view.set_vexpand(true);
}

View file

@ -100,3 +100,20 @@ pub fn opening_double_click_function_big_files(tree_view: &gtk::TreeView, event:
}
gtk::Inhibit(false)
}
pub fn opening_double_click_function_zeroed_files(tree_view: &gtk::TreeView, event: &gdk::EventButton) -> gtk::Inhibit {
if event.get_event_type() == gdk::EventType::DoubleButtonPress {
let selection = tree_view.get_selection();
let (selection_rows, tree_model) = selection.get_selected_rows();
for tree_path in selection_rows.iter().rev() {
let name = tree_model.get_value(&tree_model.get_iter(tree_path).unwrap(), ColumnsZeroedFiles::Name as i32).get::<String>().unwrap().unwrap();
let path = tree_model.get_value(&tree_model.get_iter(tree_path).unwrap(), ColumnsZeroedFiles::Path as i32).get::<String>().unwrap().unwrap();
if open::that(format!("{}/{}", path, name)).is_err() {
println!("Failed to open {}/{}", path, name);
}
}
}
gtk::Inhibit(false)
}

View file

@ -49,6 +49,12 @@ pub enum ColumnsSimilarImages {
Color,
TextColor,
}
pub enum ColumnsZeroedFiles {
Size = 0,
Name,
Path,
Modification,
}
pub const TEXT_COLOR: &str = "#ffffff";
pub const MAIN_ROW_COLOR: &str = "#343434";

View file

@ -20,6 +20,7 @@ use czkawka_core::empty_files::EmptyFiles;
use czkawka_core::empty_folder::EmptyFolder;
use czkawka_core::similar_files::SimilarImages;
use czkawka_core::temporary::Temporary;
use czkawka_core::zeroed::ZeroedFiles;
use gtk::prelude::*;
use gtk::{Builder, SelectionMode, TreeIter, TreeView};
use std::cell::RefCell;
@ -67,7 +68,7 @@ fn main() {
////////////////////////////////////////////////////////////////////////////////////////////////
//// States
let main_notebooks_labels = ["duplicate", "empty_folder", "empty_file", "temporary_file", "big_file", "similar_images"];
let main_notebooks_labels = ["duplicate", "empty_folder", "empty_file", "temporary_file", "big_file", "similar_images", "zeroed_files"];
let upper_notebooks_labels = [/*"general",*/ "included_directories", "excluded_directories", "excluded_items", "allowed_extensions"];
let buttons_labels = ["search", "stop", "resume", "pause", "select", "delete", "save"];
@ -111,6 +112,7 @@ fn main() {
let shared_temporary_files_state: Rc<RefCell<_>> = Rc::new(RefCell::new(Temporary::new()));
let shared_big_files_state: Rc<RefCell<_>> = Rc::new(RefCell::new(BigFile::new()));
let shared_similar_images_state: Rc<RefCell<_>> = Rc::new(RefCell::new(SimilarImages::new()));
let shared_zeroed_files_state: Rc<RefCell<_>> = Rc::new(RefCell::new(ZeroedFiles::new()));
// State of confirmation dialogs
let shared_confirmation_dialog_delete_dialog_showing_state: Rc<RefCell<_>> = Rc::new(RefCell::new(true));
@ -198,6 +200,7 @@ fn main() {
let scrolled_window_main_temporary_files_finder: gtk::ScrolledWindow = builder.get_object("scrolled_window_main_temporary_files_finder").unwrap();
let scrolled_window_big_files_finder: gtk::ScrolledWindow = builder.get_object("scrolled_window_big_files_finder").unwrap();
let scrolled_window_similar_images_finder: gtk::ScrolledWindow = builder.get_object("scrolled_window_similar_images_finder").unwrap();
let scrolled_window_zeroed_files_finder: gtk::ScrolledWindow = builder.get_object("scrolled_window_zeroed_files_finder").unwrap();
// Upper notebook
let scrolled_window_included_directories: gtk::ScrolledWindow = builder.get_object("scrolled_window_included_directories").unwrap();
@ -212,6 +215,7 @@ fn main() {
BigFiles(BigFile),
Temporary(Temporary),
SimilarImages(SimilarImages),
ZeroedFiles(ZeroedFiles),
}
// Used for getting data from thread
@ -349,6 +353,22 @@ fn main() {
scrolled_window_similar_images_finder.add(&tree_view);
scrolled_window_similar_images_finder.show_all();
}
// Zeroed Files
{
let col_types: [glib::types::Type; 4] = [glib::types::Type::String, glib::types::Type::String, glib::types::Type::String, glib::types::Type::String];
let list_store: gtk::ListStore = gtk::ListStore::new(&col_types);
let mut tree_view: gtk::TreeView = TreeView::with_model(&list_store);
tree_view.get_selection().set_mode(SelectionMode::Multiple);
create_tree_view_zeroed_files(&mut tree_view);
tree_view.connect_button_press_event(opening_double_click_function_zeroed_files);
scrolled_window_zeroed_files_finder.add(&tree_view);
scrolled_window_zeroed_files_finder.show_all();
}
}
// Set Included Directory
@ -439,6 +459,7 @@ fn main() {
"scrolled_window_main_temporary_files_finder" => page = "temporary_file",
"notebook_big_main_file_finder" => page = "big_file",
"notebook_main_similar_images_finder_label" => page = "similar_images",
"notebook_main_zeroed_files_finder" => page = "zeroed_files",
e => {
panic!("Not existent page {}", e);
}
@ -625,6 +646,22 @@ fn main() {
let _ = sender.send(Message::SimilarImages(sf));
});
}
"notebook_main_zeroed_files_finder" => {
let sender = sender.clone();
let receiver_stop = rx.clone();
// Find temporary files
thread::spawn(move || {
let mut zf = ZeroedFiles::new();
zf.set_included_directory(included_directories);
zf.set_excluded_directory(excluded_directories);
zf.set_recursive_search(recursive_search);
zf.set_excluded_items(excluded_items);
zf.find_zeroed_files(Option::from(&receiver_stop));
let _ = sender.send(Message::ZeroedFiles(zf));
});
}
e => panic!("Not existent {}", e),
}
});
@ -641,6 +678,7 @@ fn main() {
let scrolled_window_main_empty_files_finder = scrolled_window_main_empty_files_finder.clone();
let scrolled_window_main_temporary_files_finder = scrolled_window_main_temporary_files_finder.clone();
let scrolled_window_similar_images_finder = scrolled_window_similar_images_finder.clone();
let scrolled_window_zeroed_files_finder = scrolled_window_zeroed_files_finder.clone();
buttons_delete.connect_clicked(move |_| {
if *shared_confirmation_dialog_delete_dialog_showing_state.borrow_mut() {
@ -988,6 +1026,36 @@ fn main() {
text_view_errors.get_buffer().unwrap().set_text(messages.as_str());
selection.unselect_all();
}
"notebook_main_zeroed_files_finder" => {
let tree_view = scrolled_window_zeroed_files_finder.get_children().get(0).unwrap().clone().downcast::<gtk::TreeView>().unwrap();
let selection = tree_view.get_selection();
let (selection_rows, tree_model) = selection.get_selected_rows();
if selection_rows.is_empty() {
return;
}
let list_store = tree_model.clone().downcast::<gtk::ListStore>().unwrap();
// let new_tree_model = TreeModel::new(); // TODO - maybe create new model when inserting a new data, because this seems to be not optimal when using thousands of rows
let mut messages: String = "".to_string();
// Must be deleted from end to start, because when deleting entries, TreePath(and also TreeIter) will points to invalid data
for tree_path in selection_rows.iter().rev() {
let name = tree_model.get_value(&tree_model.get_iter(tree_path).unwrap(), ColumnsZeroedFiles::Name as i32).get::<String>().unwrap().unwrap();
let path = tree_model.get_value(&tree_model.get_iter(tree_path).unwrap(), ColumnsZeroedFiles::Path as i32).get::<String>().unwrap().unwrap();
match fs::remove_file(format!("{}/{}", path, name)) {
Ok(_) => {
list_store.remove(&list_store.get_iter(tree_path).unwrap());
}
Err(_) => messages += format!("Failed to remove file {}/{} because file doesn't exists or you don't have permissions.\n", path, name).as_str(),
}
}
text_view_errors.get_buffer().unwrap().set_text(messages.as_str());
selection.unselect_all();
}
e => panic!("Not existent {}", e),
}
});
@ -1018,6 +1086,7 @@ fn main() {
let shared_temporary_files_state = shared_temporary_files_state.clone();
let shared_empty_files_state = shared_empty_files_state.clone();
let shared_similar_images_state = shared_similar_images_state.clone();
let shared_zeroed_files_state = shared_zeroed_files_state.clone();
let notebook_main = notebook_main.clone();
buttons_save_clone.connect_clicked(move |_| match notebook_main_children_names.get(notebook_main.get_current_page().unwrap() as usize).unwrap().as_str() {
"notebook_main_duplicate_finder_label" => {
@ -1098,6 +1167,19 @@ fn main() {
*shared_buttons.borrow_mut().get_mut("similar_images").unwrap().get_mut("save").unwrap() = false;
}
}
"notebook_main_zeroed_files_finder_label" => {
let file_name = "results_zeroed_files.txt";
let mut zf = shared_zeroed_files_state.borrow_mut();
zf.save_results_to_file(file_name);
entry_info.set_text(format!("Saved results to file {}", file_name).as_str());
// Set state
{
buttons_save.hide();
*shared_buttons.borrow_mut().get_mut("zeroed_files").unwrap().get_mut("save").unwrap() = false;
}
}
e => panic!("Not existent {}", e),
});
}
@ -2065,6 +2147,79 @@ fn main() {
}
}
}
Message::ZeroedFiles(zf) => {
if zf.get_stopped_search() {
entry_info.set_text("Searching for zeroed files was stopped by user");
//Also clear list
scrolled_window_zeroed_files_finder
.get_children()
.get(0)
.unwrap()
.clone()
.downcast::<gtk::TreeView>()
.unwrap()
.get_model()
.unwrap()
.downcast::<gtk::ListStore>()
.unwrap()
.clear();
} else {
let information = zf.get_information();
let text_messages = zf.get_text_messages();
let zeroed_files_number: usize = information.number_of_zeroed_files;
entry_info.set_text(format!("Found {} zeroed files.", zeroed_files_number).as_str());
// Create GUI
{
let list_store = scrolled_window_zeroed_files_finder
.get_children()
.get(0)
.unwrap()
.clone()
.downcast::<gtk::TreeView>()
.unwrap()
.get_model()
.unwrap()
.downcast::<gtk::ListStore>()
.unwrap();
list_store.clear();
let col_indices = [0, 1, 2, 3];
let vector = zf.get_zeroed_files();
for file_entry in vector {
let (directory, file) = split_path(&file_entry.path);
let values: [&dyn ToValue; 4] = [
&(file_entry.size.file_size(options::BINARY).unwrap()),
&file,
&directory,
&(NaiveDateTime::from_timestamp(file_entry.modified_date as i64, 0).to_string()),
];
list_store.set(&list_store.append(), &col_indices, &values);
}
print_text_messages_to_text_view(text_messages, &text_view_errors);
}
// Set state
{
*shared_zeroed_files_state.borrow_mut() = zf;
if zeroed_files_number > 0 {
*shared_buttons.borrow_mut().get_mut("zeroed_files").unwrap().get_mut("save").unwrap() = true;
*shared_buttons.borrow_mut().get_mut("zeroed_files").unwrap().get_mut("delete").unwrap() = true;
} else {
*shared_buttons.borrow_mut().get_mut("zeroed_files").unwrap().get_mut("save").unwrap() = false;
*shared_buttons.borrow_mut().get_mut("zeroed_files").unwrap().get_mut("delete").unwrap() = false;
}
set_buttons(&mut *shared_buttons.borrow_mut().get_mut("zeroed_files").unwrap(), &buttons_array, &buttons_names);
}
}
}
}
// Returning false here would close the receiver and have senders fail
glib::Continue(true)