Index disk serialization without DB

This commit is contained in:
Antoine Gersant 2024-10-06 12:50:39 -07:00
parent a5061dfc92
commit 1a8bf91628
4 changed files with 49 additions and 21 deletions

1
.gitignore vendored
View file

@ -14,6 +14,7 @@ TestConfig.toml
*.sqlite *.sqlite
**/*.sqlite-shm **/*.sqlite-shm
**/*.sqlite-wal **/*.sqlite-wal
collection.index
polaris.log polaris.log
polaris.ndb polaris.ndb
polaris.pid polaris.pid

View file

@ -183,7 +183,7 @@ impl App {
let auth_secret = settings_manager.get_auth_secret().await?; let auth_secret = settings_manager.get_auth_secret().await?;
let ddns_manager = ddns::Manager::new(db.clone()); let ddns_manager = ddns::Manager::new(db.clone());
let user_manager = user::Manager::new(db.clone(), auth_secret); let user_manager = user::Manager::new(db.clone(), auth_secret);
let index_manager = index::Manager::new(db.clone()).await; let index_manager = index::Manager::new(&paths.data_dir_path).await?;
let scanner = scanner::Scanner::new( let scanner = scanner::Scanner::new(
index_manager.clone(), index_manager.clone(),
settings_manager.clone(), settings_manager.clone(),

View file

@ -1,6 +1,6 @@
use std::{ use std::{
collections::HashMap, collections::HashMap,
path::PathBuf, path::{Path, PathBuf},
sync::{Arc, RwLock}, sync::{Arc, RwLock},
}; };
@ -10,7 +10,6 @@ use serde::{Deserialize, Serialize};
use tokio::task::spawn_blocking; use tokio::task::spawn_blocking;
use crate::app::{scanner, Error}; use crate::app::{scanner, Error};
use crate::db::DB;
mod browser; mod browser;
mod collection; mod collection;
@ -24,20 +23,28 @@ use storage::{store_song, AlbumKey, ArtistKey, GenreKey, InternPath, SongKey};
#[derive(Clone)] #[derive(Clone)]
pub struct Manager { pub struct Manager {
db: DB, index_file_path: PathBuf,
index: Arc<RwLock<Index>>, // Not a tokio RwLock as we want to do CPU-bound work with Index index: Arc<RwLock<Index>>, // Not a tokio RwLock as we want to do CPU-bound work with Index and lock this inside spawn_blocking()
} }
impl Manager { impl Manager {
pub async fn new(db: DB) -> Self { pub async fn new(directory: &Path) -> Result<Self, Error> {
tokio::fs::create_dir_all(directory)
.await
.map_err(|e| Error::Io(directory.to_owned(), e))?;
let mut index_manager = Self { let mut index_manager = Self {
db, index_file_path: directory.join("collection.index"),
index: Arc::default(), index: Arc::default(),
}; };
if let Err(e) = index_manager.try_restore_index().await { if let Err(e) = index_manager.try_restore_index().await {
error!("Failed to restore index: {}", e); error!("Failed to restore index: {}", e);
} else {
info!("Restored collection index from disk");
} }
index_manager
Ok(index_manager)
} }
pub async fn replace_index(&mut self, new_index: Index) { pub async fn replace_index(&mut self, new_index: Index) {
@ -57,22 +64,26 @@ impl Manager {
Ok(s) => s, Ok(s) => s,
Err(_) => return Err(Error::IndexSerializationError), Err(_) => return Err(Error::IndexSerializationError),
}; };
sqlx::query!("UPDATE collection_index SET content = $1", serialized) tokio::fs::write(&self.index_file_path, &serialized[..])
.execute(self.db.connect().await?.as_mut()) .await
.await?; .map_err(|e| Error::Io(self.index_file_path.clone(), e))?;
Ok(()) Ok(())
} }
async fn try_restore_index(&mut self) -> Result<bool, Error> { async fn try_restore_index(&mut self) -> Result<bool, Error> {
let serialized = sqlx::query_scalar!("SELECT content FROM collection_index") match tokio::fs::try_exists(&self.index_file_path).await {
.fetch_one(self.db.connect().await?.as_mut()) Ok(false) => {
.await?; info!("No existing index to restore");
return Ok(false);
let Some(serialized) = serialized else { }
info!("Database did not contain a collection to restore"); Ok(true) => (),
return Ok(false); Err(e) => return Err(Error::Io(self.index_file_path.clone(), e)),
}; };
let serialized = tokio::fs::read(&self.index_file_path)
.await
.map_err(|e| Error::Io(self.index_file_path.clone(), e))?;
let index = match bitcode::deserialize(&serialized[..]) { let index = match bitcode::deserialize(&serialized[..]) {
Ok(i) => i, Ok(i) => i,
Err(_) => return Err(Error::IndexDeserializationError), Err(_) => return Err(Error::IndexDeserializationError),
@ -354,3 +365,20 @@ impl Default for Builder {
Self::new() Self::new()
} }
} }
#[cfg(test)]
mod test {
use crate::{
app::{index, test},
test_name,
};
#[tokio::test]
async fn can_persist_index() {
let mut ctx = test::ContextBuilder::new(test_name!()).build().await;
assert_eq!(ctx.index_manager.try_restore_index().await.unwrap(), false);
let index = index::Builder::new().build();
ctx.index_manager.persist_index(&index).await.unwrap();
assert_eq!(ctx.index_manager.try_restore_index().await.unwrap(), true);
}
}

View file

@ -52,10 +52,9 @@ impl ContextBuilder {
} }
pub async fn build(self) -> Context { pub async fn build(self) -> Context {
let db_path = self.test_directory.join("db.sqlite"); let db_path = self.test_directory.join("db.sqlite");
let ndb_path = self.test_directory.join("polaris.ndb");
let db = DB::new(&db_path).await.unwrap(); let db = DB::new(&db_path).await.unwrap();
let ndb_manager = ndb::Manager::new(&ndb_path).unwrap(); let ndb_manager = ndb::Manager::new(&self.test_directory).unwrap();
let settings_manager = settings::Manager::new(db.clone()); let settings_manager = settings::Manager::new(db.clone());
let auth_secret = settings_manager.get_auth_secret().await.unwrap(); let auth_secret = settings_manager.get_auth_secret().await.unwrap();
let user_manager = user::Manager::new(db.clone(), auth_secret); let user_manager = user::Manager::new(db.clone(), auth_secret);
@ -67,7 +66,7 @@ impl ContextBuilder {
vfs_manager.clone(), vfs_manager.clone(),
ddns_manager.clone(), ddns_manager.clone(),
); );
let index_manager = index::Manager::new(db.clone()).await; let index_manager = index::Manager::new(&self.test_directory).await.unwrap();
let scanner = scanner::Scanner::new( let scanner = scanner::Scanner::new(
index_manager.clone(), index_manager.clone(),
settings_manager.clone(), settings_manager.clone(),