use std::sync::Arc; use crossbeam_channel as chan; 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 for Error { fn from(_: chan::RecvError) -> Self { Self::NotConnected } } impl From for Error { fn from(err: chan::RecvTimeoutError) -> Self { match err { chan::RecvTimeoutError::Timeout => Self::Timeout, chan::RecvTimeoutError::Disconnected => Self::NotConnected, } } } impl From> for Error { fn from(_: chan::SendError) -> Self { Self::NotConnected } } pub struct Handle { pub(crate) controller: reactor::Controller, } impl Clone for Handle { fn clone(&self) -> Self { Self { controller: self.controller.clone(), } } } impl From> for Handle { fn from(controller: reactor::Controller) -> Handle { Handle { controller } } } impl> Handle { fn command(&self, cmd: service::Command) -> Result<(), Error> { self.controller.send(cmd)?; Ok(()) } } impl> radicle::node::Handle for Handle { type Session = Session; type FetchLookup = FetchLookup; type Error = Error; fn connect(&mut self, node: NodeId, addr: radicle::node::Address) -> Result<(), Error> { self.command(service::Command::Connect(node, addr))?; Ok(()) } fn fetch(&mut self, id: Id) -> Result { let (sender, receiver) = chan::bounded(1); self.command(service::Command::Fetch(id, sender))?; receiver.recv().map_err(Error::from) } fn track_node(&mut self, id: NodeId, alias: Option) -> Result { let (sender, receiver) = chan::bounded(1); self.command(service::Command::TrackNode(id, alias, sender))?; receiver.recv().map_err(Error::from) } fn untrack_node(&mut self, id: NodeId) -> Result { let (sender, receiver) = chan::bounded(1); self.command(service::Command::UntrackNode(id, sender))?; receiver.recv().map_err(Error::from) } fn track_repo(&mut self, id: Id) -> Result { let (sender, receiver) = chan::bounded(1); self.command(service::Command::TrackRepo(id, sender))?; receiver.recv().map_err(Error::from) } fn untrack_repo(&mut self, id: Id) -> Result { let (sender, receiver) = chan::bounded(1); self.command(service::Command::UntrackRepo(id, sender))?; receiver.recv().map_err(Error::from) } fn announce_refs(&mut self, id: Id) -> Result<(), Error> { self.command(service::Command::AnnounceRefs(id)) } fn routing(&self) -> Result, Error> { let (sender, receiver) = chan::unbounded(); let query: Arc = Arc::new(move |state| { for (id, node) in state.routing().entries()? { if sender.send((id, node)).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, Error> { // TODO: This can be implemented once we have real peer sessions. todo!() } fn inventory(&self) -> Result, Error> { let (sender, receiver) = chan::unbounded(); let query: Arc = 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) } fn shutdown(self) -> Result<(), Error> { self.controller.shutdown().map_err(|_| Error::NotConnected) } }