mirror of
https://github.com/sxyazi/yazi.git
synced 2026-07-21 23:01:05 +00:00
222 lines
5 KiB
Rust
222 lines
5 KiB
Rust
use std::{collections::{BTreeMap, HashMap, HashSet}, ffi::OsStr, path::{Path, PathBuf}, sync::Arc};
|
|
|
|
use tracing::trace;
|
|
|
|
use super::{Scheduler, TASKS_PADDING, TASKS_PERCENT};
|
|
use crate::{config::{open::Opener, OPEN}, core::{files::File, input::InputOpt, Position}, emit, misc::{tty_size, MimeKind}};
|
|
|
|
#[derive(Clone, Debug)]
|
|
pub struct Task {
|
|
pub id: usize,
|
|
pub name: String,
|
|
pub stage: TaskStage,
|
|
|
|
pub found: u32,
|
|
pub processed: u32,
|
|
|
|
pub todo: u64,
|
|
pub done: u64,
|
|
}
|
|
|
|
impl Task {
|
|
pub fn new(id: usize, name: String) -> Self {
|
|
Self { id, name, stage: Default::default(), found: 0, processed: 0, todo: 0, done: 0 }
|
|
}
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub enum TaskOp {
|
|
// task_id, size
|
|
New(usize, u64),
|
|
// task_id, processed, size
|
|
Adv(usize, u32, u64),
|
|
// task_id
|
|
Done(usize),
|
|
}
|
|
|
|
#[derive(Clone, Debug, Default, Eq, PartialEq, Ord, PartialOrd)]
|
|
pub enum TaskStage {
|
|
#[default]
|
|
Pending,
|
|
Dispatched,
|
|
Hooked,
|
|
}
|
|
|
|
pub struct Tasks {
|
|
scheduler: Arc<Scheduler>,
|
|
|
|
pub visible: bool,
|
|
pub cursor: usize,
|
|
pub progress: (u8, u32),
|
|
}
|
|
|
|
impl Tasks {
|
|
pub fn start() -> Self {
|
|
Self {
|
|
scheduler: Arc::new(Scheduler::start()),
|
|
visible: false,
|
|
cursor: 0,
|
|
progress: (100, 0),
|
|
}
|
|
}
|
|
|
|
#[inline]
|
|
pub fn limit() -> usize {
|
|
(tty_size().ws_row * TASKS_PERCENT / 100).saturating_sub(TASKS_PADDING) as usize
|
|
}
|
|
|
|
pub fn toggle(&mut self) -> bool {
|
|
self.visible = !self.visible;
|
|
true
|
|
}
|
|
|
|
pub fn next(&mut self) -> bool {
|
|
let limit = Self::limit().min(self.len());
|
|
|
|
let old = self.cursor;
|
|
self.cursor = limit.saturating_sub(1).min(self.cursor + 1);
|
|
|
|
old != self.cursor
|
|
}
|
|
|
|
pub fn prev(&mut self) -> bool {
|
|
let old = self.cursor;
|
|
self.cursor = self.cursor.saturating_sub(1);
|
|
old != self.cursor
|
|
}
|
|
|
|
pub fn paginate(&self) -> Vec<Task> {
|
|
let running = self.scheduler.running.read();
|
|
running.values().take(Self::limit()).cloned().collect::<Vec<_>>()
|
|
}
|
|
|
|
pub fn cancel(&self) -> bool {
|
|
let id = self.scheduler.running.read().values().skip(self.cursor).next().map(|t| t.id);
|
|
id.map(|id| self.scheduler.cancel(id)).unwrap_or(false)
|
|
}
|
|
|
|
pub fn file_open(&self, targets: &[(impl AsRef<Path>, impl AsRef<str>)]) -> bool {
|
|
let mut openers = BTreeMap::new();
|
|
for (path, mime) in targets {
|
|
if let Some(opener) = OPEN.openers(path, mime).and_then(|o| o.first().cloned()) {
|
|
openers.entry(opener).or_insert_with(|| vec![]).push(path.as_ref());
|
|
}
|
|
}
|
|
for (opener, args) in openers {
|
|
self.file_open_with(opener, &args);
|
|
}
|
|
false
|
|
}
|
|
|
|
pub fn file_open_with(&self, opener: &Opener, args: &[impl AsRef<OsStr>]) -> bool {
|
|
if opener.spread {
|
|
self.scheduler.process_open(&opener, args);
|
|
return false;
|
|
}
|
|
for target in args {
|
|
self.scheduler.process_open(&opener, &[target]);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
pub fn file_cut(&self, src: &HashSet<PathBuf>, dest: PathBuf, force: bool) -> bool {
|
|
for p in src {
|
|
let to = dest.join(p.file_name().unwrap());
|
|
if force && *p == to {
|
|
trace!("file_cut: same file, skipping {:?}", to);
|
|
} else {
|
|
self.scheduler.file_cut(p.clone(), to, force);
|
|
}
|
|
}
|
|
false
|
|
}
|
|
|
|
pub fn file_copy(
|
|
&self,
|
|
src: &HashSet<PathBuf>,
|
|
dest: PathBuf,
|
|
force: bool,
|
|
follow: bool,
|
|
) -> bool {
|
|
for p in src {
|
|
let to = dest.join(p.file_name().unwrap());
|
|
if force && *p == to {
|
|
trace!("file_copy: same file, skipping {:?}", to);
|
|
} else {
|
|
self.scheduler.file_copy(p.clone(), to, force, follow);
|
|
}
|
|
}
|
|
false
|
|
}
|
|
|
|
pub fn file_remove(&self, targets: Vec<PathBuf>, permanently: bool) -> bool {
|
|
let scheduler = self.scheduler.clone();
|
|
tokio::spawn(async move {
|
|
let result = emit!(Input(InputOpt {
|
|
title: "Are you sure delete these files? (y/N)".to_string(),
|
|
value: "".to_string(),
|
|
position: Position::Hovered,
|
|
}))
|
|
.await;
|
|
|
|
if let Ok(choice) = result {
|
|
if choice.to_lowercase() != "y" {
|
|
return;
|
|
}
|
|
for p in targets {
|
|
if permanently {
|
|
scheduler.file_delete(p);
|
|
} else {
|
|
scheduler.file_trash(p);
|
|
}
|
|
}
|
|
}
|
|
});
|
|
false
|
|
}
|
|
|
|
#[inline]
|
|
pub fn precache_mime(&self, targets: Vec<&File>, mimetype: &HashMap<PathBuf, String>) -> bool {
|
|
let targets = targets
|
|
.into_iter()
|
|
.filter(|f| f.meta.is_file() && !mimetype.contains_key(&f.path))
|
|
.map(|f| f.path.clone())
|
|
.collect::<Vec<_>>();
|
|
|
|
if !targets.is_empty() {
|
|
self.scheduler.precache_mime(targets);
|
|
}
|
|
false
|
|
}
|
|
|
|
pub fn precache_image(&self, mimetype: &BTreeMap<PathBuf, String>) -> bool {
|
|
let targets = mimetype
|
|
.into_iter()
|
|
.filter(|(_, m)| MimeKind::new(m) == MimeKind::Image)
|
|
.map(|(p, _)| p.clone())
|
|
.collect::<Vec<_>>();
|
|
|
|
if !targets.is_empty() {
|
|
self.scheduler.precache_image(targets);
|
|
}
|
|
false
|
|
}
|
|
|
|
pub fn precache_video(&self, mimetype: &BTreeMap<PathBuf, String>) -> bool {
|
|
let targets = mimetype
|
|
.into_iter()
|
|
.filter(|(_, m)| MimeKind::new(m) == MimeKind::Video)
|
|
.map(|(p, _)| p.clone())
|
|
.collect::<Vec<_>>();
|
|
|
|
if !targets.is_empty() {
|
|
self.scheduler.precache_video(targets);
|
|
}
|
|
false
|
|
}
|
|
}
|
|
|
|
impl Tasks {
|
|
#[inline]
|
|
pub fn len(&self) -> usize { self.scheduler.running.read().len() }
|
|
}
|