feat: expose read-only local review CLI

This commit is contained in:
Valdemar Lorenzen
2026-10-16 15:42:33 +02:00
parent 6ae8bf06a8
commit a6310594c5
7 changed files with 484 additions and 40 deletions
+45 -17
View File
@@ -299,6 +299,50 @@ pub struct Store {
}
impl Store {
/// Open existing storage without creating files, changing permissions, or migrating schemas.
pub fn open_read_only(directory: &Path) -> Result<Option<Self>, String> {
let path = directory.join("reviews.sqlite3");
let metadata = match fs::symlink_metadata(&path) {
Ok(metadata) => metadata,
Err(error) if error.kind() == std::io::ErrorKind::NotFound => return Ok(None),
Err(error) => return Err(error.to_string()),
};
if !metadata.is_file() || metadata.file_type().is_symlink() {
return Err("Review database must be a regular file, not a symbolic link".into());
}
let path = fs::canonicalize(directory)
.map_err(|error| format!("Cannot resolve review directory: {error}"))?
.join("reviews.sqlite3");
let connection = Connection::open_with_flags(
&path,
OpenFlags::SQLITE_OPEN_READ_ONLY
| OpenFlags::SQLITE_OPEN_NO_MUTEX
| OpenFlags::SQLITE_OPEN_NOFOLLOW,
)
.map_err(database_error)?;
Self::check_schema(&connection)?;
Ok(Some(Self { connection }))
}
fn check_schema(connection: &Connection) -> Result<(), String> {
let version: i64 = connection
.pragma_query_value(None, "user_version", |row| row.get(0))
.map_err(database_error)?;
let schema: Vec<String> = connection
.prepare("SELECT sql FROM sqlite_schema WHERE name NOT GLOB 'sqlite_*' ORDER BY name")
.map_err(database_error)?
.query_map([], |row| row.get(0))
.map_err(database_error)?
.collect::<Result<_, _>>()
.map_err(database_error)?;
if version != 1 || schema != [SCHEMA] {
return Err(format!(
"Unsupported review database schema (version {version}); existing data was not changed"
));
}
Ok(())
}
pub fn open(directory: &Path) -> Result<Self, String> {
let mut builder = fs::DirBuilder::new();
builder.recursive(true);
@@ -352,23 +396,7 @@ impl Store {
.map_err(database_error)?;
transaction.commit().map_err(database_error)?;
} else {
let version: i64 = connection
.pragma_query_value(None, "user_version", |row| row.get(0))
.map_err(database_error)?;
let schema: Vec<String> = connection
.prepare(
"SELECT sql FROM sqlite_schema WHERE name NOT GLOB 'sqlite_*' ORDER BY name",
)
.map_err(database_error)?
.query_map([], |row| row.get(0))
.map_err(database_error)?
.collect::<Result<_, _>>()
.map_err(database_error)?;
if version != 1 || schema != [SCHEMA] {
return Err(format!(
"Unsupported review database schema (version {version}); existing data was not changed"
));
}
Self::check_schema(&connection)?;
}
#[cfg(unix)]
{