Rewrite Curator in Rust with an egui GUI

This commit is contained in:
2026-10-17 11:28:17 +02:00
parent 364b515e3c
commit 65b5b74c9f
156 changed files with 24359 additions and 25795 deletions
+176
View File
@@ -0,0 +1,176 @@
//! The library organizer (name + kind of each top-level pool folder) and the
//! choice of libraries feeding miniDLNA generation.
use std::collections::HashSet;
use std::fs;
use super::{DialogState, Env, Event, modal, ok_cancel_row};
use crate::core::file_manager::{FileManager, Library, LibraryKind};
use crate::ui::common::{GREY, RED};
pub struct LibraryOrganizerDialog {
rows: Vec<(String, LibraryKind)>,
selected: Option<usize>,
}
impl LibraryOrganizerDialog {
pub fn new(fm: &FileManager) -> Self {
Self {
rows: fm
.libraries()
.into_iter()
.map(|l| (l.name, l.kind))
.collect(),
selected: None,
}
}
pub fn show(&mut self, ctx: &egui::Context, env: &mut Env) -> DialogState {
let mut ok = None;
let (_, close) = modal(ctx, "libraries", "Knihovny poolu", 560.0, |ui| {
ui.label("Knihovny = složky v kořeni poolu, každá je jedna knihovna v Jellyfinu.\nTyp určuje, jak Curator složku zpracuje.");
ui.add_space(6.0);
egui::Grid::new("lib_rows")
.striped(true)
.num_columns(3)
.show(ui, |ui| {
ui.strong("");
ui.strong("Název složky");
ui.strong("Typ");
ui.end_row();
for (i, (name, kind)) in self.rows.iter_mut().enumerate() {
ui.radio_value(&mut self.selected, Some(i), "");
crate::ui::common::sized(ui, 240.0, egui::TextEdit::singleline(name));
egui::ComboBox::from_id_salt(("kind", i))
.selected_text(kind.label())
.show_ui(ui, |ui| {
for k in LibraryKind::ALL {
ui.selectable_value(kind, k, k.label());
}
});
ui.end_row();
}
});
ui.horizontal(|ui| {
if ui.button("➕ Přidat knihovnu").clicked() {
self.rows.push((String::new(), LibraryKind::Movie));
}
if ui.button("➖ Odebrat").clicked()
&& let Some(i) = self.selected.take().filter(|i| *i < self.rows.len())
{
self.rows.remove(i);
}
});
ui.add(egui::Label::new(egui::RichText::new(
"Pozn.: „Mix“ zpracuje filmy (v kořeni) i seriály (v podsložkách); „Domácí videa“ se do miniDLNA zrcadlí celé. Po uložení nabídnu smazání prázdných složek, které už nejsou knihovnou (s obsahem nechám být).",
).color(GREY)).wrap());
ok = ok_cancel_row(ui);
});
match ok {
Some(true) => {
let specs: Vec<Library> = self
.rows
.iter()
.filter(|(n, _)| !n.trim().is_empty())
.map(|(n, k)| Library::new(n.trim(), *k))
.collect();
if let Err(e) = env.fm.set_libraries(specs) {
env.warn("Knihovny", e.to_string());
return DialogState::Open;
}
if let Some(pool) = env.fm.pool_dir() {
for lib in env.fm.libraries() {
let _ = fs::create_dir_all(pool.join(&lib.name));
}
}
let names: Vec<String> = env
.fm
.libraries()
.iter()
.map(|l| format!("{} ({})", l.name, l.kind.as_str()))
.collect();
env.events
.push(Event::Status(format!("Knihovny: {}", names.join(", "))));
env.events.push(Event::LibrariesChanged);
DialogState::Closed
}
Some(false) => DialogState::Closed,
None if close => DialogState::Closed,
None => DialogState::Open,
}
}
}
pub struct MinidlnaLibrariesDialog {
rows: Vec<(String, LibraryKind, bool)>,
}
impl MinidlnaLibrariesDialog {
pub fn new(fm: &FileManager) -> Self {
let chosen: Option<HashSet<String>> =
fm.minidlna_libraries().map(|v| v.into_iter().collect());
Self {
rows: fm
.libraries()
.into_iter()
.map(|l| {
let on = chosen.as_ref().is_none_or(|c| c.contains(&l.name));
(l.name, l.kind, on)
})
.collect(),
}
}
pub fn show(&mut self, ctx: &egui::Context, env: &mut Env) -> DialogState {
let mut ok = None;
let (_, close) = modal(
ctx,
"minidlna_libs",
"Knihovny pro generování miniDLNA",
440.0,
|ui| {
ui.add(egui::Label::new("Které knihovny se mají promítnout do miniDLNA stromu (žánry, Tipy dne, Nově přidané, zrcadlené seriály)?").wrap());
ui.add(egui::Label::new(egui::RichText::new("⚠ Odškrtnutá knihovna: její odkazy ve stromu se při dalším generování odeberou jako zastaralé, dokud ji znovu nezaškrtneš.").color(RED)).wrap());
ui.add_space(6.0);
for (name, kind, on) in &mut self.rows {
ui.checkbox(on, format!("{name} ({})", kind.as_str()));
}
ui.horizontal(|ui| {
if ui.button("Vše").clicked() {
self.rows.iter_mut().for_each(|r| r.2 = true);
}
if ui.button("Nic").clicked() {
self.rows.iter_mut().for_each(|r| r.2 = false);
}
});
ok = ok_cancel_row(ui);
},
);
match ok {
Some(true) => {
let chosen: Vec<String> = self
.rows
.iter()
.filter(|r| r.2)
.map(|r| r.0.clone())
.collect();
let all = chosen.len() == self.rows.len();
if let Err(e) =
env.fm
.set_minidlna_libraries(if all { None } else { Some(chosen.clone()) })
{
env.warn("miniDLNA", e.to_string());
}
env.events.push(Event::Status(format!(
"miniDLNA: {}/{} knihoven vybráno pro generování.",
chosen.len(),
self.rows.len()
)));
DialogState::Closed
}
Some(false) => DialogState::Closed,
None if close => DialogState::Closed,
None => DialogState::Open,
}
}
}