refactor: use Url instead of PathBuf (#107)

This commit is contained in:
三咲雅 · Misaki Masa 2023-09-05 19:32:53 +08:00 committed by GitHub
parent a2ecc9fb70
commit 9c3d5cc400
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
32 changed files with 532 additions and 416 deletions

View file

@ -4,7 +4,7 @@ use std::ffi::OsString;
use anyhow::{Ok, Result}; use anyhow::{Ok, Result};
use config::{keymap::{Control, Key, KeymapLayer}, BOOT}; use config::{keymap::{Control, Key, KeymapLayer}, BOOT};
use crossterm::event::KeyEvent; use crossterm::event::KeyEvent;
use shared::{absolute_path, Term}; use shared::{absolute_url, Term};
use tokio::sync::oneshot; use tokio::sync::oneshot;
use crate::{Ctx, Executor, Logs, Root, Signals}; use crate::{Ctx, Executor, Logs, Root, Signals};
@ -119,26 +119,25 @@ impl App {
let manager = &mut self.cx.manager; let manager = &mut self.cx.manager;
let tasks = &mut self.cx.tasks; let tasks = &mut self.cx.tasks;
match event { match event {
Event::Cd(path) => { Event::Cd(url) => {
futures::executor::block_on(async { futures::executor::block_on(async {
manager.active_mut().cd(absolute_path(path)).await; manager.active_mut().cd(absolute_url(url)).await;
}); });
} }
Event::Refresh => { Event::Refresh => {
manager.refresh(); manager.refresh();
} }
Event::Files(op) => { Event::Files(op) => {
let calc = matches!(op, FilesOp::Read(..) | FilesOp::Search(..)); let read = matches!(op, FilesOp::Read(..));
let b = match op { let b = match op {
FilesOp::Read(..) => manager.update_read(op), FilesOp::Read(..) => manager.update_read(op),
FilesOp::Size(..) => manager.update_read(op), FilesOp::Size(..) => manager.update_read(op),
FilesOp::Search(..) => manager.update_search(op),
FilesOp::IOErr(..) => manager.update_ioerr(op), FilesOp::IOErr(..) => manager.update_ioerr(op),
}; };
if b { if b {
emit!(Render); emit!(Render);
} }
if calc { if read {
tasks.precache_size(&manager.current().files); tasks.precache_size(&manager.current().files);
} }
} }

View file

@ -38,7 +38,7 @@ impl Ctx {
} }
Position::Hovered(rect @ Rect { mut x, y, width, height }) => { Position::Hovered(rect @ Rect { mut x, y, width, height }) => {
let Some(r) = 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 { else {
return self.area(&Position::Top(*rect)); return self.area(&Position::Top(*rect));
}; };

View file

@ -1,8 +1,7 @@
use core::{emit, files::FilesSorter, input::InputMode}; use core::{emit, files::FilesSorter, input::InputMode};
use std::path::PathBuf;
use config::{keymap::{Control, Exec, Key, KeymapLayer}, manager::SortBy, KEYMAP}; use config::{keymap::{Control, Exec, Key, KeymapLayer}, manager::SortBy, KEYMAP};
use shared::optional_bool; use shared::{optional_bool, Url};
use super::Ctx; use super::Ctx;
@ -74,11 +73,11 @@ impl Executor {
"back" => cx.manager.active_mut().back(), "back" => cx.manager.active_mut().back(),
"forward" => cx.manager.active_mut().forward(), "forward" => cx.manager.active_mut().forward(),
"cd" => { "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") { if exec.named.contains_key("interactive") {
cx.manager.active_mut().cd_interactive(path) cx.manager.active_mut().cd_interactive(url)
} else { } else {
emit!(Cd(path)); emit!(Cd(url));
false false
} }
} }
@ -109,7 +108,7 @@ impl Executor {
} }
} }
"remove" => { "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")) cx.tasks.file_remove(targets, exec.named.contains_key("permanently"))
} }
"create" => cx.manager.create(), "create" => cx.manager.create(),
@ -155,7 +154,7 @@ impl Executor {
let path = if exec.named.contains_key("current") { let path = if exec.named.contains_key("current") {
cx.manager.cwd().to_owned() cx.manager.cwd().to_owned()
} else { } 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) cx.manager.tabs_mut().create(&path)
} }

View file

