summaryrefslogtreecommitdiffstats
path: root/src/commands/file_ops
diff options
context:
space:
mode:
authorJiayi Zhao <jeff.no.zhao@gmail.com>2020-02-09 12:39:31 -0500
committerJiayi Zhao <jeff.no.zhao@gmail.com>2020-02-09 13:07:31 -0500
commitbecbb90b2f22d58c98693d653f55ba604bb03f75 (patch)
tree085b9ac9b197a9ad3f0dd20df36848c5619adefe /src/commands/file_ops
parent656582a6c867c25667661be9b327b4cc73859d7d (diff)
rework input thread and file operations
- no longer depend on fs_extra for copy/paste files - in house solution preserves permissions - ioworkers are now queued, no more parallel io tasks - input thread now listens for ioworker threads as well - cargo fmt
Diffstat (limited to 'src/commands/file_ops')
-rw-r--r--src/commands/file_ops/copy.rs36
-rw-r--r--src/commands/file_ops/cut.rs36
-rw-r--r--src/commands/file_ops/local_state.rs56
-rw-r--r--src/commands/file_ops/mod.rs11
-rw-r--r--src/commands/file_ops/name_resolution.rs15
-rw-r--r--src/commands/file_ops/paste.rs55
-rw-r--r--src/commands/file_ops/paste_copy.rs68
-rw-r--r--src/commands/file_ops/paste_cut.rs76
8 files changed, 353 insertions, 0 deletions
diff --git a/src/commands/file_ops/copy.rs b/src/commands/file_ops/copy.rs
new file mode 100644
index 0000000..a1a562e
--- /dev/null
+++ b/src/commands/file_ops/copy.rs
@@ -0,0 +1,36 @@
+use crate::commands::{JoshutoCommand, JoshutoRunnable};
+use crate::context::JoshutoContext;
+use crate::error::JoshutoResult;
+use crate::window::JoshutoView;
+
+use super::local_state::{FileOp, LocalState};
+
+#[derive(Clone, Debug)]
+pub struct CopyFiles;
+
+impl CopyFiles {
+ pub fn new() -> Self {
+ CopyFiles
+ }
+ pub const fn command() -> &'static str {
+ "copy_files"
+ }
+}
+
+impl JoshutoCommand for CopyFiles {}
+
+impl std::fmt::Display for CopyFiles {
+ fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
+ f.write_str(Self::command())
+ }
+}
+
+impl JoshutoRunnable for CopyFiles {
+ fn execute(&self, context: &mut JoshutoContext, _: &JoshutoView) -> JoshutoResult<()> {
+ let curr_tab = context.curr_tab_ref();
+ LocalState::repopulated_selected_files(&curr_tab.curr_list)?;
+ LocalState::set_file_op(FileOp::Copy);
+ LocalState::set_tab_src(context.curr_tab_index);
+ Ok(())
+ }
+}
diff --git a/src/commands/file_ops/cut.rs b/src/commands/file_ops/cut.rs
new file mode 100644
index 0000000..f466ca7
--- /dev/null
+++ b/src/commands/file_ops/cut.rs
@@ -0,0 +1,36 @@
+use crate::commands::{JoshutoCommand, JoshutoRunnable};
+use crate::context::JoshutoContext;
+use crate::error::JoshutoResult;
+use crate::window::JoshutoView;
+
+use super::local_state::{FileOp, LocalState};
+
+#[derive(Clone, Debug)]
+pub struct CutFiles;
+
+impl CutFiles {
+ pub fn new() -> Self {
+ CutFiles
+ }
+ pub const fn command() -> &'static str {
+ "cut_files"
+ }
+}
+
+impl JoshutoCommand for CutFiles {}
+
+impl std::fmt::Display for CutFiles {
+ fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
+ f.write_str(Self::command())
+ }
+}
+
+impl JoshutoRunnable for CutFiles {
+ fn execute(&self, context: &mut JoshutoContext, _: &JoshutoView) -> JoshutoResult<()> {
+ let curr_tab = context.curr_tab_ref();
+ LocalState::repopulated_selected_files(&curr_tab.curr_list)?;
+ LocalState::set_file_op(FileOp::Cut);
+ LocalState::set_tab_src(context.curr_tab_index);
+ Ok(())
+ }
+}
diff --git a/src/commands/file_ops/local_state.rs b/src/commands/file_ops/local_state.rs
new file mode 100644
index 0000000..2849893
--- /dev/null
+++ b/src/commands/file_ops/local_state.rs
@@ -0,0 +1,56 @@
+use lazy_static::lazy_static;
+
+use std::path;
+use std::sync::{atomic, Mutex};
+
+use crate::fs::JoshutoDirList;
+
+lazy_static! {
+ static ref SELECTED_FILES: Mutex<Option<Vec<path::PathBuf>>> = Mutex::new(None);
+ static ref FILE_OPERATION: Mutex<FileOp> = Mutex::new(FileOp::Copy);
+ static ref TAB_SRC: atomic::AtomicUsize = atomic::AtomicUsize::new(0);
+}
+
+#[derive(Clone, Debug)]
+pub enum FileOp {
+ Cut,
+ Copy,
+}
+
+pub struct LocalState;
+
+impl LocalState {
+ pub fn set_file_op(operation: FileOp) {
+ let mut data = FILE_OPERATION.lock().unwrap();
+ *data = operation;
+ }
+
+ pub fn set_tab_src(tab_index: usize) {
+ TAB_SRC.store(tab_index, atomic::Ordering::Release);
+ }
+
+ pub fn repopulated_selected_files(dirlist: &JoshutoDirList) -> std::io::Result<()> {
+ let selected = dirlist.get_selected_paths();
+ if selected.is_empty() {
+ Err(std::io::Error::new(
+ std::io::ErrorKind::Other,
+ "no files selected",
+ ))
+ } else {
+ let selected_clone: Vec<path::PathBuf> =
+ selected.iter().map(|p| (*p).clone()).collect();
+ let mut data = SELECTED_FILES.lock().unwrap();
+ *data = Some(selected_clone);
+ Ok(())
+ }
+ }
+
+ pub fn take_selected_files() -> Option<Vec<path::PathBuf>> {
+ let paths = SELECTED_FILES.lock().unwrap().take();
+ paths
+ }
+
+ pub fn get_file_operation() -> FileOp {
+ (*FILE_OPERATION.lock().unwrap()).clone()
+ }
+}
diff --git a/src/commands/file_ops/mod.rs b/src/commands/file_ops/mod.rs
new file mode 100644
index 0000000..e4a0da3
--- /dev/null
+++ b/src/commands/file_ops/mod.rs
@@ -0,0 +1,11 @@
+mod copy;
+mod cut;
+mod local_state;
+mod name_resolution;
+mod paste;
+mod paste_copy;
+mod paste_cut;
+
+pub use self::copy::CopyFiles;
+pub use self::cut::CutFiles;
+pub use self::paste::PasteFiles;
diff --git a/src/commands/file_ops/name_resolution.rs b/src/commands/file_ops/name_resolution.rs
new file mode 100644
index 0000000..a8e520b
--- /dev/null
+++ b/src/commands/file_ops/name_resolution.rs
@@ -0,0 +1,15 @@
+use std::path;
+
+pub fn rename_filename_conflict(path: &mut path::PathBuf) {
+ let file_name = path.file_name().unwrap().to_os_string();
+ for i in 0.. {
+ if !path.exists() {
+ break;
+ }
+ path.pop();
+
+ let mut file_name = file_name.clone();
+ file_name.push(&format!("_{}", i));
+ path.push(file_name);
+ }
+}
diff --git a/src/commands/file_ops/paste.rs b/src/commands/file_ops/paste.rs
new file mode 100644
index 0000000..ba5dc43
--- /dev/null
+++ b/src/commands/file_ops/paste.rs
@@ -0,0 +1,55 @@
+use crate::commands::{JoshutoCommand, JoshutoRunnable};
+use crate::context::JoshutoContext;
+use crate::error::JoshutoResult;
+use crate::io::Options;
+use crate::window::JoshutoView;
+
+use super::local_state::{FileOp, LocalState};
+use super::paste_copy::paste_copy;
+use super::paste_cut::paste_cut;
+
+pub struct PasteFiles {
+ options: Options,
+}
+
+impl JoshutoCommand for PasteFiles {}
+
+impl std::fmt::Display for PasteFiles {
+ fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
+ write!(
+ f,
+ "{} overwrite={} skip_exist={}",
+ Self::command(),
+ self.options.overwrite,
+ self.options.skip_exist,
+ )
+ }
+}
+
+impl std::fmt::Debug for PasteFiles {
+ fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
+ f.write_str(Self::command())
+ }
+}
+
+impl JoshutoRunnable for PasteFiles {
+ fn execute(&self, context: &mut JoshutoContext, _: &JoshutoView) -> JoshutoResult<()> {
+ let file_operation = LocalState::get_file_operation();
+ let thread = match file_operation {
+ FileOp::Copy => paste_copy(context, self.options.clone()),
+ FileOp::Cut => paste_cut(context, self.options.clone()),
+ };
+ let thread = thread?;
+ context.add_new_worker(thread);
+ Ok(())
+ }
+}
+
+impl PasteFiles {
+ pub fn new(options: Options) -> Self {
+ PasteFiles { options }
+ }
+ pub const fn command() -> &'static str {
+ "paste_files"
+ }
+}
diff --git a/src/commands/file_ops/paste_copy.rs b/src/commands/file_ops/paste_copy.rs
new file mode 100644
index 0000000..26f0e5c
--- /dev/null
+++ b/src/commands/file_ops/paste_copy.rs
@@ -0,0 +1,68 @@
+use std::fs;
+use std::path::Path;
+use std::sync::mpsc;
+use std::thread;
+
+use crate::context::JoshutoContext;
+use crate::io::{IOWorkerThread, Options};
+use crate::util::event::Event;
+
+use super::local_state::LocalState;
+use super::name_resolution::rename_filename_conflict;
+
+pub fn recursive_copy(dest: &Path, src: &Path, options: &Options) -> std::io::Result<u64> {
+ let mut dest_buf = dest.to_path_buf();
+ if let Some(s) = src.file_name() {
+ dest_buf.push(s);
+ }
+ rename_filename_conflict(&mut dest_buf);
+ if !src.is_dir() {
+ std::fs::copy(src, dest_buf)
+ } else {
+ fs::create_dir(dest_buf.as_path())?;
+ let mut total = 0;
+ for entry in fs::read_dir(src)? {
+ let entry = entry?;
+ let entry_path = entry.path();
+ total += recursive_copy(dest_buf.as_path(), entry_path.as_path(), options)?;
+ }
+ Ok(total)
+ }
+}
+
+pub fn paste_copy(
+ context: &mut JoshutoContext,
+ options: Options,
+) -> std::io::Result<IOWorkerThread> {
+ let paths = LocalState::take_selected_files()
+ .ok_or_else(|| std::io::Error::new(std::io::ErrorKind::Other, "no files selected"))?;
+ if paths.is_empty() {
+ return Err(std::io::Error::new(
+ std::io::ErrorKind::Other,
+ "no files selected",
+ ));
+ }
+
+ let tab_dest = context.curr_tab_index;
+ let dest = context.tabs[tab_dest].curr_path.clone();
+
+ let (tx_start, rx_start) = mpsc::channel();
+ let (tx, rx) = mpsc::channel();
+ let handle: thread::JoinHandle<std::io::Result<u64>> = thread::spawn(move || {
+ let mut total = 0;
+ rx_start.recv();
+ for path in paths {
+ total += recursive_copy(dest.as_path(), path.as_path(), &options)?;
+ tx.send(Event::IOWorkerProgress(total));
+ }
+ Ok(total)
+ });
+
+ let thread = IOWorkerThread {
+ handle,
+ tx_start,
+ rx,
+ };
+
+ Ok(thread)
+}
diff --git a/src/commands/file_ops/paste_cut.rs b/src/commands/file_ops/paste_cut.rs
new file mode 100644
index 0000000..1177f12
--- /dev/null
+++ b/src/commands/file_ops/paste_cut.rs
@@ -0,0 +1,76 @@
+use std::fs;
+use std::path::Path;
+use std::sync::mpsc;
+use std::thread;
+
+use crate::context::JoshutoContext;
+use crate::io::{IOWorkerThread, Options};
+use crate::util::event::Event;
+
+use super::local_state::LocalState;
+use super::name_resolution::rename_filename_conflict;
+
+pub fn recursive_cut(dest: &Path, src: &Path, options: &Options) -> std::io::Result<u64> {
+ let mut dest_buf = dest.to_path_buf();
+ if let Some(s) = src.file_name() {
+ dest_buf.push(s);
+ }
+ rename_filename_conflict(&mut dest_buf);
+ if !src.is_dir() {
+ let metadata = src.metadata()?;
+ match std::fs::rename(src, dest_buf.as_path()) {
+ Err(_) => {
+ std::fs::copy(src, dest_buf.as_path())?;
+ std::fs::remove_file(src)?;
+ }
+ _ => {}
+ }
+ Ok(metadata.len())
+ } else {
+ fs::create_dir(dest_buf.as_path())?;
+ let mut total = 0;
+ for entry in fs::read_dir(src)? {
+ let entry = entry?;
+ let entry_path = entry.path();
+ total += recursive_cut(dest_buf.as_path(), entry_path.as_path(), options)?;
+ }
+ Ok(total)
+ }
+}
+
+pub fn paste_cut(
+ context: &mut JoshutoContext,
+ options: Options,
+) -> std::io::Result<IOWorkerThread> {
+ let paths = LocalState::take_selected_files()
+ .ok_or_else(|| std::io::Error::new(std::io::ErrorKind::Other, "no files selected"))?;
+ if paths.is_empty() {
+ return Err(std::io::Error::new(
+ std::io::ErrorKind::Other,
+ "no files selected",
+ ));
+ }
+
+ let tab_dest = context.curr_tab_index;
+ let dest = context.tabs[tab_dest].curr_path.clone();
+
+ let (tx_start, rx_start) = mpsc::channel();
+ let (tx, rx) = mpsc::channel();
+ let handle: thread::JoinHandle<std::io::Result<u64>> = thread::spawn(move || {
+ let mut total = 0;
+ rx_start.recv();
+ for path in paths {
+ total += recursive_cut(dest.as_path(), path.as_path(), &options)?;
+ tx.send(Event::IOWorkerProgress(total));
+ }
+ Ok(total)
+ });
+
+ let thread = IOWorkerThread {
+ handle,
+ tx_start,
+ rx,
+ };
+
+ Ok(thread)
+}