diff --git a/Shokofin/SignalR/Interfaces/IFileMatchedEventArgs.cs b/Shokofin/SignalR/Interfaces/IFileMatchedEventArgs.cs new file mode 100644 index 00000000..d008802e --- /dev/null +++ b/Shokofin/SignalR/Interfaces/IFileMatchedEventArgs.cs @@ -0,0 +1,49 @@ +using System.Collections.Generic; +using System.Text.Json.Serialization; + +namespace Shokofin.SignalR.Interfaces; + +public interface IFileEventArgs +{ + /// + /// Shoko file id. + /// + int FileId { get; } + + /// + /// The ID of the new import folder the event was detected in. + /// + /// + int ImportFolderId { get; } + + /// + /// The relative path of the new file from the import folder base location. + /// + string RelativePath { get; } + + /// + /// Cross references of episodes linked to this file. + /// + List CrossReferences { get; } + + public class FileCrossReference + { + /// + /// Shoko episode id. + /// + [JsonPropertyName("EpisodeID")] + public int EpisodeId { get; set; } + + /// + /// Shoko series id. + /// + [JsonPropertyName("SeriesID")] + public int SeriesId { get; set; } + + /// + /// Shoko group id. + /// + [JsonPropertyName("GroupID")] + public int GroupId { get; set; } + } +} \ No newline at end of file diff --git a/Shokofin/SignalR/Models/EpisodeInfoUpdatedEventArgs.cs b/Shokofin/SignalR/Models/EpisodeInfoUpdatedEventArgs.cs index 43fccb8b..fb13d620 100644 --- a/Shokofin/SignalR/Models/EpisodeInfoUpdatedEventArgs.cs +++ b/Shokofin/SignalR/Models/EpisodeInfoUpdatedEventArgs.cs @@ -1,3 +1,4 @@ +using System.Collections.Generic; using System.Text.Json.Serialization; namespace Shokofin.SignalR.Models; @@ -5,20 +6,42 @@ namespace Shokofin.SignalR.Models; public class EpisodeInfoUpdatedEventArgs { /// - /// Shoko episode id. + /// The update reason. + /// + public UpdateReason Reason { get; set; } + /// + /// The provider metadata source. + /// + [JsonPropertyName("Source")] + public string ProviderName { get; set; } = string.Empty; + + /// + /// The provided metadata episode id. /// [JsonPropertyName("EpisodeID")] - public int EpisodeId { get; set; } + public int ProviderId { get; set; } /// - /// Shoko series id. + /// The provided metadata series id. /// [JsonPropertyName("SeriesID")] - public int SeriesId { get; set; } + public int ProviderSeriesId { get; set; } + + /// + /// Shoko episode ids affected by this update. + /// + [JsonPropertyName("ShokoEpisodeIDs")] + public List EpisodeIds { get; set; } = new(); + + /// + /// Shoko series ids affected by this update. + /// + [JsonPropertyName("ShokoSeriesIDs")] + public List SeriesIds { get; set; } = new(); /// - /// Shoko group id. + /// Shoko group ids affected by this update. /// - [JsonPropertyName("GroupID")] - public int GroupId { get; set; } + [JsonPropertyName("ShokoGroupIDs")] + public List GroupIds { get; set; } = new(); } \ No newline at end of file diff --git a/Shokofin/SignalR/Models/FileEventArgs.cs b/Shokofin/SignalR/Models/FileEventArgs.cs index 5a4e69a1..1968d4e4 100644 --- a/Shokofin/SignalR/Models/FileEventArgs.cs +++ b/Shokofin/SignalR/Models/FileEventArgs.cs @@ -1,8 +1,10 @@ +using System.Collections.Generic; using System.Text.Json.Serialization; +using Shokofin.SignalR.Interfaces; namespace Shokofin.SignalR.Models; -public class FileEventArgs +public class FileEventArgs : IFileEventArgs { /// /// Shoko file id. @@ -22,4 +24,9 @@ public class FileEventArgs /// [JsonPropertyName("RelativePath")] public string RelativePath { get; set; } = string.Empty; + + /// + /// Cross references of episodes linked to this file. + /// + public List CrossReferences { get; set; } = new(); } diff --git a/Shokofin/SignalR/Models/FileMatchedEventArgs.cs b/Shokofin/SignalR/Models/FileMatchedEventArgs.cs index f1067c66..efe4ade0 100644 --- a/Shokofin/SignalR/Models/FileMatchedEventArgs.cs +++ b/Shokofin/SignalR/Models/FileMatchedEventArgs.cs @@ -1,34 +1,33 @@ using System.Collections.Generic; using System.Text.Json.Serialization; +using Shokofin.SignalR.Interfaces; namespace Shokofin.SignalR.Models; -public class FileMatchedEventArgs : FileEventArgs +public class FileMatchedEventArgsV1 : IFileEventArgs { /// - /// Cross references of episodes linked to this file. + /// Shoko file id. /// - [JsonPropertyName("CrossRefs")] - public List CrossReferences { get; set; } = new(); + [JsonPropertyName("FileID")] + public int FileId { get; set; } - public class FileCrossReference - { - /// - /// Shoko episode id. - /// - [JsonPropertyName("EpisodeID")] - public int EpisodeId { get; set; } + /// + /// The ID of the import folder the event was detected in. + /// + /// + [JsonPropertyName("ImportFolderID")] + public int ImportFolderId { get; set; } - /// - /// Shoko series id. - /// - [JsonPropertyName("SeriesID")] - public int SeriesId { get; set; } + /// + /// The relative path of the file from the import folder base location. + /// + [JsonPropertyName("RelativePath")] + public string RelativePath { get; set; } = string.Empty; - /// - /// Shoko group id. - /// - [JsonPropertyName("GroupID")] - public int GroupId { get; set; } - } + /// + /// Cross references of episodes linked to this file. + /// + [JsonPropertyName("CrossRefs")] + public List CrossReferences { get; set; } = new(); } \ No newline at end of file diff --git a/Shokofin/SignalR/Models/FileMovedEventArgs.cs b/Shokofin/SignalR/Models/FileMovedEventArgs.cs index b1247725..c54ee78a 100644 --- a/Shokofin/SignalR/Models/FileMovedEventArgs.cs +++ b/Shokofin/SignalR/Models/FileMovedEventArgs.cs @@ -3,7 +3,7 @@ namespace Shokofin.SignalR.Models; -public class FileMovedEventArgs : IFileRelocationEventArgs +public class FileMovedEventArgsV1 : IFileRelocationEventArgs { /// /// Shoko file id. @@ -37,3 +37,18 @@ public class FileMovedEventArgs : IFileRelocationEventArgs [JsonPropertyName("OldRelativePath")] public string PreviousRelativePath { get; set; } = string.Empty; } + +public class FileMovedEventArgs: FileEventArgs, IFileRelocationEventArgs +{ + /// + /// The ID of the old import folder the event was detected in. + /// + /// + [JsonPropertyName("PreviousImportFolderID")] + public int PreviousImportFolderId { get; set; } + + /// + /// The relative path of the old file from the import folder base location. + /// + public string PreviousRelativePath { get; set; } = string.Empty; +} diff --git a/Shokofin/SignalR/Models/FileRenamedEventArgs.cs b/Shokofin/SignalR/Models/FileRenamedEventArgs.cs index 9db248f6..997a08d2 100644 --- a/Shokofin/SignalR/Models/FileRenamedEventArgs.cs +++ b/Shokofin/SignalR/Models/FileRenamedEventArgs.cs @@ -3,8 +3,27 @@ namespace Shokofin.SignalR.Models; -public class FileRenamedEventArgs : FileEventArgs, IFileRelocationEventArgs +public class FileRenamedEventArgsV1 : IFileRelocationEventArgs { + /// + /// Shoko file id. + /// + [JsonPropertyName("FileID")] + public int FileId { get; set; } + + /// + /// The ID of the import folder the event was detected in. + /// + /// + [JsonPropertyName("ImportFolderID")] + public int ImportFolderId { get; set; } + + /// + /// The relative path of the file from the import folder base location. + /// + [JsonPropertyName("RelativePath")] + public string RelativePath { get; set; } = string.Empty; + /// /// The new File name. /// @@ -17,10 +36,29 @@ public class FileRenamedEventArgs : FileEventArgs, IFileRelocationEventArgs [JsonPropertyName("OldFileName")] public string PreviousFileName { get; set; } = string.Empty; - public int PreviousImportFolderId { get; set; } + + /// + public int PreviousImportFolderId => ImportFolderId; + + /// + public string PreviousRelativePath => RelativePath[^FileName.Length] + PreviousFileName; +} + +public class FileRenamedEventArgs : FileEventArgs, IFileRelocationEventArgs +{ + /// + /// The new File name. + /// + public string FileName { get; set; } = string.Empty; /// - /// The relative path of the old file from the import folder base location. + /// The old file name. /// + public string PreviousFileName { get; set; } = string.Empty; + + /// + public int PreviousImportFolderId => ImportFolderId; + + /// public string PreviousRelativePath => RelativePath[^FileName.Length] + PreviousFileName; } \ No newline at end of file diff --git a/Shokofin/SignalR/Models/SeriesInfoUpdatedEventArgs.cs b/Shokofin/SignalR/Models/SeriesInfoUpdatedEventArgs.cs index 7bab9b68..824d2bc1 100644 --- a/Shokofin/SignalR/Models/SeriesInfoUpdatedEventArgs.cs +++ b/Shokofin/SignalR/Models/SeriesInfoUpdatedEventArgs.cs @@ -1,3 +1,4 @@ +using System.Collections.Generic; using System.Text.Json.Serialization; namespace Shokofin.SignalR.Models; @@ -5,14 +6,31 @@ namespace Shokofin.SignalR.Models; public class SeriesInfoUpdatedEventArgs { /// - /// Shoko series id. + /// The update reason. + /// + public UpdateReason Reason { get; set; } + + /// + /// The provider metadata source. + /// + [JsonPropertyName("Source")] + public string ProviderName { get; set; } = string.Empty; + + /// + /// The provided metadata series id. /// [JsonPropertyName("SeriesID")] - public int SeriesId { get; set; } + public int ProviderId { get; set; } + + /// + /// Shoko series ids affected by this update. + /// + [JsonPropertyName("ShokoSeriesIDs")] + public List SeriesIds { get; set; } = new(); /// - /// Shoko group id. + /// Shoko group ids affected by this update. /// - [JsonPropertyName("GroupID")] - public int GroupId { get; set; } + [JsonPropertyName("ShokoGroupIDs")] + public List GroupIds { get; set; } = new(); } \ No newline at end of file diff --git a/Shokofin/SignalR/Models/UpdateReason.cs b/Shokofin/SignalR/Models/UpdateReason.cs new file mode 100644 index 00000000..a3f003e8 --- /dev/null +++ b/Shokofin/SignalR/Models/UpdateReason.cs @@ -0,0 +1,13 @@ + +using System.Text.Json.Serialization; + +namespace Shokofin.SignalR.Models; + +[JsonConverter(typeof(JsonStringEnumConverter))] +public enum UpdateReason +{ + None = 0, + Added = 1, + Updated = 2, + Removed = 3, +} \ No newline at end of file diff --git a/Shokofin/SignalR/SignalRConnectionManager.cs b/Shokofin/SignalR/SignalRConnectionManager.cs index 954db286..5676b376 100644 --- a/Shokofin/SignalR/SignalRConnectionManager.cs +++ b/Shokofin/SignalR/SignalRConnectionManager.cs @@ -6,6 +6,7 @@ using Microsoft.AspNetCore.SignalR.Client; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; +using Shokofin.API.Models; using Shokofin.Configuration; using Shokofin.Resolvers; using Shokofin.SignalR.Interfaces; @@ -15,6 +16,14 @@ namespace Shokofin.SignalR; public class SignalRConnectionManager : IDisposable { + private static ComponentVersion? ServerVersion => + Plugin.Instance.Configuration.ServerVersion; + + private static readonly DateTime EventChangedDate = DateTime.Parse("2024-04-01T04:04:00.000Z"); + + private static bool UseOlderEvents => + ServerVersion != null && ((ServerVersion.ReleaseChannel == ReleaseChannel.Stable && ServerVersion.Version == "4.2.2.0") || (ServerVersion.ReleaseDate.HasValue && ServerVersion.ReleaseDate.Value < EventChangedDate)); + private const string HubUrl = "/signalr/aggregate?feeds=shoko"; private readonly ILogger Logger; @@ -76,10 +85,18 @@ private async Task ConnectAsync(PluginConfiguration config) connection.On("ShokoEvent:SeriesUpdated", OnSeriesInfoUpdated); // Attach file events. - connection.On("ShokoEvent:FileMatched", OnFileMatched); - connection.On("ShokoEvent:FileMoved", OnFileMoved); - connection.On("ShokoEvent:FileRenamed", OnFileRenamed); - connection.On("ShokoEvent:FileDeleted", OnFileDeleted); + if (UseOlderEvents) { + connection.On("ShokoEvent:FileMatched", OnFileMatched); + connection.On("ShokoEvent:FileMoved", OnFileRelocated); + connection.On("ShokoEvent:FileRenamed", OnFileRelocated); + connection.On("ShokoEvent:FileDeleted", OnFileDeleted); + } + else { + connection.On("ShokoEvent:FileMatched", OnFileMatched); + connection.On("ShokoEvent:FileMoved", OnFileRelocated); + connection.On("ShokoEvent:FileRenamed", OnFileRelocated); + connection.On("ShokoEvent:FileDeleted", OnFileDeleted); + } try { await connection.StartAsync().ConfigureAwait(false); @@ -181,10 +198,10 @@ private static string ConstructKey(PluginConfiguration config) #region File Events - private void OnFileMatched(FileMatchedEventArgs eventArgs) + private void OnFileMatched(IFileEventArgs eventArgs) { Logger.LogDebug( - "File matched; {ImportFolderIdB} {PathB} (File={FileId})", + "File matched; {ImportFolderId} {Path} (File={FileId})", eventArgs.ImportFolderId, eventArgs.RelativePath, eventArgs.FileId @@ -222,13 +239,7 @@ private void OnFileRelocated(IFileRelocationEventArgs eventArgs) // the links broke, and if the newly generated links is not in the list provided by the base items, then remove the broken link. } - private void OnFileMoved(FileMovedEventArgs eventArgs) - => OnFileRelocated(eventArgs); - - private void OnFileRenamed(FileRenamedEventArgs eventArgs) - => OnFileRelocated(eventArgs); - - private void OnFileDeleted(FileEventArgs eventArgs) + private void OnFileDeleted(IFileEventArgs eventArgs) { Logger.LogDebug( "File deleted; {ImportFolderIdB} {PathB} (File={FileId})", @@ -253,10 +264,14 @@ private void OnFileDeleted(FileEventArgs eventArgs) private void OnEpisodeInfoUpdated(EpisodeInfoUpdatedEventArgs eventArgs) { Logger.LogDebug( - "Episode updated. (Episode={EpisodeId},Series={SeriesId},Group={GroupId})", - eventArgs.EpisodeId, - eventArgs.SeriesId, - eventArgs.GroupId + "{ProviderName} episode {ProviderId} ({ProviderSeriesId}) dispatched event {UpdateReason}. (Episode={EpisodeId},Series={SeriesId},Group={GroupId})", + eventArgs.ProviderName, + eventArgs.ProviderId, + eventArgs.ProviderSeriesId, + eventArgs.Reason, + eventArgs.EpisodeIds, + eventArgs.SeriesIds, + eventArgs.GroupIds ); // Refresh all epoisodes and movies linked to the episode. @@ -265,9 +280,12 @@ private void OnEpisodeInfoUpdated(EpisodeInfoUpdatedEventArgs eventArgs) private void OnSeriesInfoUpdated(SeriesInfoUpdatedEventArgs eventArgs) { Logger.LogDebug( - "Series updated. (Series={SeriesId},Group={GroupId})", - eventArgs.SeriesId, - eventArgs.GroupId + "{ProviderName} series {ProviderId} dispatched event {UpdateReason}. (Series={SeriesId},Group={GroupId})", + eventArgs.ProviderName, + eventArgs.ProviderId, + eventArgs.Reason, + eventArgs.SeriesIds, + eventArgs.GroupIds ); // look up the series/season/movie, then check the media folder they're