1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
use std::path::PathBuf;
use std::fs::{OpenOptions, File};
use std::io::Read;

use std::fmt;

/// Actual battery state.
// TODO: Merge Status and OsStatus?
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Status {
    Missing,
    Discharging,
    Charging,
    Full,
}

impl fmt::Display for Status {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "{:?}", self)
    }
}

/// Battery state produced by `acpid`.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum OsStatus {
    Missing,
    Discharging,
    Charging,
    Full,
    Unknown,
}

impl OsStatus {
    pub fn from_str(status: &str) -> Self {
        match status.trim() {
            "Full" => OsStatus::Full,
            "Charging" => OsStatus::Charging,
            "Discharging" => OsStatus::Discharging,
            "Missing" => OsStatus::Missing,
            _ => OsStatus::Unknown,
        }
    }
}

impl fmt::Display for OsStatus {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "{:?}", self)
    }
}

/// The struct providing an interface to the system's battery.
// TODO: Implement retrieving the amount of time left
pub struct Battery {
    index: u32,
    base_path: PathBuf,
}

impl Battery {
    pub fn new() -> Self {
        Self::with_index(0)
    }

    pub fn with_index(index: u32) -> Self {
        let battery_path = format!("/sys/class/power_supply/BAT{}", index);
        Battery {
            index,
            base_path: PathBuf::from(battery_path),
        }
    }

    pub fn is_charging(&self) -> bool {
        self.status() == Status::Charging
    }

    pub fn is_full(&self) -> bool {
        self.status() == Status::Full
    }

    pub fn status(&self) -> Status {
        match self.os_status() {
            OsStatus::Full => Status::Full,
            OsStatus::Discharging => Status::Discharging,
            OsStatus::Charging => Status::Charging,
            OsStatus::Missing => Status::Missing,
            OsStatus::Unknown => {
                match self.capacity() {
                    Ok(x) if x >= 99 => Status::Full,
                    Ok(x) if x <= 50 => Status::Discharging,
                    _ => Status::Charging,
                }
            }
        }
    }

    fn os_status(&self) -> OsStatus {
        OsStatus::from_str(&self.read_file("status").unwrap_or("Missing".to_string()))
    }

    // HACK: Battery::is_present must be rewritten
    pub fn is_present(&self) -> bool {
        self.base_path.as_path().exists()
    }

    // REVIEW: Return Result instead of u8?
    pub fn capacity(&self) -> Result<u8, ()> {
        self.read_file("capacity").and_then(|x| x.trim().parse::<u8>().map_err(|_| ()))
    }

    fn read_file(&self, name: &str) -> Result<String, ()> {
        let mut buf: String = String::new();
        let file = OpenOptions::new()
            .read(true)
            .open(self.base_path.join(name));

        // HACK: Such usage of `map_err` is inappropriate for production code
        file.map_err(|_| ())?.read_to_string(&mut buf).map_err(|_| ())?;
        Ok(buf)
    }
}