feat(desktop): federation peers editor (add/edit/remove/test, kind-aware)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
476efcf129
commit
b9271e4ab1
1 changed files with 90 additions and 0 deletions
90
src/app.rs
90
src/app.rs
|
|
@ -271,6 +271,8 @@ pub struct App {
|
||||||
/// Receiver for the background fetch of a peer's library (network I/O is
|
/// Receiver for the background fetch of a peer's library (network I/O is
|
||||||
/// done off the UI thread). `None` when no fetch is in flight.
|
/// done off the UI thread). `None` when no fetch is in flight.
|
||||||
remote_rx: Option<Receiver<Result<crate::remote::RemoteLibrary, String>>>,
|
remote_rx: Option<Receiver<Result<crate::remote::RemoteLibrary, String>>>,
|
||||||
|
/// Test-connection result for the settings editor: (row index, message).
|
||||||
|
remote_test_rx: Option<Receiver<(usize, String)>>,
|
||||||
stats_report: Option<crate::stats::StatsReport>,
|
stats_report: Option<crate::stats::StatsReport>,
|
||||||
// Per-channel download-options dialog state
|
// Per-channel download-options dialog state
|
||||||
show_channel_options: bool,
|
show_channel_options: bool,
|
||||||
|
|
@ -699,6 +701,7 @@ impl App {
|
||||||
remote_library: None,
|
remote_library: None,
|
||||||
remote_status: String::new(),
|
remote_status: String::new(),
|
||||||
remote_rx: None,
|
remote_rx: None,
|
||||||
|
remote_test_rx: None,
|
||||||
stats_report: None,
|
stats_report: None,
|
||||||
show_channel_options: false,
|
show_channel_options: false,
|
||||||
channel_options_target: None,
|
channel_options_target: None,
|
||||||
|
|
@ -2547,6 +2550,30 @@ impl App {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Reachability-test the remote at `self.config.remotes[idx]` on a thread.
|
||||||
|
/// The result is delivered over `remote_test_rx`, drained in `update()`.
|
||||||
|
fn start_remote_test(&mut self, idx: usize) {
|
||||||
|
let Some(section) = self.config.remotes.get(idx).cloned() else { return };
|
||||||
|
self.remote_status = format!("Testing {}…", section.name);
|
||||||
|
let (tx, rx) = std::sync::mpsc::channel();
|
||||||
|
self.remote_test_rx = Some(rx);
|
||||||
|
let ctx = self.egui_ctx.clone();
|
||||||
|
std::thread::spawn(move || {
|
||||||
|
let msg = match crate::remote::RemoteClientKind::from_section(§ion) {
|
||||||
|
crate::remote::RemoteClientKind::Catacomb(c) => match c.library_json() {
|
||||||
|
Ok(_) => "✓ reachable".to_string(),
|
||||||
|
Err(e) => format!("✗ {e}"),
|
||||||
|
},
|
||||||
|
crate::remote::RemoteClientKind::Peertube(p) => match p.list_channels() {
|
||||||
|
Ok(ch) => format!("✓ {} channels", ch.len()),
|
||||||
|
Err(e) => format!("✗ {e}"),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
let _ = tx.send((idx, msg));
|
||||||
|
ctx.request_repaint();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/// Launch the configured player on a remote (absolute, tokenized) URL.
|
/// Launch the configured player on a remote (absolute, tokenized) URL.
|
||||||
/// mpv streams it straight from the peer; no resume tracking (that's local).
|
/// mpv streams it straight from the peer; no resume tracking (that's local).
|
||||||
fn play_remote_url(&mut self, url: &str) {
|
fn play_remote_url(&mut self, url: &str) {
|
||||||
|
|
@ -3850,6 +3877,56 @@ impl App {
|
||||||
ui.separator();
|
ui.separator();
|
||||||
ui.add_space(4.0);
|
ui.add_space(4.0);
|
||||||
|
|
||||||
|
ui.heading("🌐 Federation peers");
|
||||||
|
ui.label(egui::RichText::new(
|
||||||
|
"Browse other Catacomb instances, or PeerTube channels, from this one. \
|
||||||
|
PeerTube peers can be added now; browsing them arrives in a later update.")
|
||||||
|
.weak().small());
|
||||||
|
let mut remove: Option<usize> = None;
|
||||||
|
let mut test: Option<usize> = None;
|
||||||
|
for (i, r) in self.config.remotes.iter_mut().enumerate() {
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
egui::ComboBox::from_id_source(("rm-kind", i))
|
||||||
|
.selected_text(match r.kind {
|
||||||
|
crate::config::RemoteKind::Catacomb => "Catacomb",
|
||||||
|
crate::config::RemoteKind::Peertube => "PeerTube",
|
||||||
|
})
|
||||||
|
.show_ui(ui, |ui| {
|
||||||
|
ui.selectable_value(&mut r.kind, crate::config::RemoteKind::Catacomb, "Catacomb");
|
||||||
|
ui.selectable_value(&mut r.kind, crate::config::RemoteKind::Peertube, "PeerTube");
|
||||||
|
});
|
||||||
|
ui.add(egui::TextEdit::singleline(&mut r.name).hint_text("name").desired_width(90.0));
|
||||||
|
ui.add(egui::TextEdit::singleline(&mut r.url).hint_text("url").desired_width(180.0));
|
||||||
|
if r.kind == crate::config::RemoteKind::Peertube {
|
||||||
|
let mut user = r.username.clone().unwrap_or_default();
|
||||||
|
if ui.add(egui::TextEdit::singleline(&mut user).hint_text("username").desired_width(90.0)).changed() {
|
||||||
|
r.username = if user.is_empty() { None } else { Some(user) };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let mut pass = r.password.clone().unwrap_or_default();
|
||||||
|
if ui.add(egui::TextEdit::singleline(&mut pass).password(true).hint_text("password").desired_width(90.0)).changed() {
|
||||||
|
r.password = if pass.is_empty() { None } else { Some(pass) };
|
||||||
|
}
|
||||||
|
if ui.button("Test").clicked() { test = Some(i); }
|
||||||
|
if ui.button("✕").clicked() { remove = Some(i); }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if ui.button("+ Add peer").clicked() {
|
||||||
|
self.config.remotes.push(crate::config::RemoteSection {
|
||||||
|
name: String::new(), url: String::new(),
|
||||||
|
kind: crate::config::RemoteKind::Catacomb, username: None, password: None,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if let Some(i) = remove { self.config.remotes.remove(i); }
|
||||||
|
if let Some(i) = test { self.start_remote_test(i); }
|
||||||
|
if !self.remote_status.is_empty() {
|
||||||
|
ui.label(egui::RichText::new(&self.remote_status).weak().small());
|
||||||
|
}
|
||||||
|
|
||||||
|
ui.add_space(8.0);
|
||||||
|
ui.separator();
|
||||||
|
ui.add_space(4.0);
|
||||||
|
|
||||||
ui.horizontal(|ui| {
|
ui.horizontal(|ui| {
|
||||||
if ui.button("Apply & Save").clicked() {
|
if ui.button("Apply & Save").clicked() {
|
||||||
let new_dir = PathBuf::from(&self.settings_dir);
|
let new_dir = PathBuf::from(&self.settings_dir);
|
||||||
|
|
@ -3912,6 +3989,12 @@ impl App {
|
||||||
self.downloader.fetch_comments = self.config.backup.fetch_comments;
|
self.downloader.fetch_comments = self.config.backup.fetch_comments;
|
||||||
self.downloader.dedup_enabled = self.config.backup.dedup_enabled;
|
self.downloader.dedup_enabled = self.config.backup.dedup_enabled;
|
||||||
self.downloader.convert_defaults = self.config.convert.clone();
|
self.downloader.convert_defaults = self.config.convert.clone();
|
||||||
|
// Rebuild the live federation clients from the edited list.
|
||||||
|
self.remotes = self.config.remotes.iter()
|
||||||
|
.map(|r| std::sync::Arc::new(crate::remote::RemoteClientKind::from_section(r)))
|
||||||
|
.collect();
|
||||||
|
self.remote_selected = None;
|
||||||
|
self.remote_library = None;
|
||||||
if dir_changed {
|
if dir_changed {
|
||||||
self.channels_root = new_dir.clone();
|
self.channels_root = new_dir.clone();
|
||||||
self.library_root = new_dir
|
self.library_root = new_dir
|
||||||
|
|
@ -4936,6 +5019,13 @@ impl eframe::App for App {
|
||||||
ctx.request_repaint();
|
ctx.request_repaint();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Settings editor test-connection result (see start_remote_test).
|
||||||
|
if let Some((_, msg)) = self.remote_test_rx.as_ref().and_then(|rx| rx.try_recv().ok()) {
|
||||||
|
self.remote_status = msg;
|
||||||
|
self.remote_test_rx = None;
|
||||||
|
ctx.request_repaint();
|
||||||
|
}
|
||||||
|
|
||||||
// ── Background dedup result hand-off ────────────────────────────
|
// ── Background dedup result hand-off ────────────────────────────
|
||||||
// Drain in the main loop so the scan completes (and the button
|
// Drain in the main loop so the scan completes (and the button
|
||||||
// re-enables) even if the user navigated away from Maintenance
|
// re-enables) even if the user navigated away from Maintenance
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue