mirror of https://git.meli.delivery/meli/meli
You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
162 lines
5.1 KiB
Rust
162 lines
5.1 KiB
Rust
/*
|
|
* meli - melib
|
|
*
|
|
* Copyright 2020 Manos Pitsidianakis
|
|
*
|
|
* This file is part of meli.
|
|
*
|
|
* meli is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* meli is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with meli. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
use crate::{error::*, logging::log, Envelope};
|
|
use rusqlite::types::{FromSql, FromSqlError, FromSqlResult, ToSql, ToSqlOutput};
|
|
pub use rusqlite::{self, params, Connection};
|
|
use std::path::PathBuf;
|
|
|
|
#[derive(Copy, Clone, Debug)]
|
|
pub struct DatabaseDescription {
|
|
pub name: &'static str,
|
|
pub init_script: Option<&'static str>,
|
|
pub version: u32,
|
|
}
|
|
|
|
pub fn db_path(name: &str) -> Result<PathBuf> {
|
|
let data_dir =
|
|
xdg::BaseDirectories::with_prefix("meli").map_err(|e| MeliError::new(e.to_string()))?;
|
|
Ok(data_dir
|
|
.place_data_file(name)
|
|
.map_err(|e| MeliError::new(e.to_string()))?)
|
|
}
|
|
|
|
pub fn open_db(db_path: PathBuf) -> Result<Connection> {
|
|
if !db_path.exists() {
|
|
return Err(MeliError::new("Database doesn't exist"));
|
|
}
|
|
Connection::open(&db_path).map_err(|e| MeliError::new(e.to_string()))
|
|
}
|
|
|
|
pub fn open_or_create_db(
|
|
description: &DatabaseDescription,
|
|
identifier: Option<&str>,
|
|
) -> Result<Connection> {
|
|
let mut second_try: bool = false;
|
|
loop {
|
|
let db_path = if let Some(id) = identifier {
|
|
db_path(&format!("{}_{}", id, description.name))
|
|
} else {
|
|
db_path(description.name)
|
|
}?;
|
|
let mut set_mode = false;
|
|
if !db_path.exists() {
|
|
log(
|
|
format!(
|
|
"Creating {} database in {}",
|
|
description.name,
|
|
db_path.display()
|
|
),
|
|
crate::INFO,
|
|
);
|
|
set_mode = true;
|
|
}
|
|
let conn = Connection::open(&db_path).map_err(|e| MeliError::new(e.to_string()))?;
|
|
if set_mode {
|
|
use std::os::unix::fs::PermissionsExt;
|
|
let file = std::fs::File::open(&db_path)?;
|
|
let metadata = file.metadata()?;
|
|
let mut permissions = metadata.permissions();
|
|
|
|
permissions.set_mode(0o600); // Read/write for owner only.
|
|
file.set_permissions(permissions)?;
|
|
}
|
|
let version: i32 = conn.pragma_query_value(None, "user_version", |row| row.get(0))?;
|
|
if version != 0_i32 && version as u32 != description.version {
|
|
log(
|
|
format!(
|
|
"Database version mismatch, is {} but expected {}",
|
|
version, description.version
|
|
),
|
|
crate::INFO,
|
|
);
|
|
if second_try {
|
|
return Err(MeliError::new(format!(
|
|
"Database version mismatch, is {} but expected {}. Could not recreate database.",
|
|
version, description.version
|
|
)));
|
|
}
|
|
reset_db(description, identifier)?;
|
|
second_try = true;
|
|
continue;
|
|
}
|
|
|
|
if version == 0 {
|
|
conn.pragma_update(None, "user_version", &description.version)?;
|
|
}
|
|
if let Some(s) = description.init_script {
|
|
conn.execute_batch(s)
|
|
.map_err(|e| MeliError::new(e.to_string()))?;
|
|
}
|
|
|
|
return Ok(conn);
|
|
}
|
|
}
|
|
|
|
/// Return database to a clean slate.
|
|
pub fn reset_db(description: &DatabaseDescription, identifier: Option<&str>) -> Result<()> {
|
|
let db_path = if let Some(id) = identifier {
|
|
db_path(&format!("{}_{}", id, description.name))
|
|
} else {
|
|
db_path(description.name)
|
|
}?;
|
|
if !db_path.exists() {
|
|
return Ok(());
|
|
}
|
|
log(
|
|
format!(
|
|
"Resetting {} database in {}",
|
|
description.name,
|
|
db_path.display()
|
|
),
|
|
crate::INFO,
|
|
);
|
|
std::fs::remove_file(&db_path)?;
|
|
Ok(())
|
|
}
|
|
|
|
impl ToSql for Envelope {
|
|
fn to_sql(&self) -> rusqlite::Result<ToSqlOutput> {
|
|
let v: Vec<u8> = bincode::Options::serialize(bincode::config::DefaultOptions::new(), self)
|
|
.map_err(|e| {
|
|
rusqlite::Error::ToSqlConversionFailure(Box::new(MeliError::new(e.to_string())))
|
|
})?;
|
|
Ok(ToSqlOutput::from(v))
|
|
}
|
|
}
|
|
|
|
impl FromSql for Envelope {
|
|
fn column_result(value: rusqlite::types::ValueRef) -> FromSqlResult<Self> {
|
|
use std::convert::TryFrom;
|
|
|
|
let b: Vec<u8> = FromSql::column_result(value)?;
|
|
|
|
Ok(bincode::Options::deserialize(
|
|
bincode::Options::with_limit(
|
|
bincode::config::DefaultOptions::new(),
|
|
2 * u64::try_from(b.len()).map_err(|e| FromSqlError::Other(Box::new(e)))?,
|
|
),
|
|
&b,
|
|
)
|
|
.map_err(|e| FromSqlError::Other(Box::new(e)))?)
|
|
}
|
|
}
|