diff --git a/src/cmds/run.rs b/src/cmds/run.rs index bbeb2bd..bd69b6d 100644 --- a/src/cmds/run.rs +++ b/src/cmds/run.rs @@ -42,6 +42,11 @@ use tokio_core::reactor; use tokio_signal::unix::{Signal, SIGINT, SIGTERM}; use web; +// These are used in a hack to get the name of the current time zone (e.g. America/Los_Angeles). +// They seem to be correct for Linux and OS X at least. +const LOCALTIME_PATH: &'static str = "/etc/localtime"; +const ZONEINFO_PATH: &'static str = "/usr/share/zoneinfo/"; + const USAGE: &'static str = r#" Usage: moonfire-nvr run [options] @@ -77,6 +82,16 @@ fn setup_shutdown_future(h: &reactor::Handle) -> Box String { + let p = ::std::fs::read_link(LOCALTIME_PATH).expect("unable to read localtime symlink"); + let p = p.to_str().expect("localtime symlink destination must be valid UTF-8"); + if !p.starts_with(ZONEINFO_PATH) { + panic!("Expected {} to point to a path within {}; actually points to {}", + LOCALTIME_PATH, ZONEINFO_PATH, p); + } + p[ZONEINFO_PATH.len()..].into() +} + pub fn run() -> Result<(), Error> { let args: Args = super::parse_args(USAGE)?; let (_db_dir, conn) = super::open_conn( @@ -86,7 +101,7 @@ pub fn run() -> Result<(), Error> { let dir = dir::SampleFileDir::new(&args.flag_sample_file_dir, db.clone()).unwrap(); info!("Database is loaded."); - let s = web::Service::new(db.clone(), dir.clone(), Some(&args.flag_ui_dir))?; + let s = web::Service::new(db.clone(), dir.clone(), Some(&args.flag_ui_dir), resolve_zone())?; // Start a streamer for each camera. let shutdown_streamers = Arc::new(AtomicBool::new(false)); diff --git a/src/json.rs b/src/json.rs index d6f8cfa..df5c9bb 100644 --- a/src/json.rs +++ b/src/json.rs @@ -34,10 +34,13 @@ use std::collections::BTreeMap; use uuid::Uuid; #[derive(Debug, Serialize)] -pub struct ListCameras<'a> { +#[serde(rename_all="camelCase")] +pub struct TopLevel<'a> { + pub time_zone_name: &'a str, + // Use a custom serializer which presents the map's values as a sequence and includes the // "days" attribute or not, according to the bool in the tuple. - #[serde(serialize_with = "ListCameras::serialize_cameras")] + #[serde(serialize_with = "TopLevel::serialize_cameras")] pub cameras: (&'a BTreeMap, bool), } @@ -104,9 +107,8 @@ struct CameraDayValue { pub total_duration_90k: i64, } -impl<'a> ListCameras<'a> { - /// Serializes cameras as a list (rather than a map), wrapping each camera in the - /// `ListCamerasCamera` type to tweak the data returned. +impl<'a> TopLevel<'a> { + /// Serializes cameras as a list (rather than a map), optionally including the `days` field. fn serialize_cameras(cameras: &(&BTreeMap, bool), serializer: S) -> Result where S: Serializer { diff --git a/src/web.rs b/src/web.rs index 49845af..9b9ec3c 100644 --- a/src/web.rs +++ b/src/web.rs @@ -172,6 +172,7 @@ struct ServiceInner { dir: Arc, ui_files: HashMap, pool: futures_cpupool::CpuPool, + time_zone_name: String, } impl ServiceInner { @@ -197,7 +198,10 @@ impl ServiceInner { let buf = { let db = self.db.lock(); - serde_json::to_vec(&json::ListCameras{cameras: (db.cameras_by_id(), days)})? + serde_json::to_vec(&json::TopLevel { + time_zone_name: &self.time_zone_name, + cameras: (db.cameras_by_id(), days), + })? }; let len = buf.len(); let body: slices::Body = Box::new(stream::once(Ok(ARefs::new(buf)))); @@ -390,7 +394,7 @@ impl ServiceInner { pub struct Service(Arc); impl Service { - pub fn new(db: Arc, dir: Arc, ui_dir: Option<&str>) + pub fn new(db: Arc, dir: Arc, ui_dir: Option<&str>, zone: String) -> Result { let mut ui_files = HashMap::new(); if let Some(d) = ui_dir { @@ -402,6 +406,7 @@ impl Service { dir, ui_files, pool: futures_cpupool::Builder::new().pool_size(1).name_prefix("static").create(), + time_zone_name: zone, }))) }