Skip to content
This repository was archived by the owner on Sep 21, 2024. It is now read-only.

Commit a26fd83

Browse files
committed
feat: Introduce storage migrations via ImportStorage/ExportStorage traits, and other trait-based operations.
1 parent 36cf25e commit a26fd83

File tree

16 files changed

+812
-153
lines changed

16 files changed

+812
-153
lines changed

.vscode/settings.json

+2-3
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212
},
1313
"rust-analyzer.cargo.features": [
1414
"test-kubo",
15-
"helpers",
16-
"performance"
15+
"helpers"
1716
]
18-
}
17+
}

rust/noosphere-storage/Cargo.toml

+9-11
Original file line numberDiff line numberDiff line change
@@ -21,30 +21,28 @@ readme = "README.md"
2121
anyhow = { workspace = true }
2222
async-trait = "~0.1"
2323
async-stream = { workspace = true }
24-
tokio-stream = { workspace = true }
24+
base64 = "=0.21.2"
2525
cid = { workspace = true }
26-
noosphere-common = { version = "0.1.0", path = "../noosphere-common" }
27-
tracing = "~0.1"
28-
ucan = { workspace = true }
26+
instant = { version = "0.1.12", features = ["wasm-bindgen"] }
2927
libipld-core = { workspace = true }
3028
libipld-cbor = { workspace = true }
29+
noosphere-common = { version = "0.1.0", path = "../noosphere-common" }
30+
rand = { workspace = true }
3131
serde = { workspace = true }
32-
base64 = "=0.21.2"
32+
tokio-stream = { workspace = true }
33+
tracing = "~0.1"
34+
ucan = { workspace = true }
3335
url = { version = "^2" }
36+
witty-phrase-generator = "~0.2"
3437

3538
[dev-dependencies]
36-
witty-phrase-generator = "~0.2"
3739
wasm-bindgen-test = { workspace = true }
38-
rand = { workspace = true }
3940
noosphere-core-dev = { path = "../noosphere-core", features = ["helpers"], package = "noosphere-core" }
4041
noosphere-common = { path = "../noosphere-common", features = ["helpers"] }
41-
instant = { version = "0.1.12", features = ["wasm-bindgen"] }
42-
43-
[target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies]
44-
tempfile = { workspace = true }
4542

4643
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
4744
sled = "~0.34"
45+
tempfile = { workspace = true }
4846
tokio = { workspace = true, features = ["full"] }
4947
rocksdb = { version = "0.21.0", optional = true }
5048

rust/noosphere-storage/examples/bench/main.rs

+2-4
Original file line numberDiff line numberDiff line change
@@ -132,17 +132,15 @@ impl BenchmarkStorage {
132132
))]
133133
let (storage, storage_name) = {
134134
(
135-
noosphere_storage::SledStorage::new(noosphere_storage::SledStorageInit::Path(
136-
storage_path.into(),
137-
))?,
135+
noosphere_storage::SledStorage::new(&storage_path)?,
138136
"SledDbStorage",
139137
)
140138
};
141139

142140
#[cfg(all(not(target_arch = "wasm32"), feature = "rocksdb"))]
143141
let (storage, storage_name) = {
144142
(
145-
noosphere_storage::RocksDbStorage::new(storage_path.into())?,
143+
noosphere_storage::RocksDbStorage::new(&storage_path)?,
146144
"RocksDbStorage",
147145
)
148146
};

rust/noosphere-storage/src/backup.rs

+154
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
use crate::storage::Storage;
2+
use anyhow::Result;
3+
use async_trait::async_trait;
4+
use noosphere_common::ConditionalSend;
5+
use std::path::{Path, PathBuf};
6+
7+
#[cfg(not(target_arch = "wasm32"))]
8+
fn create_backup_path<P: AsRef<Path>>(path: P) -> Result<PathBuf> {
9+
use instant::SystemTime;
10+
use rand::Rng;
11+
12+
let mut path = path.as_ref().to_owned();
13+
let timestamp = SystemTime::UNIX_EPOCH
14+
.elapsed()
15+
.map_err(|_| anyhow::anyhow!("Could not generate timestamp."))?
16+
.as_secs();
17+
let nonce = rand::thread_rng().gen::<u32>();
18+
path.set_extension(format!("backup.{}-{}", timestamp, nonce));
19+
Ok(path)
20+
}
21+
22+
/// [Storage] that can be backed up and restored.
23+
/// [FsBackedStorage] types get a blanket implementation.
24+
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
25+
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
26+
pub trait BackupStorage: Storage {
27+
/// Backup [Storage] located at `path`, moving to a backup location.
28+
async fn backup<P: AsRef<Path> + ConditionalSend>(path: P) -> Result<PathBuf>;
29+
/// Backup [Storage] at `restore_to`, moving [Storage] from `backup_path` to `restore_to`.
30+
async fn restore<P: AsRef<Path> + ConditionalSend, Q: AsRef<Path> + ConditionalSend>(
31+
backup_path: P,
32+
restore_to: Q,
33+
) -> Result<PathBuf>;
34+
/// List paths to backups for `path`.
35+
async fn list_backups<P: AsRef<Path> + ConditionalSend>(path: P) -> Result<Vec<PathBuf>>;
36+
}
37+
38+
#[cfg(not(target_arch = "wasm32"))]
39+
#[async_trait]
40+
impl<T> BackupStorage for T
41+
where
42+
T: crate::FsBackedStorage,
43+
{
44+
async fn backup<P: AsRef<Path> + ConditionalSend>(path: P) -> Result<PathBuf> {
45+
let backup_path = create_backup_path(path.as_ref())?;
46+
T::rename(path, &backup_path).await?;
47+
Ok(backup_path)
48+
}
49+
50+
async fn restore<P: AsRef<Path> + ConditionalSend, Q: AsRef<Path> + ConditionalSend>(
51+
backup_path: P,
52+
restore_to: Q,
53+
) -> Result<PathBuf> {
54+
let restoration_path = restore_to.as_ref().to_owned();
55+
let original_backup = T::backup(&restoration_path).await?;
56+
T::rename(backup_path, &restoration_path).await?;
57+
Ok(original_backup)
58+
}
59+
60+
async fn list_backups<P: AsRef<Path> + ConditionalSend>(path: P) -> Result<Vec<PathBuf>> {
61+
let mut backups = vec![];
62+
let matcher = format!(
63+
"{}.backup.",
64+
path.as_ref()
65+
.file_name()
66+
.ok_or_else(|| anyhow::anyhow!("Could not stringify path."))?
67+
.to_str()
68+
.ok_or_else(|| anyhow::anyhow!("Could not stringify path."))?
69+
);
70+
let parent_dir = path
71+
.as_ref()
72+
.parent()
73+
.ok_or_else(|| anyhow::anyhow!("Could not find storage parent directory."))?;
74+
let mut stream = tokio::fs::read_dir(parent_dir).await?;
75+
while let Ok(Some(entry)) = stream.next_entry().await {
76+
if let Ok(file_name) = entry.file_name().into_string() {
77+
if file_name.starts_with(&matcher) {
78+
backups.push(entry.path());
79+
}
80+
}
81+
}
82+
Ok(backups)
83+
}
84+
}
85+
86+
#[cfg(all(not(target_arch = "wasm32"), test))]
87+
mod test {
88+
use crate::{OpenStorage, PreferredPlatformStorage, Store};
89+
90+
use super::*;
91+
92+
#[tokio::test]
93+
pub async fn it_can_backup_storages() -> Result<()> {
94+
noosphere_core_dev::tracing::initialize_tracing(None);
95+
96+
let temp_dir = tempfile::TempDir::new()?;
97+
let db_source = temp_dir.path().join("db");
98+
99+
{
100+
let storage = PreferredPlatformStorage::open(&db_source).await?;
101+
let mut store = storage.get_key_value_store("links").await?;
102+
store.write(b"1", b"1").await?;
103+
}
104+
105+
let backup_1 = PreferredPlatformStorage::backup(&db_source).await?;
106+
107+
{
108+
let storage = PreferredPlatformStorage::open(&db_source).await?;
109+
let mut store = storage.get_key_value_store("links").await?;
110+
assert!(store.read(b"1").await?.is_none(), "Backup is a move");
111+
store.write(b"2", b"2").await?;
112+
}
113+
114+
let backup_2 = PreferredPlatformStorage::backup(&db_source).await?;
115+
116+
{
117+
let storage = PreferredPlatformStorage::open(&db_source).await?;
118+
let mut store = storage.get_key_value_store("links").await?;
119+
assert!(store.read(b"1").await?.is_none(), "Backup is a move");
120+
assert!(store.read(b"2").await?.is_none(), "Backup is a move");
121+
store.write(b"3", b"3").await?;
122+
}
123+
124+
let backups = PreferredPlatformStorage::list_backups(&db_source).await?;
125+
assert_eq!(backups.len(), 2);
126+
assert!(backups.contains(&backup_1));
127+
assert!(backups.contains(&backup_2));
128+
129+
let backup_3 = PreferredPlatformStorage::restore(&backup_1, &db_source).await?;
130+
{
131+
let storage = PreferredPlatformStorage::open(&db_source).await?;
132+
let store = storage.get_key_value_store("links").await?;
133+
assert_eq!(store.read(b"1").await?.unwrap(), b"1");
134+
assert!(store.read(b"2").await?.is_none(), "Backup is a move");
135+
assert!(store.read(b"3").await?.is_none(), "Backup is a move");
136+
}
137+
138+
let backups = PreferredPlatformStorage::list_backups(db_source).await?;
139+
assert_eq!(backups.len(), 2);
140+
assert!(
141+
backups.contains(&backup_3),
142+
"contains backup from restoration."
143+
);
144+
assert!(
145+
!backups.contains(&backup_1),
146+
"moves backup that was restored."
147+
);
148+
assert!(
149+
backups.contains(&backup_2),
150+
"contains backups that were untouched."
151+
);
152+
Ok(())
153+
}
154+
}

rust/noosphere-storage/src/fs.rs

+68
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
use crate::storage::Storage;
2+
use anyhow::Result;
3+
use async_trait::async_trait;
4+
use noosphere_common::ConditionalSend;
5+
use std::path::Path;
6+
7+
/// [Storage] that is based on a file system. Implementing [FsBackedStorage]
8+
/// provides blanket implementations for other trait-based [Storage] operations.
9+
#[cfg(not(target_arch = "wasm32"))]
10+
#[async_trait]
11+
pub trait FsBackedStorage: Storage + Sized {
12+
/// Deletes the storage located at `path` directory. Returns `Ok(())` if
13+
/// the directory is successfully removed, or if it already does not exist.
14+
async fn delete<P: AsRef<Path> + ConditionalSend>(path: P) -> Result<()> {
15+
match std::fs::metadata(path.as_ref()) {
16+
Ok(_) => std::fs::remove_dir_all(path.as_ref()).map_err(|e| e.into()),
17+
Err(_) => Ok(()),
18+
}
19+
}
20+
21+
/// Moves the storage located at `from` to the `to` location.
22+
async fn rename<P: AsRef<Path> + ConditionalSend, Q: AsRef<Path> + ConditionalSend>(
23+
from: P,
24+
to: Q,
25+
) -> Result<()> {
26+
std::fs::rename(from, to).map_err(|e| e.into())
27+
}
28+
}
29+
30+
/// [Storage] that is based on a file system.
31+
#[cfg(target_arch = "wasm32")]
32+
#[async_trait(?Send)]
33+
pub trait FsBackedStorage: Storage + Sized {
34+
/// Deletes the storage located at `path` directory. Returns `Ok(())` if
35+
/// the directory is successfully removed, or if it already does not exist.
36+
async fn delete<P: AsRef<Path> + ConditionalSend>(path: P) -> Result<()>;
37+
38+
/// Moves the storage located at `from` to the `to` location.
39+
async fn rename<P: AsRef<Path> + ConditionalSend, Q: AsRef<Path> + ConditionalSend>(
40+
from: P,
41+
to: Q,
42+
) -> Result<()>;
43+
}
44+
45+
#[cfg(not(target_arch = "wasm32"))]
46+
#[async_trait]
47+
impl<T> crate::ops::DeleteStorage for T
48+
where
49+
T: FsBackedStorage,
50+
{
51+
async fn delete<P: AsRef<Path> + ConditionalSend>(path: P) -> Result<()> {
52+
<T as FsBackedStorage>::delete(path).await
53+
}
54+
}
55+
56+
#[cfg(not(target_arch = "wasm32"))]
57+
#[async_trait]
58+
impl<T> crate::ops::RenameStorage for T
59+
where
60+
T: FsBackedStorage,
61+
{
62+
async fn rename<P: AsRef<Path> + ConditionalSend, Q: AsRef<Path> + ConditionalSend>(
63+
from: P,
64+
to: Q,
65+
) -> Result<()> {
66+
<T as FsBackedStorage>::rename(from, to).await
67+
}
68+
}

rust/noosphere-storage/src/helpers.rs

-34
This file was deleted.

0 commit comments

Comments
 (0)