Skip to content

Commit

Permalink
Dynamic dispatch for Storage, no generics in Feed
Browse files Browse the repository at this point in the history
This removes the generic argument from the Feed struct, making it
simpler to work with. Instead, the Storage is internally put into a
Box<dyn DynStorage>, where DynStorage is an async trait with the public
Storage functions.
  • Loading branch information
Frando authored and bltavares committed May 17, 2020
1 parent 37d2a9c commit 1d8c6da
Show file tree
Hide file tree
Showing 7 changed files with 162 additions and 118 deletions.
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ tree-index = "0.6.0"
bitfield-rle = "0.1.1"
futures = "0.3.4"
async-std = "1.5.0"
async-trait = "0.1.30"

[dev-dependencies]
quickcheck = "0.9.2"
Expand Down
15 changes: 3 additions & 12 deletions examples/async.rs
Original file line number Diff line number Diff line change
@@ -1,27 +1,18 @@
use async_std::task;
use hypercore::Feed;
use random_access_storage::RandomAccess;
use std::fmt::Debug;

async fn append<T>(feed: &mut Feed<T>, content: &[u8])
where
T: RandomAccess<Error = Box<dyn std::error::Error + Send + Sync>> + Debug + Send,
{
async fn append(feed: &mut Feed, content: &[u8]) {
feed.append(content).await.unwrap();
}

async fn print<T>(feed: &mut Feed<T>)
where
T: RandomAccess<Error = Box<dyn std::error::Error + Send + Sync>> + Debug + Send,
{
async fn print(feed: &mut Feed) {
println!("{:?}", feed.get(0).await);
println!("{:?}", feed.get(1).await);
}

fn main() {
task::block_on(task::spawn(async {
let mut feed = Feed::default();

let mut feed = Feed::open_in_memory().await.unwrap();
append(&mut feed, b"hello").await;
append(&mut feed, b"world").await;
print(&mut feed).await;
Expand Down
70 changes: 30 additions & 40 deletions src/feed.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@
use crate::feed_builder::FeedBuilder;
use crate::replicate::{Message, Peer};
pub use crate::storage::{Node, NodeTrait, Storage, Store};
pub use crate::storage::{
storage_disk, storage_memory, BoxStorage, Node, NodeTrait, Storage, Store,
};

use crate::audit::Audit;
use crate::bitfield::Bitfield;
Expand All @@ -13,12 +15,8 @@ use crate::proof::Proof;
use anyhow::{bail, ensure, Result};
use flat_tree as flat;
use pretty_hash::fmt as pretty_fmt;
use random_access_disk::RandomAccessDisk;
use random_access_memory::RandomAccessMemory;
use random_access_storage::RandomAccess;
use tree_index::TreeIndex;

use std::borrow::Borrow;
use std::cmp;
use std::fmt::{self, Debug, Display};
use std::ops::Range;
Expand Down Expand Up @@ -55,15 +53,12 @@ use std::sync::Arc;
/// [builder]: crate::feed_builder::FeedBuilder
/// [with_storage]: crate::feed::Feed::with_storage
#[derive(Debug)]
pub struct Feed<T>
where
T: RandomAccess<Error = Box<dyn std::error::Error + Send + Sync>> + Debug,
{
pub struct Feed {
/// Merkle tree instance.
pub(crate) merkle: Merkle,
pub(crate) public_key: PublicKey,
pub(crate) secret_key: Option<SecretKey>,
pub(crate) storage: Storage<T>,
pub(crate) storage: BoxStorage,
/// Total length of raw data stored in bytes.
pub(crate) byte_length: u64,
/// Total number of entries stored in the `Feed`
Expand All @@ -74,12 +69,9 @@ where
pub(crate) peers: Vec<Peer>,
}

impl<T> Feed<T>
where
T: RandomAccess<Error = Box<dyn std::error::Error + Send + Sync>> + Debug + Send,
{
impl Feed {
/// Create a new instance with a custom storage backend.
pub async fn with_storage(mut storage: crate::storage::Storage<T>) -> Result<Self> {
pub async fn with_storage(mut storage: BoxStorage) -> Result<Self> {
match storage.read_partial_keypair().await {
Some(partial_keypair) => {
let builder = FeedBuilder::new(partial_keypair.public, storage);
Expand All @@ -105,11 +97,27 @@ where
}

/// Starts a `FeedBuilder` with the provided `PublicKey` and `Storage`.
pub fn builder(public_key: PublicKey, storage: Storage<T>) -> FeedBuilder<T> {
pub fn builder(public_key: PublicKey, storage: BoxStorage) -> FeedBuilder {
FeedBuilder::new(public_key, storage)
}

/// Get the number of entries in the feed.
/// Create a new instance that persists to disk at the location of `dir`.
// TODO: Ensure that dir is always a directory.
// NOTE: Should we `mkdirp` here?
// NOTE: Should we call these `data.bitfield` / `data.tree`?
pub async fn open_from_disk<P: AsRef<Path>>(path: P) -> Result<Self> {
let dir = path.as_ref().to_owned();
let storage = storage_disk(&dir).await?;
Self::with_storage(storage).await
}

/// Create a new in-memory instance.
pub async fn open_in_memory() -> Result<Self> {
let storage = storage_memory().await.unwrap();
Self::with_storage(storage).await
}

/// Get the amount of entries in the feed.
#[inline]
pub fn len(&self) -> u64 {
self.length
Expand Down Expand Up @@ -151,7 +159,7 @@ where
let index = self.length;
let message = hash_with_length_as_bytes(hash, index + 1);
let signature = sign(&self.public_key, key, &message);
self.storage.put_signature(index, signature).await?;
self.storage.put_signature(index, &signature).await?;

for node in self.merkle.nodes() {
self.storage.put_node(node).await?;
Expand Down Expand Up @@ -387,8 +395,7 @@ where
}

if let Some(sig) = sig {
let sig = sig.borrow();
self.storage.put_signature(index, sig).await?;
self.storage.put_signature(index, &sig).await?;
}

for node in nodes {
Expand Down Expand Up @@ -593,35 +600,18 @@ where
}
}

impl Feed<RandomAccessDisk> {
/// Create a new instance that persists to disk at the location of `dir`.
// TODO: Ensure that dir is always a directory.
// NOTE: Should we `mkdirp` here?
// NOTE: Should we call these `data.bitfield` / `data.tree`?
pub async fn open<P: AsRef<Path>>(path: P) -> Result<Self> {
let dir = path.as_ref().to_owned();
let storage = Storage::new_disk(&dir).await?;
Self::with_storage(storage).await
}
}

/// Create a new instance with an in-memory storage backend.
///
/// ## Panics
/// Can panic if constructing the in-memory store fails, which is highly
/// unlikely.
impl Default for Feed<RandomAccessMemory> {
impl Default for Feed {
fn default() -> Self {
async_std::task::block_on(async {
let storage = Storage::new_memory().await.unwrap();
Self::with_storage(storage).await.unwrap()
})
async_std::task::block_on(async { Self::open_in_memory().await.unwrap() })
}
}

impl<T: RandomAccess<Error = Box<dyn std::error::Error + Send + Sync>> + Debug + Send> Display
for Feed<T>
{
impl Display for Feed {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
// TODO: yay, we should find a way to convert this .unwrap() to an error
// type that's accepted by `fmt::Result<(), fmt::Error>`.
Expand Down
19 changes: 6 additions & 13 deletions src/feed_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@ use ed25519_dalek::{PublicKey, SecretKey};

use crate::bitfield::Bitfield;
use crate::crypto::Merkle;
use crate::storage::Storage;
use random_access_storage::RandomAccess;
use crate::storage::BoxStorage;
use std::fmt::Debug;
use tree_index::TreeIndex;

Expand All @@ -14,22 +13,16 @@ use anyhow::Result;
// TODO: make this an actual builder pattern.
// https://deterministic.space/elegant-apis-in-rust.html#builder-pattern
#[derive(Debug)]
pub struct FeedBuilder<T>
where
T: RandomAccess + Debug,
{
storage: Storage<T>,
pub struct FeedBuilder {
storage: BoxStorage,
public_key: PublicKey,
secret_key: Option<SecretKey>,
}

impl<T> FeedBuilder<T>
where
T: RandomAccess<Error = Box<dyn std::error::Error + Send + Sync>> + Debug,
{
impl FeedBuilder {
/// Create a new instance.
#[inline]
pub fn new(public_key: PublicKey, storage: Storage<T>) -> Self {
pub fn new(public_key: PublicKey, storage: BoxStorage) -> Self {
Self {
storage,
public_key,
Expand All @@ -45,7 +38,7 @@ where

/// Finalize the builder.
#[inline]
pub fn build(self) -> Result<Feed<T>> {
pub fn build(self) -> Result<Feed> {
Ok(Feed {
merkle: Merkle::new(),
byte_length: 0,
Expand Down
6 changes: 2 additions & 4 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,6 @@ pub use ed25519_dalek::{PublicKey, SecretKey};
use std::path::Path;

/// Create a new Hypercore `Feed`.
pub async fn open<P: AsRef<Path>>(
path: P,
) -> anyhow::Result<Feed<random_access_disk::RandomAccessDisk>> {
Feed::open(path).await
pub async fn open<P: AsRef<Path>>(path: P) -> anyhow::Result<Feed> {
Feed::open_from_disk(path).await
}
Loading

0 comments on commit 1d8c6da

Please sign in to comment.