Skip to main content

aranet_core/
guard.rs

1//! Connection guard for automatic disconnect on drop.
2//!
3//! This module provides RAII-style connection management for Aranet devices,
4//! ensuring that connections are properly closed when the guard goes out of scope.
5
6use std::ops::{Deref, DerefMut};
7use std::sync::Arc;
8
9use tokio::runtime::Handle;
10use tracing::warn;
11
12use crate::device::Device;
13
14/// A guard that automatically disconnects from the device when dropped.
15///
16/// This provides RAII-style management of BLE connections. When the guard
17/// is dropped, it will attempt to disconnect from the device.
18///
19/// # Example
20///
21/// ```ignore
22/// use aranet_core::{Device, DeviceGuard};
23///
24/// async fn read_with_guard() -> Result<(), Box<dyn std::error::Error>> {
25///     let device = Device::connect("Aranet4 12345").await?;
26///     let guard = DeviceGuard::new(device);
27///
28///     // Use the device through the guard
29///     let reading = guard.read_current().await?;
30///     println!("CO2: {}", reading.co2);
31///
32///     // Device is automatically disconnected when guard goes out of scope
33///     Ok(())
34/// }
35/// ```
36pub struct DeviceGuard {
37    device: Option<Device>,
38}
39
40impl DeviceGuard {
41    /// Create a new device guard.
42    pub fn new(device: Device) -> Self {
43        Self {
44            device: Some(device),
45        }
46    }
47
48    /// Take ownership of the device, preventing automatic disconnect.
49    ///
50    /// After calling this, you are responsible for disconnecting the device.
51    /// This consumes the guard, so the device cannot be "already taken" —
52    /// the `Option` is only `None` during `Drop`.
53    pub fn into_inner(mut self) -> Device {
54        // SAFETY: `device` is always `Some` except inside `Drop`.
55        // Since `into_inner` takes `self` by value, Drop hasn't run yet.
56        self.device
57            .take()
58            .expect("DeviceGuard invariant violated: device is None outside of Drop")
59    }
60
61    /// Get a reference to the device.
62    fn device(&self) -> &Device {
63        // SAFETY: Same invariant as above — always `Some` outside of `Drop`.
64        self.device
65            .as_ref()
66            .expect("DeviceGuard invariant violated: device is None outside of Drop")
67    }
68
69    /// Get a mutable reference to the device.
70    fn device_mut(&mut self) -> &mut Device {
71        self.device
72            .as_mut()
73            .expect("DeviceGuard invariant violated: device is None outside of Drop")
74    }
75}
76
77impl Deref for DeviceGuard {
78    type Target = Device;
79
80    fn deref(&self) -> &Self::Target {
81        self.device()
82    }
83}
84
85impl DerefMut for DeviceGuard {
86    fn deref_mut(&mut self) -> &mut Self::Target {
87        self.device_mut()
88    }
89}
90
91impl Drop for DeviceGuard {
92    fn drop(&mut self) {
93        if let Some(device) = self.device.take() {
94            // Try to get a runtime handle to perform async disconnect
95            if let Ok(handle) = Handle::try_current() {
96                handle.spawn(async move {
97                    match tokio::time::timeout(
98                        std::time::Duration::from_secs(5),
99                        device.disconnect(),
100                    )
101                    .await
102                    {
103                        Ok(Ok(())) => {}
104                        Ok(Err(e)) => {
105                            warn!("Failed to disconnect device in guard drop: {}", e);
106                        }
107                        Err(_) => {
108                            warn!("Timeout disconnecting device in guard drop");
109                        }
110                    }
111                });
112            } else {
113                // No runtime available, log warning
114                warn!("No tokio runtime available for device disconnect in guard drop");
115            }
116        }
117    }
118}
119
120/// A guard for Arc-wrapped devices.
121///
122/// Similar to `DeviceGuard` but for shared device references.
123pub struct SharedDeviceGuard {
124    device: Arc<Device>,
125}
126
127impl SharedDeviceGuard {
128    /// Create a new shared device guard.
129    pub fn new(device: Arc<Device>) -> Self {
130        Self { device }
131    }
132
133    /// Consume the guard and return the underlying Arc.
134    ///
135    /// After calling this, the device will NOT be automatically disconnected
136    /// when the returned Arc is dropped. You are responsible for managing
137    /// the device lifecycle.
138    pub fn into_inner(self) -> Arc<Device> {
139        // Use ManuallyDrop to prevent Drop from running
140        let guard = std::mem::ManuallyDrop::new(self);
141        Arc::clone(&guard.device)
142    }
143}
144
145impl Deref for SharedDeviceGuard {
146    type Target = Device;
147
148    fn deref(&self) -> &Self::Target {
149        &self.device
150    }
151}
152
153impl Drop for SharedDeviceGuard {
154    fn drop(&mut self) {
155        let device = Arc::clone(&self.device);
156        if let Ok(handle) = Handle::try_current() {
157            handle.spawn(async move {
158                if let Err(e) = device.disconnect().await {
159                    warn!("Failed to disconnect shared device in guard drop: {}", e);
160                }
161            });
162        }
163    }
164}