From 9c3d5cc40005eda4bc79485bc4dc6fd4fb5119c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=89=E5=92=B2=E9=9B=85=20=C2=B7=20Misaki=20Masa?= Date: Tue, 5 Sep 2023 19:32:53 +0800 Subject: [PATCH] refactor: use `Url` instead of `PathBuf` (#107) --- app/src/app.rs | 11 ++- app/src/context.rs | 2 +- app/src/executor.rs | 13 ++- app/src/header/layout.rs | 2 +- app/src/manager/folder.rs | 10 +-- app/src/manager/preview.rs | 2 +- core/src/event.rs | 14 +-- core/src/external/fd.rs | 8 +- core/src/external/file.rs | 14 +-- core/src/external/fzf.rs | 7 +- core/src/external/pdftoppm.rs | 4 +- core/src/external/rg.rs | 8 +- core/src/external/zoxide.rs | 12 +-- core/src/files/file.rs | 37 ++++---- core/src/files/files.rs | 52 +++++------ core/src/files/op.rs | 25 +++--- core/src/files/sorter.rs | 8 +- core/src/manager/folder.rs | 42 ++++----- core/src/manager/manager.rs | 117 ++++++++++-------------- core/src/manager/preview/preview.rs | 128 ++++++++++++++++++--------- core/src/manager/preview/provider.rs | 16 +--- core/src/manager/tab.rs | 118 ++++++++++++------------ core/src/manager/tabs.rs | 9 +- core/src/manager/watcher.rs | 50 +++++------ core/src/tasks/scheduler.rs | 22 ++--- core/src/tasks/tasks.rs | 50 +++++------ core/src/tasks/workers/file.rs | 22 ++--- core/src/tasks/workers/precache.rs | 28 +++--- shared/src/fns.rs | 10 ++- shared/src/lib.rs | 2 + shared/src/mime.rs | 5 +- shared/src/url.rs | 100 +++++++++++++++++++++ 32 files changed, 532 insertions(+), 416 deletions(-) create mode 100644 shared/src/url.rs diff --git a/app/src/app.rs b/app/src/app.rs index baf8c08b..ab83a0ab 100644 --- a/app/src/app.rs +++ b/app/src/app.rs @@ -4,7 +4,7 @@ use std::ffi::OsString; use anyhow::{Ok, Result}; use config::{keymap::{Control, Key, KeymapLayer}, BOOT}; use crossterm::event::KeyEvent; -use shared::{absolute_path, Term}; +use shared::{absolute_url, Term}; use tokio::sync::oneshot; use crate::{Ctx, Executor, Logs, Root, Signals}; @@ -119,26 +119,25 @@ impl App { let manager = &mut self.cx.manager; let tasks = &mut self.cx.tasks; match event { - Event::Cd(path) => { + Event::Cd(url) => { futures::executor::block_on(async { - manager.active_mut().cd(absolute_path(path)).await; + manager.active_mut().cd(absolute_url(url)).await; }); } Event::Refresh => { manager.refresh(); } Event::Files(op) => { - let calc = matches!(op, FilesOp::Read(..) | FilesOp::Search(..)); + let read = matches!(op, FilesOp::Read(..)); let b = match op { FilesOp::Read(..) => manager.update_read(op), FilesOp::Size(..) => manager.update_read(op), - FilesOp::Search(..) => manager.update_search(op), FilesOp::IOErr(..) => manager.update_ioerr(op), }; if b { emit!(Render); } - if calc { + if read { tasks.precache_size(&manager.current().files); } } diff --git a/app/src/context.rs b/app/src/context.rs index 83e8dcd5..aab59192 100644 --- a/app/src/context.rs +++ b/app/src/context.rs @@ -38,7 +38,7 @@ impl Ctx { } Position::Hovered(rect @ Rect { mut x, y, width, height }) => { let Some(r) = - self.manager.hovered().and_then(|h| self.manager.current().rect_current(h.path())) + self.manager.hovered().and_then(|h| self.manager.current().rect_current(h.url())) else { return self.area(&Position::Top(*rect)); }; diff --git a/app/src/executor.rs b/app/src/executor.rs index f1b0f3b1..7a350a00 100644 --- a/app/src/executor.rs +++ b/app/src/executor.rs @@ -1,8 +1,7 @@ use core::{emit, files::FilesSorter, input::InputMode}; -use std::path::PathBuf; use config::{keymap::{Control, Exec, Key, KeymapLayer}, manager::SortBy, KEYMAP}; -use shared::optional_bool; +use shared::{optional_bool, Url}; use super::Ctx; @@ -74,11 +73,11 @@ impl Executor { "back" => cx.manager.active_mut().back(), "forward" => cx.manager.active_mut().forward(), "cd" => { - let path = exec.args.get(0).map(PathBuf::from).unwrap_or_default(); + let url = exec.args.get(0).map(Url::from).unwrap_or_default(); if exec.named.contains_key("interactive") { - cx.manager.active_mut().cd_interactive(path) + cx.manager.active_mut().cd_interactive(url) } else { - emit!(Cd(path)); + emit!(Cd(url)); false } } @@ -109,7 +108,7 @@ impl Executor { } } "remove" => { - let targets = cx.manager.selected().into_iter().map(|f| f.path_owned()).collect(); + let targets = cx.manager.selected().into_iter().map(|f| f.url_owned()).collect(); cx.tasks.file_remove(targets, exec.named.contains_key("permanently")) } "create" => cx.manager.create(), @@ -155,7 +154,7 @@ impl Executor { let path = if exec.named.contains_key("current") { cx.manager.cwd().to_owned() } else { - exec.args.get(0).map(|p| p.into()).unwrap_or("/".into()) + exec.args.get(0).map(Url::from).unwrap_or_else(|| Url::from("/")) }; cx.manager.tabs_mut().create(&path) } diff --git a/app/src/header/layout.rs b/app/src/header/layout.rs index a704bf4f..a98ddec8 100644 --- a/app/src/header/layout.rs +++ b/app/src/header/layout.rs @@ -20,7 +20,7 @@ impl<'a> Widget for Layout<'a> { .split(area); let current = &self.cx.manager.current(); - let location = if current.in_search { + let location = if current.cwd.is_search() { format!("{} (search)", readable_home(¤t.cwd)) } else { readable_home(¤t.cwd) diff --git a/app/src/manager/folder.rs b/app/src/manager/folder.rs index 0d1fc104..99290990 100644 --- a/app/src/manager/folder.rs +++ b/app/src/manager/folder.rs @@ -36,7 +36,7 @@ impl<'a> Folder<'a> { THEME .filetypes .iter() - .find(|x| x.matches(file.path(), mimetype.get(file.path()), file.is_dir())) + .find(|x| x.matches(file.url(), mimetype.get(file.url()), file.is_dir())) .map(|x| x.style.get()) .unwrap_or_else(Style::new) } @@ -60,11 +60,11 @@ impl<'a> Widget for Folder<'a> { let icon = THEME .icons .iter() - .find(|x| x.name.match_path(f.path(), Some(f.is_dir()))) + .find(|x| x.name.match_path(f.url(), Some(f.is_dir()))) .map(|x| x.display.as_ref()) .unwrap_or(""); - let is_selected = self.folder.files.is_selected(f.path()); + let is_selected = self.folder.files.is_selected(f.url()); if (!self.is_selection && is_selected) || (self.is_selection && mode.pending(self.folder.offset() + i, is_selected)) { @@ -78,7 +78,7 @@ impl<'a> Widget for Folder<'a> { ); } - let hovered = matches!(self.folder.hovered, Some(ref h) if h.path() == f.path()); + let hovered = matches!(self.folder.hovered, Some(ref h) if h.url() == f.url()); let style = if self.is_preview && hovered { THEME.preview.hovered.get() } else if hovered { @@ -87,7 +87,7 @@ impl<'a> Widget for Folder<'a> { self.file_style(f) }; - let mut path = format!(" {icon} {}", readable_path(f.path(), &self.folder.cwd)); + let mut path = format!(" {icon} {}", readable_path(f.url(), &self.folder.cwd)); if let Some(link_to) = f.link_to() { if MANAGER.show_symlink { path.push_str(&format!(" -> {}", link_to.display())); diff --git a/app/src/manager/preview.rs b/app/src/manager/preview.rs index 4890ec34..c75df174 100644 --- a/app/src/manager/preview.rs +++ b/app/src/manager/preview.rs @@ -17,7 +17,7 @@ impl<'a> Preview<'a> { impl<'a> Widget for Preview<'a> { fn render(self, area: Rect, buf: &mut Buffer) { let manager = &self.cx.manager; - let Some(hovered) = manager.hovered().map(|h| h.path()) else { + let Some(hovered) = manager.hovered().map(|h| h.url()) else { return; }; diff --git a/core/src/event.rs b/core/src/event.rs index fd53cd0f..97039857 100644 --- a/core/src/event.rs +++ b/core/src/event.rs @@ -1,9 +1,9 @@ -use std::{collections::BTreeMap, ffi::OsString, path::PathBuf}; +use std::{collections::BTreeMap, ffi::OsString}; use anyhow::Result; use config::{keymap::{Control, KeymapLayer}, open::Opener}; use crossterm::event::KeyEvent; -use shared::RoCell; +use shared::{RoCell, Url}; use tokio::sync::{mpsc::UnboundedSender, oneshot}; use super::{files::{File, FilesOp}, input::InputOpt, select::SelectOpt}; @@ -21,13 +21,13 @@ pub enum Event { Ctrl(Control, KeymapLayer), // Manager - Cd(PathBuf), + Cd(Url), Refresh, Files(FilesOp), Pages(usize), - Mimetype(BTreeMap), + Mimetype(BTreeMap), Hover(Option), - Peek(usize, Option), + Peek(usize, Option), Preview(PreviewLock), // Input @@ -92,8 +92,8 @@ macro_rules! emit { (Peek) => { $crate::Event::Peek(0, None).emit(); }; - (Peek($skip:expr, $path:expr)) => { - $crate::Event::Peek($skip, Some($path)).emit(); + (Peek($skip:expr, $url:expr)) => { + $crate::Event::Peek($skip, Some($url)).emit(); }; (Preview($lock:expr)) => { $crate::Event::Preview($lock).emit(); diff --git a/core/src/external/fd.rs b/core/src/external/fd.rs index b93baaf6..ba302f69 100644 --- a/core/src/external/fd.rs +++ b/core/src/external/fd.rs @@ -1,18 +1,18 @@ -use std::{path::PathBuf, process::Stdio, time::Duration}; +use std::{process::Stdio, time::Duration}; use anyhow::Result; -use shared::StreamBuf; +use shared::{StreamBuf, Url}; use tokio::{io::{AsyncBufReadExt, BufReader}, process::Command, sync::mpsc}; use tokio_stream::wrappers::UnboundedReceiverStream; pub struct FdOpt { - pub cwd: PathBuf, + pub cwd: Url, pub hidden: bool, pub glob: bool, pub subject: String, } -pub fn fd(opt: FdOpt) -> Result>> { +pub fn fd(opt: FdOpt) -> Result>> { let mut child = Command::new("fd") .arg("--base-directory") .arg(&opt.cwd) diff --git a/core/src/external/file.rs b/core/src/external/file.rs index d0c0cd1a..c48891f5 100644 --- a/core/src/external/file.rs +++ b/core/src/external/file.rs @@ -1,12 +1,12 @@ -use std::{collections::BTreeMap, ffi::OsStr, path::PathBuf}; +use std::collections::BTreeMap; use anyhow::{bail, Result}; use futures::TryFutureExt; -use shared::MimeKind; +use shared::{MimeKind, Url}; use tokio::process::Command; use tracing::error; -pub async fn file(files: &[impl AsRef]) -> Result> { +async fn _file(files: &[&Url]) -> Result> { if files.is_empty() { bail!("no files to get mime types for"); } @@ -25,12 +25,16 @@ pub async fn file(files: &[impl AsRef]) -> Result>()); + error!("failed to get mime types: {:?}", files); bail!("failed to get mime types"); } Ok(mimes) } + +pub async fn file(files: &[impl AsRef]) -> Result> { + _file(&files.iter().map(AsRef::as_ref).collect::>()).await +} diff --git a/core/src/external/fzf.rs b/core/src/external/fzf.rs index aec3659d..2c9dba77 100644 --- a/core/src/external/fzf.rs +++ b/core/src/external/fzf.rs @@ -1,13 +1,14 @@ -use std::{path::PathBuf, process::Stdio}; +use std::process::Stdio; use anyhow::Result; +use shared::Url; use tokio::{process::Command, sync::oneshot::{self, Receiver}}; pub struct FzfOpt { - pub cwd: PathBuf, + pub cwd: Url, } -pub fn fzf(opt: FzfOpt) -> Result>> { +pub fn fzf(opt: FzfOpt) -> Result>> { let child = Command::new("fzf").current_dir(&opt.cwd).kill_on_drop(true).stdout(Stdio::piped()).spawn()?; diff --git a/core/src/external/pdftoppm.rs b/core/src/external/pdftoppm.rs index ded40197..38b1ad86 100644 --- a/core/src/external/pdftoppm.rs +++ b/core/src/external/pdftoppm.rs @@ -1,4 +1,4 @@ -use std::path::Path; +use std::{path::Path, sync::Arc}; use adaptor::Image; use regex::Regex; @@ -25,5 +25,5 @@ pub async fn pdftoppm(src: &Path, dest: impl AsRef, skip: usize) -> Result return if pages > 0 { Err(PeekError::Exceed(pages - 1)) } else { Err(s.to_string().into()) }; } - Ok(Image::precache_anyway(output.stdout.into(), dest).await?) + Ok(Image::precache_anyway(Arc::new(output.stdout), dest).await?) } diff --git a/core/src/external/rg.rs b/core/src/external/rg.rs index 41bd4ca2..9e3a5fb1 100644 --- a/core/src/external/rg.rs +++ b/core/src/external/rg.rs @@ -1,17 +1,17 @@ -use std::{path::PathBuf, process::Stdio, time::Duration}; +use std::{process::Stdio, time::Duration}; use anyhow::Result; -use shared::StreamBuf; +use shared::{StreamBuf, Url}; use tokio::{io::{AsyncBufReadExt, BufReader}, process::Command, sync::mpsc}; use tokio_stream::wrappers::UnboundedReceiverStream; pub struct RgOpt { - pub cwd: PathBuf, + pub cwd: Url, pub hidden: bool, pub subject: String, } -pub fn rg(opt: RgOpt) -> Result>> { +pub fn rg(opt: RgOpt) -> Result>> { let mut child = Command::new("rg") .current_dir(&opt.cwd) .args(["--color=never", "--files-with-matches", "--smart-case"]) diff --git a/core/src/external/zoxide.rs b/core/src/external/zoxide.rs index fbd57671..ae46e07d 100644 --- a/core/src/external/zoxide.rs +++ b/core/src/external/zoxide.rs @@ -1,26 +1,28 @@ -use std::{path::PathBuf, process::Stdio}; +use std::process::Stdio; use anyhow::Result; +use shared::Url; use tokio::{process::Command, sync::oneshot::{self, Receiver}}; pub struct ZoxideOpt { - pub cwd: PathBuf, + pub cwd: Url, } -pub fn zoxide(opt: ZoxideOpt) -> Result>> { +pub fn zoxide(opt: ZoxideOpt) -> Result>> { let child = Command::new("zoxide") .args(["query", "-i", "--exclude"]) - .arg(opt.cwd) + .arg(&opt.cwd) .kill_on_drop(true) .stdout(Stdio::piped()) .spawn()?; let (tx, rx) = oneshot::channel(); + let cwd = opt.cwd.clone(); tokio::spawn(async move { if let Ok(output) = child.wait_with_output().await { let selected = String::from_utf8_lossy(&output.stdout).trim().to_string(); if !selected.is_empty() { - tx.send(Ok(selected.into())).ok(); + tx.send(Ok(Url::new(selected, &cwd))).ok(); return; } } diff --git a/core/src/files/file.rs b/core/src/files/file.rs index 355e932d..abfdbf38 100644 --- a/core/src/files/file.rs +++ b/core/src/files/file.rs @@ -1,11 +1,12 @@ -use std::{borrow::Cow, ffi::OsStr, fs::Metadata, path::{Path, PathBuf}}; +use std::{borrow::Cow, ffi::OsStr, fs::Metadata, path::PathBuf}; use anyhow::Result; +use shared::Url; use tokio::fs; #[derive(Clone, Debug)] pub struct File { - pub(super) path: PathBuf, + pub(super) url: Url, pub(super) meta: Metadata, pub(super) length: Option, pub(super) link_to: Option, @@ -15,53 +16,53 @@ pub struct File { impl File { #[inline] - pub async fn from(path: &Path) -> Result { - let meta = fs::metadata(path).await?; - Ok(Self::from_meta(path, meta).await) + pub async fn from(url: Url) -> Result { + let meta = fs::metadata(&url).await?; + Ok(Self::from_meta(url, meta).await) } - pub async fn from_meta(path: &Path, mut meta: Metadata) -> Self { + pub async fn from_meta(url: Url, mut meta: Metadata) -> Self { let is_link = meta.is_symlink(); let mut link_to = None; if is_link { - meta = fs::metadata(&path).await.unwrap_or(meta); - link_to = fs::read_link(&path).await.ok(); + meta = fs::metadata(&url).await.unwrap_or(meta); + link_to = fs::read_link(&url).await.ok(); } let length = if meta.is_dir() { None } else { Some(meta.len()) }; - let is_hidden = path.file_name().map(|s| s.to_string_lossy().starts_with('.')).unwrap_or(false); - Self { path: path.to_path_buf(), meta, length, link_to, is_link, is_hidden } + let is_hidden = url.file_name().map(|s| s.to_string_lossy().starts_with('.')).unwrap_or(false); + Self { url, meta, length, link_to, is_link, is_hidden } } } impl File { // --- Path #[inline] - pub fn path(&self) -> &PathBuf { &self.path } + pub fn url(&self) -> &Url { &self.url } #[inline] - pub fn set_path(&mut self, path: PathBuf) { self.path = path; } + pub fn set_url(&mut self, url: Url) { self.url = url; } #[inline] - pub fn path_owned(&self) -> PathBuf { self.path.clone() } + pub fn url_owned(&self) -> Url { self.url.clone() } #[inline] - pub fn path_os_str(&self) -> &OsStr { self.path.as_os_str() } + pub fn url_os_str(&self) -> &OsStr { self.url.as_os_str() } #[inline] - pub fn name(&self) -> Option<&OsStr> { self.path.file_name() } + pub fn name(&self) -> Option<&OsStr> { self.url.file_name() } #[inline] pub fn name_display(&self) -> Option> { - self.path.file_name().map(|s| s.to_string_lossy()) + self.url.file_name().map(|s| s.to_string_lossy()) } #[inline] - pub fn stem(&self) -> Option<&OsStr> { self.path.file_stem() } + pub fn stem(&self) -> Option<&OsStr> { self.url.file_stem() } #[inline] - pub fn parent(&self) -> Option<&Path> { self.path.parent() } + pub fn parent(&self) -> Option { self.url.parent_url() } // --- Meta #[inline] diff --git a/core/src/files/files.rs b/core/src/files/files.rs index 69d0ce1d..831c3ec3 100644 --- a/core/src/files/files.rs +++ b/core/src/files/files.rs @@ -1,7 +1,8 @@ -use std::{collections::{BTreeMap, BTreeSet}, mem, ops::Deref, path::{Path, PathBuf}}; +use std::{collections::{BTreeMap, BTreeSet}, mem, ops::Deref}; use anyhow::Result; use config::manager::SortBy; +use shared::Url; use tokio::fs; use super::{File, FilesSorter}; @@ -10,8 +11,8 @@ pub struct Files { items: Vec, hidden: Vec, - sizes: BTreeMap, - selected: BTreeSet, + sizes: BTreeMap, + selected: BTreeSet, sorter: FilesSorter, show_hidden: bool, @@ -39,22 +40,22 @@ impl Deref for Files { } impl Files { - pub async fn read(paths: &[impl AsRef]) -> Vec { - let mut items = Vec::with_capacity(paths.len()); - for path in paths { - if let Ok(file) = File::from(path.as_ref()).await { + pub async fn read(urls: Vec) -> Vec { + let mut items = Vec::with_capacity(urls.len()); + for url in urls { + if let Ok(file) = File::from(url).await { items.push(file); } } items } - pub async fn read_dir(path: &Path) -> Result> { - let mut it = fs::read_dir(path).await?; + pub async fn read_dir(url: &Url) -> Result> { + let mut it = fs::read_dir(url).await?; let mut items = Vec::new(); while let Ok(Some(item)) = it.next_entry().await { if let Ok(meta) = item.metadata().await { - items.push(File::from_meta(&item.path(), meta).await); + items.push(File::from_meta(Url::new(item.path(), url), meta).await); } } Ok(items) @@ -63,8 +64,8 @@ impl Files { impl Files { #[inline] - pub fn select(&mut self, path: &Path, state: Option) -> bool { - let old = self.selected.contains(path); + pub fn select(&mut self, url: &Url, state: Option) -> bool { + let old = self.selected.contains(url); let new = if let Some(new) = state { new } else { !old }; if new == old { @@ -72,9 +73,9 @@ impl Files { } if new { - self.selected.insert(path.to_owned()); + self.selected.insert(url.to_owned()); } else { - self.selected.remove(path); + self.selected.remove(url); } true } @@ -82,17 +83,17 @@ impl Files { pub fn select_all(&mut self, state: Option) -> bool { match state { Some(true) => { - self.selected = self.iter().map(|f| f.path_owned()).collect(); + self.selected = self.iter().map(|f| f.url_owned()).collect(); } Some(false) => { self.selected.clear(); } None => { for item in &self.items { - if self.selected.contains(&item.path) { - self.selected.remove(&item.path); + if self.selected.contains(&item.url) { + self.selected.remove(&item.url); } else { - self.selected.insert(item.path_owned()); + self.selected.insert(item.url_owned()); } } } @@ -102,7 +103,7 @@ impl Files { pub fn select_index(&mut self, indices: &BTreeSet, state: Option) -> bool { let mut applied = false; - let paths: Vec<_> = self.pick(indices).iter().map(|f| f.path_owned()).collect(); + let paths: Vec<_> = self.pick(indices).iter().map(|f| f.url_owned()).collect(); for path in paths { applied |= self.select(&path, state); @@ -119,7 +120,7 @@ impl Files { true } - pub fn update_size(&mut self, items: BTreeMap) -> bool { + pub fn update_size(&mut self, items: BTreeMap) -> bool { self.sizes.extend(items); if self.sorter.by == SortBy::Size { self.sorter.sort(&mut self.items); @@ -127,6 +128,7 @@ impl Files { true } + // TODO: remove this pub fn update_search(&mut self, items: Vec) -> bool { if !items.is_empty() { if self.show_hidden { @@ -164,7 +166,7 @@ impl Files { } #[inline] - pub fn position(&self, path: &Path) -> Option { self.iter().position(|f| f.path == path) } + pub fn position(&self, url: &Url) -> Option { self.iter().position(|f| f.url == *url) } #[inline] pub fn duplicate(&self, idx: usize) -> Option { self.items.get(idx).cloned() } @@ -177,7 +179,7 @@ impl Files { let selected: BTreeSet<_> = self.selected.iter().collect(); let pending: BTreeSet<_> = - pending.iter().filter_map(|&i| self.items.get(i)).map(|f| &f.path).collect(); + pending.iter().filter_map(|&i| self.items.get(i)).map(|f| &f.url).collect(); let selected: BTreeSet<_> = if unset { selected.difference(&pending).cloned().collect() @@ -187,7 +189,7 @@ impl Files { let mut items = Vec::with_capacity(selected.len()); for item in &self.items { - if selected.contains(&item.path) { + if selected.contains(&item.url) { items.push(item); } if items.len() == selected.len() { @@ -198,14 +200,14 @@ impl Files { } #[inline] - pub fn is_selected(&self, path: &Path) -> bool { self.selected.contains(path) } + pub fn is_selected(&self, url: &Url) -> bool { self.selected.contains(url) } #[inline] pub fn has_selected(&self) -> bool { if self.selected.is_empty() { return false; } - self.iter().any(|f| self.selected.contains(&f.path)) + self.iter().any(|f| self.selected.contains(&f.url)) } // --- Sorter diff --git a/core/src/files/op.rs b/core/src/files/op.rs index c9208162..4d3723c0 100644 --- a/core/src/files/op.rs +++ b/core/src/files/op.rs @@ -1,30 +1,27 @@ -use std::{collections::BTreeMap, path::{Path, PathBuf}}; +use std::collections::BTreeMap; + +use shared::Url; use super::File; #[derive(Debug)] pub enum FilesOp { - Read(PathBuf, Vec), - Size(PathBuf, BTreeMap), - Search(PathBuf, Vec), - IOErr(PathBuf), + Read(Url, Vec), + Size(Url, BTreeMap), + IOErr(Url), } impl FilesOp { #[inline] - pub fn path(&self) -> PathBuf { + pub fn url(&self) -> Url { match self { - Self::Read(path, _) => path, - Self::Size(path, _) => path, - Self::Search(path, _) => path, - Self::IOErr(path) => path, + Self::Read(url, _) => url, + Self::Size(url, _) => url, + Self::IOErr(url) => url, } .clone() } #[inline] - pub fn read_empty(path: &Path) -> Self { Self::Read(path.to_path_buf(), Vec::new()) } - - #[inline] - pub fn search_empty(path: &Path) -> Self { Self::Search(path.to_path_buf(), Vec::new()) } + pub fn clear(url: &Url) -> Self { Self::Read(url.clone(), Vec::new()) } } diff --git a/core/src/files/sorter.rs b/core/src/files/sorter.rs index f1404122..b9f7789e 100644 --- a/core/src/files/sorter.rs +++ b/core/src/files/sorter.rs @@ -1,4 +1,4 @@ -use std::{cmp::Ordering, mem, path::PathBuf}; +use std::{cmp::Ordering, mem}; use config::{manager::SortBy, MANAGER}; @@ -29,7 +29,7 @@ impl FilesSorter { match self.by { SortBy::Alphabetical => { - items.sort_unstable_by(|a, b| self.cmp(&a.path, &b.path, self.promote(a, b))) + items.sort_unstable_by(|a, b| self.cmp(&*a.url, &*b.url, self.promote(a, b))) } SortBy::Created => items.sort_unstable_by(|a, b| { if let (Ok(aa), Ok(bb)) = (a.meta.created(), b.meta.created()) { @@ -56,7 +56,7 @@ impl FilesSorter { let mut entities = Vec::with_capacity(items.len()); for (i, file) in items.iter().enumerate() { indices.push(i); - entities.push((file.path.to_string_lossy(), file)); + entities.push((file.url.to_string_lossy(), file)); } indices.sort_unstable_by(|&a, &b| { @@ -71,7 +71,7 @@ impl FilesSorter { }); let dummy = File { - path: PathBuf::new(), + url: Default::default(), meta: items[0].meta.clone(), length: None, link_to: None, diff --git a/core/src/manager/folder.rs b/core/src/manager/folder.rs index d94d6b38..d9bb6166 100644 --- a/core/src/manager/folder.rs +++ b/core/src/manager/folder.rs @@ -1,34 +1,34 @@ -use std::path::{Path, PathBuf}; - use config::MANAGER; use ratatui::layout::Rect; +use shared::Url; use crate::{emit, files::{File, Files, FilesOp}}; #[derive(Default)] pub struct Folder { - pub cwd: PathBuf, + pub cwd: Url, pub files: Files, - offset: usize, - cursor: usize, - pub page: usize, - pub hovered: Option, - pub in_search: bool, + offset: usize, + cursor: usize, + + pub page: usize, + pub hovered: Option, +} + +impl From for Folder { + fn from(cwd: Url) -> Self { Self { cwd, ..Default::default() } } +} + +impl From<&Url> for Folder { + fn from(cwd: &Url) -> Self { Self::from(cwd.clone()) } } impl Folder { - pub fn new(cwd: &Path) -> Self { Self { cwd: cwd.to_path_buf(), ..Default::default() } } - - pub fn new_search(cwd: &Path) -> Self { - Self { cwd: cwd.to_path_buf(), in_search: true, ..Default::default() } - } - pub fn update(&mut self, op: FilesOp) -> bool { let b = match op { FilesOp::Read(_, items) => self.files.update_read(items), FilesOp::Size(_, items) => self.files.update_size(items), - FilesOp::Search(_, items) => self.files.update_search(items), _ => unreachable!(), }; if !b { @@ -102,18 +102,18 @@ impl Folder { &self.files[start..end] } - pub fn hover(&mut self, path: &Path) -> bool { - let new = self.files.position(path).unwrap_or(self.cursor); + pub fn hover(&mut self, url: &Url) -> bool { + let new = self.files.position(url).unwrap_or(self.cursor); if new > self.cursor { self.next(new - self.cursor) } else { self.prev(self.cursor - new) } } #[inline] pub fn hover_repos(&mut self) -> bool { - self.hover(&self.hovered.as_ref().map(|h| h.path_owned()).unwrap_or_default()) + self.hover(&self.hovered.as_ref().map(|h| h.url_owned()).unwrap_or_default()) } pub fn hover_force(&mut self, file: File) -> bool { - if self.hover(file.path()) { + if self.hover(file.url()) { return true; } @@ -138,8 +138,8 @@ impl Folder { &self.files[start..end] } - pub fn rect_current(&self, path: &Path) -> Option { - let y = self.files.position(path)? - self.offset; + pub fn rect_current(&self, url: &Url) -> Option { + let y = self.files.position(url)? - self.offset; let mut rect = MANAGER.layout.folder_rect(); rect.y = rect.y.saturating_sub(1) + y as u16; diff --git a/core/src/manager/manager.rs b/core/src/manager/manager.rs index 35808d6a..779024d5 100644 --- a/core/src/manager/manager.rs +++ b/core/src/manager/manager.rs @@ -1,8 +1,8 @@ -use std::{collections::{BTreeMap, BTreeSet, HashMap, HashSet}, env, ffi::OsStr, io::{stdout, BufWriter, Write}, mem, path::{Path, PathBuf}}; +use std::{collections::{BTreeMap, BTreeSet, HashMap, HashSet}, env, ffi::OsStr, io::{stdout, BufWriter, Write}, path::PathBuf}; use anyhow::{anyhow, bail, Error, Result}; use config::{OPEN, PREVIEW}; -use shared::{max_common_root, Defer, Term, MIME_DIR}; +use shared::{max_common_root, Defer, Term, Url, MIME_DIR}; use tokio::{fs::{self, OpenOptions}, io::{stdin, AsyncReadExt, AsyncWriteExt}}; use super::{Tab, Tabs, Watcher}; @@ -10,10 +10,10 @@ use crate::{emit, external::{self, ShellOpt}, files::{File, FilesOp}, input::Inp pub struct Manager { tabs: Tabs, - yanked: (bool, HashSet), + yanked: (bool, HashSet), watcher: Watcher, - pub mimetype: HashMap, + pub mimetype: HashMap, } impl Manager { @@ -44,7 +44,7 @@ impl Manager { to_watch.insert(&tab.current.cwd); if let Some(ref h) = tab.current.hovered { if h.is_dir() { - to_watch.insert(h.path()); + to_watch.insert(h.url()); } } if let Some(ref p) = tab.parent { @@ -56,20 +56,23 @@ impl Manager { pub fn peek(&mut self, sequent: bool, show_image: bool) -> bool { let Some(hovered) = self.hovered().cloned() else { - return self.active_mut().preview.reset(); + return self.active_mut().preview_reset(); }; + let url = hovered.url(); if !show_image { self.active_mut().preview_reset_image(); } - let mime = if hovered.is_dir() { - MIME_DIR.to_owned() - } else if let Some(m) = self.mimetype.get(hovered.path()).cloned() { - m - } else { + if hovered.is_dir() { + let len = self.active().history(url).map(|f| f.files.len()); + self.active_mut().preview.folder(url, len, sequent); + return false; + } + + let Some(mime) = self.mimetype.get(url).cloned() else { tokio::spawn(async move { - if let Ok(mimes) = external::file(&[hovered.path()]).await { + if let Ok(mimes) = external::file(&[hovered.url()]).await { emit!(Mimetype(mimes)); } }); @@ -77,16 +80,16 @@ impl Manager { }; if sequent { - self.active_mut().preview.sequent(hovered.path(), &mime, show_image); + self.active_mut().preview.sequent(url, &mime, show_image); } else { - self.active_mut().preview.go(hovered.path(), &mime, show_image); + self.active_mut().preview.go(url, &mime, show_image); } false } pub fn yank(&mut self, cut: bool) -> bool { self.yanked.0 = cut; - self.yanked.1 = self.selected().into_iter().map(|f| f.path_owned()).collect(); + self.yanked.1 = self.selected().into_iter().map(|f| f.url_owned()).collect(); false } @@ -124,8 +127,8 @@ impl Manager { .into_iter() .map(|f| { ( - f.path_os_str().to_owned(), - if f.is_dir() { Some(MIME_DIR.to_owned()) } else { self.mimetype.get(f.path()).cloned() }, + f.url_owned(), + if f.is_dir() { Some(MIME_DIR.to_owned()) } else { self.mimetype.get(f.url()).cloned() }, ) }) .collect(); @@ -135,18 +138,20 @@ impl Manager { } tokio::spawn(async move { - let todo = files.iter().filter(|(_, m)| m.is_none()).map(|(p, _)| p).collect::>(); + 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(|(p, m)| { - let mime = m.or_else(|| mimes.remove(&PathBuf::from(&p))); - (p, mime) + .map(|(u, m)| { + let mime = m.or_else(|| mimes.remove(&u)); + (u, mime) }) - .collect::>(); + .collect(); } - let files = files.into_iter().filter_map(|(p, m)| m.map(|m| (p, m))).collect::>(); + let files: Vec<_> = + files.into_iter().filter_map(|(u, m)| m.map(|m| (u.into_os_string(), m))).collect(); + if !interactive { emit!(Open(files, None)); return; @@ -184,7 +189,7 @@ impl Manager { fs::File::create(path).await?; } - if let Ok(file) = File::from(&hovered).await { + if let Ok(file) = File::from(Url::new(hovered, &cwd)).await { emit!(Hover(file)); emit!(Refresh); } @@ -199,7 +204,7 @@ impl Manager { return self.bulk_rename(); } - let Some(hovered) = self.hovered().map(|h| h.path_owned()) else { + let Some(hovered) = self.hovered().map(|h| h.url_owned()) else { return false; }; @@ -217,7 +222,7 @@ impl Manager { } pub fn bulk_rename(&self) -> bool { - let old: Vec<_> = self.selected().into_iter().map(|f| f.path()).collect(); + let old: Vec<_> = self.selected().into_iter().map(|f| f.url()).collect(); let root = max_common_root(&old); let old: Vec<_> = old.into_iter().map(|p| p.strip_prefix(&root).unwrap().to_owned()).collect(); @@ -256,7 +261,7 @@ impl Manager { })?; child.wait().await?; - let new: Vec<_> = fs::read_to_string(&tmp).await?.lines().map(|l| l.into()).collect(); + let new: Vec<_> = fs::read_to_string(&tmp).await?.lines().map(PathBuf::from).collect(); Self::bulk_rename_do(root, old, new).await }); @@ -321,55 +326,36 @@ impl Manager { } pub fn update_read(&mut self, op: FilesOp) -> bool { - let path = op.path(); + let url = op.url(); let cwd = self.cwd().to_owned(); - let hovered = self.hovered().map(|h| h.path_owned()); + let hovered = self.hovered().map(|h| h.url_owned()); - let mut b = if cwd == path && !self.current().in_search { + let mut b = if cwd == url && !cwd.is_search() { self.current_mut().update(op) - } else if matches!(self.parent(), Some(p) if p.cwd == path) { + } else if matches!(self.parent(), Some(p) if p.cwd == url) { self.active_mut().parent.as_mut().unwrap().update(op) } else { - self - .active_mut() - .history - .entry(path.to_path_buf()) - .or_insert_with(|| Folder::new(&path)) - .update(op); + self.active_mut().history.entry(url.clone()).or_insert_with(|| Folder::from(&url)).update(op); - matches!(self.hovered(), Some(h) if h.path() == &path) + matches!(self.hovered(), Some(h) if *h.url() == url) }; b |= self.active_mut().parent.as_mut().map_or(false, |p| p.hover(&cwd)); b |= hovered.as_ref().map_or(false, |h| self.current_mut().hover(h)); - if hovered.as_ref() != self.hovered().map(|h| h.path()) { + if hovered.as_ref() != self.hovered().map(|h| h.url()) { emit!(Hover); } b } - pub fn update_search(&mut self, op: FilesOp) -> bool { - let path = op.path(); - if self.current().in_search && self.cwd() == path { - return self.current_mut().update(op); - } - - let rep = mem::replace(self.current_mut(), Folder::new_search(&path)); - if !rep.in_search { - self.active_mut().history.insert(path, rep); - } - self.current_mut().update(op); - true - } - pub fn update_ioerr(&mut self, op: FilesOp) -> bool { - let path = op.path(); - let op = FilesOp::read_empty(&path); + let url = op.url(); + let op = FilesOp::clear(&url); - if path == self.cwd() { + if url == *self.cwd() { self.current_mut().update(op); - } else if matches!(self.parent(), Some(p) if p.cwd == path) { + } else if matches!(self.parent(), Some(p) if p.cwd == url) { self.active_mut().parent.as_mut().unwrap().update(op); } else { return false; @@ -379,7 +365,7 @@ impl Manager { true } - pub fn update_mimetype(&mut self, mut mimes: BTreeMap, tasks: &Tasks) -> bool { + pub fn update_mimetype(&mut self, mut mimes: BTreeMap, tasks: &Tasks) -> bool { mimes.retain(|f, m| self.mimetype.get(f) != Some(m)); if mimes.is_empty() { return false; @@ -393,22 +379,15 @@ impl Manager { true } + #[inline] pub fn update_hover(&mut self, file: Option) -> bool { - let b = file.map(|f| self.current_mut().hover_force(f)).unwrap_or(false); - let Some(hovered) = self.hovered() else { - return b; - }; - - if hovered.is_dir() { - self.watcher.trigger_dirs(&[hovered.path()]); - } - b + file.map(|f| self.current_mut().hover_force(f)) == Some(true) } } impl Manager { #[inline] - pub fn cwd(&self) -> &Path { &self.current().cwd } + pub fn cwd(&self) -> &Url { &self.current().cwd } #[inline] pub fn tabs(&self) -> &Tabs { &self.tabs } @@ -438,5 +417,5 @@ impl Manager { pub fn selected(&self) -> Vec<&File> { self.tabs.active().selected() } #[inline] - pub fn yanked(&self) -> &(bool, HashSet) { &self.yanked } + pub fn yanked(&self) -> &(bool, HashSet) { &self.yanked } } diff --git a/core/src/manager/preview/preview.rs b/core/src/manager/preview/preview.rs index 21227a2d..04cdb6ec 100644 --- a/core/src/manager/preview/preview.rs +++ b/core/src/manager/preview/preview.rs @@ -1,12 +1,12 @@ -use std::{path::{Path, PathBuf}, sync::atomic::Ordering}; +use std::sync::atomic::Ordering; use adaptor::Adaptor; use config::MANAGER; -use shared::{MimeKind, PeekError}; +use shared::{MimeKind, PeekError, Url, MIME_DIR}; use tokio::task::JoinHandle; use super::{Provider, INCR}; -use crate::emit; +use crate::{emit, files::{Files, FilesOp}}; #[derive(Default)] pub struct Preview { @@ -17,7 +17,7 @@ pub struct Preview { } pub struct PreviewLock { - pub path: PathBuf, + pub url: Url, pub mime: String, pub skip: usize, pub data: PreviewData, @@ -31,52 +31,93 @@ pub enum PreviewData { } impl Preview { - pub fn go(&mut self, path: &Path, mime: &str, show_image: bool) { + pub fn go(&mut self, url: &Url, mime: &str, show_image: bool) { let kind = MimeKind::new(mime); if !show_image && kind.show_as_image() { return; - } else if self.same(path, mime) { + } else if self.same(url, mime) { return; } - self.reset(); - if !self.same_path(path) { + self.reset(|_| true); + if !self.same_mime(url, mime) { self.skip = 0; } - let (path, mime, skip) = (path.to_path_buf(), mime.to_owned(), self.skip); + let (url, mime, skip) = (url.clone(), mime.to_owned(), self.skip); self.handle = Some(tokio::spawn(async move { - match Provider::auto(kind, &path, skip).await { + match Provider::auto(kind, &url, skip).await { Ok(data) => { - emit!(Preview(PreviewLock { path, mime, skip, data })); + emit!(Preview(PreviewLock { url, mime, skip, data })); } Err(PeekError::Exceed(max)) => { - emit!(Peek(max, path)); + emit!(Peek(max, url)); } _ => {} } })); } - pub fn sequent(&mut self, path: &Path, mime: &str, show_image: bool) { + pub fn folder(&mut self, url: &Url, files: Option, sequent: bool) { + if let Some(files) = files { + self.skip = self.skip.min(files.saturating_sub(MANAGER.layout.preview_height())); + } + + if self.same(url, MIME_DIR) { + return; + } else if !self.same_mime(url, MIME_DIR) { + self.skip = 0; + } + + self.reset(|_| true); + if files.is_some() || sequent { + emit!(Preview(PreviewLock { + url: url.clone(), + mime: MIME_DIR.to_owned(), + skip: self.skip, + data: PreviewData::Folder, + })); + } + + if sequent { + return; + } + + let (url, skip) = (url.clone(), self.skip); + self.handle = Some(tokio::spawn(async move { + emit!(Files(match Files::read_dir(&url).await { + Ok(items) => FilesOp::Read(url.clone(), items), + Err(_) => FilesOp::IOErr(url.clone()), + })); + + emit!(Preview(PreviewLock { + url, + mime: MIME_DIR.to_owned(), + skip, + data: PreviewData::Folder, + })); + })); + } + + pub fn sequent(&mut self, url: &Url, mime: &str, show_image: bool) { let kind = MimeKind::new(mime); if !show_image && kind.show_as_image() { return; - } else if self.same(path, mime) { + } else if self.same(url, mime) { return; } self.handle.take().map(|h| h.abort()); INCR.fetch_add(1, Ordering::Relaxed); - let (path, mime, skip) = (path.to_path_buf(), mime.to_owned(), self.skip); + let (url, mime, skip) = (url.clone(), mime.to_owned(), self.skip); self.handle = Some(tokio::spawn(async move { - match Provider::auto(kind, &path, skip).await { + match Provider::auto(kind, &url, skip).await { Ok(data) => { - emit!(Preview(PreviewLock { path, mime, skip, data })); + emit!(Preview(PreviewLock { url, mime, skip, data })); } Err(PeekError::Exceed(max)) => { - emit!(Peek(max, path)); + emit!(Peek(max, url)); } _ => {} } @@ -84,37 +125,32 @@ impl Preview { } #[inline] - pub fn arrow(&mut self, step: isize, absolute: bool) -> bool { - let old = self.skip; - if absolute { - self.skip = step.unsigned_abs(); - } else if let Some(kind) = self.lock.as_ref().map(|l| MimeKind::new(&l.mime)) { - let size = Provider::step_size(kind, step.unsigned_abs()); - self.skip = if step < 0 { old.saturating_sub(size) } else { old + size }; - } + pub fn arrow(&mut self, step: isize) -> bool { + let Some(kind) = self.lock.as_ref().map(|l| MimeKind::new(&l.mime)) else { + return false; + }; + let old = self.skip; + let size = Provider::step_size(kind, step.unsigned_abs()); + + self.skip = if step < 0 { old.saturating_sub(size) } else { old + size }; self.skip != old } - pub fn reset(&mut self) -> bool { + pub fn reset bool>(&mut self, f: F) -> bool { self.handle.take().map(|h| h.abort()); INCR.fetch_add(1, Ordering::Relaxed); Adaptor::image_hide(MANAGER.layout.preview_rect()).ok(); - let b = matches!(&self.lock, Some(l) if !l.is_image()); - self.lock = None; - b - } + let Some(ref lock) = self.lock else { + return false; + }; - pub fn reset_image(&mut self) -> bool { - self.handle.take().map(|h| h.abort()); - INCR.fetch_add(1, Ordering::Relaxed); - Adaptor::image_hide(MANAGER.layout.preview_rect()).ok(); - - if matches!(&self.lock, Some(l) if l.is_image()) { + let b = !lock.is_image(); + if f(lock) { self.lock = None; } - false + b } } @@ -126,17 +162,25 @@ impl Preview { pub fn skip(&self) -> usize { self.skip } #[inline] - pub fn same(&self, path: &Path, mime: &str) -> bool { + pub fn same(&self, url: &Url, mime: &str) -> bool { if let Some(ref lock) = self.lock { - return lock.path == path && lock.mime == mime && lock.skip == self.skip; + return lock.url == *url && lock.mime == mime && lock.skip == self.skip; } false } #[inline] - pub fn same_path(&self, path: &Path) -> bool { + pub fn same_mime(&self, url: &Url, mime: &str) -> bool { if let Some(ref lock) = self.lock { - return lock.path == path; + return lock.url == *url && lock.mime == mime; + } + false + } + + #[inline] + pub fn same_path(&self, url: &Url) -> bool { + if let Some(ref lock) = self.lock { + return lock.url == *url; } false } diff --git a/core/src/manager/preview/provider.rs b/core/src/manager/preview/provider.rs index 66bfbaab..45680eae 100644 --- a/core/src/manager/preview/provider.rs +++ b/core/src/manager/preview/provider.rs @@ -7,8 +7,9 @@ use shared::{MimeKind, PeekError}; use syntect::{easy::HighlightFile, util::as_24_bit_terminal_escaped}; use tokio::fs; + use super::PreviewData; -use crate::{emit, external, files::{Files, FilesOp}, highlighter}; +use crate::{external, highlighter}; pub(super) struct Provider; @@ -23,7 +24,6 @@ impl Provider { match kind { MimeKind::Empty => Err("Empty file".into()), MimeKind::Archive => Provider::archive(path, skip).await.map(PreviewData::Text), - MimeKind::Dir => Provider::folder(path).await, MimeKind::Image => Provider::image(path).await, MimeKind::Video => Provider::video(path, skip).await, MimeKind::JSON => Provider::json(path, skip).await.map(PreviewData::Text), @@ -37,25 +37,15 @@ impl Provider { match kind { MimeKind::Empty => 0, MimeKind::Archive => step * MANAGER.layout.preview_height() / 10, - MimeKind::Dir => step * MANAGER.layout.preview_height() / 10, MimeKind::Image => 0, MimeKind::Video => step, MimeKind::JSON => step * MANAGER.layout.preview_height() / 10, MimeKind::PDF => 1, MimeKind::Text => step * MANAGER.layout.preview_height() / 10, - MimeKind::Others => 0, + MimeKind::Others => step * MANAGER.layout.preview_height() / 10, } } - pub(super) async fn folder(path: &Path) -> Result { - emit!(Files(match Files::read_dir(path).await { - Ok(items) => FilesOp::Read(path.to_path_buf(), items), - Err(_) => FilesOp::IOErr(path.to_path_buf()), - })); - - Ok(PreviewData::Folder) - } - pub(super) async fn image(path: &Path) -> Result { Adaptor::image_show(path, MANAGER.layout.preview_rect()).await?; Ok(PreviewData::Image) diff --git a/core/src/manager/tab.rs b/core/src/manager/tab.rs index de6f6b98..0604c97b 100644 --- a/core/src/manager/tab.rs +++ b/core/src/manager/tab.rs @@ -1,9 +1,9 @@ -use std::{borrow::Cow, collections::{BTreeMap, BTreeSet}, ffi::{OsStr, OsString}, mem, path::{Path, PathBuf}}; +use std::{borrow::Cow, collections::{BTreeMap, BTreeSet}, ffi::{OsStr, OsString}, mem}; use anyhow::{Error, Result}; -use config::{open::Opener, MANAGER}; +use config::open::Opener; use futures::StreamExt; -use shared::{Defer, MIME_DIR}; +use shared::{Defer, Url}; use tokio::task::JoinHandle; use super::{Folder, Mode, Preview, PreviewLock}; @@ -14,28 +14,36 @@ pub struct Tab { pub(super) current: Folder, pub(super) parent: Option, - pub(super) history: BTreeMap, + pub(super) history: BTreeMap, pub(super) preview: Preview, search: Option>>, pub(super) show_hidden: bool, } -impl Tab { - pub fn new(path: &Path) -> Self { +impl From for Tab { + fn from(url: Url) -> Self { + let parent = url.parent_url().map(Folder::from); + Self { - mode: Default::default(), - current: Folder::new(path), - parent: path.parent().map(Folder::new), + mode: Default::default(), + current: Folder::from(url), + parent, history: Default::default(), preview: Default::default(), - search: None, + search: None, show_hidden: true, } } +} +impl From<&Url> for Tab { + fn from(url: &Url) -> Self { Self::from(url.clone()) } +} + +impl Tab { pub fn escape(&mut self) -> bool { if let Some((_, indices)) = self.mode.visual() { self.current.files.select_index(indices, Some(self.mode.is_select())); @@ -74,19 +82,19 @@ impl Tab { true } - pub async fn cd(&mut self, mut target: PathBuf) -> bool { - let Ok(file) = File::from(&target).await else { + pub async fn cd(&mut self, mut target: Url) -> bool { + let Ok(file) = File::from(target.clone()).await else { return false; }; let mut hovered = None; if !file.is_dir() { hovered = Some(file); - target = target.parent().unwrap().to_path_buf(); + target = target.parent_url().unwrap(); } if self.current.cwd == target { - if hovered.map(|h| self.current.hover_force(h)).unwrap_or(false) { + if hovered.map(|h| self.current.hover_force(h)) == Some(true) { emit!(Hover); } return false; @@ -98,12 +106,12 @@ impl Tab { let rep = self.history_new(&target); let rep = mem::replace(&mut self.current, rep); - if !rep.in_search { + if !rep.cwd.is_search() { self.history.insert(rep.cwd.clone(), rep); } - if let Some(parent) = target.parent() { - self.parent = Some(self.history_new(parent)); + if let Some(parent) = target.parent_url() { + self.parent = Some(self.history_new(&parent)); } if let Some(h) = hovered { @@ -113,13 +121,13 @@ impl Tab { true } - pub fn cd_interactive(&mut self, target: PathBuf) -> bool { + pub fn cd_interactive(&mut self, target: Url) -> bool { tokio::spawn(async move { let result = emit!(Input(InputOpt::top("Change directory:").with_value(target.to_string_lossy()))); - if let Ok(target) = result.await { - emit!(Cd(PathBuf::from(target))); + if let Ok(s) = result.await { + emit!(Cd(Url::new(s, &target))); } }); false @@ -133,16 +141,16 @@ impl Tab { return false; } - let rep = self.history_new(hovered.path()); + let rep = self.history_new(hovered.url()); let rep = mem::replace(&mut self.current, rep); - if !rep.in_search { + if !rep.cwd.is_search() { self.history.insert(rep.cwd.clone(), rep); } if let Some(rep) = self.parent.take() { self.history.insert(rep.cwd.clone(), rep); } - self.parent = Some(self.history_new(hovered.parent().unwrap())); + self.parent = Some(self.history_new(&hovered.parent().unwrap())); emit!(Refresh); true @@ -154,23 +162,23 @@ impl Tab { .hovered .as_ref() .and_then(|h| h.parent()) - .and_then(|p| if p == self.current.cwd { None } else { Some(p) }) - .or_else(|| self.current.cwd.parent()); + .filter(|p| *p != self.current.cwd) + .or_else(|| self.current.cwd.parent_url()); - let Some(current) = current.map(Path::to_path_buf) else { + let Some(current) = current else { return false; }; if let Some(rep) = self.parent.take() { self.history.insert(rep.cwd.clone(), rep); } - if let Some(parent) = current.parent() { - self.parent = Some(self.history_new(parent)); + if let Some(parent) = current.parent_url() { + self.parent = Some(self.history_new(&parent)); } let rep = self.history_new(¤t); let rep = mem::replace(&mut self.current, rep); - if !rep.in_search { + if !rep.cwd.is_search() { self.history.insert(rep.cwd.clone(), rep); } @@ -186,7 +194,7 @@ impl Tab { pub fn select(&mut self, state: Option) -> bool { if let Some(ref hovered) = self.current.hovered { - return self.current.files.select(hovered.path(), state); + return self.current.files.select(hovered.url(), state); } false } @@ -209,8 +217,8 @@ impl Tab { let mut it = self.selected().into_iter().peekable(); while let Some(f) = it.next() { s.push(match type_ { - "path" => f.path_os_str(), - "dirname" => f.parent().map_or(OsStr::new(""), |p| p.as_os_str()), + "path" => f.url_os_str(), + "dirname" => f.url().parent().map_or(OsStr::new(""), |p| p.as_os_str()), "filename" => f.name().unwrap_or(OsStr::new("")), "name_without_ext" => f.stem().unwrap_or(OsStr::new("")), _ => return false, @@ -241,9 +249,9 @@ impl Tab { external::fd(external::FdOpt { cwd: cwd.clone(), hidden, glob: false, subject }) }?; - emit!(Files(FilesOp::search_empty(&cwd))); + emit!(Files(FilesOp::clear(&cwd))); while let Some(chunk) = rx.next().await { - emit!(Files(FilesOp::Search(cwd.clone(), Files::read(&chunk).await))); + emit!(Files(FilesOp::Read(cwd.clone(), Files::read(chunk).await))); } Ok(()) })); @@ -254,7 +262,7 @@ impl Tab { if let Some(handle) = self.search.take() { handle.abort(); } - if self.current.in_search { + if self.current.cwd.is_search() { self.preview_reset_image(); let cwd = self.current.cwd.clone(); @@ -288,7 +296,7 @@ impl Tab { let selected: Vec<_> = self .selected() .into_iter() - .map(|f| (f.path_os_str().to_owned(), Default::default())) + .map(|f| (f.url_os_str().to_owned(), Default::default())) .collect(); let mut exec = exec.to_owned(); @@ -310,34 +318,24 @@ impl Tab { false } - pub fn update_peek(&mut self, step: isize, path: Option) { + pub fn update_peek(&mut self, step: isize, url: Option) -> bool { let Some(ref hovered) = self.current.hovered else { - return; + return false; }; - if path.as_ref().map(|p| p != hovered.path()).unwrap_or(false) { - return; - } else if !self.preview.arrow(step, path.is_some()) { - return; - } else if !matches!(&self.preview.lock, Some(l) if l.mime == MIME_DIR) { - return; + if url.as_ref().map(|p| p != hovered.url()) == Some(true) { + return false; } - let path = &self.preview.lock.as_ref().unwrap().path; - if let Some(folder) = self.history(path) { - let max = folder.files.len().saturating_sub(MANAGER.layout.preview_height()); - if self.preview.skip() > max { - self.preview.arrow(max as isize, true); - } - } + self.preview.arrow(step) } pub fn update_preview(&mut self, lock: PreviewLock) -> bool { - let Some(hovered) = self.current.hovered.as_ref().map(|h| h.path()) else { - return self.preview.reset(); + let Some(hovered) = self.current.hovered.as_ref().map(|h| h.url()) else { + return self.preview_reset(); }; - if lock.path != *hovered { + if lock.url != *hovered { return false; } @@ -382,11 +380,11 @@ impl Tab { // --- History #[inline] - pub fn history(&self, path: &Path) -> Option<&Folder> { self.history.get(path) } + pub fn history(&self, url: &Url) -> Option<&Folder> { self.history.get(url) } #[inline] - pub fn history_new(&mut self, path: &Path) -> Folder { - self.history.remove(path).unwrap_or_else(|| Folder::new(path)) + pub fn history_new(&mut self, url: &Url) -> Folder { + self.history.remove(url).unwrap_or_else(|| Folder::from(url)) } // --- Preview @@ -394,10 +392,10 @@ impl Tab { pub fn preview(&self) -> &Preview { &self.preview } #[inline] - pub fn preview_reset(&mut self) -> bool { self.preview.reset() } + pub fn preview_reset(&mut self) -> bool { self.preview.reset(|_| true) } #[inline] - pub fn preview_reset_image(&mut self) -> bool { self.preview.reset_image() } + pub fn preview_reset_image(&mut self) -> bool { self.preview.reset(|l| l.is_image()) } // --- Sorter pub fn set_sorter(&mut self, sorter: FilesSorter) -> bool { @@ -433,7 +431,7 @@ impl Tab { applied |= match self.current.hovered { Some(ref h) if h.is_dir() => { - self.history.get_mut(h.path()).map(|f| f.files.set_show_hidden(state)).unwrap_or(false) + self.history.get_mut(h.url()).map(|f| f.files.set_show_hidden(state)) == Some(true) } _ => false, }; diff --git a/core/src/manager/tabs.rs b/core/src/manager/tabs.rs index ef25393c..66340be4 100644 --- a/core/src/manager/tabs.rs +++ b/core/src/manager/tabs.rs @@ -1,6 +1,5 @@ -use std::path::Path; - use config::{BOOT, MANAGER}; +use shared::Url; use super::Tab; use crate::emit; @@ -14,7 +13,7 @@ pub struct Tabs { impl Tabs { pub fn make() -> Self { - let mut tab = Tab::new(&BOOT.cwd); + let mut tab = Tab::from(Url::from(&BOOT.cwd)); tab.set_show_hidden(Some(MANAGER.show_hidden)); let mut tabs = Self { idx: usize::MAX, items: vec![tab] }; @@ -22,12 +21,12 @@ impl Tabs { tabs } - pub fn create(&mut self, path: &Path) -> bool { + pub fn create(&mut self, url: &Url) -> bool { if self.items.len() >= MAX_TABS { return false; } - let mut tab = Tab::new(path); + let mut tab = Tab::from(url); tab.set_show_hidden(Some(self.active().show_hidden)); self.items.insert(self.idx + 1, tab); diff --git a/core/src/manager/watcher.rs b/core/src/manager/watcher.rs index 42a70f99..2c19c820 100644 --- a/core/src/manager/watcher.rs +++ b/core/src/manager/watcher.rs @@ -1,10 +1,10 @@ -use std::{collections::BTreeSet, path::{Path, PathBuf}, sync::Arc, time::Duration}; +use std::{collections::BTreeSet, sync::Arc, time::Duration}; use futures::StreamExt; use indexmap::IndexMap; use notify::{event::{MetadataKind, ModifyKind}, EventKind, RecommendedWatcher, RecursiveMode, Watcher as _Watcher}; use parking_lot::RwLock; -use shared::StreamBuf; +use shared::{StreamBuf, Url}; use tokio::{fs, sync::mpsc}; use tokio_stream::wrappers::UnboundedReceiverStream; @@ -12,7 +12,7 @@ use crate::{emit, external, files::{Files, FilesOp}}; pub struct Watcher { watcher: RecommendedWatcher, - watched: Arc>>>, + watched: Arc>>>, } impl Watcher { @@ -28,11 +28,11 @@ impl Watcher { return; }; - let Some(path) = event.paths.first().cloned() else { + let Some(path) = event.paths.first().map(Url::from) else { return; }; - let parent = path.parent().unwrap_or(&path).to_path_buf(); + let parent = path.parent_url().unwrap_or_else(|| path.clone()); match event.kind { EventKind::Create(_) => { tx.send(parent).ok(); @@ -69,7 +69,7 @@ impl Watcher { instance } - pub(super) fn watch(&mut self, mut watched: BTreeSet<&PathBuf>) { + pub(super) fn watch(&mut self, mut watched: BTreeSet<&Url>) { let (to_unwatch, to_watch): (BTreeSet<_>, BTreeSet<_>) = { let guard = self.watched.read(); let keys = guard.keys().collect::>(); @@ -79,12 +79,12 @@ impl Watcher { ) }; - for p in to_unwatch { - self.watcher.unwatch(&p).ok(); + for u in to_unwatch { + self.watcher.unwatch(&u).ok(); } - for p in to_watch { - if self.watcher.watch(&p, RecursiveMode::NonRecursive).is_err() { - watched.remove(&p); + for u in to_watch { + if self.watcher.watch(&u, RecursiveMode::NonRecursive).is_err() { + watched.remove(&u); } } @@ -109,7 +109,7 @@ impl Watcher { for k in to_resolve { match fs::canonicalize(&k).await { Ok(v) if v != *k => { - ext.insert(k, Some(v)); + ext.insert(k, Some(Url::from(v))); } _ => {} } @@ -121,9 +121,9 @@ impl Watcher { }); } - pub(super) fn trigger_dirs(&self, dirs: &[&Path]) { + pub(super) fn trigger_dirs(&self, dirs: &[&Url]) { let watched = self.watched.clone(); - let dirs = dirs.iter().map(|p| p.to_path_buf()).collect::>(); + let dirs: Vec<_> = dirs.iter().map(|&u| u.clone()).collect(); tokio::spawn(async move { for dir in dirs { Self::dir_changed(&dir, watched.clone()).await; @@ -132,8 +132,8 @@ impl Watcher { } async fn changed( - mut rx: StreamBuf>, - watched: Arc>>>, + mut rx: StreamBuf>, + watched: Arc>>>, ) { while let Some(paths) = rx.next().await { let (mut files, mut dirs): (Vec<_>, Vec<_>) = Default::default(); @@ -145,7 +145,7 @@ impl Watcher { } } - Self::file_changed(files.iter().map(AsRef::as_ref).collect()).await; + Self::file_changed(files.iter().collect()).await; for file in files { emit!(Files(FilesOp::IOErr(file))); } @@ -156,24 +156,24 @@ impl Watcher { } } - async fn file_changed(paths: Vec<&Path>) { - if let Ok(mimes) = external::file(&paths).await { + async fn file_changed(urls: Vec<&Url>) { + if let Ok(mimes) = external::file(&urls).await { emit!(Mimetype(mimes)); } } - async fn dir_changed(path: &Path, watched: Arc>>>) { + async fn dir_changed(url: &Url, watched: Arc>>>) { let linked = watched .read() .iter() - .map_while(|(k, v)| v.as_ref().and_then(|v| path.strip_prefix(v).ok()).map(|v| k.join(v))) + .map_while(|(k, v)| v.as_ref().and_then(|v| url.strip_prefix(v)).map(|v| k.join(v))) .collect::>(); - let result = Files::read_dir(path).await; + let result = Files::read_dir(url).await; if linked.is_empty() { emit!(Files(match result { - Ok(items) => FilesOp::Read(path.into(), items), - Err(_) => FilesOp::IOErr(path.into()), + Ok(items) => FilesOp::Read(url.clone(), items), + Err(_) => FilesOp::IOErr(url.clone()), })); return; } @@ -184,7 +184,7 @@ impl Watcher { let mut files = Vec::with_capacity(items.len()); for item in items { let mut file = item.clone(); - file.set_path(ori.join(item.path().strip_prefix(path).unwrap())); + file.set_url(ori.join(item.url().strip_prefix(url).unwrap())); files.push(file); } FilesOp::Read(ori, files) diff --git a/core/src/tasks/scheduler.rs b/core/src/tasks/scheduler.rs index 912c23ef..7e319a2e 100644 --- a/core/src/tasks/scheduler.rs +++ b/core/src/tasks/scheduler.rs @@ -1,10 +1,10 @@ -use std::{ffi::OsStr, path::PathBuf, sync::Arc, time::Duration}; +use std::{ffi::OsStr, sync::Arc, time::Duration}; use async_channel::{Receiver, Sender}; use config::{open::Opener, TASKS}; use futures::{future::BoxFuture, FutureExt}; use parking_lot::RwLock; -use shared::{unique_path, Throttle}; +use shared::{unique_path, Throttle, Url}; use tokio::{fs, select, sync::{mpsc::{self, UnboundedReceiver}, oneshot}, time::sleep}; use tracing::{info, trace}; @@ -187,7 +187,7 @@ impl Scheduler { b } - pub(super) fn file_cut(&self, from: PathBuf, mut to: PathBuf, force: bool) { + pub(super) fn file_cut(&self, from: Url, mut to: Url, force: bool) { let mut running = self.running.write(); let id = running.add(format!("Cut {:?} to {:?}", from, to)); @@ -218,7 +218,7 @@ impl Scheduler { }); } - pub(super) fn file_copy(&self, from: PathBuf, mut to: PathBuf, force: bool, follow: bool) { + pub(super) fn file_copy(&self, from: Url, mut to: Url, force: bool, follow: bool) { let name = format!("Copy {:?} to {:?}", from, to); let id = self.running.write().add(name); @@ -234,7 +234,7 @@ impl Scheduler { }); } - pub(super) fn file_delete(&self, target: PathBuf) { + pub(super) fn file_delete(&self, target: Url) { let mut running = self.running.write(); let id = running.add(format!("Delete {:?}", target)); @@ -262,7 +262,7 @@ impl Scheduler { }); } - pub(super) fn file_trash(&self, target: PathBuf) { + pub(super) fn file_trash(&self, target: Url) { let name = format!("Trash {:?}", target); let id = self.running.write().add(name); @@ -318,7 +318,7 @@ impl Scheduler { }); } - pub(super) fn precache_size(&self, targets: Vec<&PathBuf>) { + pub(super) fn precache_size(&self, targets: Vec<&Url>) { let throttle = Arc::new(Throttle::new(targets.len(), Duration::from_millis(300))); let mut handing = self.precache.size_handing.lock(); let mut running = self.running.write(); @@ -343,7 +343,7 @@ impl Scheduler { } } - pub(super) fn precache_mime(&self, targets: Vec) { + pub(super) fn precache_mime(&self, targets: Vec) { let name = format!("Preload mimetype for {} files", targets.len()); let id = self.running.write().add(name); @@ -356,21 +356,21 @@ impl Scheduler { }); } - pub(super) fn precache_image(&self, targets: Vec) { + pub(super) fn precache_image(&self, targets: Vec) { let name = format!("Precache of {} image files", targets.len()); let id = self.running.write().add(name); self.precache.image(id, targets).ok(); } - pub(super) fn precache_video(&self, targets: Vec) { + pub(super) fn precache_video(&self, targets: Vec) { let name = format!("Precache of {} video files", targets.len()); let id = self.running.write().add(name); self.precache.video(id, targets).ok(); } - pub(super) fn precache_pdf(&self, targets: Vec) { + pub(super) fn precache_pdf(&self, targets: Vec) { let name = format!("Precache of {} PDF files", targets.len()); let id = self.running.write().add(name); diff --git a/core/src/tasks/tasks.rs b/core/src/tasks/tasks.rs index 6c541865..e7f0e27d 100644 --- a/core/src/tasks/tasks.rs +++ b/core/src/tasks/tasks.rs @@ -1,8 +1,8 @@ -use std::{collections::{BTreeMap, HashMap, HashSet}, ffi::OsStr, io::{stdout, Write}, path::{Path, PathBuf}, sync::Arc}; +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}; +use shared::{Defer, MimeKind, Term, Url}; use tokio::{io::{stdin, AsyncReadExt}, select, sync::mpsc, time}; use tracing::trace; @@ -56,7 +56,7 @@ impl Tasks { pub fn paginate(&self) -> Vec { let running = self.scheduler.running.read(); - running.values().take(Self::limit()).map(|t| t.into()).collect() + running.values().take(Self::limit()).map(Into::into).collect() } pub fn inspect(&self) -> bool { @@ -154,7 +154,7 @@ impl Tasks { false } - pub fn file_cut(&self, src: &HashSet, dest: PathBuf, force: bool) -> bool { + pub fn file_cut(&self, src: &HashSet, dest: Url, force: bool) -> bool { for p in src { let to = dest.join(p.file_name().unwrap()); if force && *p == to { @@ -166,13 +166,7 @@ impl Tasks { false } - pub fn file_copy( - &self, - src: &HashSet, - dest: PathBuf, - force: bool, - follow: bool, - ) -> bool { + pub fn file_copy(&self, src: &HashSet, dest: Url, force: bool, follow: bool) -> bool { for p in src { let to = dest.join(p.file_name().unwrap()); if force && *p == to { @@ -184,7 +178,7 @@ impl Tasks { false } - pub fn file_remove(&self, targets: Vec, permanently: bool) -> bool { + pub fn file_remove(&self, targets: Vec, permanently: bool) -> bool { let scheduler = self.scheduler.clone(); tokio::spawn(async move { let s = if targets.len() > 1 { 's' } else { ' ' }; @@ -217,7 +211,7 @@ impl Tasks { } let targets: Vec<_> = - targets.iter().filter(|f| f.is_dir() && f.length().is_none()).map(|f| f.path()).collect(); + 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); @@ -227,11 +221,11 @@ impl Tasks { } #[inline] - pub fn precache_mime(&self, targets: &[File], mimetype: &HashMap) -> bool { + pub fn precache_mime(&self, targets: &[File], mimetype: &HashMap) -> bool { let targets: Vec<_> = targets .iter() - .filter(|f| f.is_file() && !mimetype.contains_key(f.path())) - .map(|f| f.path_owned()) + .filter(|f| f.is_file() && !mimetype.contains_key(f.url())) + .map(|f| f.url_owned()) .collect(); if !targets.is_empty() { @@ -240,12 +234,12 @@ impl Tasks { false } - pub fn precache_image(&self, mimetype: &BTreeMap) -> bool { - let targets = mimetype + pub fn precache_image(&self, mimetype: &BTreeMap) -> bool { + let targets: Vec<_> = mimetype .iter() .filter(|(_, m)| MimeKind::new(m) == MimeKind::Image) - .map(|(p, _)| p.clone()) - .collect::>(); + .map(|(u, _)| u.clone()) + .collect(); if !targets.is_empty() { self.scheduler.precache_image(targets); @@ -253,12 +247,12 @@ impl Tasks { false } - pub fn precache_video(&self, mimetype: &BTreeMap) -> bool { - let targets = mimetype + pub fn precache_video(&self, mimetype: &BTreeMap) -> bool { + let targets: Vec<_> = mimetype .iter() .filter(|(_, m)| MimeKind::new(m) == MimeKind::Video) - .map(|(p, _)| p.clone()) - .collect::>(); + .map(|(u, _)| u.clone()) + .collect(); if !targets.is_empty() { self.scheduler.precache_video(targets); @@ -266,12 +260,12 @@ impl Tasks { false } - pub fn precache_pdf(&self, mimetype: &BTreeMap) -> bool { - let targets = mimetype + pub fn precache_pdf(&self, mimetype: &BTreeMap) -> bool { + let targets: Vec<_> = mimetype .iter() .filter(|(_, m)| MimeKind::new(m) == MimeKind::PDF) - .map(|(p, _)| p.clone()) - .collect::>(); + .map(|(u, _)| u.clone()) + .collect(); if !targets.is_empty() { self.scheduler.precache_pdf(targets); diff --git a/core/src/tasks/workers/file.rs b/core/src/tasks/workers/file.rs index e808fae4..4f52eacb 100644 --- a/core/src/tasks/workers/file.rs +++ b/core/src/tasks/workers/file.rs @@ -3,7 +3,7 @@ use std::{collections::VecDeque, fs::Metadata, path::{Path, PathBuf}}; use anyhow::Result; use config::TASKS; use futures::{future::BoxFuture, FutureExt}; -use shared::{calculate_size, copy_with_progress}; +use shared::{calculate_size, copy_with_progress, Url}; use tokio::{fs, io::{self, ErrorKind::{AlreadyExists, NotFound}}, sync::mpsc}; use tracing::trace; @@ -27,8 +27,8 @@ pub(crate) enum FileOp { #[derive(Clone, Debug)] pub(crate) struct FileOpPaste { pub id: usize, - pub from: PathBuf, - pub to: PathBuf, + pub from: Url, + pub to: Url, pub cut: bool, pub follow: bool, pub retry: u8, @@ -37,8 +37,8 @@ pub(crate) struct FileOpPaste { #[derive(Clone, Debug)] pub(crate) struct FileOpLink { pub id: usize, - pub from: PathBuf, - pub to: PathBuf, + pub from: Url, + pub to: Url, pub cut: bool, pub length: u64, } @@ -46,14 +46,14 @@ pub(crate) struct FileOpLink { #[derive(Clone, Debug)] pub(crate) struct FileOpDelete { pub id: usize, - pub target: PathBuf, + pub target: Url, pub length: u64, } #[derive(Clone, Debug)] pub(crate) struct FileOpTrash { pub id: usize, - pub target: PathBuf, + pub target: Url, pub length: u64, } @@ -220,7 +220,7 @@ impl File { }; while let Ok(Some(entry)) = it.next_entry().await { - let src = entry.path(); + let src = Url::from(entry.path()); let Ok(meta) = Self::metadata(&src, task.follow).await else { continue; }; @@ -256,7 +256,7 @@ impl File { let mut dirs = VecDeque::from([task.target]); while let Some(target) = dirs.pop_front() { - let mut it = match fs::read_dir(target).await { + let mut it = match fs::read_dir(&target).await { Ok(it) => it, Err(_) => continue, }; @@ -268,11 +268,11 @@ impl File { }; if meta.is_dir() { - dirs.push_front(entry.path()); + dirs.push_front(Url::new(entry.path(), &target)); continue; } - task.target = entry.path(); + task.target = Url::new(entry.path(), &target); task.length = meta.len(); self.sch.send(TaskOp::New(task.id, meta.len()))?; self.tx.send(FileOp::Delete(task.clone())).await?; diff --git a/core/src/tasks/workers/precache.rs b/core/src/tasks/workers/precache.rs index e360eb2d..244c985c 100644 --- a/core/src/tasks/workers/precache.rs +++ b/core/src/tasks/workers/precache.rs @@ -1,10 +1,10 @@ -use std::{collections::{BTreeMap, BTreeSet}, path::PathBuf, sync::Arc}; +use std::{collections::{BTreeMap, BTreeSet}, sync::Arc}; use adaptor::Image; use anyhow::Result; use config::PREVIEW; use parking_lot::Mutex; -use shared::{calculate_size, Throttle}; +use shared::{calculate_size, Throttle, Url}; use tokio::{fs, sync::mpsc}; use crate::{emit, external, files::FilesOp, tasks::TaskOp}; @@ -15,7 +15,7 @@ pub(crate) struct Precache { sch: mpsc::UnboundedSender, - pub(crate) size_handing: Mutex>, + pub(crate) size_handing: Mutex>, } #[derive(Debug)] @@ -28,32 +28,32 @@ pub(crate) enum PrecacheOp { #[derive(Debug)] pub(crate) struct PrecacheOpSize { pub id: usize, - pub target: PathBuf, - pub throttle: Arc>, + pub target: Url, + pub throttle: Arc>, } #[derive(Debug)] pub(crate) struct PrecacheOpMime { pub id: usize, - pub targets: Vec, + pub targets: Vec, } #[derive(Debug)] pub(crate) struct PrecacheOpImage { pub id: usize, - pub target: PathBuf, + pub target: Url, } #[derive(Debug)] pub(crate) struct PrecacheOpVideo { pub id: usize, - pub target: PathBuf, + pub target: Url, } #[derive(Debug)] pub(crate) struct PrecacheOpPDF { pub id: usize, - pub target: PathBuf, + pub target: Url, } impl Precache { @@ -79,7 +79,7 @@ impl Precache { return Ok(self.sch.send(TaskOp::Adv(task.id, 1, 0))?); } if let Ok(img) = fs::read(&task.target).await { - Image::precache(img.into(), cache).await.ok(); + Image::precache(Arc::new(img), cache).await.ok(); } self.sch.send(TaskOp::Adv(task.id, 1, 0))?; } @@ -128,7 +128,7 @@ impl Precache { handing.remove(path); } - let parent = buf[0].0.parent().unwrap().to_path_buf(); + let parent = buf[0].0.parent_url().unwrap(); emit!(Files(FilesOp::Size(parent, BTreeMap::from_iter(buf)))); }); @@ -136,7 +136,7 @@ impl Precache { self.done(task.id) } - pub(crate) fn image(&self, id: usize, targets: Vec) -> Result<()> { + pub(crate) fn image(&self, id: usize, targets: Vec) -> Result<()> { for target in targets { self.sch.send(TaskOp::New(id, 0))?; self.tx.send_blocking(PrecacheOp::Image(PrecacheOpImage { id, target }))?; @@ -144,7 +144,7 @@ impl Precache { self.done(id) } - pub(crate) fn video(&self, id: usize, targets: Vec) -> Result<()> { + pub(crate) fn video(&self, id: usize, targets: Vec) -> Result<()> { for target in targets { self.sch.send(TaskOp::New(id, 0))?; self.tx.send_blocking(PrecacheOp::Video(PrecacheOpVideo { id, target }))?; @@ -152,7 +152,7 @@ impl Precache { self.done(id) } - pub(crate) fn pdf(&self, id: usize, targets: Vec) -> Result<()> { + pub(crate) fn pdf(&self, id: usize, targets: Vec) -> Result<()> { for target in targets { self.sch.send(TaskOp::New(id, 0))?; self.tx.send_blocking(PrecacheOp::Pdf(PrecacheOpPDF { id, target }))?; diff --git a/shared/src/fns.rs b/shared/src/fns.rs index 34a1e34f..11fff35d 100644 --- a/shared/src/fns.rs +++ b/shared/src/fns.rs @@ -2,6 +2,8 @@ use std::{env, path::{Path, PathBuf}}; use tokio::fs; +use crate::Url; + pub fn absolute_path(p: impl AsRef) -> PathBuf { let p = p.as_ref(); if let Ok(p) = p.strip_prefix("~") { @@ -12,6 +14,12 @@ pub fn absolute_path(p: impl AsRef) -> PathBuf { std::fs::canonicalize(p).unwrap_or_else(|_| p.to_path_buf()) } +#[inline] +pub fn absolute_url(mut u: Url) -> Url { + u.set_path(absolute_path(&u)); + u +} + pub fn readable_path(p: &Path, base: &Path) -> String { if let Ok(p) = p.strip_prefix(base) { return p.display().to_string(); @@ -39,7 +47,7 @@ pub fn readable_size(size: u64) -> String { format!("{:.1} {}", size, units[i]) } -pub async fn unique_path(mut p: PathBuf) -> PathBuf { +pub async fn unique_path(mut p: Url) -> Url { let Some(name) = p.file_name().map(|n| n.to_os_string()) else { return p; }; diff --git a/shared/src/lib.rs b/shared/src/lib.rs index 6a6a1ed7..db1e9db0 100644 --- a/shared/src/lib.rs +++ b/shared/src/lib.rs @@ -11,6 +11,7 @@ mod stream; mod term; mod throttle; mod time; +mod url; pub use chars::*; pub use defer::*; @@ -23,3 +24,4 @@ pub use stream::*; pub use term::*; pub use throttle::*; pub use time::*; +pub use url::*; diff --git a/shared/src/mime.rs b/shared/src/mime.rs index cb42d532..534949d7 100644 --- a/shared/src/mime.rs +++ b/shared/src/mime.rs @@ -5,7 +5,6 @@ pub enum MimeKind { Empty, Archive, - Dir, Image, Video, @@ -19,9 +18,7 @@ pub enum MimeKind { impl MimeKind { pub fn new(s: &str) -> Self { - if s == MIME_DIR { - Self::Dir - } else if s.starts_with("text/") || s.ends_with("/xml") { + if s.starts_with("text/") || s.ends_with("/xml") { Self::Text } else if s.starts_with("image/") { Self::Image diff --git a/shared/src/url.rs b/shared/src/url.rs new file mode 100644 index 00000000..4cff2a51 --- /dev/null +++ b/shared/src/url.rs @@ -0,0 +1,100 @@ +use std::{ffi::{OsStr, OsString}, ops::{Deref, DerefMut}, path::{Path, PathBuf}}; + +#[derive(Clone, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)] +pub struct Url { + scheme: UrlScheme, + path: PathBuf, +} + +#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)] +pub enum UrlScheme { + #[default] + None, + Search, + Archive, +} + +impl Deref for Url { + type Target = PathBuf; + + fn deref(&self) -> &Self::Target { &self.path } +} + +impl DerefMut for Url { + fn deref_mut(&mut self) -> &mut Self::Target { &mut self.path } +} + +impl From for Url { + fn from(path: PathBuf) -> Self { Self { path, ..Default::default() } } +} + +impl From<&PathBuf> for Url { + fn from(path: &PathBuf) -> Self { Self::from(path.clone()) } +} + +impl From<&Path> for Url { + fn from(path: &Path) -> Self { Self::from(path.to_path_buf()) } +} + +impl From for Url { + fn from(path: String) -> Self { Self::from(PathBuf::from(path)) } +} + +impl From<&String> for Url { + fn from(path: &String) -> Self { Self::from(PathBuf::from(path)) } +} + +impl From<&str> for Url { + fn from(path: &str) -> Self { Self::from(PathBuf::from(path)) } +} + +impl AsRef for Url { + fn as_ref(&self) -> &Url { self } +} + +impl AsRef for Url { + fn as_ref(&self) -> &Path { &self.path } +} + +impl AsRef for Url { + fn as_ref(&self) -> &OsStr { self.path.as_os_str() } +} + +impl Url { + #[inline] + pub fn new(url: impl Into, ctx: &Url) -> Self { + let mut url: Self = url.into(); + url.scheme = ctx.scheme; + url + } + + #[inline] + pub fn join(&self, path: impl AsRef) -> Self { Self::new(self.path.join(path), self) } + + #[inline] + pub fn strip_prefix(&self, base: impl AsRef) -> Option<&Path> { + self.path.strip_prefix(base).ok() + } + + #[inline] + pub fn into_os_string(self) -> OsString { self.path.into_os_string() } +} + +impl Url { + // --- Scheme + #[inline] + pub fn is_none(&self) -> bool { self.scheme == UrlScheme::None } + + #[inline] + pub fn is_search(&self) -> bool { self.scheme == UrlScheme::Search } + + #[inline] + pub fn is_archive(&self) -> bool { self.scheme == UrlScheme::Archive } + + // --- Path + #[inline] + pub fn set_path(&mut self, path: PathBuf) { self.path = path; } + + #[inline] + pub fn parent_url(&self) -> Option { self.path.parent().map(|p| Self::new(p, self)) } +}