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-shm
**/*.sqlite-wal
collection.index
polaris.log
polaris.ndb
polaris.pid

View file

@ -183,7 +183,7 @@ impl App {
let auth_secret = settings_manager.get_auth_secret().await?;
let ddns_manager = ddns::Manager::new(db.clone());
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(
index_manager.clone(),
settings_manager.clone(),

View file

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