update `cursive`

`cursive` now requires `Send + Sync` bounds, so I had to switch from
`Rc<RefCell<...>>` to `Arc<Mutex<...>>`. I also coalesced some
lock calls together, hopefully without introducing any deadlocks.
I don't see any calls into the UI that would need the model while these
are held, and it seemed fine in a quick test.
This commit is contained in:
Scott Lamb 2024-08-24 13:11:17 -07:00
parent f9e3fb56b3
commit 06f942582c
5 changed files with 174 additions and 81 deletions

137
server/Cargo.lock generated
View File

@ -200,6 +200,15 @@ version = "1.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8318a53db07bb3f8dca91a600466bdb3f2eaadeedfdbcf02e1accbad9271ba50"
[[package]]
name = "castaway"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0abae9be0aaf9ea96a3b1b8b1b55c602ca751eba1b1500220cea4ecbafe7c0d5"
dependencies = [
"rustversion",
]
[[package]]
name = "cc"
version = "1.1.14"
@ -244,6 +253,20 @@ name = "coded"
version = "0.2.0-pre"
source = "git+https://github.com/scottlamb/coded?rev=2c97994974a73243d5dd12134831814f42cdb0e8#2c97994974a73243d5dd12134831814f42cdb0e8"
[[package]]
name = "compact_str"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6050c3a16ddab2e412160b31f2c871015704239bca62f72f6e5f0be631d3f644"
dependencies = [
"castaway",
"cfg-if",
"itoa",
"rustversion",
"ryu",
"static_assertions",
]
[[package]]
name = "constant_time_eq"
version = "0.3.0"
@ -330,9 +353,9 @@ dependencies = [
[[package]]
name = "cursive"
version = "0.20.0"
version = "0.21.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5438eb16bdd8af51b31e74764fef5d0a9260227a5ec82ba75c9d11ce46595839"
checksum = "386d5a36020bb856e9a34ecb8a4e6c9bd6b0262d1857bae4db7bc7e2fdaa532e"
dependencies = [
"ahash",
"cfg-if",
@ -348,19 +371,31 @@ dependencies = [
]
[[package]]
name = "cursive_core"
version = "0.3.7"
name = "cursive-macros"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4db3b58161228d0dcb45c7968c5e74c3f03ad39e8983e58ad7d57061aa2cd94d"
checksum = "ac7ac0eb0cede3dfdfebf4d5f22354e05a730b79c25fd03481fc69fcfba0a73e"
dependencies = [
"proc-macro2",
]
[[package]]
name = "cursive_core"
version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "321ec774d27fafc66e812034d0025f8858bd7d9095304ff8fc200e0b9f9cc257"
dependencies = [
"ahash",
"compact_str",
"crossbeam-channel",
"cursive-macros",
"enum-map",
"enumset",
"lazy_static",
"log",
"num",
"owning_ref",
"parking_lot",
"serde_json",
"time 0.3.36",
"unicode-segmentation",
"unicode-width",
@ -980,6 +1015,17 @@ version = "0.2.158"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439"
[[package]]
name = "libredox"
version = "0.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3af92c55d7d839293953fcd0fda5ecfe93297cfde6ffbdec13b41d99c0ba6607"
dependencies = [
"bitflags 2.6.0",
"libc",
"redox_syscall 0.4.1",
]
[[package]]
name = "libsqlite3-sys"
version = "0.30.1"
@ -1015,6 +1061,16 @@ version = "0.4.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89"
[[package]]
name = "lock_api"
version = "0.4.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17"
dependencies = [
"autocfg",
"scopeguard",
]
[[package]]
name = "log"
version = "0.4.22"
@ -1399,21 +1455,35 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39"
[[package]]
name = "owning_ref"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ff55baddef9e4ad00f88b6c743a2a8062d4c6ade126c2a528644b8e444d52ce"
dependencies = [
"stable_deref_trait",
]
[[package]]
name = "owo-colors"
version = "4.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "caff54706df99d2a78a5a4e3455ff45448d81ef1bb63c22cd14052ca0e993a3f"
[[package]]
name = "parking_lot"
version = "0.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27"
dependencies = [
"lock_api",
"parking_lot_core",
]
[[package]]
name = "parking_lot_core"
version = "0.9.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8"
dependencies = [
"cfg-if",
"libc",
"redox_syscall 0.5.3",
"smallvec",
"windows-targets 0.52.6",
]
[[package]]
name = "password-hash"
version = "0.5.0"
@ -1616,13 +1686,22 @@ dependencies = [
[[package]]
name = "redox_syscall"
version = "0.2.16"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a"
checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa"
dependencies = [
"bitflags 1.3.2",
]
[[package]]
name = "redox_syscall"
version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2a908a6e00f1fdd0dfd9c0eb08ce85126f6d8bbda50017e74bc4a4b7d4a926a4"
dependencies = [
"bitflags 2.6.0",
]
[[package]]
name = "redox_termios"
version = "0.1.3"
@ -1802,6 +1881,12 @@ dependencies = [
"windows-sys 0.52.0",
]
[[package]]
name = "rustversion"
version = "1.0.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6"
[[package]]
name = "ryu"
version = "1.0.18"
@ -1826,6 +1911,12 @@ dependencies = [
"winapi-util",
]
[[package]]
name = "scopeguard"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]]
name = "scrypt"
version = "0.11.0"
@ -1994,6 +2085,12 @@ version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
[[package]]
name = "static_assertions"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
[[package]]
name = "subtle"
version = "2.6.1"
@ -2062,13 +2159,13 @@ dependencies = [
[[package]]
name = "termion"
version = "1.5.6"
version = "4.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "077185e2eac69c3f8379a4298e1e07cd36beb962290d4a51199acf0fdc10607e"
checksum = "1ccce68e518d1173e80876edd54760b60b792750d0cab6444a79101c6ea03848"
dependencies = [
"libc",
"libredox",
"numtoa",
"redox_syscall",
"redox_termios",
]

View File

@ -43,7 +43,7 @@ blake3 = "1.0.0"
bpaf = { version = "0.9.1", features = ["autocomplete", "bright-color", "derive"]}
bytes = "1"
byteorder = "1.0"
cursive = { version = "0.20.0", default-features = false, features = ["termion-backend"] }
cursive = { version = "0.21.1", default-features = false, features = ["termion-backend"] }
db = { package = "moonfire-db", path = "db" }
futures = "0.3"
h264-reader = { workspace = true }

View File

@ -9,11 +9,9 @@ use cursive::view::Scrollable;
use cursive::Cursive;
use cursive::{views, With};
use db::writer;
use std::cell::RefCell;
use std::collections::BTreeMap;
use std::path::Path;
use std::rc::Rc;
use std::sync::Arc;
use std::sync::{Arc, Mutex};
use tracing::{debug, trace};
use super::tab_complete::TabCompleteEditView;
@ -58,10 +56,8 @@ fn update_limits(model: &Model, siv: &mut Cursive) {
}
}
fn edit_limit(model: &RefCell<Model>, siv: &mut Cursive, id: i32, content: &str) {
fn edit_limit(model: &mut Model, siv: &mut Cursive, id: i32, content: &str) {
debug!("on_edit called for id {}", id);
let mut model = model.borrow_mut();
let model: &mut Model = &mut model;
let stream = model.streams.get_mut(&id).unwrap();
let new_value = decode_size(content).ok();
let delta = new_value.unwrap_or(0) - stream.retain.unwrap_or(0);
@ -96,14 +92,12 @@ fn edit_limit(model: &RefCell<Model>, siv: &mut Cursive, id: i32, content: &str)
}
}
fn edit_record(model: &RefCell<Model>, id: i32, record: bool) {
let mut model = model.borrow_mut();
let model: &mut Model = &mut model;
fn edit_record(model: &mut Model, id: i32, record: bool) {
let stream = model.streams.get_mut(&id).unwrap();
stream.record = record;
}
fn confirm_deletion(model: &RefCell<Model>, siv: &mut Cursive, to_delete: i64) {
fn confirm_deletion(model: &Mutex<Model>, siv: &mut Cursive, to_delete: i64) {
let typed = siv
.find_name::<views::EditView>("confirm")
.unwrap()
@ -124,8 +118,8 @@ fn confirm_deletion(model: &RefCell<Model>, siv: &mut Cursive, to_delete: i64) {
}
}
fn actually_delete(model: &RefCell<Model>, siv: &mut Cursive) {
let model = &*model.borrow();
fn actually_delete(model: &Mutex<Model>, siv: &mut Cursive) {
let model = model.lock().unwrap();
let new_limits: Vec<_> = model
.streams
.iter()
@ -147,20 +141,21 @@ fn actually_delete(model: &RefCell<Model>, siv: &mut Cursive) {
.dismiss_button("Abort"),
);
} else {
update_limits(model, siv);
update_limits(&model, siv);
}
}
fn press_change(model: &Rc<RefCell<Model>>, siv: &mut Cursive) {
if model.borrow().errors > 0 {
return;
}
let to_delete = model
.borrow()
.streams
.values()
.map(|s| ::std::cmp::max(s.used - s.retain.unwrap(), 0))
.sum();
fn press_change(model: &Arc<Mutex<Model>>, siv: &mut Cursive) {
let to_delete = {
let l = model.lock().unwrap();
if l.errors > 0 {
return;
}
l.streams
.values()
.map(|s| ::std::cmp::max(s.used - s.retain.unwrap(), 0))
.sum()
};
debug!("change press, to_delete={}", to_delete);
if to_delete > 0 {
let prompt = format!(
@ -190,7 +185,7 @@ fn press_change(model: &Rc<RefCell<Model>>, siv: &mut Cursive) {
siv.add_layer(dialog);
} else {
siv.pop_layer();
update_limits(&model.borrow(), siv);
update_limits(&model.lock().unwrap(), siv);
}
}
@ -367,7 +362,7 @@ fn edit_dir_dialog(db: &Arc<db::Database>, siv: &mut Cursive, dir_id: i32) {
fs_capacity = stat.block_size() as i64 * stat.blocks_available() as i64 + total_used;
path = dir.path.clone();
}
Rc::new(RefCell::new(Model {
Arc::new(Mutex::new(Model {
dir_id,
db: db.clone(),
fs_capacity,
@ -389,12 +384,13 @@ fn edit_dir_dialog(db: &Arc<db::Database>, siv: &mut Cursive, dir_id: i32) {
.child(views::TextView::new("usage").fixed_width(BYTES_WIDTH))
.child(views::TextView::new("limit").fixed_width(BYTES_WIDTH)),
);
for (&id, stream) in &model.borrow().streams {
let l = model.lock().unwrap();
for (&id, stream) in &l.streams {
let mut record_cb = views::Checkbox::new();
record_cb.set_checked(stream.record);
record_cb.set_on_change({
let model = model.clone();
move |_siv, record| edit_record(&model, id, record)
move |_siv, record| edit_record(&mut model.lock().unwrap(), id, record)
});
list.add_child(
&stream.label,
@ -406,7 +402,9 @@ fn edit_dir_dialog(db: &Arc<db::Database>, siv: &mut Cursive, dir_id: i32) {
.content(encode_size(stream.retain.unwrap()))
.on_edit({
let model = model.clone();
move |siv, content, _pos| edit_limit(&model, siv, id, content)
move |siv, content, _pos| {
edit_limit(&mut model.lock().unwrap(), siv, id, content)
}
})
.on_submit({
let model = model.clone();
@ -421,17 +419,14 @@ fn edit_dir_dialog(db: &Arc<db::Database>, siv: &mut Cursive, dir_id: i32) {
),
);
}
let over = model.borrow().total_retain > model.borrow().fs_capacity;
let over = l.total_retain > l.fs_capacity;
list.add_child(
"total",
views::LinearLayout::horizontal()
.child(views::DummyView {}.fixed_width(RECORD_WIDTH))
.child(views::TextView::new(encode_size(l.total_used)).fixed_width(BYTES_WIDTH))
.child(
views::TextView::new(encode_size(model.borrow().total_used))
.fixed_width(BYTES_WIDTH),
)
.child(
views::TextView::new(encode_size(model.borrow().total_retain))
views::TextView::new(encode_size(l.total_retain))
.with_name("total_retain")
.fixed_width(BYTES_WIDTH),
)
@ -442,11 +437,9 @@ fn edit_dir_dialog(db: &Arc<db::Database>, siv: &mut Cursive, dir_id: i32) {
views::LinearLayout::horizontal()
.child(views::DummyView {}.fixed_width(RECORD_WIDTH))
.child(views::DummyView {}.fixed_width(BYTES_WIDTH))
.child(
views::TextView::new(encode_size(model.borrow().fs_capacity))
.fixed_width(BYTES_WIDTH),
),
.child(views::TextView::new(encode_size(l.fs_capacity)).fixed_width(BYTES_WIDTH)),
);
drop(l);
let mut change_button = views::Button::new("Change", move |siv| press_change(&model, siv));
change_button.set_enabled(!over);
let mut buttons = views::LinearLayout::horizontal().child(views::DummyView.full_width());

View File

@ -2,7 +2,7 @@
// Copyright (C) 2020 The Moonfire NVR Authors; see AUTHORS and LICENSE.txt.
// SPDX-License-Identifier: GPL-v3.0-or-later WITH GPL-3.0-linking-exception.
use std::{cell::RefCell, rc::Rc};
use std::sync::{Arc, Mutex};
use cursive::{
direction::Direction,
@ -13,46 +13,49 @@ use cursive::{
Printer, Rect, Vec2, View, With,
};
type TabCompleteFn = Rc<dyn Fn(&str) -> Vec<String>>;
type TabCompleteFn = Arc<dyn Fn(&str) -> Vec<String> + Send + Sync>;
pub struct TabCompleteEditView {
edit_view: Rc<RefCell<EditView>>,
edit_view: Arc<Mutex<EditView>>,
tab_completer: Option<TabCompleteFn>,
}
impl TabCompleteEditView {
pub fn new(edit_view: EditView) -> Self {
Self {
edit_view: Rc::new(RefCell::new(edit_view)),
edit_view: Arc::new(Mutex::new(edit_view)),
tab_completer: None,
}
}
pub fn on_tab_complete(mut self, handler: impl Fn(&str) -> Vec<String> + 'static) -> Self {
self.tab_completer = Some(Rc::new(handler));
pub fn on_tab_complete(
mut self,
handler: impl Fn(&str) -> Vec<String> + Send + Sync + 'static,
) -> Self {
self.tab_completer = Some(Arc::new(handler));
self
}
pub fn get_content(&self) -> Rc<String> {
self.edit_view.borrow_mut().get_content()
pub fn get_content(&self) -> Arc<String> {
self.edit_view.lock().unwrap().get_content()
}
}
impl View for TabCompleteEditView {
fn draw(&self, printer: &Printer) {
self.edit_view.borrow().draw(printer)
self.edit_view.lock().unwrap().draw(printer)
}
fn layout(&mut self, size: Vec2) {
self.edit_view.borrow_mut().layout(size)
self.edit_view.lock().unwrap().layout(size)
}
fn take_focus(&mut self, source: Direction) -> Result<EventResult, CannotFocus> {
self.edit_view.borrow_mut().take_focus(source)
self.edit_view.lock().unwrap().take_focus(source)
}
fn on_event(&mut self, event: Event) -> EventResult {
if !self.edit_view.borrow().is_enabled() {
if !self.edit_view.lock().unwrap().is_enabled() {
return EventResult::Ignored;
}
@ -63,32 +66,32 @@ impl View for TabCompleteEditView {
EventResult::consumed()
}
} else {
self.edit_view.borrow_mut().on_event(event)
self.edit_view.lock().unwrap().on_event(event)
}
}
fn important_area(&self, view_size: Vec2) -> Rect {
self.edit_view.borrow().important_area(view_size)
self.edit_view.lock().unwrap().important_area(view_size)
}
}
fn tab_complete(
edit_view: Rc<RefCell<EditView>>,
edit_view: Arc<Mutex<EditView>>,
tab_completer: TabCompleteFn,
autofill_one: bool,
) -> EventResult {
let completions = tab_completer(edit_view.borrow().get_content().as_str());
let completions = tab_completer(edit_view.lock().unwrap().get_content().as_str());
EventResult::with_cb_once(move |siv| match *completions {
[] => {}
[ref completion] if autofill_one => edit_view.borrow_mut().set_content(completion)(siv),
[ref completion] if autofill_one => edit_view.lock().unwrap().set_content(completion)(siv),
[..] => {
siv.add_layer(TabCompletePopup {
popup: views::MenuPopup::new(Rc::new({
popup: views::MenuPopup::new(Arc::new({
menu::Tree::new().with(|tree| {
for completion in completions {
let edit_view = edit_view.clone();
tree.add_leaf(completion.clone(), move |siv| {
edit_view.borrow_mut().set_content(&completion)(siv)
edit_view.lock().unwrap().set_content(&completion)(siv)
})
}
})
@ -101,7 +104,7 @@ fn tab_complete(
}
struct TabCompletePopup {
edit_view: Rc<RefCell<EditView>>,
edit_view: Arc<Mutex<EditView>>,
popup: MenuPopup,
tab_completer: TabCompleteFn,
}
@ -111,7 +114,7 @@ impl TabCompletePopup {
let tab_completer = self.tab_completer.clone();
EventResult::with_cb_once(move |s| {
s.pop_layer();
edit_view.borrow_mut().on_event(event).process(s);
edit_view.lock().unwrap().on_event(event).process(s);
tab_complete(edit_view, tab_completer, false).process(s);
})
}

View File

@ -189,7 +189,7 @@ fn edit_user_dialog(db: &Arc<db::Database>, siv: &mut Cursive, item: Option<i32>
] {
let mut checkbox = views::Checkbox::new();
checkbox.set_checked(*b);
perms.add_child(name, checkbox.with_name(format!("perm_{name}")));
perms.add_child(*name, checkbox.with_name(format!("perm_{name}")));
}
layout.add_child(perms);