use std::{
collections::HashMap,
fs,
io::ErrorKind,
path::{Path, PathBuf},
sync::{Arc, Mutex, MutexGuard, OnceLock},
};
use rusqlite::Connection;
use super::migrate_index;
use crate::sqlite_support::{OwnerOnlySqlite, ParentDirectoryPrivacy};
const INDEX_FILE_NAME: &str = "index.sqlite3";
static INDEX_CONNECTIONS: OnceLock<Mutex<HashMap<PathBuf, Arc<Mutex<Connection>>>>> =
OnceLock::new();
pub(in crate::session) fn sync_committed_writes(
connection: MutexGuard<'_, Connection>,
) -> anyhow::Result<()> {
let wal = wal_path(&connection)?;
drop(connection);
match fs::OpenOptions::new().write(true).open(&wal) {
Ok(file) => Ok(file.sync_data()?),
Err(error) if error.kind() == ErrorKind::NotFound => Ok(()),
Err(error) => Err(error.into()),
}
}
fn wal_path(connection: &Connection) -> anyhow::Result<PathBuf> {
let mut file = connection.query_row(
"select file from pragma_database_list where name = 'main'",
[],
|row| Ok(row.get_ref(0)?.as_bytes()?.to_vec()),
)?;
anyhow::ensure!(!file.is_empty(), "session index has no database file");
file.extend_from_slice(b"-wal");
#[cfg(unix)]
let wal = {
use std::os::unix::ffi::OsStringExt;
PathBuf::from(std::ffi::OsString::from_vec(file))
};
#[cfg(not(unix))]
let wal = PathBuf::from(String::from_utf8(file)?);
Ok(wal)
}
pub(in crate::session) fn open_index(
session_root: &Path,
) -> anyhow::Result<Arc<Mutex<Connection>>> {
let path = session_root.join(INDEX_FILE_NAME);
let connections = INDEX_CONNECTIONS.get_or_init(|| Mutex::new(HashMap::new()));
if let Some(connection) = connections
.lock()
.expect("session index cache poisoned")
.get(&path)
{
return Ok(Arc::clone(connection));
}
let database = OwnerOnlySqlite::open(
&path,
ParentDirectoryPrivacy::EnforcePrivate,
|connection| migrate_index(connection).map_err(IndexOpenError::from_migration),
)?;
let opened = Arc::new(Mutex::new(database.open_write_connection()?));
let mut connections = connections.lock().expect("session index cache poisoned");
Ok(Arc::clone(connections.entry(path).or_insert(opened)))
}
#[derive(Debug, thiserror::Error)]
enum IndexOpenError {
#[error("session index I/O failed: {0}")]
Io(#[from] std::io::Error),
#[error("session index SQLite operation failed: {0}")]
Sqlite(#[from] rusqlite::Error),
#[error(transparent)]
Migration(anyhow::Error),
}
impl IndexOpenError {
fn from_migration(error: anyhow::Error) -> Self {
match error.downcast() {
Ok(sqlite) => Self::Sqlite(sqlite),
Err(other) => Self::Migration(other),
}
}
}
#[cfg(test)]
#[path = "index_connection_tests.rs"]
mod tests;