use std::fs::File;
use std::io::Read;
use std::path::{Path, PathBuf};
use std::sync::Arc;
use xdg;
use json::{self, JsonValue, object, array};
use chrono::{self, Timelike};
use xcb;
use xcbu;
use crate::{Display, error};
pub struct Cache {
display: Arc<Display>,
data: JsonValue,
path: PathBuf,
profile: String,
}
pub enum Mode {
Manual,
Desktop(i32),
Window(Option<xcb::Window>),
Luminance(f32),
Time(chrono::DateTime<chrono::Local>),
}
impl Cache {
pub fn open<T: AsRef<Path>>(display: Arc<Display>, path: Option<T>) -> error::Result<Self> {
let path = if let Some(path) = path {
path.as_ref().into()
}
else {
xdg::BaseDirectories::with_prefix("dux").unwrap()
.place_config_file("cache.json").unwrap()
};
let mut data = if path.exists() {
let mut file = File::open(&path)?;
let mut content = String::new();
file.read_to_string(&mut content)?;
json::parse(&content).unwrap_or_else(|_| object!{})
}
else {
object!{}
};
if data["default"].is_null() {
data["default"] = object!{};
}
Ok(Cache {
display, data, path,
profile: "default".into(),
})
}
pub fn save(&mut self) -> error::Result<()> {
let mut file = File::create(&self.path)?;
self.data.write_pretty(&mut file, 2)?;
Ok(())
}
pub fn profile<T: Into<String>>(&mut self, name: T) {
self.profile = name.into();
if self.data[&self.profile].is_null() {
self.data[&self.profile] = object!{};
}
}
pub fn set(&mut self, mode: Mode, value: f32) -> error::Result<()> {
match mode {
Mode::Manual => {
self.data[&self.profile]["manual"] = value.into();
}
Mode::Desktop(id) => {
if self.data[&self.profile]["desktop"].is_null() {
self.data[&self.profile]["desktop"] = object!{};
}
self.data[&self.profile]["desktop"][id.to_string()] = value.into();
}
Mode::Window(active) => {
if let Some(id) = active {
if self.data[&self.profile]["window"].is_null() {
self.data[&self.profile]["window"] = object!{};
}
let name = xcbu::icccm::get_wm_class(&self.display, id).get_reply()?;
self.data[&self.profile]["window"][name.instance()] = value.into();
self.data[&self.profile]["window"][name.class()] = value.into();
}
}
Mode::Luminance(luma) => {
if self.data[&self.profile]["luminance"].is_null() {
self.data[&self.profile]["luminance"] = array!{};
}
if let JsonValue::Array(ref mut array) = self.data[&self.profile]["luminance"] {
let luma = (luma * 20.0).round() as u8;
match array.binary_search_by_key(&luma, |v| v[0].as_u8().unwrap()) {
Ok(index) =>
array[index] = array![luma, value],
Err(index) =>
array.insert(index, array![luma, value])
}
}
}
Mode::Time(time) => {
if self.data[&self.profile]["time"].is_null() {
self.data[&self.profile]["time"] = array!{};
}
if let JsonValue::Array(ref mut array) = self.data[&self.profile]["time"] {
let halves = time.num_seconds_from_midnight() / (30 * 60);
match array.binary_search_by_key(&halves, |v| v[0].as_u32().unwrap()) {
Ok(index) =>
array[index] = array![halves, value],
Err(index) =>
array.insert(index, array![halves, value]),
}
}
}
}
Ok(())
}
pub fn get(&mut self, mode: Mode) -> error::Result<Option<f32>> {
match mode {
Mode::Manual => {
if let Some(value) = self.data[&self.profile]["manual"].as_f32() {
return Ok(Some(value));
}
}
Mode::Desktop(id) => {
if let Some(value) = self.data[&self.profile]["desktop"][id.to_string()].as_f32() {
return Ok(Some(value))
}
}
Mode::Window(active) => {
if let Some(id) = active {
let name = xcbu::icccm::get_wm_class(&self.display, id).get_reply()?;
if let Some(value) = self.data[&self.profile]["window"][name.instance()].as_f32() {
return Ok(Some(value));
}
if let Some(value) = self.data[&self.profile]["window"][name.class()].as_f32() {
return Ok(Some(value));
}
}
}
Mode::Luminance(luma) => {
if let JsonValue::Array(ref slice) = self.data[&self.profile]["luminance"] {
if slice.is_empty() {
return Ok(None);
}
let luma = (luma * 20.0).round() as u8;
let index = match slice.binary_search_by_key(&luma, |v| v[0].as_u8().unwrap()) {
Ok(index) | Err(index) => index
};
let before = slice.get(index.overflowing_sub(1).0).map(|v| (v[0].as_u8().unwrap(), v[1].as_f32().unwrap()));
let after = slice.get(index).map(|v| (v[0].as_u8().unwrap(), v[1].as_f32().unwrap()));
match (before, after) {
(None, None) =>
unreachable!(),
(Some((_, value)), None) | (None, Some((_, value))) => {
return Ok(Some(value));
}
(Some((g1, d1)), Some((g2, d2))) => {
let g = f32::from(luma);
let g1 = f32::from(g1);
let g2 = f32::from(g2);
return Ok(Some(d1 + ((g - g1) / (g2 - g1)) * (d2 - d1)));
}
}
}
}
Mode::Time(time) => {
if let JsonValue::Array(ref slice) = self.data[&self.profile]["time"] {
if slice.is_empty() {
return Ok(None);
}
let halves = time.num_seconds_from_midnight() / (30 * 60);
let index = match slice.binary_search_by_key(&halves, |v| v[0].as_u32().unwrap()) {
Ok(index) | Err(index) => index
};
let before = slice.get(index.overflowing_sub(1).0).map(|v| (v[0].as_u32().unwrap(), v[1].as_f32().unwrap()));
let after = slice.get(index).map(|v| (v[0].as_u32().unwrap(), v[1].as_f32().unwrap()));
match (before, after) {
(None, None) =>
unreachable!(),
(Some((_, value)), None) | (None, Some((_, value))) => {
return Ok(Some(value));
}
(Some((g1, d1)), Some((g2, d2))) => {
let g = halves as f32;
let g1 = g1 as f32;
let g2 = g2 as f32;
return Ok(Some(d1 + ((g - g1) / (g2 - g1)) * (d2 - d1)));
}
}
}
}
}
Ok(None)
}
}
impl Drop for Cache {
fn drop(&mut self) {
self.save().unwrap();
}
}