forked from NextGraph/nextgraph-rs
chore/ng-app/ng-worker-upload-download
parent
86e5ec52ef
commit
5fed085379
@ -0,0 +1 @@ |
||||
tests |
@ -0,0 +1,5 @@ |
||||
pub mod repo_pin_status; |
||||
|
||||
pub mod pin_repo; |
||||
|
||||
pub mod topic_sub; |
@ -0,0 +1,124 @@ |
||||
/* |
||||
* 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. |
||||
*/ |
||||
use crate::broker::{ServerConfig, BROKER}; |
||||
use crate::connection::NoiseFSM; |
||||
use crate::types::*; |
||||
use crate::{actor::*, errors::ProtocolError, types::ProtocolMessage}; |
||||
|
||||
use async_std::sync::Mutex; |
||||
use ng_repo::log::*; |
||||
use ng_repo::repo::Repo; |
||||
use ng_repo::types::*; |
||||
use serde::{Deserialize, Serialize}; |
||||
use std::sync::Arc; |
||||
|
||||
impl PinRepo { |
||||
pub fn get_actor(&self) -> Box<dyn EActor> { |
||||
Actor::<PinRepo, RepoOpened>::new_responder() |
||||
} |
||||
pub fn from_repo(repo: &Repo, broker_id: &DirectPeerId) -> PinRepo { |
||||
let overlay = OverlayAccess::ReadWrite(( |
||||
OverlayId::inner_from_store(&repo.store), |
||||
OverlayId::outer(repo.store.id()), |
||||
)); |
||||
let mut rw_topics = Vec::with_capacity(repo.branches.len()); |
||||
let mut ro_topics = vec![]; |
||||
for (_, branch) in repo.branches.iter() { |
||||
if let Some(privkey) = &branch.topic_priv_key { |
||||
rw_topics.push(PublisherAdvert::new( |
||||
branch.topic, |
||||
privkey.clone(), |
||||
*broker_id, |
||||
)); |
||||
} else { |
||||
ro_topics.push(branch.topic); |
||||
} |
||||
} |
||||
PinRepo::V0(PinRepoV0 { |
||||
hash: repo.id.into(), |
||||
overlay, |
||||
// TODO: overlay_root_topic
|
||||
overlay_root_topic: None, |
||||
expose_outer: false, |
||||
peers: vec![], |
||||
max_peer_count: 0, |
||||
allowed_peers: vec![], |
||||
ro_topics, |
||||
rw_topics, |
||||
}) |
||||
} |
||||
} |
||||
|
||||
impl TryFrom<ProtocolMessage> for PinRepo { |
||||
type Error = ProtocolError; |
||||
fn try_from(msg: ProtocolMessage) -> Result<Self, Self::Error> { |
||||
let req: ClientRequestContentV0 = msg.try_into()?; |
||||
if let ClientRequestContentV0::PinRepo(a) = req { |
||||
Ok(a) |
||||
} else { |
||||
log_debug!("INVALID {:?}", req); |
||||
Err(ProtocolError::InvalidValue) |
||||
} |
||||
} |
||||
} |
||||
|
||||
impl From<PinRepo> for ProtocolMessage { |
||||
fn from(msg: PinRepo) -> ProtocolMessage { |
||||
let overlay = match msg { |
||||
PinRepo::V0(ref v0) => v0.overlay.overlay_id_for_client_protocol_purpose().clone(), |
||||
}; |
||||
ProtocolMessage::from_client_request_v0(ClientRequestContentV0::PinRepo(msg), overlay) |
||||
} |
||||
} |
||||
|
||||
impl TryFrom<ProtocolMessage> for RepoOpened { |
||||
type Error = ProtocolError; |
||||
fn try_from(msg: ProtocolMessage) -> Result<Self, Self::Error> { |
||||
let res: ClientResponseContentV0 = msg.try_into()?; |
||||
if let ClientResponseContentV0::RepoOpened(a) = res { |
||||
Ok(a) |
||||
} else { |
||||
log_debug!("INVALID {:?}", res); |
||||
Err(ProtocolError::InvalidValue) |
||||
} |
||||
} |
||||
} |
||||
|
||||
impl From<RepoOpened> for ProtocolMessage { |
||||
fn from(res: RepoOpened) -> ProtocolMessage { |
||||
ClientResponseContentV0::RepoOpened(res).into() |
||||
} |
||||
} |
||||
|
||||
impl Actor<'_, RepoPinStatusReq, RepoPinStatus> {} |
||||
|
||||
#[async_trait::async_trait] |
||||
impl EActor for Actor<'_, PinRepo, RepoOpened> { |
||||
async fn respond( |
||||
&mut self, |
||||
msg: ProtocolMessage, |
||||
fsm: Arc<Mutex<NoiseFSM>>, |
||||
) -> Result<(), ProtocolError> { |
||||
let req = PinRepo::try_from(msg)?; |
||||
|
||||
//TODO implement all the server side logic
|
||||
let broker = BROKER.read().await; |
||||
let res = broker.get_server_storage()?.pin_repo( |
||||
req.overlay(), |
||||
req.hash(), |
||||
req.ro_topics(), |
||||
req.rw_topics(), |
||||
)?; |
||||
|
||||
fsm.lock().await.send(res.into()).await?; |
||||
Ok(()) |
||||
} |
||||
} |
@ -0,0 +1,88 @@ |
||||
/* |
||||
* 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. |
||||
*/ |
||||
use crate::broker::{ServerConfig, BROKER}; |
||||
use crate::connection::NoiseFSM; |
||||
use crate::types::*; |
||||
use crate::{actor::*, errors::ProtocolError, types::ProtocolMessage}; |
||||
|
||||
use async_std::sync::Mutex; |
||||
use ng_repo::log::*; |
||||
use ng_repo::types::PubKey; |
||||
use serde::{Deserialize, Serialize}; |
||||
use std::sync::Arc; |
||||
|
||||
impl RepoPinStatusReq { |
||||
pub fn get_actor(&self) -> Box<dyn EActor> { |
||||
Actor::<RepoPinStatusReq, RepoPinStatus>::new_responder() |
||||
} |
||||
} |
||||
|
||||
impl TryFrom<ProtocolMessage> for RepoPinStatusReq { |
||||
type Error = ProtocolError; |
||||
fn try_from(msg: ProtocolMessage) -> Result<Self, Self::Error> { |
||||
let req: ClientRequestContentV0 = msg.try_into()?; |
||||
if let ClientRequestContentV0::RepoPinStatusReq(a) = req { |
||||
Ok(a) |
||||
} else { |
||||
log_debug!("INVALID {:?}", req); |
||||
Err(ProtocolError::InvalidValue) |
||||
} |
||||
} |
||||
} |
||||
|
||||
impl From<RepoPinStatusReq> for ProtocolMessage { |
||||
fn from(msg: RepoPinStatusReq) -> ProtocolMessage { |
||||
let overlay = *msg.overlay(); |
||||
ProtocolMessage::from_client_request_v0( |
||||
ClientRequestContentV0::RepoPinStatusReq(msg), |
||||
overlay, |
||||
) |
||||
} |
||||
} |
||||
|
||||
impl TryFrom<ProtocolMessage> for RepoPinStatus { |
||||
type Error = ProtocolError; |
||||
fn try_from(msg: ProtocolMessage) -> Result<Self, Self::Error> { |
||||
let res: ClientResponseContentV0 = msg.try_into()?; |
||||
if let ClientResponseContentV0::RepoPinStatus(a) = res { |
||||
Ok(a) |
||||
} else { |
||||
log_debug!("INVALID {:?}", res); |
||||
Err(ProtocolError::InvalidValue) |
||||
} |
||||
} |
||||
} |
||||
|
||||
impl From<RepoPinStatus> for ProtocolMessage { |
||||
fn from(res: RepoPinStatus) -> ProtocolMessage { |
||||
ClientResponseContentV0::RepoPinStatus(res).into() |
||||
} |
||||
} |
||||
|
||||
impl Actor<'_, RepoPinStatusReq, RepoPinStatus> {} |
||||
|
||||
#[async_trait::async_trait] |
||||
impl EActor for Actor<'_, RepoPinStatusReq, RepoPinStatus> { |
||||
async fn respond( |
||||
&mut self, |
||||
msg: ProtocolMessage, |
||||
fsm: Arc<Mutex<NoiseFSM>>, |
||||
) -> Result<(), ProtocolError> { |
||||
let req = RepoPinStatusReq::try_from(msg)?; |
||||
let broker = BROKER.read().await; |
||||
let res = broker |
||||
.get_server_storage()? |
||||
.get_repo_pin_status(req.overlay(), req.hash())?; |
||||
|
||||
fsm.lock().await.send(res.into()).await?; |
||||
Ok(()) |
||||
} |
||||
} |
@ -0,0 +1,113 @@ |
||||
/* |
||||
* 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. |
||||
*/ |
||||
use crate::broker::{ServerConfig, BROKER}; |
||||
use crate::connection::NoiseFSM; |
||||
use crate::types::*; |
||||
use crate::{actor::*, errors::ProtocolError, types::ProtocolMessage}; |
||||
|
||||
use async_std::sync::Mutex; |
||||
use ng_repo::log::*; |
||||
use ng_repo::repo::{BranchInfo, Repo}; |
||||
use ng_repo::types::*; |
||||
use serde::{Deserialize, Serialize}; |
||||
use std::sync::Arc; |
||||
|
||||
impl TopicSub { |
||||
pub fn get_actor(&self) -> Box<dyn EActor> { |
||||
Actor::<TopicSub, TopicSubRes>::new_responder() |
||||
} |
||||
/// only set broker_id if you want to be a publisher
|
||||
pub fn new(repo: &Repo, branch: &BranchInfo, broker_id: Option<&DirectPeerId>) -> TopicSub { |
||||
let (overlay, publisher) = if broker_id.is_some() && branch.topic_priv_key.is_some() { |
||||
( |
||||
OverlayId::inner_from_store(&repo.store), |
||||
Some(PublisherAdvert::new( |
||||
branch.topic, |
||||
branch.topic_priv_key.to_owned().unwrap(), |
||||
*broker_id.unwrap(), |
||||
)), |
||||
) |
||||
} else { |
||||
(OverlayId::outer(repo.store.id()), None) |
||||
}; |
||||
|
||||
TopicSub::V0(TopicSubV0 { |
||||
repo_hash: repo.id.into(), |
||||
overlay: Some(overlay), |
||||
topic: branch.topic, |
||||
publisher, |
||||
}) |
||||
} |
||||
} |
||||
|
||||
impl TryFrom<ProtocolMessage> for TopicSub { |
||||
type Error = ProtocolError; |
||||
fn try_from(msg: ProtocolMessage) -> Result<Self, Self::Error> { |
||||
let req: ClientRequestContentV0 = msg.try_into()?; |
||||
if let ClientRequestContentV0::TopicSub(a) = req { |
||||
Ok(a) |
||||
} else { |
||||
log_debug!("INVALID {:?}", req); |
||||
Err(ProtocolError::InvalidValue) |
||||
} |
||||
} |
||||
} |
||||
|
||||
impl From<TopicSub> for ProtocolMessage { |
||||
fn from(msg: TopicSub) -> ProtocolMessage { |
||||
let overlay = *msg.overlay(); |
||||
ProtocolMessage::from_client_request_v0(ClientRequestContentV0::TopicSub(msg), overlay) |
||||
} |
||||
} |
||||
|
||||
impl TryFrom<ProtocolMessage> for TopicSubRes { |
||||
type Error = ProtocolError; |
||||
fn try_from(msg: ProtocolMessage) -> Result<Self, Self::Error> { |
||||
let res: ClientResponseContentV0 = msg.try_into()?; |
||||
if let ClientResponseContentV0::TopicSubRes(a) = res { |
||||
Ok(a) |
||||
} else { |
||||
log_debug!("INVALID {:?}", res); |
||||
Err(ProtocolError::InvalidValue) |
||||
} |
||||
} |
||||
} |
||||
|
||||
impl From<TopicSubRes> for ProtocolMessage { |
||||
fn from(res: TopicSubRes) -> ProtocolMessage { |
||||
ClientResponseContentV0::TopicSubRes(res).into() |
||||
} |
||||
} |
||||
|
||||
impl Actor<'_, TopicSub, TopicSubRes> {} |
||||
|
||||
#[async_trait::async_trait] |
||||
impl EActor for Actor<'_, TopicSub, TopicSubRes> { |
||||
async fn respond( |
||||
&mut self, |
||||
msg: ProtocolMessage, |
||||
fsm: Arc<Mutex<NoiseFSM>>, |
||||
) -> Result<(), ProtocolError> { |
||||
let req = TopicSub::try_from(msg)?; |
||||
|
||||
//TODO implement all the server side logic
|
||||
let broker = BROKER.read().await; |
||||
let res = broker.get_server_storage()?.topic_sub( |
||||
req.overlay(), |
||||
req.hash(), |
||||
req.topic(), |
||||
req.publisher(), |
||||
)?; |
||||
|
||||
fsm.lock().await.send(res.into()).await?; |
||||
Ok(()) |
||||
} |
||||
} |
@ -0,0 +1,173 @@ |
||||
// 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::errors::ProtocolError; |
||||
use ng_net::types::*; |
||||
use ng_repo::block_storage::BlockStorage; |
||||
use ng_repo::errors::StorageError; |
||||
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::ReadCap; |
||||
use ng_repo::types::RepoId; |
||||
use ng_repo::types::SymKey; |
||||
use ng_repo::types::Timestamp; |
||||
use ng_repo::types::TopicId; |
||||
use serde_bare::to_vec; |
||||
|
||||
use super::prop; |
||||
|
||||
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(), |
||||
storage, |
||||
) |
||||
} |
||||
|
||||
pub fn create( |
||||
id: &BranchId, |
||||
read_cap: &ReadCap, |
||||
branch_type: &BranchType, |
||||
topic: &TopicId, |
||||
publisher: Option<&BranchWriteCapSecret>, |
||||
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)?; |
||||
} |
||||
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(), |
||||
}; |
||||
Ok(bs) |
||||
} |
||||
|
||||
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(&ObjectId::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() {} |
||||
} |
@ -0,0 +1,29 @@ |
||||
// 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.
|
||||
|
||||
pub mod storage; |
||||
|
||||
pub use storage::*; |
||||
|
||||
pub mod repo; |
||||
|
||||
pub mod branch; |
||||
|
||||
use ng_repo::errors::StorageError; |
||||
use serde::Deserialize; |
||||
use serde_bare::from_slice; |
||||
use std::collections::HashMap; |
||||
pub(crate) fn prop<A>(prop: u8, props: &HashMap<u8, Vec<u8>>) -> Result<A, StorageError> |
||||
where |
||||
A: for<'a> Deserialize<'a>, |
||||
{ |
||||
Ok(from_slice( |
||||
&props.get(&prop).ok_or(StorageError::PropertyNotFound)?, |
||||
)?) |
||||
} |
@ -0,0 +1,352 @@ |
||||
// 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.
|
||||
|
||||
//! Repo storage on disk
|
||||
|
||||
use std::collections::hash_map::DefaultHasher; |
||||
use std::collections::HashMap; |
||||
use std::collections::HashSet; |
||||
use std::hash::Hash; |
||||
use std::hash::Hasher; |
||||
use std::time::SystemTime; |
||||
|
||||
use either::{Either, Left, Right}; |
||||
use ng_net::errors::ProtocolError; |
||||
use ng_net::types::*; |
||||
use ng_repo::block_storage::BlockStorage; |
||||
use ng_repo::errors::StorageError; |
||||
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::ReadCap; |
||||
use ng_repo::types::RepoId; |
||||
use ng_repo::types::RepoWriteCapSecret; |
||||
use ng_repo::types::Repository; |
||||
use ng_repo::types::StoreRepo; |
||||
use ng_repo::types::SymKey; |
||||
use ng_repo::types::Timestamp; |
||||
use ng_repo::utils::now_timestamp; |
||||
use serde::Deserialize; |
||||
use serde_bare::from_slice; |
||||
use serde_bare::to_vec; |
||||
use std::sync::{Arc, RwLock}; |
||||
|
||||
use super::branch::BranchStorage; |
||||
use super::prop; |
||||
|
||||
pub struct RepoStorage<'a> { |
||||
storage: &'a dyn KCVStorage, |
||||
id: RepoId, |
||||
} |
||||
|
||||
impl<'a> RepoStorage<'a> { |
||||
const PREFIX: u8 = b'r'; |
||||
|
||||
// repo properties suffixes
|
||||
const SIGNER_CAP_OWNER: u8 = b'a'; |
||||
const SIGNER_CAP_PARTIAL: u8 = b'b'; |
||||
const CHAT_BRANCH: u8 = b'c'; |
||||
const DEFINITION: u8 = b'd'; |
||||
const STORE_BRANCH: u8 = b'e'; |
||||
const INHERIT: u8 = b'i'; |
||||
const OVERLAY_BRANCH: u8 = b'l'; |
||||
const MAIN_BRANCH: u8 = b'm'; |
||||
const OWNERS: u8 = b'o'; |
||||
const PINNED: u8 = b'p'; |
||||
const QUORUM: u8 = b'q'; |
||||
const READ_CAP: u8 = b'r'; |
||||
const STORE_REPO: u8 = b's'; |
||||
const SIGNER_CAP_TOTAL: u8 = b't'; |
||||
const USER_BRANCH: u8 = b'u'; |
||||
const WRITE_CAP_SECRET: u8 = b'w'; |
||||
|
||||
const ALL_PROPERTIES: [u8; 16] = [ |
||||
Self::SIGNER_CAP_OWNER, |
||||
Self::SIGNER_CAP_PARTIAL, |
||||
Self::CHAT_BRANCH, |
||||
Self::DEFINITION, |
||||
Self::STORE_BRANCH, |
||||
Self::INHERIT, |
||||
Self::OVERLAY_BRANCH, |
||||
Self::MAIN_BRANCH, |
||||
Self::OWNERS, |
||||
Self::PINNED, |
||||
Self::QUORUM, |
||||
Self::READ_CAP, |
||||
Self::STORE_REPO, |
||||
Self::SIGNER_CAP_TOTAL, |
||||
Self::USER_BRANCH, |
||||
Self::WRITE_CAP_SECRET, |
||||
]; |
||||
|
||||
const PREFIX_BRANCHES: u8 = b'b'; |
||||
|
||||
const SUFFIX_FOR_EXIST_CHECK: u8 = Self::READ_CAP; |
||||
|
||||
pub fn open(id: &RepoId, storage: &'a dyn KCVStorage) -> Result<RepoStorage<'a>, StorageError> { |
||||
let opening = RepoStorage { |
||||
id: id.clone(), |
||||
storage, |
||||
}; |
||||
if !opening.exists() { |
||||
return Err(StorageError::NotFound); |
||||
} |
||||
Ok(opening) |
||||
} |
||||
|
||||
pub fn create_from_repo( |
||||
repo: &Repo, |
||||
storage: &'a dyn KCVStorage, |
||||
) -> Result<RepoStorage<'a>, StorageError> { |
||||
Self::create( |
||||
&repo.id, |
||||
repo.read_cap.as_ref().unwrap(), |
||||
repo.write_cap.as_ref(), |
||||
repo.store.get_store_repo(), |
||||
&repo.repo_def, |
||||
&repo.branches, |
||||
storage, |
||||
) |
||||
} |
||||
|
||||
// TODO: signers
|
||||
pub fn create( |
||||
id: &RepoId, |
||||
read_cap: &ReadCap, |
||||
write_cap: Option<&RepoWriteCapSecret>, |
||||
store_repo: &StoreRepo, |
||||
repo_def: &Repository, |
||||
branches: &HashMap<BranchId, BranchInfo>, |
||||
storage: &'a dyn KCVStorage, |
||||
) -> Result<RepoStorage<'a>, StorageError> { |
||||
let repo = RepoStorage { |
||||
id: id.clone(), |
||||
storage, |
||||
}; |
||||
if repo.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(store_repo)?; |
||||
tx.put(Self::PREFIX, &id_ser, Some(Self::STORE_REPO), &value, &None)?; |
||||
let value = to_vec(repo_def)?; |
||||
tx.put(Self::PREFIX, &id_ser, Some(Self::DEFINITION), &value, &None)?; |
||||
if let Some(wc) = write_cap { |
||||
let value = to_vec(wc)?; |
||||
tx.put( |
||||
Self::PREFIX, |
||||
&id_ser, |
||||
Some(Self::WRITE_CAP_SECRET), |
||||
&value, |
||||
&None, |
||||
)?; |
||||
} |
||||
Ok(()) |
||||
})?; |
||||
|
||||
for branch in branches.values() { |
||||
BranchStorage::create_from_info(branch, storage)?; |
||||
} |
||||
|
||||
Ok(repo) |
||||
} |
||||
|
||||
pub fn load( |
||||
id: &RepoId, |
||||
store: Either<Arc<Store>, Arc<RwLock<dyn BlockStorage + Send + Sync>>>, |
||||
storage: &'a dyn KCVStorage, |
||||
) -> Result<Repo, StorageError> { |
||||
let branch_ids = Self::get_all_branches(id, storage)?; |
||||
let mut branches = HashMap::new(); |
||||
let mut overlay_branch_read_cap = None; |
||||
for branch in branch_ids { |
||||
let info = BranchStorage::load(&branch, storage)?; |
||||
if info.branch_type == BranchType::Overlay { |
||||
overlay_branch_read_cap = Some(info.read_cap.clone()); |
||||
} |
||||
let _ = branches.insert(branch, info); |
||||
} |
||||
|
||||
let props = storage.get_all_properties_of_key( |
||||
Self::PREFIX, |
||||
to_vec(id).unwrap(), |
||||
Self::ALL_PROPERTIES.to_vec(), |
||||
&None, |
||||
)?; |
||||
|
||||
let store = match store { |
||||
Left(s) => s, |
||||
Right(bs) => { |
||||
// we want to load a store. let's start by retrieving the store repo
|
||||
// TODO: check that it has a STORE_BRANCH
|
||||
let store_repo: StoreRepo = |
||||
prop(Self::STORE_REPO, &props).map_err(|_| StorageError::NotAStoreRepo)?; |
||||
let store_info = branches.get(id).ok_or(StorageError::NotFound)?; |
||||
let overlay_branch_read_cap = if store_repo.is_private() { |
||||
store_info.read_cap.clone() |
||||
} else { |
||||
overlay_branch_read_cap.ok_or(StorageError::OverlayBranchNotFound)? |
||||
}; |
||||
Arc::new(Store::new( |
||||
store_repo, |
||||
store_info.read_cap.clone(), |
||||
overlay_branch_read_cap, |
||||
bs, |
||||
)) |
||||
} |
||||
}; |
||||
|
||||
let repo = Repo { |
||||
id: id.clone(), |
||||
repo_def: prop(Self::DEFINITION, &props)?, |
||||
read_cap: prop(Self::READ_CAP, &props)?, |
||||
write_cap: prop(Self::WRITE_CAP_SECRET, &props).ok(), |
||||
//TODO: signer
|
||||
signer: None, |
||||
//TODO: members
|
||||
members: HashMap::new(), |
||||
branches, |
||||
opened_branches: HashMap::new(), |
||||
store, |
||||
}; |
||||
Ok(repo) |
||||
} |
||||
|
||||
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 get_all_branches( |
||||
id: &RepoId, |
||||
storage: &'a dyn KCVStorage, |
||||
) -> Result<Vec<BranchId>, StorageError> { |
||||
let size = to_vec(&BranchId::nil())?.len(); |
||||
let key_prefix = to_vec(id).unwrap(); |
||||
let mut res: Vec<BranchId> = vec![]; |
||||
let total_size = key_prefix.len() + size; |
||||
for branch in storage.get_all_keys_and_values( |
||||
Self::PREFIX_BRANCHES, |
||||
total_size, |
||||
key_prefix, |
||||
None, |
||||
&None, |
||||
)? { |
||||
if branch.0.len() == total_size + 1 { |
||||
let branch_id: BranchId = from_slice(&branch.0[1..branch.0.len()])?; |
||||
res.push(branch_id); |
||||
} |
||||
} |
||||
Ok(res) |
||||
} |
||||
|
||||
pub fn get_all_store_and_repo_ids( |
||||
storage: &'a dyn KCVStorage, |
||||
) -> Result<HashMap<StoreRepo, Vec<RepoId>>, StorageError> { |
||||
let mut res = HashMap::new(); |
||||
let size = to_vec(&RepoId::nil())?.len(); |
||||
let mut store_ids = HashSet::new(); |
||||
for (store_id_ser, _) in storage.get_all_keys_and_values( |
||||
Self::PREFIX, |
||||
size, |
||||
vec![], |
||||
Some(Self::STORE_BRANCH), |
||||
&None, |
||||
)? { |
||||
let store_id: RepoId = from_slice(&store_id_ser)?; |
||||
store_ids.insert(store_id); |
||||
} |
||||
let mut repo_ids = HashMap::new(); |
||||
for (repo_id_ser, store_repo_ser) in storage.get_all_keys_and_values( |
||||
Self::PREFIX, |
||||
size, |
||||
vec![], |
||||
Some(Self::STORE_REPO), |
||||
&None, |
||||
)? { |
||||
let repo_id: RepoId = from_slice(&repo_id_ser)?; |
||||
let store_repo: StoreRepo = from_slice(&store_repo_ser)?; |
||||
repo_ids.insert(repo_id, store_repo); |
||||
} |
||||
|
||||
for store in store_ids.iter() { |
||||
let store_repo = repo_ids.get(store).ok_or(StorageError::NotAStoreRepo)?; |
||||
res.insert(*store_repo, vec![]); |
||||
} |
||||
|
||||
for (repo_id, store_repo) in repo_ids.iter() { |
||||
if store_ids.get(repo_id).is_none() { |
||||
let repos = res.get_mut(store_repo).ok_or(StorageError::NotFound)?; |
||||
repos.push(*repo_id); |
||||
} |
||||
} |
||||
|
||||
Ok(res) |
||||
} |
||||
|
||||
// pub fn get_type(&self) -> Result<u8, ProtocolError> {
|
||||
// let type_ser = self
|
||||
// .store
|
||||
// .get(Self::PREFIX, &to_vec(&self.id)?, Some(Self::TYPE), &None)?;
|
||||
// let t: (u8, u32, Option<String>) = from_slice(&type_ser)?;
|
||||
// // if t.1 < now_timestamp() {
|
||||
// // return Err(ProtocolError::Expired);
|
||||
// // }
|
||||
// Ok(t.0)
|
||||
// }
|
||||
|
||||
// pub fn is_expired(&self) -> Result<bool, StorageError> {
|
||||
// let expire_ser =
|
||||
// self.store
|
||||
// .get(Self::PREFIX, &to_vec(&self.id)?, Some(Self::TYPE), &None)?;
|
||||
// let expire: (u8, u32, Option<String>) = from_slice(&expire_ser)?;
|
||||
// if expire.1 < now_timestamp() {
|
||||
// return Ok(true);
|
||||
// }
|
||||
// Ok(false)
|
||||
// }
|
||||
|
||||
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(&BranchId::nil())?.len(); |
||||
tx.del_all_values(Self::PREFIX_BRANCHES, 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() {} |
||||
} |
Loading…
Reference in new issue