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 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);
}
}

View file

@ -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));
};

View file

@ -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)
}

View file

@ -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(&current.cwd))
} else {
readable_home(&current.cwd)

View file

@ -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()));

View file

@ -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;
};

View file

@ -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<PathBuf, String>),
Mimetype(BTreeMap<Url, String>),
Hover(Option<File>),
Peek(usize, Option<PathBuf>),
Peek(usize, Option<Url>),
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();

View file

@ -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<StreamBuf<UnboundedReceiverStream<PathBuf>>> {
pub fn fd(opt: FdOpt) -> Result<StreamBuf<UnboundedReceiverStream<Url>>> {
let mut child = Command::new("fd")
.arg("--base-directory")
.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 futures::TryFutureExt;
use shared::MimeKind;
use shared::{MimeKind, Url};
use tokio::process::Command;
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() {
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()
.zip(output.trim().lines())
.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() {
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");
}
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 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<Receiver<Result<PathBuf>>> {
pub fn fzf(opt: FzfOpt) -> Result<Receiver<Result<Url>>> {
let child =
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 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()) };
}
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 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<StreamBuf<UnboundedReceiverStream<PathBuf>>> {
pub fn rg(opt: RgOpt) -> Result<StreamBuf<UnboundedReceiverStream<Url>>> {
let mut child = Command::new("rg")
.current_dir(&opt.cwd)
.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 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<Receiver<Result<PathBuf>>> {
pub fn zoxide(opt: ZoxideOpt) -> Result<Receiver<Result<Url>>> {
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;
}
}

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 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<u64>,
pub(super) link_to: Option<PathBuf>,
@ -15,53 +16,53 @@ pub struct File {
impl File {
#[inline]
pub async fn from(path: &Path) -> Result<Self> {
let meta = fs::metadata(path).await?;
Ok(Self::from_meta(path, meta).await)
pub async fn from(url: Url) -> Result<Self> {
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<Cow<str>> {
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<Url> { self.url.parent_url() }
// --- Meta
#[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 config::manager::SortBy;
use shared::Url;
use tokio::fs;
use super::{File, FilesSorter};
@ -10,8 +11,8 @@ pub struct Files {
items: Vec<File>,
hidden: Vec<File>,
sizes: BTreeMap<PathBuf, u64>,
selected: BTreeSet<PathBuf>,
sizes: BTreeMap<Url, u64>,
selected: BTreeSet<Url>,
sorter: FilesSorter,
show_hidden: bool,
@ -39,22 +40,22 @@ impl Deref for Files {
}
impl Files {
pub async fn read(paths: &[impl AsRef<Path>]) -> Vec<File> {
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<Url>) -> Vec<File> {
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<Vec<File>> {
let mut it = fs::read_dir(path).await?;
pub async fn read_dir(url: &Url) -> Result<Vec<File>> {
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>) -> bool {
let old = self.selected.contains(path);
pub fn select(&mut self, url: &Url, state: Option<bool>) -> bool {
let old = self.selected.contains(url);
let new = if let Some(new) = state { new } else { !old };
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>) -> 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<usize>, state: Option<bool>) -> 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<PathBuf, u64>) -> bool {
pub fn update_size(&mut self, items: BTreeMap<Url, u64>) -> 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<File>) -> bool {
if !items.is_empty() {
if self.show_hidden {
@ -164,7 +166,7 @@ impl Files {
}
#[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]
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 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

View file

@ -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<File>),
Size(PathBuf, BTreeMap<PathBuf, u64>),
Search(PathBuf, Vec<File>),
IOErr(PathBuf),
Read(Url, Vec<File>),
Size(Url, BTreeMap<Url, u64>),
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()) }
}

View file

@ -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,

View file

@ -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<File>,
pub in_search: bool,
offset: usize,
cursor: usize,
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 {
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<Rect> {
let y = self.files.position(path)? - self.offset;
pub fn rect_current(&self, url: &Url) -> Option<Rect> {
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;

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 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<PathBuf>),
yanked: (bool, HashSet<Url>),
watcher: Watcher,
pub mimetype: HashMap<PathBuf, String>,
pub mimetype: HashMap<Url, String>,
}
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::<Vec<_>>();
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::<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 {
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<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));
if mimes.is_empty() {
return false;
@ -393,22 +379,15 @@ impl Manager {
true
}
#[inline]
pub fn update_hover(&mut self, file: Option<File>) -> 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<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 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<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);
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<F: FnOnce(&PreviewLock) -> 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
}

View file

@ -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<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> {
Adaptor::image_show(path, MANAGER.layout.preview_rect()).await?;
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 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<Folder>,
pub(super) history: BTreeMap<PathBuf, Folder>,
pub(super) history: BTreeMap<Url, Folder>,
pub(super) preview: Preview,
search: Option<JoinHandle<Result<()>>>,
pub(super) show_hidden: bool,
}
impl Tab {
pub fn new(path: &Path) -> Self {
impl From<Url> 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(&current);
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>) -> 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<PathBuf>) {
pub fn update_peek(&mut self, step: isize, url: Option<Url>) -> 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,
};

View file

@ -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);

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 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<RwLock<IndexMap<PathBuf, Option<PathBuf>>>>,
watched: Arc<RwLock<IndexMap<Url, Option<Url>>>>,
}
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::<BTreeSet<_>>();
@ -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::<Vec<_>>();
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<UnboundedReceiverStream<PathBuf>>,
watched: Arc<RwLock<IndexMap<PathBuf, Option<PathBuf>>>>,
mut rx: StreamBuf<UnboundedReceiverStream<Url>>,
watched: Arc<RwLock<IndexMap<Url, Option<Url>>>>,
) {
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<RwLock<IndexMap<PathBuf, Option<PathBuf>>>>) {
async fn dir_changed(url: &Url, watched: Arc<RwLock<IndexMap<Url, Option<Url>>>>) {
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::<Vec<_>>();
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)

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 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<PathBuf>) {
pub(super) fn precache_mime(&self, targets: Vec<Url>) {
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<PathBuf>) {
pub(super) fn precache_image(&self, targets: Vec<Url>) {
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<PathBuf>) {
pub(super) fn precache_video(&self, targets: Vec<Url>) {
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<PathBuf>) {
pub(super) fn precache_pdf(&self, targets: Vec<Url>) {
let name = format!("Precache of {} PDF files", targets.len());
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 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<TaskSummary> {
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<PathBuf>, dest: PathBuf, force: bool) -> bool {
pub fn file_cut(&self, src: &HashSet<Url>, 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<PathBuf>,
dest: PathBuf,
force: bool,
follow: bool,
) -> bool {
pub fn file_copy(&self, src: &HashSet<Url>, 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<PathBuf>, permanently: bool) -> bool {
pub fn file_remove(&self, targets: Vec<Url>, 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<PathBuf, String>) -> bool {
pub fn precache_mime(&self, targets: &[File], mimetype: &HashMap<Url, String>) -> 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<PathBuf, String>) -> bool {
let targets = mimetype
pub fn precache_image(&self, mimetype: &BTreeMap<Url, String>) -> bool {
let targets: Vec<_> = mimetype
.iter()
.filter(|(_, m)| MimeKind::new(m) == MimeKind::Image)
.map(|(p, _)| p.clone())
.collect::<Vec<_>>();
.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<PathBuf, String>) -> bool {
let targets = mimetype
pub fn precache_video(&self, mimetype: &BTreeMap<Url, String>) -> bool {
let targets: Vec<_> = mimetype
.iter()
.filter(|(_, m)| MimeKind::new(m) == MimeKind::Video)
.map(|(p, _)| p.clone())
.collect::<Vec<_>>();
.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<PathBuf, String>) -> bool {
let targets = mimetype
pub fn precache_pdf(&self, mimetype: &BTreeMap<Url, String>) -> bool {
let targets: Vec<_> = mimetype
.iter()
.filter(|(_, m)| MimeKind::new(m) == MimeKind::PDF)
.map(|(p, _)| p.clone())
.collect::<Vec<_>>();
.map(|(u, _)| u.clone())
.collect();
if !targets.is_empty() {
self.scheduler.precache_pdf(targets);

View file

@ -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?;

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 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<TaskOp>,
pub(crate) size_handing: Mutex<BTreeSet<PathBuf>>,
pub(crate) size_handing: Mutex<BTreeSet<Url>>,
}
#[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<Throttle<(PathBuf, u64)>>,
pub target: Url,
pub throttle: Arc<Throttle<(Url, u64)>>,
}
#[derive(Debug)]
pub(crate) struct PrecacheOpMime {
pub id: usize,
pub targets: Vec<PathBuf>,
pub targets: Vec<Url>,
}
#[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<PathBuf>) -> Result<()> {
pub(crate) fn image(&self, id: usize, targets: Vec<Url>) -> 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<PathBuf>) -> Result<()> {
pub(crate) fn video(&self, id: usize, targets: Vec<Url>) -> 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<PathBuf>) -> Result<()> {
pub(crate) fn pdf(&self, id: usize, targets: Vec<Url>) -> Result<()> {
for target in targets {
self.sch.send(TaskOp::New(id, 0))?;
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 crate::Url;
pub fn absolute_path(p: impl AsRef<Path>) -> PathBuf {
let p = p.as_ref();
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())
}
#[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;
};

View file

@ -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::*;

View file

@ -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

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)) }
}