-
Notifications
You must be signed in to change notification settings - Fork 13
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #234 from bdach/daily-challenge
Announce currently active "daily challenge" playlist to clients
- Loading branch information
Showing
13 changed files
with
233 additions
and
16 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
// Copyright (c) ppy Pty Ltd <[email protected]>. Licensed under the MIT Licence. | ||
// See the LICENCE file in the repository root for full licence text. | ||
|
||
using System.Threading; | ||
using System.Threading.Tasks; | ||
using Microsoft.AspNetCore.SignalR; | ||
using Microsoft.Extensions.Logging; | ||
using Moq; | ||
using osu.Game.Online.Metadata; | ||
using osu.Server.Spectator.Database; | ||
using osu.Server.Spectator.Database.Models; | ||
using osu.Server.Spectator.Hubs.Metadata; | ||
using Xunit; | ||
|
||
namespace osu.Server.Spectator.Tests | ||
{ | ||
public class DailyChallengeUpdaterTest | ||
{ | ||
private readonly Mock<ILoggerFactory> loggerFactoryMock; | ||
private readonly Mock<IDatabaseFactory> databaseFactoryMock; | ||
private readonly Mock<IDatabaseAccess> databaseAccessMock; | ||
private readonly Mock<IHubContext<MetadataHub>> metadataHubContextMock; | ||
private readonly Mock<IClientProxy> allClientsProxy; | ||
|
||
public DailyChallengeUpdaterTest() | ||
{ | ||
loggerFactoryMock = new Mock<ILoggerFactory>(); | ||
loggerFactoryMock.Setup(factory => factory.CreateLogger(It.IsAny<string>())) | ||
.Returns(new Mock<ILogger>().Object); | ||
|
||
databaseFactoryMock = new Mock<IDatabaseFactory>(); | ||
databaseAccessMock = new Mock<IDatabaseAccess>(); | ||
databaseFactoryMock.Setup(factory => factory.GetInstance()).Returns(databaseAccessMock.Object); | ||
|
||
metadataHubContextMock = new Mock<IHubContext<MetadataHub>>(); | ||
allClientsProxy = new Mock<IClientProxy>(); | ||
metadataHubContextMock.Setup(ctx => ctx.Clients.All).Returns(allClientsProxy.Object); | ||
} | ||
|
||
[Fact] | ||
public async Task TestChangeTracking() | ||
{ | ||
databaseAccessMock.Setup(db => db.GetActiveDailyChallengeRoomsAsync()) | ||
.ReturnsAsync([new multiplayer_room { id = 4, category = room_category.daily_challenge }]); | ||
|
||
var updater = new DailyChallengeUpdater( | ||
loggerFactoryMock.Object, | ||
databaseFactoryMock.Object, | ||
metadataHubContextMock.Object) | ||
{ | ||
UpdateInterval = 50 | ||
}; | ||
|
||
var task = updater.StartAsync(default); | ||
await Task.Delay(100); | ||
|
||
allClientsProxy.Verify(proxy => proxy.SendCoreAsync( | ||
nameof(IMetadataClient.DailyChallengeUpdated), | ||
It.Is<object[]>(args => ((DailyChallengeInfo?)args![0]).Value.RoomID == 4), | ||
It.IsAny<CancellationToken>()), | ||
Times.Once); | ||
|
||
databaseAccessMock.Setup(db => db.GetActiveDailyChallengeRoomsAsync()) | ||
.ReturnsAsync([]); | ||
await Task.Delay(100); | ||
|
||
allClientsProxy.Verify(proxy => proxy.SendCoreAsync( | ||
nameof(IMetadataClient.DailyChallengeUpdated), | ||
It.Is<object?[]>(args => args[0] == null), | ||
It.IsAny<CancellationToken>()), | ||
Times.Once); | ||
|
||
databaseAccessMock.Setup(db => db.GetActiveDailyChallengeRoomsAsync()) | ||
.ReturnsAsync([new multiplayer_room { id = 5, category = room_category.daily_challenge }]); | ||
await Task.Delay(100); | ||
|
||
allClientsProxy.Verify(proxy => proxy.SendCoreAsync( | ||
nameof(IMetadataClient.DailyChallengeUpdated), | ||
It.Is<object[]>(args => ((DailyChallengeInfo?)args![0]).HasValue && ((DailyChallengeInfo?)args[0]).Value.RoomID == 5), | ||
It.IsAny<CancellationToken>()), | ||
Times.Once); | ||
|
||
await updater.StopAsync(default); | ||
await task; | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
// Copyright (c) ppy Pty Ltd <[email protected]>. Licensed under the MIT Licence. | ||
// See the LICENCE file in the repository root for full licence text. | ||
|
||
using System; | ||
|
||
namespace osu.Server.Spectator.Database.Models | ||
{ | ||
// ReSharper disable once InconsistentNaming | ||
[Serializable] | ||
public enum room_category | ||
{ | ||
normal, | ||
spotlights, | ||
featured_artist, | ||
daily_challenge, | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
88 changes: 88 additions & 0 deletions
88
osu.Server.Spectator/Hubs/Metadata/DailyChallengeUpdater.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
// Copyright (c) ppy Pty Ltd <[email protected]>. Licensed under the MIT Licence. | ||
// See the LICENCE file in the repository root for full licence text. | ||
|
||
using System; | ||
using System.Linq; | ||
using System.Threading; | ||
using System.Threading.Tasks; | ||
using Microsoft.AspNetCore.SignalR; | ||
using Microsoft.Extensions.Hosting; | ||
using Microsoft.Extensions.Logging; | ||
using osu.Game.Online.Metadata; | ||
using osu.Server.Spectator.Database; | ||
|
||
namespace osu.Server.Spectator.Hubs.Metadata | ||
{ | ||
public interface IDailyChallengeUpdater : IHostedService | ||
{ | ||
DailyChallengeInfo? Current { get; } | ||
} | ||
|
||
public class DailyChallengeUpdater : BackgroundService, IDailyChallengeUpdater | ||
{ | ||
/// <summary> | ||
/// Amount of time (in milliseconds) between subsequent polls for the current beatmap of the day. | ||
/// </summary> | ||
public int UpdateInterval = 60_000; | ||
|
||
public DailyChallengeInfo? Current { get; private set; } | ||
|
||
private readonly ILogger logger; | ||
private readonly IDatabaseFactory databaseFactory; | ||
private readonly IHubContext<MetadataHub> hubContext; | ||
|
||
public DailyChallengeUpdater( | ||
ILoggerFactory loggerFactory, | ||
IDatabaseFactory databaseFactory, | ||
IHubContext<MetadataHub> hubContext) | ||
{ | ||
logger = loggerFactory.CreateLogger(nameof(DailyChallengeUpdater)); | ||
this.databaseFactory = databaseFactory; | ||
this.hubContext = hubContext; | ||
} | ||
|
||
protected override async Task ExecuteAsync(CancellationToken stoppingToken) | ||
{ | ||
while (!stoppingToken.IsCancellationRequested) | ||
{ | ||
try | ||
{ | ||
await updateDailyChallengeInfo(stoppingToken); | ||
} | ||
catch (Exception ex) | ||
{ | ||
logger.LogError(ex, "Failed to update beatmap of the day"); | ||
} | ||
|
||
await Task.Delay(UpdateInterval, stoppingToken); | ||
} | ||
} | ||
|
||
private async Task updateDailyChallengeInfo(CancellationToken cancellationToken) | ||
{ | ||
using var db = databaseFactory.GetInstance(); | ||
|
||
var activeRooms = (await db.GetActiveDailyChallengeRoomsAsync()).ToList(); | ||
|
||
if (activeRooms.Count > 1) | ||
{ | ||
logger.LogWarning("More than one active 'beatmap of the day' room detected (ids: {roomIds}). Will only use the first one.", | ||
string.Join(',', activeRooms.Select(room => room.id))); | ||
} | ||
|
||
DailyChallengeInfo? newInfo = null; | ||
|
||
var activeRoom = activeRooms.FirstOrDefault(); | ||
|
||
if (activeRoom?.id != null) | ||
newInfo = new DailyChallengeInfo { RoomID = activeRoom.id }; | ||
|
||
if (!Current.Equals(newInfo)) | ||
{ | ||
logger.LogInformation("Broadcasting 'beatmap of the day' room change from id {oldRoomID} to {newRoomId}", Current?.RoomID, newInfo?.RoomID); | ||
Current = newInfo; | ||
await hubContext.Clients.All.SendAsync(nameof(IMetadataClient.DailyChallengeUpdated), Current, cancellationToken); | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters