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

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);