diff --git a/src/app.rs b/src/app.rs new file mode 100644 index 0000000..3356b25 --- /dev/null +++ b/src/app.rs @@ -0,0 +1,20 @@ +use crate::database; +use crate::output; +use crate::params::Params; +use crate::scanner; +use anyhow::Result; + +pub struct App; + +impl App { + pub fn init(app_args: &Params) -> Result<()> { + let connection = database::get_connection(app_args)?; + let duplicates = scanner::duplicates(app_args, &connection)?; + match app_args.interactive { + true => output::interactive(duplicates, app_args), + false => output::print(duplicates, app_args), + } + + Ok(()) + } +} diff --git a/src/app/event_handler.rs b/src/app/event_handler.rs deleted file mode 100644 index da7129b..0000000 --- a/src/app/event_handler.rs +++ /dev/null @@ -1,28 +0,0 @@ -use std::time::Duration; - -use anyhow::Result; -use crossterm::event::{self, KeyCode, KeyEvent}; - -use super::events; - -pub struct EventHandler; - -impl EventHandler { - pub fn init() -> Result { - if crossterm::event::poll(Duration::from_millis(10))? { - match event::read()? { - event::Event::Key(keycode) => Self::handle_keypress(keycode), - _ => Ok(events::Event::Noop), - } - } else { - Ok(events::Event::Noop) - } - } - - fn handle_keypress(keyevent: KeyEvent) -> Result { - match keyevent.code { - KeyCode::Char('q') => Ok(events::Event::Exit), - _ => Ok(events::Event::Noop), - } - } -} diff --git a/src/app/events.rs b/src/app/events.rs deleted file mode 100644 index c7167d0..0000000 --- a/src/app/events.rs +++ /dev/null @@ -1,4 +0,0 @@ -pub enum Event { - Exit, - Noop, -} diff --git a/src/app/formatter.rs b/src/app/formatter.rs deleted file mode 100644 index e69de29..0000000 diff --git a/src/app/mod.rs b/src/app/mod.rs deleted file mode 100644 index aa85541..0000000 --- a/src/app/mod.rs +++ /dev/null @@ -1,89 +0,0 @@ -#![allow(unused)] - -mod event_handler; -mod events; -mod formatter; -mod ui; -pub mod file_manager; - -use std::{io, thread, time::Duration}; - -use anyhow::{anyhow, Result}; -use crossterm::{event, execute, terminal}; -use event_handler::EventHandler; -use tui::{ - backend::CrosstermBackend, - widgets::{Block, Borders, Widget}, - Terminal, -}; -use ui::Ui; - -use crate::database; -use crate::output; -use crate::params::Params; -use crate::scanner; - -pub struct App; - -impl App { - pub fn init(app_args: &Params) -> Result<()> { - // let mut term = Self::init_terminal()?; - - let connection = database::get_connection(app_args)?; - let duplicates = scanner::duplicates(app_args, &connection)?; - - // Self::init_render_loop(&mut term)?; - // Self::cleanup(&mut term)?; - - match app_args.interactive { - true => output::interactive(duplicates, app_args), - false => output::print(duplicates, app_args) /* TODO: APP TUI INIT FUNCTION */ - } - - Ok(()) - } - - fn cleanup(term: &mut Terminal>) -> Result<()> { - terminal::disable_raw_mode()?; - execute!( - term.backend_mut(), - terminal::LeaveAlternateScreen, - event::DisableMouseCapture - )?; - - term.show_cursor()?; - Ok(()) - } - - fn render_cycle(term: &mut Terminal>) -> Result<()> { - match EventHandler::init()? { - events::Event::Noop => Ui::render_frame(term), - events::Event::Exit => Err(anyhow!("Exit")), - } - } - - fn init_render_loop(term: &mut Terminal>) -> Result<()> { - // this could be simplified with a `while Self::render_cycle(term).is_ok() {}` in the current state, but maybe - // it's good to keep it to handle errors in the future - loop { - match Self::render_cycle(term) { - Ok(_) => continue, - Err(_) => break, - } - } - - Ok(()) - } - - fn init_terminal() -> Result>> { - terminal::enable_raw_mode()?; - let mut stdout = io::stdout(); - execute!( - stdout, - terminal::EnterAlternateScreen, - event::EnableMouseCapture - )?; - let backend = CrosstermBackend::new(stdout); - Ok(Terminal::new(backend)?) - } -} diff --git a/src/app/ui.rs b/src/app/ui.rs deleted file mode 100644 index 9b8c63b..0000000 --- a/src/app/ui.rs +++ /dev/null @@ -1,54 +0,0 @@ -use std::io; - -use anyhow::Result; -use tui::{ - backend::{Backend, CrosstermBackend}, - layout::{Constraint, Direction, Layout, Rect}, - style::{Modifier, Style}, - text::{Span, Spans}, - widgets::{Block, Borders, List, ListItem, Widget}, - Frame, Terminal, -}; - -pub struct Ui; - -impl Ui { - fn generate_file_list() -> impl Widget { - let tasks: Vec = vec!["Sreedev"; 100] - .into_iter() - .map(|item| ListItem::new(vec![Spans::from(Span::raw(item))])) - .collect(); - - List::new(tasks) - .block(Block::default().borders(Borders::ALL).title("List")) - .highlight_style(Style::default().add_modifier(Modifier::BOLD)) - .highlight_symbol("> ") - } - - fn generate_info_bar() -> impl Widget { - Block::default().title("Description").borders(Borders::ALL) - } - - fn generate_file_desc() -> impl Widget { - Block::default().title("Description").borders(Borders::ALL) - } - - pub fn render_frame(term: &mut Terminal>) -> Result<()> { - term.draw(|f| { - let windows = Layout::default() - .direction(Direction::Vertical) - .constraints([Constraint::Ratio(2, 16), Constraint::Ratio(14, 16)].as_ref()) - .split(f.size()); - - let subwindows = Layout::default() - .direction(Direction::Horizontal) - .constraints([Constraint::Ratio(1, 4), Constraint::Ratio(3, 4)].as_ref()) - .split(windows[1]); - - f.render_widget(Self::generate_info_bar(), windows[0]); - f.render_widget(Self::generate_file_list(), subwindows[0]); - f.render_widget(Self::generate_file_desc(), subwindows[1]); - })?; - Ok(()) - } -} diff --git a/src/database.rs b/src/database.rs index 371d41f..3f8595c 100644 --- a/src/database.rs +++ b/src/database.rs @@ -1,14 +1,7 @@ use std::env::temp_dir; - use anyhow::Result; - use crate::params::Params; - -#[derive(Debug, Clone)] -pub struct File { - pub path: String, - pub hash: String, -} +use crate::file_manager::File; fn db_connection_url(args: &Params) -> String { match args.nocache { diff --git a/src/app/file_manager.rs b/src/file_manager.rs similarity index 80% rename from src/app/file_manager.rs rename to src/file_manager.rs index 4f57d81..9f97318 100644 --- a/src/app/file_manager.rs +++ b/src/file_manager.rs @@ -1,7 +1,12 @@ -use crate::database::File; use anyhow::Result; use colored::Colorize; +#[derive(Debug, Clone)] +pub struct File { + pub path: String, + pub hash: String, +} + pub fn delete_files(files: Vec) -> Result<()> { files.into_iter().for_each(|file| { match std::fs::remove_file(file.path.clone()) { diff --git a/src/main.rs b/src/main.rs index 21ac960..25b703c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,6 @@ -#![allow(unused)] // TODO: remove this once TUI is implemented mod app; mod database; +mod file_manager; mod output; mod params; mod scanner; diff --git a/src/output.rs b/src/output.rs index fcc2518..b52eca5 100644 --- a/src/output.rs +++ b/src/output.rs @@ -1,22 +1,18 @@ -use std::{collections::HashMap, fs, io}; use std::io::Write; - +use std::{collections::HashMap, fs, io}; use anyhow::Result; use chrono::offset::Utc; use chrono::DateTime; use colored::Colorize; use humansize::{format_size, DECIMAL}; use itertools::Itertools; - -use crate::app::file_manager; -use crate::database::File; +use crate::file_manager::{self, File}; use crate::params::Params; -use prettytable::{format, row, Cell, Row, Table}; +use prettytable::{format, row, Table}; use unicode_segmentation::UnicodeSegmentation; fn format_path(path: &str, opts: &Params) -> Result { let display_path = path.replace(&opts.get_directory()?, ""); - let display_range = if display_path.chars().count() > 32 { display_path .graphemes(true) @@ -82,7 +78,7 @@ fn scan_group_confirmation() -> Result { match user_input.trim() { "Y" | "y" => Ok(true), - _ => Ok(false) + _ => Ok(false), } } @@ -108,20 +104,27 @@ fn process_group_action(duplicates: &Vec, dup_index: usize, dup_size: usiz print!("{esc}[2J{esc}[1;1H", esc = 27 as char); - if parsed_file_indices.is_empty() { return } + if parsed_file_indices.is_empty() { + return; + } let files_to_delete = parsed_file_indices .into_iter() .map(|index| duplicates[index].clone()); println!("\n{}", "The following files will be deleted:".red()); - files_to_delete.clone().enumerate().for_each(|(index, file)| { - println!("{}: {}", index.to_string().blue(), file.path); - }); - + files_to_delete + .clone() + .enumerate() + .for_each(|(index, file)| { + println!("{}: {}", index.to_string().blue(), file.path); + }); + match scan_group_confirmation().unwrap() { - true => { file_manager::delete_files(files_to_delete.collect_vec()); }, - false => println!("{}", "\nCancelled Delete Operation.".red()) + true => { + file_manager::delete_files(files_to_delete.collect_vec()); + } + false => println!("{}", "\nCancelled Delete Operation.".red()), } } @@ -129,21 +132,24 @@ pub fn interactive(duplicates: Vec, opts: &Params) { print_meta_info(&duplicates, opts); let grouped_duplicates = group_duplicates(duplicates); - grouped_duplicates.iter().enumerate().for_each(|(gindex, (hash, group))| { - let mut itable = Table::new(); - itable.set_format(*format::consts::FORMAT_NO_BORDER_LINE_SEPARATOR); - itable.set_titles(row!["index", "filename", "size", "updated_at"]); - group.iter().enumerate().for_each(|(index, file)| { - itable.add_row(row![ - index, - format_path(&file.path, opts).unwrap_or_default().blue(), - file_size(&file.path).unwrap_or_default().red(), - modified_time(&file.path).unwrap_or_default().yellow() - ]); - }); + grouped_duplicates + .iter() + .enumerate() + .for_each(|(gindex, (hash, group))| { + let mut itable = Table::new(); + itable.set_format(*format::consts::FORMAT_NO_BORDER_LINE_SEPARATOR); + itable.set_titles(row!["index", "filename", "size", "updated_at"]); + group.iter().enumerate().for_each(|(index, file)| { + itable.add_row(row![ + index, + format_path(&file.path, opts).unwrap_or_default().blue(), + file_size(&file.path).unwrap_or_default().red(), + modified_time(&file.path).unwrap_or_default().yellow() + ]); + }); - process_group_action(group, gindex, grouped_duplicates.len(), itable); - }); + process_group_action(group, gindex, grouped_duplicates.len(), itable); + }); } pub fn print(duplicates: Vec, opts: &Params) { diff --git a/src/params.rs b/src/params.rs index 11741d9..5694eec 100644 --- a/src/params.rs +++ b/src/params.rs @@ -1,5 +1,4 @@ use std::{fs, path::PathBuf}; - use anyhow::{anyhow, Result}; use clap::Parser; @@ -17,7 +16,7 @@ pub struct Params { pub nocache: bool, /// Delete files interactively #[arg(long, short)] - pub interactive: bool + pub interactive: bool, } impl Params { diff --git a/src/scanner.rs b/src/scanner.rs index 21bb3ab..ba0fc6c 100644 --- a/src/scanner.rs +++ b/src/scanner.rs @@ -1,15 +1,12 @@ -use std::{fs, path::PathBuf}; -use indicatif::{HumanDuration, MultiProgress, ProgressBar, ProgressStyle, ParallelProgressIterator}; use anyhow::Result; use fxhash::hash32 as hasher; use glob::glob; +use indicatif::{ParallelProgressIterator, ProgressStyle}; use itertools::Itertools; use rayon::prelude::*; +use std::{fs, path::PathBuf}; -use crate::{ - database::{self, File}, - params::Params, -}; +use crate::{database, file_manager::File, params::Params}; pub fn duplicates(app_opts: &Params, connection: &sqlite::Connection) -> Result> { let scan_results = scan(app_opts, connection)?; @@ -46,7 +43,12 @@ fn scan(app_opts: &Params, connection: &sqlite::Connection) -> Result = glob_patterns .par_iter() - .progress_with_style(ProgressStyle::with_template("{spinner:.green} [scanning files] [{wide_bar:.cyan/blue}] {pos}/{len} files").unwrap()) + .progress_with_style( + ProgressStyle::with_template( + "{spinner:.green} [scanning files] [{wide_bar:.cyan/blue}] {pos}/{len} files", + ) + .unwrap(), + ) .filter_map(|glob_pattern| glob(glob_pattern.as_os_str().to_str()?).ok()) .flat_map(|file_vec| { file_vec @@ -67,10 +69,15 @@ fn scan(app_opts: &Params, connection: &sqlite::Connection) -> Result, connection: &sqlite::Connection) -> Result<()> { let hashed: Vec = files .into_par_iter() - .progress_with_style(ProgressStyle::with_template("{spinner:.green} [indexing files] [{wide_bar:.cyan/blue}] {pos}/{len} files").unwrap()) + .progress_with_style( + ProgressStyle::with_template( + "{spinner:.green} [indexing files] [{wide_bar:.cyan/blue}] {pos}/{len} files", + ) + .unwrap(), + ) .filter_map(|file| { let hash = hash_file(&file).ok()?; - Some(database::File { path: file, hash }) + Some(File { path: file, hash }) }) .collect();