Intern browser directories

This commit is contained in:
Antoine Gersant 2024-08-04 19:07:10 -07:00
parent 8f6e72fbd6
commit 6b1133e27c
4 changed files with 152 additions and 26 deletions

26
Cargo.lock generated
View file

@ -526,6 +526,19 @@ dependencies = [
"libc", "libc",
] ]
[[package]]
name = "dashmap"
version = "5.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856"
dependencies = [
"cfg-if",
"hashbrown",
"lock_api",
"once_cell",
"parking_lot_core",
]
[[package]] [[package]]
name = "der" name = "der"
version = "0.7.9" version = "0.7.9"
@ -851,6 +864,7 @@ checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
dependencies = [ dependencies = [
"ahash", "ahash",
"allocator-api2", "allocator-api2",
"serde",
] ]
[[package]] [[package]]
@ -1107,6 +1121,17 @@ dependencies = [
"wasm-bindgen", "wasm-bindgen",
] ]
[[package]]
name = "lasso2"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a030549b8dfea08d7981ad0381acb4bc0cdc7a4fd616e3b9659c31dc0a3474fd"
dependencies = [
"dashmap",
"hashbrown",
"serde",
]
[[package]] [[package]]
name = "lazy_static" name = "lazy_static"
version = "1.5.0" version = "1.5.0"
@ -1609,6 +1634,7 @@ dependencies = [
"http 1.1.0", "http 1.1.0",
"id3", "id3",
"image", "image",
"lasso2",
"lewton", "lewton",
"log", "log",
"metaflac", "metaflac",

View file

@ -19,6 +19,7 @@ getopts = "0.2.21"
headers = "0.4" headers = "0.4"
http = "1.1.0" http = "1.1.0"
id3 = "1.14.0" id3 = "1.14.0"
lasso2 = { version = "0.8.2", features = ["multi-threaded", "serialize"] }
lewton = "0.10.2" lewton = "0.10.2"
log = "0.4.22" log = "0.4.22"
metaflac = "0.2.7" metaflac = "0.2.7"

View file

@ -1,8 +1,9 @@
use std::{ use std::{
path::PathBuf, path::{Path, PathBuf},
sync::{Arc, RwLock}, sync::{Arc, RwLock},
}; };
use lasso2::ThreadedRodeo;
use log::{error, info}; use log::{error, info};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use tokio::task::spawn_blocking; use tokio::task::spawn_blocking;
@ -188,19 +189,26 @@ pub struct Index {
impl Default for Index { impl Default for Index {
fn default() -> Self { fn default() -> Self {
Self { Self {
browser: browser::Browser::new(), browser: browser::Browser::new(Arc::default()),
collection: Default::default(), collection: Default::default(),
} }
} }
} }
#[derive(Default)]
pub struct Builder { pub struct Builder {
browser_builder: browser::Builder, browser_builder: browser::Builder,
collection_builder: collection::Builder, collection_builder: collection::Builder,
} }
impl Builder { impl Builder {
pub fn new() -> Self {
let strings = Arc::new(ThreadedRodeo::new());
Self {
browser_builder: browser::Builder::new(strings),
collection_builder: collection::Builder::default(),
}
}
pub fn add_directory(&mut self, directory: scanner::Directory) { pub fn add_directory(&mut self, directory: scanner::Directory) {
self.browser_builder.add_directory(directory); self.browser_builder.add_directory(directory);
} }
@ -217,3 +225,45 @@ impl Builder {
} }
} }
} }
impl Default for Builder {
fn default() -> Self {
Self::new()
}
}
#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize, Deserialize)]
pub(self) struct PathID(lasso2::Spur);
pub(self) trait InternPath {
fn get_or_intern(self, strings: &mut Arc<ThreadedRodeo>) -> Option<PathID>;
fn get(self, strings: &Arc<ThreadedRodeo>) -> Option<PathID>;
}
impl<P: AsRef<Path>> InternPath for P {
fn get_or_intern(self, strings: &mut Arc<ThreadedRodeo>) -> Option<PathID> {
let id = self
.as_ref()
.as_os_str()
.to_str()
.map(|s| strings.get_or_intern(s))
.map(PathID);
if id.is_none() {
error!("Unsupported path: `{}`", self.as_ref().to_string_lossy());
}
id
}
fn get(self, strings: &Arc<ThreadedRodeo>) -> Option<PathID> {
let id = self
.as_ref()
.as_os_str()
.to_str()
.and_then(|s| strings.get(s))
.map(PathID);
if id.is_none() {
error!("Unsupported path: `{}`", self.as_ref().to_string_lossy());
}
id
}
}

View file

