1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
use std::fmt::Write;
use std::path::PathBuf;
use std::sync::Arc;
use std::thread;

use fs_extra;
use indicatif::{InMemoryTerm, ProgressBar, ProgressDrawTarget, ProgressState, ProgressStyle};
use log::info;
// use notify_rust::Notification;
use tuikit::prelude::{Attr, Color, Effect, Event, Term};

use crate::fileinfo::human_size;
use crate::fm_error::FmResult;
use crate::opener::execute_in_child;

fn setup(
    action: String,
    height: usize,
    width: usize,
) -> FmResult<(InMemoryTerm, ProgressBar, fs_extra::dir::CopyOptions)> {
    let in_mem = InMemoryTerm::new(height as u16, width as u16);
    let pb = ProgressBar::with_draw_target(
        Some(100),
        ProgressDrawTarget::term_like(Box::new(in_mem.clone())),
    );
    pb.set_style(
        ProgressStyle::with_template(
            "{spinner} {action} [{elapsed}] [{wide_bar}] {percent}% ({eta})",
        )
        .unwrap()
        .with_key("eta", |state: &ProgressState, w: &mut dyn Write| {
            write!(w, "{:.1}s", state.eta().as_secs_f64()).unwrap()
        })
        .with_key("action", move |_: &ProgressState, w: &mut dyn Write| {
            write!(w, "{}", &action).unwrap()
        })
        .progress_chars("#>-"),
    );
    let options = fs_extra::dir::CopyOptions::new();
    Ok((in_mem, pb, options))
}

fn handle_progress_display(
    in_mem: &InMemoryTerm,
    pb: &ProgressBar,
    term: &Arc<Term>,
    process_info: fs_extra::TransitProcess,
) -> fs_extra::dir::TransitProcessResult {
    pb.set_position(100 * process_info.copied_bytes / process_info.total_bytes);
    let _ = term.print_with_attr(
        1,
        0,
        &in_mem.to_owned().contents(),
        Attr {
            fg: Color::CYAN,
            bg: Color::default(),
            effect: Effect::REVERSE | Effect::BOLD,
        },
    );
    let _ = term.present();
    fs_extra::dir::TransitProcessResult::ContinueOrAbort
}

/// Different kind of movement of files : copying or moving.
#[derive(Debug)]
pub enum CopyMove {
    Copy,
    Move,
}

impl CopyMove {
    fn verb(&self) -> &str {
        match *self {
            CopyMove::Copy => "copy",
            CopyMove::Move => "move",
        }
    }

    fn preterit(&self) -> &str {
        match *self {
            CopyMove::Copy => "copied",
            CopyMove::Move => "moved",
        }
    }
}

/// Will copy or move a bunch of files to `dest`.
/// A progress bar is displayed on the passed terminal.
/// A notification is then sent to the user if a compatible notification system
/// is installed.
pub fn copy_move(
    copy_or_move: CopyMove,
    sources: Vec<PathBuf>,
    dest: &str,
    term: Arc<Term>,
) -> FmResult<()> {
    let c_term = term.clone();
    let (height, width) = term.term_size()?;
    let (in_mem, pb, options) = setup(copy_or_move.verb().to_owned(), height, width)?;
    let handle_progress = move |process_info: fs_extra::TransitProcess| {
        handle_progress_display(&in_mem, &pb, &term, process_info)
    };
    let dest = dest.to_owned();
    let _ = thread::spawn(move || {
        let copier_mover = match copy_or_move {
            CopyMove::Copy => fs_extra::copy_items_with_progress,
            CopyMove::Move => fs_extra::move_items_with_progress,
        };
        let transfered_bytes = match copier_mover(&sources, &dest, &options, handle_progress) {
            Ok(transfered_bytes) => transfered_bytes,
            Err(e) => {
                info!("copy move couldn't copy: {:?}", e);
                0
            }
        };

        let _ = c_term.send_event(Event::User(()));
        let _ = notify(&format!(
            "fm: {} finished {}B {}",
            copy_or_move.verb(),
            human_size(transfered_bytes),
            copy_or_move.preterit()
        ));
        info!(
            "{} finished {}B",
            copy_or_move.verb(),
            human_size(transfered_bytes)
        );
        info!(target: "special",
            "{} finished {}B",
            copy_or_move.verb(),
            human_size(transfered_bytes)
        )
    });
    Ok(())
}

/// Send a notification to the desktop.
/// Requires "notify-send" to be installed.
fn notify(text: &str) -> FmResult<()> {
    execute_in_child("notify-send", &vec![text])?;
    Ok(())
}