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    pub fn into_inner(mut self) -> Device {
52        self.device.take().expect("device already taken")
53    }
54
55    /// Get a reference to the device.
56    pub fn device(&self) -> &Device {
57        self.device.as_ref().expect("device already taken")
58    }
59
60    /// Get a mutable reference to the device.
61    pub fn device_mut(&mut self) -> &mut Device {
62        self.device.as_mut().expect("device already taken")
63    }
64}
65
66impl Deref for DeviceGuard {
67    type Target = Device;
68
69    fn deref(&self) -> &Self::Target {
70        self.device()
71    }
72}
73
74impl DerefMut for DeviceGuard {
75    fn deref_mut(&mut self) -> &mut Self::Target {
76        self.device_mut()
77    }
78}
79
80impl Drop for DeviceGuard {
81    fn drop(&mut self) {
82        if let Some(device) = self.device.take() {
83            // Try to get a runtime handle to perform async disconnect
84            if let Ok(handle) = Handle::try_current() {
85                handle.spawn(async move {
86                    if let Err(e) = device.disconnect().await {
87                        warn!("Failed to disconnect device in guard drop: {}", e);
88                    }
89                });
90            } else {
91                // No runtime available, log warning
92                warn!("No tokio runtime available for device disconnect in guard drop");
93            }
94        }
95    }
96}
97
98/// A guard for Arc-wrapped devices.
99///
100/// Similar to `DeviceGuard` but for shared device references.
101pub struct SharedDeviceGuard {
102    device: Arc<Device>,
103}
104
105impl SharedDeviceGuard {
106    /// Create a new shared device guard.
107    pub fn new(device: Arc<Device>) -> Self {
108        Self { device }
109    }
110
111    /// Consume the guard and return the underlying Arc.
112    ///
113    /// After calling this, the device will NOT be automatically disconnected
114    /// when the returned Arc is dropped. You are responsible for managing
115    /// the device lifecycle.
116    pub fn into_inner(self) -> Arc<Device> {
117        // Use ManuallyDrop to prevent Drop from running
118        let guard = std::mem::ManuallyDrop::new(self);
119        Arc::clone(&guard.device)
120    }
121}
122
123impl Deref for SharedDeviceGuard {
124    type Target = Device;
125
126    fn deref(&self) -> &Self::Target {
127        &self.device
128    }
129}
130
131impl Drop for SharedDeviceGuard {
132    fn drop(&mut self) {
133        let device = Arc::clone(&self.device);
134        if let Ok(handle) = Handle::try_current() {
135            handle.spawn(async move {
136                if let Err(e) = device.disconnect().await {
137                    warn!("Failed to disconnect shared device in guard drop: {}", e);
138                }
139            });
140        }
141    }
142}