Skip to content

Commit

Permalink
feat: show removed services visually as dead instead of removing them (
Browse files Browse the repository at this point in the history
  • Loading branch information
hrzlgnm authored May 14, 2024
1 parent 0a9ba61 commit 983f27e
Show file tree
Hide file tree
Showing 3 changed files with 120 additions and 50 deletions.
29 changes: 23 additions & 6 deletions src-tauri/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,11 +51,15 @@ pub struct ResolvedService {
updated_at_ms: u64,
}

fn timestamp_millis() -> u64 {
let now = SystemTime::now();
let since_epoch = now.duration_since(SystemTime::UNIX_EPOCH).unwrap();

since_epoch.as_secs() * 1000 + u64::from(since_epoch.subsec_millis())
}

impl From<&ServiceInfo> for ResolvedService {
fn from(info: &ServiceInfo) -> ResolvedService {
let now = SystemTime::now();
let since_epoch = now.duration_since(SystemTime::UNIX_EPOCH).unwrap();
let millisseconds = since_epoch.as_secs() * 1000 + u64::from(since_epoch.subsec_millis());
let mut sorted_addresses: Vec<IpAddr> = info.get_addresses().clone().drain().collect();
sorted_addresses.sort();
let mut sorted_txt: Vec<TxtRecord> = info
Expand All @@ -74,7 +78,7 @@ impl From<&ServiceInfo> for ResolvedService {
addresses: sorted_addresses,
subtype: info.get_subtype().clone(),
txt: sorted_txt,
updated_at_ms: millisseconds,
updated_at_ms: timestamp_millis(),
}
}
}
Expand All @@ -99,6 +103,7 @@ type SearchStoppedEvent = SearchStartedEvent;
#[derive(Serialize, Clone, Debug)]
pub struct ServiceRemovedEvent {
instance_name: String,
at_ms: u64,
}

type ServiceFoundEvent = ServiceRemovedEvent;
Expand Down Expand Up @@ -171,7 +176,13 @@ fn browse(service_type: String, window: Window, state: State<MdnsState>) {
match event {
ServiceEvent::ServiceFound(_service_type, instance_name) => {
window
.emit("service-found", &ServiceFoundEvent { instance_name })
.emit(
"service-found",
&ServiceFoundEvent {
instance_name,
at_ms: timestamp_millis(),
},
)
.expect("To emit");
}
ServiceEvent::SearchStarted(service_type) => {
Expand All @@ -191,7 +202,13 @@ fn browse(service_type: String, window: Window, state: State<MdnsState>) {
}
ServiceEvent::ServiceRemoved(_service_type, instance_name) => {
window
.emit("service-removed", &ServiceRemovedEvent { instance_name })
.emit(
"service-removed",
&ServiceRemovedEvent {
instance_name,
at_ms: timestamp_millis(),
},
)
.expect("To emit");
}
ServiceEvent::SearchStopped(service_type) => {
Expand Down
2 changes: 1 addition & 1 deletion src-tauri/tauri.conf.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
},
"package": {
"productName": "mdns-browser",
"version": "0.6.1"
"version": "0.6.2"
},
"tauri": {
"allowlist": {
Expand Down
139 changes: 96 additions & 43 deletions src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,10 @@ impl Display for TxtRecord {
}
}

fn default_dead() -> bool {
false
}

#[derive(Deserialize, Debug, Clone)]
struct ResolvedService {
instance_name: String,
Expand All @@ -47,6 +51,15 @@ struct ResolvedService {
subtype: Option<String>,
txt: Vec<TxtRecord>,
updated_at_ms: u64,
#[serde(default = "default_dead")]
dead: bool,
}

impl ResolvedService {
fn die_at(&mut self, at_ms: u64) {
self.dead = true;
self.updated_at_ms = at_ms;
}
}
type ResolvedServices = Vec<ResolvedService>;

Expand Down Expand Up @@ -97,6 +110,7 @@ pub struct ResolvedServiceEventRes {
#[derive(Deserialize, Clone, Debug)]
pub struct ServiceRemovedEventRes {
instance_name: String,
at_ms: u64,
}

async fn listen_on_browse_events(event_writer: WriteSignal<ResolvedServices>) {
Expand All @@ -123,7 +137,14 @@ async fn listen_on_browse_events(event_writer: WriteSignal<ResolvedServices>) {
event = removed_fused.next() => {
if let Some(event) = event {
log::debug!("Received event 'service-removed': {:#?}", event);
event_writer.update(|evts| evts.retain(|r| r.instance_name != event.payload.instance_name));
event_writer.update(|evts| {
for item in evts.iter_mut() {
if item.instance_name == event.payload.instance_name {
item.die_at(event.payload.at_ms);
break;
}
}
});
}
}
complete => break,
Expand Down Expand Up @@ -190,6 +211,14 @@ fn ValuesTable(values: Vec<String>, #[prop(into)] title: String) -> impl IntoVie
}
}

fn remove_trailing_local(input: &str) -> String {
if let Some(stripped) = input.strip_suffix(".local.") {
stripped.to_string()
} else {
input.to_string()
}
}

/// Component that auto completes service types
#[component]
fn AutoCompleteServiceType(
Expand Down Expand Up @@ -218,6 +247,67 @@ fn AutoCompleteServiceType(
}
}

/// Component that shows a service as a card
#[component]
fn ResolvedServiceGridItem(resolved_service: ResolvedService) -> impl IntoView {
let mut hostname = resolved_service.hostname;
hostname.pop(); // remove the trailing dot
let updated_at =
DateTime::from_timestamp_millis(resolved_service.updated_at_ms as i64).unwrap();
let as_local_datetime: DateTime<Local> = updated_at.with_timezone(&Local);
let addrs = resolved_service
.addresses
.iter()
.map(|a| a.to_string())
.collect::<Vec<_>>();
let txts = resolved_service
.txt
.iter()
.map(|t| t.to_string())
.collect::<Vec<_>>();
let subtype = match resolved_service.subtype {
None => vec![],
Some(s) => vec![s],
};
let card_title = remove_trailing_local(resolved_service.instance_name.as_str());
let details_title = resolved_service.instance_name.clone();
let show_details = create_rw_signal(false);
let hostname_variant = match resolved_service.dead {
true => TagVariant::Default,
false => TagVariant::Success,
};
let port_variant = match resolved_service.dead {
true => TagVariant::Default,
false => TagVariant::Warning,
};
let addrs_footer = match resolved_service.dead {
true => vec![],
false => addrs.clone(),
};
view! {
<GridItem>
<Card title=card_title>
<CardHeaderExtra slot>
{as_local_datetime.format("%Y-%m-%d %H:%M:%S").to_string()}
</CardHeaderExtra>
<Space align=SpaceAlign::Center>
<Tag variant=hostname_variant>{hostname}</Tag>
<Tag variant=port_variant>{resolved_service.port}</Tag>
<Button size=ButtonSize::Tiny disabled=resolved_service.dead on_click=move |_| show_details.set(true)>
"Details"
</Button>
<Modal title=details_title show=show_details>
<ValuesTable values=subtype title="subtype"/>
<ValuesTable values=addrs title="IPs"/>
<ValuesTable values=txts title="txt"/>
</Modal>
</Space>
<CardFooter slot>{addrs_footer.first()}</CardFooter>
</Card>
</GridItem>
}
}

/// Component that allows for mdns browsing using events
#[component]
fn Browse() -> impl IntoView {
Expand Down Expand Up @@ -282,51 +372,14 @@ fn Browse() -> impl IntoView {
<For
each=move || resolved.get()
key=|rs| format!("{}{}", rs.instance_name.clone(), rs.updated_at_ms)
children=move |rs| {
let mut hostname = rs.hostname;
hostname.pop();
let updated_at = DateTime::from_timestamp_millis(rs.updated_at_ms as i64)
.unwrap();
let as_local_datetime: DateTime<Local> = updated_at.with_timezone(&Local);
let addrs = rs.addresses.iter().map(|a| a.to_string()).collect::<Vec<_>>();
let addrs_cloned = addrs.clone();
let txts = rs.txt.iter().map(|t| t.to_string()).collect::<Vec<_>>();
let subtype = match rs.subtype {
None => vec![],
Some(s) => vec![s],
};
let show = create_rw_signal(false);
view! {
<GridItem>
<Card title=rs.instance_name.clone()>
<CardHeaderExtra slot>
{as_local_datetime.format("%Y-%m-%d %H:%M:%S").to_string()}
</CardHeaderExtra>
<Space align=SpaceAlign::Center>
<Tag variant=TagVariant::Success>{hostname}</Tag>
<Tag variant=TagVariant::Warning>{rs.port}</Tag>
<Button
size=ButtonSize::Tiny
on_click=move |_| show.set(true)
>
"Details"
</Button>
<Modal title=rs.instance_name.clone() show>
<ValuesTable values=subtype title="subtype"/>
<ValuesTable values=addrs title="IPs"/>
<ValuesTable values=txts title="txt"/>
</Modal>
</Space>
<CardFooter slot>{addrs_cloned.first()}</CardFooter>
</Card>
</GridItem>
}
children=move |resolved_service| {
view! { <ResolvedServiceGridItem resolved_service/> }
}
/>

</Grid>
<Style>
".responsivegrid {
"
.responsivegrid {
grid-template-columns: repeat(5, 1fr) !important;
grid-gap: 10px 10px !important;
}
Expand All @@ -350,7 +403,7 @@ fn Browse() -> impl IntoView {
grid-template-columns: 1fr !important;
}
}
"
"
</Style>
</Layout>
}
Expand Down

0 comments on commit 983f27e

Please sign in to comment.