@ -20,7 +20,7 @@ impl<'a> Widget for Layout<'a> {
.split(area); .split(area);
let current = &self.cx.manager.current(); let current = &self.cx.manager.current();
let location = if current.in_search { let location = if current.cwd.is_search() {
format!("{} (search)", readable_home(&current.cwd)) format!("{} (search)", readable_home(&current.cwd))
} else { } else {
readable_home(&current.cwd) readable_home(&current.cwd)

View file

@ -36,7 +36,7 @@ impl<'a> Folder<'a> {
THEME THEME
.filetypes .filetypes
.iter() .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()) .map(|x| x.style.get())
.unwrap_or_else(Style::new) .unwrap_or_else(Style::new)
} }
@ -60,11 +60,11 @@ impl<'a> Widget for Folder<'a> {
let icon = THEME let icon = THEME
.icons .icons
.iter() .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()) .map(|x| x.display.as_ref())
.unwrap_or(""); .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) if (!self.is_selection && is_selected)
|| (self.is_selection && mode.pending(self.folder.offset() + i, 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 { let style = if self.is_preview && hovered {
THEME.preview.hovered.get() THEME.preview.hovered.get()
} else if hovered { } else if hovered {
@ -87,7 +87,7 @@ impl<'a> Widget for Folder<'a> {
self.file_style(f) 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 let Some(link_to) = f.link_to() {
if MANAGER.show_symlink { if MANAGER.show_symlink {
path.push_str(&format!(" -> {}", link_to.display())); path.push_str(&format!(" -> {}", link_to.display()));

View file

@ -17,7 +17,7 @@ impl<'a> Preview<'a> {
impl<'a> Widget for Preview<'a> { impl<'a> Widget for Preview<'a> {
fn render(self, area: Rect, buf: &mut Buffer) { fn render(self, area: Rect, buf: &mut Buffer) {
let manager = &self.cx.manager; 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; return;
}; };

View file

@ -1,9 +1,9 @@
use std::{collections::BTreeMap, ffi::OsString, path::PathBuf}; use std::{collections::BTreeMap, ffi::OsString};
use anyhow::Result; use anyhow::Result;
use config::{keymap::{Control, KeymapLayer}, open::Opener}; use config::{keymap::{Control, KeymapLayer}, open::Opener};
use crossterm::event::KeyEvent; use crossterm::event::KeyEvent;
use shared::RoCell; use shared::{RoCell, Url};
use tokio::sync::{mpsc::UnboundedSender, oneshot}; use tokio::sync::{mpsc::UnboundedSender, oneshot};
use super::{files::{File, FilesOp}, input::InputOpt, select::SelectOpt}; use super::{files::{File, FilesOp}, input::InputOpt, select::SelectOpt};
@ -21,13 +21,13 @@ pub enum Event {
Ctrl(Control, KeymapLayer), Ctrl(Control, KeymapLayer),
// Manager // Manager
Cd(PathBuf), Cd(Url),
Refresh, Refresh,
Files(FilesOp), Files(FilesOp),
Pages(usize), Pages(usize),
Mimetype(BTreeMap<PathBuf, String>), Mimetype(BTreeMap<Url, String>),
Hover(Option<File>), Hover(Option<File>),
Peek(usize, Option<PathBuf>), Peek(usize, Option<Url>),
Preview(PreviewLock), Preview(PreviewLock),
// Input // Input
@ -92,8 +92,8 @@ macro_rules! emit {
(Peek) => { (Peek) => {
$crate::Event::Peek(0, None).emit(); $crate::Event::Peek(0, None).emit();
}; };
(Peek($skip:expr, $path:expr)) => { (Peek($skip:expr, $url:expr)) => {
$crate::Event::Peek($skip, Some($path)).emit(); $crate::Event::Peek($skip, Some($url)).emit();
}; };
(Preview($lock:expr)) => { (Preview($lock:expr)) => {
$crate::Event::Preview($lock).emit(); $crate::Event::Preview($lock).emit();

View file

@ -1,18 +1,18 @@
use std::{path::PathBuf, process::Stdio, time::Duration}; use std::{process::Stdio, time::Duration};
use anyhow::Result; use anyhow::Result;
use shared::StreamBuf; use shared::{StreamBuf, Url};
use tokio::{io::{AsyncBufReadExt, BufReader}, process::Command, sync::mpsc}; use tokio::{io::{AsyncBufReadExt, BufReader}, process::Command, sync::mpsc};
use tokio_stream::wrappers::UnboundedReceiverStream; use tokio_stream::wrappers::UnboundedReceiverStream;
pub struct FdOpt { pub struct FdOpt {
pub cwd: PathBuf, pub cwd: Url,
pub hidden: bool, pub hidden: bool,
pub glob: bool, pub glob: bool,
pub subject: String, pub subject: String,
} }
pub fn fd(opt: FdOpt) -> Result<StreamBuf<UnboundedReceiverStream<PathBuf>>> { pub fn fd(opt: FdOpt) -> Result<StreamBuf<UnboundedReceiverStream<Url>>> {
let mut child = Command::new("fd") let mut child = Command::new("fd")
.arg("--base-directory") .arg("--base-directory")
.arg(&opt.cwd) .arg(&opt.cwd)

View file

@ -1,12 +1,12 @@
use std::{collections::BTreeMap, ffi::OsStr, path::PathBuf}; use std::collections::BTreeMap;
use anyhow::{bail, Result}; use anyhow::{bail, Result};
use futures::TryFutureExt; use futures::TryFutureExt;
use shared::MimeKind; use shared::{MimeKind, Url};
use tokio::process::Command; use tokio::process::Command;
use tracing::error; use tracing::error;
pub async fn file(files: &[impl AsRef<OsStr>]) -> Result<BTreeMap<PathBuf, String>> { async fn _file(files: &[&Url]) -> Result<BTreeMap<Url, String>> {
if files.is_empty() { if files.is_empty() {
bail!("no files to get mime types for"); bail!("no files to get mime types for");
} }
@ -25,12 +25,16 @@ pub async fn file(files: &[impl AsRef<OsStr>]) -> Result<BTreeMap<PathBuf, Strin
.iter() .iter()
.zip(output.trim().lines()) .zip(output.trim().lines())
.filter(|(_, m)| MimeKind::valid(m)) .filter(|(_, m)| MimeKind::valid(m))
.map(|(f, m)| (f.as_ref().into(), m.to_string())), .map(|(&f, m)| (f.clone(), m.to_string())),
); );
if mimes.is_empty() { if mimes.is_empty() {
error!("failed to get mime types: {:?}", files.iter().map(AsRef::as_ref).collect::<Vec<_>>()); error!("failed to get mime types: {:?}", files);
bail!("failed to get mime types"); bail!("failed to get mime types");
} }
Ok(mimes) Ok(mimes)
} }
pub async fn file(files: &[impl AsRef<Url>]) -> Result<BTreeMap<Url, String>> {
_file(&files.iter().map(AsRef::as_ref).collect::<Vec<_>>()).await
}

View file

@ -1,13 +1,14 @@
use std::{path::PathBuf, process::Stdio}; use std::process::Stdio;
use anyhow::Result; use anyhow::Result;
use shared::Url;
use tokio::{process::Command, sync::oneshot::{self, Receiver}}; use tokio::{process::Command, sync::oneshot::{self, Receiver}};
pub struct FzfOpt { pub struct FzfOpt {
pub cwd: PathBuf, pub cwd: Url,
} }
pub fn fzf(opt: FzfOpt) -> Result<Receiver<Result<PathBuf>>> { pub fn fzf(opt: FzfOpt) -> Result<Receiver<Result<Url>>> {
let child = let child =
Command::new("fzf").current_dir(&opt.cwd).kill_on_drop(true).stdout(Stdio::piped()).spawn()?; Command::new("fzf").current_dir(&opt.cwd).kill_on_drop(true).stdout(Stdio::piped()).spawn()?;

View file

@ -1,4 +1,4 @@
use std::path::Path; use std::{path::Path, sync::Arc};
use adaptor::Image; use adaptor::Image;
use regex::Regex; use regex::Regex;
@ -25,5 +25,5 @@ pub async fn pdftoppm(src: &Path, dest: impl AsRef<Path>, skip: usize) -> Result
return if pages > 0 { Err(PeekError::Exceed(pages - 1)) } else { Err(s.to_string().into()) }; 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?)
} }

View file

@ -1,17 +1,17 @@
use std::{path::PathBuf, process::Stdio, time::Duration}; use std::{process::Stdio, time::Duration};
use anyhow::Result; use anyhow::Result;
use shared::StreamBuf; use shared::{StreamBuf, Url};
use tokio::{io::{AsyncBufReadExt, BufReader}, process::Command, sync::mpsc}; use tokio::{io::{AsyncBufReadExt, BufReader}, process::Command, sync::mpsc};
use tokio_stream::wrappers::UnboundedReceiverStream; use tokio_stream::wrappers::UnboundedReceiverStream;
pub struct RgOpt { pub struct RgOpt {
pub cwd: PathBuf, pub cwd: Url,
pub hidden: bool, pub hidden: bool,
pub subject: String, pub subject: String,
} }
pub fn rg(opt: RgOpt) -> Result<StreamBuf<UnboundedReceiverStream<PathBuf>>> { pub fn rg(opt: RgOpt) -> Result<StreamBuf<UnboundedReceiverStream<Url>>> {
let mut child = Command::new("rg") let mut child = Command::new("rg")
.current_dir(&opt.cwd) .current_dir(&opt.cwd)
.args(["--color=never", "--files-with-matches", "--smart-case"]) .args(["--color=never", "--files-with-matches", "--smart-case"])

View file

@ -1,26 +1,28 @@
use std::{path::PathBuf, process::Stdio}; use std::process::Stdio;
use anyhow::Result; use anyhow::Result;
use shared::Url;
use tokio::{process::Command, sync::oneshot::{self, Receiver}}; use tokio::{process::Command, sync::oneshot::{self, Receiver}};
pub struct ZoxideOpt { pub struct ZoxideOpt {
pub cwd: PathBuf, pub cwd: Url,
} }
pub fn zoxide(opt: ZoxideOpt) -> Result<Receiver<Result<PathBuf>>> { pub fn zoxide(opt: ZoxideOpt) -> Result<Receiver<Result<Url>>> {
let child = Command::new("zoxide") let child = Command::new("zoxide")
.args(["query", "-i", "--exclude"]) .args(["query", "-i", "--exclude"])
.arg(opt.cwd) .arg(&opt.cwd)
.kill_on_drop(true) .kill_on_drop(true)
.stdout(Stdio::piped()) .stdout(Stdio::piped())
.spawn()?; .spawn()?;
let (tx, rx) = oneshot::channel(); let (tx, rx) = oneshot::channel();
let cwd = opt.cwd.clone();
tokio::spawn(async move { tokio::spawn(async move {
if let Ok(output) = child.wait_with_output().await { if let Ok(output) = child.wait_with_output().await {
let selected = String::from_utf8_lossy(&output.stdout).trim().to_string(); let selected = String::from_utf8_lossy(&output.stdout).trim().to_string();
if !selected.is_empty() { if !selected.is_empty() {
tx.send(Ok(selected.into())).ok(); tx.send(Ok(Url::new(selected, &cwd))).ok();
return; return;
} }
} }

View file

@ -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 anyhow::Result;
use shared::Url;
use tokio::fs; use tokio::fs;
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct File { pub struct File {
pub(super) path: PathBuf, pub(super) url: Url,
pub(super) meta: Metadata, pub(super) meta: Metadata,
pub(super) length: Option<u64>, pub(super) length: Option<u64>,
pub(super) link_to: Option<PathBuf>, pub(super) link_to: Option<PathBuf>,
@ -15,53 +16,53 @@ pub struct File {
impl File { impl File {
#[inline] #[inline]
pub async fn from(path: &Path) -> Result<Self> { pub async fn from(url: Url) -> Result<Self> {
let meta = fs::metadata(path).await?; let meta = fs::metadata(&url).await?;
Ok(Self::from_meta(path, meta).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 is_link = meta.is_symlink();
let mut link_to = None; let mut link_to = None;
if is_link { if is_link {
meta = fs::metadata(&path).await.unwrap_or(meta); meta = fs::metadata(&url).await.unwrap_or(meta);
link_to = fs::read_link(&path).await.ok(); link_to = fs::read_link(&url).await.ok();
} }
let length = if meta.is_dir() { None } else { Some(meta.len()) }; 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); let is_hidden = url.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 } Self { url, meta, length, link_to, is_link, is_hidden }
} }
} }
impl File { impl File {
// --- Path // --- Path
#[inline] #[inline]
pub fn path(&self) -> &PathBuf { &self.path } pub fn url(&self) -> &Url { &self.url }
#[inline] #[inline]
pub fn set_path(&mut self, path: PathBuf) { self.path = path; } pub fn set_url(&mut self, url: Url) { self.url = url; }
#[inline] #[inline]
pub fn path_owned(&self) -> PathBuf { self.path.clone() } pub fn url_owned(&self) -> Url { self.url.clone() }
#[inline] #[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] #[inline]
pub fn name(&self) -> Option<&OsStr> { self.path.file_name() } pub fn name(&self) -> Option<&OsStr> { self.url.file_name() }
#[inline] #[inline]
pub fn name_display(&self) -> Option<Cow<str>> { pub fn name_display(&self) -> Option<Cow<str>> {
self.path.file_name().map(|s| s.to_string_lossy()) self.url.file_name().map(|s| s.to_string_lossy())
} }
#[inline] #[inline]
pub fn stem(&self) -> Option<&OsStr> { self.path.file_stem() } pub fn stem(&self) -> Option<&OsStr> { self.url.file_stem() }
#[inline] #[inline]
pub fn parent(&self) -> Option<&Path> { self.path.parent() } pub fn parent(&self) -> Option<Url> { self.url.parent_url() }
// --- Meta // --- Meta
#[inline] #[inline]

View file

@ -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 anyhow::Result;
use config::manager::SortBy; use config::manager::SortBy;
use shared::Url;
use tokio::fs; use tokio::fs;
use super::{File, FilesSorter}; use super::{File, FilesSorter};
@ -10,8 +11,8 @@ pub struct Files {
items: Vec<File>, items: Vec<File>,
hidden: Vec<File>, hidden: Vec<File>,
sizes: BTreeMap<PathBuf, u64>, sizes: BTreeMap<Url, u64>,
selected: BTreeSet<PathBuf>, selected: BTreeSet<Url>,
sorter: FilesSorter, sorter: FilesSorter,
show_hidden: bool, show_hidden: bool,
@ -39,22 +40,22 @@ impl Deref for Files {
} }
impl Files { impl Files {
pub async fn read(paths: &[impl AsRef<Path>]) -> Vec<File> { pub async fn read(urls: Vec<Url>) -> Vec<File> {
let mut items = Vec::with_capacity(paths.len()); let mut items = Vec::with_capacity(urls.len());
for path in paths { for url in urls {
if let Ok(file) = File::from(path.as_ref()).await { if let Ok(file) = File::from(url).await {
items.push(file); items.push(file);
} }
} }
items items
} }
pub async fn read_dir(path: &Path) -> Result<Vec<File>> { pub async fn read_dir(url: &Url) -> Result<Vec<File>> {
let mut it = fs::read_dir(path).await?; let mut it = fs::read_dir(url).await?;
let mut items = Vec::new(); let mut items = Vec::new();
while let Ok(Some(item)) = it.next_entry().await { while let Ok(Some(item)) = it.next_entry().await {
if let Ok(meta) = item.metadata().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) Ok(items)
@ -63,8 +64,8 @@ impl Files {
impl Files { impl Files {
#[inline] #[inline]
pub fn select(&mut self, path: &Path, state: Option<bool>) -> bool { pub fn select(&mut self, url: &Url, state: Option<bool>) -> bool {
let old = self.selected.contains(path); let old = self.selected.contains(url);
let new = if let Some(new) = state { new } else { !old }; let new = if let Some(new) = state { new } else { !old };
if new == old { if new == old {
@ -72,9 +73,9 @@ impl Files {
} }
if new { if new {
self.selected.insert(path.to_owned()); self.selected.insert(url.to_owned());
} else { } else {
self.selected.remove(path); self.selected.remove(url);
} }
true true
} }
@ -82,17 +83,17 @@ impl Files {
pub fn select_all(&mut self, state: Option<bool>) -> bool { pub fn select_all(&mut self, state: Option<bool>) -> bool {
match state { match state {
Some(true) => { Some(true) => {
self.selected = self.iter().map(|f| f.path_owned()).collect(); self.selected = self.iter().map(|f| f.url_owned()).collect();
} }
Some(false) => { Some(false) => {
self.selected.clear(); self.selected.clear();
} }
None => { None => {
for item in &self.items { for item in &self.items {
if self.selected.contains(&item.path) { if self.selected.contains(&item.url) {
self.selected.remove(&item.path); self.selected.remove(&item.url);
} else { } 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<usize>, state: Option<bool>) -> bool { pub fn select_index(&mut self, indices: &BTreeSet<usize>, state: Option<bool>) -> bool {
let mut applied = false; 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 { for path in paths {
applied |= self.select(&path, state); applied |= self.select(&path, state);
@ -119,7 +120,7 @@ impl Files {
true true
} }
pub fn update_size(&mut self, items: BTreeMap<PathBuf, u64>) -> bool { pub fn update_size(&mut self, items: BTreeMap<Url, u64>) -> bool {
self.sizes.extend(items); self.sizes.extend(items);
if self.sorter.by == SortBy::Size { if self.sorter.by == SortBy::Size {
self.sorter.sort(&mut self.items); self.sorter.sort(&mut self.items);
@ -127,6 +128,7 @@ impl Files {
true true
} }
// TODO: remove this
pub fn update_search(&mut self, items: Vec<File>) -> bool { pub fn update_search(&mut self, items: Vec<File>) -> bool {
if !items.is_empty() { if !items.is_empty() {
if self.show_hidden { if self.show_hidden {
@ -164,7 +166,7 @@ impl Files {
} }
#[inline] #[inline]
pub fn position(&self, path: &Path) -> Option<usize> { self.iter().position(|f| f.path == path) } pub fn position(&self, url: &Url) -> Option<usize> { self.iter().position(|f| f.url == *url) }
#[inline] #[inline]
pub fn duplicate(&self, idx: usize) -> Option<File> { self.items.get(idx).cloned() } pub fn duplicate(&self, idx: usize) -> Option<File> { self.items.get(idx).cloned() }
@ -177,7 +179,7 @@ impl Files {
let selected: BTreeSet<_> = self.selected.iter().collect(); let selected: BTreeSet<_> = self.selected.iter().collect();
let pending: BTreeSet<_> = 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 { let selected: BTreeSet<_> = if unset {
selected.difference(&pending).cloned().collect() selected.difference(&pending).cloned().collect()
@ -187,7 +189,7 @@ impl Files {
let mut items = Vec::with_capacity(selected.len()); let mut items = Vec::with_capacity(selected.len());
for item in &self.items { for item in &self.items {
if selected.contains(&item.path) { if selected.contains(&item.url) {
items.push(item); items.push(item);
} }
if items.len() == selected.len() { if items.len() == selected.len() {
@ -198,14 +200,14 @@ impl Files {
} }
#[inline] #[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] #[inline]
pub fn has_selected(&self) -> bool { pub fn has_selected(&self) -> bool {
if self.selected.is_empty() { if self.selected.is_empty() {
return false; return false;
} }
self.iter().any(|f| self.selected.contains(&f.path)) self.iter().any(|f| self.selected.contains(&f.url))
} }
// --- Sorter // --- Sorter

View file

@ -1,30 +1,27 @@
use std::{collections::BTreeMap, path::{Path, PathBuf}}; use std::collections::BTreeMap;
use shared::Url;
use super::File; use super::File;
#[derive(Debug)] #[derive(Debug)]
pub enum FilesOp { pub enum FilesOp {
Read(PathBuf, Vec<File>), Read(Url, Vec<File>),
Size(PathBuf, BTreeMap<PathBuf, u64>), Size(Url, BTreeMap<Url, u64>),
Search(PathBuf, Vec<File>), IOErr(Url),
IOErr(PathBuf),
} }
impl FilesOp { impl FilesOp {
#[inline] #[inline]
pub fn path(&self) -> PathBuf { pub fn url(&self) -> Url {
match self { match self {
Self::Read(path, _) => path, Self::Read(url, _) => url,
Self::Size(path, _) => path, Self::Size(url, _) => url,
Self::Search(path, _) => path, Self::IOErr(url) => url,
Self::IOErr(path) => path,
} }
.clone() .clone()
} }
#[inline] #[inline]
pub fn read_empty(path: &Path) -> Self { Self::Read(path.to_path_buf(), Vec::new()) } pub fn clear(url: &Url) -> Self { Self::Read(url.clone(), Vec::new()) }
#[inline]
pub fn search_empty(path: &Path) -> Self { Self::Search(path.to_path_buf(), Vec::new()) }
} }

View file

@ -1,4 +1,4 @@
use std::{cmp::Ordering, mem, path::PathBuf}; use std::{cmp::Ordering, mem};
use config::{manager::SortBy, MANAGER}; use config::{manager::SortBy, MANAGER};
@ -29,7 +29,7 @@ impl FilesSorter {
match self.by { match self.by {
SortBy::Alphabetical => { 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| { SortBy::Created => items.sort_unstable_by(|a, b| {
if let (Ok(aa), Ok(bb)) = (a.meta.created(), b.meta.created()) { 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()); let mut entities = Vec::with_capacity(items.len());
for (i, file) in items.iter().enumerate() { for (i, file) in items.iter().enumerate() {
indices.push(i); 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| { indices.sort_unstable_by(|&a, &b| {
@ -71,7 +71,7 @@ impl FilesSorter {
}); });
let dummy = File { let dummy = File {
path: PathBuf::new(), url: Default::default(),
meta: items[0].meta.clone(), meta: items[0].meta.clone(),
length: None, length: None,
link_to: None, link_to: None,

View file

@ -1,34 +1,34 @@
use std::path::{Path, PathBuf};
use config::MANAGER; use config::MANAGER;
use ratatui::layout::Rect; use ratatui::layout::Rect;
use shared::Url;
use crate::{emit, files::{File, Files, FilesOp}}; use crate::{emit, files::{File, Files, FilesOp}};
#[derive(Default)] #[derive(Default)]
pub struct Folder { pub struct Folder {
pub cwd: PathBuf, pub cwd: Url,
pub files: Files, pub files: Files,
offset: usize,
cursor: usize,
pub page: usize, offset: usize,
pub hovered: Option<File>, cursor: usize,
pub in_search: bool,
pub page: usize,
pub hovered: Option<File>,
}
impl From<Url> 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 { 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 { pub fn update(&mut self, op: FilesOp) -> bool {
let b = match op { let b = match op {
FilesOp::Read(_, items) => self.files.update_read(items), FilesOp::Read(_, items) => self.files.update_read(items),
FilesOp::Size(_, items) => self.files.update_size(items), FilesOp::Size(_, items) => self.files.update_size(items),
FilesOp::Search(_, items) => self.files.update_search(items),
_ => unreachable!(), _ => unreachable!(),
}; };
if !b { if !b {
@ -102,18 +102,18 @@ impl Folder {
&self.files[start..end] &self.files[start..end]
} }
pub fn hover(&mut self, path: &Path) -> bool { pub fn hover(&mut self, url: &Url) -> bool {
let new = self.files.position(path).unwrap_or(self.cursor); 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) } if new > self.cursor { self.next(new - self.cursor) } else { self.prev(self.cursor - new) }
} }
#[inline] #[inline]
pub fn hover_repos(&mut self) -> bool { 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 { pub fn hover_force(&mut self, file: File) -> bool {
if self.hover(file.path()) { if self.hover(file.url()) {
return true; return true;
} }
@ -138,8 +138,8 @@ impl Folder {
&self.files[start..end] &self.files[start..end]
} }
pub fn rect_current(&self, path: &Path) -> Option<Rect> { pub fn rect_current(&self, url: &Url) -> Option<Rect> {
let y = self.files.position(path)? - self.offset; let y = self.files.position(url)? - self.offset;
let mut rect = MANAGER.layout.folder_rect(); let mut rect = MANAGER.layout.folder_rect();
rect.y = rect.y.saturating_sub(1) + y as u16; rect.y = rect.y.saturating_sub(1) + y as u16;

View file

@ -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 anyhow::{anyhow, bail, Error, Result};
use config::{OPEN, PREVIEW}; 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 tokio::{fs::{self, OpenOptions}, io::{stdin, AsyncReadExt, AsyncWriteExt}};
use super::{Tab, Tabs, Watcher}; use super::{Tab, Tabs, Watcher};
@ -10,10 +10,10 @@ use crate::{emit, external::{self, ShellOpt}, files::{File, FilesOp}, input::Inp
pub struct Manager { pub struct Manager {
tabs: Tabs, tabs: Tabs,
yanked: (bool, HashSet<PathBuf>), yanked: (bool, HashSet<Url>),
watcher: Watcher, watcher: Watcher,
pub mimetype: HashMap<PathBuf, String>, pub mimetype: HashMap<Url, String>,
} }
impl Manager { impl Manager {
@ -44,7 +44,7 @@ impl Manager {
to_watch.insert(&tab.current.cwd); to_watch.insert(&tab.current.cwd);
if let Some(ref h) = tab.current.hovered { if let Some(ref h) = tab.current.hovered {
if h.is_dir() { if h.is_dir() {
to_watch.insert(h.path()); to_watch.insert(h.url());
} }
} }
if let Some(ref p) = tab.parent { if let Some(ref p) = tab.parent {
@ -56,20 +56,23 @@ impl Manager {
pub fn peek(&mut self, sequent: bool, show_image: bool) -> bool { pub fn peek(&mut self, sequent: bool, show_image: bool) -> bool {
let Some(hovered) = self.hovered().cloned() else { 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 { if !show_image {
self.active_mut().preview_reset_image(); self.active_mut().preview_reset_image();
} }
let mime = if hovered.is_dir() { if hovered.is_dir() {
MIME_DIR.to_owned() let len = self.active().history(url).map(|f| f.files.len());
} else if let Some(m) = self.mimetype.get(hovered.path()).cloned() { self.active_mut().preview.folder(url, len, sequent);
m return false;
} else { }
let Some(mime) = self.mimetype.get(url).cloned() else {
tokio::spawn(async move { 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)); emit!(Mimetype(mimes));
} }
}); });
@ -77,16 +80,16 @@ impl Manager {
}; };
if sequent { if sequent {
self.active_mut().preview.sequent(hovered.path(), &mime, show_image); self.active_mut().preview.sequent(url, &mime, show_image);
} else { } else {
self.active_mut().preview.go(hovered.path(), &mime, show_image); self.active_mut().preview.go(url, &mime, show_image);
} }
false false
} }
pub fn yank(&mut self, cut: bool) -> bool { pub fn yank(&mut self, cut: bool) -> bool {
self.yanked.0 = cut; 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 false
} }
@ -124,8 +127,8 @@ impl Manager {
.into_iter() .into_iter()
.map(|f| { .map(|f| {
( (
f.path_os_str().to_owned(), f.url_owned(),
if f.is_dir() { Some(MIME_DIR.to_owned()) } else { self.mimetype.get(f.path()).cloned() }, if f.is_dir() { Some(MIME_DIR.to_owned()) } else { self.mimetype.get(f.url()).cloned() },
) )
}) })
.collect(); .collect();
@ -135,18 +138,20 @@ impl Manager {
} }
tokio::spawn(async move { tokio::spawn(async move {
let todo = files.iter().filter(|(_, m)| m.is_none()).map(|(p, _)| p).collect::<Vec<_>>(); let todo: Vec<_> = files.iter().filter(|(_, m)| m.is_none()).map(|(u, _)| u).collect();
if let Ok(mut mimes) = external::file(&todo).await { if let Ok(mut mimes) = external::file(&todo).await {
files = files files = files
.into_iter() .into_iter()
.map(|(p, m)| { .map(|(u, m)| {
let mime = m.or_else(|| mimes.remove(&PathBuf::from(&p))); let mime = m.or_else(|| mimes.remove(&u));
(p, mime) (u, mime)
}) })
.collect::<Vec<_>>(); .collect();
} }
let files = files.into_iter().filter_map(|(p, m)| m.map(|m| (p, m))).collect::<Vec<_>>(); let files: Vec<_> =
files.into_iter().filter_map(|(u, m)| m.map(|m| (u.into_os_string(), m))).collect();
if !interactive { if !interactive {
emit!(Open(files, None)); emit!(Open(files, None));
return; return;
@ -184,7 +189,7 @@ impl Manager {
fs::File::create(path).await?; 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!(Hover(file));
emit!(Refresh); emit!(Refresh);
} }
@ -199,7 +204,7 @@ impl Manager {
return self.bulk_rename(); 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; return false;
}; };
@ -217,7 +222,7 @@ impl Manager {
} }
pub fn bulk_rename(&self) -> bool { 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 root = max_common_root(&old);
let old: Vec<_> = old.into_iter().map(|p| p.strip_prefix(&root).unwrap().to_owned()).collect(); 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?; 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 Self::bulk_rename_do(root, old, new).await
}); });
@ -321,55 +326,36 @@ impl Manager {
} }
pub fn update_read(&mut self, op: FilesOp) -> bool { pub fn update_read(&mut self, op: FilesOp) -> bool {
let path = op.path(); let url = op.url();
let cwd = self.cwd().to_owned(); 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) 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) self.active_mut().parent.as_mut().unwrap().update(op)
} else { } else {
self self.active_mut().history.entry(url.clone()).or_insert_with(|| Folder::from(&url)).update(op);
.active_mut()
.history
.entry(path.to_path_buf())
.or_insert_with(|| Folder::new(&path))
.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 |= 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)); 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); emit!(Hover);
} }
b 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 { pub fn update_ioerr(&mut self, op: FilesOp) -> bool {
let path = op.path(); let url = op.url();
let op = FilesOp::read_empty(&path); let op = FilesOp::clear(&url);
if path == self.cwd() { if url == *self.cwd() {
self.current_mut().update(op); 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); self.active_mut().parent.as_mut().unwrap().update(op);
} else { } else {
return false; return false;
@ -379,7 +365,7 @@ impl Manager {
true true
} }
pub fn update_mimetype(&mut self, mut mimes: BTreeMap<PathBuf, String>, tasks: &Tasks) -> bool { pub fn update_mimetype(&mut self, mut mimes: BTreeMap<Url, String>, tasks: &Tasks) -> bool {
mimes.retain(|f, m| self.mimetype.get(f) != Some(m)); mimes.retain(|f, m| self.mimetype.get(f) != Some(m));
if mimes.is_empty() { if mimes.is_empty() {
return false; return false;
@ -393,22 +379,15 @@ impl Manager {
true true
} }
#[inline]
pub fn update_hover(&mut self, file: Option<File>) -> bool { pub fn update_hover(&mut self, file: Option<File>) -> bool {
let b = file.map(|f| self.current_mut().hover_force(f)).unwrap_or(false); file.map(|f| self.current_mut().hover_force(f)) == Some(true)
let Some(hovered) = self.hovered() else {
return b;
};
if hovered.is_dir() {
self.watcher.trigger_dirs(&[hovered.path()]);
}
b
} }
} }
impl Manager { impl Manager {
#[inline] #[inline]
pub fn cwd(&self) -> &Path { &self.current().cwd } pub fn cwd(&self) -> &Url { &self.current().cwd }
#[inline] #[inline]
pub fn tabs(&self) -> &Tabs { &self.tabs } pub fn tabs(&self) -> &Tabs { &self.tabs }
@ -438,5 +417,5 @@ impl Manager {
pub fn selected(&self) -> Vec<&File> { self.tabs.active().selected() } pub fn selected(&self) -> Vec<&File> { self.tabs.active().selected() }
#[inline] #[inline]
pub fn yanked(&self) -> &(bool, HashSet<PathBuf>) { &self.yanked } pub fn yanked(&self) -> &(bool, HashSet<Url>) { &self.yanked }
} }

View file

@ -1,12 +1,12 @@
use std::{path::{Path, PathBuf}, sync::atomic::Ordering}; use std::sync::atomic::Ordering;
use adaptor::Adaptor; use adaptor::Adaptor;
use config::MANAGER; use config::MANAGER;
use shared::{MimeKind, PeekError}; use shared::{MimeKind, PeekError, Url, MIME_DIR};
use tokio::task::JoinHandle; use tokio::task::JoinHandle;
use super::{Provider, INCR}; use super::{Provider, INCR};
use crate::emit; use crate::{emit, files::{Files, FilesOp}};
#[derive(Default)] #[derive(Default)]
pub struct Preview { pub struct Preview {
@ -17,7 +17,7 @@ pub struct Preview {
} }
pub struct PreviewLock { pub struct PreviewLock {
pub path: PathBuf, pub url: Url,
pub mime: String, pub mime: String,
pub skip: usize, pub skip: usize,
pub data: PreviewData, pub data: PreviewData,
@ -31,52 +31,93 @@ pub enum PreviewData {
} }
impl Preview { 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); let kind = MimeKind::new(mime);
if !show_image && kind.show_as_image() { if !show_image && kind.show_as_image() {
return; return;
} else if self.same(path, mime) { } else if self.same(url, mime) {
return; return;
} }
self.reset(); self.reset(|_| true);
if !self.same_path(path) { if !self.same_mime(url, mime) {
self.skip = 0; 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 { self.handle = Some(tokio::spawn(async move {
match Provider::auto(kind, &path, skip).await { match Provider::auto(kind, &url, skip).await {
Ok(data) => { Ok(data) => {
emit!(Preview(PreviewLock { path, mime, skip, data })); emit!(Preview(PreviewLock { url, mime, skip, data }));
} }
Err(PeekError::Exceed(max)) => { 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<usize>, 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); let kind = MimeKind::new(mime);
if !show_image && kind.show_as_image() { if !show_image && kind.show_as_image() {
return; return;
} else if self.same(path, mime) { } else if self.same(url, mime) {
return; return;
} }
self.handle.take().map(|h| h.abort()); self.handle.take().map(|h| h.abort());
INCR.fetch_add(1, Ordering::Relaxed); 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 { self.handle = Some(tokio::spawn(async move {
match Provider::auto(kind, &path, skip).await { match Provider::auto(kind, &url, skip).await {
Ok(data) => { Ok(data) => {
emit!(Preview(PreviewLock { path, mime, skip, data })); emit!(Preview(PreviewLock { url, mime, skip, data }));
} }
Err(PeekError::Exceed(max)) => { Err(PeekError::Exceed(max)) => {
emit!(Peek(max, path)); emit!(Peek(max, url));
} }
_ => {} _ => {}
} }
@ -84,37 +125,32 @@ impl Preview {
} }
#[inline] #[inline]
pub fn arrow(&mut self, step: isize, absolute: bool) -> bool { pub fn arrow(&mut self, step: isize) -> bool {
let old = self.skip; let Some(kind) = self.lock.as_ref().map(|l| MimeKind::new(&l.mime)) else {
if absolute { return false;
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 };
}
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 self.skip != old
} }
pub fn reset(&mut self) -> bool { pub fn reset<F: FnOnce(&PreviewLock) -> bool>(&mut self, f: F) -> bool {
self.handle.take().map(|h| h.abort()); self.handle.take().map(|h| h.abort());
INCR.fetch_add(1, Ordering::Relaxed); INCR.fetch_add(1, Ordering::Relaxed);
Adaptor::image_hide(MANAGER.layout.preview_rect()).ok(); Adaptor::image_hide(MANAGER.layout.preview_rect()).ok();
let b = matches!(&self.lock, Some(l) if !l.is_image()); let Some(ref lock) = self.lock else {
self.lock = None; return false;
b };
}
pub fn reset_image(&mut self) -> bool { let b = !lock.is_image();
self.handle.take().map(|h| h.abort()); if f(lock) {
INCR.fetch_add(1, Ordering::Relaxed);
Adaptor::image_hide(MANAGER.layout.preview_rect()).ok();
if matches!(&self.lock, Some(l) if l.is_image()) {
self.lock = None; self.lock = None;
} }
false b
} }
} }
@ -126,17 +162,25 @@ impl Preview {
pub fn skip(&self) -> usize { self.skip } pub fn skip(&self) -> usize { self.skip }
#[inline] #[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 { 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 false
} }
#[inline] #[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 { 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 false
} }

View file

@ -7,8 +7,9 @@ use shared::{MimeKind, PeekError};
use syntect::{easy::HighlightFile, util::as_24_bit_terminal_escaped}; use syntect::{easy::HighlightFile, util::as_24_bit_terminal_escaped};
use tokio::fs; use tokio::fs;
use super::PreviewData; use super::PreviewData;
use crate::{emit, external, files::{Files, FilesOp}, highlighter}; use crate::{external, highlighter};
pub(super) struct Provider; pub(super) struct Provider;
@ -23,7 +24,6 @@ impl Provider {
match kind { match kind {
MimeKind::Empty => Err("Empty file".into()), MimeKind::Empty => Err("Empty file".into()),
MimeKind::Archive => Provider::archive(path, skip).await.map(PreviewData::Text), MimeKind::Archive => Provider::archive(path, skip).await.map(PreviewData::Text),
MimeKind::Dir => Provider::folder(path).await,
MimeKind::Image => Provider::image(path).await, MimeKind::Image => Provider::image(path).await,
MimeKind::Video => Provider::video(path, skip).await, MimeKind::Video => Provider::video(path, skip).await,
MimeKind::JSON => Provider::json(path, skip).await.map(PreviewData::Text), MimeKind::JSON => Provider::json(path, skip).await.map(PreviewData::Text),
@ -37,25 +37,15 @@ impl Provider {
match kind { match kind {
MimeKind::Empty => 0, MimeKind::Empty => 0,
MimeKind::Archive => step * MANAGER.layout.preview_height() / 10, MimeKind::Archive => step * MANAGER.layout.preview_height() / 10,
MimeKind::Dir => step * MANAGER.layout.preview_height() / 10,
MimeKind::Image => 0, MimeKind::Image => 0,
MimeKind::Video => step, MimeKind::Video => step,
MimeKind::JSON => step * MANAGER.layout.preview_height() / 10, MimeKind::JSON => step * MANAGER.layout.preview_height() / 10,
MimeKind::PDF => 1, MimeKind::PDF => 1,
MimeKind::Text => step * MANAGER.layout.preview_height() / 10, 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<PreviewData, PeekError> {
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<PreviewData, PeekError> { pub(super) async fn image(path: &Path) -> Result<PreviewData, PeekError> {
Adaptor::image_show(path, MANAGER.layout.preview_rect()).await?; Adaptor::image_show(path, MANAGER.layout.preview_rect()).await?;
Ok(PreviewData::Image) Ok(PreviewData::Image)

View file

@ -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 anyhow::{Error, Result};
use config::{open::Opener, MANAGER}; use config::open::Opener;
use futures::StreamExt; use futures::StreamExt;
use shared::{Defer, MIME_DIR}; use shared::{Defer, Url};
use tokio::task::JoinHandle; use tokio::task::JoinHandle;
use super::{Folder, Mode, Preview, PreviewLock}; use super::{Folder, Mode, Preview, PreviewLock};
@ -14,28 +14,36 @@ pub struct Tab {
pub(super) current: Folder, pub(super) current: Folder,
pub(super) parent: Option<Folder>, pub(super) parent: Option<Folder>,
pub(super) history: BTreeMap<PathBuf, Folder>, pub(super) history: BTreeMap<Url, Folder>,
pub(super) preview: Preview, pub(super) preview: Preview,
search: Option<JoinHandle<Result<()>>>, search: Option<JoinHandle<Result<()>>>,
pub(super) show_hidden: bool, pub(super) show_hidden: bool,
} }
impl Tab { impl From<Url> for Tab {
pub fn new(path: &Path) -> Self { fn from(url: Url) -> Self {
let parent = url.parent_url().map(Folder::from);
Self { Self {
mode: Default::default(), mode: Default::default(),
current: Folder::new(path), current: Folder::from(url),
parent: path.parent().map(Folder::new), parent,
history: Default::default(), history: Default::default(),
preview: Default::default(), preview: Default::default(),
search: None, search: None,
show_hidden: true, 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 { pub fn escape(&mut self) -> bool {
if let Some((_, indices)) = self.mode.visual() { if let Some((_, indices)) = self.mode.visual() {
self.current.files.select_index(indices, Some(self.mode.is_select())); self.current.files.select_index(indices, Some(self.mode.is_select()));
@ -74,19 +82,19 @@ impl Tab {
true true
} }
pub async fn cd(&mut self, mut target: PathBuf) -> bool { pub async fn cd(&mut self, mut target: Url) -> bool {
let Ok(file) = File::from(&target).await else { let Ok(file) = File::from(target.clone()).await else {
return false; return false;
}; };
let mut hovered = None; let mut hovered = None;
if !file.is_dir() { if !file.is_dir() {
hovered = Some(file); hovered = Some(file);
target = target.parent().unwrap().to_path_buf(); target = target.parent_url().unwrap();
} }
if self.current.cwd == target { 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); emit!(Hover);
} }
return false; return false;
@ -98,12 +106,12 @@ impl Tab {
let rep = self.history_new(&target); let rep = self.history_new(&target);
let rep = mem::replace(&mut self.current, rep); let rep = mem::replace(&mut self.current, rep);
if !rep.in_search { if !rep.cwd.is_search() {
self.history.insert(rep.cwd.clone(), rep); self.history.insert(rep.cwd.clone(), rep);
} }
if let Some(parent) = target.parent() { if let Some(parent) = target.parent_url() {
self.parent = Some(self.history_new(parent)); self.parent = Some(self.history_new(&parent));
} }
if let Some(h) = hovered { if let Some(h) = hovered {
@ -113,13 +121,13 @@ impl Tab {
true true
} }
pub fn cd_interactive(&mut self, target: PathBuf) -> bool { pub fn cd_interactive(&mut self, target: Url) -> bool {
tokio::spawn(async move { tokio::spawn(async move {
let result = let result =
emit!(Input(InputOpt::top("Change directory:").with_value(target.to_string_lossy()))); emit!(Input(InputOpt::top("Change directory:").with_value(target.to_string_lossy())));
if let Ok(target) = result.await { if let Ok(s) = result.await {
emit!(Cd(PathBuf::from(target))); emit!(Cd(Url::new(s, &target)));
} }
}); });
false false
@ -133,16 +141,16 @@ impl Tab {
return false; return false;
} }
let rep = self.history_new(hovered.path()); let rep = self.history_new(hovered.url());
let rep = mem::replace(&mut self.current, rep); let rep = mem::replace(&mut self.current, rep);
if !rep.in_search { if !rep.cwd.is_search() {
self.history.insert(rep.cwd.clone(), rep); self.history.insert(rep.cwd.clone(), rep);
} }
if let Some(rep) = self.parent.take() { if let Some(rep) = self.parent.take() {
self.history.insert(rep.cwd.clone(), rep); 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); emit!(Refresh);
true true
@ -154,23 +162,23 @@ impl Tab {
.hovered .hovered
.as_ref() .as_ref()
.and_then(|h| h.parent()) .and_then(|h| h.parent())
.and_then(|p| if p == self.current.cwd { None } else { Some(p) }) .filter(|p| *p != self.current.cwd)
.or_else(|| self.current.cwd.parent()); .or_else(|| self.current.cwd.parent_url());
let Some(current) = current.map(Path::to_path_buf) else { let Some(current) = current else {
return false; return false;
}; };
if let Some(rep) = self.parent.take() { if let Some(rep) = self.parent.take() {
self.history.insert(rep.cwd.clone(), rep); self.history.insert(rep.cwd.clone(), rep);
} }
if let Some(parent) = current.parent() { if let Some(parent) = current.parent_url() {
self.parent = Some(self.history_new(parent)); self.parent = Some(self.history_new(&parent));
} }
let rep = self.history_new(&current); let rep = self.history_new(&current);
let rep = mem::replace(&mut self.current, rep); let rep = mem::replace(&mut self.current, rep);
if !rep.in_search { if !rep.cwd.is_search() {
self.history.insert(rep.cwd.clone(), rep); self.history.insert(rep.cwd.clone(), rep);
} }
@ -186,7 +194,7 @@ impl Tab {
pub fn select(&mut self, state: Option<bool>) -> bool { pub fn select(&mut self, state: Option<bool>) -> bool {
if let Some(ref hovered) = self.current.hovered { 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 false
} }
@ -209,8 +217,8 @@ impl Tab {
let mut it = self.selected().into_iter().peekable(); let mut it = self.selected().into_iter().peekable();
while let Some(f) = it.next() { while let Some(f) = it.next() {
s.push(match type_ { s.push(match type_ {
"path" => f.path_os_str(), "path" => f.url_os_str(),
"dirname" => f.parent().map_or(OsStr::new(""), |p| p.as_os_str()), "dirname" => f.url().parent().map_or(OsStr::new(""), |p| p.as_os_str()),
"filename" => f.name().unwrap_or(OsStr::new("")), "filename" => f.name().unwrap_or(OsStr::new("")),
"name_without_ext" => f.stem().unwrap_or(OsStr::new("")), "name_without_ext" => f.stem().unwrap_or(OsStr::new("")),
_ => return false, _ => return false,
@ -241,9 +249,9 @@ impl Tab {
external::fd(external::FdOpt { cwd: cwd.clone(), hidden, glob: false, subject }) 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 { 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(()) Ok(())
})); }));
@ -254,7 +262,7 @@ impl Tab {
if let Some(handle) = self.search.take() { if let Some(handle) = self.search.take() {
handle.abort(); handle.abort();
} }
if self.current.in_search { if self.current.cwd.is_search() {
self.preview_reset_image(); self.preview_reset_image();
let cwd = self.current.cwd.clone(); let cwd = self.current.cwd.clone();
@ -288,7 +296,7 @@ impl Tab {
let selected: Vec<_> = self let selected: Vec<_> = self
.selected() .selected()
.into_iter() .into_iter()
.map(|f| (f.path_os_str().to_owned(), Default::default())) .map(|f| (f.url_os_str().to_owned(), Default::default()))
.collect(); .collect();
let mut exec = exec.to_owned(); let mut exec = exec.to_owned();
@ -310,34 +318,24 @@ impl Tab {
false false
} }
pub fn update_peek(&mut self, step: isize, path: Option<PathBuf>) { pub fn update_peek(&mut self, step: isize, url: Option<Url>) -> bool {
let Some(ref hovered) = self.current.hovered else { let Some(ref hovered) = self.current.hovered else {
return; return false;
}; };
if path.as_ref().map(|p| p != hovered.path()).unwrap_or(false) { if url.as_ref().map(|p| p != hovered.url()) == Some(true) {
return; return false;
} else if !self.preview.arrow(step, path.is_some()) {
return;
} else if !matches!(&self.preview.lock, Some(l) if l.mime == MIME_DIR) {
return;
} }
let path = &self.preview.lock.as_ref().unwrap().path; self.preview.arrow(step)
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);
}
}
} }
pub fn update_preview(&mut self, lock: PreviewLock) -> bool { pub fn update_preview(&mut self, lock: PreviewLock) -> bool {
let Some(hovered) = self.current.hovered.as_ref().map(|h| h.path()) else { let Some(hovered) = self.current.hovered.as_ref().map(|h| h.url()) else {
return self.preview.reset(); return self.preview_reset();
}; };
if lock.path != *hovered { if lock.url != *hovered {
return false; return false;
} }
@ -382,11 +380,11 @@ impl Tab {
// --- History // --- History
#[inline] #[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] #[inline]
pub fn history_new(&mut self, path: &Path) -> Folder { pub fn history_new(&mut self, url: &Url) -> Folder {
self.history.remove(path).unwrap_or_else(|| Folder::new(path)) self.history.remove(url).unwrap_or_else(|| Folder::from(url))
} }
// --- Preview // --- Preview
@ -394,10 +392,10 @@ impl Tab {
pub fn preview(&self) -> &Preview { &self.preview } pub fn preview(&self) -> &Preview { &self.preview }
#[inline] #[inline]
pub fn preview_reset(&mut self) -> bool { self.preview.reset() } pub fn preview_reset(&mut self) -> bool { self.preview.reset(|_| true) }
#[inline] #[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 // --- Sorter
pub fn set_sorter(&mut self, sorter: FilesSorter) -> bool { pub fn set_sorter(&mut self, sorter: FilesSorter) -> bool {
@ -433,7 +431,7 @@ impl Tab {
applied |= match self.current.hovered { applied |= match self.current.hovered {
Some(ref h) if h.is_dir() => { 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, _ => false,
}; };

View file

@ -1,6 +1,5 @@
use std::path::Path;
use config::{BOOT, MANAGER}; use config::{BOOT, MANAGER};
use shared::Url;
use super::Tab; use super::Tab;
use crate::emit; use crate::emit;
@ -14,7 +13,7 @@ pub struct Tabs {
impl Tabs { impl Tabs {
pub fn make() -> Self { 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)); tab.set_show_hidden(Some(MANAGER.show_hidden));
let mut tabs = Self { idx: usize::MAX, items: vec![tab] }; let mut tabs = Self { idx: usize::MAX, items: vec![tab] };
@ -22,12 +21,12 @@ impl Tabs {
tabs tabs
} }
pub fn create(&mut self, path: &Path) -> bool { pub fn create(&mut self, url: &Url) -> bool {
if self.items.len() >= MAX_TABS { if self.items.len() >= MAX_TABS {
return false; return false;
} }
let mut tab = Tab::new(path); let mut tab = Tab::from(url);
tab.set_show_hidden(Some(self.active().show_hidden)); tab.set_show_hidden(Some(self.active().show_hidden));
self.items.insert(self.idx + 1, tab); self.items.insert(self.idx + 1, tab);

View file

@ -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 futures::StreamExt;
use indexmap::IndexMap; use indexmap::IndexMap;
use notify::{event::{MetadataKind, ModifyKind}, EventKind, RecommendedWatcher, RecursiveMode, Watcher as _Watcher}; use notify::{event::{MetadataKind, ModifyKind}, EventKind, RecommendedWatcher, RecursiveMode, Watcher as _Watcher};
use parking_lot::RwLock; use parking_lot::RwLock;
use shared::StreamBuf; use shared::{StreamBuf, Url};
use tokio::{fs, sync::mpsc}; use tokio::{fs, sync::mpsc};
use tokio_stream::wrappers::UnboundedReceiverStream; use tokio_stream::wrappers::UnboundedReceiverStream;
@ -12,7 +12,7 @@ use crate::{emit, external, files::{Files, FilesOp}};
pub struct Watcher { pub struct Watcher {
watcher: RecommendedWatcher, watcher: RecommendedWatcher,
watched: Arc<RwLock<IndexMap<PathBuf, Option<PathBuf>>>>, watched: Arc<RwLock<IndexMap<Url, Option<Url>>>>,
} }
impl Watcher { impl Watcher {
@ -28,11 +28,11 @@ impl Watcher {
return; return;
}; };
let Some(path) = event.paths.first().cloned() else { let Some(path) = event.paths.first().map(Url::from) else {
return; return;
}; };
let parent = path.parent().unwrap_or(&path).to_path_buf(); let parent = path.parent_url().unwrap_or_else(|| path.clone());
match event.kind { match event.kind {
EventKind::Create(_) => { EventKind::Create(_) => {
tx.send(parent).ok(); tx.send(parent).ok();
@ -69,7 +69,7 @@ impl Watcher {
instance 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 (to_unwatch, to_watch): (BTreeSet<_>, BTreeSet<_>) = {
let guard = self.watched.read(); let guard = self.watched.read();
let keys = guard.keys().collect::<BTreeSet<_>>(); let keys = guard.keys().collect::<BTreeSet<_>>();
@ -79,12 +79,12 @@ impl Watcher {
) )
}; };
for p in to_unwatch { for u in to_unwatch {
self.watcher.unwatch(&p).ok(); self.watcher.unwatch(&u).ok();
} }
for p in to_watch { for u in to_watch {
if self.watcher.watch(&p, RecursiveMode::NonRecursive).is_err() { if self.watcher.watch(&u, RecursiveMode::NonRecursive).is_err() {
watched.remove(&p); watched.remove(&u);
} }
} }
@ -109,7 +109,7 @@ impl Watcher {
for k in to_resolve { for k in to_resolve {
match fs::canonicalize(&k).await { match fs::canonicalize(&k).await {
Ok(v) if v != *k => { 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 watched = self.watched.clone();
let dirs = dirs.iter().map(|p| p.to_path_buf()).collect::<Vec<_>>(); let dirs: Vec<_> = dirs.iter().map(|&u| u.clone()).collect();
tokio::spawn(async move { tokio::spawn(async move {
for dir in dirs { for dir in dirs {
Self::dir_changed(&dir, watched.clone()).await; Self::dir_changed(&dir, watched.clone()).await;
@ -132,8 +132,8 @@ impl Watcher {
} }
async fn changed( async fn changed(
mut rx: StreamBuf<UnboundedReceiverStream<PathBuf>>, mut rx: StreamBuf<UnboundedReceiverStream<Url>>,
watched: Arc<RwLock<IndexMap<PathBuf, Option<PathBuf>>>>, watched: Arc<RwLock<IndexMap<Url, Option<Url>>>>,
) { ) {
while let Some(paths) = rx.next().await { while let Some(paths) = rx.next().await {
let (mut files, mut dirs): (Vec<_>, Vec<_>) = Default::default(); 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 { for file in files {
emit!(Files(FilesOp::IOErr(file))); emit!(Files(FilesOp::IOErr(file)));
} }
@ -156,24 +156,24 @@ impl Watcher {
} }
} }
async fn file_changed(paths: Vec<&Path>) { async fn file_changed(urls: Vec<&Url>) {
if let Ok(mimes) = external::file(&paths).await { if let Ok(mimes) = external::file(&urls).await {
emit!(Mimetype(mimes)); emit!(Mimetype(mimes));
} }
} }
async fn dir_changed(path: &Path, watched: Arc<RwLock<IndexMap<PathBuf, Option<PathBuf>>>>) { async fn dir_changed(url: &Url, watched: Arc<RwLock<IndexMap<Url, Option<Url>>>>) {
let linked = watched let linked = watched
.read() .read()
.iter() .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::<Vec<_>>(); .collect::<Vec<_>>();
let result = Files::read_dir(path).await; let result = Files::read_dir(url).await;
if linked.is_empty() { if linked.is_empty() {
emit!(Files(match result { emit!(Files(match result {
Ok(items) => FilesOp::Read(path.into(), items), Ok(items) => FilesOp::Read(url.clone(), items),
Err(_) => FilesOp::IOErr(path.into()), Err(_) => FilesOp::IOErr(url.clone()),
})); }));
return; return;
} }
@ -184,7 +184,7 @@ impl Watcher {
let mut files = Vec::with_capacity(items.len()); let mut files = Vec::with_capacity(items.len());
for item in items { for item in items {
let mut file = item.clone(); 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); files.push(file);
} }
FilesOp::Read(ori, files) FilesOp::Read(ori, files)

View file

@ -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 async_channel::{Receiver, Sender};
use config::{open::Opener, TASKS}; use config::{open::Opener, TASKS};
use futures::{future::BoxFuture, FutureExt}; use futures::{future::BoxFuture, FutureExt};
use parking_lot::RwLock; 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 tokio::{fs, select, sync::{mpsc::{self, UnboundedReceiver}, oneshot}, time::sleep};
use tracing::{info, trace}; use tracing::{info, trace};
@ -187,7 +187,7 @@ impl Scheduler {
b 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 mut running = self.running.write();
let id = running.add(format!("Cut {:?} to {:?}", from, to)); 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 name = format!("Copy {:?} to {:?}", from, to);
let id = self.running.write().add(name); 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 mut running = self.running.write();
let id = running.add(format!("Delete {:?}", target)); 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 name = format!("Trash {:?}", target);
let id = self.running.write().add(name); 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 throttle = Arc::new(Throttle::new(targets.len(), Duration::from_millis(300)));
let mut handing = self.precache.size_handing.lock(); let mut handing = self.precache.size_handing.lock();
let mut running = self.running.write(); let mut running = self.running.write();
@ -343,7 +343,7 @@ impl Scheduler {
} }
} }
pub(super) fn precache_mime(&self, targets: Vec<PathBuf>) { pub(super) fn precache_mime(&self, targets: Vec<Url>) {
let name = format!("Preload mimetype for {} files", targets.len()); let name = format!("Preload mimetype for {} files", targets.len());
let id = self.running.write().add(name); let id = self.running.write().add(name);
@ -356,21 +356,21 @@ impl Scheduler {
}); });
} }
pub(super) fn precache_image(&self, targets: Vec<PathBuf>) { pub(super) fn precache_image(&self, targets: Vec<Url>) {
let name = format!("Precache of {} image files", targets.len()); let name = format!("Precache of {} image files", targets.len());
let id = self.running.write().add(name); let id = self.running.write().add(name);
self.precache.image(id, targets).ok(); self.precache.image(id, targets).ok();
} }
pub(super) fn precache_video(&self, targets: Vec<PathBuf>) { pub(super) fn precache_video(&self, targets: Vec<Url>) {
let name = format!("Precache of {} video files", targets.len()); let name = format!("Precache of {} video files", targets.len());
let id = self.running.write().add(name); let id = self.running.write().add(name);
self.precache.video(id, targets).ok(); self.precache.video(id, targets).ok();
} }
pub(super) fn precache_pdf(&self, targets: Vec<PathBuf>) { pub(super) fn precache_pdf(&self, targets: Vec<Url>) {
let name = format!("Precache of {} PDF files", targets.len()); let name = format!("Precache of {} PDF files", targets.len());
let id = self.running.write().add(name); let id = self.running.write().add(name);

View file

@ -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 config::{manager::SortBy, open::Opener, OPEN};
use crossterm::terminal::{disable_raw_mode, enable_raw_mode}; 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 tokio::{io::{stdin, AsyncReadExt}, select, sync::mpsc, time};
use tracing::trace; use tracing::trace;
@ -56,7 +56,7 @@ impl Tasks {
pub fn paginate(&self) -> Vec<TaskSummary> { pub fn paginate(&self) -> Vec<TaskSummary> {
let running = self.scheduler.running.read(); 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 { pub fn inspect(&self) -> bool {
@ -154,7 +154,7 @@ impl Tasks {
false false
} }
pub fn file_cut(&self, src: &HashSet<PathBuf>, dest: PathBuf, force: bool) -> bool { pub fn file_cut(&self, src: &HashSet<Url>, dest: Url, force: bool) -> bool {
for p in src { for p in src {
let to = dest.join(p.file_name().unwrap()); let to = dest.join(p.file_name().unwrap());
if force && *p == to { if force && *p == to {
@ -166,13 +166,7 @@ impl Tasks {
false false
} }
pub fn file_copy( pub fn file_copy(&self, src: &HashSet<Url>, dest: Url, force: bool, follow: bool) -> bool {
&self,
src: &HashSet<PathBuf>,
dest: PathBuf,
force: bool,
follow: bool,
) -> bool {
for p in src { for p in src {
let to = dest.join(p.file_name().unwrap()); let to = dest.join(p.file_name().unwrap());
if force && *p == to { if force && *p == to {
@ -184,7 +178,7 @@ impl Tasks {
false false
} }
pub fn file_remove(&self, targets: Vec<PathBuf>, permanently: bool) -> bool { pub fn file_remove(&self, targets: Vec<Url>, permanently: bool) -> bool {
let scheduler = self.scheduler.clone(); let scheduler = self.scheduler.clone();
tokio::spawn(async move { tokio::spawn(async move {
let s = if targets.len() > 1 { 's' } else { ' ' }; let s = if targets.len() > 1 { 's' } else { ' ' };
@ -217,7 +211,7 @@ impl Tasks {
} }
let targets: Vec<_> = 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() { if !targets.is_empty() {
self.scheduler.precache_size(targets); self.scheduler.precache_size(targets);
@ -227,11 +221,11 @@ impl Tasks {
} }
#[inline] #[inline]
pub fn precache_mime(&self, targets: &[File], mimetype: &HashMap<PathBuf, String>) -> bool { pub fn precache_mime(&self, targets: &[File], mimetype: &HashMap<Url, String>) -> bool {
let targets: Vec<_> = targets let targets: Vec<_> = targets
.iter() .iter()
.filter(|f| f.is_file() && !mimetype.contains_key(f.path())) .filter(|f| f.is_file() && !mimetype.contains_key(f.url()))
.map(|f| f.path_owned()) .map(|f| f.url_owned())
.collect(); .collect();
if !targets.is_empty() { if !targets.is_empty() {
@ -240,12 +234,12 @@ impl Tasks {
false false
} }
pub fn precache_image(&self, mimetype: &BTreeMap<PathBuf, String>) -> bool { pub fn precache_image(&self, mimetype: &BTreeMap<Url, String>) -> bool {
let targets = mimetype let targets: Vec<_> = mimetype
.iter() .iter()
.filter(|(_, m)| MimeKind::new(m) == MimeKind::Image) .filter(|(_, m)| MimeKind::new(m) == MimeKind::Image)
.map(|(p, _)| p.clone()) .map(|(u, _)| u.clone())
.collect::<Vec<_>>(); .collect();
if !targets.is_empty() { if !targets.is_empty() {
self.scheduler.precache_image(targets); self.scheduler.precache_image(targets);
@ -253,12 +247,12 @@ impl Tasks {
false false
} }
pub fn precache_video(&self, mimetype: &BTreeMap<PathBuf, String>) -> bool { pub fn precache_video(&self, mimetype: &BTreeMap<Url, String>) -> bool {
let targets = mimetype let targets: Vec<_> = mimetype
.iter() .iter()
.filter(|(_, m)| MimeKind::new(m) == MimeKind::Video) .filter(|(_, m)| MimeKind::new(m) == MimeKind::Video)
.map(|(p, _)| p.clone()) .map(|(u, _)| u.clone())
.collect::<Vec<_>>(); .collect();
if !targets.is_empty() { if !targets.is_empty() {
self.scheduler.precache_video(targets); self.scheduler.precache_video(targets);
@ -266,12 +260,12 @@ impl Tasks {
false false
} }
pub fn precache_pdf(&self, mimetype: &BTreeMap<PathBuf, String>) -> bool { pub fn precache_pdf(&self, mimetype: &BTreeMap<Url, String>) -> bool {
let targets = mimetype let targets: Vec<_> = mimetype
.iter() .iter()
.filter(|(_, m)| MimeKind::new(m) == MimeKind::PDF) .filter(|(_, m)| MimeKind::new(m) == MimeKind::PDF)
.map(|(p, _)| p.clone()) .map(|(u, _)| u.clone())
.collect::<Vec<_>>(); .collect();
if !targets.is_empty() { if !targets.is_empty() {
self.scheduler.precache_pdf(targets); self.scheduler.precache_pdf(targets);

View file

@ -3,7 +3,7 @@ use std::{collections::VecDeque, fs::Metadata, path::{Path, PathBuf}};
use anyhow::Result; use anyhow::Result;
use config::TASKS; use config::TASKS;
use futures::{future::BoxFuture, FutureExt}; 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 tokio::{fs, io::{self, ErrorKind::{AlreadyExists, NotFound}}, sync::mpsc};
use tracing::trace; use tracing::trace;
@ -27,8 +27,8 @@ pub(crate) enum FileOp {
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub(crate) struct FileOpPaste { pub(crate) struct FileOpPaste {
pub id: usize, pub id: usize,
pub from: PathBuf, pub from: Url,
pub to: PathBuf, pub to: Url,
pub cut: bool, pub cut: bool,
pub follow: bool, pub follow: bool,
pub retry: u8, pub retry: u8,
@ -37,8 +37,8 @@ pub(crate) struct FileOpPaste {
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub(crate) struct FileOpLink { pub(crate) struct FileOpLink {
pub id: usize, pub id: usize,
pub from: PathBuf, pub from: Url,
pub to: PathBuf, pub to: Url,
pub cut: bool, pub cut: bool,
pub length: u64, pub length: u64,
} }
@ -46,14 +46,14 @@ pub(crate) struct FileOpLink {
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub(crate) struct FileOpDelete { pub(crate) struct FileOpDelete {
pub id: usize, pub id: usize,
pub target: PathBuf, pub target: Url,
pub length: u64, pub length: u64,
} }
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub(crate) struct FileOpTrash { pub(crate) struct FileOpTrash {
pub id: usize, pub id: usize,
pub target: PathBuf, pub target: Url,
pub length: u64, pub length: u64,
} }
@ -220,7 +220,7 @@ impl File {
}; };
while let Ok(Some(entry)) = it.next_entry().await { 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 { let Ok(meta) = Self::metadata(&src, task.follow).await else {
continue; continue;
}; };
@ -256,7 +256,7 @@ impl File {
let mut dirs = VecDeque::from([task.target]); let mut dirs = VecDeque::from([task.target]);
while let Some(target) = dirs.pop_front() { 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, Ok(it) => it,
Err(_) => continue, Err(_) => continue,
}; };
@ -268,11 +268,11 @@ impl File {
}; };
if meta.is_dir() { if meta.is_dir() {
dirs.push_front(entry.path()); dirs.push_front(Url::new(entry.path(), &target));
continue; continue;
} }
task.target = entry.path(); task.target = Url::new(entry.path(), &target);
task.length = meta.len(); task.length = meta.len();
self.sch.send(TaskOp::New(task.id, meta.len()))?; self.sch.send(TaskOp::New(task.id, meta.len()))?;
self.tx.send(FileOp::Delete(task.clone())).await?; self.tx.send(FileOp::Delete(task.clone())).await?;

View file

@ -1,10 +1,10 @@
use std::{collections::{BTreeMap, BTreeSet}, path::PathBuf, sync::Arc}; use std::{collections::{BTreeMap, BTreeSet}, sync::Arc};
use adaptor::Image; use adaptor::Image;
use anyhow::Result; use anyhow::Result;
use config::PREVIEW; use config::PREVIEW;
use parking_lot::Mutex; use parking_lot::Mutex;
use shared::{calculate_size, Throttle}; use shared::{calculate_size, Throttle, Url};
use tokio::{fs, sync::mpsc}; use tokio::{fs, sync::mpsc};
use crate::{emit, external, files::FilesOp, tasks::TaskOp}; use crate::{emit, external, files::FilesOp, tasks::TaskOp};
@ -15,7 +15,7 @@ pub(crate) struct Precache {
sch: mpsc::UnboundedSender<TaskOp>, sch: mpsc::UnboundedSender<TaskOp>,
pub(crate) size_handing: Mutex<BTreeSet<PathBuf>>, pub(crate) size_handing: Mutex<BTreeSet<Url>>,
} }
#[derive(Debug)] #[derive(Debug)]
@ -28,32 +28,32 @@ pub(crate) enum PrecacheOp {
#[derive(Debug)] #[derive(Debug)]
pub(crate) struct PrecacheOpSize { pub(crate) struct PrecacheOpSize {
pub id: usize, pub id: usize,
pub target: PathBuf, pub target: Url,
pub throttle: Arc<Throttle<(PathBuf, u64)>>, pub throttle: Arc<Throttle<(Url, u64)>>,
} }
#[derive(Debug)] #[derive(Debug)]
pub(crate) struct PrecacheOpMime { pub(crate) struct PrecacheOpMime {
pub id: usize, pub id: usize,
pub targets: Vec<PathBuf>, pub targets: Vec<Url>,
} }
#[derive(Debug)] #[derive(Debug)]
pub(crate) struct PrecacheOpImage { pub(crate) struct PrecacheOpImage {
pub id: usize, pub id: usize,
pub target: PathBuf, pub target: Url,
} }
#[derive(Debug)] #[derive(Debug)]
pub(crate) struct PrecacheOpVideo { pub(crate) struct PrecacheOpVideo {
pub id: usize, pub id: usize,
pub target: PathBuf, pub target: Url,
} }
#[derive(Debug)] #[derive(Debug)]
pub(crate) struct PrecacheOpPDF { pub(crate) struct PrecacheOpPDF {
pub id: usize, pub id: usize,
pub target: PathBuf, pub target: Url,
} }
impl Precache { impl Precache {
@ -79,7 +79,7 @@ impl Precache {
return Ok(self.sch.send(TaskOp::Adv(task.id, 1, 0))?); return Ok(self.sch.send(TaskOp::Adv(task.id, 1, 0))?);
} }
if let Ok(img) = fs::read(&task.target).await { 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))?; self.sch.send(TaskOp::Adv(task.id, 1, 0))?;
} }
@ -128,7 +128,7 @@ impl Precache {
handing.remove(path); 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)))); emit!(Files(FilesOp::Size(parent, BTreeMap::from_iter(buf))));
}); });
@ -136,7 +136,7 @@ impl Precache {
self.done(task.id) self.done(task.id)
} }
pub(crate) fn image(&self, id: usize, targets: Vec<PathBuf>) -> Result<()> { pub(crate) fn image(&self, id: usize, targets: Vec<Url>) -> Result<()> {
for target in targets { for target in targets {
self.sch.send(TaskOp::New(id, 0))?; self.sch.send(TaskOp::New(id, 0))?;
self.tx.send_blocking(PrecacheOp::Image(PrecacheOpImage { id, target }))?; self.tx.send_blocking(PrecacheOp::Image(PrecacheOpImage { id, target }))?;
@ -144,7 +144,7 @@ impl Precache {
self.done(id) self.done(id)
} }
pub(crate) fn video(&self, id: usize, targets: Vec<PathBuf>) -> Result<()> { pub(crate) fn video(&self, id: usize, targets: Vec<Url>) -> Result<()> {
for target in targets { for target in targets {
self.sch.send(TaskOp::New(id, 0))?; self.sch.send(TaskOp::New(id, 0))?;
self.tx.send_blocking(PrecacheOp::Video(PrecacheOpVideo { id, target }))?; self.tx.send_blocking(PrecacheOp::Video(PrecacheOpVideo { id, target }))?;
@ -152,7 +152,7 @@ impl Precache {
self.done(id) self.done(id)
} }
pub(crate) fn pdf(&self, id: usize, targets: Vec<PathBuf>) -> Result<()> { pub(crate) fn pdf(&self, id: usize, targets: Vec<Url>) -> Result<()> {
for target in targets { for target in targets {
self.sch.send(TaskOp::New(id, 0))?; self.sch.send(TaskOp::New(id, 0))?;
self.tx.send_blocking(PrecacheOp::Pdf(PrecacheOpPDF { id, target }))?; self.tx.send_blocking(PrecacheOp::Pdf(PrecacheOpPDF { id, target }))?;

View file

@ -2,6 +2,8 @@ use std::{env, path::{Path, PathBuf}};
use tokio::fs; use tokio::fs;
use crate::Url;
pub fn absolute_path(p: impl AsRef<Path>) -> PathBuf { pub fn absolute_path(p: impl AsRef<Path>) -> PathBuf {
let p = p.as_ref(); let p = p.as_ref();
if let Ok(p) = p.strip_prefix("~") { if let Ok(p) = p.strip_prefix("~") {
@ -12,6 +14,12 @@ pub fn absolute_path(p: impl AsRef<Path>) -> PathBuf {
std::fs::canonicalize(p).unwrap_or_else(|_| p.to_path_buf()) 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 { pub fn readable_path(p: &Path, base: &Path) -> String {
if let Ok(p) = p.strip_prefix(base) { if let Ok(p) = p.strip_prefix(base) {
return p.display().to_string(); return p.display().to_string();
@ -39,7 +47,7 @@ pub fn readable_size(size: u64) -> String {
format!("{:.1} {}", size, units[i]) 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 { let Some(name) = p.file_name().map(|n| n.to_os_string()) else {
return p; return p;
}; };

View file

@ -11,6 +11,7 @@ mod stream;
mod term; mod term;
mod throttle; mod throttle;
mod time; mod time;
mod url;
pub use chars::*; pub use chars::*;
pub use defer::*; pub use defer::*;
@ -23,3 +24,4 @@ pub use stream::*;
pub use term::*; pub use term::*;
pub use throttle::*; pub use throttle::*;
pub use time::*; pub use time::*;
pub use url::*;

View file

@ -5,7 +5,6 @@ pub enum MimeKind {
Empty, Empty,
Archive, Archive,
Dir,
Image, Image,
Video, Video,
@ -19,9 +18,7 @@ pub enum MimeKind {
impl MimeKind { impl MimeKind {
pub fn new(s: &str) -> Self { pub fn new(s: &str) -> Self {
if s == MIME_DIR { if s.starts_with("text/") || s.ends_with("/xml") {
Self::Dir
} else if s.starts_with("text/") || s.ends_with("/xml") {
Self::Text Self::Text
} else if s.starts_with("image/") { } else if s.starts_with("image/") {
Self::Image Self::Image

100
shared/src/url.rs Normal file
View file

@ -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<PathBuf> 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<String> 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<Url> for Url {
fn as_ref(&self) -> &Url { self }
}
impl AsRef<Path> for Url {
fn as_ref(&self) -> &Path { &self.path }
}
impl AsRef<OsStr> for Url {
fn as_ref(&self) -> &OsStr { self.path.as_os_str() }
}
impl Url {
#[inline]
pub fn new(url: impl Into<Url>, ctx: &Url) -> Self {
let mut url: Self = url.into();
url.scheme = ctx.scheme;
url
}
#[inline]
pub fn join(&self, path: impl AsRef<Path>) -> Self { Self::new(self.path.join(path), self) }
#[inline]
pub fn strip_prefix(&self, base: impl AsRef<Path>) -> 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<Url> { self.path.parent().map(|p| Self::new(p, self)) }
}