230 lines
7.5 KiB
Rust
230 lines
7.5 KiB
Rust
/*
|
|
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <hello@stalw.art>
|
|
*
|
|
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
|
*/
|
|
|
|
use super::backup::MAGIC_MARKER;
|
|
use crate::{Core, DATABASE_SCHEMA_VERSION};
|
|
use lz4_flex::frame::FrameDecoder;
|
|
use registry::schema::enums::CompressionAlgo;
|
|
use std::{
|
|
fs::File,
|
|
io::{BufReader, ErrorKind, Read},
|
|
path::{Path, PathBuf},
|
|
};
|
|
use store::{
|
|
BlobStore, SUBSPACE_BLOBS, SUBSPACE_COUNTER, SUBSPACE_INDEXES, SUBSPACE_QUOTA,
|
|
SUBSPACE_REGISTRY_IDX, Store, U32_LEN,
|
|
write::{AnyClass, BatchBuilder, ValueClass, key::DeserializeBigEndian},
|
|
};
|
|
use types::{collection::Collection, field::Field};
|
|
use utils::{UnwrapFailure, failed};
|
|
|
|
impl Core {
|
|
pub async fn restore(&self, src: PathBuf) {
|
|
// Backup the core
|
|
if src.is_dir() {
|
|
// Iterate directory and spawn a task for each file
|
|
let mut tasks = Vec::new();
|
|
for entry in std::fs::read_dir(&src).failed("Failed to read directory") {
|
|
let entry = entry.failed("Failed to read entry");
|
|
let path = entry.path();
|
|
if path.is_file() {
|
|
let storage = self.storage.clone();
|
|
let blob_store = self.storage.blob.clone();
|
|
tasks.push(tokio::spawn(async move {
|
|
restore_file(storage.data, blob_store, &path).await;
|
|
}));
|
|
}
|
|
}
|
|
|
|
for task in tasks {
|
|
task.await.failed("Failed to wait for task");
|
|
}
|
|
} else {
|
|
restore_file(self.storage.data.clone(), self.storage.blob.clone(), &src).await;
|
|
}
|
|
}
|
|
}
|
|
|
|
async fn restore_file(store: Store, blob_store: BlobStore, path: &Path) {
|
|
println!("Importing database dump from {}.", path.to_str().unwrap());
|
|
|
|
let mut reader = KeyValueReader::new(path);
|
|
let mut batch = BatchBuilder::new();
|
|
|
|
match reader.subspace {
|
|
SUBSPACE_BLOBS => {
|
|
while let Some((key, value)) = reader.next() {
|
|
blob_store
|
|
.put_blob(&key, &value, CompressionAlgo::Lz4)
|
|
.await
|
|
.failed("Failed to write blob");
|
|
}
|
|
}
|
|
SUBSPACE_COUNTER | SUBSPACE_QUOTA => {
|
|
while let Some((key, value)) = reader.next() {
|
|
batch.add(
|
|
ValueClass::Any(AnyClass {
|
|
subspace: reader.subspace,
|
|
key,
|
|
}),
|
|
u64::from_le_bytes(
|
|
value
|
|
.try_into()
|
|
.expect("Failed to deserialize counter/quota"),
|
|
) as i64,
|
|
);
|
|
if batch.is_large_batch() {
|
|
store
|
|
.write(batch.build_all())
|
|
.await
|
|
.failed("Failed to write batch");
|
|
batch = BatchBuilder::new();
|
|
}
|
|
}
|
|
}
|
|
SUBSPACE_INDEXES | SUBSPACE_REGISTRY_IDX => {
|
|
while let Some((key, _)) = reader.next() {
|
|
let account_id = key
|
|
.as_slice()
|
|
.deserialize_be_u32(0)
|
|
.failed("Failed to deserialize account ID");
|
|
let collection = *key.get(U32_LEN).failed("Missing collection byte");
|
|
let field = *key.get(U32_LEN + 1).failed("Missing field byte");
|
|
let value = key
|
|
.get(U32_LEN + 2..key.len() - U32_LEN)
|
|
.failed("Missing index key")
|
|
.to_vec();
|
|
let document_id = key
|
|
.as_slice()
|
|
.deserialize_be_u32(key.len() - U32_LEN)
|
|
.failed("Failed to deserialize document ID");
|
|
|
|
batch
|
|
.with_account_id(account_id)
|
|
.with_collection(Collection::from(collection))
|
|
.with_document(document_id)
|
|
.index(Field::new(field), value);
|
|
|
|
if batch.is_large_batch() {
|
|
store
|
|
.write(batch.build_all())
|
|
.await
|
|
.failed("Failed to write batch");
|
|
batch = BatchBuilder::new();
|
|
}
|
|
}
|
|
}
|
|
_ => {
|
|
while let Some((key, value)) = reader.next() {
|
|
batch.set(
|
|
ValueClass::Any(AnyClass {
|
|
subspace: reader.subspace,
|
|
key,
|
|
}),
|
|
value,
|
|
);
|
|
if batch.is_large_batch() {
|
|
store
|
|
.write(batch.build_all())
|
|
.await
|
|
.failed("Failed to write batch");
|
|
batch = BatchBuilder::new();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if !batch.is_empty() {
|
|
store
|
|
.write(batch.build_all())
|
|
.await
|
|
.failed("Failed to write batch");
|
|
}
|
|
}
|
|
|
|
struct KeyValueReader {
|
|
subspace: u8,
|
|
file: FrameDecoder<BufReader<File>>,
|
|
}
|
|
|
|
impl KeyValueReader {
|
|
fn new(path: &Path) -> Self {
|
|
let mut file = FrameDecoder::new(BufReader::new(
|
|
File::open(path).failed("Failed to open file"),
|
|
));
|
|
let mut buf = [0u8; 1];
|
|
file.read_exact(&mut buf)
|
|
.failed(&format!("Failed to read magic marker from {path:?}"));
|
|
|
|
if buf[0] != MAGIC_MARKER {
|
|
failed(&format!("Invalid magic marker in {path:?}"));
|
|
}
|
|
|
|
file.read_exact(&mut buf)
|
|
.failed(&format!("Failed to read subspace from {path:?}"));
|
|
let subspace = buf[0];
|
|
|
|
let mut buf = [0u8; 4];
|
|
file.read_exact(&mut buf)
|
|
.failed(&format!("Failed to read version from {path:?}"));
|
|
let version = u32::from_le_bytes(buf);
|
|
|
|
if version != DATABASE_SCHEMA_VERSION {
|
|
failed(&format!(
|
|
"Invalid database schema version in {path:?}: Expected {DATABASE_SCHEMA_VERSION}, found {version}"
|
|
));
|
|
}
|
|
|
|
Self { file, subspace }
|
|
}
|
|
|
|
fn next(&mut self) -> Option<(Vec<u8>, Vec<u8>)> {
|
|
let size = self.read_size()?;
|
|
|
|
let mut key = vec![0; size as usize];
|
|
self.file
|
|
.read_exact(&mut key)
|
|
.failed("Failed to read bytes");
|
|
let value = self.expect_sized_bytes();
|
|
|
|
Some((key, value))
|
|
}
|
|
|
|
fn read_size(&mut self) -> Option<u32> {
|
|
let mut result = 0;
|
|
let mut buf = [0u8; 1];
|
|
|
|
for shift in [0, 7, 14, 21, 28] {
|
|
if let Err(err) = self.file.read_exact(&mut buf) {
|
|
if err.kind() == ErrorKind::UnexpectedEof {
|
|
return None;
|
|
} else {
|
|
failed(&format!("Failed to read file: {err:?}"));
|
|
}
|
|
}
|
|
|
|
let byte = buf[0];
|
|
if (byte & 0x80) == 0 {
|
|
result |= (byte as u32) << shift;
|
|
return Some(result);
|
|
} else {
|
|
result |= ((byte & 0x7F) as u32) << shift;
|
|
}
|
|
}
|
|
|
|
failed("Invalid leb128 sequence")
|
|
}
|
|
|
|
fn expect_sized_bytes(&mut self) -> Vec<u8> {
|
|
let len = self.read_size().failed("Missing leb128 value sequence") as usize;
|
|
let mut bytes = vec![0; len];
|
|
self.file
|
|
.read_exact(&mut bytes)
|
|
.failed("Failed to read bytes");
|
|
bytes
|
|
}
|
|
}
|