fix(peer): coalesce full UI view publications
Keep one queued and one replaceable pending snapshot for remote-library and Call-to-Play views while preserving lifecycle-event FIFO delivery. Generation barriers and fenced drains prevent stale nonempty views from crossing disable or acknowledgement boundaries. Test Plan: - just test - just clippy - focused burst, lifecycle ordering, fence, repeated-barrier, and stale-view tests - independent ordering review - git diff --check
This commit is contained in:
@@ -34,6 +34,7 @@ use lanspread_peer::{
|
|||||||
PeerCommand,
|
PeerCommand,
|
||||||
PeerEndpoint,
|
PeerEndpoint,
|
||||||
PeerEvent,
|
PeerEvent,
|
||||||
|
PeerEventReceiver,
|
||||||
PeerGameDB,
|
PeerGameDB,
|
||||||
PeerIdentity,
|
PeerIdentity,
|
||||||
PeerRuntimeComponent,
|
PeerRuntimeComponent,
|
||||||
@@ -44,6 +45,7 @@ use lanspread_peer::{
|
|||||||
StreamInstallSettings,
|
StreamInstallSettings,
|
||||||
load_peer_identity,
|
load_peer_identity,
|
||||||
migrate_legacy_state,
|
migrate_legacy_state,
|
||||||
|
peer_event_channel,
|
||||||
start_peer_with_options,
|
start_peer_with_options,
|
||||||
};
|
};
|
||||||
use lanspread_peer_cli::{
|
use lanspread_peer_cli::{
|
||||||
@@ -308,7 +310,7 @@ async fn main() -> eyre::Result<()> {
|
|||||||
let fixture_seeds = seed_fixtures(&args.games_dir, &args.fixtures)?;
|
let fixture_seeds = seed_fixtures(&args.games_dir, &args.fixtures)?;
|
||||||
let migration = migrate_legacy_state(&args.games_dir, &args.state_dir).await;
|
let migration = migrate_legacy_state(&args.games_dir, &args.state_dir).await;
|
||||||
|
|
||||||
let (tx_events, rx_events) = mpsc::unbounded_channel();
|
let (tx_events, rx_events) = peer_event_channel();
|
||||||
let peer_game_db = Arc::new(RwLock::new(PeerGameDB::new()));
|
let peer_game_db = Arc::new(RwLock::new(PeerGameDB::new()));
|
||||||
let catalog_game_db = Arc::new(catalog_game_db);
|
let catalog_game_db = Arc::new(catalog_game_db);
|
||||||
let active_outbound_transfers: OutboundTransfers = Arc::new(RwLock::new(HashMap::new()));
|
let active_outbound_transfers: OutboundTransfers = Arc::new(RwLock::new(HashMap::new()));
|
||||||
@@ -865,7 +867,7 @@ async fn wait_peers(shared: &SharedState, count: usize, timeout: Duration) -> ey
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn event_loop(
|
async fn event_loop(
|
||||||
mut rx_events: mpsc::UnboundedReceiver<PeerEvent>,
|
mut rx_events: PeerEventReceiver,
|
||||||
shared: Arc<SharedState>,
|
shared: Arc<SharedState>,
|
||||||
writer: JsonlWriter,
|
writer: JsonlWriter,
|
||||||
) {
|
) {
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ use tokio_util::{sync::CancellationToken, task::TaskTracker};
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
PeerEvent,
|
PeerEvent,
|
||||||
|
PeerEventSender,
|
||||||
StreamInstallProvider,
|
StreamInstallProvider,
|
||||||
Unpacker,
|
Unpacker,
|
||||||
call_to_play::CallToPlayStore,
|
call_to_play::CallToPlayStore,
|
||||||
@@ -45,7 +46,7 @@ const OUTBOUND_CHANGE_DIRTY: u8 = 2;
|
|||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct OutboundTransferChange {
|
pub struct OutboundTransferChange {
|
||||||
state: Arc<AtomicU8>,
|
state: Arc<AtomicU8>,
|
||||||
tx_notify_ui: tokio::sync::mpsc::UnboundedSender<PeerEvent>,
|
tx_notify_ui: PeerEventSender,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Drop for OutboundTransferChange {
|
impl Drop for OutboundTransferChange {
|
||||||
@@ -105,13 +106,13 @@ impl Drop for OutboundTransferChange {
|
|||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub(crate) struct OutboundTransferNotifier {
|
pub(crate) struct OutboundTransferNotifier {
|
||||||
state: Arc<AtomicU8>,
|
state: Arc<AtomicU8>,
|
||||||
tx_notify_ui: tokio::sync::mpsc::UnboundedSender<PeerEvent>,
|
tx_notify_ui: PeerEventSender,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl OutboundTransferNotifier {
|
impl OutboundTransferNotifier {
|
||||||
fn new(
|
fn new(
|
||||||
state: Arc<AtomicU8>,
|
state: Arc<AtomicU8>,
|
||||||
tx_notify_ui: tokio::sync::mpsc::UnboundedSender<PeerEvent>,
|
tx_notify_ui: PeerEventSender,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
state,
|
state,
|
||||||
@@ -242,7 +243,7 @@ impl NetworkServiceCtx {
|
|||||||
|
|
||||||
pub(crate) fn to_peer_ctx(
|
pub(crate) fn to_peer_ctx(
|
||||||
&self,
|
&self,
|
||||||
tx_notify_ui: tokio::sync::mpsc::UnboundedSender<PeerEvent>,
|
tx_notify_ui: PeerEventSender,
|
||||||
) -> PeerCtx {
|
) -> PeerCtx {
|
||||||
self.core.to_peer_ctx(tx_notify_ui, self.shutdown.clone())
|
self.core.to_peer_ctx(tx_notify_ui, self.shutdown.clone())
|
||||||
}
|
}
|
||||||
@@ -271,7 +272,7 @@ pub struct PeerCtx {
|
|||||||
pub peer_id: PeerId,
|
pub peer_id: PeerId,
|
||||||
pub(crate) runtime_session_id: RuntimeSessionId,
|
pub(crate) runtime_session_id: RuntimeSessionId,
|
||||||
pub(crate) state_sync: StateSyncHandle,
|
pub(crate) state_sync: StateSyncHandle,
|
||||||
pub tx_notify_ui: tokio::sync::mpsc::UnboundedSender<PeerEvent>,
|
pub tx_notify_ui: PeerEventSender,
|
||||||
pub stream_install_provider: Arc<dyn StreamInstallProvider>,
|
pub stream_install_provider: Arc<dyn StreamInstallProvider>,
|
||||||
pub shutdown: CancellationToken,
|
pub shutdown: CancellationToken,
|
||||||
pub active_outbound_transfers: OutboundTransfers,
|
pub active_outbound_transfers: OutboundTransfers,
|
||||||
@@ -345,7 +346,7 @@ impl Ctx {
|
|||||||
/// Creates a `PeerCtx` from this context.
|
/// Creates a `PeerCtx` from this context.
|
||||||
pub fn to_peer_ctx(
|
pub fn to_peer_ctx(
|
||||||
&self,
|
&self,
|
||||||
tx_notify_ui: tokio::sync::mpsc::UnboundedSender<PeerEvent>,
|
tx_notify_ui: PeerEventSender,
|
||||||
shutdown: CancellationToken,
|
shutdown: CancellationToken,
|
||||||
) -> PeerCtx {
|
) -> PeerCtx {
|
||||||
let outbound_transfer_notifier = OutboundTransferNotifier::new(
|
let outbound_transfer_notifier = OutboundTransferNotifier::new(
|
||||||
@@ -459,7 +460,7 @@ mod tests {
|
|||||||
sync::{Arc, atomic::AtomicU8},
|
sync::{Arc, atomic::AtomicU8},
|
||||||
};
|
};
|
||||||
|
|
||||||
use tokio::sync::{RwLock, mpsc};
|
use tokio::sync::RwLock;
|
||||||
use tokio_util::sync::CancellationToken;
|
use tokio_util::sync::CancellationToken;
|
||||||
|
|
||||||
use super::{OUTBOUND_CHANGE_IDLE, OperationGuard, OperationKind, OutboundTransferNotifier};
|
use super::{OUTBOUND_CHANGE_IDLE, OperationGuard, OperationKind, OutboundTransferNotifier};
|
||||||
@@ -467,7 +468,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn outbound_transfer_churn_keeps_at_most_one_edge_queued() {
|
fn outbound_transfer_churn_keeps_at_most_one_edge_queued() {
|
||||||
let (tx, mut rx) = mpsc::unbounded_channel();
|
let (tx, mut rx) = crate::peer_event_channel();
|
||||||
let notifier =
|
let notifier =
|
||||||
OutboundTransferNotifier::new(Arc::new(AtomicU8::new(OUTBOUND_CHANGE_IDLE)), tx);
|
OutboundTransferNotifier::new(Arc::new(AtomicU8::new(OUTBOUND_CHANGE_IDLE)), tx);
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ use std::{
|
|||||||
use futures::stream::FuturesUnordered;
|
use futures::stream::FuturesUnordered;
|
||||||
use lanspread_db::content_manifest::ContentId;
|
use lanspread_db::content_manifest::ContentId;
|
||||||
use lanspread_proto::PeerEndpoint;
|
use lanspread_proto::PeerEndpoint;
|
||||||
use tokio::sync::mpsc::UnboundedSender;
|
use crate::PeerEventSender;
|
||||||
use tokio_util::sync::CancellationToken;
|
use tokio_util::sync::CancellationToken;
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
@@ -153,7 +153,7 @@ pub(crate) struct DownloadGameRequest<'a> {
|
|||||||
pub(crate) sources: &'a [PeerEndpoint],
|
pub(crate) sources: &'a [PeerEndpoint],
|
||||||
pub(crate) content_id: ContentId,
|
pub(crate) content_id: ContentId,
|
||||||
pub(crate) quarantine: &'a ContentQuarantine,
|
pub(crate) quarantine: &'a ContentQuarantine,
|
||||||
pub(crate) tx_notify_ui: UnboundedSender<PeerEvent>,
|
pub(crate) tx_notify_ui: PeerEventSender,
|
||||||
pub(crate) cancel_token: CancellationToken,
|
pub(crate) cancel_token: CancellationToken,
|
||||||
pub(crate) quic: QuicConnector,
|
pub(crate) quic: QuicConnector,
|
||||||
}
|
}
|
||||||
@@ -372,7 +372,7 @@ struct TransferContext<'a> {
|
|||||||
sources: &'a [PeerEndpoint],
|
sources: &'a [PeerEndpoint],
|
||||||
content_id: ContentId,
|
content_id: ContentId,
|
||||||
quarantine: &'a ContentQuarantine,
|
quarantine: &'a ContentQuarantine,
|
||||||
tx_notify_ui: &'a UnboundedSender<PeerEvent>,
|
tx_notify_ui: &'a PeerEventSender,
|
||||||
cancel_token: &'a CancellationToken,
|
cancel_token: &'a CancellationToken,
|
||||||
quic: &'a QuicConnector,
|
quic: &'a QuicConnector,
|
||||||
version_buffer: Arc<VersionIniBuffer>,
|
version_buffer: Arc<VersionIniBuffer>,
|
||||||
|
|||||||
@@ -2,15 +2,20 @@
|
|||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
collections::{BTreeMap, HashMap},
|
collections::{BTreeMap, HashMap},
|
||||||
sync::Arc,
|
fmt,
|
||||||
|
sync::{Arc, Mutex, MutexGuard},
|
||||||
};
|
};
|
||||||
|
|
||||||
use lanspread_db::content_manifest::ContentId;
|
use lanspread_db::content_manifest::ContentId;
|
||||||
use tokio::sync::{RwLock, mpsc::UnboundedSender};
|
use tokio::sync::{
|
||||||
|
RwLock,
|
||||||
|
mpsc::{self, error::TryRecvError},
|
||||||
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
ActiveOperation,
|
ActiveOperation,
|
||||||
ActiveOperationKind,
|
ActiveOperationKind,
|
||||||
|
CallToPlayView,
|
||||||
PeerEvent,
|
PeerEvent,
|
||||||
RemoteGameAvailability,
|
RemoteGameAvailability,
|
||||||
RemoteLibraryView,
|
RemoteLibraryView,
|
||||||
@@ -18,10 +23,431 @@ use crate::{
|
|||||||
peer_db::PeerGameDB,
|
peer_db::PeerGameDB,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn send(tx_notify_ui: &UnboundedSender<PeerEvent>, event: PeerEvent) {
|
/// Sender side of the peer-to-UI event transport.
|
||||||
|
///
|
||||||
|
/// Lifecycle events are queued losslessly in send order. The two complete-view
|
||||||
|
/// variants keep one concrete queued delivery plus one replaceable latest
|
||||||
|
/// pending snapshot, so a slow UI cannot make complete snapshots accumulate.
|
||||||
|
pub struct PeerEventSender {
|
||||||
|
queue: mpsc::UnboundedSender<QueuedPeerEvent>,
|
||||||
|
views: Arc<Mutex<CoalescedViews>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Clone for PeerEventSender {
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
Self {
|
||||||
|
queue: self.queue.clone(),
|
||||||
|
views: Arc::clone(&self.views),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Debug for PeerEventSender {
|
||||||
|
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
formatter
|
||||||
|
.debug_struct("PeerEventSender")
|
||||||
|
.field("closed", &self.is_closed())
|
||||||
|
.finish_non_exhaustive()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Receiver side of the peer-to-UI event transport.
|
||||||
|
pub struct PeerEventReceiver {
|
||||||
|
queue: mpsc::UnboundedReceiver<QueuedPeerEvent>,
|
||||||
|
views: Arc<Mutex<CoalescedViews>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct PeerEventSendError {
|
||||||
|
kind: &'static str,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PeerEventSendError {
|
||||||
|
#[must_use]
|
||||||
|
pub const fn kind(&self) -> &'static str {
|
||||||
|
self.kind
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for PeerEventSendError {
|
||||||
|
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
write!(
|
||||||
|
formatter,
|
||||||
|
"peer event channel closed while sending {}",
|
||||||
|
self.kind
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::error::Error for PeerEventSendError {}
|
||||||
|
|
||||||
|
enum QueuedPeerEvent {
|
||||||
|
Lifecycle(PeerEvent),
|
||||||
|
RemoteLibrary(ViewWake),
|
||||||
|
CallToPlay(ViewWake),
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ViewWake {
|
||||||
|
queue: mpsc::UnboundedSender<QueuedPeerEvent>,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct QueueClosed;
|
||||||
|
|
||||||
|
struct VersionedView<T> {
|
||||||
|
generation: Arc<()>,
|
||||||
|
snapshot: T,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct CoalescedViews {
|
||||||
|
generation: Arc<()>,
|
||||||
|
remote_library: ViewSlot<RemoteLibraryView>,
|
||||||
|
call_to_play: ViewSlot<CallToPlayView>,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ViewSlot<T> {
|
||||||
|
marker_queued: bool,
|
||||||
|
queued_delivery: Option<VersionedView<T>>,
|
||||||
|
pending_latest: Option<VersionedView<T>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Default for ViewSlot<T> {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
marker_queued: false,
|
||||||
|
queued_delivery: None,
|
||||||
|
pending_latest: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for CoalescedViews {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
generation: Arc::new(()),
|
||||||
|
remote_library: ViewSlot::default(),
|
||||||
|
call_to_play: ViewSlot::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates the event transport used by one peer runtime embedding.
|
||||||
|
#[must_use]
|
||||||
|
pub fn peer_event_channel() -> (PeerEventSender, PeerEventReceiver) {
|
||||||
|
let (queue_tx, queue_rx) = mpsc::unbounded_channel();
|
||||||
|
let views = Arc::new(Mutex::new(CoalescedViews::default()));
|
||||||
|
(
|
||||||
|
PeerEventSender {
|
||||||
|
queue: queue_tx,
|
||||||
|
views: Arc::clone(&views),
|
||||||
|
},
|
||||||
|
PeerEventReceiver {
|
||||||
|
queue: queue_rx,
|
||||||
|
views,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PeerEventSender {
|
||||||
|
/// Sends one event, coalescing only complete remote-library and Call-to-Play
|
||||||
|
/// projections.
|
||||||
|
pub fn send(&self, event: PeerEvent) -> Result<(), PeerEventSendError> {
|
||||||
|
let kind: &'static str = (&event).into();
|
||||||
|
if self.queue.is_closed() {
|
||||||
|
return Err(PeerEventSendError { kind });
|
||||||
|
}
|
||||||
|
let mut views = lock_views(&self.views);
|
||||||
|
let generation = Arc::clone(&views.generation);
|
||||||
|
let mut failed_lifecycle = None;
|
||||||
|
let result = match event {
|
||||||
|
PeerEvent::RemoteLibraryView(view) => enqueue_view(
|
||||||
|
&self.queue,
|
||||||
|
&generation,
|
||||||
|
&mut views.remote_library,
|
||||||
|
view,
|
||||||
|
QueuedPeerEvent::RemoteLibrary,
|
||||||
|
),
|
||||||
|
PeerEvent::CallToPlayView(view) => enqueue_view(
|
||||||
|
&self.queue,
|
||||||
|
&generation,
|
||||||
|
&mut views.call_to_play,
|
||||||
|
view,
|
||||||
|
QueuedPeerEvent::CallToPlay,
|
||||||
|
),
|
||||||
|
event => self
|
||||||
|
.queue
|
||||||
|
.send(QueuedPeerEvent::Lifecycle(event))
|
||||||
|
.map_err(|error| {
|
||||||
|
failed_lifecycle = Some(error);
|
||||||
|
QueueClosed
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
drop(views);
|
||||||
|
drop(failed_lifecycle);
|
||||||
|
result.map_err(|_| PeerEventSendError { kind })
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub fn is_closed(&self) -> bool {
|
||||||
|
self.queue.is_closed()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Starts a new view-publication generation and atomically installs both
|
||||||
|
/// authoritative replacement views.
|
||||||
|
///
|
||||||
|
/// The network manager calls this after the prior generation has drained and
|
||||||
|
/// before it publishes authoritative clear views. New clear deliveries can
|
||||||
|
/// therefore be queued ahead of `Disabled` even when stale markers remain in
|
||||||
|
/// the underlying FIFO.
|
||||||
|
pub(crate) fn publish_view_generation(
|
||||||
|
&self,
|
||||||
|
remote_library: RemoteLibraryView,
|
||||||
|
call_to_play: CallToPlayView,
|
||||||
|
) -> Result<(), PeerEventSendError> {
|
||||||
|
if self.queue.is_closed() {
|
||||||
|
return Err(PeerEventSendError {
|
||||||
|
kind: "ViewGenerationBarrier",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
let mut views = lock_views(&self.views);
|
||||||
|
views.generation = Arc::new(());
|
||||||
|
let generation = Arc::clone(&views.generation);
|
||||||
|
replace_view_at_barrier(
|
||||||
|
&self.queue,
|
||||||
|
&generation,
|
||||||
|
&mut views.remote_library,
|
||||||
|
remote_library,
|
||||||
|
QueuedPeerEvent::RemoteLibrary,
|
||||||
|
)
|
||||||
|
.map_err(|_| PeerEventSendError {
|
||||||
|
kind: "RemoteLibraryView",
|
||||||
|
})?;
|
||||||
|
replace_view_at_barrier(
|
||||||
|
&self.queue,
|
||||||
|
&generation,
|
||||||
|
&mut views.call_to_play,
|
||||||
|
call_to_play,
|
||||||
|
QueuedPeerEvent::CallToPlay,
|
||||||
|
)
|
||||||
|
.map_err(|_| PeerEventSendError {
|
||||||
|
kind: "CallToPlayView",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn enqueue_view<T>(
|
||||||
|
queue: &mpsc::UnboundedSender<QueuedPeerEvent>,
|
||||||
|
generation: &Arc<()>,
|
||||||
|
slot: &mut ViewSlot<T>,
|
||||||
|
snapshot: T,
|
||||||
|
wrap: impl FnOnce(ViewWake) -> QueuedPeerEvent,
|
||||||
|
) -> Result<(), QueueClosed> {
|
||||||
|
let versioned = VersionedView {
|
||||||
|
generation: Arc::clone(generation),
|
||||||
|
snapshot,
|
||||||
|
};
|
||||||
|
if slot.marker_queued {
|
||||||
|
slot.pending_latest = Some(versioned);
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
slot.marker_queued = true;
|
||||||
|
slot.queued_delivery = Some(versioned);
|
||||||
|
slot.pending_latest = None;
|
||||||
|
let queued = wrap(ViewWake {
|
||||||
|
queue: queue.clone(),
|
||||||
|
});
|
||||||
|
if queue.send(queued).is_ok() {
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
slot.marker_queued = false;
|
||||||
|
slot.queued_delivery = None;
|
||||||
|
Err(QueueClosed)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn replace_view_at_barrier<T>(
|
||||||
|
queue: &mpsc::UnboundedSender<QueuedPeerEvent>,
|
||||||
|
generation: &Arc<()>,
|
||||||
|
slot: &mut ViewSlot<T>,
|
||||||
|
snapshot: T,
|
||||||
|
wrap: impl FnOnce(ViewWake) -> QueuedPeerEvent,
|
||||||
|
) -> Result<(), QueueClosed> {
|
||||||
|
slot.queued_delivery = Some(VersionedView {
|
||||||
|
generation: Arc::clone(generation),
|
||||||
|
snapshot,
|
||||||
|
});
|
||||||
|
slot.pending_latest = None;
|
||||||
|
if slot.marker_queued {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
slot.marker_queued = true;
|
||||||
|
let queued = wrap(ViewWake {
|
||||||
|
queue: queue.clone(),
|
||||||
|
});
|
||||||
|
if queue.send(queued).is_ok() {
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
slot.marker_queued = false;
|
||||||
|
slot.queued_delivery = None;
|
||||||
|
Err(QueueClosed)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PeerEventReceiver {
|
||||||
|
pub async fn recv(&mut self) -> Option<PeerEvent> {
|
||||||
|
while let Some(queued) = self.queue.recv().await {
|
||||||
|
if let Some(event) = resolve_queued_event(queued, &self.views) {
|
||||||
|
return Some(event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn try_recv(&mut self) -> Result<PeerEvent, TryRecvError> {
|
||||||
|
loop {
|
||||||
|
let queued = self.queue.try_recv()?;
|
||||||
|
if let Some(event) = resolve_queued_event(queued, &self.views) {
|
||||||
|
return Ok(event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Consumes exactly one item from the underlying FIFO. A stale view marker
|
||||||
|
/// resolves to `None`; lifecycle fences use this to drain an exact captured
|
||||||
|
/// queue prefix without consuming later traffic.
|
||||||
|
pub fn try_recv_queued(&mut self) -> Result<Option<PeerEvent>, TryRecvError> {
|
||||||
|
self.queue
|
||||||
|
.try_recv()
|
||||||
|
.map(|queued| resolve_queued_event(queued, &self.views))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub fn len(&self) -> usize {
|
||||||
|
self.queue.len()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub fn is_empty(&self) -> bool {
|
||||||
|
self.queue.is_empty()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Drains every event and coalesced snapshot admitted before this call
|
||||||
|
/// acquired the shared transport lock. Producers cannot replace pending
|
||||||
|
/// views until the captured prefix and both pending slots have been taken.
|
||||||
|
pub fn drain_ready(&mut self) -> Result<Vec<PeerEvent>, TryRecvError> {
|
||||||
|
let mut views = lock_views(&self.views);
|
||||||
|
let generation = Arc::clone(&views.generation);
|
||||||
|
let mut events = Vec::with_capacity(self.queue.len() + 2);
|
||||||
|
loop {
|
||||||
|
let queued = match self.queue.try_recv() {
|
||||||
|
Ok(queued) => queued,
|
||||||
|
Err(TryRecvError::Empty) => break,
|
||||||
|
Err(error) => {
|
||||||
|
drop(views);
|
||||||
|
return Err(error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
match queued {
|
||||||
|
QueuedPeerEvent::Lifecycle(event) => events.push(event),
|
||||||
|
QueuedPeerEvent::RemoteLibrary(wake) => {
|
||||||
|
if let Some(event) = resolve_view_locked(
|
||||||
|
&wake,
|
||||||
|
&generation,
|
||||||
|
&mut views.remote_library,
|
||||||
|
QueuedPeerEvent::RemoteLibrary,
|
||||||
|
PeerEvent::RemoteLibraryView,
|
||||||
|
) {
|
||||||
|
events.push(event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
QueuedPeerEvent::CallToPlay(wake) => {
|
||||||
|
if let Some(event) = resolve_view_locked(
|
||||||
|
&wake,
|
||||||
|
&generation,
|
||||||
|
&mut views.call_to_play,
|
||||||
|
QueuedPeerEvent::CallToPlay,
|
||||||
|
PeerEvent::CallToPlayView,
|
||||||
|
) {
|
||||||
|
events.push(event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(events)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn resolve_queued_event(
|
||||||
|
queued: QueuedPeerEvent,
|
||||||
|
views: &Mutex<CoalescedViews>,
|
||||||
|
) -> Option<PeerEvent> {
|
||||||
|
match queued {
|
||||||
|
QueuedPeerEvent::Lifecycle(event) => Some(event),
|
||||||
|
QueuedPeerEvent::RemoteLibrary(wake) => resolve_view(
|
||||||
|
&wake,
|
||||||
|
views,
|
||||||
|
|views| &mut views.remote_library,
|
||||||
|
QueuedPeerEvent::RemoteLibrary,
|
||||||
|
PeerEvent::RemoteLibraryView,
|
||||||
|
),
|
||||||
|
QueuedPeerEvent::CallToPlay(wake) => resolve_view(
|
||||||
|
&wake,
|
||||||
|
views,
|
||||||
|
|views| &mut views.call_to_play,
|
||||||
|
QueuedPeerEvent::CallToPlay,
|
||||||
|
PeerEvent::CallToPlayView,
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn resolve_view<T>(
|
||||||
|
wake: &ViewWake,
|
||||||
|
views: &Mutex<CoalescedViews>,
|
||||||
|
slot: impl FnOnce(&mut CoalescedViews) -> &mut ViewSlot<T>,
|
||||||
|
wrap_queue: impl FnOnce(ViewWake) -> QueuedPeerEvent,
|
||||||
|
wrap_event: impl FnOnce(T) -> PeerEvent,
|
||||||
|
) -> Option<PeerEvent> {
|
||||||
|
let mut views = lock_views(views);
|
||||||
|
let current_generation = Arc::clone(&views.generation);
|
||||||
|
let slot = slot(&mut views);
|
||||||
|
resolve_view_locked(wake, ¤t_generation, slot, wrap_queue, wrap_event)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn resolve_view_locked<T>(
|
||||||
|
wake: &ViewWake,
|
||||||
|
current_generation: &Arc<()>,
|
||||||
|
slot: &mut ViewSlot<T>,
|
||||||
|
wrap_queue: impl FnOnce(ViewWake) -> QueuedPeerEvent,
|
||||||
|
wrap_event: impl FnOnce(T) -> PeerEvent,
|
||||||
|
) -> Option<PeerEvent> {
|
||||||
|
let delivery = slot.queued_delivery.take();
|
||||||
|
if let Some(pending) = slot.pending_latest.take() {
|
||||||
|
slot.queued_delivery = Some(pending);
|
||||||
|
let next = wrap_queue(ViewWake {
|
||||||
|
queue: wake.queue.clone(),
|
||||||
|
});
|
||||||
|
if wake.queue.send(next).is_err() {
|
||||||
|
slot.marker_queued = false;
|
||||||
|
slot.queued_delivery = None;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
slot.marker_queued = false;
|
||||||
|
}
|
||||||
|
delivery
|
||||||
|
.filter(|delivery| Arc::ptr_eq(&delivery.generation, current_generation))
|
||||||
|
.map(|delivery| wrap_event(delivery.snapshot))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn lock_views(views: &Mutex<CoalescedViews>) -> MutexGuard<'_, CoalescedViews> {
|
||||||
|
views
|
||||||
|
.lock()
|
||||||
|
.unwrap_or_else(std::sync::PoisonError::into_inner)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn send(tx_notify_ui: &PeerEventSender, event: PeerEvent) {
|
||||||
if let Err(err) = tx_notify_ui.send(event) {
|
if let Err(err) = tx_notify_ui.send(event) {
|
||||||
let kind: &'static str = (&err.0).into();
|
log::error!("Failed to send {} event: channel closed", err.kind());
|
||||||
log::error!("Failed to send {kind} event: channel closed");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -40,7 +466,7 @@ pub(crate) fn active_operation_snapshot_from_map(
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn send_active_operations_snapshot(
|
pub(crate) fn send_active_operations_snapshot(
|
||||||
tx_notify_ui: &UnboundedSender<PeerEvent>,
|
tx_notify_ui: &PeerEventSender,
|
||||||
active_operations: &HashMap<String, OperationKind>,
|
active_operations: &HashMap<String, OperationKind>,
|
||||||
) {
|
) {
|
||||||
send(
|
send(
|
||||||
@@ -53,7 +479,7 @@ pub(crate) fn send_active_operations_snapshot(
|
|||||||
|
|
||||||
pub(crate) async fn emit_active_operations(
|
pub(crate) async fn emit_active_operations(
|
||||||
active_operations: &Arc<RwLock<HashMap<String, OperationKind>>>,
|
active_operations: &Arc<RwLock<HashMap<String, OperationKind>>>,
|
||||||
tx_notify_ui: &UnboundedSender<PeerEvent>,
|
tx_notify_ui: &PeerEventSender,
|
||||||
) {
|
) {
|
||||||
let active_operations = active_operations.read().await;
|
let active_operations = active_operations.read().await;
|
||||||
send_active_operations_snapshot(tx_notify_ui, &active_operations);
|
send_active_operations_snapshot(tx_notify_ui, &active_operations);
|
||||||
@@ -71,16 +497,13 @@ fn active_operation_kind(operation: OperationKind) -> ActiveOperationKind {
|
|||||||
|
|
||||||
pub async fn emit_peer_game_list(
|
pub async fn emit_peer_game_list(
|
||||||
peer_game_db: &Arc<RwLock<PeerGameDB>>,
|
peer_game_db: &Arc<RwLock<PeerGameDB>>,
|
||||||
tx_notify_ui: &UnboundedSender<PeerEvent>,
|
tx_notify_ui: &PeerEventSender,
|
||||||
) {
|
) {
|
||||||
let db = peer_game_db.read().await;
|
let db = peer_game_db.read().await;
|
||||||
send_remote_library_view_locked(&db, tx_notify_ui);
|
send_remote_library_view_locked(&db, tx_notify_ui);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn send_remote_library_view_locked(
|
fn send_remote_library_view_locked(peer_game_db: &PeerGameDB, tx_notify_ui: &PeerEventSender) {
|
||||||
peer_game_db: &PeerGameDB,
|
|
||||||
tx_notify_ui: &UnboundedSender<PeerEvent>,
|
|
||||||
) {
|
|
||||||
send(
|
send(
|
||||||
tx_notify_ui,
|
tx_notify_ui,
|
||||||
PeerEvent::RemoteLibraryView(remote_library_view(peer_game_db)),
|
PeerEvent::RemoteLibraryView(remote_library_view(peer_game_db)),
|
||||||
@@ -113,7 +536,7 @@ pub(crate) fn remote_library_view(peer_game_db: &PeerGameDB) -> RemoteLibraryVie
|
|||||||
|
|
||||||
pub async fn emit_peer_count(
|
pub async fn emit_peer_count(
|
||||||
peer_game_db: &Arc<RwLock<PeerGameDB>>,
|
peer_game_db: &Arc<RwLock<PeerGameDB>>,
|
||||||
tx_notify_ui: &UnboundedSender<PeerEvent>,
|
tx_notify_ui: &PeerEventSender,
|
||||||
) {
|
) {
|
||||||
let db = peer_game_db.read().await;
|
let db = peer_game_db.read().await;
|
||||||
let peer_count = db.peer_endpoints().len();
|
let peer_count = db.peer_endpoints().len();
|
||||||
@@ -124,6 +547,151 @@ pub async fn emit_peer_count(
|
|||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
|
fn remote_view(game_id: &str) -> RemoteLibraryView {
|
||||||
|
RemoteLibraryView {
|
||||||
|
games: vec![RemoteGameAvailability {
|
||||||
|
game_id: game_id.to_owned(),
|
||||||
|
content_id: ContentId::from_bytes(
|
||||||
|
[u8::try_from(game_id.len()).expect("test game ID length should fit u8"); 32],
|
||||||
|
),
|
||||||
|
peer_count: 1,
|
||||||
|
}],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn call_to_play_view(author_name: &str, seed: u8) -> CallToPlayView {
|
||||||
|
let author_id = crate::PeerId::from_bytes([seed; 32]);
|
||||||
|
CallToPlayView {
|
||||||
|
events: vec![crate::CallToPlayViewEvent {
|
||||||
|
id: crate::EventNonce::from_bytes([seed; 16]),
|
||||||
|
call_id: crate::CallId::new(
|
||||||
|
author_id,
|
||||||
|
lanspread_proto::CallNonce::from_bytes([seed; 16]),
|
||||||
|
),
|
||||||
|
author_id,
|
||||||
|
author_name: author_name.to_owned(),
|
||||||
|
at: i64::from(seed),
|
||||||
|
action: crate::CallToPlayAction::Rsvp,
|
||||||
|
}],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn complete_views_keep_first_queued_and_only_latest_pending() {
|
||||||
|
let (tx, mut rx) = peer_event_channel();
|
||||||
|
tx.send(PeerEvent::RemoteLibraryView(remote_view("first")))
|
||||||
|
.expect("first remote view should enqueue");
|
||||||
|
tx.send(PeerEvent::RemoteLibraryView(remote_view("middle")))
|
||||||
|
.expect("middle remote view should coalesce");
|
||||||
|
tx.send(PeerEvent::RemoteLibraryView(remote_view("latest")))
|
||||||
|
.expect("latest remote view should replace pending");
|
||||||
|
tx.send(PeerEvent::PeerCountUpdated(1))
|
||||||
|
.expect("first lifecycle event should enqueue");
|
||||||
|
tx.send(PeerEvent::PeerCountUpdated(2))
|
||||||
|
.expect("second lifecycle event should enqueue");
|
||||||
|
tx.send(PeerEvent::CallToPlayView(call_to_play_view("first", 1)))
|
||||||
|
.expect("first Call-to-Play view should enqueue");
|
||||||
|
tx.send(PeerEvent::CallToPlayView(call_to_play_view("latest", 2)))
|
||||||
|
.expect("latest Call-to-Play view should become pending");
|
||||||
|
|
||||||
|
assert_eq!(rx.len(), 4, "pending snapshots must not occupy FIFO slots");
|
||||||
|
assert!(matches!(
|
||||||
|
rx.recv().await,
|
||||||
|
Some(PeerEvent::RemoteLibraryView(view)) if view.games[0].game_id == "first"
|
||||||
|
));
|
||||||
|
assert!(matches!(
|
||||||
|
rx.recv().await,
|
||||||
|
Some(PeerEvent::PeerCountUpdated(1))
|
||||||
|
));
|
||||||
|
assert!(matches!(
|
||||||
|
rx.recv().await,
|
||||||
|
Some(PeerEvent::PeerCountUpdated(2))
|
||||||
|
));
|
||||||
|
assert!(matches!(
|
||||||
|
rx.recv().await,
|
||||||
|
Some(PeerEvent::CallToPlayView(view)) if view.events[0].author_name == "first"
|
||||||
|
));
|
||||||
|
assert!(matches!(
|
||||||
|
rx.recv().await,
|
||||||
|
Some(PeerEvent::RemoteLibraryView(view)) if view.games[0].game_id == "latest"
|
||||||
|
));
|
||||||
|
assert!(matches!(
|
||||||
|
rx.recv().await,
|
||||||
|
Some(PeerEvent::CallToPlayView(view)) if view.events[0].author_name == "latest"
|
||||||
|
));
|
||||||
|
assert!(matches!(rx.try_recv(), Err(TryRecvError::Empty)));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn ready_fence_drains_queued_and_pending_views_without_later_traffic() {
|
||||||
|
let (tx, mut rx) = peer_event_channel();
|
||||||
|
tx.send(PeerEvent::RemoteLibraryView(remote_view("queued")))
|
||||||
|
.expect("queued view should enqueue");
|
||||||
|
tx.send(PeerEvent::PeerCountUpdated(7))
|
||||||
|
.expect("lifecycle event should enqueue");
|
||||||
|
tx.send(PeerEvent::RemoteLibraryView(remote_view("pending")))
|
||||||
|
.expect("pending view should coalesce");
|
||||||
|
|
||||||
|
let drained = rx.drain_ready().expect("ready prefix should drain");
|
||||||
|
assert_eq!(drained.len(), 3);
|
||||||
|
assert!(matches!(
|
||||||
|
&drained[0],
|
||||||
|
PeerEvent::RemoteLibraryView(view) if view.games[0].game_id == "queued"
|
||||||
|
));
|
||||||
|
assert!(matches!(&drained[1], PeerEvent::PeerCountUpdated(7)));
|
||||||
|
assert!(matches!(
|
||||||
|
&drained[2],
|
||||||
|
PeerEvent::RemoteLibraryView(view) if view.games[0].game_id == "pending"
|
||||||
|
));
|
||||||
|
assert!(matches!(rx.try_recv(), Err(TryRecvError::Empty)));
|
||||||
|
|
||||||
|
tx.send(PeerEvent::PeerCountUpdated(8))
|
||||||
|
.expect("later lifecycle traffic should enqueue independently");
|
||||||
|
assert!(matches!(rx.try_recv(), Ok(PeerEvent::PeerCountUpdated(8))));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn generation_barrier_discards_old_views_before_clear_and_disabled() {
|
||||||
|
let (tx, mut rx) = peer_event_channel();
|
||||||
|
tx.send(PeerEvent::RemoteLibraryView(remote_view("old-first")))
|
||||||
|
.expect("old remote view should enqueue");
|
||||||
|
tx.send(PeerEvent::RemoteLibraryView(remote_view("old-pending")))
|
||||||
|
.expect("old remote pending view should coalesce");
|
||||||
|
tx.send(PeerEvent::CallToPlayView(call_to_play_view("old-first", 1)))
|
||||||
|
.expect("old Call-to-Play view should enqueue");
|
||||||
|
tx.send(PeerEvent::CallToPlayView(call_to_play_view(
|
||||||
|
"old-pending",
|
||||||
|
2,
|
||||||
|
)))
|
||||||
|
.expect("old Call-to-Play pending view should coalesce");
|
||||||
|
|
||||||
|
tx.publish_view_generation(RemoteLibraryView::default(), CallToPlayView::default())
|
||||||
|
.expect("clear views should enqueue in the new generation");
|
||||||
|
tx.publish_view_generation(RemoteLibraryView::default(), CallToPlayView::default())
|
||||||
|
.expect("repeated barriers should replace in place");
|
||||||
|
assert_eq!(rx.len(), 2, "barriers must not accumulate view markers");
|
||||||
|
tx.send(PeerEvent::LocalNetworkSharingStateChanged(
|
||||||
|
crate::LocalNetworkSharingState::Disabled,
|
||||||
|
))
|
||||||
|
.expect("Disabled should remain a lifecycle event");
|
||||||
|
|
||||||
|
assert!(matches!(
|
||||||
|
rx.recv().await,
|
||||||
|
Some(PeerEvent::RemoteLibraryView(view)) if view.games.is_empty()
|
||||||
|
));
|
||||||
|
assert!(matches!(
|
||||||
|
rx.recv().await,
|
||||||
|
Some(PeerEvent::CallToPlayView(view)) if view.events.is_empty()
|
||||||
|
));
|
||||||
|
assert!(matches!(
|
||||||
|
rx.recv().await,
|
||||||
|
Some(PeerEvent::LocalNetworkSharingStateChanged(
|
||||||
|
crate::LocalNetworkSharingState::Disabled
|
||||||
|
))
|
||||||
|
));
|
||||||
|
assert!(matches!(rx.try_recv(), Err(TryRecvError::Empty)));
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn remote_library_view_is_enqueued_before_a_waiting_writer_can_commit() {
|
async fn remote_library_view_is_enqueued_before_a_waiting_writer_can_commit() {
|
||||||
let peer_game_db = Arc::new(RwLock::new(PeerGameDB::new()));
|
let peer_game_db = Arc::new(RwLock::new(PeerGameDB::new()));
|
||||||
@@ -135,7 +703,7 @@ mod tests {
|
|||||||
tokio::task::yield_now().await;
|
tokio::task::yield_now().await;
|
||||||
assert!(!writer.is_finished(), "writer must wait for the read guard");
|
assert!(!writer.is_finished(), "writer must wait for the read guard");
|
||||||
|
|
||||||
let (tx, mut rx) = tokio::sync::mpsc::unbounded_channel();
|
let (tx, mut rx) = peer_event_channel();
|
||||||
send_remote_library_view_locked(&guard, &tx);
|
send_remote_library_view_locked(&guard, &tx);
|
||||||
assert!(matches!(
|
assert!(matches!(
|
||||||
rx.try_recv(),
|
rx.try_recv(),
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ use lanspread_db::{
|
|||||||
db::GameDB,
|
db::GameDB,
|
||||||
};
|
};
|
||||||
use lanspread_proto::PeerEndpoint;
|
use lanspread_proto::PeerEndpoint;
|
||||||
use tokio::sync::mpsc::UnboundedSender;
|
use crate::PeerEventSender;
|
||||||
use tokio_util::sync::CancellationToken;
|
use tokio_util::sync::CancellationToken;
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
@@ -82,7 +82,7 @@ where
|
|||||||
|
|
||||||
async fn register_download_attempt(
|
async fn register_download_attempt(
|
||||||
ctx: &Ctx,
|
ctx: &Ctx,
|
||||||
tx_notify_ui: &UnboundedSender<PeerEvent>,
|
tx_notify_ui: &PeerEventSender,
|
||||||
key: DownloadAttemptKey,
|
key: DownloadAttemptKey,
|
||||||
cancellation: CancellationToken,
|
cancellation: CancellationToken,
|
||||||
) -> DownloadAttemptStatus {
|
) -> DownloadAttemptStatus {
|
||||||
@@ -202,7 +202,7 @@ impl fmt::Display for StreamDownloadError {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Handles the `ListGames` command.
|
/// Handles the `ListGames` command.
|
||||||
pub async fn handle_list_games_command(ctx: &Ctx, tx_notify_ui: &UnboundedSender<PeerEvent>) {
|
pub async fn handle_list_games_command(ctx: &Ctx, tx_notify_ui: &PeerEventSender) {
|
||||||
log::info!("ListGames command received");
|
log::info!("ListGames command received");
|
||||||
events::emit_peer_game_list(&ctx.peer_game_db, tx_notify_ui).await;
|
events::emit_peer_game_list(&ctx.peer_game_db, tx_notify_ui).await;
|
||||||
}
|
}
|
||||||
@@ -211,7 +211,7 @@ pub async fn handle_list_games_command(ctx: &Ctx, tx_notify_ui: &UnboundedSender
|
|||||||
#[allow(clippy::too_many_lines)]
|
#[allow(clippy::too_many_lines)]
|
||||||
pub async fn handle_download_game_files_command(
|
pub async fn handle_download_game_files_command(
|
||||||
ctx: &Ctx,
|
ctx: &Ctx,
|
||||||
tx_notify_ui: &UnboundedSender<PeerEvent>,
|
tx_notify_ui: &PeerEventSender,
|
||||||
id: String,
|
id: String,
|
||||||
install_after_download: bool,
|
install_after_download: bool,
|
||||||
) {
|
) {
|
||||||
@@ -494,7 +494,7 @@ pub async fn handle_download_game_files_command(
|
|||||||
|
|
||||||
fn finish_cached_download(
|
fn finish_cached_download(
|
||||||
ctx: &Ctx,
|
ctx: &Ctx,
|
||||||
tx_notify_ui: &UnboundedSender<PeerEvent>,
|
tx_notify_ui: &PeerEventSender,
|
||||||
attempt: &DownloadAttemptKey,
|
attempt: &DownloadAttemptKey,
|
||||||
install_after_download: bool,
|
install_after_download: bool,
|
||||||
target: OperationTarget,
|
target: OperationTarget,
|
||||||
@@ -547,7 +547,7 @@ async fn settle_download_completion(
|
|||||||
/// Handles the `InstallGame` command.
|
/// Handles the `InstallGame` command.
|
||||||
pub async fn handle_install_game_command(
|
pub async fn handle_install_game_command(
|
||||||
ctx: &Ctx,
|
ctx: &Ctx,
|
||||||
tx_notify_ui: &UnboundedSender<PeerEvent>,
|
tx_notify_ui: &PeerEventSender,
|
||||||
id: String,
|
id: String,
|
||||||
) {
|
) {
|
||||||
let games_folder = ctx.game_dir.read().await.clone();
|
let games_folder = ctx.game_dir.read().await.clone();
|
||||||
@@ -575,7 +575,7 @@ async fn stream_install_target_is_ready(ctx: &Ctx, target: &OperationTarget) ->
|
|||||||
|
|
||||||
async fn begin_stream_install_operation(
|
async fn begin_stream_install_operation(
|
||||||
ctx: &Ctx,
|
ctx: &Ctx,
|
||||||
tx_notify_ui: &UnboundedSender<PeerEvent>,
|
tx_notify_ui: &PeerEventSender,
|
||||||
target: &OperationTarget,
|
target: &OperationTarget,
|
||||||
attempt: &DownloadAttemptKey,
|
attempt: &DownloadAttemptKey,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
@@ -631,7 +631,7 @@ async fn begin_stream_install_operation(
|
|||||||
|
|
||||||
pub async fn handle_stream_install_game_command(
|
pub async fn handle_stream_install_game_command(
|
||||||
ctx: &Ctx,
|
ctx: &Ctx,
|
||||||
tx_notify_ui: &UnboundedSender<PeerEvent>,
|
tx_notify_ui: &PeerEventSender,
|
||||||
id: String,
|
id: String,
|
||||||
settings: StreamInstallSettings,
|
settings: StreamInstallSettings,
|
||||||
) {
|
) {
|
||||||
@@ -742,7 +742,7 @@ pub async fn handle_stream_install_game_command(
|
|||||||
|
|
||||||
struct StreamInstallOperation {
|
struct StreamInstallOperation {
|
||||||
ctx: Ctx,
|
ctx: Ctx,
|
||||||
tx_notify_ui: UnboundedSender<PeerEvent>,
|
tx_notify_ui: PeerEventSender,
|
||||||
target: OperationTarget,
|
target: OperationTarget,
|
||||||
manifest: Arc<CatalogContentManifest>,
|
manifest: Arc<CatalogContentManifest>,
|
||||||
settings: StreamInstallSettings,
|
settings: StreamInstallSettings,
|
||||||
@@ -768,7 +768,7 @@ async fn stream_install_sources(
|
|||||||
|
|
||||||
async fn select_stream_install_sources_or_finish(
|
async fn select_stream_install_sources_or_finish(
|
||||||
ctx: &Ctx,
|
ctx: &Ctx,
|
||||||
tx_notify_ui: &UnboundedSender<PeerEvent>,
|
tx_notify_ui: &PeerEventSender,
|
||||||
target: &OperationTarget,
|
target: &OperationTarget,
|
||||||
manifest: &CatalogContentManifest,
|
manifest: &CatalogContentManifest,
|
||||||
download_guard: OperationGuard,
|
download_guard: OperationGuard,
|
||||||
@@ -798,7 +798,7 @@ async fn select_stream_install_sources_or_finish(
|
|||||||
/// Handles the `UninstallGame` command.
|
/// Handles the `UninstallGame` command.
|
||||||
pub async fn handle_uninstall_game_command(
|
pub async fn handle_uninstall_game_command(
|
||||||
ctx: &Ctx,
|
ctx: &Ctx,
|
||||||
tx_notify_ui: &UnboundedSender<PeerEvent>,
|
tx_notify_ui: &PeerEventSender,
|
||||||
id: String,
|
id: String,
|
||||||
) {
|
) {
|
||||||
let games_folder = ctx.game_dir.read().await.clone();
|
let games_folder = ctx.game_dir.read().await.clone();
|
||||||
@@ -812,7 +812,7 @@ pub async fn handle_uninstall_game_command(
|
|||||||
|
|
||||||
pub async fn handle_remove_downloaded_game_command(
|
pub async fn handle_remove_downloaded_game_command(
|
||||||
ctx: &Ctx,
|
ctx: &Ctx,
|
||||||
tx_notify_ui: &UnboundedSender<PeerEvent>,
|
tx_notify_ui: &PeerEventSender,
|
||||||
id: String,
|
id: String,
|
||||||
) {
|
) {
|
||||||
let games_folder = ctx.game_dir.read().await.clone();
|
let games_folder = ctx.game_dir.read().await.clone();
|
||||||
@@ -826,7 +826,7 @@ pub async fn handle_remove_downloaded_game_command(
|
|||||||
|
|
||||||
pub async fn handle_cancel_download_command(
|
pub async fn handle_cancel_download_command(
|
||||||
ctx: &Ctx,
|
ctx: &Ctx,
|
||||||
_tx_notify_ui: &UnboundedSender<PeerEvent>,
|
_tx_notify_ui: &PeerEventSender,
|
||||||
id: String,
|
id: String,
|
||||||
) {
|
) {
|
||||||
let signal = ctx.active_downloads.read().await.get(&id).cloned();
|
let signal = ctx.active_downloads.read().await.get(&id).cloned();
|
||||||
@@ -945,7 +945,7 @@ async fn run_stream_install_operation(operation: StreamInstallOperation) {
|
|||||||
|
|
||||||
async fn finish_stream_receive_error(
|
async fn finish_stream_receive_error(
|
||||||
ctx: &Ctx,
|
ctx: &Ctx,
|
||||||
tx_notify_ui: &UnboundedSender<PeerEvent>,
|
tx_notify_ui: &PeerEventSender,
|
||||||
target: &OperationTarget,
|
target: &OperationTarget,
|
||||||
download_guard: OperationGuard,
|
download_guard: OperationGuard,
|
||||||
download_attempt: &DownloadAttemptStatus,
|
download_attempt: &DownloadAttemptStatus,
|
||||||
@@ -975,7 +975,7 @@ async fn finish_stream_receive_error(
|
|||||||
|
|
||||||
struct StreamInstallPromotionPreparation<'a> {
|
struct StreamInstallPromotionPreparation<'a> {
|
||||||
ctx: &'a Ctx,
|
ctx: &'a Ctx,
|
||||||
tx_notify_ui: &'a UnboundedSender<PeerEvent>,
|
tx_notify_ui: &'a PeerEventSender,
|
||||||
target: &'a OperationTarget,
|
target: &'a OperationTarget,
|
||||||
transaction: install::StreamedInstallTransaction,
|
transaction: install::StreamedInstallTransaction,
|
||||||
settings: &'a StreamInstallSettings,
|
settings: &'a StreamInstallSettings,
|
||||||
@@ -1055,7 +1055,7 @@ async fn prepare_streamed_install_for_promotion(
|
|||||||
|
|
||||||
struct StreamInstallReceiveRequest<'a> {
|
struct StreamInstallReceiveRequest<'a> {
|
||||||
ctx: &'a Ctx,
|
ctx: &'a Ctx,
|
||||||
tx_notify_ui: &'a UnboundedSender<PeerEvent>,
|
tx_notify_ui: &'a PeerEventSender,
|
||||||
target: &'a OperationTarget,
|
target: &'a OperationTarget,
|
||||||
manifest: &'a Arc<CatalogContentManifest>,
|
manifest: &'a Arc<CatalogContentManifest>,
|
||||||
sources: &'a [PeerEndpoint],
|
sources: &'a [PeerEndpoint],
|
||||||
@@ -1218,7 +1218,7 @@ async fn receive_streamed_install_from_peers(
|
|||||||
|
|
||||||
async fn finish_failed_stream_download(
|
async fn finish_failed_stream_download(
|
||||||
ctx: &Ctx,
|
ctx: &Ctx,
|
||||||
tx_notify_ui: &UnboundedSender<PeerEvent>,
|
tx_notify_ui: &PeerEventSender,
|
||||||
target: &OperationTarget,
|
target: &OperationTarget,
|
||||||
guard: OperationGuard,
|
guard: OperationGuard,
|
||||||
status: &DownloadAttemptStatus,
|
status: &DownloadAttemptStatus,
|
||||||
@@ -1243,7 +1243,7 @@ async fn finish_failed_stream_download(
|
|||||||
|
|
||||||
struct StreamInstallCommit {
|
struct StreamInstallCommit {
|
||||||
ctx: Ctx,
|
ctx: Ctx,
|
||||||
tx_notify_ui: UnboundedSender<PeerEvent>,
|
tx_notify_ui: PeerEventSender,
|
||||||
target: OperationTarget,
|
target: OperationTarget,
|
||||||
transaction: install::StreamedInstallTransaction,
|
transaction: install::StreamedInstallTransaction,
|
||||||
manifest: Arc<CatalogContentManifest>,
|
manifest: Arc<CatalogContentManifest>,
|
||||||
@@ -1346,7 +1346,7 @@ fn promote_streamed_install(
|
|||||||
|
|
||||||
fn spawn_install_operation(
|
fn spawn_install_operation(
|
||||||
ctx: &Ctx,
|
ctx: &Ctx,
|
||||||
tx_notify_ui: &UnboundedSender<PeerEvent>,
|
tx_notify_ui: &PeerEventSender,
|
||||||
target: OperationTarget,
|
target: OperationTarget,
|
||||||
) {
|
) {
|
||||||
let ctx = ctx.clone();
|
let ctx = ctx.clone();
|
||||||
@@ -1358,7 +1358,7 @@ fn spawn_install_operation(
|
|||||||
|
|
||||||
async fn run_install_operation(
|
async fn run_install_operation(
|
||||||
ctx: &Ctx,
|
ctx: &Ctx,
|
||||||
tx_notify_ui: &UnboundedSender<PeerEvent>,
|
tx_notify_ui: &PeerEventSender,
|
||||||
target: OperationTarget,
|
target: OperationTarget,
|
||||||
) {
|
) {
|
||||||
let id = target.game_id().to_owned();
|
let id = target.game_id().to_owned();
|
||||||
@@ -1442,7 +1442,7 @@ struct PreparedInstallOperation {
|
|||||||
|
|
||||||
async fn prepare_install_operation(
|
async fn prepare_install_operation(
|
||||||
ctx: &Ctx,
|
ctx: &Ctx,
|
||||||
tx_notify_ui: &UnboundedSender<PeerEvent>,
|
tx_notify_ui: &PeerEventSender,
|
||||||
target: &OperationTarget,
|
target: &OperationTarget,
|
||||||
) -> Option<PreparedInstallOperation> {
|
) -> Option<PreparedInstallOperation> {
|
||||||
let id = target.game_id();
|
let id = target.game_id();
|
||||||
@@ -1499,7 +1499,7 @@ async fn prepare_install_operation(
|
|||||||
|
|
||||||
async fn revalidate_install_operation(
|
async fn revalidate_install_operation(
|
||||||
ctx: &Ctx,
|
ctx: &Ctx,
|
||||||
tx_notify_ui: &UnboundedSender<PeerEvent>,
|
tx_notify_ui: &PeerEventSender,
|
||||||
target: &OperationTarget,
|
target: &OperationTarget,
|
||||||
expected_kind: OperationKind,
|
expected_kind: OperationKind,
|
||||||
) -> Option<PreparedInstallOperation> {
|
) -> Option<PreparedInstallOperation> {
|
||||||
@@ -1522,7 +1522,7 @@ async fn revalidate_install_operation(
|
|||||||
|
|
||||||
async fn run_started_install_operation(
|
async fn run_started_install_operation(
|
||||||
ctx: &Ctx,
|
ctx: &Ctx,
|
||||||
tx_notify_ui: &UnboundedSender<PeerEvent>,
|
tx_notify_ui: &PeerEventSender,
|
||||||
target: OperationTarget,
|
target: OperationTarget,
|
||||||
prepared: PreparedInstallOperation,
|
prepared: PreparedInstallOperation,
|
||||||
operation_guard: OperationGuard,
|
operation_guard: OperationGuard,
|
||||||
@@ -1599,7 +1599,7 @@ async fn run_started_install_operation(
|
|||||||
|
|
||||||
async fn run_uninstall_operation(
|
async fn run_uninstall_operation(
|
||||||
ctx: &Ctx,
|
ctx: &Ctx,
|
||||||
tx_notify_ui: &UnboundedSender<PeerEvent>,
|
tx_notify_ui: &PeerEventSender,
|
||||||
target: OperationTarget,
|
target: OperationTarget,
|
||||||
) {
|
) {
|
||||||
let id = target.game_id().to_owned();
|
let id = target.game_id().to_owned();
|
||||||
@@ -1696,7 +1696,7 @@ async fn run_uninstall_operation(
|
|||||||
|
|
||||||
async fn run_remove_downloaded_operation(
|
async fn run_remove_downloaded_operation(
|
||||||
ctx: &Ctx,
|
ctx: &Ctx,
|
||||||
tx_notify_ui: &UnboundedSender<PeerEvent>,
|
tx_notify_ui: &PeerEventSender,
|
||||||
target: OperationTarget,
|
target: OperationTarget,
|
||||||
) {
|
) {
|
||||||
let id = target.game_id().to_owned();
|
let id = target.game_id().to_owned();
|
||||||
@@ -1804,7 +1804,7 @@ enum BeginOperationResult {
|
|||||||
|
|
||||||
async fn begin_operation(
|
async fn begin_operation(
|
||||||
ctx: &Ctx,
|
ctx: &Ctx,
|
||||||
tx_notify_ui: &UnboundedSender<PeerEvent>,
|
tx_notify_ui: &PeerEventSender,
|
||||||
target: &OperationTarget,
|
target: &OperationTarget,
|
||||||
operation: OperationKind,
|
operation: OperationKind,
|
||||||
) -> BeginOperationResult {
|
) -> BeginOperationResult {
|
||||||
@@ -1820,7 +1820,7 @@ async fn begin_operation(
|
|||||||
|
|
||||||
async fn begin_operation_with_drain_timeout(
|
async fn begin_operation_with_drain_timeout(
|
||||||
ctx: &Ctx,
|
ctx: &Ctx,
|
||||||
tx_notify_ui: &UnboundedSender<PeerEvent>,
|
tx_notify_ui: &PeerEventSender,
|
||||||
target: &OperationTarget,
|
target: &OperationTarget,
|
||||||
operation: OperationKind,
|
operation: OperationKind,
|
||||||
drain_timeout: Duration,
|
drain_timeout: Duration,
|
||||||
@@ -2009,7 +2009,7 @@ async fn cancel_and_wait_for_outbound_transfers(
|
|||||||
|
|
||||||
async fn transition_download_to_install(
|
async fn transition_download_to_install(
|
||||||
ctx: &Ctx,
|
ctx: &Ctx,
|
||||||
tx_notify_ui: &UnboundedSender<PeerEvent>,
|
tx_notify_ui: &PeerEventSender,
|
||||||
id: &str,
|
id: &str,
|
||||||
operation: OperationKind,
|
operation: OperationKind,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
@@ -2042,7 +2042,7 @@ async fn transition_download_to_install(
|
|||||||
transitioned
|
transitioned
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn end_operation(ctx: &Ctx, tx_notify_ui: &UnboundedSender<PeerEvent>, id: &str) {
|
async fn end_operation(ctx: &Ctx, tx_notify_ui: &PeerEventSender, id: &str) {
|
||||||
if ctx.active_operations.write().await.remove(id).is_some() {
|
if ctx.active_operations.write().await.remove(id).is_some() {
|
||||||
events::emit_active_operations(&ctx.active_operations, tx_notify_ui).await;
|
events::emit_active_operations(&ctx.active_operations, tx_notify_ui).await;
|
||||||
}
|
}
|
||||||
@@ -2059,7 +2059,7 @@ async fn clear_active_download(ctx: &Ctx, attempt: &DownloadAttemptKey) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn send_download_failed(
|
fn send_download_failed(
|
||||||
tx_notify_ui: &UnboundedSender<PeerEvent>,
|
tx_notify_ui: &PeerEventSender,
|
||||||
attempt: &DownloadAttemptKey,
|
attempt: &DownloadAttemptKey,
|
||||||
reason: DownloadFailureReason,
|
reason: DownloadFailureReason,
|
||||||
) {
|
) {
|
||||||
@@ -2074,7 +2074,7 @@ fn send_download_failed(
|
|||||||
|
|
||||||
async fn end_download_operation(
|
async fn end_download_operation(
|
||||||
ctx: &Ctx,
|
ctx: &Ctx,
|
||||||
tx_notify_ui: &UnboundedSender<PeerEvent>,
|
tx_notify_ui: &PeerEventSender,
|
||||||
attempt: &DownloadAttemptKey,
|
attempt: &DownloadAttemptKey,
|
||||||
) {
|
) {
|
||||||
clear_active_download(ctx, attempt).await;
|
clear_active_download(ctx, attempt).await;
|
||||||
@@ -2083,7 +2083,7 @@ async fn end_download_operation(
|
|||||||
|
|
||||||
async fn settle_target_state(
|
async fn settle_target_state(
|
||||||
ctx: &Ctx,
|
ctx: &Ctx,
|
||||||
tx_notify_ui: &UnboundedSender<PeerEvent>,
|
tx_notify_ui: &PeerEventSender,
|
||||||
target: &OperationTarget,
|
target: &OperationTarget,
|
||||||
) -> eyre::Result<()> {
|
) -> eyre::Result<()> {
|
||||||
install::recover_game_root(
|
install::recover_game_root(
|
||||||
@@ -2097,7 +2097,7 @@ async fn settle_target_state(
|
|||||||
|
|
||||||
async fn settle_and_end_operation(
|
async fn settle_and_end_operation(
|
||||||
ctx: &Ctx,
|
ctx: &Ctx,
|
||||||
tx_notify_ui: &UnboundedSender<PeerEvent>,
|
tx_notify_ui: &PeerEventSender,
|
||||||
target: &OperationTarget,
|
target: &OperationTarget,
|
||||||
guard: OperationGuard,
|
guard: OperationGuard,
|
||||||
label: &str,
|
label: &str,
|
||||||
@@ -2121,7 +2121,7 @@ async fn settle_and_end_operation(
|
|||||||
|
|
||||||
async fn settle_and_end_download(
|
async fn settle_and_end_download(
|
||||||
ctx: &Ctx,
|
ctx: &Ctx,
|
||||||
tx_notify_ui: &UnboundedSender<PeerEvent>,
|
tx_notify_ui: &PeerEventSender,
|
||||||
target: &OperationTarget,
|
target: &OperationTarget,
|
||||||
guard: OperationGuard,
|
guard: OperationGuard,
|
||||||
attempt: &DownloadAttemptKey,
|
attempt: &DownloadAttemptKey,
|
||||||
@@ -2142,7 +2142,7 @@ async fn settle_and_end_download(
|
|||||||
|
|
||||||
async fn finish_successful_download_after_refresh(
|
async fn finish_successful_download_after_refresh(
|
||||||
ctx: &Ctx,
|
ctx: &Ctx,
|
||||||
tx_notify_ui: &UnboundedSender<PeerEvent>,
|
tx_notify_ui: &PeerEventSender,
|
||||||
target: &OperationTarget,
|
target: &OperationTarget,
|
||||||
guard: OperationGuard,
|
guard: OperationGuard,
|
||||||
status: &DownloadAttemptStatus,
|
status: &DownloadAttemptStatus,
|
||||||
@@ -2164,7 +2164,7 @@ async fn finish_successful_download_after_refresh(
|
|||||||
|
|
||||||
async fn finish_failed_download_after_refresh(
|
async fn finish_failed_download_after_refresh(
|
||||||
ctx: &Ctx,
|
ctx: &Ctx,
|
||||||
tx_notify_ui: &UnboundedSender<PeerEvent>,
|
tx_notify_ui: &PeerEventSender,
|
||||||
target: &OperationTarget,
|
target: &OperationTarget,
|
||||||
guard: OperationGuard,
|
guard: OperationGuard,
|
||||||
status: &DownloadAttemptStatus,
|
status: &DownloadAttemptStatus,
|
||||||
@@ -2193,7 +2193,7 @@ fn catalog_contains(ctx: &Ctx, id: &str) -> bool {
|
|||||||
|
|
||||||
async fn begin_local_recovery(
|
async fn begin_local_recovery(
|
||||||
ctx: &Ctx,
|
ctx: &Ctx,
|
||||||
tx_notify_ui: &UnboundedSender<PeerEvent>,
|
tx_notify_ui: &PeerEventSender,
|
||||||
game_dir: &Path,
|
game_dir: &Path,
|
||||||
force_empty_snapshot: bool,
|
force_empty_snapshot: bool,
|
||||||
) -> eyre::Result<()> {
|
) -> eyre::Result<()> {
|
||||||
@@ -2225,7 +2225,7 @@ async fn begin_local_recovery(
|
|||||||
/// quarantined. `Err` is returned only before changing the configured root.
|
/// quarantined. `Err` is returned only before changing the configured root.
|
||||||
pub async fn handle_set_game_dir_command(
|
pub async fn handle_set_game_dir_command(
|
||||||
ctx: &Ctx,
|
ctx: &Ctx,
|
||||||
tx_notify_ui: &UnboundedSender<PeerEvent>,
|
tx_notify_ui: &PeerEventSender,
|
||||||
game_dir: PathBuf,
|
game_dir: PathBuf,
|
||||||
) -> Result<PathBuf, String> {
|
) -> Result<PathBuf, String> {
|
||||||
handle_set_game_dir_command_with_drain_timeout(
|
handle_set_game_dir_command_with_drain_timeout(
|
||||||
@@ -2239,7 +2239,7 @@ pub async fn handle_set_game_dir_command(
|
|||||||
|
|
||||||
async fn handle_set_game_dir_command_with_drain_timeout(
|
async fn handle_set_game_dir_command_with_drain_timeout(
|
||||||
ctx: &Ctx,
|
ctx: &Ctx,
|
||||||
tx_notify_ui: &UnboundedSender<PeerEvent>,
|
tx_notify_ui: &PeerEventSender,
|
||||||
requested_game_dir: PathBuf,
|
requested_game_dir: PathBuf,
|
||||||
drain_timeout: Duration,
|
drain_timeout: Duration,
|
||||||
) -> Result<PathBuf, String> {
|
) -> Result<PathBuf, String> {
|
||||||
@@ -2344,14 +2344,14 @@ async fn handle_set_game_dir_command_with_drain_timeout(
|
|||||||
/// Loads the configured local library and announces the result.
|
/// Loads the configured local library and announces the result.
|
||||||
pub async fn load_local_library(
|
pub async fn load_local_library(
|
||||||
ctx: &Ctx,
|
ctx: &Ctx,
|
||||||
tx_notify_ui: &UnboundedSender<PeerEvent>,
|
tx_notify_ui: &PeerEventSender,
|
||||||
) -> eyre::Result<()> {
|
) -> eyre::Result<()> {
|
||||||
load_local_library_with_policy(ctx, tx_notify_ui, LocalLibraryEventPolicy::OnChange).await
|
load_local_library_with_policy(ctx, tx_notify_ui, LocalLibraryEventPolicy::OnChange).await
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn load_local_library_with_policy(
|
async fn load_local_library_with_policy(
|
||||||
ctx: &Ctx,
|
ctx: &Ctx,
|
||||||
tx_notify_ui: &UnboundedSender<PeerEvent>,
|
tx_notify_ui: &PeerEventSender,
|
||||||
event_policy: LocalLibraryEventPolicy,
|
event_policy: LocalLibraryEventPolicy,
|
||||||
) -> eyre::Result<()> {
|
) -> eyre::Result<()> {
|
||||||
let game_dir = { ctx.game_dir.read().await.clone() };
|
let game_dir = { ctx.game_dir.read().await.clone() };
|
||||||
@@ -2385,7 +2385,7 @@ async fn load_local_library_with_policy(
|
|||||||
|
|
||||||
async fn scan_and_announce_local_library(
|
async fn scan_and_announce_local_library(
|
||||||
ctx: &Ctx,
|
ctx: &Ctx,
|
||||||
tx_notify_ui: &UnboundedSender<PeerEvent>,
|
tx_notify_ui: &PeerEventSender,
|
||||||
game_dir: &Path,
|
game_dir: &Path,
|
||||||
event_policy: LocalLibraryEventPolicy,
|
event_policy: LocalLibraryEventPolicy,
|
||||||
recovery_failed_ids: &HashSet<String>,
|
recovery_failed_ids: &HashSet<String>,
|
||||||
@@ -2410,7 +2410,7 @@ async fn scan_and_announce_local_library(
|
|||||||
/// active-operation snapshot, while preserving freeze behavior for other games.
|
/// active-operation snapshot, while preserving freeze behavior for other games.
|
||||||
async fn refresh_local_game_for_ending_operation(
|
async fn refresh_local_game_for_ending_operation(
|
||||||
ctx: &Ctx,
|
ctx: &Ctx,
|
||||||
tx_notify_ui: &UnboundedSender<PeerEvent>,
|
tx_notify_ui: &PeerEventSender,
|
||||||
target: &OperationTarget,
|
target: &OperationTarget,
|
||||||
) -> eyre::Result<()> {
|
) -> eyre::Result<()> {
|
||||||
let catalog = ctx.catalog.catalog();
|
let catalog = ctx.catalog.catalog();
|
||||||
@@ -2456,7 +2456,7 @@ async fn active_operation_ids(ctx: &Ctx) -> HashSet<String> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Handles the `GetPeerCount` command.
|
/// Handles the `GetPeerCount` command.
|
||||||
pub async fn handle_get_peer_count_command(ctx: &Ctx, tx_notify_ui: &UnboundedSender<PeerEvent>) {
|
pub async fn handle_get_peer_count_command(ctx: &Ctx, tx_notify_ui: &PeerEventSender) {
|
||||||
log::info!("GetPeerCount command received");
|
log::info!("GetPeerCount command received");
|
||||||
events::emit_peer_count(&ctx.peer_game_db, tx_notify_ui).await;
|
events::emit_peer_count(&ctx.peer_game_db, tx_notify_ui).await;
|
||||||
}
|
}
|
||||||
@@ -2464,7 +2464,7 @@ pub async fn handle_get_peer_count_command(ctx: &Ctx, tx_notify_ui: &UnboundedSe
|
|||||||
/// Connects to a peer directly, bypassing mDNS discovery.
|
/// Connects to a peer directly, bypassing mDNS discovery.
|
||||||
pub async fn handle_connect_peer_command(
|
pub async fn handle_connect_peer_command(
|
||||||
ctx: &Ctx,
|
ctx: &Ctx,
|
||||||
tx_notify_ui: &UnboundedSender<PeerEvent>,
|
tx_notify_ui: &PeerEventSender,
|
||||||
endpoint: PeerEndpoint,
|
endpoint: PeerEndpoint,
|
||||||
) {
|
) {
|
||||||
log::info!("Direct connect command received for {}", endpoint.addr);
|
log::info!("Direct connect command received for {}", endpoint.addr);
|
||||||
@@ -2509,7 +2509,7 @@ pub async fn handle_connect_peer_command(
|
|||||||
/// Updates the local game database and announces changes to peers.
|
/// Updates the local game database and announces changes to peers.
|
||||||
pub async fn update_and_announce_games(
|
pub async fn update_and_announce_games(
|
||||||
ctx: &Ctx,
|
ctx: &Ctx,
|
||||||
tx_notify_ui: &UnboundedSender<PeerEvent>,
|
tx_notify_ui: &PeerEventSender,
|
||||||
scan: LocalLibraryScan,
|
scan: LocalLibraryScan,
|
||||||
) {
|
) {
|
||||||
let _ = update_and_announce_games_with_policy(
|
let _ = update_and_announce_games_with_policy(
|
||||||
@@ -2524,7 +2524,7 @@ pub async fn update_and_announce_games(
|
|||||||
|
|
||||||
async fn update_and_announce_games_with_policy(
|
async fn update_and_announce_games_with_policy(
|
||||||
ctx: &Ctx,
|
ctx: &Ctx,
|
||||||
tx_notify_ui: &UnboundedSender<PeerEvent>,
|
tx_notify_ui: &PeerEventSender,
|
||||||
scan: LocalLibraryScan,
|
scan: LocalLibraryScan,
|
||||||
event_policy: LocalLibraryEventPolicy,
|
event_policy: LocalLibraryEventPolicy,
|
||||||
ending_operation_id: Option<&str>,
|
ending_operation_id: Option<&str>,
|
||||||
@@ -2659,7 +2659,7 @@ mod tests {
|
|||||||
PeerId,
|
PeerId,
|
||||||
RuntimeSessionId,
|
RuntimeSessionId,
|
||||||
};
|
};
|
||||||
use tokio::sync::{RwLock, mpsc, oneshot};
|
use tokio::sync::{RwLock, oneshot};
|
||||||
use tokio_util::{sync::CancellationToken, task::TaskTracker};
|
use tokio_util::{sync::CancellationToken, task::TaskTracker};
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
@@ -2834,7 +2834,7 @@ mod tests {
|
|||||||
|
|
||||||
async fn register_test_download(
|
async fn register_test_download(
|
||||||
ctx: &Ctx,
|
ctx: &Ctx,
|
||||||
tx_notify_ui: &UnboundedSender<PeerEvent>,
|
tx_notify_ui: &PeerEventSender,
|
||||||
) -> (DownloadAttemptStatus, CancellationToken) {
|
) -> (DownloadAttemptStatus, CancellationToken) {
|
||||||
let cancellation = CancellationToken::new();
|
let cancellation = CancellationToken::new();
|
||||||
let status = register_download_attempt(
|
let status = register_download_attempt(
|
||||||
@@ -2868,7 +2868,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn cancelled_download_owner_does_not_emit_failed_event() {
|
fn cancelled_download_owner_does_not_emit_failed_event() {
|
||||||
let (tx, mut rx) = mpsc::unbounded_channel();
|
let (tx, mut rx) = crate::peer_event_channel();
|
||||||
let status = DownloadAttemptStatus::new(
|
let status = DownloadAttemptStatus::new(
|
||||||
DownloadAttemptKey::next("game".to_owned()),
|
DownloadAttemptKey::next("game".to_owned()),
|
||||||
CancellationToken::new(),
|
CancellationToken::new(),
|
||||||
@@ -2885,7 +2885,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn uncancelled_download_error_emits_failed_event() {
|
fn uncancelled_download_error_emits_failed_event() {
|
||||||
let (tx, mut rx) = mpsc::unbounded_channel();
|
let (tx, mut rx) = crate::peer_event_channel();
|
||||||
let attempt = DownloadAttemptKey::next("game".to_owned());
|
let attempt = DownloadAttemptKey::next("game".to_owned());
|
||||||
|
|
||||||
send_download_failed(&tx, &attempt, DownloadFailureReason::OperationFailed);
|
send_download_failed(&tx, &attempt, DownloadFailureReason::OperationFailed);
|
||||||
@@ -2959,7 +2959,7 @@ mod tests {
|
|||||||
);
|
);
|
||||||
seed_exact_download_ownership(&ctx, games.path(), content_id).await;
|
seed_exact_download_ownership(&ctx, games.path(), content_id).await;
|
||||||
let peer_db = ctx.peer_game_db.write().await;
|
let peer_db = ctx.peer_game_db.write().await;
|
||||||
let (tx, mut rx) = mpsc::unbounded_channel();
|
let (tx, mut rx) = crate::peer_event_channel();
|
||||||
|
|
||||||
tokio::time::timeout(
|
tokio::time::timeout(
|
||||||
Duration::from_secs(1),
|
Duration::from_secs(1),
|
||||||
@@ -2993,7 +2993,7 @@ mod tests {
|
|||||||
NetworkControl::disabled_for_test(),
|
NetworkControl::disabled_for_test(),
|
||||||
);
|
);
|
||||||
let peer_db = ctx.peer_game_db.write().await;
|
let peer_db = ctx.peer_game_db.write().await;
|
||||||
let (tx, mut rx) = mpsc::unbounded_channel();
|
let (tx, mut rx) = crate::peer_event_channel();
|
||||||
|
|
||||||
tokio::time::timeout(
|
tokio::time::timeout(
|
||||||
Duration::from_secs(1),
|
Duration::from_secs(1),
|
||||||
@@ -3033,7 +3033,7 @@ mod tests {
|
|||||||
NetworkControl::disabled_for_test(),
|
NetworkControl::disabled_for_test(),
|
||||||
);
|
);
|
||||||
let peer_db = ctx.peer_game_db.write().await;
|
let peer_db = ctx.peer_game_db.write().await;
|
||||||
let (tx, mut rx) = mpsc::unbounded_channel();
|
let (tx, mut rx) = crate::peer_event_channel();
|
||||||
|
|
||||||
tokio::time::timeout(
|
tokio::time::timeout(
|
||||||
Duration::from_secs(1),
|
Duration::from_secs(1),
|
||||||
@@ -3070,7 +3070,7 @@ mod tests {
|
|||||||
NetworkControl::disabled_for_test(),
|
NetworkControl::disabled_for_test(),
|
||||||
);
|
);
|
||||||
let peer_db = ctx.peer_game_db.write().await;
|
let peer_db = ctx.peer_game_db.write().await;
|
||||||
let (tx, mut rx) = mpsc::unbounded_channel();
|
let (tx, mut rx) = crate::peer_event_channel();
|
||||||
|
|
||||||
tokio::time::timeout(
|
tokio::time::timeout(
|
||||||
Duration::from_secs(1),
|
Duration::from_secs(1),
|
||||||
@@ -3100,7 +3100,7 @@ mod tests {
|
|||||||
.current_publication()
|
.current_publication()
|
||||||
.expect("initial Call-to-Play projection should load")
|
.expect("initial Call-to-Play projection should load")
|
||||||
.view;
|
.view;
|
||||||
let (tx, mut rx) = mpsc::unbounded_channel();
|
let (tx, mut rx) = crate::peer_event_channel();
|
||||||
let (reply_tx, reply_rx) = oneshot::channel();
|
let (reply_tx, reply_rx) = oneshot::channel();
|
||||||
|
|
||||||
tokio::time::timeout(
|
tokio::time::timeout(
|
||||||
@@ -3136,7 +3136,7 @@ mod tests {
|
|||||||
let games = TempDir::new("lanspread-handler-enabled-call-to-play");
|
let games = TempDir::new("lanspread-handler-enabled-call-to-play");
|
||||||
let ctx = test_ctx(games.path().to_path_buf());
|
let ctx = test_ctx(games.path().to_path_buf());
|
||||||
let store = ctx.call_to_play.write().await;
|
let store = ctx.call_to_play.write().await;
|
||||||
let (tx, mut rx) = mpsc::unbounded_channel();
|
let (tx, mut rx) = crate::peer_event_channel();
|
||||||
let (reply_tx, mut reply_rx) = oneshot::channel();
|
let (reply_tx, mut reply_rx) = oneshot::channel();
|
||||||
let task = tokio::spawn({
|
let task = tokio::spawn({
|
||||||
let ctx = ctx.clone();
|
let ctx = ctx.clone();
|
||||||
@@ -3177,7 +3177,7 @@ mod tests {
|
|||||||
async fn streamed_install_without_extracted_catalog_manifest_fails_before_admission() {
|
async fn streamed_install_without_extracted_catalog_manifest_fails_before_admission() {
|
||||||
let games = TempDir::new("lanspread-handler-stream-capability");
|
let games = TempDir::new("lanspread-handler-stream-capability");
|
||||||
let ctx = test_ctx(games.path().to_path_buf());
|
let ctx = test_ctx(games.path().to_path_buf());
|
||||||
let (tx, mut rx) = mpsc::unbounded_channel();
|
let (tx, mut rx) = crate::peer_event_channel();
|
||||||
|
|
||||||
handle_stream_install_game_command(
|
handle_stream_install_game_command(
|
||||||
&ctx,
|
&ctx,
|
||||||
@@ -3226,7 +3226,7 @@ mod tests {
|
|||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
);
|
);
|
||||||
let (tx, mut rx) = mpsc::unbounded_channel();
|
let (tx, mut rx) = crate::peer_event_channel();
|
||||||
|
|
||||||
handle_download_game_files_command(&ctx, &tx, "game".to_string(), false).await;
|
handle_download_game_files_command(&ctx, &tx, "game".to_string(), false).await;
|
||||||
|
|
||||||
@@ -3264,7 +3264,7 @@ mod tests {
|
|||||||
.write()
|
.write()
|
||||||
.await
|
.await
|
||||||
.insert("game".to_owned(), OperationKind::Downloading);
|
.insert("game".to_owned(), OperationKind::Downloading);
|
||||||
let (tx, mut rx) = mpsc::unbounded_channel();
|
let (tx, mut rx) = crate::peer_event_channel();
|
||||||
|
|
||||||
handle_download_game_files_command(&ctx, &tx, "game".to_owned(), false).await;
|
handle_download_game_files_command(&ctx, &tx, "game".to_owned(), false).await;
|
||||||
|
|
||||||
@@ -3401,7 +3401,7 @@ mod tests {
|
|||||||
games.path().to_path_buf(),
|
games.path().to_path_buf(),
|
||||||
catalog_bundle([("broken", "20250101"), ("healthy", "20250101")]),
|
catalog_bundle([("broken", "20250101"), ("healthy", "20250101")]),
|
||||||
);
|
);
|
||||||
let (tx, _rx) = mpsc::unbounded_channel();
|
let (tx, _rx) = crate::peer_event_channel();
|
||||||
|
|
||||||
let error = load_local_library(&ctx, &tx)
|
let error = load_local_library(&ctx, &tx)
|
||||||
.await
|
.await
|
||||||
@@ -3445,7 +3445,7 @@ mod tests {
|
|||||||
.expect("game-root symlink should be created");
|
.expect("game-root symlink should be created");
|
||||||
|
|
||||||
let ctx = test_ctx(games.path().to_path_buf());
|
let ctx = test_ctx(games.path().to_path_buf());
|
||||||
let (tx, _rx) = mpsc::unbounded_channel();
|
let (tx, _rx) = crate::peer_event_channel();
|
||||||
|
|
||||||
let error = load_local_library(&ctx, &tx)
|
let error = load_local_library(&ctx, &tx)
|
||||||
.await
|
.await
|
||||||
@@ -3470,7 +3470,7 @@ mod tests {
|
|||||||
async fn operation_admission_rejects_recovering_and_failed_games() {
|
async fn operation_admission_rejects_recovering_and_failed_games() {
|
||||||
let games = TempDir::new("lanspread-handler-recovery-admission");
|
let games = TempDir::new("lanspread-handler-recovery-admission");
|
||||||
let ctx = test_ctx(games.path().to_path_buf());
|
let ctx = test_ctx(games.path().to_path_buf());
|
||||||
let (tx, _rx) = mpsc::unbounded_channel();
|
let (tx, _rx) = crate::peer_event_channel();
|
||||||
|
|
||||||
ctx.recovery_quarantine.begin(games.path().to_path_buf());
|
ctx.recovery_quarantine.begin(games.path().to_path_buf());
|
||||||
let target = operation_target(games.path());
|
let target = operation_target(games.path());
|
||||||
@@ -3496,7 +3496,7 @@ mod tests {
|
|||||||
let games = TempDir::new("lanspread-handler-download-refresh-failure");
|
let games = TempDir::new("lanspread-handler-download-refresh-failure");
|
||||||
write_file(&games.game_root(), b"not a directory");
|
write_file(&games.game_root(), b"not a directory");
|
||||||
let ctx = test_ctx(games.path().to_path_buf());
|
let ctx = test_ctx(games.path().to_path_buf());
|
||||||
let (tx, mut rx) = mpsc::unbounded_channel();
|
let (tx, mut rx) = crate::peer_event_channel();
|
||||||
let (status, cancel) = register_test_download(&ctx, &tx).await;
|
let (status, cancel) = register_test_download(&ctx, &tx).await;
|
||||||
ctx.active_operations
|
ctx.active_operations
|
||||||
.write()
|
.write()
|
||||||
@@ -3519,14 +3519,14 @@ mod tests {
|
|||||||
assert_no_event(&mut rx).await;
|
assert_no_event(&mut rx).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn recv_event(rx: &mut mpsc::UnboundedReceiver<PeerEvent>) -> PeerEvent {
|
async fn recv_event(rx: &mut crate::PeerEventReceiver) -> PeerEvent {
|
||||||
tokio::time::timeout(Duration::from_secs(1), rx.recv())
|
tokio::time::timeout(Duration::from_secs(1), rx.recv())
|
||||||
.await
|
.await
|
||||||
.expect("event should arrive")
|
.expect("event should arrive")
|
||||||
.expect("event channel should remain open")
|
.expect("event channel should remain open")
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn assert_no_event(rx: &mut mpsc::UnboundedReceiver<PeerEvent>) {
|
async fn assert_no_event(rx: &mut crate::PeerEventReceiver) {
|
||||||
assert!(
|
assert!(
|
||||||
tokio::time::timeout(Duration::from_millis(50), rx.recv())
|
tokio::time::timeout(Duration::from_millis(50), rx.recv())
|
||||||
.await
|
.await
|
||||||
@@ -3535,7 +3535,7 @@ mod tests {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn drain_events(rx: &mut mpsc::UnboundedReceiver<PeerEvent>) -> Vec<PeerEvent> {
|
fn drain_events(rx: &mut crate::PeerEventReceiver) -> Vec<PeerEvent> {
|
||||||
let mut events = Vec::new();
|
let mut events = Vec::new();
|
||||||
while let Ok(event) = rx.try_recv() {
|
while let Ok(event) = rx.try_recv() {
|
||||||
events.push(event);
|
events.push(event);
|
||||||
@@ -3682,7 +3682,7 @@ mod tests {
|
|||||||
let state = TempDir::new("lanspread-handler-stream-retry-setup-state");
|
let state = TempDir::new("lanspread-handler-stream-retry-setup-state");
|
||||||
std::fs::create_dir_all(games.game_root().join("local"))
|
std::fs::create_dir_all(games.game_root().join("local"))
|
||||||
.expect("installed tree should be created");
|
.expect("installed tree should be created");
|
||||||
let (tx, mut rx) = mpsc::unbounded_channel();
|
let (tx, mut rx) = crate::peer_event_channel();
|
||||||
let status = DownloadAttemptStatus::new(
|
let status = DownloadAttemptStatus::new(
|
||||||
DownloadAttemptKey::next("game".to_owned()),
|
DownloadAttemptKey::next("game".to_owned()),
|
||||||
CancellationToken::new(),
|
CancellationToken::new(),
|
||||||
@@ -3719,7 +3719,7 @@ mod tests {
|
|||||||
write_file(&root.join("game.eti"), b"archive");
|
write_file(&root.join("game.eti"), b"archive");
|
||||||
|
|
||||||
let ctx = test_ctx(temp.path().to_path_buf());
|
let ctx = test_ctx(temp.path().to_path_buf());
|
||||||
let (tx, mut rx) = mpsc::unbounded_channel();
|
let (tx, mut rx) = crate::peer_event_channel();
|
||||||
let catalog = ctx.catalog.catalog();
|
let catalog = ctx.catalog.catalog();
|
||||||
|
|
||||||
// 1. Initial scan: the game is ready and announced
|
// 1. Initial scan: the game is ready and announced
|
||||||
@@ -3845,7 +3845,7 @@ mod tests {
|
|||||||
.expect("catalog construction should defer manifest body parsing"),
|
.expect("catalog construction should defer manifest body parsing"),
|
||||||
);
|
);
|
||||||
let ctx = test_ctx_with_catalog(games.path().to_path_buf(), catalog);
|
let ctx = test_ctx_with_catalog(games.path().to_path_buf(), catalog);
|
||||||
let (tx, mut rx) = mpsc::unbounded_channel();
|
let (tx, mut rx) = crate::peer_event_channel();
|
||||||
let scan = scan_local_library(games.path(), ctx.state_dir.as_ref(), ctx.catalog.catalog())
|
let scan = scan_local_library(games.path(), ctx.state_dir.as_ref(), ctx.catalog.catalog())
|
||||||
.await
|
.await
|
||||||
.expect("ready game should scan before manifest priming");
|
.expect("ready game should scan before manifest priming");
|
||||||
@@ -3884,7 +3884,7 @@ mod tests {
|
|||||||
);
|
);
|
||||||
|
|
||||||
let ctx = test_ctx(current.path().to_path_buf());
|
let ctx = test_ctx(current.path().to_path_buf());
|
||||||
let (tx, mut rx) = mpsc::unbounded_channel();
|
let (tx, mut rx) = crate::peer_event_channel();
|
||||||
let catalog = ctx.catalog.catalog();
|
let catalog = ctx.catalog.catalog();
|
||||||
|
|
||||||
let current_scan = scan_local_library(current.path(), ctx.state_dir.as_ref(), catalog)
|
let current_scan = scan_local_library(current.path(), ctx.state_dir.as_ref(), catalog)
|
||||||
@@ -3921,7 +3921,7 @@ mod tests {
|
|||||||
write_file(&root.join("game.eti"), b"archive");
|
write_file(&root.join("game.eti"), b"archive");
|
||||||
|
|
||||||
let ctx = test_ctx(temp.path().to_path_buf());
|
let ctx = test_ctx(temp.path().to_path_buf());
|
||||||
let (tx, mut rx) = mpsc::unbounded_channel();
|
let (tx, mut rx) = crate::peer_event_channel();
|
||||||
let catalog = ctx.catalog.catalog();
|
let catalog = ctx.catalog.catalog();
|
||||||
let older_scan = scan_local_library(temp.path(), ctx.state_dir.as_ref(), catalog)
|
let older_scan = scan_local_library(temp.path(), ctx.state_dir.as_ref(), catalog)
|
||||||
.await
|
.await
|
||||||
@@ -3973,7 +3973,7 @@ mod tests {
|
|||||||
write_file(&root.join("game.eti"), b"archive");
|
write_file(&root.join("game.eti"), b"archive");
|
||||||
|
|
||||||
let ctx = test_ctx(temp.path().to_path_buf());
|
let ctx = test_ctx(temp.path().to_path_buf());
|
||||||
let (tx, mut rx) = mpsc::unbounded_channel();
|
let (tx, mut rx) = crate::peer_event_channel();
|
||||||
let target = operation_target(temp.path());
|
let target = operation_target(temp.path());
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
@@ -3997,7 +3997,7 @@ mod tests {
|
|||||||
write_file(&root.join("game.eti"), b"archive");
|
write_file(&root.join("game.eti"), b"archive");
|
||||||
|
|
||||||
let ctx = test_ctx(temp.path().to_path_buf());
|
let ctx = test_ctx(temp.path().to_path_buf());
|
||||||
let (tx, mut rx) = mpsc::unbounded_channel();
|
let (tx, mut rx) = crate::peer_event_channel();
|
||||||
let scan = scan_local_library(temp.path(), ctx.state_dir.as_ref(), ctx.catalog.catalog())
|
let scan = scan_local_library(temp.path(), ctx.state_dir.as_ref(), ctx.catalog.catalog())
|
||||||
.await
|
.await
|
||||||
.expect("initial scan should succeed");
|
.expect("initial scan should succeed");
|
||||||
@@ -4057,7 +4057,7 @@ mod tests {
|
|||||||
write_file(&root.join("game.eti"), b"archive");
|
write_file(&root.join("game.eti"), b"archive");
|
||||||
|
|
||||||
let ctx = test_ctx(temp.path().to_path_buf());
|
let ctx = test_ctx(temp.path().to_path_buf());
|
||||||
let (tx, mut rx) = mpsc::unbounded_channel();
|
let (tx, mut rx) = crate::peer_event_channel();
|
||||||
let target = operation_target(temp.path());
|
let target = operation_target(temp.path());
|
||||||
let scan = scan_local_library(temp.path(), ctx.state_dir.as_ref(), ctx.catalog.catalog())
|
let scan = scan_local_library(temp.path(), ctx.state_dir.as_ref(), ctx.catalog.catalog())
|
||||||
.await
|
.await
|
||||||
@@ -4122,7 +4122,7 @@ mod tests {
|
|||||||
write_file(&root.join("game.eti"), b"archive");
|
write_file(&root.join("game.eti"), b"archive");
|
||||||
|
|
||||||
let ctx = test_ctx(temp.path().to_path_buf());
|
let ctx = test_ctx(temp.path().to_path_buf());
|
||||||
let (tx, mut rx) = mpsc::unbounded_channel();
|
let (tx, mut rx) = crate::peer_event_channel();
|
||||||
let scan = scan_local_library(temp.path(), ctx.state_dir.as_ref(), ctx.catalog.catalog())
|
let scan = scan_local_library(temp.path(), ctx.state_dir.as_ref(), ctx.catalog.catalog())
|
||||||
.await
|
.await
|
||||||
.expect("initial scan should succeed");
|
.expect("initial scan should succeed");
|
||||||
@@ -4195,7 +4195,7 @@ mod tests {
|
|||||||
write_file(&root.join("game.eti"), b"archive");
|
write_file(&root.join("game.eti"), b"archive");
|
||||||
|
|
||||||
let ctx = test_ctx(temp.path().to_path_buf());
|
let ctx = test_ctx(temp.path().to_path_buf());
|
||||||
let (tx, mut rx) = mpsc::unbounded_channel();
|
let (tx, mut rx) = crate::peer_event_channel();
|
||||||
let catalog = ctx.catalog.catalog();
|
let catalog = ctx.catalog.catalog();
|
||||||
|
|
||||||
let scan = scan_local_library(temp.path(), ctx.state_dir.as_ref(), catalog)
|
let scan = scan_local_library(temp.path(), ctx.state_dir.as_ref(), catalog)
|
||||||
@@ -4221,7 +4221,7 @@ mod tests {
|
|||||||
write_file(&root.join("local").join("old.txt"), b"old");
|
write_file(&root.join("local").join("old.txt"), b"old");
|
||||||
|
|
||||||
let ctx = test_ctx(temp.path().to_path_buf());
|
let ctx = test_ctx(temp.path().to_path_buf());
|
||||||
let (tx, mut rx) = mpsc::unbounded_channel();
|
let (tx, mut rx) = crate::peer_event_channel();
|
||||||
let catalog = ctx.catalog.catalog();
|
let catalog = ctx.catalog.catalog();
|
||||||
let scan = scan_local_library(temp.path(), ctx.state_dir.as_ref(), catalog)
|
let scan = scan_local_library(temp.path(), ctx.state_dir.as_ref(), catalog)
|
||||||
.await
|
.await
|
||||||
@@ -4256,7 +4256,7 @@ mod tests {
|
|||||||
write_file(&root.join("game.eti"), b"archive");
|
write_file(&root.join("game.eti"), b"archive");
|
||||||
|
|
||||||
let ctx = test_ctx(temp.path().to_path_buf());
|
let ctx = test_ctx(temp.path().to_path_buf());
|
||||||
let (tx, mut rx) = mpsc::unbounded_channel();
|
let (tx, mut rx) = crate::peer_event_channel();
|
||||||
|
|
||||||
run_install_operation(&ctx, &tx, operation_target(temp.path())).await;
|
run_install_operation(&ctx, &tx, operation_target(temp.path())).await;
|
||||||
|
|
||||||
@@ -4281,7 +4281,7 @@ mod tests {
|
|||||||
write_file(&root.join("game.eti"), b"archive");
|
write_file(&root.join("game.eti"), b"archive");
|
||||||
let ctx = test_ctx(temp.path().to_path_buf());
|
let ctx = test_ctx(temp.path().to_path_buf());
|
||||||
let target = operation_target(temp.path());
|
let target = operation_target(temp.path());
|
||||||
let (tx, mut rx) = mpsc::unbounded_channel();
|
let (tx, mut rx) = crate::peer_event_channel();
|
||||||
|
|
||||||
let prepared = prepare_install_operation(&ctx, &tx, &target)
|
let prepared = prepare_install_operation(&ctx, &tx, &target)
|
||||||
.await
|
.await
|
||||||
@@ -4334,7 +4334,7 @@ mod tests {
|
|||||||
write_file(&root.join("game.eti"), b"archive");
|
write_file(&root.join("game.eti"), b"archive");
|
||||||
let ctx = test_ctx(temp.path().to_path_buf());
|
let ctx = test_ctx(temp.path().to_path_buf());
|
||||||
let target = operation_target(temp.path());
|
let target = operation_target(temp.path());
|
||||||
let (tx, mut rx) = mpsc::unbounded_channel();
|
let (tx, mut rx) = crate::peer_event_channel();
|
||||||
|
|
||||||
assert!(stream_install_target_is_ready(&ctx, &target).await);
|
assert!(stream_install_target_is_ready(&ctx, &target).await);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
@@ -4383,7 +4383,7 @@ mod tests {
|
|||||||
write_file(&root.join("game.eti"), b"archive");
|
write_file(&root.join("game.eti"), b"archive");
|
||||||
|
|
||||||
let ctx = test_ctx(temp.path().to_path_buf());
|
let ctx = test_ctx(temp.path().to_path_buf());
|
||||||
let (prepare_tx, _prepare_rx) = mpsc::unbounded_channel();
|
let (prepare_tx, _prepare_rx) = crate::peer_event_channel();
|
||||||
let (download_status, _download_cancel) = register_test_download(&ctx, &prepare_tx).await;
|
let (download_status, _download_cancel) = register_test_download(&ctx, &prepare_tx).await;
|
||||||
ctx.active_operations
|
ctx.active_operations
|
||||||
.write()
|
.write()
|
||||||
@@ -4394,7 +4394,7 @@ mod tests {
|
|||||||
.await
|
.await
|
||||||
.expect("downloaded game should be installable");
|
.expect("downloaded game should be installable");
|
||||||
let read_guard = ctx.active_operations.read().await;
|
let read_guard = ctx.active_operations.read().await;
|
||||||
let (tx, mut rx) = mpsc::unbounded_channel();
|
let (tx, mut rx) = crate::peer_event_channel();
|
||||||
|
|
||||||
let install_task = tokio::spawn({
|
let install_task = tokio::spawn({
|
||||||
let ctx = ctx.clone();
|
let ctx = ctx.clone();
|
||||||
@@ -4445,7 +4445,7 @@ mod tests {
|
|||||||
async fn cancel_download_command_only_cancels_active_token() {
|
async fn cancel_download_command_only_cancels_active_token() {
|
||||||
let temp = TempDir::new("lanspread-handler-cancel-download");
|
let temp = TempDir::new("lanspread-handler-cancel-download");
|
||||||
let ctx = test_ctx(temp.path().to_path_buf());
|
let ctx = test_ctx(temp.path().to_path_buf());
|
||||||
let (tx, mut rx) = mpsc::unbounded_channel();
|
let (tx, mut rx) = crate::peer_event_channel();
|
||||||
let (_status, cancel) = register_test_download(&ctx, &tx).await;
|
let (_status, cancel) = register_test_download(&ctx, &tx).await;
|
||||||
ctx.active_operations
|
ctx.active_operations
|
||||||
.write()
|
.write()
|
||||||
@@ -4472,7 +4472,7 @@ mod tests {
|
|||||||
write_file(&root.join("local").join("old.txt"), b"old");
|
write_file(&root.join("local").join("old.txt"), b"old");
|
||||||
|
|
||||||
let ctx = test_ctx(temp.path().to_path_buf());
|
let ctx = test_ctx(temp.path().to_path_buf());
|
||||||
let (tx, mut rx) = mpsc::unbounded_channel();
|
let (tx, mut rx) = crate::peer_event_channel();
|
||||||
|
|
||||||
run_install_operation(&ctx, &tx, operation_target(temp.path())).await;
|
run_install_operation(&ctx, &tx, operation_target(temp.path())).await;
|
||||||
|
|
||||||
@@ -4497,7 +4497,7 @@ mod tests {
|
|||||||
write_file(&root.join("game.eti"), b"old archive");
|
write_file(&root.join("game.eti"), b"old archive");
|
||||||
|
|
||||||
let ctx = test_ctx(temp.path().to_path_buf());
|
let ctx = test_ctx(temp.path().to_path_buf());
|
||||||
let (tx, mut rx) = mpsc::unbounded_channel();
|
let (tx, mut rx) = crate::peer_event_channel();
|
||||||
|
|
||||||
run_install_operation(&ctx, &tx, operation_target(temp.path())).await;
|
run_install_operation(&ctx, &tx, operation_target(temp.path())).await;
|
||||||
assert_active_update(
|
assert_active_update(
|
||||||
@@ -4560,7 +4560,7 @@ mod tests {
|
|||||||
write_file(&root.join("local").join("old.txt"), b"old");
|
write_file(&root.join("local").join("old.txt"), b"old");
|
||||||
|
|
||||||
let ctx = test_ctx(temp.path().to_path_buf());
|
let ctx = test_ctx(temp.path().to_path_buf());
|
||||||
let (tx, mut rx) = mpsc::unbounded_channel();
|
let (tx, mut rx) = crate::peer_event_channel();
|
||||||
|
|
||||||
run_uninstall_operation(&ctx, &tx, operation_target(temp.path())).await;
|
run_uninstall_operation(&ctx, &tx, operation_target(temp.path())).await;
|
||||||
|
|
||||||
@@ -4592,7 +4592,7 @@ mod tests {
|
|||||||
&["game.eti"],
|
&["game.eti"],
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
let (tx, mut rx) = mpsc::unbounded_channel();
|
let (tx, mut rx) = crate::peer_event_channel();
|
||||||
let catalog = ctx.catalog.catalog();
|
let catalog = ctx.catalog.catalog();
|
||||||
let scan = scan_local_library(temp.path(), ctx.state_dir.as_ref(), catalog)
|
let scan = scan_local_library(temp.path(), ctx.state_dir.as_ref(), catalog)
|
||||||
.await
|
.await
|
||||||
@@ -4630,7 +4630,7 @@ mod tests {
|
|||||||
write_file(&root.join("game.eti"), b"archive");
|
write_file(&root.join("game.eti"), b"archive");
|
||||||
}
|
}
|
||||||
let ctx = test_ctx(current.path().to_path_buf());
|
let ctx = test_ctx(current.path().to_path_buf());
|
||||||
let (tx, mut rx) = mpsc::unbounded_channel();
|
let (tx, mut rx) = crate::peer_event_channel();
|
||||||
|
|
||||||
// On the current-thread test runtime, this ready lock acquisition does
|
// On the current-thread test runtime, this ready lock acquisition does
|
||||||
// not yield to the newly spawned command. Move the configured root
|
// not yield to the newly spawned command. Move the configured root
|
||||||
@@ -4671,7 +4671,7 @@ mod tests {
|
|||||||
write_file(&root.join("local/payload.txt"), b"installed");
|
write_file(&root.join("local/payload.txt"), b"installed");
|
||||||
}
|
}
|
||||||
let ctx = test_ctx(current.path().to_path_buf());
|
let ctx = test_ctx(current.path().to_path_buf());
|
||||||
let (tx, mut rx) = mpsc::unbounded_channel();
|
let (tx, mut rx) = crate::peer_event_channel();
|
||||||
|
|
||||||
handle_uninstall_game_command(&ctx, &tx, "game".to_string()).await;
|
handle_uninstall_game_command(&ctx, &tx, "game".to_string()).await;
|
||||||
*ctx.game_dir.write().await = next.path().to_path_buf();
|
*ctx.game_dir.write().await = next.path().to_path_buf();
|
||||||
@@ -4715,7 +4715,7 @@ mod tests {
|
|||||||
&["game.eti"],
|
&["game.eti"],
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
let (tx, mut rx) = mpsc::unbounded_channel();
|
let (tx, mut rx) = crate::peer_event_channel();
|
||||||
|
|
||||||
handle_remove_downloaded_game_command(&ctx, &tx, "game".to_string()).await;
|
handle_remove_downloaded_game_command(&ctx, &tx, "game".to_string()).await;
|
||||||
*ctx.game_dir.write().await = next.path().to_path_buf();
|
*ctx.game_dir.write().await = next.path().to_path_buf();
|
||||||
@@ -4757,7 +4757,7 @@ mod tests {
|
|||||||
.write()
|
.write()
|
||||||
.await
|
.await
|
||||||
.insert("game".to_string(), OperationKind::Downloading);
|
.insert("game".to_string(), OperationKind::Downloading);
|
||||||
let (tx, _rx) = mpsc::unbounded_channel();
|
let (tx, _rx) = crate::peer_event_channel();
|
||||||
|
|
||||||
let error = handle_set_game_dir_command(&ctx, &tx, next.path().to_path_buf())
|
let error = handle_set_game_dir_command(&ctx, &tx, next.path().to_path_buf())
|
||||||
.await
|
.await
|
||||||
@@ -4778,7 +4778,7 @@ mod tests {
|
|||||||
symlink(current.path(), &alias).expect("same-root alias should be created");
|
symlink(current.path(), &alias).expect("same-root alias should be created");
|
||||||
let current = std::fs::canonicalize(current.path()).expect("root should canonicalize");
|
let current = std::fs::canonicalize(current.path()).expect("root should canonicalize");
|
||||||
let ctx = test_ctx(current.clone());
|
let ctx = test_ctx(current.clone());
|
||||||
let (tx, _rx) = mpsc::unbounded_channel();
|
let (tx, _rx) = crate::peer_event_channel();
|
||||||
|
|
||||||
let accepted = handle_set_game_dir_command(&ctx, &tx, alias)
|
let accepted = handle_set_game_dir_command(&ctx, &tx, alias)
|
||||||
.await
|
.await
|
||||||
@@ -4801,7 +4801,7 @@ mod tests {
|
|||||||
let current = std::fs::canonicalize(current.path()).expect("root should canonicalize");
|
let current = std::fs::canonicalize(current.path()).expect("root should canonicalize");
|
||||||
let next = std::fs::canonicalize(next.path()).expect("new root should canonicalize");
|
let next = std::fs::canonicalize(next.path()).expect("new root should canonicalize");
|
||||||
let ctx = test_ctx(current);
|
let ctx = test_ctx(current);
|
||||||
let (tx, _rx) = mpsc::unbounded_channel();
|
let (tx, _rx) = crate::peer_event_channel();
|
||||||
|
|
||||||
let accepted = handle_set_game_dir_command(&ctx, &tx, alias.clone())
|
let accepted = handle_set_game_dir_command(&ctx, &tx, alias.clone())
|
||||||
.await
|
.await
|
||||||
@@ -4817,7 +4817,7 @@ mod tests {
|
|||||||
let current = TempDir::new("lanspread-handler-invalid-dir-current");
|
let current = TempDir::new("lanspread-handler-invalid-dir-current");
|
||||||
let missing = current.path().join("missing");
|
let missing = current.path().join("missing");
|
||||||
let ctx = test_ctx(current.path().to_path_buf());
|
let ctx = test_ctx(current.path().to_path_buf());
|
||||||
let (tx, mut rx) = mpsc::unbounded_channel();
|
let (tx, mut rx) = crate::peer_event_channel();
|
||||||
|
|
||||||
let error = handle_set_game_dir_command(&ctx, &tx, missing)
|
let error = handle_set_game_dir_command(&ctx, &tx, missing)
|
||||||
.await
|
.await
|
||||||
@@ -4837,7 +4837,7 @@ mod tests {
|
|||||||
let candidate_path =
|
let candidate_path =
|
||||||
std::fs::canonicalize(candidate.path()).expect("candidate root should canonicalize");
|
std::fs::canonicalize(candidate.path()).expect("candidate root should canonicalize");
|
||||||
let ctx = test_ctx(current_path.clone());
|
let ctx = test_ctx(current_path.clone());
|
||||||
let (tx, mut rx) = mpsc::unbounded_channel();
|
let (tx, mut rx) = crate::peer_event_channel();
|
||||||
let published = summary("game", "20250101", Availability::Ready);
|
let published = summary("game", "20250101", Availability::Ready);
|
||||||
ctx.local_library
|
ctx.local_library
|
||||||
.write()
|
.write()
|
||||||
@@ -4884,7 +4884,7 @@ mod tests {
|
|||||||
let temp = TempDir::new("lanspread-handler-same-dir");
|
let temp = TempDir::new("lanspread-handler-same-dir");
|
||||||
write_file(&temp.game_root().join(".version.ini.tmp"), b"tmp");
|
write_file(&temp.game_root().join(".version.ini.tmp"), b"tmp");
|
||||||
let ctx = test_ctx(temp.path().to_path_buf());
|
let ctx = test_ctx(temp.path().to_path_buf());
|
||||||
let (tx, _rx) = mpsc::unbounded_channel();
|
let (tx, _rx) = crate::peer_event_channel();
|
||||||
let first = CancellationToken::new();
|
let first = CancellationToken::new();
|
||||||
let second = CancellationToken::new();
|
let second = CancellationToken::new();
|
||||||
*ctx.active_outbound_transfers.write().await = HashMap::from([
|
*ctx.active_outbound_transfers.write().await = HashMap::from([
|
||||||
@@ -4936,7 +4936,7 @@ mod tests {
|
|||||||
let temp = TempDir::new("lanspread-handler-same-dir-timeout");
|
let temp = TempDir::new("lanspread-handler-same-dir-timeout");
|
||||||
write_file(&temp.game_root().join(".version.ini.tmp"), b"tmp");
|
write_file(&temp.game_root().join(".version.ini.tmp"), b"tmp");
|
||||||
let ctx = test_ctx(temp.path().to_path_buf());
|
let ctx = test_ctx(temp.path().to_path_buf());
|
||||||
let (tx, mut rx) = mpsc::unbounded_channel();
|
let (tx, mut rx) = crate::peer_event_channel();
|
||||||
let token = CancellationToken::new();
|
let token = CancellationToken::new();
|
||||||
ctx.active_outbound_transfers
|
ctx.active_outbound_transfers
|
||||||
.write()
|
.write()
|
||||||
@@ -4969,7 +4969,7 @@ mod tests {
|
|||||||
.write()
|
.write()
|
||||||
.await
|
.await
|
||||||
.insert("game".to_string(), OperationKind::Downloading);
|
.insert("game".to_string(), OperationKind::Downloading);
|
||||||
let (tx, _rx) = mpsc::unbounded_channel();
|
let (tx, _rx) = crate::peer_event_channel();
|
||||||
|
|
||||||
let error = handle_set_game_dir_command(&ctx, &tx, temp.path().to_path_buf())
|
let error = handle_set_game_dir_command(&ctx, &tx, temp.path().to_path_buf())
|
||||||
.await
|
.await
|
||||||
@@ -4985,7 +4985,7 @@ mod tests {
|
|||||||
let next = TempDir::new("lanspread-handler-new-dir");
|
let next = TempDir::new("lanspread-handler-new-dir");
|
||||||
write_file(&next.game_root().join(".version.ini.tmp"), b"tmp");
|
write_file(&next.game_root().join(".version.ini.tmp"), b"tmp");
|
||||||
let ctx = test_ctx(current.path().to_path_buf());
|
let ctx = test_ctx(current.path().to_path_buf());
|
||||||
let (tx, _rx) = mpsc::unbounded_channel();
|
let (tx, _rx) = crate::peer_event_channel();
|
||||||
let first = CancellationToken::new();
|
let first = CancellationToken::new();
|
||||||
let second = CancellationToken::new();
|
let second = CancellationToken::new();
|
||||||
*ctx.active_outbound_transfers.write().await = HashMap::from([
|
*ctx.active_outbound_transfers.write().await = HashMap::from([
|
||||||
@@ -5041,7 +5041,7 @@ mod tests {
|
|||||||
write_file(&root.join("version.ini"), b"20250101");
|
write_file(&root.join("version.ini"), b"20250101");
|
||||||
write_file(&root.join("game.eti"), b"archive");
|
write_file(&root.join("game.eti"), b"archive");
|
||||||
let ctx = test_ctx(current.path().to_path_buf());
|
let ctx = test_ctx(current.path().to_path_buf());
|
||||||
let (tx, mut rx) = mpsc::unbounded_channel();
|
let (tx, mut rx) = crate::peer_event_channel();
|
||||||
|
|
||||||
let catalog = ctx.catalog.catalog();
|
let catalog = ctx.catalog.catalog();
|
||||||
let scan = scan_local_library(current.path(), ctx.state_dir.as_ref(), catalog)
|
let scan = scan_local_library(current.path(), ctx.state_dir.as_ref(), catalog)
|
||||||
@@ -5095,7 +5095,7 @@ mod tests {
|
|||||||
let current = TempDir::new("lanspread-handler-revision-exhausted-current");
|
let current = TempDir::new("lanspread-handler-revision-exhausted-current");
|
||||||
let next = TempDir::new("lanspread-handler-revision-exhausted-next");
|
let next = TempDir::new("lanspread-handler-revision-exhausted-next");
|
||||||
let ctx = test_ctx(current.path().to_path_buf());
|
let ctx = test_ctx(current.path().to_path_buf());
|
||||||
let (tx, mut rx) = mpsc::unbounded_channel();
|
let (tx, mut rx) = crate::peer_event_channel();
|
||||||
let published = summary("game", "20250101", Availability::Ready);
|
let published = summary("game", "20250101", Availability::Ready);
|
||||||
ctx.local_library
|
ctx.local_library
|
||||||
.write()
|
.write()
|
||||||
@@ -5138,7 +5138,7 @@ mod tests {
|
|||||||
std::fs::create_dir_all(next_root.join(".version.ini.tmp"))
|
std::fs::create_dir_all(next_root.join(".version.ini.tmp"))
|
||||||
.expect("invalid recovery scratch directory should be created");
|
.expect("invalid recovery scratch directory should be created");
|
||||||
let ctx = test_ctx(current.path().to_path_buf());
|
let ctx = test_ctx(current.path().to_path_buf());
|
||||||
let (tx, mut rx) = mpsc::unbounded_channel();
|
let (tx, mut rx) = crate::peer_event_channel();
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
handle_set_game_dir_command_with_drain_timeout(
|
handle_set_game_dir_command_with_drain_timeout(
|
||||||
@@ -5198,7 +5198,7 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let ctx = test_ctx(current.path().to_path_buf());
|
let ctx = test_ctx(current.path().to_path_buf());
|
||||||
let (tx, mut rx) = mpsc::unbounded_channel();
|
let (tx, mut rx) = crate::peer_event_channel();
|
||||||
let catalog = ctx.catalog.catalog();
|
let catalog = ctx.catalog.catalog();
|
||||||
let scan = scan_local_library(current.path(), ctx.state_dir.as_ref(), catalog)
|
let scan = scan_local_library(current.path(), ctx.state_dir.as_ref(), catalog)
|
||||||
.await
|
.await
|
||||||
|
|||||||
@@ -87,7 +87,7 @@ pub use scoped_blocking::scoped_blocking;
|
|||||||
pub use scoped_process::{ScopedProcess, ScopedProcessOutput};
|
pub use scoped_process::{ScopedProcess, ScopedProcessOutput};
|
||||||
use tokio::sync::{
|
use tokio::sync::{
|
||||||
RwLock,
|
RwLock,
|
||||||
mpsc::{UnboundedReceiver, UnboundedSender},
|
mpsc::UnboundedReceiver,
|
||||||
oneshot,
|
oneshot,
|
||||||
};
|
};
|
||||||
use tokio_util::{sync::CancellationToken, task::TaskTracker};
|
use tokio_util::{sync::CancellationToken, task::TaskTracker};
|
||||||
@@ -134,6 +134,7 @@ pub use crate::{
|
|||||||
StreamInstallProvider,
|
StreamInstallProvider,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
pub use events::{PeerEventReceiver, PeerEventSender, peer_event_channel};
|
||||||
|
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
// Public API types
|
// Public API types
|
||||||
@@ -563,7 +564,7 @@ impl std::fmt::Debug for PeerStartOptions {
|
|||||||
#[allow(clippy::implicit_hasher)]
|
#[allow(clippy::implicit_hasher)]
|
||||||
pub fn start_peer(
|
pub fn start_peer(
|
||||||
game_dir: impl Into<PathBuf>,
|
game_dir: impl Into<PathBuf>,
|
||||||
tx_notify_ui: UnboundedSender<PeerEvent>,
|
tx_notify_ui: PeerEventSender,
|
||||||
peer_game_db: Arc<RwLock<PeerGameDB>>,
|
peer_game_db: Arc<RwLock<PeerGameDB>>,
|
||||||
unpacker: Arc<dyn Unpacker>,
|
unpacker: Arc<dyn Unpacker>,
|
||||||
catalog: Arc<CatalogBundle>,
|
catalog: Arc<CatalogBundle>,
|
||||||
@@ -582,7 +583,7 @@ pub fn start_peer(
|
|||||||
#[allow(clippy::implicit_hasher)]
|
#[allow(clippy::implicit_hasher)]
|
||||||
pub fn start_peer_with_options(
|
pub fn start_peer_with_options(
|
||||||
game_dir: impl Into<PathBuf>,
|
game_dir: impl Into<PathBuf>,
|
||||||
tx_notify_ui: UnboundedSender<PeerEvent>,
|
tx_notify_ui: PeerEventSender,
|
||||||
peer_game_db: Arc<RwLock<PeerGameDB>>,
|
peer_game_db: Arc<RwLock<PeerGameDB>>,
|
||||||
unpacker: Arc<dyn Unpacker>,
|
unpacker: Arc<dyn Unpacker>,
|
||||||
catalog: Arc<CatalogBundle>,
|
catalog: Arc<CatalogBundle>,
|
||||||
@@ -669,7 +670,7 @@ const fn identity_durability(persistence: &PeerIdentityPersistence) -> PeerIdent
|
|||||||
#[allow(clippy::too_many_arguments, clippy::implicit_hasher)]
|
#[allow(clippy::too_many_arguments, clippy::implicit_hasher)]
|
||||||
async fn run_peer(
|
async fn run_peer(
|
||||||
mut rx_control: UnboundedReceiver<PeerCommand>,
|
mut rx_control: UnboundedReceiver<PeerCommand>,
|
||||||
tx_notify_ui: UnboundedSender<PeerEvent>,
|
tx_notify_ui: PeerEventSender,
|
||||||
peer_game_db: Arc<RwLock<PeerGameDB>>,
|
peer_game_db: Arc<RwLock<PeerGameDB>>,
|
||||||
peer_identity: Arc<PeerIdentity>,
|
peer_identity: Arc<PeerIdentity>,
|
||||||
game_dir: PathBuf,
|
game_dir: PathBuf,
|
||||||
@@ -724,7 +725,7 @@ async fn run_peer(
|
|||||||
|
|
||||||
async fn handle_peer_commands(
|
async fn handle_peer_commands(
|
||||||
ctx: &Ctx,
|
ctx: &Ctx,
|
||||||
tx_notify_ui: &UnboundedSender<PeerEvent>,
|
tx_notify_ui: &PeerEventSender,
|
||||||
rx_control: &mut UnboundedReceiver<PeerCommand>,
|
rx_control: &mut UnboundedReceiver<PeerCommand>,
|
||||||
) -> eyre::Result<()> {
|
) -> eyre::Result<()> {
|
||||||
loop {
|
loop {
|
||||||
@@ -805,7 +806,7 @@ async fn handle_peer_commands(
|
|||||||
|
|
||||||
async fn handle_apply_call_to_play_intent(
|
async fn handle_apply_call_to_play_intent(
|
||||||
ctx: &Ctx,
|
ctx: &Ctx,
|
||||||
tx_notify_ui: &UnboundedSender<PeerEvent>,
|
tx_notify_ui: &PeerEventSender,
|
||||||
intent: CallToPlayLocalIntent,
|
intent: CallToPlayLocalIntent,
|
||||||
display_name: String,
|
display_name: String,
|
||||||
reply: oneshot::Sender<Result<CallToPlayReceipt, String>>,
|
reply: oneshot::Sender<Result<CallToPlayReceipt, String>>,
|
||||||
@@ -837,7 +838,7 @@ async fn handle_apply_call_to_play_intent(
|
|||||||
|
|
||||||
async fn handle_set_call_to_play_display_name(
|
async fn handle_set_call_to_play_display_name(
|
||||||
ctx: &Ctx,
|
ctx: &Ctx,
|
||||||
tx_notify_ui: &UnboundedSender<PeerEvent>,
|
tx_notify_ui: &PeerEventSender,
|
||||||
display_name: String,
|
display_name: String,
|
||||||
reply: oneshot::Sender<Result<bool, String>>,
|
reply: oneshot::Sender<Result<bool, String>>,
|
||||||
) {
|
) {
|
||||||
@@ -863,7 +864,7 @@ async fn handle_set_call_to_play_display_name(
|
|||||||
|
|
||||||
async fn handle_get_call_to_play_view(
|
async fn handle_get_call_to_play_view(
|
||||||
ctx: &Ctx,
|
ctx: &Ctx,
|
||||||
tx_notify_ui: &UnboundedSender<PeerEvent>,
|
tx_notify_ui: &PeerEventSender,
|
||||||
reply: Option<oneshot::Sender<Result<CallToPlayView, String>>>,
|
reply: Option<oneshot::Sender<Result<CallToPlayView, String>>>,
|
||||||
) {
|
) {
|
||||||
let result = {
|
let result = {
|
||||||
@@ -999,7 +1000,7 @@ mod configured_game_dir_tests {
|
|||||||
let state = TempDir::new("lanspread-startup-state-root");
|
let state = TempDir::new("lanspread-startup-state-root");
|
||||||
let alias = target.path().join(".");
|
let alias = target.path().join(".");
|
||||||
let expected = std::fs::canonicalize(target.path()).expect("target should canonicalize");
|
let expected = std::fs::canonicalize(target.path()).expect("target should canonicalize");
|
||||||
let (events, _event_rx) = mpsc::unbounded_channel();
|
let (events, _event_rx) = crate::peer_event_channel();
|
||||||
|
|
||||||
let mut handle = start_peer_with_options(
|
let mut handle = start_peer_with_options(
|
||||||
alias,
|
alias,
|
||||||
@@ -1044,7 +1045,7 @@ mod configured_game_dir_tests {
|
|||||||
let identity = Arc::new(loaded.identity);
|
let identity = Arc::new(loaded.identity);
|
||||||
let expected_peer_id = identity.peer_id();
|
let expected_peer_id = identity.peer_id();
|
||||||
let (tx_control, rx_control) = mpsc::unbounded_channel();
|
let (tx_control, rx_control) = mpsc::unbounded_channel();
|
||||||
let (tx_events, mut rx_events) = mpsc::unbounded_channel();
|
let (tx_events, mut rx_events) = crate::peer_event_channel();
|
||||||
|
|
||||||
let mut handle = crate::startup::spawn_peer_runtime(
|
let mut handle = crate::startup::spawn_peer_runtime(
|
||||||
tx_control,
|
tx_control,
|
||||||
@@ -1128,7 +1129,7 @@ mod configured_game_dir_tests {
|
|||||||
)
|
)
|
||||||
.expect("old-root intent should be valid");
|
.expect("old-root intent should be valid");
|
||||||
write_intent(state.path(), "orphan", &intent).expect("old-root intent should be persisted");
|
write_intent(state.path(), "orphan", &intent).expect("old-root intent should be persisted");
|
||||||
let (events, _event_rx) = mpsc::unbounded_channel();
|
let (events, _event_rx) = crate::peer_event_channel();
|
||||||
|
|
||||||
let result = start_peer_with_options(
|
let result = start_peer_with_options(
|
||||||
candidate.path().to_path_buf(),
|
candidate.path().to_path_buf(),
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ use tokio_util::{
|
|||||||
use crate::{
|
use crate::{
|
||||||
LocalNetworkSharingState,
|
LocalNetworkSharingState,
|
||||||
PeerEvent,
|
PeerEvent,
|
||||||
|
PeerEventSender,
|
||||||
PeerRuntimeComponent,
|
PeerRuntimeComponent,
|
||||||
context::{Ctx, NetworkServiceCtx},
|
context::{Ctx, NetworkServiceCtx},
|
||||||
events,
|
events,
|
||||||
@@ -283,7 +284,7 @@ impl EnableFailure {
|
|||||||
struct GenerationStart {
|
struct GenerationStart {
|
||||||
id: NetworkGenerationId,
|
id: NetworkGenerationId,
|
||||||
core: Ctx,
|
core: Ctx,
|
||||||
tx_notify_ui: mpsc::UnboundedSender<PeerEvent>,
|
tx_notify_ui: PeerEventSender,
|
||||||
shutdown: CancellationToken,
|
shutdown: CancellationToken,
|
||||||
tasks: TaskTracker,
|
tasks: TaskTracker,
|
||||||
failure_tx: mpsc::UnboundedSender<GenerationFailure>,
|
failure_tx: mpsc::UnboundedSender<GenerationFailure>,
|
||||||
@@ -410,7 +411,7 @@ impl NetworkManager {
|
|||||||
pub(crate) async fn run(
|
pub(crate) async fn run(
|
||||||
mut self,
|
mut self,
|
||||||
ctx: Ctx,
|
ctx: Ctx,
|
||||||
tx_notify_ui: mpsc::UnboundedSender<PeerEvent>,
|
tx_notify_ui: PeerEventSender,
|
||||||
initially_enabled: bool,
|
initially_enabled: bool,
|
||||||
) {
|
) {
|
||||||
let mut current = None;
|
let mut current = None;
|
||||||
@@ -445,7 +446,7 @@ impl NetworkManager {
|
|||||||
async fn run_body(
|
async fn run_body(
|
||||||
&mut self,
|
&mut self,
|
||||||
ctx: &Ctx,
|
ctx: &Ctx,
|
||||||
tx_notify_ui: &mpsc::UnboundedSender<PeerEvent>,
|
tx_notify_ui: &PeerEventSender,
|
||||||
initially_enabled: bool,
|
initially_enabled: bool,
|
||||||
current: &mut Option<NetworkGeneration>,
|
current: &mut Option<NetworkGeneration>,
|
||||||
) {
|
) {
|
||||||
@@ -495,7 +496,7 @@ impl NetworkManager {
|
|||||||
async fn enable(
|
async fn enable(
|
||||||
&mut self,
|
&mut self,
|
||||||
ctx: &Ctx,
|
ctx: &Ctx,
|
||||||
tx_notify_ui: &mpsc::UnboundedSender<PeerEvent>,
|
tx_notify_ui: &PeerEventSender,
|
||||||
current: &mut Option<NetworkGeneration>,
|
current: &mut Option<NetworkGeneration>,
|
||||||
) -> Result<bool, EnableFailure> {
|
) -> Result<bool, EnableFailure> {
|
||||||
if current.is_some() {
|
if current.is_some() {
|
||||||
@@ -585,7 +586,7 @@ impl NetworkManager {
|
|||||||
async fn start_generation(
|
async fn start_generation(
|
||||||
&self,
|
&self,
|
||||||
ctx: &Ctx,
|
ctx: &Ctx,
|
||||||
tx_notify_ui: &mpsc::UnboundedSender<PeerEvent>,
|
tx_notify_ui: &PeerEventSender,
|
||||||
id: NetworkGenerationId,
|
id: NetworkGenerationId,
|
||||||
shutdown: &CancellationToken,
|
shutdown: &CancellationToken,
|
||||||
tasks: &TaskTracker,
|
tasks: &TaskTracker,
|
||||||
@@ -673,7 +674,7 @@ impl NetworkManager {
|
|||||||
async fn disable(
|
async fn disable(
|
||||||
&self,
|
&self,
|
||||||
ctx: &Ctx,
|
ctx: &Ctx,
|
||||||
tx_notify_ui: &mpsc::UnboundedSender<PeerEvent>,
|
tx_notify_ui: &PeerEventSender,
|
||||||
current: &mut Option<NetworkGeneration>,
|
current: &mut Option<NetworkGeneration>,
|
||||||
) {
|
) {
|
||||||
let Some(generation) = current.as_mut() else {
|
let Some(generation) = current.as_mut() else {
|
||||||
@@ -693,7 +694,7 @@ impl NetworkManager {
|
|||||||
async fn stop_generation(
|
async fn stop_generation(
|
||||||
&self,
|
&self,
|
||||||
ctx: &Ctx,
|
ctx: &Ctx,
|
||||||
tx_notify_ui: &mpsc::UnboundedSender<PeerEvent>,
|
tx_notify_ui: &PeerEventSender,
|
||||||
generation: &mut NetworkGeneration,
|
generation: &mut NetworkGeneration,
|
||||||
) {
|
) {
|
||||||
self.begin_stop_generation(generation);
|
self.begin_stop_generation(generation);
|
||||||
@@ -800,7 +801,7 @@ async fn clear_local_peer_addr(ctx: &Ctx) {
|
|||||||
*ctx.local_peer_addr.write().await = None;
|
*ctx.local_peer_addr.write().await = None;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn send_state(tx_notify_ui: &mpsc::UnboundedSender<PeerEvent>, state: LocalNetworkSharingState) {
|
fn send_state(tx_notify_ui: &PeerEventSender, state: LocalNetworkSharingState) {
|
||||||
events::send(
|
events::send(
|
||||||
tx_notify_ui,
|
tx_notify_ui,
|
||||||
PeerEvent::LocalNetworkSharingStateChanged(state),
|
PeerEvent::LocalNetworkSharingStateChanged(state),
|
||||||
@@ -808,7 +809,7 @@ fn send_state(tx_notify_ui: &mpsc::UnboundedSender<PeerEvent>, state: LocalNetwo
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn report_failure(
|
fn report_failure(
|
||||||
tx_notify_ui: &mpsc::UnboundedSender<PeerEvent>,
|
tx_notify_ui: &PeerEventSender,
|
||||||
component: PeerRuntimeComponent,
|
component: PeerRuntimeComponent,
|
||||||
error: String,
|
error: String,
|
||||||
) {
|
) {
|
||||||
@@ -991,7 +992,7 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn wait_for_state(
|
async fn wait_for_state(
|
||||||
events: &mut mpsc::UnboundedReceiver<PeerEvent>,
|
events: &mut crate::PeerEventReceiver,
|
||||||
expected: LocalNetworkSharingState,
|
expected: LocalNetworkSharingState,
|
||||||
) {
|
) {
|
||||||
loop {
|
loop {
|
||||||
@@ -1055,7 +1056,7 @@ mod tests {
|
|||||||
async fn startup_disabled_does_not_construct_a_network_generation() {
|
async fn startup_disabled_does_not_construct_a_network_generation() {
|
||||||
let harness = test_harness();
|
let harness = test_harness();
|
||||||
let (_temp, ctx) = test_ctx(harness.control.clone());
|
let (_temp, ctx) = test_ctx(harness.control.clone());
|
||||||
let (event_tx, mut events) = mpsc::unbounded_channel();
|
let (event_tx, mut events) = crate::peer_event_channel();
|
||||||
let manager_task = tokio::spawn(harness.manager.run(ctx.clone(), event_tx, false));
|
let manager_task = tokio::spawn(harness.manager.run(ctx.clone(), event_tx, false));
|
||||||
|
|
||||||
wait_for_state(&mut events, LocalNetworkSharingState::Disabled).await;
|
wait_for_state(&mut events, LocalNetworkSharingState::Disabled).await;
|
||||||
@@ -1071,7 +1072,7 @@ mod tests {
|
|||||||
async fn enable_reply_waits_for_readiness_and_atomic_admission() {
|
async fn enable_reply_waits_for_readiness_and_atomic_admission() {
|
||||||
let mut harness = test_harness();
|
let mut harness = test_harness();
|
||||||
let (_temp, ctx) = test_ctx(harness.control.clone());
|
let (_temp, ctx) = test_ctx(harness.control.clone());
|
||||||
let (event_tx, mut events) = mpsc::unbounded_channel();
|
let (event_tx, mut events) = crate::peer_event_channel();
|
||||||
let manager_task = tokio::spawn(harness.manager.run(ctx.clone(), event_tx, false));
|
let manager_task = tokio::spawn(harness.manager.run(ctx.clone(), event_tx, false));
|
||||||
wait_for_state(&mut events, LocalNetworkSharingState::Disabled).await;
|
wait_for_state(&mut events, LocalNetworkSharingState::Disabled).await;
|
||||||
|
|
||||||
@@ -1105,7 +1106,7 @@ mod tests {
|
|||||||
async fn disable_reply_waits_for_every_admitted_permit() {
|
async fn disable_reply_waits_for_every_admitted_permit() {
|
||||||
let mut harness = test_harness();
|
let mut harness = test_harness();
|
||||||
let (_temp, ctx) = test_ctx(harness.control.clone());
|
let (_temp, ctx) = test_ctx(harness.control.clone());
|
||||||
let (event_tx, mut events) = mpsc::unbounded_channel();
|
let (event_tx, mut events) = crate::peer_event_channel();
|
||||||
let manager_task = tokio::spawn(harness.manager.run(ctx.clone(), event_tx, false));
|
let manager_task = tokio::spawn(harness.manager.run(ctx.clone(), event_tx, false));
|
||||||
wait_for_state(&mut events, LocalNetworkSharingState::Disabled).await;
|
wait_for_state(&mut events, LocalNetworkSharingState::Disabled).await;
|
||||||
|
|
||||||
@@ -1188,7 +1189,7 @@ mod tests {
|
|||||||
async fn readiness_failure_drains_partial_generation_and_allows_reenable() {
|
async fn readiness_failure_drains_partial_generation_and_allows_reenable() {
|
||||||
let mut harness = test_harness();
|
let mut harness = test_harness();
|
||||||
let (_temp, ctx) = test_ctx(harness.control.clone());
|
let (_temp, ctx) = test_ctx(harness.control.clone());
|
||||||
let (event_tx, mut events) = mpsc::unbounded_channel();
|
let (event_tx, mut events) = crate::peer_event_channel();
|
||||||
let manager_task = tokio::spawn(harness.manager.run(ctx.clone(), event_tx, false));
|
let manager_task = tokio::spawn(harness.manager.run(ctx.clone(), event_tx, false));
|
||||||
wait_for_state(&mut events, LocalNetworkSharingState::Disabled).await;
|
wait_for_state(&mut events, LocalNetworkSharingState::Disabled).await;
|
||||||
|
|
||||||
@@ -1223,7 +1224,7 @@ mod tests {
|
|||||||
async fn disable_interrupts_initial_enable_without_a_failure_diagnostic() {
|
async fn disable_interrupts_initial_enable_without_a_failure_diagnostic() {
|
||||||
let mut harness = test_harness();
|
let mut harness = test_harness();
|
||||||
let (_temp, ctx) = test_ctx(harness.control.clone());
|
let (_temp, ctx) = test_ctx(harness.control.clone());
|
||||||
let (event_tx, mut events) = mpsc::unbounded_channel();
|
let (event_tx, mut events) = crate::peer_event_channel();
|
||||||
let manager_task = tokio::spawn(harness.manager.run(ctx.clone(), event_tx, true));
|
let manager_task = tokio::spawn(harness.manager.run(ctx.clone(), event_tx, true));
|
||||||
let pending_ready = harness
|
let pending_ready = harness
|
||||||
.started
|
.started
|
||||||
@@ -1273,7 +1274,7 @@ mod tests {
|
|||||||
async fn current_required_failure_auto_disables_and_closes_admission() {
|
async fn current_required_failure_auto_disables_and_closes_admission() {
|
||||||
let mut harness = test_harness();
|
let mut harness = test_harness();
|
||||||
let (_temp, ctx) = test_ctx(harness.control.clone());
|
let (_temp, ctx) = test_ctx(harness.control.clone());
|
||||||
let (event_tx, mut events) = mpsc::unbounded_channel();
|
let (event_tx, mut events) = crate::peer_event_channel();
|
||||||
let manager_task = tokio::spawn(harness.manager.run(ctx.clone(), event_tx, false));
|
let manager_task = tokio::spawn(harness.manager.run(ctx.clone(), event_tx, false));
|
||||||
wait_for_state(&mut events, LocalNetworkSharingState::Disabled).await;
|
wait_for_state(&mut events, LocalNetworkSharingState::Disabled).await;
|
||||||
let (id, failures) = enable_fake_generation(&harness.control, &mut harness.started).await;
|
let (id, failures) = enable_fake_generation(&harness.control, &mut harness.started).await;
|
||||||
@@ -1297,7 +1298,7 @@ mod tests {
|
|||||||
async fn root_shutdown_waits_for_held_permit_and_joins_runtime() {
|
async fn root_shutdown_waits_for_held_permit_and_joins_runtime() {
|
||||||
let mut harness = test_harness();
|
let mut harness = test_harness();
|
||||||
let (_temp, ctx) = test_ctx(harness.control.clone());
|
let (_temp, ctx) = test_ctx(harness.control.clone());
|
||||||
let (event_tx, mut events) = mpsc::unbounded_channel();
|
let (event_tx, mut events) = crate::peer_event_channel();
|
||||||
let manager_task = tokio::spawn(harness.manager.run(ctx.clone(), event_tx, false));
|
let manager_task = tokio::spawn(harness.manager.run(ctx.clone(), event_tx, false));
|
||||||
wait_for_state(&mut events, LocalNetworkSharingState::Disabled).await;
|
wait_for_state(&mut events, LocalNetworkSharingState::Disabled).await;
|
||||||
enable_fake_generation(&harness.control, &mut harness.started).await;
|
enable_fake_generation(&harness.control, &mut harness.started).await;
|
||||||
@@ -1321,7 +1322,7 @@ mod tests {
|
|||||||
async fn dropping_enable_reply_does_not_cancel_the_transition() {
|
async fn dropping_enable_reply_does_not_cancel_the_transition() {
|
||||||
let mut harness = test_harness();
|
let mut harness = test_harness();
|
||||||
let (_temp, ctx) = test_ctx(harness.control.clone());
|
let (_temp, ctx) = test_ctx(harness.control.clone());
|
||||||
let (event_tx, mut events) = mpsc::unbounded_channel();
|
let (event_tx, mut events) = crate::peer_event_channel();
|
||||||
let manager_task = tokio::spawn(harness.manager.run(ctx.clone(), event_tx, false));
|
let manager_task = tokio::spawn(harness.manager.run(ctx.clone(), event_tx, false));
|
||||||
wait_for_state(&mut events, LocalNetworkSharingState::Disabled).await;
|
wait_for_state(&mut events, LocalNetworkSharingState::Disabled).await;
|
||||||
|
|
||||||
@@ -1362,7 +1363,7 @@ mod tests {
|
|||||||
async fn idempotent_stable_requests_do_not_restart_or_republish() {
|
async fn idempotent_stable_requests_do_not_restart_or_republish() {
|
||||||
let mut harness = test_harness();
|
let mut harness = test_harness();
|
||||||
let (_temp, ctx) = test_ctx(harness.control.clone());
|
let (_temp, ctx) = test_ctx(harness.control.clone());
|
||||||
let (event_tx, mut events) = mpsc::unbounded_channel();
|
let (event_tx, mut events) = crate::peer_event_channel();
|
||||||
let manager_task = tokio::spawn(harness.manager.run(ctx.clone(), event_tx, false));
|
let manager_task = tokio::spawn(harness.manager.run(ctx.clone(), event_tx, false));
|
||||||
wait_for_state(&mut events, LocalNetworkSharingState::Disabled).await;
|
wait_for_state(&mut events, LocalNetworkSharingState::Disabled).await;
|
||||||
|
|
||||||
@@ -1388,7 +1389,7 @@ mod tests {
|
|||||||
async fn client_cleanup_error_is_diagnostic_but_off_ack_is_authoritative() {
|
async fn client_cleanup_error_is_diagnostic_but_off_ack_is_authoritative() {
|
||||||
let mut harness = test_harness();
|
let mut harness = test_harness();
|
||||||
let (_temp, ctx) = test_ctx(harness.control.clone());
|
let (_temp, ctx) = test_ctx(harness.control.clone());
|
||||||
let (event_tx, mut events) = mpsc::unbounded_channel();
|
let (event_tx, mut events) = crate::peer_event_channel();
|
||||||
let manager_task = tokio::spawn(harness.manager.run(ctx.clone(), event_tx, false));
|
let manager_task = tokio::spawn(harness.manager.run(ctx.clone(), event_tx, false));
|
||||||
wait_for_state(&mut events, LocalNetworkSharingState::Disabled).await;
|
wait_for_state(&mut events, LocalNetworkSharingState::Disabled).await;
|
||||||
enable_fake_generation(&harness.control, &mut harness.started).await;
|
enable_fake_generation(&harness.control, &mut harness.started).await;
|
||||||
@@ -1423,7 +1424,7 @@ mod tests {
|
|||||||
let harness = test_harness();
|
let harness = test_harness();
|
||||||
harness.start_panics.store(true, Ordering::SeqCst);
|
harness.start_panics.store(true, Ordering::SeqCst);
|
||||||
let (_temp, ctx) = test_ctx(harness.control.clone());
|
let (_temp, ctx) = test_ctx(harness.control.clone());
|
||||||
let (event_tx, mut events) = mpsc::unbounded_channel();
|
let (event_tx, mut events) = crate::peer_event_channel();
|
||||||
let manager_task = tokio::spawn(harness.manager.run(ctx.clone(), event_tx, false));
|
let manager_task = tokio::spawn(harness.manager.run(ctx.clone(), event_tx, false));
|
||||||
wait_for_state(&mut events, LocalNetworkSharingState::Disabled).await;
|
wait_for_state(&mut events, LocalNetworkSharingState::Disabled).await;
|
||||||
|
|
||||||
@@ -1448,7 +1449,7 @@ mod tests {
|
|||||||
async fn stale_generation_failure_cannot_close_current_admission() {
|
async fn stale_generation_failure_cannot_close_current_admission() {
|
||||||
let mut harness = test_harness();
|
let mut harness = test_harness();
|
||||||
let (_temp, ctx) = test_ctx(harness.control.clone());
|
let (_temp, ctx) = test_ctx(harness.control.clone());
|
||||||
let (event_tx, mut events) = mpsc::unbounded_channel();
|
let (event_tx, mut events) = crate::peer_event_channel();
|
||||||
let manager_task = tokio::spawn(harness.manager.run(ctx.clone(), event_tx, false));
|
let manager_task = tokio::spawn(harness.manager.run(ctx.clone(), event_tx, false));
|
||||||
wait_for_state(&mut events, LocalNetworkSharingState::Disabled).await;
|
wait_for_state(&mut events, LocalNetworkSharingState::Disabled).await;
|
||||||
|
|
||||||
@@ -1513,7 +1514,7 @@ mod tests {
|
|||||||
let mut harness = test_harness();
|
let mut harness = test_harness();
|
||||||
harness.manager.last_generation = u64::MAX;
|
harness.manager.last_generation = u64::MAX;
|
||||||
let (_temp, ctx) = test_ctx(harness.control.clone());
|
let (_temp, ctx) = test_ctx(harness.control.clone());
|
||||||
let (event_tx, mut events) = mpsc::unbounded_channel();
|
let (event_tx, mut events) = crate::peer_event_channel();
|
||||||
let manager_task = tokio::spawn(harness.manager.run(ctx.clone(), event_tx, false));
|
let manager_task = tokio::spawn(harness.manager.run(ctx.clone(), event_tx, false));
|
||||||
wait_for_state(&mut events, LocalNetworkSharingState::Disabled).await;
|
wait_for_state(&mut events, LocalNetworkSharingState::Disabled).await;
|
||||||
|
|
||||||
|
|||||||
@@ -12,13 +12,14 @@ use futures::{StreamExt as _, stream::FuturesUnordered};
|
|||||||
use lanspread_mdns::{LANSPREAD_SERVICE_TYPE, MdnsBrowser, MdnsService, MdnsServicePoll};
|
use lanspread_mdns::{LANSPREAD_SERVICE_TYPE, MdnsBrowser, MdnsService, MdnsServicePoll};
|
||||||
use lanspread_proto::{PROTOCOL_VERSION, PeerEndpoint, PeerId};
|
use lanspread_proto::{PROTOCOL_VERSION, PeerEndpoint, PeerId};
|
||||||
use tokio::sync::{
|
use tokio::sync::{
|
||||||
mpsc::{self, UnboundedSender},
|
mpsc,
|
||||||
oneshot,
|
oneshot,
|
||||||
};
|
};
|
||||||
use tokio_util::sync::CancellationToken;
|
use tokio_util::sync::CancellationToken;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
PeerEvent,
|
PeerEvent,
|
||||||
|
PeerEventSender,
|
||||||
context::NetworkServiceCtx,
|
context::NetworkServiceCtx,
|
||||||
events,
|
events,
|
||||||
services::{
|
services::{
|
||||||
@@ -188,7 +189,7 @@ impl Drop for DiscoveryWorker {
|
|||||||
/// Runs the peer discovery service using mDNS.
|
/// Runs the peer discovery service using mDNS.
|
||||||
#[allow(clippy::too_many_lines)]
|
#[allow(clippy::too_many_lines)]
|
||||||
pub async fn run_peer_discovery(
|
pub async fn run_peer_discovery(
|
||||||
tx_notify_ui: UnboundedSender<PeerEvent>,
|
tx_notify_ui: PeerEventSender,
|
||||||
ctx: NetworkServiceCtx,
|
ctx: NetworkServiceCtx,
|
||||||
) -> eyre::Result<()> {
|
) -> eyre::Result<()> {
|
||||||
log::info!("Starting peer discovery task");
|
log::info!("Starting peer discovery task");
|
||||||
|
|||||||
@@ -3,10 +3,8 @@
|
|||||||
use std::{sync::Arc, time::Duration};
|
use std::{sync::Arc, time::Duration};
|
||||||
|
|
||||||
use futures::{StreamExt as _, stream};
|
use futures::{StreamExt as _, stream};
|
||||||
use tokio::sync::mpsc::UnboundedSender;
|
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
PeerEvent,
|
PeerEventSender,
|
||||||
config::{PEER_PING_IDLE_SECS, PEER_PING_INTERVAL_SECS, peer_stale_timeout},
|
config::{PEER_PING_IDLE_SECS, PEER_PING_INTERVAL_SECS, peer_stale_timeout},
|
||||||
content_quarantine::ContentQuarantine,
|
content_quarantine::ContentQuarantine,
|
||||||
context::{NetworkServiceCtx, OperationKind},
|
context::{NetworkServiceCtx, OperationKind},
|
||||||
@@ -22,7 +20,7 @@ const MAX_CONCURRENT_PINGS: usize = 8;
|
|||||||
/// uses `last_revision_check`; inbound and content traffic only affect
|
/// uses `last_revision_check`; inbound and content traffic only affect
|
||||||
/// `last_seen`, which remains the stale-pruning clock.
|
/// `last_seen`, which remains the stale-pruning clock.
|
||||||
pub async fn run_ping_service(
|
pub async fn run_ping_service(
|
||||||
tx_notify_ui: UnboundedSender<PeerEvent>,
|
tx_notify_ui: PeerEventSender,
|
||||||
ctx: NetworkServiceCtx,
|
ctx: NetworkServiceCtx,
|
||||||
) -> eyre::Result<()> {
|
) -> eyre::Result<()> {
|
||||||
log::info!(
|
log::info!(
|
||||||
|
|||||||
@@ -14,13 +14,13 @@ use std::{
|
|||||||
|
|
||||||
use futures::FutureExt;
|
use futures::FutureExt;
|
||||||
use tokio::{
|
use tokio::{
|
||||||
sync::{RwLock, mpsc::UnboundedSender},
|
sync::RwLock,
|
||||||
task::{JoinError, JoinSet},
|
task::{JoinError, JoinSet},
|
||||||
time::{Instant, MissedTickBehavior},
|
time::{Instant, MissedTickBehavior},
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
PeerEvent,
|
PeerEventSender,
|
||||||
config::{LOCAL_GAME_FALLBACK_SCAN_SECS, LOCAL_GAME_POLL_INTERVAL_SECS},
|
config::{LOCAL_GAME_FALLBACK_SCAN_SECS, LOCAL_GAME_POLL_INTERVAL_SECS},
|
||||||
context::Ctx,
|
context::Ctx,
|
||||||
game_paths::{is_download_protected_root_name, is_ignored_games_root_name},
|
game_paths::{is_download_protected_root_name, is_ignored_games_root_name},
|
||||||
@@ -67,10 +67,7 @@ struct RescanGate {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Monitors the local game directory for changes.
|
/// Monitors the local game directory for changes.
|
||||||
pub async fn run_local_game_monitor(
|
pub async fn run_local_game_monitor(tx_notify_ui: PeerEventSender, ctx: Ctx) -> eyre::Result<()> {
|
||||||
tx_notify_ui: UnboundedSender<PeerEvent>,
|
|
||||||
ctx: Ctx,
|
|
||||||
) -> eyre::Result<()> {
|
|
||||||
log::info!("Starting polling-based local game directory monitor");
|
log::info!("Starting polling-based local game directory monitor");
|
||||||
|
|
||||||
let mut snapshot = initial_poll_snapshot(&ctx).await;
|
let mut snapshot = initial_poll_snapshot(&ctx).await;
|
||||||
@@ -171,7 +168,7 @@ async fn initial_poll_snapshot(ctx: &Ctx) -> Option<PollSnapshot> {
|
|||||||
|
|
||||||
async fn poll_local_game_changes(
|
async fn poll_local_game_changes(
|
||||||
ctx: &Ctx,
|
ctx: &Ctx,
|
||||||
tx_notify_ui: &UnboundedSender<PeerEvent>,
|
tx_notify_ui: &PeerEventSender,
|
||||||
gate: &RescanGate,
|
gate: &RescanGate,
|
||||||
rescans: &mut JoinSet<()>,
|
rescans: &mut JoinSet<()>,
|
||||||
previous: &mut Option<PollSnapshot>,
|
previous: &mut Option<PollSnapshot>,
|
||||||
@@ -328,7 +325,7 @@ fn changed_game_ids(previous: &PollSnapshot, current: &PollSnapshot) -> BTreeSet
|
|||||||
|
|
||||||
async fn queue_changed_games(
|
async fn queue_changed_games(
|
||||||
ctx: &Ctx,
|
ctx: &Ctx,
|
||||||
tx_notify_ui: &UnboundedSender<PeerEvent>,
|
tx_notify_ui: &PeerEventSender,
|
||||||
gate: &RescanGate,
|
gate: &RescanGate,
|
||||||
rescans: &mut JoinSet<()>,
|
rescans: &mut JoinSet<()>,
|
||||||
changed_ids: BTreeSet<String>,
|
changed_ids: BTreeSet<String>,
|
||||||
@@ -354,7 +351,7 @@ async fn queue_changed_games(
|
|||||||
|
|
||||||
async fn queue_rescan(
|
async fn queue_rescan(
|
||||||
ctx: &Ctx,
|
ctx: &Ctx,
|
||||||
tx_notify_ui: &UnboundedSender<PeerEvent>,
|
tx_notify_ui: &PeerEventSender,
|
||||||
gate: &RescanGate,
|
gate: &RescanGate,
|
||||||
rescans: &mut JoinSet<()>,
|
rescans: &mut JoinSet<()>,
|
||||||
id: String,
|
id: String,
|
||||||
@@ -376,12 +373,7 @@ async fn queue_rescan(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn run_gated_rescan(
|
async fn run_gated_rescan(ctx: Ctx, tx_notify_ui: PeerEventSender, gate: RescanGate, id: String) {
|
||||||
ctx: Ctx,
|
|
||||||
tx_notify_ui: UnboundedSender<PeerEvent>,
|
|
||||||
gate: RescanGate,
|
|
||||||
id: String,
|
|
||||||
) {
|
|
||||||
loop {
|
loop {
|
||||||
gate.pending.write().await.remove(&id);
|
gate.pending.write().await.remove(&id);
|
||||||
|
|
||||||
@@ -418,7 +410,7 @@ async fn run_gated_rescan(
|
|||||||
gate.running.write().await.remove(&id);
|
gate.running.write().await.remove(&id);
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn run_fallback_scan(ctx: &Ctx, tx_notify_ui: &UnboundedSender<PeerEvent>) {
|
async fn run_fallback_scan(ctx: &Ctx, tx_notify_ui: &PeerEventSender) {
|
||||||
let _admission = ctx.operation_admission.lock().await;
|
let _admission = ctx.operation_admission.lock().await;
|
||||||
let game_dir = ctx.game_dir.read().await.clone();
|
let game_dir = ctx.game_dir.read().await.clone();
|
||||||
let catalog = ctx.catalog.catalog();
|
let catalog = ctx.catalog.catalog();
|
||||||
@@ -445,11 +437,12 @@ mod tests {
|
|||||||
};
|
};
|
||||||
|
|
||||||
use lanspread_db::content_manifest::CatalogBundle;
|
use lanspread_db::content_manifest::CatalogBundle;
|
||||||
use tokio::sync::{RwLock, mpsc};
|
use tokio::sync::RwLock;
|
||||||
use tokio_util::{sync::CancellationToken, task::TaskTracker};
|
use tokio_util::{sync::CancellationToken, task::TaskTracker};
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::{
|
use crate::{
|
||||||
|
PeerEvent,
|
||||||
UnpackFuture,
|
UnpackFuture,
|
||||||
Unpacker,
|
Unpacker,
|
||||||
context::OperationKind,
|
context::OperationKind,
|
||||||
@@ -512,9 +505,7 @@ mod tests {
|
|||||||
panic!("injected monitor loop panic");
|
panic!("injected monitor loop panic");
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn recv_local_update(
|
async fn recv_local_update(rx: &mut crate::PeerEventReceiver) -> Vec<lanspread_db::db::Game> {
|
||||||
rx: &mut mpsc::UnboundedReceiver<PeerEvent>,
|
|
||||||
) -> Vec<lanspread_db::db::Game> {
|
|
||||||
let event = tokio::time::timeout(Duration::from_secs(1), rx.recv())
|
let event = tokio::time::timeout(Duration::from_secs(1), rx.recv())
|
||||||
.await
|
.await
|
||||||
.expect("local update event should arrive")
|
.expect("local update event should arrive")
|
||||||
@@ -604,7 +595,7 @@ mod tests {
|
|||||||
.insert("game".to_string(), OperationKind::Downloading);
|
.insert("game".to_string(), OperationKind::Downloading);
|
||||||
let rescan_gate = RescanGate::default();
|
let rescan_gate = RescanGate::default();
|
||||||
let mut rescans = JoinSet::new();
|
let mut rescans = JoinSet::new();
|
||||||
let (tx, mut rx) = mpsc::unbounded_channel();
|
let (tx, mut rx) = crate::peer_event_channel();
|
||||||
|
|
||||||
queue_changed_games(
|
queue_changed_games(
|
||||||
&ctx,
|
&ctx,
|
||||||
@@ -635,7 +626,7 @@ mod tests {
|
|||||||
);
|
);
|
||||||
let rescan_gate = RescanGate::default();
|
let rescan_gate = RescanGate::default();
|
||||||
let mut rescans = JoinSet::new();
|
let mut rescans = JoinSet::new();
|
||||||
let (tx, mut rx) = mpsc::unbounded_channel();
|
let (tx, mut rx) = crate::peer_event_channel();
|
||||||
let mut state = Some(
|
let mut state = Some(
|
||||||
capture_poll_snapshot(&ctx)
|
capture_poll_snapshot(&ctx)
|
||||||
.await
|
.await
|
||||||
@@ -669,7 +660,7 @@ mod tests {
|
|||||||
);
|
);
|
||||||
let gate = RescanGate::default();
|
let gate = RescanGate::default();
|
||||||
let mut rescans = JoinSet::new();
|
let mut rescans = JoinSet::new();
|
||||||
let (tx, mut rx) = mpsc::unbounded_channel();
|
let (tx, mut rx) = crate::peer_event_channel();
|
||||||
|
|
||||||
let library_guard = ctx.local_library.write().await;
|
let library_guard = ctx.local_library.write().await;
|
||||||
queue_rescan(&ctx, &tx, &gate, &mut rescans, "game".to_string()).await;
|
queue_rescan(&ctx, &tx, &gate, &mut rescans, "game".to_string()).await;
|
||||||
@@ -707,7 +698,7 @@ mod tests {
|
|||||||
);
|
);
|
||||||
let gate = RescanGate::default();
|
let gate = RescanGate::default();
|
||||||
let mut rescans = JoinSet::new();
|
let mut rescans = JoinSet::new();
|
||||||
let (tx, mut rx) = mpsc::unbounded_channel();
|
let (tx, mut rx) = crate::peer_event_channel();
|
||||||
let admission = ctx.operation_admission.lock().await;
|
let admission = ctx.operation_admission.lock().await;
|
||||||
|
|
||||||
queue_rescan(&ctx, &tx, &gate, &mut rescans, "game".to_string()).await;
|
queue_rescan(&ctx, &tx, &gate, &mut rescans, "game".to_string()).await;
|
||||||
@@ -751,7 +742,7 @@ mod tests {
|
|||||||
);
|
);
|
||||||
let gate = RescanGate::default();
|
let gate = RescanGate::default();
|
||||||
let mut rescans = JoinSet::new();
|
let mut rescans = JoinSet::new();
|
||||||
let (tx, mut rx) = mpsc::unbounded_channel();
|
let (tx, mut rx) = crate::peer_event_channel();
|
||||||
let admission = ctx.operation_admission.lock().await;
|
let admission = ctx.operation_admission.lock().await;
|
||||||
|
|
||||||
queue_rescan(&ctx, &tx, &gate, &mut rescans, "game".to_string()).await;
|
queue_rescan(&ctx, &tx, &gate, &mut rescans, "game".to_string()).await;
|
||||||
@@ -792,7 +783,7 @@ mod tests {
|
|||||||
let temp = TempDir::new("lanspread-local-monitor-structured-shutdown");
|
let temp = TempDir::new("lanspread-local-monitor-structured-shutdown");
|
||||||
let ctx = test_ctx(temp.path().to_path_buf(), empty_catalog_bundle());
|
let ctx = test_ctx(temp.path().to_path_buf(), empty_catalog_bundle());
|
||||||
let monitor_ctx = ctx.clone();
|
let monitor_ctx = ctx.clone();
|
||||||
let (tx, _rx) = mpsc::unbounded_channel();
|
let (tx, _rx) = crate::peer_event_channel();
|
||||||
let monitor = tokio::spawn(run_local_game_monitor(tx, monitor_ctx));
|
let monitor = tokio::spawn(run_local_game_monitor(tx, monitor_ctx));
|
||||||
|
|
||||||
tokio::task::yield_now().await;
|
tokio::task::yield_now().await;
|
||||||
@@ -813,7 +804,7 @@ mod tests {
|
|||||||
temp.path().to_path_buf(),
|
temp.path().to_path_buf(),
|
||||||
catalog_bundle([("game", "20250101")]),
|
catalog_bundle([("game", "20250101")]),
|
||||||
);
|
);
|
||||||
let (tx, mut rx) = mpsc::unbounded_channel();
|
let (tx, mut rx) = crate::peer_event_channel();
|
||||||
|
|
||||||
run_fallback_scan(&ctx, &tx).await;
|
run_fallback_scan(&ctx, &tx).await;
|
||||||
|
|
||||||
@@ -837,7 +828,7 @@ mod tests {
|
|||||||
temp.path().to_path_buf(),
|
temp.path().to_path_buf(),
|
||||||
catalog_bundle([("game", "20250101")]),
|
catalog_bundle([("game", "20250101")]),
|
||||||
);
|
);
|
||||||
let (tx, mut rx) = mpsc::unbounded_channel();
|
let (tx, mut rx) = crate::peer_event_channel();
|
||||||
|
|
||||||
run_fallback_scan(&ctx, &tx).await;
|
run_fallback_scan(&ctx, &tx).await;
|
||||||
|
|
||||||
|
|||||||
@@ -10,12 +10,13 @@ use lanspread_proto::{
|
|||||||
PeerStateSnapshot,
|
PeerStateSnapshot,
|
||||||
RuntimeSessionId,
|
RuntimeSessionId,
|
||||||
};
|
};
|
||||||
use tokio::sync::{RwLock, mpsc::UnboundedSender};
|
use tokio::sync::RwLock;
|
||||||
use tokio_util::sync::CancellationToken;
|
use tokio_util::sync::CancellationToken;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
CallToPlayView,
|
CallToPlayView,
|
||||||
PeerEvent,
|
PeerEvent,
|
||||||
|
PeerEventSender,
|
||||||
call_to_play::{
|
call_to_play::{
|
||||||
CallToPlayPublication,
|
CallToPlayPublication,
|
||||||
CallToPlayStore,
|
CallToPlayStore,
|
||||||
@@ -45,7 +46,7 @@ use crate::{
|
|||||||
pub(crate) struct RemoteStateCtx {
|
pub(crate) struct RemoteStateCtx {
|
||||||
local_peer_id: PeerId,
|
local_peer_id: PeerId,
|
||||||
peer_game_db: Arc<RwLock<PeerGameDB>>,
|
peer_game_db: Arc<RwLock<PeerGameDB>>,
|
||||||
tx_notify_ui: UnboundedSender<PeerEvent>,
|
tx_notify_ui: PeerEventSender,
|
||||||
call_to_play: Arc<RwLock<CallToPlayStore>>,
|
call_to_play: Arc<RwLock<CallToPlayStore>>,
|
||||||
quic: QuicConnector,
|
quic: QuicConnector,
|
||||||
cancellation: CancellationToken,
|
cancellation: CancellationToken,
|
||||||
@@ -56,7 +57,7 @@ impl RemoteStateCtx {
|
|||||||
#[must_use]
|
#[must_use]
|
||||||
pub(crate) fn from_network(
|
pub(crate) fn from_network(
|
||||||
ctx: &NetworkServiceCtx,
|
ctx: &NetworkServiceCtx,
|
||||||
tx_notify_ui: &UnboundedSender<PeerEvent>,
|
tx_notify_ui: &PeerEventSender,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
local_peer_id: ctx.peer_id,
|
local_peer_id: ctx.peer_id,
|
||||||
@@ -310,7 +311,7 @@ fn log_call_to_play_outcome(peer_id: PeerId, outcome: &ObserveRemoteAuthorOutcom
|
|||||||
fn enqueue_commit_transition(
|
fn enqueue_commit_transition(
|
||||||
db: &PeerGameDB,
|
db: &PeerGameDB,
|
||||||
call_to_play: &mut CallToPlayStore,
|
call_to_play: &mut CallToPlayStore,
|
||||||
tx_notify_ui: &UnboundedSender<PeerEvent>,
|
tx_notify_ui: &PeerEventSender,
|
||||||
state_sync: &StateSyncHandle,
|
state_sync: &StateSyncHandle,
|
||||||
publication: PreparedCallToPlayPublication,
|
publication: PreparedCallToPlayPublication,
|
||||||
endpoint: PeerEndpoint,
|
endpoint: PeerEndpoint,
|
||||||
@@ -351,7 +352,7 @@ fn enqueue_commit_transition(
|
|||||||
fn enqueue_final_views(
|
fn enqueue_final_views(
|
||||||
db: &PeerGameDB,
|
db: &PeerGameDB,
|
||||||
call_to_play: &mut CallToPlayStore,
|
call_to_play: &mut CallToPlayStore,
|
||||||
tx_notify_ui: &UnboundedSender<PeerEvent>,
|
tx_notify_ui: &PeerEventSender,
|
||||||
state_sync: &StateSyncHandle,
|
state_sync: &StateSyncHandle,
|
||||||
publication: PreparedCallToPlayPublication,
|
publication: PreparedCallToPlayPublication,
|
||||||
) {
|
) {
|
||||||
@@ -368,11 +369,11 @@ fn enqueue_final_views(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Clears every remote projection after one network generation has fully
|
/// Clears every remote projection after one network generation has fully
|
||||||
/// drained, then queues the authoritative empty replacement views while the
|
/// drained, then queues the authoritative replacement views while the
|
||||||
/// database-to-Call-to-Play lock order is still held.
|
/// database-to-Call-to-Play lock order is still held.
|
||||||
pub(crate) async fn clear_remote_state_and_publish(
|
pub(crate) async fn clear_remote_state_and_publish(
|
||||||
ctx: &Ctx,
|
ctx: &Ctx,
|
||||||
tx_notify_ui: &UnboundedSender<PeerEvent>,
|
tx_notify_ui: &PeerEventSender,
|
||||||
) {
|
) {
|
||||||
let mut db = ctx.peer_game_db.write().await;
|
let mut db = ctx.peer_game_db.write().await;
|
||||||
let mut call_to_play = ctx.call_to_play.write().await;
|
let mut call_to_play = ctx.call_to_play.write().await;
|
||||||
@@ -383,11 +384,14 @@ pub(crate) async fn clear_remote_state_and_publish(
|
|||||||
events::send(tx_notify_ui, PeerEvent::PeerLost(endpoint));
|
events::send(tx_notify_ui, PeerEvent::PeerLost(endpoint));
|
||||||
}
|
}
|
||||||
events::send(tx_notify_ui, PeerEvent::PeerCountUpdated(0));
|
events::send(tx_notify_ui, PeerEvent::PeerCountUpdated(0));
|
||||||
events::send(
|
ctx.state_sync
|
||||||
tx_notify_ui,
|
.publish_call_to_play_revision(publication.local_revision);
|
||||||
PeerEvent::RemoteLibraryView(events::remote_library_view(&db)),
|
if let Err(error) = tx_notify_ui.publish_view_generation(
|
||||||
);
|
events::remote_library_view(&db),
|
||||||
enqueue_call_to_play_publication(publication, tx_notify_ui, &ctx.state_sync, true);
|
CallToPlayView::from(publication.view),
|
||||||
|
) {
|
||||||
|
log::error!("Failed to publish cleared peer view generation: {error}");
|
||||||
|
}
|
||||||
|
|
||||||
if let Some(error) = preparation_error {
|
if let Some(error) = preparation_error {
|
||||||
log::warn!(
|
log::warn!(
|
||||||
@@ -398,7 +402,7 @@ pub(crate) async fn clear_remote_state_and_publish(
|
|||||||
|
|
||||||
fn enqueue_local_prune_if_changed(
|
fn enqueue_local_prune_if_changed(
|
||||||
call_to_play: &mut CallToPlayStore,
|
call_to_play: &mut CallToPlayStore,
|
||||||
tx_notify_ui: &UnboundedSender<PeerEvent>,
|
tx_notify_ui: &PeerEventSender,
|
||||||
state_sync: &StateSyncHandle,
|
state_sync: &StateSyncHandle,
|
||||||
publication: PreparedCallToPlayPublication,
|
publication: PreparedCallToPlayPublication,
|
||||||
) {
|
) {
|
||||||
@@ -411,7 +415,7 @@ fn enqueue_local_prune_if_changed(
|
|||||||
|
|
||||||
fn enqueue_call_to_play_publication(
|
fn enqueue_call_to_play_publication(
|
||||||
publication: CallToPlayPublication,
|
publication: CallToPlayPublication,
|
||||||
tx_notify_ui: &UnboundedSender<PeerEvent>,
|
tx_notify_ui: &PeerEventSender,
|
||||||
state_sync: &StateSyncHandle,
|
state_sync: &StateSyncHandle,
|
||||||
force_view: bool,
|
force_view: bool,
|
||||||
) {
|
) {
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ use tokio::sync::{Mutex, mpsc, watch};
|
|||||||
use tokio_util::sync::CancellationToken;
|
use tokio_util::sync::CancellationToken;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
PeerEvent,
|
PeerEventSender,
|
||||||
context::NetworkServiceCtx,
|
context::NetworkServiceCtx,
|
||||||
network::{send_call_to_play_changed, send_library_changed},
|
network::{send_call_to_play_changed, send_library_changed},
|
||||||
peer_db::PeerRevisionSnapshot,
|
peer_db::PeerRevisionSnapshot,
|
||||||
@@ -170,7 +170,7 @@ type FanoutFuture = Pin<Box<dyn Future<Output = ()> + Send>>;
|
|||||||
/// Runs the bounded pull scheduler and local hint fanout in one lexical scope.
|
/// Runs the bounded pull scheduler and local hint fanout in one lexical scope.
|
||||||
pub(crate) async fn run_state_sync(
|
pub(crate) async fn run_state_sync(
|
||||||
ctx: NetworkServiceCtx,
|
ctx: NetworkServiceCtx,
|
||||||
tx_notify_ui: tokio::sync::mpsc::UnboundedSender<PeerEvent>,
|
tx_notify_ui: PeerEventSender,
|
||||||
cancellation: CancellationToken,
|
cancellation: CancellationToken,
|
||||||
) -> eyre::Result<()> {
|
) -> eyre::Result<()> {
|
||||||
let mut pinned_rx = ctx.state_sync.inbox.pinned_rx.lock().await;
|
let mut pinned_rx = ctx.state_sync.inbox.pinned_rx.lock().await;
|
||||||
|
|||||||
@@ -504,7 +504,7 @@ mod tests {
|
|||||||
CatalogContentManifestBody,
|
CatalogContentManifestBody,
|
||||||
CatalogExtractedEntry,
|
CatalogExtractedEntry,
|
||||||
};
|
};
|
||||||
use tokio::sync::{RwLock, mpsc};
|
use tokio::sync::RwLock;
|
||||||
use tokio_util::task::TaskTracker;
|
use tokio_util::task::TaskTracker;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
@@ -611,7 +611,7 @@ mod tests {
|
|||||||
root: &Path,
|
root: &Path,
|
||||||
manifest: &CatalogContentManifest,
|
manifest: &CatalogContentManifest,
|
||||||
provider: Arc<CountingStreamInstallProvider>,
|
provider: Arc<CountingStreamInstallProvider>,
|
||||||
) -> (PeerCtx, mpsc::UnboundedReceiver<crate::PeerEvent>) {
|
) -> (PeerCtx, crate::PeerEventReceiver) {
|
||||||
let catalog = Arc::new(
|
let catalog = Arc::new(
|
||||||
CatalogBundle::from_manifests([manifest.clone()])
|
CatalogBundle::from_manifests([manifest.clone()])
|
||||||
.expect("test catalog should be complete"),
|
.expect("test catalog should be complete"),
|
||||||
@@ -643,7 +643,7 @@ mod tests {
|
|||||||
availability: Availability::Ready,
|
availability: Availability::Ready,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
let (tx, rx) = mpsc::unbounded_channel();
|
let (tx, rx) = crate::peer_event_channel();
|
||||||
(ctx.to_peer_ctx(tx, CancellationToken::new()), rx)
|
(ctx.to_peer_ctx(tx, CancellationToken::new()), rx)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -23,9 +23,9 @@ use tokio_util::{sync::CancellationToken, task::TaskTracker};
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
PeerCommand,
|
PeerCommand,
|
||||||
PeerEvent,
|
|
||||||
PeerId,
|
PeerId,
|
||||||
PeerIdentityDurability,
|
PeerIdentityDurability,
|
||||||
|
PeerEventSender,
|
||||||
PeerRuntimeComponent,
|
PeerRuntimeComponent,
|
||||||
StreamInstallProvider,
|
StreamInstallProvider,
|
||||||
Unpacker,
|
Unpacker,
|
||||||
@@ -284,7 +284,7 @@ pub(crate) enum SupervisionPolicy {
|
|||||||
pub(crate) fn spawn_peer_runtime(
|
pub(crate) fn spawn_peer_runtime(
|
||||||
tx_control: UnboundedSender<PeerCommand>,
|
tx_control: UnboundedSender<PeerCommand>,
|
||||||
rx_control: UnboundedReceiver<PeerCommand>,
|
rx_control: UnboundedReceiver<PeerCommand>,
|
||||||
tx_notify_ui: UnboundedSender<PeerEvent>,
|
tx_notify_ui: PeerEventSender,
|
||||||
peer_game_db: Arc<RwLock<PeerGameDB>>,
|
peer_game_db: Arc<RwLock<PeerGameDB>>,
|
||||||
peer_identity: Arc<PeerIdentity>,
|
peer_identity: Arc<PeerIdentity>,
|
||||||
identity_durability: PeerIdentityDurability,
|
identity_durability: PeerIdentityDurability,
|
||||||
@@ -380,11 +380,11 @@ async fn run_runtime_root<Root, Cleanup>(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn spawn_core_services(ctx: &Ctx, tx_notify_ui: &UnboundedSender<PeerEvent>) {
|
pub(crate) fn spawn_core_services(ctx: &Ctx, tx_notify_ui: &PeerEventSender) {
|
||||||
spawn_local_library_monitor(ctx, tx_notify_ui);
|
spawn_local_library_monitor(ctx, tx_notify_ui);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn spawn_local_library_monitor(ctx: &Ctx, tx_notify_ui: &UnboundedSender<PeerEvent>) {
|
fn spawn_local_library_monitor(ctx: &Ctx, tx_notify_ui: &PeerEventSender) {
|
||||||
let ctx = ctx.clone();
|
let ctx = ctx.clone();
|
||||||
let tx_notify_ui = tx_notify_ui.clone();
|
let tx_notify_ui = tx_notify_ui.clone();
|
||||||
let task_tracker = ctx.task_tracker.clone();
|
let task_tracker = ctx.task_tracker.clone();
|
||||||
@@ -408,7 +408,7 @@ fn spawn_local_library_monitor(ctx: &Ctx, tx_notify_ui: &UnboundedSender<PeerEve
|
|||||||
fn spawn_supervised_service<F, Fut>(
|
fn spawn_supervised_service<F, Fut>(
|
||||||
task_tracker: &TaskTracker,
|
task_tracker: &TaskTracker,
|
||||||
shutdown: &CancellationToken,
|
shutdown: &CancellationToken,
|
||||||
tx_notify_ui: &UnboundedSender<PeerEvent>,
|
tx_notify_ui: &PeerEventSender,
|
||||||
component: PeerRuntimeComponent,
|
component: PeerRuntimeComponent,
|
||||||
policy: SupervisionPolicy,
|
policy: SupervisionPolicy,
|
||||||
mut make_service: F,
|
mut make_service: F,
|
||||||
@@ -505,13 +505,16 @@ fn spawn_supervised_service<F, Fut>(
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
fn report_required_service_failure(
|
fn report_required_service_failure(
|
||||||
tx_notify_ui: &UnboundedSender<PeerEvent>,
|
tx_notify_ui: &PeerEventSender,
|
||||||
component: PeerRuntimeComponent,
|
component: PeerRuntimeComponent,
|
||||||
error: String,
|
error: String,
|
||||||
shutdown: &CancellationToken,
|
shutdown: &CancellationToken,
|
||||||
) {
|
) {
|
||||||
log::error!("{component:?} failed: {error}");
|
log::error!("{component:?} failed: {error}");
|
||||||
crate::events::send(tx_notify_ui, PeerEvent::RuntimeFailed { component, error });
|
crate::events::send(
|
||||||
|
tx_notify_ui,
|
||||||
|
crate::PeerEvent::RuntimeFailed { component, error },
|
||||||
|
);
|
||||||
shutdown.cancel();
|
shutdown.cancel();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -611,7 +614,7 @@ mod tests {
|
|||||||
async fn required_service_failure_cancels_runtime_and_emits_event() {
|
async fn required_service_failure_cancels_runtime_and_emits_event() {
|
||||||
let tracker = TaskTracker::new();
|
let tracker = TaskTracker::new();
|
||||||
let shutdown = CancellationToken::new();
|
let shutdown = CancellationToken::new();
|
||||||
let (tx, mut rx) = tokio::sync::mpsc::unbounded_channel();
|
let (tx, mut rx) = crate::peer_event_channel();
|
||||||
|
|
||||||
spawn_supervised_service(
|
spawn_supervised_service(
|
||||||
&tracker,
|
&tracker,
|
||||||
@@ -646,7 +649,7 @@ mod tests {
|
|||||||
async fn restart_service_restarts_until_shutdown() {
|
async fn restart_service_restarts_until_shutdown() {
|
||||||
let tracker = TaskTracker::new();
|
let tracker = TaskTracker::new();
|
||||||
let shutdown = CancellationToken::new();
|
let shutdown = CancellationToken::new();
|
||||||
let (tx, _rx) = tokio::sync::mpsc::unbounded_channel();
|
let (tx, _rx) = crate::peer_event_channel();
|
||||||
let attempts = Arc::new(AtomicUsize::new(0));
|
let attempts = Arc::new(AtomicUsize::new(0));
|
||||||
|
|
||||||
spawn_supervised_service(
|
spawn_supervised_service(
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ use s2n_quic::{
|
|||||||
use tokio::{
|
use tokio::{
|
||||||
io::{AsyncRead, AsyncReadExt},
|
io::{AsyncRead, AsyncReadExt},
|
||||||
process::Command,
|
process::Command,
|
||||||
sync::{mpsc, mpsc::UnboundedSender},
|
sync::mpsc,
|
||||||
time::{self, Instant as TokioInstant, MissedTickBehavior},
|
time::{self, Instant as TokioInstant, MissedTickBehavior},
|
||||||
};
|
};
|
||||||
use tokio_util::{
|
use tokio_util::{
|
||||||
@@ -47,6 +47,7 @@ use tokio_util::{
|
|||||||
use crate::{
|
use crate::{
|
||||||
DownloadProgress,
|
DownloadProgress,
|
||||||
PeerEvent,
|
PeerEvent,
|
||||||
|
PeerEventSender,
|
||||||
install::root_eti_archives,
|
install::root_eti_archives,
|
||||||
network::connect_to_peer,
|
network::connect_to_peer,
|
||||||
path_validation::validate_game_file_path,
|
path_validation::validate_game_file_path,
|
||||||
@@ -1274,7 +1275,7 @@ impl StreamInstallReceiveState {
|
|||||||
game_id: &str,
|
game_id: &str,
|
||||||
peer_endpoint: PeerEndpoint,
|
peer_endpoint: PeerEndpoint,
|
||||||
content_id: ContentId,
|
content_id: ContentId,
|
||||||
tx_notify_ui: &UnboundedSender<PeerEvent>,
|
tx_notify_ui: &PeerEventSender,
|
||||||
) -> StreamInstallReceiveResult<ReceiveFrameOutcome> {
|
) -> StreamInstallReceiveResult<ReceiveFrameOutcome> {
|
||||||
match frame {
|
match frame {
|
||||||
StreamInstallFrame::ArchiveBegin {
|
StreamInstallFrame::ArchiveBegin {
|
||||||
@@ -1381,7 +1382,7 @@ pub(crate) struct ReceiveStreamedInstallRequest<'a> {
|
|||||||
pub(crate) manifest: Arc<CatalogContentManifest>,
|
pub(crate) manifest: Arc<CatalogContentManifest>,
|
||||||
pub(crate) staging_dir: &'a Path,
|
pub(crate) staging_dir: &'a Path,
|
||||||
pub(crate) attempt: DownloadAttemptReporter,
|
pub(crate) attempt: DownloadAttemptReporter,
|
||||||
pub(crate) tx_notify_ui: UnboundedSender<PeerEvent>,
|
pub(crate) tx_notify_ui: PeerEventSender,
|
||||||
pub(crate) quic: &'a QuicConnector,
|
pub(crate) quic: &'a QuicConnector,
|
||||||
pub(crate) cancel_token: CancellationToken,
|
pub(crate) cancel_token: CancellationToken,
|
||||||
}
|
}
|
||||||
@@ -1854,7 +1855,7 @@ impl<W: std::io::Write> IncomingFile<W> {
|
|||||||
game_id: &str,
|
game_id: &str,
|
||||||
peer_endpoint: PeerEndpoint,
|
peer_endpoint: PeerEndpoint,
|
||||||
content_id: ContentId,
|
content_id: ContentId,
|
||||||
tx_notify_ui: &UnboundedSender<PeerEvent>,
|
tx_notify_ui: &PeerEventSender,
|
||||||
) -> StreamInstallReceiveResult<()> {
|
) -> StreamInstallReceiveResult<()> {
|
||||||
if &self.relative_path != relative_path {
|
if &self.relative_path != relative_path {
|
||||||
return Err(StreamInstallReceiveError::integrity(eyre::eyre!(
|
return Err(StreamInstallReceiveError::integrity(eyre::eyre!(
|
||||||
@@ -3051,7 +3052,7 @@ mod tests {
|
|||||||
.begin_archive(&canonical_path("a.eti"), 1)
|
.begin_archive(&canonical_path("a.eti"), 1)
|
||||||
.expect("sender telemetry need not equal the catalog-owned total");
|
.expect("sender telemetry need not equal the catalog-owned total");
|
||||||
|
|
||||||
let (tx, _rx) = tokio::sync::mpsc::unbounded_channel();
|
let (tx, _rx) = crate::peer_event_channel();
|
||||||
let status = crate::transfer_status::DownloadAttemptStatus::new(
|
let status = crate::transfer_status::DownloadAttemptStatus::new(
|
||||||
crate::DownloadAttemptKey::next("game".to_owned()),
|
crate::DownloadAttemptKey::next("game".to_owned()),
|
||||||
CancellationToken::new(),
|
CancellationToken::new(),
|
||||||
@@ -3146,7 +3147,7 @@ mod tests {
|
|||||||
let payload = b"payload";
|
let payload = b"payload";
|
||||||
let endpoint = peer_endpoint();
|
let endpoint = peer_endpoint();
|
||||||
let exact_content_id = content_id();
|
let exact_content_id = content_id();
|
||||||
let (event_tx, mut event_rx) = tokio::sync::mpsc::unbounded_channel();
|
let (event_tx, mut event_rx) = crate::peer_event_channel();
|
||||||
let mut accepted = IncomingFile::new(
|
let mut accepted = IncomingFile::new(
|
||||||
canonical_path("accepted.bin"),
|
canonical_path("accepted.bin"),
|
||||||
PathBuf::from("accepted.bin"),
|
PathBuf::from("accepted.bin"),
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ use std::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
use serde::{Deserialize, Deserializer, Serialize, Serializer, de};
|
use serde::{Deserialize, Deserializer, Serialize, Serializer, de};
|
||||||
use tokio::sync::mpsc::UnboundedSender;
|
use crate::PeerEventSender;
|
||||||
use tokio_util::sync::CancellationToken;
|
use tokio_util::sync::CancellationToken;
|
||||||
|
|
||||||
use crate::{DownloadProgress, PeerEvent, events};
|
use crate::{DownloadProgress, PeerEvent, events};
|
||||||
@@ -140,7 +140,7 @@ struct AttemptRuntimeState {
|
|||||||
struct DownloadAttemptState {
|
struct DownloadAttemptState {
|
||||||
key: DownloadAttemptKey,
|
key: DownloadAttemptKey,
|
||||||
cancellation: CancellationToken,
|
cancellation: CancellationToken,
|
||||||
tx_notify_ui: UnboundedSender<PeerEvent>,
|
tx_notify_ui: PeerEventSender,
|
||||||
runtime: Mutex<AttemptRuntimeState>,
|
runtime: Mutex<AttemptRuntimeState>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -153,7 +153,7 @@ impl DownloadAttemptStatus {
|
|||||||
pub(crate) fn new(
|
pub(crate) fn new(
|
||||||
key: DownloadAttemptKey,
|
key: DownloadAttemptKey,
|
||||||
cancellation: CancellationToken,
|
cancellation: CancellationToken,
|
||||||
tx_notify_ui: UnboundedSender<PeerEvent>,
|
tx_notify_ui: PeerEventSender,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
state: Arc::new(DownloadAttemptState {
|
state: Arc::new(DownloadAttemptState {
|
||||||
@@ -459,9 +459,9 @@ mod tests {
|
|||||||
|
|
||||||
fn attempt() -> (
|
fn attempt() -> (
|
||||||
DownloadAttemptStatus,
|
DownloadAttemptStatus,
|
||||||
tokio::sync::mpsc::UnboundedReceiver<PeerEvent>,
|
crate::PeerEventReceiver,
|
||||||
) {
|
) {
|
||||||
let (tx, rx) = tokio::sync::mpsc::unbounded_channel();
|
let (tx, rx) = crate::peer_event_channel();
|
||||||
(
|
(
|
||||||
DownloadAttemptStatus::new(
|
DownloadAttemptStatus::new(
|
||||||
DownloadAttemptKey::next("game".to_owned()),
|
DownloadAttemptKey::next("game".to_owned()),
|
||||||
|
|||||||
@@ -35,6 +35,8 @@ use lanspread_peer::{
|
|||||||
NoopStreamInstallProvider,
|
NoopStreamInstallProvider,
|
||||||
PeerCommand,
|
PeerCommand,
|
||||||
PeerEvent,
|
PeerEvent,
|
||||||
|
PeerEventReceiver,
|
||||||
|
PeerEventSender,
|
||||||
PeerGameDB,
|
PeerGameDB,
|
||||||
PeerIdentity,
|
PeerIdentity,
|
||||||
PeerIdentityDurability,
|
PeerIdentityDurability,
|
||||||
@@ -47,6 +49,7 @@ use lanspread_peer::{
|
|||||||
UnpackFuture,
|
UnpackFuture,
|
||||||
Unpacker,
|
Unpacker,
|
||||||
migrate_legacy_state,
|
migrate_legacy_state,
|
||||||
|
peer_event_channel,
|
||||||
scoped_blocking,
|
scoped_blocking,
|
||||||
start_peer_with_options,
|
start_peer_with_options,
|
||||||
};
|
};
|
||||||
@@ -687,7 +690,7 @@ struct InstallSettings {
|
|||||||
language: String,
|
language: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct PeerEventTx(UnboundedSender<PeerEvent>);
|
struct PeerEventTx(PeerEventSender);
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, serde::Serialize)]
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, serde::Serialize)]
|
||||||
enum UiOperationKind {
|
enum UiOperationKind {
|
||||||
@@ -4086,7 +4089,7 @@ fn emit_game_id_event(app_handle: &AppHandle, event: &str, id: &str, label: &str
|
|||||||
|
|
||||||
fn spawn_peer_event_loop(
|
fn spawn_peer_event_loop(
|
||||||
app_handle: AppHandle,
|
app_handle: AppHandle,
|
||||||
mut rx_peer_event: UnboundedReceiver<PeerEvent>,
|
mut rx_peer_event: PeerEventReceiver,
|
||||||
mut rx_ui_state: UnboundedReceiver<UiStateCommand>,
|
mut rx_ui_state: UnboundedReceiver<UiStateCommand>,
|
||||||
) {
|
) {
|
||||||
let tasks = app_handle
|
let tasks = app_handle
|
||||||
@@ -4228,6 +4231,7 @@ fn set_identity_diagnostic_in_loop(
|
|||||||
Ok(next)
|
Ok(next)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
fn take_exactly_queued<T>(
|
fn take_exactly_queued<T>(
|
||||||
receiver: &mut UnboundedReceiver<T>,
|
receiver: &mut UnboundedReceiver<T>,
|
||||||
count: usize,
|
count: usize,
|
||||||
@@ -4243,15 +4247,17 @@ fn take_exactly_queued<T>(
|
|||||||
|
|
||||||
async fn drain_queued_peer_events(
|
async fn drain_queued_peer_events(
|
||||||
app_handle: &AppHandle,
|
app_handle: &AppHandle,
|
||||||
receiver: &mut UnboundedReceiver<PeerEvent>,
|
receiver: &mut PeerEventReceiver,
|
||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
// The core replies only after all events for the requested transition have
|
// The core replies only after all events for the requested transition have
|
||||||
// been enqueued. Once this UI command wins the fair select, snapshot the
|
// been enqueued. Once this UI command wins the fair select, hold the shared
|
||||||
// peer queue and process that exact FIFO prefix. Later autonomous traffic
|
// producer fence while draining every event already admitted, including a
|
||||||
// remains for the normal event-loop turn and cannot contaminate this
|
// coalesced pending view. Later autonomous traffic remains for the normal
|
||||||
// transition's acknowledgement boundary.
|
// event-loop turn and cannot contaminate this transition's acknowledgement
|
||||||
let count = receiver.len();
|
// boundary.
|
||||||
let queued = take_exactly_queued(receiver, count)?;
|
let queued = receiver
|
||||||
|
.drain_ready()
|
||||||
|
.map_err(|error| format!("peer-event queue changed while applying its fence: {error}"))?;
|
||||||
for event in queued {
|
for event in queued {
|
||||||
handle_peer_event(app_handle, event).await;
|
handle_peer_event(app_handle, event).await;
|
||||||
}
|
}
|
||||||
@@ -4260,7 +4266,7 @@ async fn drain_queued_peer_events(
|
|||||||
|
|
||||||
async fn fence_queued_peer_events(
|
async fn fence_queued_peer_events(
|
||||||
app_handle: &AppHandle,
|
app_handle: &AppHandle,
|
||||||
receiver: &mut UnboundedReceiver<PeerEvent>,
|
receiver: &mut PeerEventReceiver,
|
||||||
) -> Result<LocalNetworkSharingSnapshot, String> {
|
) -> Result<LocalNetworkSharingSnapshot, String> {
|
||||||
drain_queued_peer_events(app_handle, receiver).await?;
|
drain_queued_peer_events(app_handle, receiver).await?;
|
||||||
current_local_network_sharing(app_handle.state::<LanSpreadState>().inner())
|
current_local_network_sharing(app_handle.state::<LanSpreadState>().inner())
|
||||||
@@ -4268,7 +4274,7 @@ async fn fence_queued_peer_events(
|
|||||||
|
|
||||||
async fn reset_game_transfer_status_in_loop(
|
async fn reset_game_transfer_status_in_loop(
|
||||||
app_handle: &AppHandle,
|
app_handle: &AppHandle,
|
||||||
receiver: &mut UnboundedReceiver<PeerEvent>,
|
receiver: &mut PeerEventReceiver,
|
||||||
) -> Result<GameTransferStatusSnapshot, String> {
|
) -> Result<GameTransferStatusSnapshot, String> {
|
||||||
// A successful game-root command has already caused core to enqueue its
|
// A successful game-root command has already caused core to enqueue its
|
||||||
// preceding events. Drain that bounded prefix before terminalizing the old
|
// preceding events. Drain that bounded prefix before terminalizing the old
|
||||||
@@ -4290,7 +4296,7 @@ async fn reset_game_transfer_status_in_loop(
|
|||||||
async fn handle_ui_state_command(
|
async fn handle_ui_state_command(
|
||||||
app_handle: &AppHandle,
|
app_handle: &AppHandle,
|
||||||
command: UiStateCommand,
|
command: UiStateCommand,
|
||||||
peer_events: &mut UnboundedReceiver<PeerEvent>,
|
peer_events: &mut PeerEventReceiver,
|
||||||
) {
|
) {
|
||||||
match command {
|
match command {
|
||||||
UiStateCommand::MutateSharing { mutation, reply } => {
|
UiStateCommand::MutateSharing { mutation, reply } => {
|
||||||
@@ -4629,7 +4635,7 @@ struct ProtocolMismatchSnapshot {
|
|||||||
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
||||||
pub fn run() {
|
pub fn run() {
|
||||||
// channel to receive events from the peer
|
// channel to receive events from the peer
|
||||||
let (tx_peer_event, rx_peer_event) = tokio::sync::mpsc::unbounded_channel::<PeerEvent>();
|
let (tx_peer_event, rx_peer_event) = peer_event_channel();
|
||||||
let (tx_ui_state, rx_ui_state) = tokio::sync::mpsc::unbounded_channel::<UiStateCommand>();
|
let (tx_ui_state, rx_ui_state) = tokio::sync::mpsc::unbounded_channel::<UiStateCommand>();
|
||||||
|
|
||||||
tauri::Builder::default()
|
tauri::Builder::default()
|
||||||
|
|||||||
Reference in New Issue
Block a user