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; return Self::X11;
} }
if std::fs::symlink_metadata("/proc/sys/fs/binfmt_misc/WSLInterop").is_ok() { 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"); warn!("[Adaptor] Falling back to chafa");

View file

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

View file

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

View file

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

View file

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

View file

@ -49,7 +49,7 @@ impl Completion {
let ticket = self.ticket; let ticket = self.ticket;
tokio::spawn(async move { tokio::spawn(async move {
let mut dir = fs::read_dir(&parent).await?; 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 { while let Ok(Some(f)) = dir.next_entry().await {
let Ok(meta) = f.metadata().await else { let Ok(meta) = f.metadata().await else {
continue; continue;

View file

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

View file

@ -47,7 +47,7 @@ impl Manager {
tokio::spawn(async move { tokio::spawn(async move {
done.extend(todo.iter().map(|f| (f.url(), None))); 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}"); error!("preload in watcher failed: {e}");
} }

View file

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

View file

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

View file

@ -156,7 +156,7 @@ impl Watcher {
if reload.is_empty() { if reload.is_empty() {
continue; 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}"); error!("preload in watcher failed: {e}");
} }
} }

View file

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

View file

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

View file

@ -1,35 +1,57 @@
local M = {} local M = {}
function M:preload() 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 = {} local urls = {}
for _, file in ipairs(self.files) do for _, file in ipairs(self.files) do
urls[#urls + 1] = tostring(file.url) urls[#urls + 1] = tostring(file.url)
end end
local i, mimes = 1, {} local args
local output = command:args(urls):output() if ya.target_family() == "windows" then
for line in output.stdout:gmatch("[^\r\n]+") do args = { "-b", "--mime-type" }
if i > #urls then else
break args = { "-b", "-L", "--mime-type" }
end
if ya.mime_valid(line) then
mimes[urls[i]] = line
end
i = i + 1
end end
if #mimes then local child, code = Command("file"):args(args):args(urls):stdout(Command.PIPED):spawn()
ya.manager_emit("update_mimetype", {}, mimes) if not child then
return 3 ya.err("spawn `file` command returns " .. tostring(code))
return 0
end 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 end
return M return M

View file

@ -29,7 +29,9 @@ function M:preload()
:stderr(Command.PIPED) :stderr(Command.PIPED)
:output() :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 local pages = tonumber(output.stderr:match("the last page %((%d+)%)")) or 0
if self.skip > 0 and pages > 0 then 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 = "" }) 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 return 1
end end
local status = Command("ffmpegthumbnailer") local child = Command("ffmpegthumbnailer"):args({
:args({ "-q",
"-q", "6",
"6", "-c",
"-c", "jpeg",
"jpeg", "-i",
"-i", tostring(self.file.url),
tostring(self.file.url), "-o",
"-o", tostring(cache),
tostring(cache), "-t",
"-t", tostring(percentage),
tostring(percentage), "-s",
"-s", tostring(PREVIEW.max_width),
tostring(PREVIEW.max_width), }):spawn()
})
:spawn()
:wait()
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 end
return M return M

View file

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

View file

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

View file

@ -6,7 +6,12 @@ pub struct Style(pub(super) ratatui::style::Style);
impl Style { impl Style {
pub fn install(lua: &Lua, ui: &Table) -> mlua::Result<()> { 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> { pub(super) fn parse(cmd: &str, args: &[&str]) -> Vec<String> {
let mut it = cmd.chars().peekable(); let mut it = cmd.chars().peekable();
let mut expanded = Vec::new(); let mut expanded = vec![];
while let Some(c) = it.next() { while let Some(c) = it.next() {
if c.is_whitespace() { if c.is_whitespace() {

View file

@ -6,12 +6,13 @@ use super::slim_lua;
use crate::{bindings::{Cast, File}, elements::Rect, LOADED}; use crate::{bindings::{Cast, File}, elements::Rect, LOADED};
pub async fn preload( pub async fn preload(
name: String, name: &str,
files: Vec<yazi_shared::fs::File>, files: Vec<yazi_shared::fs::File>,
multi: bool, multi: bool,
) -> mlua::Result<u8> { ) -> 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 || { tokio::task::spawn_blocking(move || {
let lua = slim_lua()?; let lua = slim_lua()?;
let plugin: Table = if let Some(b) = LOADED.read().get(&name) { 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 tokio::{io::{AsyncBufReadExt, AsyncReadExt, BufReader}, process::{ChildStderr, ChildStdin, ChildStdout}, select};
use super::Status; use super::Status;
@ -21,27 +23,8 @@ impl Child {
impl UserData for Child { impl UserData for Child {
fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) { fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) {
methods.add_async_method_mut("read", |_, me, ()| async move { #[inline]
async fn read(t: Option<impl AsyncBufReadExt + Unpin>) -> Option<Vec<u8>> { async fn read_line(me: &mut Child) -> (String, 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 {
async fn read(t: Option<impl AsyncBufReadExt + Unpin>) -> Option<String> { async fn read(t: Option<impl AsyncBufReadExt + Unpin>) -> Option<String> {
let Some(mut r) = t else { let Some(mut r) = t else {
return None; 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! { Ok(select! {
Some(r) = read(me.stdout.as_mut()) => (0u8, r), Some(r) = read(me.stdout.as_mut(), len) => (r, 0u8),
Some(r) = read(me.stderr.as_mut()) => (1u8, r), Some(r) = read(me.stderr.as_mut(), len) => (r, 1u8),
else => (2u8, String::new()), else => (vec![], 2u8)
}) })
}); });
methods.add_async_method_mut("wait", |_, me, ()| async move { methods.add_async_method_mut("read_line", |_, me, ()| async move { Ok(read_line(me).await) });
Ok(Status::new(me.inner.wait().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 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}; use super::{output::Output, Child};
@ -72,9 +72,17 @@ impl UserData for Command {
}); });
Ok(ud) Ok(ud)
}); });
methods.add_method_mut("spawn", |_, me, ()| Ok(Child::new(me.inner.spawn()?))); methods.add_method_mut("spawn", |lua, me, ()| {
methods.add_async_method_mut("output", |_, me, ()| async move { Ok(match me.inner.spawn() {
Ok(Output::new(me.inner.output().await?)) 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 { impl Utils {
fn parse_args(t: Table) -> mlua::Result<(Vec<String>, BTreeMap<String, String>)> { 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(); let mut named = BTreeMap::new();
for result in t.pairs::<Value, Value>() { for result in t.pairs::<Value, Value>() {
let (k, Value::String(v)) = result? else { 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 cache;
mod call; mod call;
mod image; mod image;
mod log;
mod plugin; mod plugin;
mod preview; mod preview;
mod target; mod target;
mod text; mod text;
mod time;
mod utils; mod utils;
pub use preview::*; 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::cache(lua, &ya)?;
Utils::call(lua, &ya)?; Utils::call(lua, &ya)?;
Utils::image(lua, &ya)?; Utils::image(lua, &ya)?;
Utils::log(lua, &ya)?;
Utils::plugin(lua, &ya)?; Utils::plugin(lua, &ya)?;
Utils::preview(lua, &ya)?; Utils::preview(lua, &ya)?;
Utils::target(lua, &ya)?; Utils::target(lua, &ya)?;
Utils::time(lua, &ya)?;
Utils::text(lua, &ya)?; Utils::text(lua, &ya)?;
lua.globals().set("ya", ya) lua.globals().set("ya", ya)

View file

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

View file

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

View file

@ -41,7 +41,7 @@ impl FilesOp {
pub fn prepare(url: &Url) -> u64 { pub fn prepare(url: &Url) -> u64 {
let ticket = FILES_TICKET.fetch_add(1, Ordering::Relaxed); 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 ticket
} }