Improve search performance more
This commit is contained in:
parent
fd93917c9f
commit
f2e207b732
1 changed files with 43 additions and 20 deletions
63
src/tab.rs
63
src/tab.rs
|
|
@ -51,7 +51,7 @@ use std::{
|
||||||
io::{BufRead, BufReader},
|
io::{BufRead, BufReader},
|
||||||
os::unix::fs::MetadataExt,
|
os::unix::fs::MetadataExt,
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
sync::{atomic, Arc, Mutex},
|
sync::{atomic, Arc, Mutex, RwLock},
|
||||||
time::{Duration, Instant, SystemTime},
|
time::{Duration, Instant, SystemTime},
|
||||||
};
|
};
|
||||||
use tokio::sync::mpsc;
|
use tokio::sync::mpsc;
|
||||||
|
|
@ -285,7 +285,7 @@ fn format_permissions(metadata: &Metadata, owner: PermissionOwner) -> String {
|
||||||
perms.join(" ")
|
perms.join(" ")
|
||||||
}
|
}
|
||||||
|
|
||||||
struct FormatTime(std::time::SystemTime);
|
struct FormatTime(SystemTime);
|
||||||
|
|
||||||
impl FormatTime {
|
impl FormatTime {
|
||||||
fn from_secs(secs: i64) -> Option<Self> {
|
fn from_secs(secs: i64) -> Option<Self> {
|
||||||
|
|
@ -321,7 +321,7 @@ impl Display for FormatTime {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn format_time(time: std::time::SystemTime) -> FormatTime {
|
fn format_time(time: SystemTime) -> FormatTime {
|
||||||
FormatTime(time)
|
FormatTime(time)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -530,7 +530,7 @@ pub fn scan_path(tab_path: &PathBuf, sizes: IconSizes) -> Vec<Item> {
|
||||||
items
|
items
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn scan_search<F: Fn(PathBuf, String, Metadata) -> bool + Sync>(
|
pub fn scan_search<F: Fn(&Path, &str, Metadata) -> bool + Sync>(
|
||||||
tab_path: &PathBuf,
|
tab_path: &PathBuf,
|
||||||
term: &str,
|
term: &str,
|
||||||
callback: F,
|
callback: F,
|
||||||
|
|
@ -571,7 +571,7 @@ pub fn scan_search<F: Fn(PathBuf, String, Metadata) -> bool + Sync>(
|
||||||
if regex.is_match(file_name) {
|
if regex.is_match(file_name) {
|
||||||
let path = entry.path();
|
let path = entry.path();
|
||||||
|
|
||||||
let metadata = match fs::metadata(&path) {
|
let metadata = match entry.metadata() {
|
||||||
Ok(ok) => ok,
|
Ok(ok) => ok,
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
log::warn!("failed to read metadata for entry at {:?}: {}", path, err);
|
log::warn!("failed to read metadata for entry at {:?}: {}", path, err);
|
||||||
|
|
@ -579,7 +579,8 @@ pub fn scan_search<F: Fn(PathBuf, String, Metadata) -> bool + Sync>(
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if !callback(path.to_path_buf(), file_name.to_string(), metadata) {
|
//TODO: use entry.into_path?
|
||||||
|
if !callback(path, file_name, metadata) {
|
||||||
return ignore::WalkState::Quit;
|
return ignore::WalkState::Quit;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1036,6 +1037,13 @@ impl ItemMetadata {
|
||||||
Self::SimpleFile { .. } => false,
|
Self::SimpleFile { .. } => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn modified(&self) -> Option<SystemTime> {
|
||||||
|
match self {
|
||||||
|
Self::Path { metadata, .. } => metadata.modified().ok(),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
|
|
@ -1430,6 +1438,7 @@ impl Mode {
|
||||||
struct SearchContext {
|
struct SearchContext {
|
||||||
results_rx: mpsc::Receiver<(PathBuf, String, Metadata)>,
|
results_rx: mpsc::Receiver<(PathBuf, String, Metadata)>,
|
||||||
ready: Arc<atomic::AtomicBool>,
|
ready: Arc<atomic::AtomicBool>,
|
||||||
|
last_modified_opt: Arc<RwLock<Option<SystemTime>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct SearchContextWrapper(Option<SearchContext>);
|
pub struct SearchContextWrapper(Option<SearchContext>);
|
||||||
|
|
@ -1945,6 +1954,7 @@ impl Tab {
|
||||||
let min = range.0.min(range.1);
|
let min = range.0.min(range.1);
|
||||||
let max = range.0.max(range.1);
|
let max = range.0.max(range.1);
|
||||||
let (sort_name, sort_direction, _) = self.sort_options();
|
let (sort_name, sort_direction, _) = self.sort_options();
|
||||||
|
//TODO: this assumes the default sort order!
|
||||||
if sort_name == HeadingOptions::Name && sort_direction {
|
if sort_name == HeadingOptions::Name && sort_direction {
|
||||||
// A default/unsorted tab's view is consistent with how the
|
// A default/unsorted tab's view is consistent with how the
|
||||||
// Items are laid out internally (items_opt), so Items can be
|
// Items are laid out internally (items_opt), so Items can be
|
||||||
|
|
@ -2490,13 +2500,9 @@ impl Tab {
|
||||||
context.results_rx.blocking_recv()
|
context.results_rx.blocking_recv()
|
||||||
{
|
{
|
||||||
//TODO: combine this with column_sort logic, they must match!
|
//TODO: combine this with column_sort logic, they must match!
|
||||||
let get_modified = |x: &Item| match &x.metadata {
|
|
||||||
ItemMetadata::Path { metadata, .. } => metadata.modified().ok(),
|
|
||||||
_ => None,
|
|
||||||
};
|
|
||||||
let item_modified = metadata.modified().ok();
|
let item_modified = metadata.modified().ok();
|
||||||
let index = match items.binary_search_by(|other| {
|
let index = match items.binary_search_by(|other| {
|
||||||
item_modified.cmp(&get_modified(&other))
|
item_modified.cmp(&other.metadata.modified())
|
||||||
}) {
|
}) {
|
||||||
Ok(index) => index,
|
Ok(index) => index,
|
||||||
Err(index) => index,
|
Err(index) => index,
|
||||||
|
|
@ -2516,6 +2522,11 @@ impl Tab {
|
||||||
}
|
}
|
||||||
if items.len() >= MAX_SEARCH_RESULTS {
|
if items.len() >= MAX_SEARCH_RESULTS {
|
||||||
items.truncate(MAX_SEARCH_RESULTS);
|
items.truncate(MAX_SEARCH_RESULTS);
|
||||||
|
if let Some(last_modified) =
|
||||||
|
items.last().and_then(|item| item.metadata.modified())
|
||||||
|
{
|
||||||
|
*context.last_modified_opt.write().unwrap() = Some(last_modified);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
log::warn!("search ready but items array is empty");
|
log::warn!("search ready but items array is empty");
|
||||||
|
|
@ -2763,13 +2774,8 @@ impl Tab {
|
||||||
}),
|
}),
|
||||||
HeadingOptions::Modified => {
|
HeadingOptions::Modified => {
|
||||||
items.sort_by(|a, b| {
|
items.sort_by(|a, b| {
|
||||||
let get_modified = |x: &Item| match &x.metadata {
|
let a_modified = a.1.metadata.modified();
|
||||||
ItemMetadata::Path { metadata, .. } => metadata.modified().ok(),
|
let b_modified = b.1.metadata.modified();
|
||||||
_ => None,
|
|
||||||
};
|
|
||||||
|
|
||||||
let a_modified = get_modified(a.1);
|
|
||||||
let b_modified = get_modified(b.1);
|
|
||||||
if folders_first {
|
if folders_first {
|
||||||
match (a.1.metadata.is_dir(), b.1.metadata.is_dir()) {
|
match (a.1.metadata.is_dir(), b.1.metadata.is_dir()) {
|
||||||
(true, false) => Ordering::Less,
|
(true, false) => Ordering::Less,
|
||||||
|
|
@ -4160,7 +4166,7 @@ impl Tab {
|
||||||
let message = {
|
let message = {
|
||||||
let path = path.clone();
|
let path = path.clone();
|
||||||
tokio::task::spawn_blocking(move || {
|
tokio::task::spawn_blocking(move || {
|
||||||
let start = std::time::Instant::now();
|
let start = Instant::now();
|
||||||
//TODO: configurable thumbnail size?
|
//TODO: configurable thumbnail size?
|
||||||
let thumbnail_size = (ICON_SIZE_GRID * ICON_SCALE_MAX) as u32;
|
let thumbnail_size = (ICON_SIZE_GRID * ICON_SCALE_MAX) as u32;
|
||||||
let thumbnail = ItemThumbnail::new(&path, mime, thumbnail_size);
|
let thumbnail = ItemThumbnail::new(&path, mime, thumbnail_size);
|
||||||
|
|
@ -4201,12 +4207,14 @@ impl Tab {
|
||||||
let (results_tx, results_rx) = mpsc::channel(65536);
|
let (results_tx, results_rx) = mpsc::channel(65536);
|
||||||
|
|
||||||
let ready = Arc::new(atomic::AtomicBool::new(false));
|
let ready = Arc::new(atomic::AtomicBool::new(false));
|
||||||
|
let last_modified_opt = Arc::new(RwLock::new(None));
|
||||||
output
|
output
|
||||||
.send(Message::SearchContext(
|
.send(Message::SearchContext(
|
||||||
location.clone(),
|
location.clone(),
|
||||||
SearchContextWrapper(Some(SearchContext {
|
SearchContextWrapper(Some(SearchContext {
|
||||||
results_rx,
|
results_rx,
|
||||||
ready: ready.clone(),
|
ready: ready.clone(),
|
||||||
|
last_modified_opt: last_modified_opt.clone(),
|
||||||
})),
|
})),
|
||||||
))
|
))
|
||||||
.await
|
.await
|
||||||
|
|
@ -4217,7 +4225,22 @@ impl Tab {
|
||||||
let output = output.clone();
|
let output = output.clone();
|
||||||
tokio::task::spawn_blocking(move || {
|
tokio::task::spawn_blocking(move || {
|
||||||
scan_search(&path, &term, move |path, name, metadata| -> bool {
|
scan_search(&path, &term, move |path, name, metadata| -> bool {
|
||||||
match results_tx.blocking_send((path, name, metadata)) {
|
// Don't send if the result is too old
|
||||||
|
if let Some(last_modified) = *last_modified_opt.read().unwrap() {
|
||||||
|
if let Ok(modified) = metadata.modified() {
|
||||||
|
if metadata.modified().ok() < Some(last_modified) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
match results_tx.blocking_send((
|
||||||
|
path.to_path_buf(),
|
||||||
|
name.to_string(),
|
||||||
|
metadata,
|
||||||
|
)) {
|
||||||
Ok(()) => {
|
Ok(()) => {
|
||||||
if !ready.swap(true, atomic::Ordering::SeqCst) {
|
if !ready.swap(true, atomic::Ordering::SeqCst) {
|
||||||
// Wake up update method
|
// Wake up update method
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue