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

Commit f8ee62a

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

File tree

12 files changed

+598
-130
lines changed

12 files changed

+598
-130
lines changed

rust/noosphere-storage/Cargo.toml

+2-4
Original file line numberDiff line numberDiff line change
@@ -32,21 +32,19 @@ libipld-cbor = { workspace = true }
3232
serde = { workspace = true }
3333
base64 = "=0.21.2"
3434
url = { version = "^2" }
35+
witty-phrase-generator = "~0.2"
3536

3637
[dev-dependencies]
37-
witty-phrase-generator = "~0.2"
3838
wasm-bindgen-test = { workspace = true }
3939
rand = { workspace = true }
4040
# examples/bench
4141
noosphere-core = { version = "0.15.2", path = "../noosphere-core", features = ["helpers"] }
4242
# examples/bench
4343
noosphere-common = { version = "0.1.0", path = "../noosphere-common", features = ["helpers"] }
4444

45-
[target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies]
46-
tempfile = { workspace = true }
47-
4845
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
4946
sled = "~0.34"
47+
tempfile = { workspace = true }
5048
tokio = { workspace = true, features = ["full"] }
5149
rocksdb = { version = "0.21.0", optional = true }
5250

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

+2-4
Original file line numberDiff line numberDiff line change
@@ -128,17 +128,15 @@ impl BenchmarkStorage {
128128
))]
129129
let (storage, storage_name) = {
130130
(
131-
noosphere_storage::SledStorage::new(noosphere_storage::SledStorageInit::Path(
132-
storage_path.into(),
133-
))?,
131+
noosphere_storage::SledStorage::new(&storage_path)?,
134132
"SledDbStorage",
135133
)
136134
};
137135

138136
#[cfg(all(not(target_arch = "wasm32"), feature = "rocksdb"))]
139137
let (storage, storage_name) = {
140138
(
141-
noosphere_storage::RocksDbStorage::new(storage_path.into())?,
139+
noosphere_storage::RocksDbStorage::new(&storage_path)?,
142140
"RocksDbStorage",
143141
)
144142
};

rust/noosphere-storage/src/extra.rs

+94
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
use crate::storage::Storage;
2+
use anyhow::Result;
3+
use async_trait::async_trait;
4+
use noosphere_common::ConditionalSend;
5+
use std::{fs, path::Path};
6+
7+
/// [Storage] that can be opened via [Path] reference.
8+
/// [FsBackedStorage] types get a blanket implementation.
9+
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
10+
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
11+
pub trait OpenStorage: Storage + Sized {
12+
async fn open<P: AsRef<Path> + ConditionalSend>(path: P) -> Result<Self>;
13+
}
14+
15+
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
16+
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
17+
impl<T> OpenStorage for T
18+
where
19+
T: FsBackedStorage,
20+
{
21+
async fn open<P: AsRef<Path> + ConditionalSend>(path: P) -> Result<Self> {
22+
FsBackedStorage::open(path).await
23+
}
24+
}
25+
26+
/// [Storage] that can be deleted via [Path] reference.
27+
/// [FsBackedStorage] types get a blanket implementation.
28+
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
29+
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
30+
pub trait RemoveStorage: Storage + Sized {
31+
async fn remove<P: AsRef<Path> + ConditionalSend>(path: P) -> Result<()>;
32+
}
33+
34+
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
35+
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
36+
impl<T> RemoveStorage for T
37+
where
38+
T: FsBackedStorage,
39+
{
40+
async fn remove<P: AsRef<Path> + ConditionalSend>(path: P) -> Result<()> {
41+
<T as FsBackedStorage>::remove(path).await
42+
}
43+
}
44+
45+
/// [Storage] that can be moved/renamed via [Path] reference.
46+
/// [FsBackedStorage] types get a blanket implementation.
47+
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
48+
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
49+
pub trait RenameStorage: Storage + Sized {
50+
async fn rename<P: AsRef<Path> + ConditionalSend, Q: AsRef<Path> + ConditionalSend>(
51+
from: P,
52+
to: Q,
53+
) -> Result<()>;
54+
}
55+
56+
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
57+
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
58+
impl<T> 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+
}
69+
70+
/// [Storage] that is based on a file system. Implementing [FsBackedStorage]
71+
/// provides blanket implementations for other trait-based [Storage] operations.
72+
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
73+
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
74+
pub trait FsBackedStorage: Storage + Sized {
75+
/// Opens the storage at `path`.
76+
async fn open<P: AsRef<Path> + ConditionalSend>(path: P) -> Result<Self>;
77+
78+
/// Deletes the storage located at `path` directory. Returns `Ok(())` if
79+
/// the directory is successfully removed, or if it already does not exist.
80+
async fn remove<P: AsRef<Path> + ConditionalSend>(path: P) -> Result<()> {
81+
match fs::metadata(path.as_ref()) {
82+
Ok(_) => fs::remove_dir_all(path.as_ref()).map_err(|e| e.into()),
83+
Err(_) => Ok(()),
84+
}
85+
}
86+
87+
/// Moves the storage located at `from` to the `to` location.
88+
async fn rename<P: AsRef<Path> + ConditionalSend, Q: AsRef<Path> + ConditionalSend>(
89+
from: P,
90+
to: Q,
91+
) -> Result<()> {
92+
fs::rename(from, to).map_err(|e| e.into())
93+
}
94+
}

