use std::fmt; use std::io; use std::os::unix::net::UnixStream; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::Arc; use crossbeam_channel as chan; use cyphernet::Ecdh; use radicle::node::Seeds; use thiserror::Error; use crate::crypto::Signer; use crate::identity::Id; use crate::node::{Command, FetchResult}; use crate::profile::Home; use crate::runtime::Emitter; use crate::service; use crate::service::tracking; use crate::service::Event; use crate::service::{CommandError, QueryState}; use crate::service::{NodeId, Sessions}; use crate::wire; use crate::worker::TaskResult; /// 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) home: Home, pub(crate) controller: reactor::Controller>, /// Whether a shutdown was initiated or not. Prevents attempting to shutdown twice. shutdown: Arc, /// Publishes events to subscribers. emitter: Emitter, } impl Handle { /// Subscribe to events stream. pub fn events(&mut self) -> chan::Receiver { let (sender, receiver) = chan::unbounded(); let mut subs = self.emitter.subscribers.lock().unwrap(); subs.push(sender); receiver } } impl fmt::Debug for Handle { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("Handle").field("home", &self.home).finish() } } impl Clone for Handle { fn clone(&self) -> Self { Self { home: self.home.clone(), controller: self.controller.clone(), shutdown: self.shutdown.clone(), emitter: self.emitter.clone(), } } } impl Handle { pub fn new( home: Home, controller: reactor::Controller>, emitter: Emitter, ) -> Self { Self { home, controller, shutdown: Arc::default(), emitter, } } pub fn worker_result(&mut self, resp: TaskResult) -> Result<(), Error> { match self.controller.cmd(wire::Control::Worker(resp)) { Ok(()) => {} Err(err) if err.kind() == io::ErrorKind::BrokenPipe => return Err(Error::NotConnected), Err(err) => return Err(err.into()), } Ok(()) } fn command(&self, cmd: service::Command) -> Result<(), Error> { self.controller.cmd(wire::Control::User(cmd))?; Ok(()) } } impl radicle::node::Handle for Handle { type Sessions = Sessions; type Error = Error; fn is_running(&self) -> bool { true } fn connect(&mut self, node: NodeId, addr: radicle::node::Address) -> Result<(), Error> { self.command(service::Command::Connect(node, addr))?; Ok(()) } fn seeds(&mut self, id: Id) -> Result { let (sender, receiver) = chan::bounded(1); self.command(service::Command::Seeds(id, sender))?; receiver.recv().map_err(Error::from) } fn fetch(&mut self, id: Id, from: NodeId) -> Result { let (sender, receiver) = chan::bounded(1); self.command(service::Command::Fetch(id, from, 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, scope: tracking::Scope) -> Result { let (sender, receiver) = chan::bounded(1); self.command(service::Command::TrackRepo(id, scope, 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 announce_inventory(&mut self) -> Result<(), Error> { self.command(service::Command::AnnounceInventory) } fn sync_inventory(&mut self) -> Result { let (sender, receiver) = chan::bounded(1); self.command(service::Command::SyncInventory(sender))?; receiver.recv().map_err(Error::from) } fn sessions(&self) -> Result { let (sender, receiver) = chan::unbounded(); let query: Arc = Arc::new(move |state| { sender.send(state.sessions().clone()).ok(); Ok(()) }); let (err_sender, err_receiver) = chan::bounded(1); self.command(service::Command::QueryState(query, err_sender))?; err_receiver.recv()??; let sessions = receiver.recv()?; Ok(sessions) } fn shutdown(self) -> Result<(), Error> { // If the current value is `false`, set it to `true`, otherwise error. if self .shutdown .compare_exchange(false, true, Ordering::SeqCst, Ordering::SeqCst) .is_err() { return Ok(()); } // Send a shutdown request to our own control socket. This is the only way to kill the // control thread gracefully. Since the control thread may have called this function, // the control socket may already be disconnected. Ignore errors. UnixStream::connect(self.home.socket()) .and_then(|sock| Command::SHUTDOWN.to_writer(sock)) .ok(); self.controller.shutdown().map_err(|_| Error::NotConnected) } }