use crate::{AttoTokens, client::payment::PaymentOption};
use bytes::Bytes;
use serde::{Deserialize, Serialize};
use std::time::{Duration, SystemTime, UNIX_EPOCH};
use std::{
collections::BTreeMap,
path::{Path, PathBuf},
};
use super::Metadata;
use crate::files::normalize_path;
use crate::{
Client,
client::{
GetError, PutError,
high_level::{data::DataAddress, files::RenameError},
quote::CostError,
},
};
pub type ArchiveAddress = DataAddress;
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
pub struct PublicArchive {
map: BTreeMap<PathBuf, (DataAddress, Metadata)>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[non_exhaustive]
pub enum PublicArchiveVersioned {
V0(PublicArchive),
}
impl PublicArchive {
pub fn new() -> Self {
Self {
map: BTreeMap::new(),
}
}
pub fn rename_file(&mut self, old_path: &Path, new_path: &Path) -> Result<(), RenameError> {
let (data_addr, mut meta) = self
.map
.remove(old_path)
.ok_or(RenameError::FileNotFound(old_path.to_path_buf()))?;
let now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or(Duration::from_secs(0))
.as_secs();
meta.modified = now;
self.map.insert(new_path.to_path_buf(), (data_addr, meta));
debug!(
"Renamed file successfully in the archive, old path: {old_path:?} new_path: {new_path:?}"
);
Ok(())
}
pub fn add_file(&mut self, path: PathBuf, data_addr: DataAddress, meta: Metadata) {
let normalized_path = normalize_path(path.clone());
self.map.insert(normalized_path.clone(), (data_addr, meta));
debug!(
"Added a new file to the archive, path: {:?}",
normalized_path
);
}
pub fn files(&self) -> Vec<(PathBuf, Metadata)> {
self.map
.iter()
.map(|(path, (_, meta))| (path.clone(), meta.clone()))
.collect()
}
pub fn addresses(&self) -> Vec<DataAddress> {
self.map.values().map(|(addr, _)| *addr).collect()
}
pub fn iter(&self) -> impl Iterator<Item = (&PathBuf, &DataAddress, &Metadata)> {
self.map
.iter()
.map(|(path, (addr, meta))| (path, addr, meta))
}
pub fn map(&self) -> &BTreeMap<PathBuf, (DataAddress, Metadata)> {
&self.map
}
pub fn from_bytes(data: Bytes) -> Result<PublicArchive, rmp_serde::decode::Error> {
let root: PublicArchiveVersioned = rmp_serde::from_slice(&data[..])?;
let PublicArchiveVersioned::V0(root) = root;
Ok(root)
}
pub fn to_bytes(&self) -> Result<Bytes, rmp_serde::encode::Error> {
let versioned = PublicArchiveVersioned::V0(self.clone());
let root_serialized = rmp_serde::to_vec_named(&versioned)?;
let root_serialized = Bytes::from(root_serialized);
Ok(root_serialized)
}
pub fn merge(&mut self, other: &PublicArchive) {
self.map.extend(other.map.clone());
}
}
impl Client {
pub async fn archive_get_public(
&self,
addr: &ArchiveAddress,
) -> Result<PublicArchive, GetError> {
let data = self.data_get_public(addr).await?;
Ok(PublicArchive::from_bytes(data)?)
}
pub async fn archive_put_public(
&self,
archive: &PublicArchive,
payment_option: PaymentOption,
) -> Result<(AttoTokens, ArchiveAddress), PutError> {
let bytes = archive
.to_bytes()
.map_err(|e| PutError::Serialization(format!("Failed to serialize archive: {e:?}")))?;
crate::loud_info!(
"Uploading public archive referencing {} files",
archive.map().len()
);
let result = self.data_put_public(bytes, payment_option).await;
debug!("Uploaded archive {archive:?} to the network and the address is {result:?}");
result
}
pub async fn archive_cost(&self, archive: &PublicArchive) -> Result<AttoTokens, CostError> {
let bytes = archive
.to_bytes()
.map_err(|e| CostError::Serialization(format!("Failed to serialize archive: {e:?}")))?;
let result = self.data_cost(bytes).await;
debug!("Calculated the cost to upload archive {archive:?} is {result:?}");
result
}
}
#[cfg(test)]
mod test {
use super::*;
use std::str::FromStr;
use xor_name::XorName;
#[test]
fn compatibility() {
#[derive(Serialize, Deserialize)]
#[non_exhaustive]
pub enum FuturePublicArchiveVersioned {
V0(PublicArchive),
V1(PublicArchive),
#[serde(other)]
Unsupported,
}
let mut arch = PublicArchive::new();
arch.add_file(
PathBuf::from_str("hello_world").unwrap(),
DataAddress::new(XorName::random(&mut rand::thread_rng())),
Metadata::new_with_size(1),
);
let arch_serialized = arch.to_bytes().unwrap();
let future_arch = FuturePublicArchiveVersioned::V0(arch.clone());
let future_arch_serialized = rmp_serde::to_vec_named(&future_arch).unwrap();
let _ = PublicArchive::from_bytes(Bytes::from(future_arch_serialized)).unwrap();
let _: FuturePublicArchiveVersioned = rmp_serde::from_slice(&arch_serialized[..]).unwrap();
let future_arch = FuturePublicArchiveVersioned::V1(arch.clone());
let future_arch_serialized = rmp_serde::to_vec_named(&future_arch).unwrap();
assert!(PublicArchive::from_bytes(Bytes::from(future_arch_serialized)).is_err());
let versioned_arch = PublicArchiveVersioned::V0(arch.clone()); let versioned_arch_serialized = rmp_serde::to_vec_named(&versioned_arch).unwrap();
let _: FuturePublicArchiveVersioned = rmp_serde::from_slice(&versioned_arch_serialized[..]).unwrap();
}
#[test]
fn forward_compatibility() {
#[derive(Debug, Default, Serialize, Deserialize)]
pub struct MetadataV1p1 {
created: u64,
modified: u64,
size: u64,
extra: Option<String>,
accessed: Option<u64>, }
#[derive(Debug, Default, Serialize, Deserialize)]
pub struct PublicArchiveV1p1 {
map: BTreeMap<PathBuf, (DataAddress, MetadataV1p1)>,
}
#[derive(Debug, Serialize, Deserialize)]
pub enum PublicArchiveVersionedV1p1 {
V0(PublicArchiveV1p1),
}
let mut arch_p1 = PublicArchiveV1p1::default();
arch_p1.map.insert(
PathBuf::from_str("hello_world").unwrap(),
(
DataAddress::new(XorName::random(&mut rand::thread_rng())),
MetadataV1p1 {
accessed: Some(1),
..Default::default()
},
),
);
let arch_p1_ser =
rmp_serde::to_vec_named(&PublicArchiveVersionedV1p1::V0(arch_p1)).unwrap();
assert!(PublicArchive::from_bytes(Bytes::from(arch_p1_ser)).is_ok());
}
#[test]
fn test_archive_merge() {
let mut arch = PublicArchive::new();
let file1 = PathBuf::from_str("file1").unwrap();
let file2 = PathBuf::from_str("file2").unwrap();
arch.add_file(
file1.clone(),
DataAddress::new(XorName::random(&mut rand::thread_rng())),
Metadata::new_with_size(1),
);
let mut other_arch = PublicArchive::new();
other_arch.add_file(
file2.clone(),
DataAddress::new(XorName::random(&mut rand::thread_rng())),
Metadata::new_with_size(2),
);
arch.merge(&other_arch);
assert_eq!(arch.map().len(), 2);
assert_eq!(arch.map().get(&file1).unwrap().1.size, 1);
assert_eq!(arch.map().get(&file2).unwrap().1.size, 2);
let mut arch_with_duplicate = PublicArchive::new();
arch_with_duplicate.add_file(
file1.clone(),
DataAddress::new(XorName::random(&mut rand::thread_rng())),
Metadata::new_with_size(5),
);
arch.merge(&arch_with_duplicate);
assert_eq!(arch.map().len(), 2);
assert_eq!(arch.map().get(&file1).unwrap().1.size, 5);
assert_eq!(arch.map().get(&file2).unwrap().1.size, 2);
}
}