yazi/core/src/tasks/tasks.rs
2023-09-07 00:12:59 +08:00

280 lines
6.8 KiB
Rust

use std::{collections::{BTreeMap, HashMap, HashSet}, ffi::OsStr, io::{stdout, Write}, path::Path, sync::Arc};
use config::{manager::SortBy, open::Opener, OPEN};
use crossterm::terminal::{disable_raw_mode, enable_raw_mode};
use shared::{Defer, MimeKind, Term, Url};
use tokio::{io::{stdin, AsyncReadExt}, select, sync::mpsc, time};
use tracing::trace;
use super::{task::TaskSummary, Scheduler, TASKS_PADDING, TASKS_PERCENT};
use crate::{emit, files::{File, Files}, input::InputOpt, Event, BLOCKER};
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 {
(Term::size().rows * TASKS_PERCENT / 100).saturating_sub(TASKS_PADDING) as usize
}
pub fn toggle(&mut self) -> bool {
self.visible = !self.visible;
emit!(Peek); // Show/hide preview for images
true
}
#[allow(clippy::should_implement_trait)]
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<TaskSummary> {
let running = self.scheduler.running.read();
running.values().take(Self::limit()).map(Into::into).collect()
}
pub fn inspect(&self) -> bool {
let Some(id) = self.scheduler.running.read().get_id(self.cursor) else {
return false;
};
let scheduler = self.scheduler.clone();
tokio::spawn(async move {
let _guard = BLOCKER.acquire().await.unwrap();
let (tx, mut rx) = mpsc::unbounded_channel();
let buffered = {
let mut running = scheduler.running.write();
let Some(task) = running.get_mut(id) else { return };
task.logger = Some(tx);
task.logs.clone()
};
emit!(Stop(true)).await;
let _defer = Defer::new(|| {
disable_raw_mode().ok();
Event::Stop(false, None).emit();
});
Term::clear(&mut stdout()).ok();
stdout().write_all(buffered.as_bytes()).ok();
enable_raw_mode().ok();
let mut stdin = stdin();
let mut quit = [0; 10];
loop {
select! {
Some(line) = rx.recv() => {
let mut stdout = stdout().lock();
stdout.write_all(line.as_bytes()).ok();
stdout.write_all(b"\r\n").ok();
}
_ = time::sleep(time::Duration::from_millis(100)) => {
if scheduler.running.read().get(id).is_none() {
stdout().write_all(b"Task finished, press `q` to quit\r\n").ok();
break;
}
},
Ok(_) = stdin.read(&mut quit) => {
if quit[0] == b'q' {
break;
}
}
}
}
if let Some(task) = scheduler.running.write().get_mut(id) {
task.logger = None;
}
while quit[0] != b'q' {
stdin.read(&mut quit).await.ok();
}
});
false
}
pub fn cancel(&mut self) -> bool {
let running = self.scheduler.running.read();
if !running.get_id(self.cursor).map(|id| self.scheduler.cancel(id)).unwrap_or(false) {
return false;
}
self.cursor = self.cursor.min(running.len().saturating_sub(1));
true
}
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()) {
openers.entry(opener).or_insert_with(Vec::new).push(path.as_ref().as_os_str());
}
}
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]);
}
false
}
pub fn file_cut(&self, src: &HashSet<Url>, dest: Url, 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<Url>, dest: Url, 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<Url>, permanently: bool) -> bool {
let scheduler = self.scheduler.clone();
tokio::spawn(async move {
let s = if targets.len() > 1 { 's' } else { ' ' };
let result = emit!(Input(InputOpt::hovered(if permanently {
format!("Sure to delete selected file{s} permanently? (y/N)")
} else {
format!("Sure to move selected file{s} to trash? (y/N)")
})));
if let Ok(choice) = result.await {
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_size(&self, targets: &Files) -> bool {
if targets.sorter().by != SortBy::Size {
return false;
}
let targets: Vec<_> =
targets.iter().filter(|f| f.is_dir() && f.length().is_none()).map(|f| f.url()).collect();
if !targets.is_empty() {
self.scheduler.precache_size(targets);
}
false
}
#[inline]
pub fn precache_mime(&self, targets: &[File], mimetype: &HashMap<Url, String>) -> bool {
let targets: Vec<_> = targets
.iter()
.filter(|f| f.is_file() && !mimetype.contains_key(f.url()))
.map(|f| f.url_owned())
.collect();
if !targets.is_empty() {
self.scheduler.precache_mime(targets);
}
false
}
pub fn precache_image(&self, mimetype: &BTreeMap<Url, String>) -> bool {
let targets: Vec<_> = mimetype
.iter()
.filter(|(_, m)| MimeKind::new(m) == MimeKind::Image)
.map(|(u, _)| u.clone())
.collect();
if !targets.is_empty() {
self.scheduler.precache_image(targets);
}
false
}
pub fn precache_video(&self, mimetype: &BTreeMap<Url, String>) -> bool {
let targets: Vec<_> = mimetype
.iter()
.filter(|(_, m)| MimeKind::new(m) == MimeKind::Video)
.map(|(u, _)| u.clone())
.collect();
if !targets.is_empty() {
self.scheduler.precache_video(targets);
}
false
}
pub fn precache_pdf(&self, mimetype: &BTreeMap<Url, String>) -> bool {
let targets: Vec<_> = mimetype
.iter()
.filter(|(_, m)| MimeKind::new(m) == MimeKind::PDF)
.map(|(u, _)| u.clone())
.collect();
if !targets.is_empty() {
self.scheduler.precache_pdf(targets);
}
false
}
}
impl Tasks {
#[inline]
pub fn len(&self) -> usize { self.scheduler.running.read().len() }
}