rust/noosphere-storage/src/helpers.rs

-34
This file was deleted.

rust/noosphere-storage/src/implementation/indexed_db.rs

+106-45
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
use crate::store::Store;
22
use crate::{db::SPHERE_DB_STORE_NAMES, storage::Storage};
33
use anyhow::{anyhow, Error, Result};
4+
use async_stream::try_stream;
45
use async_trait::async_trait;
56
use js_sys::Uint8Array;
7+
use noosphere_common::ConditionalSend;
68
use rexie::{
79
KeyRange, ObjectStore, Rexie, RexieBuilder, Store as IdbStore, Transaction, TransactionMode,
810
};
9-
use std::{fmt::Debug, rc::Rc};
11+
use std::{fmt::Debug, path::Path, rc::Rc};
1012
use wasm_bindgen::{JsCast, JsValue};
1113

1214
pub const INDEXEDDB_STORAGE_VERSION: u32 = 1;
@@ -69,7 +71,12 @@ impl IndexedDbStorage {
6971
let db = Rc::into_inner(self.db)
7072
.ok_or_else(|| anyhow!("Could not unwrap inner during database clear."))?;
7173
db.close();
72-
Rexie::delete(&name)
74+
Self::delete(&name).await
75+
}
76+
77+
/// Deletes database with key `db_name` from origin storage.
78+
pub async fn delete(db_name: &str) -> Result<()> {
79+
Rexie::delete(db_name)
7380
.await
7481
.map_err(|error| anyhow!("{:?}", error))
7582
}
@@ -90,6 +97,30 @@ impl Storage for IndexedDbStorage {
9097
}
9198
}
9299