@ -1,12 +1,16 @@
use std::{ use std::{
collections::{HashMap, HashSet}, collections::HashMap,
ffi::OsStr,
hash::Hash, hash::Hash,
path::{Path, PathBuf}, path::{Path, PathBuf},
sync::Arc,
}; };
use lasso2::ThreadedRodeo;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use trie_rs::{Trie, TrieBuilder}; use trie_rs::{Trie, TrieBuilder};
use crate::app::index::{InternPath, PathID};
use crate::app::{scanner, Error}; use crate::app::{scanner, Error};
#[derive(Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] #[derive(Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
@ -17,23 +21,42 @@ pub enum File {
#[derive(Serialize, Deserialize)] #[derive(Serialize, Deserialize)]
pub struct Browser { pub struct Browser {
directories: HashMap<PathBuf, Vec<File>>, strings: Arc<ThreadedRodeo>,
directories: HashMap<PathID, Vec<storage::File>>,
flattened: Trie<String>, flattened: Trie<String>,
} }
impl Browser { impl Browser {
pub fn new() -> Self { pub fn new(strings: Arc<ThreadedRodeo>) -> Self {
Self { Self {
strings,
directories: HashMap::new(), directories: HashMap::new(),
flattened: TrieBuilder::new().build(), flattened: TrieBuilder::new().build(),
} }
} }
pub fn browse<P: AsRef<Path>>(&self, virtual_path: P) -> Result<Vec<File>, Error> { pub fn browse<P: AsRef<Path>>(&self, virtual_path: P) -> Result<Vec<File>, Error> {
let Some(files) = self.directories.get(virtual_path.as_ref()) else { let path_id = virtual_path
.as_ref()
.get(&self.strings)
.ok_or_else(|| Error::DirectoryNotFound(virtual_path.as_ref().to_owned()))?;
let Some(files) = self.directories.get(&path_id) else {
return Err(Error::DirectoryNotFound(virtual_path.as_ref().to_owned())); return Err(Error::DirectoryNotFound(virtual_path.as_ref().to_owned()));
}; };
Ok(files.clone()) Ok(files
.iter()
.map(|f| {
let path_id = match f {
storage::File::Directory(p) => p,
storage::File::Song(p) => p,
};
let path = Path::new(OsStr::new(self.strings.resolve(&path_id.0))).to_owned();
match f {
storage::File::Directory(_) => File::Directory(path),
storage::File::Song(_) => File::Song(path),
}
})
.collect())
} }
pub fn flatten<P: AsRef<Path>>(&self, virtual_path: P) -> Result<Vec<PathBuf>, Error> { pub fn flatten<P: AsRef<Path>>(&self, virtual_path: P) -> Result<Vec<PathBuf>, Error> {
@ -56,38 +79,53 @@ impl Browser {
} }
pub struct Builder { pub struct Builder {
directories: HashMap<PathBuf, Vec<File>>, strings: Arc<ThreadedRodeo>,
directories: HashMap<PathID, Vec<storage::File>>,
flattened: TrieBuilder<String>, flattened: TrieBuilder<String>,
} }
impl Default for Builder { impl Builder {
fn default() -> Self { pub fn new(strings: Arc<ThreadedRodeo>) -> Self {
Self { Self {
directories: Default::default(), strings,
flattened: Default::default(), directories: HashMap::default(),
flattened: TrieBuilder::default(),
} }
} }
}
impl Builder {
pub fn add_directory(&mut self, directory: scanner::Directory) { pub fn add_directory(&mut self, directory: scanner::Directory) {
self.directories let Some(path_id) = directory.virtual_path.get_or_intern(&mut self.strings) else {
.entry(directory.virtual_path.clone()) return;
.or_default(); };
if let Some(parent) = directory.virtual_parent { let Some(parent_id) = directory
self.directories .virtual_parent
.entry(parent.clone()) .and_then(|p| p.get_or_intern(&mut self.strings))
.or_default() else {
.push(File::Directory(directory.virtual_path)); return;
} };
self.directories.entry(path_id.clone()).or_default();
self.directories
.entry(parent_id)
.or_default()
.push(storage::File::Directory(path_id));
} }
pub fn add_song(&mut self, song: &scanner::Song) { pub fn add_song(&mut self, song: &scanner::Song) {
let Some(path_id) = (&song.virtual_path).get_or_intern(&mut self.strings) else {
return;
};
let Some(parent_id) = (&song.virtual_parent).get_or_intern(&mut self.strings) else {
return;
};
self.directories self.directories
.entry(song.virtual_parent.clone()) .entry(parent_id)
.or_default() .or_default()
.push(File::Song(song.virtual_path.clone())); .push(storage::File::Song(path_id));
self.flattened.push( self.flattened.push(
song.virtual_path song.virtual_path
@ -102,12 +140,23 @@ impl Builder {
directory.sort(); directory.sort();
} }
Browser { Browser {
strings: self.strings,
directories: self.directories, directories: self.directories,
flattened: self.flattened.build(), flattened: self.flattened.build(),
} }
} }
} }
mod storage {
use super::*;
#[derive(Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
pub enum File {
Directory(PathID),
Song(PathID),
}
}
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};