166 lines
5.4 KiB
Rust
166 lines
5.4 KiB
Rust
use std::net;
|
|
use std::sync::Arc;
|
|
|
|
use crossbeam_channel as chan;
|
|
use nakamoto_net::Waker;
|
|
use thiserror::Error;
|
|
|
|
use crate::identity::Id;
|
|
use crate::service;
|
|
use crate::service::{CommandError, FetchLookup, QueryState};
|
|
use crate::service::{NodeId, Session};
|
|
|
|
/// An error resulting from a handle method.
|
|
#[derive(Error, Debug)]
|
|
pub enum Error {
|
|
/// The command channel is no longer connected.
|
|
#[error("command channel is not connected")]
|
|
NotConnected,
|
|
/// The command returned an error.
|
|
#[error("command failed: {0}")]
|
|
Command(#[from] CommandError),
|
|
/// The operation timed out.
|
|
#[error("the operation timed out")]
|
|
Timeout,
|
|
/// An I/O error occured.
|
|
#[error(transparent)]
|
|
Io(#[from] std::io::Error),
|
|
}
|
|
|
|
impl From<chan::RecvError> for Error {
|
|
fn from(_: chan::RecvError) -> Self {
|
|
Self::NotConnected
|
|
}
|
|
}
|
|
|
|
impl From<chan::RecvTimeoutError> for Error {
|
|
fn from(err: chan::RecvTimeoutError) -> Self {
|
|
match err {
|
|
chan::RecvTimeoutError::Timeout => Self::Timeout,
|
|
chan::RecvTimeoutError::Disconnected => Self::NotConnected,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<T> From<chan::SendError<T>> for Error {
|
|
fn from(_: chan::SendError<T>) -> Self {
|
|
Self::NotConnected
|
|
}
|
|
}
|
|
|
|
pub struct Handle<W: Waker> {
|
|
pub(crate) commands: chan::Sender<service::Command>,
|
|
pub(crate) shutdown: chan::Sender<()>,
|
|
pub(crate) listening: chan::Receiver<net::SocketAddr>,
|
|
pub(crate) waker: W,
|
|
}
|
|
|
|
impl<W: Waker> traits::Handle for Handle<W> {
|
|
/// Retrieve or update the given project from the network.
|
|
fn fetch(&self, id: Id) -> Result<FetchLookup, Error> {
|
|
let (sender, receiver) = chan::bounded(1);
|
|
self.commands.send(service::Command::Fetch(id, sender))?;
|
|
receiver.recv().map_err(Error::from)
|
|
}
|
|
|
|
/// Start tracking the given project. Doesn't do anything if the project is already tracked.
|
|
fn track(&self, id: Id) -> Result<bool, Error> {
|
|
let (sender, receiver) = chan::bounded(1);
|
|
self.commands.send(service::Command::Track(id, sender))?;
|
|
receiver.recv().map_err(Error::from)
|
|
}
|
|
|
|
/// Untrack the given project and delete it from storage.
|
|
fn untrack(&self, id: Id) -> Result<bool, Error> {
|
|
let (sender, receiver) = chan::bounded(1);
|
|
self.commands.send(service::Command::Untrack(id, sender))?;
|
|
receiver.recv().map_err(Error::from)
|
|
}
|
|
|
|
/// Notify the client that a project has been updated.
|
|
fn announce_refs(&self, id: Id) -> Result<(), Error> {
|
|
self.command(service::Command::AnnounceRefs(id))
|
|
}
|
|
|
|
/// Send a command to the command channel, and wake up the event loop.
|
|
fn command(&self, cmd: service::Command) -> Result<(), Error> {
|
|
self.commands.send(cmd)?;
|
|
self.waker.wake()?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn routing(&self) -> Result<chan::Receiver<(Id, Vec<NodeId>)>, Error> {
|
|
let (sender, receiver) = chan::unbounded();
|
|
let query: Arc<QueryState> = Arc::new(move |state| {
|
|
for (id, nodes) in state.routing().iter() {
|
|
if sender.send((*id, nodes.iter().cloned().collect())).is_err() {
|
|
break;
|
|
}
|
|
}
|
|
Ok(())
|
|
});
|
|
let (err_sender, err_receiver) = chan::bounded(1);
|
|
self.command(service::Command::QueryState(query, err_sender))?;
|
|
err_receiver.recv()??;
|
|
|
|
Ok(receiver)
|
|
}
|
|
|
|
fn sessions(&self) -> Result<chan::Receiver<(NodeId, Session)>, Error> {
|
|
// TODO: This can be implemented once we have real peer sessions.
|
|
todo!()
|
|
}
|
|
|
|
fn inventory(&self) -> Result<chan::Receiver<Id>, Error> {
|
|
let (sender, receiver) = chan::unbounded();
|
|
let query: Arc<QueryState> = Arc::new(move |state| {
|
|
for id in state.inventory()?.iter() {
|
|
if sender.send(*id).is_err() {
|
|
break;
|
|
}
|
|
}
|
|
Ok(())
|
|
});
|
|
let (err_sender, err_receiver) = chan::bounded(1);
|
|
self.command(service::Command::QueryState(query, err_sender))?;
|
|
err_receiver.recv()??;
|
|
|
|
Ok(receiver)
|
|
}
|
|
|
|
/// Ask the client to shutdown.
|
|
fn shutdown(self) -> Result<(), Error> {
|
|
self.shutdown.send(())?;
|
|
self.waker.wake()?;
|
|
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
pub mod traits {
|
|
use super::*;
|
|
|
|
pub trait Handle {
|
|
/// Retrieve or update the project from network.
|
|
fn fetch(&self, id: Id) -> Result<FetchLookup, Error>;
|
|
/// Start tracking the given project. Doesn't do anything if the project is already
|
|
/// tracked.
|
|
fn track(&self, id: Id) -> Result<bool, Error>;
|
|
/// Untrack the given project and delete it from storage.
|
|
fn untrack(&self, id: Id) -> Result<bool, Error>;
|
|
/// Notify the client that a project has been updated.
|
|
fn announce_refs(&self, id: Id) -> Result<(), Error>;
|
|
/// Send a command to the command channel, and wake up the event loop.
|
|
fn command(&self, cmd: service::Command) -> Result<(), Error>;
|
|
/// Ask the client to shutdown.
|
|
fn shutdown(self) -> Result<(), Error>;
|
|
/// Query the routing table.
|
|
fn routing(&self) -> Result<chan::Receiver<(Id, Vec<NodeId>)>, Error>;
|
|
/// Query the peer session state.
|
|
fn sessions(&self) -> Result<chan::Receiver<(NodeId, Session)>, Error>;
|
|
/// Query the inventory.
|
|
fn inventory(&self) -> Result<chan::Receiver<Id>, Error>;
|
|
}
|
|
}
|