sqlitegraph 2.2.3

Embedded graph database with full ACID transactions, HNSW vector search, dual backend support, and comprehensive graph algorithms library
Documentation
//! Storage media type detection
//!
//! Detects whether storage is on SSD or HDD to optimize I/O strategies.
//! Uses heuristics based on /sys/block data on Linux and similar mechanisms
//! on other platforms.

use std::path::Path;

/// Type of storage media
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MediaType {
    /// Solid State Drive - optimal for small random I/O
    SSD,
    /// Hard Disk Drive - prefers larger sequential I/O
    HDD,
    /// Unknown media type - use conservative defaults
    Unknown,
}

/// Detects storage media type for optimal I/O configuration
///
/// # Performance Impact
///
/// - SSD: Use 4KB pages (matches SSD block size)
/// - HDD: Use 16KB pages (reduces seek overhead)
/// - Expected 10-20% improvement on appropriate hardware
///
/// # Example
///
/// ```no_run
/// use sqlitegraph::backend::native::v3::storage::MediaDetector;
///
/// let detector = MediaDetector::new();
/// let media_type = detector.detect("/var/lib/data");
/// println!("Detected: {:?}", media_type);
/// ```
pub struct MediaDetector;

impl MediaDetector {
    /// Create a new media detector
    pub fn new() -> Self {
        Self
    }

    /// Detect media type for the given path
    ///
    /// # Arguments
    ///
    /// * `path` - Path to detect media type for
    ///
    /// # Returns
    ///
    /// MediaType indicating SSD, HDD, or Unknown
    ///
    /// # Platform Support
    ///
    /// - Linux: Uses /sys/block rotational flag
    /// - macOS/Windows: Returns Unknown (conservative)
    pub fn detect<P: AsRef<Path>>(&self, path: P) -> MediaType {
        // On Linux, check /sys/block for rotational flag
        #[cfg(target_os = "linux")]
        {
            self.detect_linux(path.as_ref())
        }

        #[cfg(not(target_os = "linux"))]
        {
            MediaType::Unknown // Conservative default
        }
    }

    #[cfg(target_os = "linux")]
    fn detect_linux(&self, path: &Path) -> MediaType {
        // Get the device path
        let device_path = match self.get_device_path(path) {
            Some(dev) => dev,
            None => return MediaType::Unknown,
        };

        // Check /sys/block/<device>/queue/rotational
        let rotational_path = format!("/sys/block/{}/queue/rotational", device_path);

        if let Ok(contents) = std::fs::read_to_string(&rotational_path) {
            // "0" = SSD (non-rotational), "1" = HDD (rotational)
            if contents.trim() == "0" {
                MediaType::SSD
            } else {
                MediaType::HDD
            }
        } else {
            MediaType::Unknown
        }
    }

    #[cfg(target_os = "linux")]
    fn get_device_path(&self, path: &Path) -> Option<String> {
        use std::os::unix::fs::MetadataExt;

        // Get device number
        let metadata = std::fs::metadata(path).ok()?;
        let dev = metadata.dev();

        // Find the block device
        for entry in std::fs::read_dir("/sys/block").ok()? {
            let entry = entry.ok()?;
            let device_name = entry.file_name();
            let device_str = device_name.to_string_lossy();

            // Skip loop devices
            if device_str.starts_with("loop") {
                continue;
            }

            // Check if this device matches our file's device
            let dev_path = format!("/dev/{}", device_str);
            if let Ok(dev_metadata) = std::fs::metadata(&dev_path) {
                if dev_metadata.rdev() == dev {
                    return Some(device_str.to_string());
                }
            }
        }

        None
    }
}

impl Default for MediaDetector {
    fn default() -> Self {
        Self::new()
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_media_detector_creation() {
        let detector = MediaDetector::new();
        let _ = detector.detect("/tmp");
    }

    #[test]
    fn test_media_detector_default() {
        let detector = MediaDetector::default();
        let media_type = detector.detect("/tmp");
        // Will return Unknown or detected type depending on platform
        assert!(matches!(
            media_type,
            MediaType::SSD | MediaType::HDD | MediaType::Unknown
        ));
    }
}