use std::sync::Arc;
use crate::error::OnvifError;
use crate::soap::{SoapEnvelope, WsSecurityToken};
use crate::transport::{HttpTransport, Transport};
mod device;
mod events;
mod imaging;
mod media;
mod media2;
mod ptz;
mod recording;
pub use events::notification_listener;
#[derive(Clone)]
pub struct OnvifClient {
device_url: String,
credentials: Option<(String, String)>,
utc_offset: i64,
transport: Arc<dyn Transport>,
}
impl OnvifClient {
pub fn new(device_url: impl Into<String>) -> Self {
Self {
device_url: device_url.into(),
credentials: None,
utc_offset: 0,
transport: Arc::new(HttpTransport::new()),
}
}
pub fn with_credentials(
mut self,
username: impl Into<String>,
password: impl Into<String>,
) -> Self {
let u = username.into();
let p = password.into();
self.transport = Arc::new(HttpTransport::new().with_credentials(&u, &p));
self.credentials = Some((u, p));
self
}
pub fn with_utc_offset(mut self, offset_secs: i64) -> Self {
self.utc_offset = offset_secs;
self
}
pub fn with_transport(mut self, transport: Arc<dyn Transport>) -> Self {
self.transport = transport;
self
}
pub fn device_url(&self) -> &str {
&self.device_url
}
fn security_token(&self) -> Option<WsSecurityToken> {
self.credentials
.as_ref()
.map(|(user, pass)| WsSecurityToken::generate(user, pass, self.utc_offset))
}
async fn call(&self, url: &str, action: &str, body: &str) -> Result<String, OnvifError> {
let mut envelope = SoapEnvelope::new(body.to_string()).with_wsa_to(url);
if let Some(token) = self.security_token() {
envelope = envelope.with_security(token);
}
let xml = envelope.build();
tracing::trace!(action, url, body = %xml, "SOAP request");
let resp = self.transport.soap_post(url, action, xml).await?;
tracing::trace!(action, response = %resp, "SOAP response");
Ok(resp)
}
}
#[cfg(test)]
#[path = "../tests/client_tests.rs"]
mod tests;