fix(session): confirm routed delivery and keep playback state with the owner
This commit is contained in:
@@ -16,18 +16,20 @@ public interface IPodMessageBus
|
||||
string PodId { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Sends a message to one instance and reports how many listeners took it, so that a message
|
||||
/// addressed to an instance that is no longer there is not mistaken for a delivered one.
|
||||
/// Sends a message to one instance and waits for that instance to report what it did with it. The
|
||||
/// number of subscribers only proves the target's connection to the broker is up, so delivery is
|
||||
/// taken from the acknowledgement of the instance that has to act on the message.
|
||||
/// </summary>
|
||||
/// <param name="targetPod">The instance to deliver to.</param>
|
||||
/// <param name="message">The message.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>The number of instances the message reached.</returns>
|
||||
Task<long> PublishAsync(string targetPod, PodMessage message, CancellationToken cancellationToken = default);
|
||||
/// <returns><c>true</c> if the target acknowledged having carried the message out.</returns>
|
||||
Task<bool> RequestAsync(string targetPod, PodMessage message, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Registers a handler for the messages addressed to this instance.
|
||||
/// Registers a handler for the messages addressed to this instance. Whatever the handler returns is
|
||||
/// sent back to the origin as the acknowledgement.
|
||||
/// </summary>
|
||||
/// <param name="handler">The handler.</param>
|
||||
void Subscribe(Func<PodMessage, Task> handler);
|
||||
void Subscribe(Func<PodMessage, Task<bool>> handler);
|
||||
}
|
||||
|
||||
@@ -10,16 +10,25 @@ namespace MediaBrowser.Controller.Session;
|
||||
/// </summary>
|
||||
public interface ISessionDirectory
|
||||
{
|
||||
/// <summary>
|
||||
/// Allocates the next connection epoch for a session. The counter lives in the shared store, so the
|
||||
/// epochs of every instance are ordered by one clock instead of being compared across machines.
|
||||
/// </summary>
|
||||
/// <param name="sessionId">The session identifier.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>The allocated epoch, which is greater than every epoch allocated for the session before it.</returns>
|
||||
Task<long> AllocateConnectionEpochAsync(string sessionId, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Claims a session for the publishing instance and restarts its expiry. The claim is refused when
|
||||
/// another instance holds the connection, so an instance that merely served a request for the session
|
||||
/// cannot take ownership of it.
|
||||
/// </summary>
|
||||
/// <param name="entry">The entry.</param>
|
||||
/// <param name="connectedUtcTicks">When the publishing instance's connection to the session was established, or zero when it holds none.</param>
|
||||
/// <param name="connectionEpoch">The epoch of the publishing instance's connection to the session, or zero when it holds none.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns><c>true</c> if the entry was written.</returns>
|
||||
Task<bool> PublishAsync(SessionDirectoryEntry entry, long connectedUtcTicks, CancellationToken cancellationToken = default);
|
||||
Task<bool> PublishAsync(SessionDirectoryEntry entry, long connectionEpoch, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Removes an entry, but only while the calling instance still owns it.
|
||||
@@ -36,6 +45,7 @@ public interface ISessionDirectory
|
||||
/// <param name="sessionId">The session identifier.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>The entry, or <c>null</c> when the session is in no instance's directory.</returns>
|
||||
/// <exception cref="System.Exception">The store could not be read. An unreadable store is not an absent session.</exception>
|
||||
Task<SessionDirectoryEntry?> GetAsync(string sessionId, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -260,7 +260,8 @@ namespace MediaBrowser.Controller.Session
|
||||
/// <param name="controllingSessionId">The controlling session identifier.</param>
|
||||
/// <param name="sessionId">The session identifier.</param>
|
||||
/// <param name="itemId">The item identifier.</param>
|
||||
void ReportNowViewingItem(string controllingSessionId, string sessionId, string itemId);
|
||||
/// <returns>A task representing the operation.</returns>
|
||||
Task ReportNowViewingItem(string controllingSessionId, string sessionId, string itemId);
|
||||
|
||||
/// <summary>
|
||||
/// Authenticates the new session.
|
||||
|
||||
@@ -18,11 +18,11 @@ public sealed class NullPodMessageBus : IPodMessageBus
|
||||
public string PodId => PodIdentity.Current;
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<long> PublishAsync(string targetPod, PodMessage message, CancellationToken cancellationToken = default)
|
||||
=> Task.FromResult(0L);
|
||||
public Task<bool> RequestAsync(string targetPod, PodMessage message, CancellationToken cancellationToken = default)
|
||||
=> Task.FromResult(false);
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Subscribe(Func<PodMessage, Task> handler)
|
||||
public void Subscribe(Func<PodMessage, Task<bool>> handler)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,11 @@ public sealed class NullSessionDirectory : ISessionDirectory
|
||||
public static NullSessionDirectory Instance { get; } = new NullSessionDirectory();
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<bool> PublishAsync(SessionDirectoryEntry entry, long connectedUtcTicks, CancellationToken cancellationToken = default)
|
||||
public Task<long> AllocateConnectionEpochAsync(string sessionId, CancellationToken cancellationToken = default)
|
||||
=> Task.FromResult(0L);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<bool> PublishAsync(SessionDirectoryEntry entry, long connectionEpoch, CancellationToken cancellationToken = default)
|
||||
=> Task.FromResult(false);
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -6,6 +6,11 @@ namespace MediaBrowser.Controller.Session;
|
||||
/// </summary>
|
||||
public sealed class PodMessage
|
||||
{
|
||||
/// <summary>
|
||||
/// The <see cref="Kind"/> of the acknowledgement the receiving instance sends back.
|
||||
/// </summary>
|
||||
public const string AckKind = "Ack";
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the payload discriminator.
|
||||
/// </summary>
|
||||
@@ -16,6 +21,17 @@ public sealed class PodMessage
|
||||
/// </summary>
|
||||
public string OriginPod { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the identifier tying an acknowledgement to the message it answers.
|
||||
/// </summary>
|
||||
public string CorrelationId { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the receiving instance carried the message out. Only
|
||||
/// meaningful on an <see cref="AckKind"/> message.
|
||||
/// </summary>
|
||||
public bool Handled { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the serialized payload.
|
||||
/// </summary>
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
namespace MediaBrowser.Controller.Session;
|
||||
|
||||
/// <summary>
|
||||
/// A now-viewing report for a session held by another instance, carried as a <see cref="PodMessage"/>.
|
||||
/// The calling instance has already authorized it.
|
||||
/// </summary>
|
||||
public sealed class RoutedNowViewingItem
|
||||
{
|
||||
/// <summary>
|
||||
/// The <see cref="PodMessage.Kind"/> this payload travels under.
|
||||
/// </summary>
|
||||
public const string Kind = "NowViewingItem";
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the session the report applies to.
|
||||
/// </summary>
|
||||
public string SessionId { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the item being viewed.
|
||||
/// </summary>
|
||||
public string ItemId { get; set; } = string.Empty;
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
namespace MediaBrowser.Controller.Session;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="PodMessage.Kind"/> values a playback report travels under when the session it is
|
||||
/// addressed to is held by another instance. The payload is the report itself.
|
||||
/// </summary>
|
||||
public static class RoutedPlaybackReport
|
||||
{
|
||||
/// <summary>
|
||||
/// A <c>PlaybackStartInfo</c>.
|
||||
/// </summary>
|
||||
public const string StartKind = "PlaybackStart";
|
||||
|
||||
/// <summary>
|
||||
/// A <c>PlaybackProgressInfo</c>.
|
||||
/// </summary>
|
||||
public const string ProgressKind = "PlaybackProgress";
|
||||
|
||||
/// <summary>
|
||||
/// A <c>PlaybackStopInfo</c>.
|
||||
/// </summary>
|
||||
public const string StoppedKind = "PlaybackStopped";
|
||||
}
|
||||
Reference in New Issue
Block a user