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 committed Apr 8, 2020
1 parent f41ebe3 commit 60166b0
Show file tree
Hide file tree
Showing 7 changed files with 161 additions and 117 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
68 changes: 29 additions & 39 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 @@ -12,12 +14,8 @@ use anyhow::{bail, ensure, Result};
use ed25519_dalek::{PublicKey, SecretKey, Signature};
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 All @@ -26,15 +24,12 @@ use std::sync::Arc;

/// Append-only log structure.
#[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 data stored.
pub(crate) byte_length: u64,
/// TODO: description. Length of... roots?
Expand All @@ -45,12 +40,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 @@ -76,10 +68,26 @@ where
}

/// Starts a `FeedBuilder` with the provided `Keypair` 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)
}

/// 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 {
Expand Down Expand Up @@ -116,7 +124,7 @@ where
let hash = Hash::from_roots(self.merkle.roots());
let index = self.length;
let signature = sign(&self.public_key, key, hash.as_bytes());
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 @@ -352,8 +360,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 @@ -556,35 +563,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 60166b0

Please sign in to comment.