feat(ios,p0): iOS P0 platform, audio fixes, channel UX

This commit is contained in:
Edison Jwa
2026-05-17 22:00:00 +09:00
parent a1fefc8ab6
commit 7a59f5b9a1
38 changed files with 1705 additions and 674 deletions
+85 -21
View File
@@ -454,7 +454,9 @@ async fn connection_task(
Some(Err(e)) => {
let msg = format!("{e}");
let _ = ready_tx.send(Err(ProtocolError::DisconnectedEarly(msg.clone())));
exit!(DisconnectReason::Error(format!("disconnected early: {msg}")));
exit!(DisconnectReason::Error(format!(
"disconnected early: {msg}"
)));
}
None => {
let msg = "event stream ended before snapshot".to_string();
@@ -592,7 +594,11 @@ async fn connection_task(
let expired: Vec<MessageHandle> = pending_moves
.iter()
.filter_map(|(handle, (_, deadline))| {
if now >= *deadline { Some(*handle) } else { None }
if now >= *deadline {
Some(*handle)
} else {
None
}
})
.collect();
for handle in expired {
@@ -608,11 +614,14 @@ async fn connection_task(
let snap = build_snapshot(&con);
let _ = reply.send(snap);
}
Ok(Request::MoveToChannel { channel_id, password, reply }) => {
Ok(Request::MoveToChannel {
channel_id,
password,
reply,
}) => {
match move_self_to(&mut con, channel_id, password.as_deref()) {
Ok(handle) => {
let deadline =
std::time::Instant::now() + Duration::from_secs(3);
let deadline = std::time::Instant::now() + Duration::from_secs(3);
pending_moves.insert(handle, (reply, deadline));
}
Err(e) => {
@@ -622,7 +631,11 @@ async fn connection_task(
}
}
}
Ok(Request::SetMuted { input, output, reply }) => {
Ok(Request::SetMuted {
input,
output,
reply,
}) => {
let r = set_self_muted(&mut con, input, output);
let _ = reply.send(r);
}
@@ -785,8 +798,7 @@ fn sort_channels_tree_by<'a, T>(
// Defensive: if a channel's `parent` does not appear anywhere
// in the emitted tree (orphaned subtree) append it so it isn't
// lost. We track emitted ids and dump anything else.
let emitted: std::collections::HashSet<u64> =
out.iter().map(|c| extract(c).0).collect();
let emitted: std::collections::HashSet<u64> = out.iter().map(|c| extract(c).0).collect();
let mut orphans: Vec<&'a T> = items
.iter()
.copied()
@@ -916,10 +928,26 @@ mod tests {
// iteration order. order=0 -> first; order=X means "comes
// after the channel with id=X". Expected emitted order is
// the linked-list walk: a -> b -> c -> d.
let a = FakeChannel { id: 100, parent: 0, order: 0 };
let b = FakeChannel { id: 200, parent: 0, order: 100 };
let c = FakeChannel { id: 300, parent: 0, order: 200 };
let d = FakeChannel { id: 400, parent: 0, order: 300 };
let a = FakeChannel {
id: 100,
parent: 0,
order: 0,
};
let b = FakeChannel {
id: 200,
parent: 0,
order: 100,
};
let c = FakeChannel {
id: 300,
parent: 0,
order: 200,
};
let d = FakeChannel {
id: 400,
parent: 0,
order: 300,
};
// Deliberately shuffled inputs.
let inputs: Vec<&FakeChannel> = vec![&c, &a, &d, &b];
let sorted = sort_channels_tree_by(&inputs, extract);
@@ -933,9 +961,21 @@ mod tests {
// = 999 which does not exist among the siblings. c must
// not be dropped — it falls back to the leftover bucket
// appended sorted by id at the end.
let a = FakeChannel { id: 100, parent: 0, order: 0 };
let b = FakeChannel { id: 200, parent: 0, order: 100 };
let c = FakeChannel { id: 300, parent: 0, order: 999 };
let a = FakeChannel {
id: 100,
parent: 0,
order: 0,
};
let b = FakeChannel {
id: 200,
parent: 0,
order: 100,
};
let c = FakeChannel {
id: 300,
parent: 0,
order: 999,
};
let inputs: Vec<&FakeChannel> = vec![&c, &a, &b];
let sorted = sort_channels_tree_by(&inputs, extract);
let ids: Vec<u64> = sorted.iter().map(|c| c.id).collect();
@@ -951,10 +991,26 @@ mod tests {
// │ └── a2 (id=12, parent=10, order=11)
// └── b (id=20, order=10)
// Expected emission: a, a1, a2, b
let a = FakeChannel { id: 10, parent: 0, order: 0 };
let a1 = FakeChannel { id: 11, parent: 10, order: 0 };
let a2 = FakeChannel { id: 12, parent: 10, order: 11 };
let b = FakeChannel { id: 20, parent: 0, order: 10 };
let a = FakeChannel {
id: 10,
parent: 0,
order: 0,
};
let a1 = FakeChannel {
id: 11,
parent: 10,
order: 0,
};
let a2 = FakeChannel {
id: 12,
parent: 10,
order: 11,
};
let b = FakeChannel {
id: 20,
parent: 0,
order: 10,
};
let inputs: Vec<&FakeChannel> = vec![&b, &a2, &a, &a1];
let sorted = sort_channels_tree_by(&inputs, extract);
let ids: Vec<u64> = sorted.iter().map(|c| c.id).collect();
@@ -966,8 +1022,16 @@ mod tests {
// a says "comes after b"; b says "comes after a". The
// walk must terminate (cycle guard) and both channels
// must still appear in the output via the leftover path.
let a = FakeChannel { id: 1, parent: 0, order: 2 };
let b = FakeChannel { id: 2, parent: 0, order: 1 };
let a = FakeChannel {
id: 1,
parent: 0,
order: 2,
};
let b = FakeChannel {
id: 2,
parent: 0,
order: 1,
};
let inputs: Vec<&FakeChannel> = vec![&a, &b];
let sorted = sort_channels_tree_by(&inputs, extract);
// Both reachable in some deterministic order (id-sorted
+2 -4
View File
@@ -34,8 +34,7 @@ struct CacheEntry {
at: Instant,
}
static CACHE: Lazy<Mutex<HashMap<String, CacheEntry>>> =
Lazy::new(|| Mutex::new(HashMap::new()));
static CACHE: Lazy<Mutex<HashMap<String, CacheEntry>>> = Lazy::new(|| Mutex::new(HashMap::new()));
/// Resolve `host_input` to a list of socket addresses, preferring
/// IPv4 over IPv6 so the upstream's first connect attempt is the
@@ -189,8 +188,7 @@ mod tests {
#[tokio::test]
async fn unresolvable_returns_dns_failed() {
let r =
resolve("nonexistent-server-for-chanora-tests.invalid").await;
let r = resolve("nonexistent-server-for-chanora-tests.invalid").await;
match r {
Err(ProtocolError::DnsFailed { host, .. }) => {
assert!(host.contains("nonexistent-server-for-chanora-tests.invalid"));