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}