use crate::privilege::SystemCapabilities;
use crate::protocol::metadata::DevelopmentState;
use crate::tui::app::Section;
#[derive(Debug, Clone)]
pub struct ProtocolEntry {
pub kind: Section,
pub name: String,
pub description: String,
pub state: DevelopmentState,
pub notes: Option<String>,
pub default_port: Option<crate::protocol::default_port::DefaultPort>,
pub privilege_note: Option<String>,
}
impl ProtocolEntry {
pub fn kind_label(&self) -> &'static str {
match self.kind {
Section::Servers => "server",
Section::Clients => "client",
}
}
pub fn badge(&self) -> &'static str {
match self.state {
DevelopmentState::Stable => "[stable]",
DevelopmentState::Beta => "[beta]",
DevelopmentState::Experimental => "[exp]",
DevelopmentState::Incomplete => "[incomplete]",
}
}
}
pub fn entries(section: Section, caps: &SystemCapabilities) -> Vec<ProtocolEntry> {
let mut entries = match section {
Section::Servers => {
let registry = crate::protocol::server_registry::registry();
registry
.available_protocols()
.into_iter()
.filter_map(|name| {
let protocol = registry.get(name)?;
let metadata = protocol.metadata();
let privilege_note = if metadata.privilege_requirement.is_met_by(caps) {
None
} else {
Some(format!(
"needs {:?} — will refuse to start in this process",
metadata.privilege_requirement
))
};
Some(ProtocolEntry {
kind: Section::Servers,
name: name.to_string(),
description: protocol.description().to_string(),
state: metadata.state,
notes: metadata.notes.map(|n| n.to_string()),
default_port: crate::protocol::default_port::default_port_for_server(
protocol.as_ref(),
None,
caps,
),
privilege_note,
})
})
.collect::<Vec<_>>()
}
Section::Clients => {
let registry = &crate::protocol::CLIENT_REGISTRY;
registry
.list_protocols()
.into_iter()
.filter_map(|name| {
let protocol = registry.get(&name)?;
let metadata = protocol.metadata();
Some(ProtocolEntry {
kind: Section::Clients,
name: name.clone(),
description: protocol.description().to_string(),
state: metadata.state,
notes: metadata.notes.map(|n| n.to_string()),
default_port: None,
privilege_note: None,
})
})
.collect::<Vec<_>>()
}
};
entries.sort_by(|a, b| a.name.to_lowercase().cmp(&b.name.to_lowercase()));
entries
}
pub fn all_entries(caps: &SystemCapabilities) -> Vec<ProtocolEntry> {
let mut all = entries(Section::Servers, caps);
all.extend(entries(Section::Clients, caps));
all.sort_by(|a, b| {
a.name
.to_lowercase()
.cmp(&b.name.to_lowercase())
.then_with(|| (a.kind == Section::Clients).cmp(&(b.kind == Section::Clients)))
});
all
}
pub fn filter<'a>(entries: &'a [ProtocolEntry], needle: &str) -> Vec<&'a ProtocolEntry> {
let needle = needle.to_lowercase();
let mut words = needle.split_whitespace();
let Some(first) = words.next() else {
return entries.iter().collect();
};
let rest: Vec<&str> = words.collect();
let mut matches: Vec<(u8, &ProtocolEntry)> = entries
.iter()
.filter_map(|entry| {
let name = entry.name.to_lowercase();
let kind = entry.kind_label();
let description = entry.description.to_lowercase();
let rank = if name == first || (kind.starts_with(first) && rest.is_empty()) {
0
} else if name.starts_with(first) {
1
} else if name.contains(first) {
2
} else if description.contains(first) {
3
} else {
return None;
};
let narrowed = rest.iter().all(|word| {
name.contains(word) || kind.starts_with(word) || description.contains(word)
});
narrowed.then_some((rank, entry))
})
.collect();
matches.sort_by_key(|(rank, _)| *rank);
matches.into_iter().map(|(_, entry)| entry).collect()
}