This commit is contained in:
sxyazi 2023-12-24 00:33:01 +08:00
parent e42ec9f429
commit e5ba8e7d27
No known key found for this signature in database
36 changed files with 487 additions and 485 deletions

View file

@ -1,15 +1,16 @@
## Yazi - ⚡️ Blazing Fast Terminal File Manager
Yazi ("duck" in Chinese) is a terminal file manager written in Rust, based on non-blocking async I/O. It aims to provide an efficient, user-friendly, and customizable file management experience.
Yazi (means "duck") is a terminal file manager written in Rust, based on non-blocking async I/O. It aims to provide an efficient, user-friendly, and customizable file management experience.
💡 A new article explaining its internal workings: [Why is Yazi Fast?](https://yazi-rs.github.io/blog/why-is-yazi-fast)
- 🚀 **Full Asynchronous Support**: All I/O operations are asynchronous, CPU tasks are spread across multiple threads, making the most of available resources.
- 💪 **Powerful Async Task Scheduling and Management**: Provides real-time progress updates, task cancellation, and internal task priority assignment.
- 🖼️ **Built-in Support for Multiple Image Protocols**: Also integrated with Überzug++, covering almost all terminals.
- 🌟 **Built-in Code Highlighting and Image Encoding**: Combined with the pre-caching mechanism, greatly accelerates image and normal file loading.
- 🌟 **Built-in Code Highlighting and Image Decoding**: Combined with the pre-loading mechanism, greatly accelerates image and normal file loading.
- 🔌 **Concurrent Plugin System**: UI plugins (rewriting most of the UI), functional plugins (coming soon), custom previewer, and custom preloader; Just some pieces of Lua.
- 🧰 Integration with fd, rg, fzf, zoxide
- 💫 Vim-like Input component, and Select component
- 💫 Vim-like input/select component, auto-completion for cd paths
- 🏷️ Multi-Tab Support, Scrollable Preview (for videos, PDFs, archives, directories, code, etc.)
- 🔄 Batch Renaming, Visual Mode, File Chooser
- 🎨 Theme System, Custom Layouts, Trash Bin, CSI u

View file

@ -1 +1 @@
{"flagWords":[],"words":["Punct","KEYMAP","splitn","crossterm","YAZI","unar","peekable","ratatui","syntect","pbpaste","pbcopy","ffmpegthumbnailer","oneshot","Posix","Lsar","XADDOS","zoxide","cands","Deque","precache","imageops","IFBLK","IFCHR","IFDIR","IFIFO","IFLNK","IFMT","IFSOCK","IRGRP","IROTH","IRUSR","ISGID","ISUID","ISVTX","IWGRP","IWOTH","IWUSR","IXGRP","IXOTH","IXUSR","libc","winsize","TIOCGWINSZ","xpixel","ypixel","ioerr","appender","Catppuccin","macchiato","gitmodules","Dotfiles","bashprofile","vimrc","flac","webp","exiftool","mediainfo","ripgrep","nvim","indexmap","indexmap","unwatch","canonicalize","serde","fsevent","Ueberzug","iterm","wezterm","sixel","chafa","ueberzugpp"," Überzug"," Überzug","Konsole","Alacritty","Überzug","pkgs","paru","unarchiver","pdftoppm","poppler","prebuild","singlefile","jpegopt","EXIF","rustfmt","mktemp","nanos","xclip","xsel","natord","Mintty","nixos","nixpkgs","SIGTSTP","SIGCONT","SIGCONT","mlua","nonstatic","userdata","metatable","natsort","backstack","luajit","Succ","Succ","cand","fileencoding","foldmethod","lightgreen","darkgray","lightred","lightyellow","lightcyan","nushell","msvc","aarch","linemode","sxyazi","rsplit","ZELLIJ","bitflags","bitflags","USERPROFILE","Neovim","vergen","gitcl","Renderable","preloaders","prec","imagesize"],"language":"en","version":"0.2"}
{"language":"en","flagWords":[],"words":["Punct","KEYMAP","splitn","crossterm","YAZI","unar","peekable","ratatui","syntect","pbpaste","pbcopy","ffmpegthumbnailer","oneshot","Posix","Lsar","XADDOS","zoxide","cands","Deque","precache","imageops","IFBLK","IFCHR","IFDIR","IFIFO","IFLNK","IFMT","IFSOCK","IRGRP","IROTH","IRUSR","ISGID","ISUID","ISVTX","IWGRP","IWOTH","IWUSR","IXGRP","IXOTH","IXUSR","libc","winsize","TIOCGWINSZ","xpixel","ypixel","ioerr","appender","Catppuccin","macchiato","gitmodules","Dotfiles","bashprofile","vimrc","flac","webp","exiftool","mediainfo","ripgrep","nvim","indexmap","indexmap","unwatch","canonicalize","serde","fsevent","Ueberzug","iterm","wezterm","sixel","chafa","ueberzugpp"," Überzug"," Überzug","Konsole","Alacritty","Überzug","pkgs","paru","unarchiver","pdftoppm","poppler","prebuild","singlefile","jpegopt","EXIF","rustfmt","mktemp","nanos","xclip","xsel","natord","Mintty","nixos","nixpkgs","SIGTSTP","SIGCONT","SIGCONT","mlua","nonstatic","userdata","metatable","natsort","backstack","luajit","Succ","Succ","cand","fileencoding","foldmethod","lightgreen","darkgray","lightred","lightyellow","lightcyan","nushell","msvc","aarch","linemode","sxyazi","rsplit","ZELLIJ","bitflags","bitflags","USERPROFILE","Neovim","vergen","gitcl","Renderable","preloaders","prec","imagesize","Upserting"],"version":"0.2"}

View file

@ -1,12 +1,13 @@
use std::{ffi::OsString, fs, path::{Path, PathBuf}, process};
use clap::Parser;
use serde::Serialize;
use yazi_shared::fs::{current_cwd, expand_path};
use super::Args;
use crate::{Xdg, ARGS};
#[derive(Debug)]
#[derive(Debug, Serialize)]
pub struct Boot {
pub cwd: PathBuf,
pub file: Option<OsString>,

View file

@ -8,10 +8,10 @@ use yazi_shared::fs::{File, Url, FILES_TICKET};
use super::FilesSorter;
pub struct Files {
items: Vec<File>,
hidden: Vec<File>,
ticket: u64,
pub(crate) version: u64,
hidden: Vec<File>,
items: Vec<File>,
ticket: u64,
pub(crate) revision: u64,
pub sizes: BTreeMap<Url, u64>,
selected: BTreeSet<Url>,
@ -23,10 +23,10 @@ pub struct Files {
impl Default for Files {
fn default() -> Self {
Self {
items: Default::default(),
hidden: Default::default(),
ticket: Default::default(),
version: Default::default(),
items: Default::default(),
hidden: Default::default(),
ticket: Default::default(),
revision: Default::default(),
sizes: Default::default(),
selected: Default::default(),
@ -68,7 +68,7 @@ impl Files {
#[inline]
pub fn select(&mut self, url: &Url, state: Option<bool>) -> bool {
let old = self.selected.contains(url);
let new = if let Some(new) = state { new } else { !old };
let new = state.unwrap_or(!old);
if new == old {
return false;
@ -126,132 +126,174 @@ impl Files {
applied
}
pub fn update_full(&mut self, mut items: Vec<File>) -> bool {
if !self.show_hidden {
(self.hidden, items) = items.into_iter().partition(|f| f.is_hidden());
pub fn update_full(&mut self, files: Vec<File>) {
if files.is_empty() {
return;
}
self.ticket = FILES_TICKET.fetch_add(1, Ordering::Relaxed);
self.sorter.sort(&mut items, &self.sizes);
self.items = items;
self.version += 1;
true
self.revision += 1;
(self.hidden, self.items) = if self.show_hidden {
(vec![], files)
} else {
files.into_iter().partition(|f| f.is_hidden())
};
}
pub fn update_part(&mut self, version: u64, items: Vec<File>) -> bool {
if !items.is_empty() {
if version != self.ticket {
return false;
pub fn update_part(&mut self, files: Vec<File>, ticket: u64) {
if !files.is_empty() {
if ticket != self.ticket {
return;
}
self.revision += 1;
if self.show_hidden {
self.items.extend(items);
self.hidden.clear();
self.items.extend(files);
} else {
let (hidden, items): (Vec<_>, Vec<_>) = items.into_iter().partition(|f| f.is_hidden());
self.items.extend(items);
let (hidden, items): (Vec<_>, Vec<_>) = files.into_iter().partition(|f| f.is_hidden());
self.hidden.extend(hidden);
self.items.extend(items);
}
self.sorter.sort(&mut self.items, &self.sizes);
self.version += 1;
return true;
return;
}
self.ticket = version;
if self.items.is_empty() && self.hidden.is_empty() {
return false;
self.ticket = ticket;
if !self.items.is_empty() || !self.hidden.is_empty() {
self.revision += 1;
self.hidden.clear();
self.items.clear();
}
self.items.clear();
self.hidden.clear();
self.version += 1;
true
}
pub fn update_size(&mut self, items: BTreeMap<Url, u64>) -> bool {
self.sizes.extend(items);
pub fn update_size(&mut self, sizes: BTreeMap<Url, u64>) {
if sizes.is_empty() {
return;
}
if self.sorter.by == SortBy::Size {
self.sorter.sort(&mut self.items, &self.sizes);
self.version += 1;
self.revision += 1;
}
true
self.sizes.extend(sizes);
}
pub fn update_creating(&mut self, mut todo: BTreeMap<Url, File>) -> bool {
if !self.show_hidden {
todo.retain(|_, f| !f.is_hidden());
pub fn update_creating(&mut self, files: Vec<File>) {
if files.is_empty() {
return;
}
let b = self.update_replacing(&mut todo);
if todo.is_empty() {
return b;
}
self.items.extend(todo.into_values());
self.sorter.sort(&mut self.items, &self.sizes);
self.version += 1;
true
}
pub fn update_deleting(&mut self, mut todo: BTreeSet<Url>) -> bool {
let mut removed = Vec::with_capacity(todo.len());
macro_rules! go {
($name:expr) => {
removed.clear();
for i in 0..$name.len() {
if todo.remove(&$name[i].url) {
removed.push(i);
if todo.is_empty() {
($dist:expr, $src:expr) => {
let mut todo: BTreeMap<_, _> = $src.into_iter().map(|f| (f.url(), f)).collect();
for f in &$dist {
if todo.remove(&f.url).is_some() && todo.is_empty() {
break;
}
}
if !todo.is_empty() {
self.revision += 1;
$dist.extend(todo.into_values());
}
};
}
let (hidden, items) = if self.show_hidden {
(vec![], files)
} else {
files.into_iter().partition(|f| f.is_hidden())
};
if !items.is_empty() {
go!(self.items, items);
}
if !hidden.is_empty() {
go!(self.hidden, hidden);
}
}
pub fn update_deleting(&mut self, urls: Vec<Url>) {
if urls.is_empty() {
return;
}
macro_rules! go {
($dist:expr, $src:expr) => {
let mut todo: BTreeSet<_> = $src.into_iter().collect();
let len = $dist.len();
$dist.retain(|f| !todo.remove(&f.url));
if $dist.len() != len {
self.revision += 1;
}
};
}
let (hidden, items) =
if self.show_hidden { (vec![], urls) } else { urls.into_iter().partition(|u| u.is_hidden()) };
if !items.is_empty() {
go!(self.items, items);
}
if !hidden.is_empty() {
go!(self.hidden, hidden);
}
}
pub fn update_updating(&mut self, files: BTreeMap<Url, File>) -> [BTreeMap<Url, File>; 2] {
if files.is_empty() {
return Default::default();
}
macro_rules! go {
($dist:expr, $src:expr) => {
let len = $src.len();
for i in 0..$dist.len() {
if let Some(f) = $src.remove(&$dist[i].url) {
$dist[i] = f;
if $src.is_empty() {
break;
}
}
}
for i in (0..removed.len()).rev() {
$name.remove(removed[i]);
if $src.len() != len {
self.revision += 1;
}
};
}
let mut b = false;
if !todo.is_empty() {
go!(self.items);
b |= !removed.is_empty();
}
let (mut hidden, mut items) = if self.show_hidden {
(BTreeMap::new(), files)
} else {
files.into_iter().partition(|(_, f)| f.is_hidden())
};
if !todo.is_empty() {
go!(self.hidden);
b |= !removed.is_empty();
if !items.is_empty() {
go!(self.items, items);
}
b
if !hidden.is_empty() {
go!(self.hidden, hidden);
}
[hidden, items]
}
pub fn update_replacing(&mut self, todo: &mut BTreeMap<Url, File>) -> bool {
if todo.is_empty() {
return false;
pub fn update_upserting(&mut self, files: BTreeMap<Url, File>) {
if files.is_empty() {
return;
}
macro_rules! go {
($name:expr) => {
for i in 0..$name.len() {
if let Some(f) = todo.remove(&$name[i].url) {
$name[i] = f;
if todo.is_empty() {
self.version += 1;
return true;
}
}
}
};
let [hidden, items] = self.update_updating(files);
if hidden.is_empty() && items.is_empty() {
return;
}
let old = todo.len();
go!(self.items);
go!(self.hidden);
if old != todo.len() {
self.version += 1;
return true;
self.revision += 1;
if !hidden.is_empty() {
self.hidden.extend(hidden.into_values());
}
if !items.is_empty() {
self.items.extend(items.into_values());
}
false
}
}
@ -318,7 +360,7 @@ impl Files {
return false;
}
self.sorter = sorter;
self.version += 1;
self.revision += 1;
self.sorter.sort(&mut self.items, &self.sizes)
}
@ -339,7 +381,7 @@ impl Files {
}
self.show_hidden = state;
self.version += 1;
self.revision += 1;
true
}
}

View file

@ -25,17 +25,19 @@ impl From<&Url> for Folder {
impl Folder {
pub fn update(&mut self, op: FilesOp) -> bool {
let b = match op {
FilesOp::Full(_, items) => self.files.update_full(items),
FilesOp::Part(_, ticket, items) => self.files.update_part(ticket, items),
FilesOp::Size(_, items) => self.files.update_size(items),
let revision = self.files.revision;
match op {
FilesOp::Full(_, files) => self.files.update_full(files),
FilesOp::Part(_, files, ticket) => self.files.update_part(files, ticket),
FilesOp::Size(_, sizes) => self.files.update_size(sizes),
FilesOp::Creating(_, items) => self.files.update_creating(items),
FilesOp::Deleting(_, items) => self.files.update_deleting(items),
FilesOp::Replacing(_, mut items) => self.files.update_replacing(&mut items),
FilesOp::Creating(_, files) => self.files.update_creating(files),
FilesOp::Deleting(_, urls) => self.files.update_deleting(urls),
FilesOp::Updating(_, files) => _ = self.files.update_updating(files),
FilesOp::Upserting(_, files) => self.files.update_upserting(files),
_ => unreachable!(),
};
if !b {
}
if revision == self.files.revision {
return false;
}

View file

@ -2,7 +2,7 @@ use std::path::{PathBuf, MAIN_SEPARATOR};
use tokio::fs;
use yazi_config::popup::InputCfg;
use yazi_shared::{emit, event::Exec, fs::{File, FilesOp}, fs::Url};
use yazi_shared::{event::Exec, fs::{File, FilesOp, Url}};
use crate::{input::Input, manager::Manager};
@ -42,7 +42,7 @@ impl Manager {
let child =
Url::from(path.components().take(cwd.components().count() + 1).collect::<PathBuf>());
if let Ok(f) = File::from(child.clone()).await {
emit!(Files(FilesOp::Creating(cwd, f.into_map())));
FilesOp::Creating(cwd, vec![f]).emit();
Manager::_hover(Some(child));
}
Ok::<(), anyhow::Error>(())

View file

@ -15,5 +15,6 @@ mod tab_close;
mod tab_create;
mod tab_swap;
mod tab_switch;
mod update_files;
mod update_mimetype;
mod yank;

View file

@ -1,7 +1,6 @@
use std::ffi::OsString;
use yazi_config::{popup::SelectCfg, OPEN};
use yazi_plugin::external;
use yazi_shared::{event::Exec, MIME_DIR};
use crate::{manager::Manager, select::Select, tasks::Tasks};
@ -44,28 +43,30 @@ impl Manager {
}
let opt = opt.into() as Opt;
tokio::spawn(async move {
let todo: Vec<_> = files.iter().filter(|(_, m)| m.is_none()).map(|(u, _)| u).collect();
if let Ok(mut mimes) = external::file(&todo).await {
files = files
.into_iter()
.map(|(u, m)| {
let mime = m.or_else(|| mimes.remove(&u));
(u, mime)
})
.collect();
}
// TODO: plugin system
// tokio::spawn(async move {
// let todo: Vec<_> = files.iter().filter(|(_, m)| m.is_none()).map(|(u, _)|
// u).collect(); if let Ok(mut mimes) = external::file(&todo).await {
// files = files
// .into_iter()
// .map(|(u, m)| {
// let mime = m.or_else(|| mimes.remove(&u));
// (u, mime)
// })
// .collect();
// }
let files: Vec<_> =
files.into_iter().filter_map(|(u, m)| m.map(|m| (u.into_os_string(), m))).collect();
// let files: Vec<_> =
// files.into_iter().filter_map(|(u, m)| m.map(|m| (u.into_os_string(),
// m))).collect();
if opt.interactive {
Self::open_interactive(files).await;
return;
}
// if opt.interactive {
// Self::open_interactive(files).await;
// return;
// }
Tasks::_open(files, None);
});
// Tasks::_open(files, None);
// });
false
}
}

View file

@ -22,8 +22,8 @@ impl Manager {
self.active_mut().apply_files_attrs(false);
if let Some(f) = self.parent() {
self.watcher.trigger_dirs(&[self.cwd(), &f.cwd]);
if let Some(p) = self.parent() {
self.watcher.trigger_dirs(&[self.cwd(), &p.cwd]);
} else {
self.watcher.trigger_dirs(&[self.cwd()]);
}

View file

@ -5,7 +5,7 @@ use tokio::{fs::{self, OpenOptions}, io::{stdin, AsyncReadExt, AsyncWriteExt}};
use yazi_config::{popup::InputCfg, OPEN, PREVIEW};
use yazi_plugin::external::{self, ShellOpt};
use yazi_scheduler::{Scheduler, BLOCKER};
use yazi_shared::{emit, event::Exec, fs::{max_common_root, File, FilesOp, Url}, term::Term, Defer};
use yazi_shared::{event::Exec, fs::{max_common_root, File, FilesOp, Url}, term::Term, Defer};
use crate::{input::Input, manager::Manager};
@ -25,7 +25,7 @@ impl Manager {
}
let file = File::from(new.clone()).await?;
emit!(Files(FilesOp::Replacing(file.parent().unwrap(), BTreeMap::from_iter([(old, file)]))));
FilesOp::Upserting(file.parent().unwrap(), BTreeMap::from_iter([(old, file)])).emit();
Ok(Self::_hover(Some(new)))
}

View file

@ -0,0 +1,75 @@
use yazi_shared::{event::Exec, fs::FilesOp};
use crate::{folder::Folder, manager::Manager, tasks::Tasks};
pub struct Opt {
op: FilesOp,
}
impl TryFrom<&Exec> for Opt {
type Error = ();
fn try_from(e: &Exec) -> Result<Self, Self::Error> { Ok(Self { op: e.take_data().ok_or(())? }) }
}
impl Manager {
fn handle_read(&mut self, op: FilesOp) -> bool {
let url = op.url().clone();
let cwd = self.cwd().to_owned();
let hovered = self.hovered().map(|h| h.url());
let mut b = if cwd == url {
self.current_mut().update(op)
} else if matches!(self.parent(), Some(p) if p.cwd == url) {
self.active_mut().parent.as_mut().unwrap().update(op)
} else if matches!(self.hovered(), Some(h) if h.url == url) {
self.active_mut().history.entry(url.clone()).or_insert_with(|| Folder::from(&url));
self.active_mut().apply_files_attrs(true);
self.active_mut().history.get_mut(&url).unwrap().update(op)
} else {
self.active_mut().history.entry(url.clone()).or_insert_with(|| Folder::from(&url)).update(op);
false
};
b |= self.active_mut().parent.as_mut().is_some_and(|p| p.hover(&cwd));
b |= hovered.as_ref().is_some_and(|h| self.current_mut().hover(h));
if hovered.as_ref() != self.hovered().map(|h| &h.url) {
Self::_hover(None);
}
b
}
fn handle_ioerr(&mut self, op: FilesOp) -> bool {
let url = op.url();
let op = FilesOp::Full(url.clone(), Vec::new());
if url == self.cwd() {
self.current_mut().update(op);
self.active_mut().leave(());
true
} else if matches!(self.parent(), Some(p) if &p.cwd == url) {
self.active_mut().parent.as_mut().unwrap().update(op)
} else {
false
}
}
pub fn update_files(&mut self, opt: impl TryInto<Opt>, tasks: &Tasks) -> bool {
let Ok(opt) = opt.try_into() else {
return false;
};
let calc = !matches!(opt.op, FilesOp::Size(..) | FilesOp::IOErr(_) | FilesOp::Deleting(..));
let b = match opt.op {
FilesOp::IOErr(..) => self.handle_ioerr(opt.op),
_ => self.handle_read(opt.op),
};
if calc {
tasks.preload_sorted(&self.current().files);
}
b
}
}

View file

@ -1,6 +1,6 @@
use std::collections::{HashMap, HashSet};
use yazi_shared::fs::{File, FilesOp, Url};
use yazi_shared::fs::{File, Url};
use super::{Tabs, Watcher};
use crate::{folder::Folder, tab::Tab};
@ -23,48 +23,6 @@ impl Manager {
mimetype: Default::default(),
}
}
pub fn update_read(&mut self, op: FilesOp) -> bool {
let url = op.url().clone();
let cwd = self.cwd().to_owned();
let hovered = self.hovered().map(|h| h.url());
let mut b = if cwd == url {
self.current_mut().update(op)
} else if matches!(self.parent(), Some(p) if p.cwd == url) {
self.active_mut().parent.as_mut().unwrap().update(op)
} else if matches!(self.hovered(), Some(h) if h.url == url) {
self.active_mut().history.entry(url.clone()).or_insert_with(|| Folder::from(&url));
self.active_mut().apply_files_attrs(true);
self.active_mut().history.get_mut(&url).unwrap().update(op)
} else {
self.active_mut().history.entry(url.clone()).or_insert_with(|| Folder::from(&url)).update(op);
false
};
b |= self.active_mut().parent.as_mut().is_some_and(|p| p.hover(&cwd));
b |= hovered.as_ref().is_some_and(|h| self.current_mut().hover(h));
if hovered.as_ref() != self.hovered().map(|h| &h.url) {
Self::_hover(None);
}
b
}
pub fn update_ioerr(&mut self, op: FilesOp) -> bool {
let url = op.url();
let op = FilesOp::Full(url.clone(), Vec::new());
if url == self.cwd() {
self.current_mut().update(op);
self.active_mut().leave(());
true
} else if matches!(self.parent(), Some(p) if &p.cwd == url) {
self.active_mut().parent.as_mut().unwrap().update(op)
} else {
false
}
}
}
impl Manager {

View file

@ -1,17 +1,19 @@
use std::{collections::BTreeSet, sync::Arc, time::Duration};
use std::{collections::{BTreeMap, BTreeSet}, sync::Arc, time::Duration};
use indexmap::IndexMap;
use notify::{event::{MetadataKind, ModifyKind}, EventKind, RecommendedWatcher, RecursiveMode, Watcher as _Watcher};
use parking_lot::RwLock;
use tokio::{fs, pin, sync::mpsc::{self, UnboundedReceiver}};
use tokio_stream::{wrappers::UnboundedReceiverStream, StreamExt};
use yazi_shared::{emit, fs::{File, FilesOp, Url}};
use tracing::error;
use yazi_plugin::isolate;
use yazi_shared::fs::{File, FilesOp, Url};
use crate::folder::Files;
pub struct Watcher {
watcher: RecommendedWatcher,
watched: Arc<RwLock<IndexMap<Url, Option<Url>>>>,
watcher: RecommendedWatcher,
watched: Arc<RwLock<BTreeSet<Url>>>,
pub linked: Arc<RwLock<BTreeMap<Url, Url>>>, // from ==> to
}
impl Watcher {
@ -25,55 +27,45 @@ impl Watcher {
return;
};
let Some(path) = event.paths.first().map(Url::from) else {
return;
};
let parent = path.parent_url().unwrap_or_else(|| path.clone());
match event.kind {
EventKind::Create(_) => {
tx.send(parent).ok();
}
EventKind::Modify(kind) => {
match kind {
ModifyKind::Data(_) => {}
ModifyKind::Metadata(kind) => match kind {
MetadataKind::Permissions => {}
MetadataKind::Ownership => {}
MetadataKind::Extended => {}
_ => return,
},
ModifyKind::Name(_) => {}
EventKind::Create(_) => {}
EventKind::Modify(kind) => match kind {
ModifyKind::Data(_) => {}
ModifyKind::Metadata(md) => match md {
MetadataKind::WriteTime => {}
MetadataKind::Permissions => {}
MetadataKind::Ownership => {}
_ => return,
};
},
ModifyKind::Name(_) => {}
_ => return,
},
EventKind::Remove(_) => {}
_ => return,
}
tx.send(path).ok();
tx.send(parent).ok();
}
EventKind::Remove(_) => {
tx.send(path).ok();
tx.send(parent).ok();
}
_ => (),
for path in event.paths {
tx.send(Url::from(path)).ok();
}
}
},
Default::default(),
);
let instance = Self { watcher: watcher.unwrap(), watched: Default::default() };
tokio::spawn(Self::on_changed(rx, instance.watched.clone()));
let instance =
Self { watcher: watcher.unwrap(), watched: Default::default(), linked: Default::default() };
tokio::spawn(Self::on_changed(rx));
instance
}
pub(super) fn watch(&mut self, mut watched: BTreeSet<&Url>) {
watched.retain(|&u| u.is_regular());
pub(super) fn watch(&mut self, mut new: BTreeSet<&Url>) {
new.retain(|&u| u.is_regular());
let (to_unwatch, to_watch): (BTreeSet<_>, BTreeSet<_>) = {
let guard = self.watched.read();
let keys = guard.keys().collect::<BTreeSet<_>>();
let old: BTreeSet<_> = guard.iter().collect();
(
keys.difference(&watched).map(|&x| x.clone()).collect(),
watched.difference(&keys).map(|&x| x.clone()).collect(),
old.difference(&new).map(|&x| x.clone()).collect(),
new.difference(&old).map(|&x| x.clone()).collect(),
)
};
@ -82,141 +74,103 @@ impl Watcher {
}
for u in to_watch {
if self.watcher.watch(&u, RecursiveMode::NonRecursive).is_err() {
watched.remove(&u);
new.remove(&u);
}
}
let mut to_resolve = Vec::new();
let mut guard = self.watched.write();
*guard = watched
.into_iter()
.map(|k| {
if let Some(old) = guard.remove_entry(k) {
old
} else {
to_resolve.push(k.clone());
(k.clone(), None)
}
})
.collect();
let lock = self.watched.clone();
tokio::spawn(async move {
for k in to_resolve {
match fs::canonicalize(&k).await {
Ok(v) if v != *k => {
lock.write().insert(k, Some(Url::from(v)));
}
_ => {}
}
}
});
*self.watched.write() = new.into_iter().cloned().collect();
self.sync_linked();
}
pub(super) fn trigger_dirs(&self, dirs: &[&Url]) {
let dirs: Vec<_> = dirs.iter().filter(|&u| u.is_regular()).map(|&u| u.clone()).collect();
if dirs.is_empty() {
let urls: Vec<_> = dirs.iter().filter(|&u| u.is_regular()).map(|&u| u.clone()).collect();
if urls.is_empty() {
return;
}
let watched = self.watched.clone();
tokio::spawn(async move {
let watched = watched.read().clone();
for dir in dirs {
Self::dir_changed(&dir, &watched).await;
for u in urls {
let Ok(rx) = Files::from_dir(&u).await else {
FilesOp::IOErr(u).emit();
return;
};
let files: Vec<_> = UnboundedReceiverStream::new(rx).collect().await;
FilesOp::Full(u, files).emit();
}
});
}
async fn on_changed(
rx: UnboundedReceiver<Url>,
watched: Arc<RwLock<IndexMap<Url, Option<Url>>>>,
) {
fn sync_linked(&self) {
let mut new = self.watched.read().clone();
self.linked.write().retain(|k, _| new.remove(k));
let watched = self.watched.clone();
let linked = self.linked.clone();
macro_rules! go {
($todo:expr) => {
for from in $todo {
match fs::canonicalize(&from).await {
Ok(to) if to != *from && watched.read().contains(&from) => {
linked.write().insert(from, Url::from(to));
}
_ => {}
}
}
};
}
tokio::spawn(async move {
let old: Vec<_> = linked.read().keys().cloned().collect();
go!(new);
go!(old);
});
}
async fn on_changed(rx: UnboundedReceiver<Url>) {
// TODO: revert this once a new notification is implemented
let rx = UnboundedReceiverStream::new(rx).chunks_timeout(10, Duration::from_millis(20));
pin!(rx);
while let Some(urls) = rx.next().await {
let (mut files, mut dirs): (Vec<_>, Vec<_>) = Default::default();
for url in urls.into_iter().collect::<BTreeSet<_>>() {
if fs::metadata(&url).await.map(|m| !m.is_dir()).unwrap_or(false) {
files.push(url);
} else {
dirs.push(url);
let urls = urls.into_iter().collect::<BTreeSet<_>>();
let mut reload = Vec::with_capacity(urls.len());
for u in urls {
let Some(parent) = u.parent_url() else {
continue;
};
let Ok(file) = File::from(u.clone()).await else {
FilesOp::Deleting(parent, vec![u]).emit();
continue;
};
if !file.is_dir() {
reload.push(file.clone());
}
FilesOp::Upserting(parent, BTreeMap::from_iter([(u, file)])).emit();
}
let watched = watched.read().clone();
Self::files_changed(&files, &watched).await;
for file in files {
for u in Self::linked_urls(&file, &watched) {
emit!(Files(FilesOp::IOErr(u.clone())));
}
emit!(Files(FilesOp::IOErr(file)));
if reload.is_empty() {
continue;
}
for dir in dirs {
Self::dir_changed(&dir, &watched).await;
if let Err(e) = isolate::preload("mime.lua".to_string(), reload, true).await {
error!("preload in watcher failed: {e}");
}
}
}
async fn files_changed(urls: &[Url], watched: &IndexMap<Url, Option<Url>>) {
// TODO: plugin system
// let Ok(mut mimes) = external::file(urls).await else {
// return;
// };
// let linked: Vec<_> = watched.iter().filter_map(|(k, v)|
// v.as_ref().map(|v| (k, v))).fold( Vec::new(),
// |mut aac, (k, v)| {
// mimes
// .iter()
// .filter(|(u, _)| u.parent().map(|p| p == **v) == Some(true))
// .for_each(|(u, m)| aac.push((k.join(u.file_name().unwrap()),
// m.clone()))); aac
// },
// );
// mimes.extend(linked);
// emit!(Mimetype(mimes));
}
async fn dir_changed(url: &Url, watched: &IndexMap<Url, Option<Url>>) {
let linked = Self::linked_urls(url, watched);
let Ok(rx) = Files::from_dir(url).await else {
emit!(Files(FilesOp::IOErr(url.clone())));
for u in linked {
emit!(Files(FilesOp::IOErr(u.clone())));
}
return;
};
let linked_files = |files: &[File], linked: &Url| -> Vec<File> {
let mut new = Vec::with_capacity(files.len());
for file in files {
let mut file = file.clone();
file.url = linked.join(file.url.strip_prefix(url).unwrap());
new.push(file);
}
new
};
let files: Vec<_> = UnboundedReceiverStream::new(rx).collect().await;
for u in linked {
let files = linked_files(&files, u);
emit!(Files(FilesOp::Full(u.clone(), files)));
}
emit!(Files(FilesOp::Full(url.clone(), files)));
}
fn linked_urls<'a>(url: &'a Url, watched: &'a IndexMap<Url, Option<Url>>) -> Vec<&'a Url> {
watched
.iter()
.filter_map(|(k, v)| v.as_ref().map(|v| (k, v)))
.filter(|(_, v)| *v == url)
.map(|(k, _)| k)
.collect()
}
}
impl Watcher {
pub fn relatives(&self, url: &Url) -> BTreeSet<Url> {
let mut map = BTreeSet::from_iter([url.clone()]);
let linked = self.linked.read();
if let Some(to) = linked.get(url) {
map.extend(linked.iter().filter(|(_, v)| *v == to).map(|(k, _)| k).cloned());
}
map
}
}

View file

@ -1,4 +1,4 @@
use yazi_shared::{emit, event::Exec, fs::{File, FilesOp}, fs::{expand_path, Url}, Layer};
use yazi_shared::{emit, event::Exec, fs::{expand_path, File, FilesOp, Url}, Layer};
use crate::{manager::Manager, tab::Tab};
@ -34,10 +34,7 @@ impl Tab {
};
let b = self.cd(parent.clone());
emit!(Files(FilesOp::Creating(
parent.clone(),
File::from_dummy(opt.target.clone()).into_map()
)));
FilesOp::Creating(parent, vec![File::from_dummy(opt.target.clone())]).emit();
Manager::_hover(Some(opt.target));
b
}

View file

@ -5,7 +5,7 @@ use tokio::pin;
use tokio_stream::{wrappers::UnboundedReceiverStream, StreamExt};
use yazi_config::popup::InputCfg;
use yazi_plugin::external;
use yazi_shared::{emit, event::Exec, fs::FilesOp};
use yazi_shared::{event::Exec, fs::FilesOp};
use crate::{input::Input, manager::Manager, tab::Tab};
@ -66,7 +66,7 @@ impl Tab {
Tab::_cd(&cwd);
first = false;
}
emit!(Files(FilesOp::Part(cwd.clone(), ticket, chunk)));
FilesOp::Part(cwd.clone(), chunk, ticket).emit();
}
Ok(())
}));

View file

@ -53,7 +53,7 @@ impl Finder {
}
pub(super) fn catchup(&mut self, files: &Files) -> bool {
if self.version == files.version {
if self.version == files.revision {
return false;
}
self.matched.clear();
@ -72,7 +72,7 @@ impl Finder {
i += 1;
}
self.version = files.version;
self.version = files.revision;
true
}

View file

@ -5,7 +5,7 @@ use crossterm::event::KeyEvent;
use ratatui::backend::Backend;
use yazi_config::{keymap::Key, ARGS};
use yazi_core::input::InputMode;
use yazi_shared::{emit, event::{Event, Exec}, fs::FilesOp, term::Term, Layer, COLLISION};
use yazi_shared::{emit, event::{Event, Exec}, term::Term, Layer, COLLISION};
use crate::{lives::Lives, Ctx, Executor, Logs, Panic, Root, Signals};
@ -132,19 +132,6 @@ impl App {
let manager = &mut self.cx.manager;
let tasks = &mut self.cx.tasks;
match event {
Event::Files(op) => {
let calc = !matches!(op, FilesOp::Size(..) | FilesOp::IOErr(_));
let b = match op {
FilesOp::IOErr(..) => manager.update_ioerr(op),
_ => manager.update_read(op),
};
if b {
emit!(Render);
}
if calc {
tasks.preload_sorted(&manager.current().files);
}
}
Event::Pages(page) => {
let targets = self.cx.manager.current().paginate(page);
tasks.preload_paged(targets, &self.cx.manager.mimetype);

View file

@ -110,6 +110,7 @@ impl<'a> Executor<'a> {
};
}
on!(MANAGER, update_files, &self.app.cx.tasks);
on!(MANAGER, update_mimetype, &self.app.cx.tasks);
on!(MANAGER, hover);
on!(MANAGER, peek);

View file

@ -1,13 +1,13 @@
local Archive = {}
local M = {}
function Archive:peek()
function M:peek()
local _, bound = ya.preview_archive(self)
if bound then
ya.manager_emit("peek", { tostring(bound), only_if = tostring(self.file.url), upper_bound = "" })
end
end
function Archive:seek(units)
function M:seek(units)
local h = cx.active.current.hovered
if h and h.url == self.file.url then
local step = math.floor(units * self.area.h / 10)
@ -18,4 +18,4 @@ function Archive:seek(units)
end
end
return Archive
return M

View file

@ -1,13 +1,13 @@
local Code = {}
local M = {}
function Code:peek()
function M:peek()
local _, bound = ya.preview_code(self)
if bound then
ya.manager_emit("peek", { tostring(bound), only_if = tostring(self.file.url), upper_bound = "" })
end
end
function Code:seek(units)
function M:seek(units)
local h = cx.active.current.hovered
if h and h.url == self.file.url then
local step = math.floor(units * self.area.h / 10)
@ -18,4 +18,4 @@ function Code:seek(units)
end
end
return Code
return M

View file

@ -1,6 +1,6 @@
local Folder_ = {}
local M = {}
function Folder_:peek()
function M:peek()
local folder = Folder:by_kind(Folder.PREVIEW)
if folder == nil or folder.cwd ~= self.file.url then
return {}
@ -24,7 +24,7 @@ function Folder_:peek()
ya.preview_widgets(self, { ui.List(self.area, items) })
end
function Folder_:seek(units)
function M:seek(units)
local folder = Folder:by_kind(Folder.PREVIEW)
if folder and folder.cwd == self.file.url then
local step = math.floor(units * self.area.h / 10)
@ -36,4 +36,4 @@ function Folder_:seek(units)
end
end
return Folder_
return M

View file

@ -1,16 +1,16 @@
local Image = {}
local M = {}
function Image:cache() return ya.cache_file(self.file.url .. tostring(self.file.cha.modified)) end
function M:cache() return ya.cache_file(self.file.url .. tostring(self.file.cha.modified)) end
function Image:peek()
function M:peek()
local cache = self:cache()
ya.image_show(fs.symlink_metadata(cache) and cache or self.file.url, self.area)
ya.preview_widgets(self, {})
end
function Image:seek() end
function M:seek() end
function Image:preload()
function M:preload()
local cache = self:cache()
if fs.symlink_metadata(cache) then
return 1
@ -19,4 +19,4 @@ function Image:preload()
return ya.image_precache(self.file.url, cache) and 1 or 2
end
return Image
return M

View file

@ -1,6 +1,6 @@
local Json = {}
local M = {}
function Json:peek()
function M:peek()
local limit = self.area.h
local child = Command.new("jq")
:args({
@ -35,7 +35,7 @@ function Json:peek()
end
end
function Json:seek(units)
function M:seek(units)
local h = cx.active.current.hovered
if h and h.url == self.file.url then
local step = math.floor(units * self.area.h / 10)
@ -46,4 +46,4 @@ function Json:seek(units)
end
end
return Json
return M

View file

@ -1,6 +1,6 @@
local Mime = {}
local M = {}
function Mime:preload()
function M:preload()
local command = Command.new("file"):arg("--mime-type"):stdout(Command.PIPED):stderr(Command.PIPED)
if ya.target_family() == "windows" then
command:arg("-b")
@ -13,23 +13,23 @@ function Mime:preload()
urls[#urls + 1] = tostring(file.url)
end
local i, data = 1, {}
local i, mimes = 1, {}
local output = command:args(urls):output()
for line in output.stdout:gmatch("[^\r\n]+") do
if i > #urls then
break
end
if ya.mime_valid(line) then
data[urls[i]] = line
mimes[urls[i]] = line
end
i = i + 1
end
if #data then
ya.manager_emit("update_mimetype", {}, data)
if #mimes then
ya.manager_emit("update_mimetype", {}, mimes)
return 3
end
return 2
end
return Mime
return M

View file

@ -1,9 +1,9 @@
local Noop = {}
local M = {}
function Noop:peek() end
function M:peek() end
function Noop:seek() end
function M:seek() end
function Noop:preload() return 1 end
function M:preload() return 1 end
return Noop
return M

View file

@ -1,15 +1,15 @@
local Pdf = {}
local M = {}
function Pdf:cache() return ya.cache_file(self.file.url .. self.skip .. tostring(self.file.cha.modified)) end
function M:cache() return ya.cache_file(self.file.url .. self.skip .. tostring(self.file.cha.modified)) end
function Pdf:peek()
function M:peek()
if self:preload() == 1 then
ya.image_show(self:cache(), self.area)
ya.preview_widgets(self, {})
end
end
function Pdf:seek(units)
function M:seek(units)
local h = cx.active.current.hovered
if h and h.url == self.file.url then
local step = ya.clamp(-1, units, 1)
@ -17,7 +17,7 @@ function Pdf:seek(units)
end
end
function Pdf:preload()
function M:preload()
local cache = self:cache()
if fs.symlink_metadata(cache) then
return 1
@ -40,4 +40,4 @@ function Pdf:preload()
return fs.write(cache, output.stdout) and 1 or 2
end
return Pdf
return M

View file

@ -1,15 +1,15 @@
local Video = {}
local M = {}
function Video:cache() return ya.cache_file(self.file.url .. self.skip .. tostring(self.file.cha.modified)) end
function M:cache() return ya.cache_file(self.file.url .. self.skip .. tostring(self.file.cha.modified)) end
function Video:peek()
function M:peek()
if self:preload() == 1 then
ya.image_show(self:cache(), self.area)
ya.preview_widgets(self, {})
end
end
function Video:seek(units)
function M:seek(units)
local h = cx.active.current.hovered
if h and h.url == self.file.url then
ya.manager_emit("peek", {
@ -19,7 +19,7 @@ function Video:seek(units)
end
end
function Video:preload()
function M:preload()
local percentage = 5 + self.skip
if percentage > 95 then
ya.manager_emit("peek", { "90", only_if = tostring(self.file.url), upper_bound = "" })
@ -52,4 +52,4 @@ function Video:preload()
return status:success() and 1 or 2
end
return Video
return M

View file

@ -1,5 +1,5 @@
use mlua::{Lua, LuaSerdeExt, SerializeOptions};
use yazi_config::{MANAGER, PREVIEW, THEME};
use yazi_config::{BOOT, MANAGER, PREVIEW, THEME};
const OPTIONS: SerializeOptions =
SerializeOptions::new().serialize_none_to_null(false).serialize_unit_to_null(false);
@ -11,6 +11,16 @@ pub struct Config<'a> {
impl<'a> Config<'a> {
pub fn new(lua: &'a Lua) -> Self { Self { lua } }
pub fn install_boot(self) -> mlua::Result<Self> {
self.lua.globals().set("BOOT", self.lua.to_value_with(&*BOOT, OPTIONS)?)?;
Ok(self)
}
pub fn install_manager(self) -> mlua::Result<Self> {
self.lua.globals().set("MANAGER", self.lua.to_value_with(&*MANAGER, OPTIONS)?)?;
Ok(self)
}
pub fn install_theme(self) -> mlua::Result<Self> {
self.lua.globals().set("THEME", self.lua.to_value_with(&*THEME, OPTIONS)?)?;
Ok(self)
@ -20,9 +30,4 @@ impl<'a> Config<'a> {
self.lua.globals().set("PREVIEW", self.lua.to_value_with(&*PREVIEW, OPTIONS)?)?;
Ok(self)
}
pub fn install_manager(self) -> mlua::Result<Self> {
self.lua.globals().set("MANAGER", self.lua.to_value_with(&*MANAGER, OPTIONS)?)?;
Ok(self)
}
}

View file

@ -1,40 +0,0 @@
use std::collections::BTreeMap;
use anyhow::{bail, Result};
use futures::TryFutureExt;
use tokio::process::Command;
use tracing::error;
use yazi_shared::{fs::Url, mime_valid};
async fn _file(files: &[&Url]) -> Result<BTreeMap<Url, String>> {
if files.is_empty() {
bail!("no files to get mime types for");
}
let output = Command::new("file")
.args([cfg!(windows).then_some("-b").unwrap_or("-bL"), "--mime-type"])
.args(files)
.kill_on_drop(true)
.output()
.inspect_err(|e| error!("failed to execute `file`: {}", e))
.await?;
let output = String::from_utf8_lossy(&output.stdout);
let mimes = BTreeMap::from_iter(
files
.iter()
.zip(output.trim().lines())
.filter(|(_, m)| mime_valid(m))
.map(|(&f, m)| (f.clone(), m.to_string())),
);
if mimes.is_empty() {
error!("failed to get mime types: {:?}", files);
bail!("failed to get mime types");
}
Ok(mimes)
}
pub async fn file(files: &[impl AsRef<Url>]) -> Result<BTreeMap<Url, String>> {
_file(&files.iter().map(AsRef::as_ref).collect::<Vec<_>>()).await
}

View file

@ -1,5 +1,4 @@
mod fd;
mod file;
mod fzf;
mod highlighter;
mod lsar;
@ -8,7 +7,6 @@ mod shell;
mod zoxide;
pub use fd::*;
pub use file::*;
pub use fzf::*;
pub use highlighter::*;
pub use lsar::*;

View file

@ -22,8 +22,8 @@ impl Loader {
return Ok(());
}
let b = fs::read(BOOT.plugin_dir.join(name)).await.map(|v| v.into()).unwrap_or(Cow::from(
match name {
let b = fs::read(BOOT.plugin_dir.join(name)).await.map(|v| v.into()).or_else(|_| {
Ok(Cow::from(match name {
"noop.lua" => include_bytes!("../preset/plugins/noop.lua") as &[u8],
"archive.lua" => include_bytes!("../preset/plugins/archive.lua"),
"code.lua" => include_bytes!("../preset/plugins/code.lua"),
@ -33,9 +33,9 @@ impl Loader {
"mime.lua" => include_bytes!("../preset/plugins/mime.lua"),
"pdf.lua" => include_bytes!("../preset/plugins/pdf.lua"),
"video.lua" => include_bytes!("../preset/plugins/video.lua"),
_ => bail!("plugin not found: {}", name),
},
));
_ => bail!("plugin not found: {name}"),
}))
})?;
self.loaded.write().insert(name.to_owned(), b.into_owned());
Ok(())

View file

@ -1,5 +1,6 @@
use anyhow::Result;
use mlua::{Lua, Table};
use mlua::Lua;
use yazi_config::BOOT;
use yazi_shared::RoCell;
pub static LUA: RoCell<Lua> = RoCell::new();
@ -7,7 +8,7 @@ pub static LUA: RoCell<Lua> = RoCell::new();
pub fn init() {
fn stage_1(lua: &Lua) -> Result<()> {
crate::Loader::init();
crate::Config::new(lua).install_theme()?.install_manager()?;
crate::Config::new(lua).install_boot()?.install_manager()?.install_theme()?;
crate::utils::install(lua)?;
// Base
@ -29,14 +30,15 @@ pub fn init() {
}
fn stage_2(lua: &Lua) {
let ya: Table = lua.globals().get("ya").unwrap();
ya.set("SYNC_ON", true).unwrap();
let setup = br#"
ya.SYNC_ON = true
package.path = BOOT.plugin_dir .. "/?.yazi/init.lua;" .. BOOT.plugin_dir .. "/?.lua;" .. package.path
"#;
lua.load(setup as &[u8]).exec().unwrap();
// TODO: plugin system
// PLUGIN.preload.iter().for_each(|p| {
// let b = std::fs::read(p).unwrap_or_else(|_| panic!("failed to read
// plugin: {p:?}")); lua.load(&b).exec().unwrap_or_else(|_| panic!("failed
// to load plugin: {p:?}")); });
if let Ok(b) = std::fs::read(BOOT.config_dir.join("init.lua")) {
lua.load(b).exec().unwrap();
}
}
let lua = Lua::new();

View file

@ -4,7 +4,7 @@ use anyhow::Result;
use parking_lot::RwLock;
use tokio::sync::mpsc;
use tracing::error;
use yazi_shared::{emit, fs::{calculate_size, FilesOp, Url}, Throttle};
use yazi_shared::{fs::{calculate_size, FilesOp, Url}, Throttle};
use crate::TaskOp;
@ -74,7 +74,7 @@ impl Preload {
}
let parent = buf[0].0.parent_url().unwrap();
emit!(Files(FilesOp::Size(parent, BTreeMap::from_iter(buf))));
FilesOp::Size(parent, BTreeMap::from_iter(buf)).emit();
});
self.sch.send(TaskOp::Adv(task.id, 1, 0))?;

View file

@ -2,7 +2,7 @@ use crossterm::event::KeyEvent;
use tokio::sync::{mpsc::UnboundedSender, oneshot};
use super::Exec;
use crate::{fs::FilesOp, term::Term, Layer, RoCell};
use crate::{term::Term, Layer, RoCell};
static TX: RoCell<UnboundedSender<Event>> = RoCell::new();
@ -15,7 +15,6 @@ pub enum Event {
Call(Vec<Exec>, Layer),
// Manager
Files(FilesOp),
Pages(usize),
}
@ -44,9 +43,6 @@ macro_rules! emit {
$crate::event::Event::Call($exec, $layer).emit();
};
(Files($op:expr)) => {
$crate::event::Event::Files($op).emit();
};
(Pages($page:expr)) => {
$crate::event::Event::Pages($page).emit();
};

View file

@ -1,4 +1,4 @@
use std::{collections::BTreeMap, ffi::OsStr, fs::Metadata, ops::Deref};
use std::{ffi::OsStr, fs::Metadata, ops::Deref};
use anyhow::Result;
use tokio::fs;
@ -52,13 +52,6 @@ impl File {
let ck = if url.is_hidden() { ChaKind::HIDDEN } else { ChaKind::empty() };
Self { url, cha: Cha::default().with_kind(ck), link_to: None }
}
#[inline]
pub fn into_map(self) -> BTreeMap<Url, File> {
let mut map = BTreeMap::new();
map.insert(self.url.clone(), self);
map
}
}
impl File {

View file

@ -1,20 +1,21 @@
use std::{collections::{BTreeMap, BTreeSet}, sync::atomic::{AtomicU64, Ordering}};
use std::{collections::BTreeMap, sync::atomic::{AtomicU64, Ordering}};
use super::File;
use crate::{emit, fs::Url};
use crate::{emit, event::Exec, fs::Url, Layer};
pub static FILES_TICKET: AtomicU64 = AtomicU64::new(0);
#[derive(Debug)]
pub enum FilesOp {
Full(Url, Vec<File>),
Part(Url, u64, Vec<File>),
Part(Url, Vec<File>, u64),
Size(Url, BTreeMap<Url, u64>),
IOErr(Url),
Creating(Url, BTreeMap<Url, File>),
Deleting(Url, BTreeSet<Url>),
Replacing(Url, BTreeMap<Url, File>),
Creating(Url, Vec<File>),
Deleting(Url, Vec<Url>),
Updating(Url, BTreeMap<Url, File>),
Upserting(Url, BTreeMap<Url, File>),
}
impl FilesOp {
@ -28,14 +29,41 @@ impl FilesOp {
Self::Creating(url, _) => url,
Self::Deleting(url, _) => url,
Self::Replacing(url, _) => url,
Self::Updating(url, _) => url,
Self::Upserting(url, _) => url,
}
}
#[inline]
pub fn emit(self) {
emit!(Call(Exec::call("update_files", vec![]).with_data(self).vec(), Layer::Manager));
}
pub fn prepare(url: &Url) -> u64 {
let ticket = FILES_TICKET.fetch_add(1, Ordering::Relaxed);
emit!(Files(Self::Part(url.clone(), ticket, Vec::new())));
Self::Part(url.clone(), Vec::new(), ticket).emit();
ticket
}
pub fn chroot(&self, new: Url) -> Self {
todo!()
// Full(Url, Vec<File>),
// Part(Url, u64, Vec<File>),
// Size(Url, BTreeMap<Url, u64>),
// IOErr(Url),
// Creating(Url, BTreeMap<Url, File>),
// Deleting(Url, BTreeSet<Url>),
// Replacing(Url, BTreeMap<Url, File>),
// match self {
// FilesOp::Full(_, vec) => {}
// FilesOp::Part(_, _, vec) => todo!(),
// FilesOp::Size(_, map) => todo!(),
// FilesOp::IOErr(_) => todo!(),
// FilesOp::Creating(_, map) => todo!(),
// FilesOp::Deleting(_, set) => todo!(),
// FilesOp::Replacing(_, map) => todo!(),
// }
}
}