feat: expose read-only local review CLI
This commit is contained in:
+45
-17
@@ -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)]
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user