perf: chunk loading for mime-types (#467)

This commit is contained in:
三咲雅 · Misaki Masa 2024-01-01 18:28:38 +08:00 committed by sxyazi
parent ccb98ac21f
commit 5fb0fceacb
No known key found for this signature in database
31 changed files with 219 additions and 115 deletions

View file

@ -112,7 +112,7 @@ impl Adaptor {
return Self::X11;
}
if std::fs::symlink_metadata("/proc/sys/fs/binfmt_misc/WSLInterop").is_ok() {
return Self::Kitty;
return Self::KittyOld;
}
warn!("[Adaptor] Falling back to chafa");

View file

@ -41,7 +41,7 @@ where
where
A: de::SeqAccess<'de>,
{
let mut execs = Vec::new();
let mut execs = vec![];
while let Some(value) = &seq.next_element::<String>()? {
execs.push(parse(value).map_err(de::Error::custom)?);
}

View file

@ -31,7 +31,7 @@ impl OpenRule {
where
A: de::SeqAccess<'de>,
{
let mut uses = Vec::new();
let mut uses = vec![];
while let Some(use_) = seq.next_element::<String>()? {
uses.push(use_);
}

View file

@ -24,13 +24,16 @@ impl Pattern {
#[inline]
pub fn match_path(&self, path: impl AsRef<Path>, is_folder: bool) -> bool {
if is_folder != self.is_folder {
return false;
}
let path = path.as_ref();
let s = if self.full_path {
path.to_str()
self.matches(if self.full_path {
path.to_string_lossy()
} else {
path.file_name().and_then(|n| n.to_str()).or_else(|| path.to_str())
};
is_folder == self.is_folder && s.is_some_and(|s| self.matches(s))
path.file_name().map_or_else(|| path.to_string_lossy(), |n| n.to_string_lossy())
})
}
}

View file

@ -28,7 +28,7 @@ impl Icon {
where
A: de::MapAccess<'de>,
{
let mut icons = Vec::new();
let mut icons = vec![];
while let Some((key, value)) = &map.next_entry::<String, String>()? {
icons.push(Icon {
name: Pattern::try_from(key.clone())

View file

@ -49,7 +49,7 @@ impl Completion {
let ticket = self.ticket;
tokio::spawn(async move {
let mut dir = fs::read_dir(&parent).await?;
let mut cache = Vec::new();
let mut cache = vec![];
while let Ok(Some(f)) = dir.next_entry().await {
let Ok(meta) = f.metadata().await else {
continue;

View file

@ -343,7 +343,7 @@ impl Files {
// --- Selected
pub fn selected(&self, pending: &BTreeSet<usize>, unset: bool) -> Vec<&File> {
if self.selected.is_empty() && (unset || pending.is_empty()) {
return Vec::new();
return vec![];
}
let selected: BTreeSet<_> = self.selected.iter().collect();

View file

@ -47,7 +47,7 @@ impl Manager {
tokio::spawn(async move {
done.extend(todo.iter().map(|f| (f.url(), None)));
if let Err(e) = isolate::preload("mime.lua".to_string(), todo, true).await {
if let Err(e) = isolate::preload("mime.lua", todo, true).await {
error!("preload in watcher failed: {e}");
}

View file

@ -136,7 +136,7 @@ impl Manager {
return Ok(());
}
let mut failed = Vec::new();
let mut failed = vec![];
for (o, n) in todo {
if fs::symlink_metadata(&n).await.is_ok() {
failed.push((o, n, anyhow!("Destination already exists")));

View file

@ -42,7 +42,7 @@ impl Manager {
fn handle_ioerr(&mut self, op: FilesOp) -> bool {
let url = op.url();
let op = FilesOp::Full(url.clone(), Vec::new());
let op = FilesOp::Full(url.clone(), vec![]);
if url == self.cwd() {
self.current_mut().update(op);

View file

@ -156,7 +156,7 @@ impl Watcher {
if reload.is_empty() {
continue;
}
if let Err(e) = isolate::preload("mime.lua".to_string(), reload, true).await {
if let Err(e) = isolate::preload("mime.lua", reload, true).await {
error!("preload in watcher failed: {e}");
}
}

View file

@ -30,7 +30,7 @@ impl App {
return Ok(());
}
let mut patches = Vec::new();
let mut patches = vec![];
for x in frame.area.left()..frame.area.right() {
for y in frame.area.top()..frame.area.bottom() {
let cell = frame.buffer.get(x, y);

View file

@ -1,7 +1,6 @@
local M = {}
function M:peek()
local limit = self.area.h
local child = Command("jq")
:args({
"-C",
@ -13,10 +12,17 @@ function M:peek()
:stderr(Command.PIPED)
:spawn()
if not child then
return self:fallback_to_builtin()
end
local limit = self.area.h
local i, lines = 0, ""
repeat
local code, next = child:read_line()
if code ~= 0 then
local next, event = child:read_line()
if event == 1 then
return self:fallback_to_builtin()
elseif event ~= 0 then
break
end
@ -46,4 +52,11 @@ function M:seek(units)
end
end
function M:fallback_to_builtin()
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
return M

View file

@ -1,35 +1,57 @@
local M = {}
function M:preload()
local command = Command("file"):arg("--mime-type"):stdout(Command.PIPED):stderr(Command.PIPED)
if ya.target_family() == "windows" then
command:arg("-b")
else
command:arg("-bL")
end
local urls = {}
for _, file in ipairs(self.files) do
urls[#urls + 1] = tostring(file.url)
end
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
mimes[urls[i]] = line
end
i = i + 1
local args
if ya.target_family() == "windows" then
args = { "-b", "--mime-type" }
else
args = { "-b", "-L", "--mime-type" }
end
if #mimes then
ya.manager_emit("update_mimetype", {}, mimes)
return 3
local child, code = Command("file"):args(args):args(urls):stdout(Command.PIPED):spawn()
if not child then
ya.err("spawn `file` command returns " .. tostring(code))
return 0
end
return 2
local mimes, last = {}, ya.time()
local flush = function(force)
if not force and ya.time() - last < 0.1 then
return
end
if next(mimes) then
ya.manager_emit("update_mimetype", {}, mimes)
mimes, last = {}, ya.time()
end
end
local i, j = 1, 0
repeat
local next, event = child:read_line_with { timeout = 100 }
if event == 3 then
flush(true)
goto continue
elseif event ~= 0 then
break
end
next = next:gsub("[\r\n]+$", "")
if ya.mime_valid(next) then
j, mimes[urls[i]] = j + 1, next
flush(false)
end
i = i + 1
::continue::
until i > #urls
flush(true)
return j == #urls and 3 or 2
end
return M

View file

@ -29,7 +29,9 @@ function M:preload()
:stderr(Command.PIPED)
:output()
if not output.status:success() then
if not output then
return 0
elseif not output.status:success() then
local pages = tonumber(output.stderr:match("the last page %((%d+)%)")) or 0
if self.skip > 0 and pages > 0 then
ya.manager_emit("peek", { tostring(math.max(0, pages - 1)), only_if = tostring(self.file.url), upper_bound = "" })

View file

@ -31,25 +31,27 @@ function M:preload()
return 1
end
local status = Command("ffmpegthumbnailer")
:args({
"-q",
"6",
"-c",
"jpeg",
"-i",
tostring(self.file.url),
"-o",
tostring(cache),
"-t",
tostring(percentage),
"-s",
tostring(PREVIEW.max_width),
})
:spawn()
:wait()
local child = Command("ffmpegthumbnailer"):args({
"-q",
"6",
"-c",
"jpeg",
"-i",
tostring(self.file.url),
"-o",
tostring(cache),
"-t",
tostring(percentage),
"-s",
tostring(PREVIEW.max_width),
}):spawn()
return status:success() and 1 or 2
if not child then
return 0
end
local status = child:wait()
return status and status:success() and 1 or 2
end
return M

View file

@ -32,13 +32,12 @@ impl<'a> TryFrom<Table<'a>> for Line {
impl Line {
pub fn install(lua: &Lua, ui: &Table) -> mlua::Result<()> {
#[inline]
fn new(value: Value) -> mlua::Result<Line> {
let new = lua.create_function(|_, (_, value): (Table, Value)| {
if let Value::Table(tbl) = value {
return Line::try_from(tbl);
}
Err("expected a table of Spans or Lines".into_lua_err())
}
})?;
let line = lua.create_table_from([
// Alignment
@ -47,10 +46,7 @@ impl Line {
("RIGHT", RIGHT.into_lua(lua)?),
])?;
line.set_metatable(Some(lua.create_table_from([(
"__call",
lua.create_function(|_, (_, value): (Table, Value)| new(value))?,
)])?));
line.set_metatable(Some(lua.create_table_from([("__call", new)])?));
ui.set("Line", line)
}

View file

@ -9,25 +9,21 @@ pub struct Rect;
impl Rect {
pub fn install(lua: &Lua, ui: &Table) -> mlua::Result<()> {
#[inline]
fn new<'a>(lua: &'a Lua, args: Table) -> mlua::Result<AnyUserData<'a>> {
let new = lua.create_function(|lua, (_, args): (Table, Table)| {
Rect::cast(lua, ratatui::layout::Rect {
x: args.get("x")?,
y: args.get("y")?,
width: args.get("w")?,
height: args.get("h")?,
})
}
})?;
let rect = lua.create_table_from([(
"default",
Rect::cast(lua, ratatui::layout::Rect::default())?.into_lua(lua)?,
)])?;
rect.set_metatable(Some(lua.create_table_from([(
"__call",
lua.create_function(|lua, (_, args): (Table, Table)| new(lua, args))?,
)])?));
rect.set_metatable(Some(lua.create_table_from([("__call", new)])?));
ui.set("Rect", rect)
}

View file

@ -6,7 +6,12 @@ pub struct Style(pub(super) ratatui::style::Style);
impl Style {
pub fn install(lua: &Lua, ui: &Table) -> mlua::Result<()> {
ui.set("Style", lua.create_function(|_, ()| Ok(Self::default()))?)
let new = lua.create_function(|_, ()| Ok(Self::default()))?;
let style = lua.create_table()?;
style.set_metatable(Some(lua.create_table_from([("__call", new)])?));
ui.set("Style", style)
}
}

View file

@ -73,7 +73,7 @@ mod parser {
pub(super) fn parse(cmd: &str, args: &[&str]) -> Vec<String> {
let mut it = cmd.chars().peekable();
let mut expanded = Vec::new();
let mut expanded = vec![];
while let Some(c) = it.next() {
if c.is_whitespace() {

View file

@ -6,12 +6,13 @@ use super::slim_lua;
use crate::{bindings::{Cast, File}, elements::Rect, LOADED};
pub async fn preload(
name: String,
name: &str,
files: Vec<yazi_shared::fs::File>,
multi: bool,
) -> mlua::Result<u8> {
LOADED.ensure(&name).await.into_lua_err()?;
LOADED.ensure(name).await.into_lua_err()?;
let name = name.to_owned();
tokio::task::spawn_blocking(move || {
let lua = slim_lua()?;
let plugin: Table = if let Some(b) = LOADED.read().get(&name) {

View file

@ -1,4 +1,6 @@
use mlua::{prelude::LuaUserDataMethods, UserData};
use std::time::Duration;
use mlua::{prelude::LuaUserDataMethods, IntoLua, Table, UserData, Value};
use tokio::{io::{AsyncBufReadExt, AsyncReadExt, BufReader}, process::{ChildStderr, ChildStdin, ChildStdout}, select};
use super::Status;
@ -21,27 +23,8 @@ impl Child {
impl UserData for Child {
fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) {
methods.add_async_method_mut("read", |_, me, ()| async move {
async fn read(t: Option<impl AsyncBufReadExt + Unpin>) -> Option<Vec<u8>> {
let Some(mut r) = t else {
return None;
};
let mut buf = vec![0; 4096];
match r.read(&mut buf).await {
Ok(0) | Err(_) => return None,
Ok(n) => buf.truncate(n),
}
Some(buf)
}
Ok(select! {
Some(r) = read(me.stdout.as_mut()) => (0u8, r),
Some(r) = read(me.stderr.as_mut()) => (1u8, r),
else => (2u8, Vec::new())
})
});
methods.add_async_method_mut("read_line", |_, me, ()| async move {
#[inline]
async fn read_line(me: &mut Child) -> (String, u8) {
async fn read(t: Option<impl AsyncBufReadExt + Unpin>) -> Option<String> {
let Some(mut r) = t else {
return None;
@ -54,15 +37,52 @@ impl UserData for Child {
}
}
select! {
Some(r) = read(me.stdout.as_mut()) => (r, 0u8),
Some(r) = read(me.stderr.as_mut()) => (r, 1u8),
else => (String::new(), 2u8),
}
}
methods.add_async_method_mut("read", |_, me, len: usize| async move {
async fn read(t: Option<impl AsyncBufReadExt + Unpin>, len: usize) -> Option<Vec<u8>> {
let Some(mut r) = t else {
return None;
};
let mut buf = vec![0; len];
match r.read(&mut buf).await {
Ok(0) | Err(_) => return None,
Ok(n) => buf.truncate(n),
}
Some(buf)
}
Ok(select! {
Some(r) = read(me.stdout.as_mut()) => (0u8, r),
Some(r) = read(me.stderr.as_mut()) => (1u8, r),
else => (2u8, String::new()),
Some(r) = read(me.stdout.as_mut(), len) => (r, 0u8),
Some(r) = read(me.stderr.as_mut(), len) => (r, 1u8),
else => (vec![], 2u8)
})
});
methods.add_async_method_mut("wait", |_, me, ()| async move {
Ok(Status::new(me.inner.wait().await?))
methods.add_async_method_mut("read_line", |_, me, ()| async move { Ok(read_line(me).await) });
methods.add_async_method_mut("read_line_with", |_, me, options: Table| async move {
let timeout: u64 = options.get("timeout")?;
match tokio::time::timeout(Duration::from_millis(timeout), read_line(me)).await {
Ok(value) => Ok(value),
Err(_) => Ok((String::new(), 3u8)),
}
});
methods.add_async_method_mut("wait", |lua, me, ()| async move {
Ok(match me.inner.wait().await {
Ok(status) => (Status::new(status).into_lua(lua)?, Value::Nil),
Err(e) => (Value::Nil, e.raw_os_error().into_lua(lua)?),
})
});
methods.add_method_mut("start_kill", |lua, me, ()| {
Ok(match me.inner.start_kill() {
Ok(_) => (true, Value::Nil),
Err(e) => (false, e.raw_os_error().into_lua(lua)?),
})
});
methods.add_method_mut("start_kill", |_, me, ()| Ok(me.inner.start_kill().is_ok()));
}
}

View file

@ -1,6 +1,6 @@
use std::process::Stdio;
use mlua::{prelude::LuaUserDataMethods, AnyUserData, IntoLua, Lua, Table, UserData};
use mlua::{prelude::LuaUserDataMethods, AnyUserData, IntoLua, Lua, Table, UserData, Value};
use super::{output::Output, Child};
@ -72,9 +72,17 @@ impl UserData for Command {
});
Ok(ud)
});
methods.add_method_mut("spawn", |_, me, ()| Ok(Child::new(me.inner.spawn()?)));
methods.add_async_method_mut("output", |_, me, ()| async move {
Ok(Output::new(me.inner.output().await?))
methods.add_method_mut("spawn", |lua, me, ()| {
Ok(match me.inner.spawn() {
Ok(child) => (Child::new(child).into_lua(lua)?, Value::Nil),
Err(e) => (Value::Nil, e.raw_os_error().into_lua(lua)?),
})
});
methods.add_async_method_mut("output", |lua, me, ()| async move {
Ok(match me.inner.output().await {
Ok(output) => (Output::new(output).into_lua(lua)?, Value::Nil),
Err(e) => (Value::Nil, e.raw_os_error().into_lua(lua)?),
})
});
}
}

View file

@ -8,7 +8,7 @@ use crate::ValueSendable;
impl Utils {
fn parse_args(t: Table) -> mlua::Result<(Vec<String>, BTreeMap<String, String>)> {
let mut args = Vec::new();
let mut args = vec![];
let mut named = BTreeMap::new();
for result in t.pairs::<Value, Value>() {
let (k, Value::String(v)) = result? else {

View file

@ -0,0 +1,14 @@
use mlua::{Lua, Table};
use tracing::{debug, error};
use super::Utils;
impl Utils {
pub(super) fn log(lua: &Lua, ya: &Table) -> mlua::Result<()> {
ya.set("dbg", lua.create_async_function(|_, s: String| async move { Ok(debug!("{s}")) })?)?;
ya.set("err", lua.create_async_function(|_, s: String| async move { Ok(error!("{s}")) })?)?;
Ok(())
}
}

View file

@ -3,10 +3,12 @@
mod cache;
mod call;
mod image;
mod log;
mod plugin;
mod preview;
mod target;
mod text;
mod time;
mod utils;
pub use preview::*;

View file

@ -0,0 +1,18 @@
use std::time::{SystemTime, UNIX_EPOCH};
use mlua::{Lua, Table};
use super::Utils;
impl Utils {
pub(super) fn time(lua: &Lua, ya: &Table) -> mlua::Result<()> {
ya.set(
"time",
lua.create_function(|_, ()| {
Ok(SystemTime::now().duration_since(UNIX_EPOCH).map(|d| d.as_secs_f64()).ok())
})?,
)?;
Ok(())
}
}

View file

@ -8,9 +8,11 @@ pub fn install(lua: &Lua) -> mlua::Result<()> {
Utils::cache(lua, &ya)?;
Utils::call(lua, &ya)?;
Utils::image(lua, &ya)?;
Utils::log(lua, &ya)?;
Utils::plugin(lua, &ya)?;
Utils::preview(lua, &ya)?;
Utils::target(lua, &ya)?;
Utils::time(lua, &ya)?;
Utils::text(lua, &ya)?;
lua.globals().set("ya", ya)

View file

@ -31,7 +31,7 @@ impl Preload {
match op {
PreloadOp::Rule(task) => {
let urls: Vec<_> = task.targets.iter().map(|f| f.url()).collect();
let result = isolate::preload(task.plugin.cmd, task.targets, task.plugin.multi).await;
let result = isolate::preload(&task.plugin.cmd, task.targets, task.plugin.multi).await;
if let Err(e) = result {
self.fail(task.id, format!("Preload task failed:\n{e}"))?;
return Err(e.into());
@ -39,7 +39,7 @@ impl Preload {
let code = result.unwrap();
if code & 1 == 0 {
error!("Preload task returned {code}");
error!("Preload task `{}` returned {code}", task.plugin.cmd);
}
if code >> 1 & 1 != 0 {
let mut loaded = self.rule_loaded.write();

View file

@ -73,8 +73,8 @@ impl<'de> Deserialize<'de> for Condition {
impl Condition {
fn build(expr: &str) -> Self {
let mut stack: Vec<ConditionOp> = Vec::new();
let mut output: Vec<ConditionOp> = Vec::new();
let mut stack: Vec<ConditionOp> = vec![];
let mut output: Vec<ConditionOp> = vec![];
let mut chars = expr.chars().peekable();
while let Some(token) = chars.next() {

View file

@ -41,7 +41,7 @@ impl FilesOp {
pub fn prepare(url: &Url) -> u64 {
let ticket = FILES_TICKET.fetch_add(1, Ordering::Relaxed);
Self::Part(url.clone(), Vec::new(), ticket).emit();
Self::Part(url.clone(), vec![], ticket).emit();
ticket
}