From 81ea7d8a8700251da229d706174114b1fa7ef7e7 Mon Sep 17 00:00:00 2001 From: Skye Date: Sat, 1 Jul 2023 13:47:37 -0400 Subject: [PATCH] Add "Copy config" option to "Add camera" dialog --- server/src/cmds/config/cameras.rs | 267 ++++++++++++++++++------------ 1 file changed, 160 insertions(+), 107 deletions(-) diff --git a/server/src/cmds/config/cameras.rs b/server/src/cmds/config/cameras.rs index 4aa053d..829cf0f 100644 --- a/server/src/cmds/config/cameras.rs +++ b/server/src/cmds/config/cameras.rs @@ -5,7 +5,7 @@ use crate::stream::{self, Opener}; use base::strutil::{decode_size, encode_size}; use cursive::traits::{Finder, Nameable, Resizable, Scrollable}; -use cursive::views::{self, ViewRef}; +use cursive::views::{self, Dialog, ViewRef}; use cursive::Cursive; use db::writer; use failure::{bail, format_err, Error, ResultExt}; @@ -434,6 +434,127 @@ fn edit_stream_url(type_: db::StreamType, content: &str, mut test_button: ViewRe test_button.set_enabled(enable_test); } +fn load_camera_values( + db: &Arc, + camera_id: i32, + dialog: &mut Dialog, + overwrite_uuid: bool, +) -> (String, i64) { + let dirs: Vec<_> = ::std::iter::once(("".into(), None)) + .chain( + db.lock() + .sample_file_dirs_by_id() + .iter() + .map(|(&id, d)| (d.path.to_owned(), Some(id))), + ) + .collect(); + let l = db.lock(); + let camera = l.cameras_by_id().get(&camera_id).expect("missing camera"); + if overwrite_uuid { + dialog + .call_on_name("uuid", |v: &mut views::TextView| { + v.set_content(camera.uuid.to_string()) + }) + .expect("missing TextView"); + } + + let mut bytes = 0; + for (i, sid) in camera.streams.iter().enumerate() { + let t = db::StreamType::from_index(i).unwrap(); + + // Find the index into dirs of the stored sample file dir. + let mut selected_dir = 0; + if let Some(s) = sid.map(|sid| l.streams_by_id().get(&sid).unwrap()) { + if let Some(id) = s.sample_file_dir_id { + for (i, &(_, d_id)) in dirs.iter().skip(1).enumerate() { + if Some(id) == d_id { + selected_dir = i + 1; + break; + } + } + } + bytes += s.sample_file_bytes; + let u = if s.config.retain_bytes == 0 { + "0 / 0 (0.0%)".to_owned() + } else { + format!( + "{} / {} ({:.1}%)", + s.fs_bytes, + s.config.retain_bytes, + 100. * s.fs_bytes as f32 / s.config.retain_bytes as f32 + ) + }; + dialog.call_on_name(&format!("{}_url", t.as_str()), |v: &mut views::EditView| { + if let Some(url) = s.config.url.as_ref() { + v.set_content(url.as_str().to_owned()); + } + }); + let test_button = dialog + .find_name::(&format!("{}_test", t.as_str())) + .unwrap(); + edit_stream_url( + t, + s.config.url.as_ref().map(Url::as_str).unwrap_or(""), + test_button, + ); + dialog.call_on_name( + &format!("{}_usage_cap", t.as_str()), + |v: &mut views::TextView| v.set_content(u), + ); + dialog.call_on_name( + &format!("{}_record", t.as_str()), + |v: &mut views::Checkbox| { + v.set_checked(s.config.mode == db::json::STREAM_MODE_RECORD) + }, + ); + dialog.call_on_name( + &format!("{}_rtsp_transport", t.as_str()), + |v: &mut views::SelectView<&'static str>| { + v.set_selection(match s.config.rtsp_transport.as_str() { + "tcp" => 1, + "udp" => 2, + _ => 0, + }) + }, + ); + dialog.call_on_name(&format!("{}_flush_if_sec", t), |v: &mut views::EditView| { + v.set_content(s.config.flush_if_sec.to_string()) + }); + } + log::debug!("setting {} dir to {}", t.as_str(), selected_dir); + dialog.call_on_name( + &format!("{}_sample_file_dir", t), + |v: &mut views::SelectView>| v.set_selection(selected_dir), + ); + } + let name = camera.short_name.clone(); + for &(view_id, content) in &[ + ("short_name", &*camera.short_name), + ( + "onvif_base_url", + camera + .config + .onvif_base_url + .as_ref() + .map_or("", Url::as_str), + ), + ("username", &camera.config.username), + ("password", &camera.config.password), + ] { + dialog + .call_on_name(view_id, |v: &mut views::EditView| { + v.set_content(content.to_string()) + }) + .expect("missing EditView"); + } + dialog + .call_on_name("description", |v: &mut views::TextArea| { + v.set_content(camera.config.description.clone()) + }) + .expect("missing TextArea"); + (name, bytes) +} + /// Adds or updates a camera. /// (The former if `item` is None; the latter otherwise.) fn edit_camera_dialog(db: &Arc, siv: &mut Cursive, item: &Option) { @@ -525,108 +646,7 @@ fn edit_camera_dialog(db: &Arc, siv: &mut Cursive, item: &Option(&format!("{}_test", t.as_str())) - .unwrap(); - edit_stream_url( - t, - s.config.url.as_ref().map(Url::as_str).unwrap_or(""), - test_button, - ); - dialog.call_on_name( - &format!("{}_usage_cap", t.as_str()), - |v: &mut views::TextView| v.set_content(u), - ); - dialog.call_on_name( - &format!("{}_record", t.as_str()), - |v: &mut views::Checkbox| { - v.set_checked(s.config.mode == db::json::STREAM_MODE_RECORD) - }, - ); - dialog.call_on_name( - &format!("{}_rtsp_transport", t.as_str()), - |v: &mut views::SelectView<&'static str>| { - v.set_selection(match s.config.rtsp_transport.as_str() { - "tcp" => 1, - "udp" => 2, - _ => 0, - }) - }, - ); - dialog.call_on_name(&format!("{}_flush_if_sec", t), |v: &mut views::EditView| { - v.set_content(s.config.flush_if_sec.to_string()) - }); - } - log::debug!("setting {} dir to {}", t.as_str(), selected_dir); - dialog.call_on_name( - &format!("{}_sample_file_dir", t), - |v: &mut views::SelectView>| v.set_selection(selected_dir), - ); - } - let name = camera.short_name.clone(); - for &(view_id, content) in &[ - ("short_name", &*camera.short_name), - ( - "onvif_base_url", - camera - .config - .onvif_base_url - .as_ref() - .map_or("", Url::as_str), - ), - ("username", &camera.config.username), - ("password", &camera.config.password), - ] { - dialog - .call_on_name(view_id, |v: &mut views::EditView| { - v.set_content(content.to_string()) - }) - .expect("missing EditView"); - } - dialog - .call_on_name("description", |v: &mut views::TextArea| { - v.set_content(camera.config.description.clone()) - }) - .expect("missing TextArea"); + let (name, bytes) = load_camera_values(db, camera_id, &mut dialog, true); dialog .title("Edit camera") .button("Edit", { @@ -644,14 +664,47 @@ fn edit_camera_dialog(db: &Arc, siv: &mut Cursive, item: &Option"), ); } - dialog.title("Add camera").button("Add", { - let db = db.clone(); - move |s| press_edit(s, &db, None) - }) + dialog + .title("Add camera") + .button("Add", { + let db = db.clone(); + move |s| press_edit(s, &db, None) + }) + .button("Copy config", { + let db = db.clone(); + move |s| copy_camera_dialog(s, &db) + }) }; siv.add_layer(dialog.dismiss_button("Cancel")); } +fn copy_camera_dialog(siv: &mut Cursive, db: &Arc) { + siv.add_layer( + views::Dialog::around( + views::SelectView::new() + .with_all( + db.lock() + .cameras_by_id() + .iter() + .map(|(&id, camera)| (format!("{}: {}", id, camera.short_name), id)), + ) + .on_submit({ + let db = db.clone(); + move |siv, &camera_id| { + siv.pop_layer(); + let screen = siv.screen_mut(); + let dialog = screen.get_mut(views::LayerPosition::FromFront(0)).unwrap(); + let dialog = dialog.downcast_mut::().unwrap(); + load_camera_values(&db, camera_id, dialog, false); + } + }) + .full_width(), + ) + .dismiss_button("Cancel") + .title("Select camera to copy"), + ); +} + pub fn top_dialog(db: &Arc, siv: &mut Cursive) { siv.add_layer( views::Dialog::around(