Rust implementation of NextGraph, a Decentralized and local-first web 3.0 ecosystem
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 
nextgraph-rs/ng-verifier/src/user_storage/branch.rs

209 lines
6.2 KiB

// Copyright (c) 2022-2024 Niko Bonnieure, Par le Peuple, NextGraph.org developers
// All rights reserved.
// Licensed under the Apache License, Version 2.0
// <LICENSE-APACHE2 or http://www.apache.org/licenses/LICENSE-2.0>
// or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
// at your option. All files in the project carrying such
// notice may not be copied, modified, or distributed except
// according to those terms.
//! Branch storage on disk
use std::collections::hash_map::DefaultHasher;
use std::collections::HashMap;
use std::hash::Hash;
use std::hash::Hasher;
use std::time::SystemTime;
use ng_net::types::*;
use ng_repo::block_storage::BlockStorage;
use ng_repo::errors::ProtocolError;
use ng_repo::errors::StorageError;
use ng_repo::kcv_storage::prop;
use ng_repo::kcv_storage::KCVStorage;
use ng_repo::repo::BranchInfo;
use ng_repo::repo::Repo;
use ng_repo::store::Store;
use ng_repo::types::BranchId;
use ng_repo::types::BranchType;
use ng_repo::types::BranchWriteCapSecret;
use ng_repo::types::ObjectId;
use ng_repo::types::ObjectRef;
use ng_repo::types::ReadCap;
use ng_repo::types::RepoId;
use ng_repo::types::SymKey;
use ng_repo::types::Timestamp;
use ng_repo::types::TopicId;
use serde_bare::from_slice;
use serde_bare::to_vec;
pub struct BranchStorage<'a> {
storage: &'a dyn KCVStorage,
id: BranchId,
}
impl<'a> BranchStorage<'a> {
const PREFIX: u8 = b'c';
// branch properties suffixes
const TYPE: u8 = b'b';
const PUBLISHER: u8 = b'p';
const READ_CAP: u8 = b'r';
const TOPIC: u8 = b't';
const ALL_PROPERTIES: [u8; 4] = [Self::TYPE, Self::PUBLISHER, Self::READ_CAP, Self::TOPIC];
const PREFIX_HEADS: u8 = b'h';
const SUFFIX_FOR_EXIST_CHECK: u8 = Self::TYPE;
pub fn open(
id: &BranchId,
storage: &'a dyn KCVStorage,
) -> Result<BranchStorage<'a>, StorageError> {
let opening = BranchStorage {
id: id.clone(),
storage,
};
if !opening.exists() {
return Err(StorageError::NotFound);
}
Ok(opening)
}
pub fn create_from_info(
info: &BranchInfo,
storage: &'a dyn KCVStorage,
) -> Result<BranchStorage<'a>, StorageError> {
Self::create(
&info.id,
&info.read_cap,
&info.branch_type,
&info.topic,
info.topic_priv_key.as_ref(),
&info.current_heads,
storage,
)
}
//TODO: save all branch info under the repo_id (key prefix should be repo_id)
pub fn create(
id: &BranchId,
read_cap: &ReadCap,
branch_type: &BranchType,
topic: &TopicId,
publisher: Option<&BranchWriteCapSecret>,
current_heads: &Vec<ObjectRef>,
storage: &'a dyn KCVStorage,
) -> Result<BranchStorage<'a>, StorageError> {
let bs = BranchStorage {
id: id.clone(),
storage,
};
// if bs.exists() {
// return Err(StorageError::AlreadyExists);
// }
storage.write_transaction(&mut |tx| {
let id_ser = to_vec(&id)?;
let value = to_vec(read_cap)?;
tx.put(Self::PREFIX, &id_ser, Some(Self::READ_CAP), &value, &None)?;
let value = to_vec(branch_type)?;
tx.put(Self::PREFIX, &id_ser, Some(Self::TYPE), &value, &None)?;
let value = to_vec(topic)?;
tx.put(Self::PREFIX, &id_ser, Some(Self::TOPIC), &value, &None)?;
if let Some(privkey) = publisher {
let value = to_vec(privkey)?;
tx.put(Self::PREFIX, &id_ser, Some(Self::PUBLISHER), &value, &None)?;
}
for head in current_heads {
let mut head_ser = to_vec(head)?;
let mut key = Vec::with_capacity(id_ser.len() + head_ser.len());
key.append(&mut id_ser.clone());
key.append(&mut head_ser);
tx.put(Self::PREFIX_HEADS, &key, None, &vec![], &None)?;
}
Ok(())
})?;
Ok(bs)
}
pub fn load(id: &BranchId, storage: &'a dyn KCVStorage) -> Result<BranchInfo, StorageError> {
let props = storage.get_all_properties_of_key(
Self::PREFIX,
to_vec(id).unwrap(),
Self::ALL_PROPERTIES.to_vec(),
&None,
)?;
let bs = BranchInfo {
id: id.clone(),
branch_type: prop(Self::TYPE, &props)?,
read_cap: prop(Self::READ_CAP, &props)?,
topic: prop(Self::TOPIC, &props)?,
topic_priv_key: prop(Self::PUBLISHER, &props).ok(),
current_heads: Self::get_all_heads(id, storage)?,
};
Ok(bs)
}
pub fn get_all_heads(
id: &BranchId,
storage: &'a dyn KCVStorage,
) -> Result<Vec<ObjectRef>, StorageError> {
let size = to_vec(&ObjectRef::nil())?.len();
let key_prefix = to_vec(id).unwrap();
let mut res: Vec<ObjectRef> = vec![];
let total_size = key_prefix.len() + size;
for head in storage.get_all_keys_and_values(
Self::PREFIX_HEADS,
total_size,
key_prefix,
None,
&None,
)? {
if head.0.len() == total_size + 1 {
let head: ObjectRef = from_slice(&head.0[1..head.0.len()])?;
res.push(head);
}
}
Ok(res)
}
pub fn exists(&self) -> bool {
self.storage
.get(
Self::PREFIX,
&to_vec(&self.id).unwrap(),
Some(Self::SUFFIX_FOR_EXIST_CHECK),
&None,
)
.is_ok()
}
pub fn id(&self) -> &RepoId {
&self.id
}
pub fn del(&self) -> Result<(), StorageError> {
self.storage.write_transaction(&mut |tx| {
let key = &to_vec(&self.id)?;
tx.del_all(Self::PREFIX, key, &Self::ALL_PROPERTIES, &None)?;
let size = to_vec(&ObjectRef::nil())?.len();
tx.del_all_values(Self::PREFIX_HEADS, key, size, None, &None)?;
Ok(())
})
}
}
#[cfg(test)]
mod test {
use ng_repo::errors::StorageError;
use ng_repo::types::*;
use ng_repo::utils::*;
use std::fs;
#[test]
pub fn test_repo() {}
}