pub mod consts;
use std::borrow::Cow;
use std::error::Error;
use std::future::Future;
use std::net::SocketAddr;
use std::path::Path;
use std::pin::Pin;
use derive_builder::Builder;
use magic_wormhole::rendezvous::DEFAULT_RENDEZVOUS_SERVER;
use magic_wormhole::transfer::{self, AppVersion, ReceiveRequest, TransferError};
pub use magic_wormhole::transit::TransitInfo;
use magic_wormhole::transit::{self, RelayHint, RelayHintParseError, DEFAULT_RELAY_SERVER};
use magic_wormhole::{AppConfig, AppID, Code, Wormhole, WormholeError};
use serde::Serialize;
use smol::fs::File;
use thiserror::Error;
use url::ParseError;
type Handshake = dyn Future<Output = Result<Wormhole, WormholeError>> + Unpin + Send + Sync;
pub type Abilities = transit::Abilities;
#[non_exhaustive]
#[derive(Debug, Error)]
pub enum PylonError {
#[error("Error generating wormhole code: {0}")]
CodegenError(Box<str>),
#[error("Error parsing relay server URL")]
RelayHintParseError(
#[from]
#[source]
RelayHintParseError,
),
#[error(transparent)]
UrlParseError(#[from] ParseError),
#[error("Error occured during transfer")]
TransferError(
#[from]
#[source]
TransferError,
),
#[error(transparent)]
InternalError(#[from] WormholeError),
#[error(transparent)]
BuilderError(#[from] PylonBuilderError),
#[error("An error occured: {0}")]
Error(
#[from]
#[source]
Box<dyn Error + Send + Sync>,
),
}
impl Serialize for PylonError {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_str(self.to_string().as_ref())
}
}
#[derive(Serialize, Builder)]
#[serde(rename_all = "camelCase")]
pub struct Pylon {
id: String,
#[builder(default = "DEFAULT_RELAY_SERVER.into()")]
relay_url: String,
#[builder(default = "DEFAULT_RENDEZVOUS_SERVER.into()")]
rendezvous_url: String,
#[builder(default = "Abilities::ALL_ABILITIES")]
abilities: Abilities,
#[serde(skip)]
#[builder(setter(skip))]
handshake: Option<Box<Handshake>>,
#[serde(skip)]
#[builder(setter(skip))]
transfer_request: Option<ReceiveRequest>,
}
impl Pylon {
fn config(&self) -> AppConfig<AppVersion> {
AppConfig {
id: AppID(Cow::from(self.id.clone())),
rendezvous_url: Cow::from(self.rendezvous_url.clone()),
app_version: AppVersion {},
}
}
pub async fn gen_code(&mut self, code_length: usize) -> Result<String, PylonError> {
if let Some(_) = &self.handshake {
return Err(PylonError::CodegenError(
"The current Pylon already has a pending handshake".into(),
));
}
let (welcome, handshake) =
Wormhole::connect_without_code(self.config(), code_length).await?;
self.handshake = Some(Box::new(Box::pin(handshake)));
Ok(welcome.code.0)
}
pub async fn start_transfer<F, P, T, C>(
&mut self,
file: F,
mut progress_handler: Option<P>,
mut transit_handler: Option<T>,
mut cancel_handler: Option<C>,
) -> Result<(), PylonError>
where
F: AsRef<Path>,
P: FnMut(u64, u64) + 'static,
T: FnMut(TransitInfo, SocketAddr) + 'static,
C: Future<Output = ()>,
{
let file_name = file
.as_ref()
.file_name()
.ok_or(PylonError::Error("could not extract file name".into()))?
.to_str()
.ok_or(PylonError::Error(
"could not convert file name to str".into(),
))?;
let mut file = File::open(&file)
.await
.map_err(|e| PylonError::Error(e.into()))?;
let file_size = file
.metadata()
.await
.map_err(|e| PylonError::Error(e.into()))?
.len();
let transit_abilities = self.abilities;
let relay_hints = vec![RelayHint::from_urls(None, [self.relay_url.parse()?])?];
let transit_handler: Box<dyn FnMut(TransitInfo, SocketAddr)> = match transit_handler.take()
{
Some(t) => Box::new(t),
None => Box::new(|_: TransitInfo, _: SocketAddr| {}),
};
let progress_handler: Box<dyn FnMut(u64, u64)> = match progress_handler.take() {
Some(p) => Box::new(p),
None => Box::new(|_: u64, _: u64| {}),
};
let cancel_handler: Pin<Box<dyn Future<Output = ()>>> = match cancel_handler.take() {
Some(c) => Box::pin(c),
None => Box::pin(async { loop {} }),
};
let sender = match self.handshake.take() {
None => {
return Err(PylonError::Error(
"There is currently no active handshake".into(),
))
}
Some(h) => {
let wh = h.await?;
transfer::send_file(
wh,
relay_hints,
&mut file,
file_name,
file_size,
transit_abilities,
transit_handler,
progress_handler,
cancel_handler,
)
}
};
sender.await?;
Ok(())
}
pub async fn request_transfer<C: Future<Output = ()>>(
&mut self,
code: String,
mut cancel_handler: Option<C>,
) -> Result<(), PylonError> {
let transit_abilities = self.abilities;
let relay_hints = vec![RelayHint::from_urls(None, [self.relay_url.parse()?])?];
let cancel_handler: Pin<Box<dyn Future<Output = ()>>> = match cancel_handler.take() {
Some(c) => Box::pin(c),
None => Box::pin(async { loop {} }),
};
let (_, wh) = Wormhole::connect_with_code(self.config(), Code(code)).await?;
let request =
transfer::request_file(wh, relay_hints, transit_abilities, cancel_handler).await?;
self.transfer_request = request;
Ok(())
}
pub async fn reject_transfer(&mut self) -> Result<(), PylonError> {
if let Some(r) = self.transfer_request.take() {
r.reject().await?;
}
return Err(PylonError::Error(
"There is currently no active transfer request".into(),
));
}
pub async fn accept_transfer<F, P, T, C>(
&mut self,
file: F,
mut progress_handler: Option<P>,
mut transit_handler: Option<T>,
mut cancel_handler: Option<C>,
) -> Result<(), PylonError>
where
F: AsRef<Path>,
P: FnMut(u64, u64) + 'static,
T: FnMut(TransitInfo, SocketAddr) + 'static,
C: Future<Output = ()>,
{
let transit_handler: Box<dyn FnMut(TransitInfo, SocketAddr)> = match transit_handler.take()
{
Some(t) => Box::new(t),
None => Box::new(|_: TransitInfo, _: SocketAddr| {}),
};
let progress_handler: Box<dyn FnMut(u64, u64)> = match progress_handler.take() {
Some(p) => Box::new(p),
None => Box::new(|_: u64, _: u64| {}),
};
let cancel_handler: Pin<Box<dyn Future<Output = ()>>> = match cancel_handler.take() {
Some(c) => Box::pin(c),
None => Box::pin(async { loop {} }),
};
let mut file = File::create(&file)
.await
.map_err(|e| PylonError::Error(e.into()))?;
if let Some(r) = self.transfer_request.take() {
r.accept(transit_handler, progress_handler, &mut file, cancel_handler)
.await?;
}
return Err(PylonError::Error(
"There is currently no active transfer request".into(),
));
}
pub fn destroy(self) {
drop(self);
}
}