use std::{collections::HashMap, sync::Arc, time::Duration};
use crate::{
sink_channel_filter::{SinkChannelFilter, SinkChannelFilterFn},
ChannelDescriptor, Context, FoxgloveError,
};
use tokio::task::JoinHandle;
use super::connection::{RemoteAccessConnection, RemoteAccessConnectionOptions};
use super::{Capability, Listener};
#[doc(hidden)]
pub struct GatewayHandle {
connection: Arc<RemoteAccessConnection>,
runner: JoinHandle<()>,
}
impl GatewayHandle {
fn new(connection: Arc<RemoteAccessConnection>) -> Self {
let runner = connection.clone().spawn_run_until_cancelled();
Self { connection, runner }
}
pub fn stop(self) -> JoinHandle<()> {
self.connection.shutdown();
self.runner
}
}
const FOXGLOVE_DEVICE_TOKEN_ENV: &str = "FOXGLOVE_DEVICE_TOKEN";
const FOXGLOVE_API_URL_ENV: &str = "FOXGLOVE_API_URL";
const FOXGLOVE_API_TIMEOUT_ENV: &str = "FOXGLOVE_API_TIMEOUT";
#[must_use]
#[doc(hidden)]
#[derive(Default)]
pub struct Gateway {
options: RemoteAccessConnectionOptions,
device_token: Option<String>,
foxglove_api_url: Option<String>,
foxglove_api_timeout: Option<Duration>,
}
impl std::fmt::Debug for Gateway {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Gateway")
.field("options", &self.options)
.finish()
}
}
impl Gateway {
pub fn new() -> Self {
Self::default()
}
pub fn name(mut self, name: impl Into<String>) -> Self {
self.options.name = Some(name.into());
self
}
pub fn listener(mut self, listener: Arc<dyn Listener>) -> Self {
self.options.listener = Some(listener);
self
}
pub fn capabilities(mut self, capabilities: impl IntoIterator<Item = Capability>) -> Self {
self.options.capabilities = capabilities.into_iter().collect();
self
}
pub fn supported_encodings(
mut self,
encodings: impl IntoIterator<Item = impl Into<String>>,
) -> Self {
self.options.supported_encodings = Some(encodings.into_iter().map(|e| e.into()).collect());
self
}
pub fn session_id(mut self, id: impl Into<String>) -> Self {
self.options.session_id = id.into();
self
}
#[doc(hidden)]
pub fn server_info(mut self, info: HashMap<String, String>) -> Self {
self.options.server_info = Some(info);
self
}
pub fn context(mut self, ctx: &Arc<Context>) -> Self {
self.options.context = Arc::downgrade(ctx);
self
}
#[doc(hidden)]
pub fn tokio_runtime(mut self, handle: &tokio::runtime::Handle) -> Self {
self.options.runtime = Some(handle.clone());
self
}
pub fn channel_filter(mut self, filter: Arc<dyn SinkChannelFilter>) -> Self {
self.options.channel_filter = Some(filter);
self
}
pub fn device_token(mut self, token: impl Into<String>) -> Self {
self.device_token = Some(token.into());
self
}
pub fn foxglove_api_url(mut self, url: impl Into<String>) -> Self {
self.foxglove_api_url = Some(url.into());
self
}
pub fn foxglove_api_timeout(mut self, timeout: Duration) -> Self {
self.foxglove_api_timeout = Some(timeout);
self
}
pub fn message_backlog_size(mut self, size: usize) -> Self {
self.options.message_backlog_size = Some(size);
self
}
pub fn channel_filter_fn(
mut self,
filter: impl Fn(&ChannelDescriptor) -> bool + Sync + Send + 'static,
) -> Self {
self.options.channel_filter = Some(Arc::new(SinkChannelFilterFn(filter)));
self
}
pub fn start(mut self) -> Result<GatewayHandle, FoxgloveError> {
self.options.device_token = self
.device_token
.or_else(|| std::env::var(FOXGLOVE_DEVICE_TOKEN_ENV).ok())
.ok_or_else(|| {
FoxgloveError::ConfigurationError(format!(
"No device token provided. Set the {FOXGLOVE_DEVICE_TOKEN_ENV} environment variable or call .device_token() on the builder."
))
})?;
self.options.foxglove_api_url = self
.foxglove_api_url
.or_else(|| std::env::var(FOXGLOVE_API_URL_ENV).ok());
self.options.foxglove_api_timeout = self.foxglove_api_timeout.or_else(|| {
std::env::var(FOXGLOVE_API_TIMEOUT_ENV)
.ok()
.and_then(|s| s.parse::<u64>().ok())
.map(Duration::from_secs)
});
let connection = RemoteAccessConnection::new(self.options);
Ok(GatewayHandle::new(Arc::new(connection)))
}
}