Intern browser directories
This commit is contained in:
parent
8f6e72fbd6
commit
6b1133e27c
4 changed files with 152 additions and 26 deletions
26
Cargo.lock
generated
26
Cargo.lock
generated
|
@ -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",
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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};
|
||||||
|
|
Loading…
Add table
Reference in a new issue