100+
#[async_trait(?Send)]
101+
impl crate::extra::OpenStorage for IndexedDbStorage {
102+
async fn open<P: AsRef<Path> + ConditionalSend>(path: P) -> Result<Self> {
103+
IndexedDbStorage::new(
104+
path.as_ref()
105+
.to_str()
106+
.ok_or_else(|| anyhow!("Could not stringify path."))?,
107+
)
108+
.await
109+
}
110+
}
111+
112+
#[async_trait(?Send)]
113+
impl crate::extra::RemoveStorage for IndexedDbStorage {
114+
async fn remove<P: AsRef<Path> + ConditionalSend>(path: P) -> Result<()> {
115+
Self::delete(
116+
path.as_ref()
117+
.to_str()
118+
.ok_or_else(|| anyhow!("Could not stringify path."))?,
119+
)
120+
.await
121+
}
122+
}
123+
93124
#[derive(Clone)]
94125
pub struct IndexedDbStore {
95126
db: Rc<Rexie>,
@@ -114,87 +145,104 @@ impl IndexedDbStore {
114145
Ok(())
115146
}
116147

117-
fn bytes_to_typed_array(bytes: &[u8]) -> Result<JsValue> {
118-
let array = Uint8Array::new_with_length(bytes.len() as u32);
119-
array.copy_from(&bytes);
120-
Ok(JsValue::from(array))
121-
}
122-
123-
async fn contains(key: &JsValue, store: &IdbStore) -> Result<bool> {
148+
async fn contains(key: &[u8], store: &IdbStore) -> Result<bool> {
149+
let key_js = bytes_to_typed_array(key)?;
124150
let count = store
125151
.count(Some(
126-
&KeyRange::only(key).map_err(|error| anyhow!("{:?}", error))?,
152+
&KeyRange::only(&key_js).map_err(|error| anyhow!("{:?}", error))?,
127153
))
128154
.await
129155
.map_err(|error| anyhow!("{:?}", error))?;
130156
Ok(count > 0)
131157
}
132158

133-
async fn read(key: &JsValue, store: &IdbStore) -> Result<Option<Vec<u8>>> {
159+
async fn read(key: &[u8], store: &IdbStore) -> Result<Option<Vec<u8>>> {
160+
let key_js = bytes_to_typed_array(key)?;
134161
Ok(match IndexedDbStore::contains(&key, &store).await? {
135-
true => Some(
162+
true => Some(typed_array_to_bytes(
136163
store
137-
.get(&key)
164+
.get(&key_js)
138165
.await
139-
.map_err(|error| anyhow!("{:?}", error))?
140-
.dyn_into::<Uint8Array>()
141-
.map_err(|error| anyhow!("{:?}", error))?
142-
.to_vec(),
143-
),
166+
.map_err(|error| anyhow!("{:?}", error))?,
167+
)?),
144168
false => None,
145169
})
146170
}
171+
172+
async fn put(key: &[u8], value: &[u8], store: &IdbStore) -> Result<()> {
173+
let key_js = bytes_to_typed_array(key)?;
174+
let value_js = bytes_to_typed_array(value)?;
175+
store
176+
.put(&value_js, Some(&key_js))
177+
.await
178+
.map_err(|error| anyhow!("{:?}", error))?;
179+
Ok(())
180+
}
181+
182+
async fn delete(key: &[u8], store: &IdbStore) -> Result<()> {
183+
let key_js = bytes_to_typed_array(key)?;
184+
store
185+
.delete(&key_js)
186+
.await
187+
.map_err(|error| anyhow!("{:?}", error))?;
188+
Ok(())
189+
}
147190
}
148191

149192
#[async_trait(?Send)]
150193
impl Store for IndexedDbStore {
151194
async fn read(&self, key: &[u8]) -> Result<Option<Vec<u8>>> {
152195
let (store, tx) = self.start_transaction(TransactionMode::ReadOnly)?;
153-
let key = IndexedDbStore::bytes_to_typed_array(key)?;
154-
155-
let maybe_dag = IndexedDbStore::read(&key, &store).await?;
156-
196+
let maybe_dag = IndexedDbStore::read(key, &store).await?;
157197
IndexedDbStore::finish_transaction(tx).await?;
158-
159198
Ok(maybe_dag)
160199
}
161200

162201
async fn write(&mut self, key: &[u8], bytes: &[u8]) -> Result<Option<Vec<u8>>> {
163202
let (store, tx) = self.start_transaction(TransactionMode::ReadWrite)?;
164-
165-
let key = IndexedDbStore::bytes_to_typed_array(key)?;
166-
let value = IndexedDbStore::bytes_to_typed_array(bytes)?;
167-
168203
let old_bytes = IndexedDbStore::read(&key, &store).await?;
169-
170-
store
171-
.put(&value, Some(&key))
172-
.await
173-
.map_err(|error| anyhow!("{:?}", error))?;
174-
204+
IndexedDbStore::put(key, bytes, &store).await?;
175205
IndexedDbStore::finish_transaction(tx).await?;
176-
177206
Ok(old_bytes)
178207
}
179208

180209
async fn remove(&mut self, key: &[u8]) -> Result<Option<Vec<u8>>> {
181210
let (store, tx) = self.start_transaction(TransactionMode::ReadWrite)?;
182-
183-
let key = IndexedDbStore::bytes_to_typed_array(key)?;
184-
185-
let old_value = IndexedDbStore::read(&key, &store).await?;
186-
187-
store
188-
.delete(&key)
189-
.await
190-
.map_err(|error| anyhow!("{:?}", error))?;
191-
211+
let old_value = IndexedDbStore::read(key, &store).await?;
212+
IndexedDbStore::delete(key, &store).await?;
192213
IndexedDbStore::finish_transaction(tx).await?;
193-
194214
Ok(old_value)
195215
}
196216
}
197217

218+
impl crate::IterableStore for IndexedDbStore {
219+
fn get_all_entries(&self) -> crate::IterableStoreStream<'_> {
220+
Box::pin(try_stream! {
221+
let (store, tx) = self.start_transaction(TransactionMode::ReadWrite)?;
222+
let limit = 100;
223+
let mut offset = 0;
224+
loop {
225+
let results = store.get_all(None, Some(limit), Some(offset), None).await
226+
.map_err(|error| anyhow!("{:?}", error))?;
227+
let count = results.len();
228+
if count == 0 {
229+
IndexedDbStore::finish_transaction(tx).await?;
230+
break;
231+
}
232+
233+
offset += count as u32;
234+
235+
for (key_js, value_js) in results {
236+
yield (
237+
typed_array_to_bytes(JsValue::from(Uint8Array::new(&key_js)))?,
238+
Some(typed_array_to_bytes(value_js)?)
239+
);
240+
}
241+
}
242+
})
243+
}
244+
}
245+
198246
#[cfg(feature = "performance")]
199247
struct SpaceUsageError(Error);
200248

@@ -263,3 +311,16 @@ impl crate::Space for IndexedDbStorage {
263311
}
264312
}
265313
}
314+
315+
fn bytes_to_typed_array(bytes: &[u8]) -> Result<JsValue> {
316+
let array = Uint8Array::new_with_length(bytes.len() as u32);
317+
array.copy_from(&bytes);
318+
Ok(JsValue::from(array))
319+
}
320+
321+
fn typed_array_to_bytes(js_value: JsValue) -> Result<Vec<u8>> {
322+
Ok(js_value
323+
.dyn_into::<Uint8Array>()
324+
.map_err(|error| anyhow!("{:?}", error))?
325+
.to_vec())
326+
}

0 commit comments

Comments
 (0)