diff --git a/Cargo.lock b/Cargo.lock index cb2ba83..6ce22b8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -18,7 +18,7 @@ dependencies = [ "memmap 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "mime 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", "openssl 0.8.3 (registry+https://github.com/rust-lang/crates.io-index)", - "regex 0.1.80 (registry+https://github.com/rust-lang/crates.io-index)", + "regex 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "rusqlite 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)", "rustc-serialize 0.3.21 (registry+https://github.com/rust-lang/crates.io-index)", "serde 0.8.19 (registry+https://github.com/rust-lang/crates.io-index)", @@ -43,6 +43,14 @@ dependencies = [ "memchr 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "aho-corasick" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "memchr 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "bit-set" version = "0.2.0" @@ -340,6 +348,14 @@ dependencies = [ "libc 0.2.18 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "memchr" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.18 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "memmap" version = "0.3.0" @@ -499,11 +515,28 @@ dependencies = [ "utf8-ranges 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "regex" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "aho-corasick 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)", + "memchr 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", + "regex-syntax 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "thread_local 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", + "utf8-ranges 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "regex-syntax" version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "regex-syntax" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "rusqlite" version = "0.7.3" @@ -728,6 +761,15 @@ dependencies = [ "libc 0.2.18 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "thread-id" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.18 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "thread_local" version = "0.2.7" @@ -736,6 +778,15 @@ dependencies = [ "thread-id 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "thread_local" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "thread-id 3.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "unreachable 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "time" version = "0.1.35" @@ -782,6 +833,14 @@ name = "unicode-xid" version = "0.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "unreachable" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "url" version = "1.2.3" @@ -805,6 +864,11 @@ name = "utf8-ranges" version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "utf8-ranges" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "uuid" version = "0.3.1" @@ -814,6 +878,11 @@ dependencies = [ "serde 0.8.19 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "void" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "winapi" version = "0.2.8" @@ -826,6 +895,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [metadata] "checksum aho-corasick 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ca972c2ea5f742bfce5687b9aef75506a764f61d37f8f649047846a9686ddb66" +"checksum aho-corasick 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4f660b942762979b56c9f07b4b36bb559776fbad102f05d6771e1b629e8fd5bf" "checksum bit-set 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e6e1e6fb1c9e3d6fcdec57216a74eaa03e41f52a22f13a16438251d8e88b89da" "checksum bit-vec 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "5b97c2c8e8bbb4251754f559df8af22fb264853c7d009084a576cdf12565089d" "checksum bitflags 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "32866f4d103c4e438b1db1158aa1b1a80ee078e5d77a59a2f906fd62a577389c" @@ -865,6 +935,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum lru-cache 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "656fa4dfcb02bcf1063c592ba3ff6a5303ee1f2afe98c8a889e8b1a77c6dfdb7" "checksum matches 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "efd7622e3022e1a6eaa602c4cea8912254e5582c9c692e9167714182244801b1" "checksum memchr 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)" = "d8b629fb514376c675b98c1421e80b151d3817ac42d7c667717d282761418d20" +"checksum memchr 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "1dbccc0e46f1ea47b9f17e6d67c5a96bd27030519c519c9c91327e31275a47b4" "checksum memmap 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "9b3c19d7eabbbf4a7b3aa4a60b30216be2a47ee226f74c6b9358196977bb2ed3" "checksum mime 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "b5c93a4bd787ddc6e7833c519b73a50883deb5863d76d9b71eb8216fb7f94e66" "checksum num 0.1.36 (registry+https://github.com/rust-lang/crates.io-index)" = "bde7c03b09e7c6a301ee81f6ddf66d7a28ec305699e3d3b056d2fc56470e3120" @@ -883,7 +954,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum quote 0.3.10 (registry+https://github.com/rust-lang/crates.io-index)" = "6732e32663c9c271bfc7c1823486b471f18c47a2dbf87c066897b7b51afc83be" "checksum rand 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)" = "022e0636ec2519ddae48154b028864bdce4eaf7d35226ab8e65c611be97b189d" "checksum regex 0.1.80 (registry+https://github.com/rust-lang/crates.io-index)" = "4fd4ace6a8cf7860714a2c2280d6c1f7e6a413486c13298bbc86fd3da019402f" +"checksum regex 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4278c17d0f6d62dfef0ab00028feb45bd7d2102843f80763474eeb1be8a10c01" "checksum regex-syntax 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "f9ec002c35e86791825ed294b50008eea9ddfc8def4420124fbc6b08db834957" +"checksum regex-syntax 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2f9191b1f57603095f105d317e375d19b1c9c5c3185ea9633a99a6dcbed04457" "checksum rusqlite 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)" = "e9b3854687228334d8a579cd2f666ddd7fb46a5f68ac0460da2898394c4679d2" "checksum rustc-serialize 0.3.21 (registry+https://github.com/rust-lang/crates.io-index)" = "bff9fc1c79f2dec76b253273d07682e94a978bd8f132ded071188122b2af9818" "checksum rustc_version 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "c5f5376ea5e30ce23c03eb77cbe4962b988deead10910c372b226388b594c084" @@ -909,7 +982,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum tempdir 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "87974a6f5c1dfb344d733055601650059a3363de2a6104819293baff662132d6" "checksum term 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "3deff8a2b3b6607d6d7cc32ac25c0b33709453ca9cceac006caac51e963cf94a" "checksum thread-id 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a9539db560102d1cef46b8b78ce737ff0bb64e7e18d35b2a5688f7d097d0ff03" +"checksum thread-id 3.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4437c97558c70d129e40629a5b385b3fb1ffac301e63941335e4d354081ec14a" "checksum thread_local 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)" = "8576dbbfcaef9641452d5cf0df9b0e7eeab7694956dd33bb61515fb8f18cfdd5" +"checksum thread_local 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7793b722f0f77ce716e7f1acf416359ca32ff24d04ffbac4269f44a4a83be05d" "checksum time 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)" = "3c7ec6d62a20df54e07ab3b78b9a3932972f4b7981de295563686849eb3989af" "checksum traitobject 0.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "07eaeb7689bb7fca7ce15628319635758eda769fed481ecfe6686ddef2600616" "checksum typeable 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "1410f6f91f21d1612654e7cc69193b0334f909dcf2c790c4826254fbb86f8887" @@ -917,9 +992,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum unicode-bidi 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "c1f7ceb96afdfeedee42bade65a0d585a6a0106f681b6749c8ff4daa8df30b3f" "checksum unicode-normalization 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "26643a2f83bac55f1976fb716c10234485f9202dcd65cfbdf9da49867b271172" "checksum unicode-xid 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "36dff09cafb4ec7c8cf0023eb0b686cb6ce65499116a12201c9e11840ca01beb" +"checksum unreachable 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "1f2ae5ddb18e1c92664717616dd9549dde73f539f01bd7b77c2edb2446bdff91" "checksum url 1.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "48ccf7bd87a81b769cf84ad556e034541fb90e1cd6d4bc375c822ed9500cd9d7" "checksum user32-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4ef4711d107b21b410a3a974b1204d9accc8b10dad75d8324b5d755de1617d47" "checksum utf8-ranges 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a1ca13c08c41c9c3e04224ed9ff80461d97e121589ff27c753a16cb10830ae0f" +"checksum utf8-ranges 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "662fab6525a98beff2921d7f61a39e7d59e0b425ebc7d0d9e66d316e55124122" "checksum uuid 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "1a9ff57156caf7e22f37baf3c9d8f6ce8194842c23419dafcb0716024514d162" +"checksum void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" "checksum winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" "checksum winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" diff --git a/Cargo.toml b/Cargo.toml index 61a1bc9..ff4aa55 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,7 +22,7 @@ lru-cache = "0.1" memmap = "0.3" mime = "0.2" openssl = "0.8" -regex = "0.1" +regex = "0.2" rusqlite = "0.7" rustc-serialize = "0.3" serde = "0.8" diff --git a/src/main.rs b/src/main.rs index c5dbcc4..af5f12a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -93,6 +93,7 @@ const USAGE: &'static str = " Usage: moonfire-nvr [options] moonfire-nvr --upgrade [options] moonfire-nvr --check [options] + moonfire-nvr --ts ... moonfire-nvr (--help | --version) Options: @@ -127,8 +128,10 @@ struct Args { flag_read_only: bool, flag_check: bool, flag_upgrade: bool, + flag_ts: bool, flag_no_vacuum: bool, flag_preset_journal: String, + arg_ts: Vec, } fn main() { @@ -171,11 +174,21 @@ fn main() { upgrade::run(conn, &args.flag_preset_journal, args.flag_no_vacuum).unwrap(); } else if args.flag_check { check::run(conn, &args.flag_sample_file_dir).unwrap(); + } else if args.flag_ts { + run_ts(args.arg_ts).unwrap(); } else { run(args, conn, &signal); } } +fn run_ts(timestamps: Vec) -> Result<(), error::Error> { + for timestamp in ×tamps { + let t = recording::Time::parse(timestamp)?; + println!("{} == {}", t, t.0); + } + Ok(()) +} + fn run(args: Args, conn: rusqlite::Connection, signal: &chan::Receiver) { let db = Arc::new(db::Database::new(conn).unwrap()); let dir = dir::SampleFileDir::new(&args.flag_sample_file_dir, db.clone()).unwrap(); diff --git a/src/recording.rs b/src/recording.rs index ed2e3e3..45b5b89 100644 --- a/src/recording.rs +++ b/src/recording.rs @@ -31,9 +31,11 @@ extern crate uuid; use coding::{append_varint32, decode_varint32, unzigzag32, zigzag32}; +use core::str::FromStr; use db; -use std::ops; use error::Error; +use regex::Regex; +use std::ops; use std::fmt; use std::ops::Range; use std::string::String; @@ -53,6 +55,74 @@ impl Time { Time(tm.sec * TIME_UNITS_PER_SEC + tm.nsec as i64 * TIME_UNITS_PER_SEC / 1_000_000_000) } + /// Parses a time as either 90,000ths of a second since epoch or a RFC 3339-like string. + /// + /// The former is 90,000ths of a second since 1970-01-01T00:00:00 UTC, excluding leap seconds. + /// + /// The latter is a string such as `2006-01-02T15:04:05`, followed by an optional 90,000ths of + /// a second such as `:00001`, followed by an optional time zone offset such as `Z` or + /// `-07:00`. A missing fraction is assumed to be 0. A missing time zone offset implies the + /// local time zone. + pub fn parse(s: &str) -> Result { + lazy_static! { + static ref RE: Regex = Regex::new(r#"(?x) + ^ + ([0-9]{4})-([0-9]{2})-([0-9]{2})T([0-9]{2}):([0-9]{2}):([0-9]{2}) + (?::([0-9]{5}))? + (Z|[+-]([0-9]{2}):([0-9]{2}))? + $"#).unwrap(); + } + + // First try parsing as 90,000ths of a second since epoch. + match i64::from_str(s) { + Ok(i) => return Ok(Time(i)), + Err(_) => {}, + } + + // If that failed, parse as a time string or bust. + let c = RE.captures(s).ok_or_else(|| Error::new(format!("unparseable time {:?}", s)))?; + let mut tm = time::Tm{ + tm_sec: i32::from_str(c.get(6).unwrap().as_str()).unwrap(), + tm_min: i32::from_str(c.get(5).unwrap().as_str()).unwrap(), + tm_hour: i32::from_str(c.get(4).unwrap().as_str()).unwrap(), + tm_mday: i32::from_str(c.get(3).unwrap().as_str()).unwrap(), + tm_mon: i32::from_str(c.get(2).unwrap().as_str()).unwrap(), + tm_year: i32::from_str(c.get(1).unwrap().as_str()).unwrap(), + tm_wday: 0, + tm_yday: 0, + tm_isdst: -1, + tm_utcoff: 0, + tm_nsec: 0, + }; + if tm.tm_mon == 0 { + return Err(Error::new(format!("time {:?} has month 0", s))); + } + tm.tm_mon -= 1; + if tm.tm_year < 1900 { + return Err(Error::new(format!("time {:?} has year before 1900", s))); + } + tm.tm_year -= 1900; + + // The time crate doesn't use tm_utcoff properly; it just calls timegm() if tm_utcoff == 0, + // mktime() otherwise. If a zone is specified, use the timegm path and a manual offset. + // If no zone is specified, use the tm_utcoff path. This is pretty lame, but follow the + // chrono crate's lead and just use 0 or 1 to choose between these functions. + let sec = if let Some(zone) = c.get(8) { + tm.to_timespec().sec + if zone.as_str() == "Z" { + 0 + } else { + let off = i64::from_str(c.get(9).unwrap().as_str()).unwrap() * 3600 + + i64::from_str(c.get(10).unwrap().as_str()).unwrap() * 60; + if zone.as_str().as_bytes()[0] == b'-' { off } else { -off } + } + } else { + tm.tm_utcoff = 1; + tm.to_timespec().sec + }; + let fraction = if let Some(f) = c.get(7) { i64::from_str(f.as_str()).unwrap() } else { 0 }; + Ok(Time(sec * TIME_UNITS_PER_SEC + fraction)) + } + pub fn unix_seconds(&self) -> i64 { self.0 / TIME_UNITS_PER_SEC } } @@ -78,8 +148,10 @@ impl ops::Sub for Time { impl fmt::Display for Time { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let tm = time::at(time::Timespec{sec: self.0 / TIME_UNITS_PER_SEC, nsec: 0}); - write!(f, "{}:{:05}", tm.strftime("%FT%T%Z").or_else(|_| Err(fmt::Error))?, - self.0 % TIME_UNITS_PER_SEC) + let zone_minutes = tm.tm_utcoff.abs() / 60; + write!(f, "{}:{:05}{}{:02}:{:02}", tm.strftime("%FT%T").or_else(|_| Err(fmt::Error))?, + self.0 % TIME_UNITS_PER_SEC, + if tm.tm_utcoff > 0 { '+' } else { '-' }, zone_minutes / 60, zone_minutes % 60) } } @@ -422,6 +494,28 @@ mod tests { use super::*; use testutil::TestDb; + #[test] + fn test_parse_time() { + let tests = &[ + ("2006-01-02T15:04:05-07:00", 102261550050000), + ("2006-01-02T15:04:05:00001-07:00", 102261550050001), + ("2006-01-02T15:04:05-08:00", 102261874050000), + ("2006-01-02T15:04:05", 102261874050000), // implied -08:00 + ("2006-01-02T15:04:05:00001", 102261874050001), // implied -08:00 + ("2006-01-02T15:04:05-00:00", 102259282050000), + ("2006-01-02T15:04:05Z", 102259282050000), + ("102261550050000", 102261550050000), + ]; + for test in tests { + assert_eq!(test.1, Time::parse(test.0).unwrap().0, "parsing {}", test.0); + } + } + + #[test] + fn test_format_time() { + assert_eq!("2006-01-02T15:04:05:00000-08:00", format!("{}", Time(102261874050000))); + } + #[test] fn test_display_duration() { let tests = &[ diff --git a/src/web.rs b/src/web.rs index 8326704..a3a40f0 100644 --- a/src/web.rs +++ b/src/web.rs @@ -198,21 +198,21 @@ struct Segments { impl Segments { pub fn parse(input: &str) -> Result { let caps = SEGMENTS_RE.captures(input).ok_or(())?; - let ids_start = i32::from_str(caps.at(1).unwrap()).map_err(|_| ())?; - let ids_end = match caps.at(2) { - Some(e) => i32::from_str(&e[1..]).map_err(|_| ())?, + let ids_start = i32::from_str(caps.get(1).unwrap().as_str()).map_err(|_| ())?; + let ids_end = match caps.get(2) { + Some(e) => i32::from_str(&e.as_str()[1..]).map_err(|_| ())?, None => ids_start, } + 1; if ids_start < 0 || ids_end <= ids_start { return Err(()); } - let start_time = caps.at(3).map_or(Ok(0), i64::from_str).map_err(|_| ())?; + let start_time = caps.get(3).map_or(Ok(0), |m| i64::from_str(m.as_str())).map_err(|_| ())?; if start_time < 0 { return Err(()); } - let end_time = match caps.at(4) { + let end_time = match caps.get(4) { Some(v) => { - let e = i64::from_str(v).map_err(|_| ())?; + let e = i64::from_str(v.as_str()).map_err(|_| ())?; if e <= start_time { return Err(()); } @@ -309,19 +309,18 @@ impl Handler { fn camera_html(&self, db: MutexGuard, query: &str, uuid: Uuid) -> Result, Error> { let (r, trim) = { - let mut start = i64::min_value(); - let mut end = i64::max_value(); + let mut time = recording::Time(i64::min_value()) .. recording::Time(i64::max_value()); let mut trim = false; for (key, value) in form_urlencoded::parse(query.as_bytes()) { let (key, value) = (key.borrow(), value.borrow()); match key { - "start_time_90k" => start = i64::from_str(value)?, - "end_time_90k" => end = i64::from_str(value)?, + "start_time" => time.start = recording::Time::parse(value)?, + "end_time" => time.end = recording::Time::parse(value)?, "trim" if value == "true" => trim = true, _ => {}, } }; - (recording::Time(start) .. recording::Time(end), trim) + (time, trim) }; let camera = db.get_camera(uuid) .ok_or_else(|| Error::new("no such camera".to_owned()))?; @@ -383,12 +382,13 @@ impl Handler { } url }; + let start = if trim && row.time.start < r.start { r.start } else { row.time.start }; + let end = if trim && row.time.end > r.end { r.end } else { row.time.end }; write!(&mut buf, "\ {}\ {}{}x{}{:.0}{:b}B{}bps\n", - url, HumanizedTimestamp(Some(row.time.start)), - HumanizedTimestamp(Some(row.time.end)), row.video_sample_entry.width, - row.video_sample_entry.height, + url, HumanizedTimestamp(Some(start)), HumanizedTimestamp(Some(end)), + row.video_sample_entry.width, row.video_sample_entry.height, if seconds == 0 { 0. } else { row.video_samples as f32 / seconds as f32 }, Humanized(row.sample_file_bytes), Humanized(if seconds == 0 { 0 } else { row.sample_file_bytes * 8 / seconds }))?; @@ -540,6 +540,7 @@ impl Handler { impl server::Handler for Handler { fn handle(&self, req: server::Request, res: server::Response) { let (path, query) = get_path_and_query(&req.uri); + error!("path={:?}, query={:?}", path, query); let res = match decode_path(path) { Path::CamerasList => self.list_cameras(&req, res), Path::Camera(uuid) => self.camera(uuid, query, &req, res),