diff --git a/DependencyInjection/AddAzureBlobStorageService.cs b/DependencyInjection/AddAzureBlobStorageService.cs index 3d56713..4bfee27 100644 --- a/DependencyInjection/AddAzureBlobStorageService.cs +++ b/DependencyInjection/AddAzureBlobStorageService.cs @@ -6,34 +6,33 @@ using Serilog; using System.Configuration; -namespace LivestreamRecorderService.DependencyInjection +namespace LivestreamRecorderService.DependencyInjection; + +public static partial class Extensions { - public static partial class Extensions + public static IServiceCollection AddAzureBlobStorageService(this IServiceCollection services) { - public static IServiceCollection AddAzureBlobStorageService(this IServiceCollection services) + try { - try - { - var azureOptions = services.BuildServiceProvider().GetRequiredService>().Value; - if (null == azureOptions.BlobStorage - || string.IsNullOrEmpty(azureOptions.BlobStorage.StorageAccountName) - || string.IsNullOrEmpty(azureOptions.BlobStorage.StorageAccountKey) - || string.IsNullOrEmpty(azureOptions.BlobStorage.BlobContainerName_Public) - || string.IsNullOrEmpty(azureOptions.BlobStorage.BlobContainerName_Private)) - throw new ConfigurationErrorsException(); + var azureOptions = services.BuildServiceProvider().GetRequiredService>().Value; + if (null == azureOptions.BlobStorage + || string.IsNullOrEmpty(azureOptions.BlobStorage.StorageAccountName) + || string.IsNullOrEmpty(azureOptions.BlobStorage.StorageAccountKey) + || string.IsNullOrEmpty(azureOptions.BlobStorage.BlobContainerName_Public) + || string.IsNullOrEmpty(azureOptions.BlobStorage.BlobContainerName_Private)) + throw new ConfigurationErrorsException(); - services.AddAzureClients(clientsBuilder - => clientsBuilder.AddBlobServiceClient(azureOptions.BlobStorage.ConnectionString)); + services.AddAzureClients(clientsBuilder + => clientsBuilder.AddBlobServiceClient(azureOptions.BlobStorage.ConnectionString)); - services.AddSingleton(); + services.AddSingleton(); - return services; - } - catch (ConfigurationErrorsException) - { - Log.Fatal("Missing AzuerBlobStorage. Please set Azure:AzuerBlobStorage in appsettings.json."); - throw new ConfigurationErrorsException("Missing AzuerBlobStorage. Please set Azure:AzuerBlobStorage in appsettings.json."); - } + return services; + } + catch (ConfigurationErrorsException) + { + Log.Fatal("Missing AzuerBlobStorage. Please set Azure:AzuerBlobStorage in appsettings.json."); + throw new ConfigurationErrorsException("Missing AzuerBlobStorage. Please set Azure:AzuerBlobStorage in appsettings.json."); } } } diff --git a/DependencyInjection/AddAzureContainerInstanceService.cs b/DependencyInjection/AddAzureContainerInstanceService.cs index 135e974..8b1b66e 100644 --- a/DependencyInjection/AddAzureContainerInstanceService.cs +++ b/DependencyInjection/AddAzureContainerInstanceService.cs @@ -12,45 +12,44 @@ using Serilog; using System.Configuration; -namespace LivestreamRecorderService.DependencyInjection +namespace LivestreamRecorderService.DependencyInjection; + +public static partial class Extensions { - public static partial class Extensions + public static IServiceCollection AddAzureContainerInstanceService(this IServiceCollection services) { - public static IServiceCollection AddAzureContainerInstanceService(this IServiceCollection services) + try { - try - { - var azureOptions = services.BuildServiceProvider().GetRequiredService>().Value; - if (null == azureOptions.ContainerInstance - || string.IsNullOrEmpty(azureOptions.ContainerInstance.ClientSecret.ClientID) - || string.IsNullOrEmpty(azureOptions.ContainerInstance.ClientSecret.ClientSecret)) - throw new ConfigurationErrorsException(); + var azureOptions = services.BuildServiceProvider().GetRequiredService>().Value; + if (null == azureOptions.ContainerInstance + || string.IsNullOrEmpty(azureOptions.ContainerInstance.ClientSecret.ClientID) + || string.IsNullOrEmpty(azureOptions.ContainerInstance.ClientSecret.ClientSecret)) + throw new ConfigurationErrorsException(); - services.AddAzureClients(clientsBuilder - => clientsBuilder.UseCredential((options) - => new ClientSecretCredential(tenantId: azureOptions.ContainerInstance.ClientSecret.TenantID, - clientId: azureOptions.ContainerInstance.ClientSecret.ClientID, - clientSecret: azureOptions.ContainerInstance.ClientSecret.ClientSecret)) - .AddClient((options, token) => new ArmClient(token))); + services.AddAzureClients(clientsBuilder + => clientsBuilder.UseCredential((options) + => new ClientSecretCredential(tenantId: azureOptions.ContainerInstance.ClientSecret.TenantID, + clientId: azureOptions.ContainerInstance.ClientSecret.ClientID, + clientSecret: azureOptions.ContainerInstance.ClientSecret.ClientSecret)) + .AddClient((options, token) => new ArmClient(token))); - services.AddSingleton(); + services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); - return services; - } - catch (ConfigurationErrorsException) - { - Log.Fatal("Missing AzureContainerInstance. Please set Azure:AzureContainerInstance in appsettings.json."); - throw new ConfigurationErrorsException("Missing AzureContainerInstance. Please set Azure:AzureContainerInstance in appsettings.json."); - } + return services; + } + catch (ConfigurationErrorsException) + { + Log.Fatal("Missing AzureContainerInstance. Please set Azure:AzureContainerInstance in appsettings.json."); + throw new ConfigurationErrorsException("Missing AzureContainerInstance. Please set Azure:AzureContainerInstance in appsettings.json."); } } } diff --git a/DependencyInjection/AddCosmosDb.cs b/DependencyInjection/AddCosmosDb.cs index 028583a..9cc2c34 100644 --- a/DependencyInjection/AddCosmosDb.cs +++ b/DependencyInjection/AddCosmosDb.cs @@ -9,60 +9,59 @@ #endif using Serilog; -namespace LivestreamRecorderService.DependencyInjection +namespace LivestreamRecorderService.DependencyInjection; + +public static partial class Extensions { - public static partial class Extensions + public static IServiceCollection AddCosmosDB(this IServiceCollection services, IConfiguration configuration) { - public static IServiceCollection AddCosmosDB(this IServiceCollection services, IConfiguration configuration) - { #if !COSMOSDB - Log.Fatal("This is a CouchDB build. Please use the CosmosDB build for Azure CosmosDB support."); - throw new InvalidOperationException("This is a CouchDB build. Please use the CosmosDB build for Azure CosmosDB support."); + Log.Fatal("This is a CouchDB build. Please use the CosmosDB build for Azure CosmosDB support."); + throw new InvalidOperationException("This is a CouchDB build. Please use the CosmosDB build for Azure CosmosDB support."); #else - try - { - var azureOptions = services.BuildServiceProvider().GetRequiredService>().Value; + try + { + var azureOptions = services.BuildServiceProvider().GetRequiredService>().Value; - if (null == azureOptions.CosmosDB - || null == azureOptions.CosmosDB.Public.ConnectionStrings - || null == azureOptions.CosmosDB.Private.ConnectionStrings) - throw new ConfigurationErrorsException(); + if (null == azureOptions.CosmosDB + || null == azureOptions.CosmosDB.Public.ConnectionStrings + || null == azureOptions.CosmosDB.Private.ConnectionStrings) + throw new ConfigurationErrorsException(); - // Add CosmosDB - services.AddDbContext((options) => - { - options + // Add CosmosDB + services.AddDbContext((options) => + { + options #if !RELEASE - .EnableSensitiveDataLogging() + .EnableSensitiveDataLogging() #endif - .UseCosmos(connectionString: azureOptions.CosmosDB.Public.ConnectionStrings, - databaseName: azureOptions.CosmosDB.Public.DatabaseName, - cosmosOptionsAction: option => option.GatewayModeMaxConnectionLimit(380)); - }); - services.AddDbContext((options) => - { - options + .UseCosmos(connectionString: azureOptions.CosmosDB.Public.ConnectionStrings, + databaseName: azureOptions.CosmosDB.Public.DatabaseName, + cosmosOptionsAction: option => option.GatewayModeMaxConnectionLimit(380)); + }); + services.AddDbContext((options) => + { + options #if !RELEASE - .EnableSensitiveDataLogging() + .EnableSensitiveDataLogging() #endif - .UseCosmos(connectionString: azureOptions.CosmosDB.Private.ConnectionStrings, - databaseName: azureOptions.CosmosDB.Private.DatabaseName, - cosmosOptionsAction: option => option.GatewayModeMaxConnectionLimit(380)); - }); + .UseCosmos(connectionString: azureOptions.CosmosDB.Private.ConnectionStrings, + databaseName: azureOptions.CosmosDB.Private.DatabaseName, + cosmosOptionsAction: option => option.GatewayModeMaxConnectionLimit(380)); + }); - services.AddScoped(); - services.AddScoped(); - services.AddScoped((s) => new VideoRepository((IUnitOfWork)s.GetRequiredService(typeof(UnitOfWork_Public)))); - services.AddScoped((s) => new ChannelRepository((IUnitOfWork)s.GetRequiredService(typeof(UnitOfWork_Public)))); - services.AddScoped((s) => new UserRepository((IUnitOfWork)s.GetRequiredService(typeof(UnitOfWork_Private)))); - return services; - } - catch (ConfigurationErrorsException) - { - Log.Fatal("Missing CosmosDB Settings. Please setup CosmosDB in appsettings.json."); - throw new ConfigurationErrorsException("Missing CosmosDB Settings. Please setup CosmosDB in appsettings.json."); - } -#endif + services.AddScoped(); + services.AddScoped(); + services.AddScoped((s) => new VideoRepository((IUnitOfWork)s.GetRequiredService(typeof(UnitOfWork_Public)))); + services.AddScoped((s) => new ChannelRepository((IUnitOfWork)s.GetRequiredService(typeof(UnitOfWork_Public)))); + services.AddScoped((s) => new UserRepository((IUnitOfWork)s.GetRequiredService(typeof(UnitOfWork_Private)))); + return services; } + catch (ConfigurationErrorsException) + { + Log.Fatal("Missing CosmosDB Settings. Please setup CosmosDB in appsettings.json."); + throw new ConfigurationErrorsException("Missing CosmosDB Settings. Please setup CosmosDB in appsettings.json."); + } +#endif } } diff --git a/DependencyInjection/AddDiscordService.cs b/DependencyInjection/AddDiscordService.cs index 2f484fa..75bed35 100644 --- a/DependencyInjection/AddDiscordService.cs +++ b/DependencyInjection/AddDiscordService.cs @@ -3,44 +3,43 @@ using Serilog; using System.Configuration; -namespace LivestreamRecorderService.DependencyInjection +namespace LivestreamRecorderService.DependencyInjection; + +public static partial class Extensions { - public static partial class Extensions + public static IServiceCollection AddDiscordService(this IServiceCollection services, IConfiguration configuration) { - public static IServiceCollection AddDiscordService(this IServiceCollection services, IConfiguration configuration) + try { - try - { - IConfigurationSection config = configuration.GetSection(DiscordOption.ConfigurationSectionName); - var discordOptions = config.Get(); - if (null == discordOptions) throw new ConfigurationErrorsException(); - - services.AddOptions() - .Bind(config) - .ValidateDataAnnotations() - .ValidateOnStart(); + IConfigurationSection config = configuration.GetSection(DiscordOption.ConfigurationSectionName); + var discordOptions = config.Get(); + if (null == discordOptions) throw new ConfigurationErrorsException(); - if (!discordOptions.Enabled) - { - return services; - } + services.AddOptions() + .Bind(config) + .ValidateDataAnnotations() + .ValidateOnStart(); - if (string.IsNullOrEmpty(discordOptions.Webhook) - || string.IsNullOrEmpty(discordOptions.WebhookWarning) - || string.IsNullOrEmpty(discordOptions.WebhookAdmin) - || string.IsNullOrEmpty(discordOptions.FrontEndHost) - || null == discordOptions.Mention - || null == discordOptions.Emotes - ) throw new ConfigurationErrorsException(); - - services.AddSingleton(); - return services; - } - catch (ConfigurationErrorsException) + if (!discordOptions.Enabled) { - Log.Fatal("Missing Discord Settings. Please set Discord:Enabled Discord:Webhook, Discord:WebhookWarning, Discord:WebhookAdmin, Discord:FrontEndHost, Discord:Mention and Discord:Emotes in appsettings.json."); - throw new ConfigurationErrorsException("Missing Discord Settings. Please set Discord:Enabled Discord:Webhook, Discord:WebhookWarning, Discord:WebhookAdmin, Discord:FrontEndHost, Discord:Mention and Discord:Emotes in appsettings.json."); + return services; } + + if (string.IsNullOrEmpty(discordOptions.Webhook) + || string.IsNullOrEmpty(discordOptions.WebhookWarning) + || string.IsNullOrEmpty(discordOptions.WebhookAdmin) + || string.IsNullOrEmpty(discordOptions.FrontEndHost) + || null == discordOptions.Mention + || null == discordOptions.Emotes + ) throw new ConfigurationErrorsException(); + + services.AddSingleton(); + return services; + } + catch (ConfigurationErrorsException) + { + Log.Fatal("Missing Discord Settings. Please set Discord:Enabled Discord:Webhook, Discord:WebhookWarning, Discord:WebhookAdmin, Discord:FrontEndHost, Discord:Mention and Discord:Emotes in appsettings.json."); + throw new ConfigurationErrorsException("Missing Discord Settings. Please set Discord:Enabled Discord:Webhook, Discord:WebhookWarning, Discord:WebhookAdmin, Discord:FrontEndHost, Discord:Mention and Discord:Emotes in appsettings.json."); } } } diff --git a/DependencyInjection/AddHeartbeatWorker.cs b/DependencyInjection/AddHeartbeatWorker.cs index 92b9a80..2c6743c 100644 --- a/DependencyInjection/AddHeartbeatWorker.cs +++ b/DependencyInjection/AddHeartbeatWorker.cs @@ -3,36 +3,35 @@ using Serilog; using System.Configuration; -namespace LivestreamRecorderService.DependencyInjection +namespace LivestreamRecorderService.DependencyInjection; + +public static partial class Extensions { - public static partial class Extensions + public static IServiceCollection AddHeartbeatWorker(this IServiceCollection services, IConfiguration configuration) { - public static IServiceCollection AddHeartbeatWorker(this IServiceCollection services, IConfiguration configuration) + try { - try - { - IConfigurationSection config = configuration.GetSection(HeartbeatOption.ConfigurationSectionName); - var heartbeatOptions = config.Get(); - if (null == heartbeatOptions) throw new ConfigurationErrorsException(); - - services.AddOptions() - .Bind(config) - .ValidateDataAnnotations() - .ValidateOnStart(); + IConfigurationSection config = configuration.GetSection(HeartbeatOption.ConfigurationSectionName); + var heartbeatOptions = config.Get(); + if (null == heartbeatOptions) throw new ConfigurationErrorsException(); - if (!heartbeatOptions.Enabled) - { - return services; - } + services.AddOptions() + .Bind(config) + .ValidateDataAnnotations() + .ValidateOnStart(); - services.AddHostedService(); - return services; - } - catch (ConfigurationErrorsException) + if (!heartbeatOptions.Enabled) { - Log.Fatal("Missing Twitch ClientId or ClientSecret. Please set Twitch:ClientId and Twitch:ClientSecret in appsettings.json."); - throw new ConfigurationErrorsException("Missing Twitch ClientId or ClientSecret. Please set Twitch:ClientId and Twitch:ClientSecret in appsettings.json."); + return services; } + + services.AddHostedService(); + return services; + } + catch (ConfigurationErrorsException) + { + Log.Fatal("Missing Twitch ClientId or ClientSecret. Please set Twitch:ClientId and Twitch:ClientSecret in appsettings.json."); + throw new ConfigurationErrorsException("Missing Twitch ClientId or ClientSecret. Please set Twitch:ClientId and Twitch:ClientSecret in appsettings.json."); } } } diff --git a/DependencyInjection/AddKubernetesService.cs b/DependencyInjection/AddKubernetesService.cs index a17df1d..0cc5a0c 100644 --- a/DependencyInjection/AddKubernetesService.cs +++ b/DependencyInjection/AddKubernetesService.cs @@ -11,61 +11,61 @@ using Serilog; using System.Configuration; -namespace LivestreamRecorderService.DependencyInjection +namespace LivestreamRecorderService.DependencyInjection; + +public static partial class Extensions { - public static partial class Extensions + public static IServiceCollection AddKubernetesService(this IServiceCollection services, IConfiguration configuration) { - public static IServiceCollection AddKubernetesService(this IServiceCollection services, IConfiguration configuration) + try { - try - { - var serviceOptions = services.BuildServiceProvider().GetRequiredService>().Value; + var serviceOptions = services.BuildServiceProvider().GetRequiredService>().Value; - if (serviceOptions.JobService != ServiceName.Kubernetes) - { - return services; - } + if (serviceOptions.JobService != ServiceName.Kubernetes) + { + return services; + } - IConfigurationSection config = configuration.GetSection(KubernetesOption.ConfigurationSectionName); - var kubernetesOptions = config.Get(); - if (null == kubernetesOptions) throw new ConfigurationErrorsException(); + IConfigurationSection config = configuration.GetSection(KubernetesOption.ConfigurationSectionName); + var kubernetesOptions = config.Get(); + if (null == kubernetesOptions) throw new ConfigurationErrorsException(); - services.AddOptions() - .Bind(config) - .ValidateDataAnnotations() - .ValidateOnStart(); + services.AddOptions() + .Bind(config) + .ValidateDataAnnotations() + .ValidateOnStart(); - KubernetesClientConfiguration k8sConfig = - kubernetesOptions.UseTheSameCluster - ? KubernetesClientConfiguration.InClusterConfig() - : !string.IsNullOrWhiteSpace(kubernetesOptions.ConfigFile) - ? KubernetesClientConfiguration.BuildConfigFromConfigFile(kubernetesOptions.ConfigFile) - : KubernetesClientConfiguration.BuildDefaultConfig(); - var client = new Kubernetes(k8sConfig); - services.AddSingleton(client); + KubernetesClientConfiguration k8sConfig = + // skipcq: CS-R1114 + kubernetesOptions.UseTheSameCluster + ? KubernetesClientConfiguration.InClusterConfig() + : !string.IsNullOrWhiteSpace(kubernetesOptions.ConfigFile) + ? KubernetesClientConfiguration.BuildConfigFromConfigFile(kubernetesOptions.ConfigFile) + : KubernetesClientConfiguration.BuildDefaultConfig(); + var client = new Kubernetes(k8sConfig); + services.AddSingleton(client); - services.AddSingleton(); + services.AddSingleton(); - KubernetesService.KubernetesNamespace = string.IsNullOrWhiteSpace(kubernetesOptions.Namespace) - ? "recordermoe" - : kubernetesOptions.Namespace; + KubernetesService.KubernetesNamespace = string.IsNullOrWhiteSpace(kubernetesOptions.Namespace) + ? "recordermoe" + : kubernetesOptions.Namespace; - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); - return services; - } - catch (ConfigurationErrorsException) - { - Log.Fatal("Kubernetes configuration is invalid."); - throw new ConfigurationErrorsException("Kubernetes configuration is invalid."); - } + return services; + } + catch (ConfigurationErrorsException) + { + Log.Fatal("Kubernetes configuration is invalid."); + throw new ConfigurationErrorsException("Kubernetes configuration is invalid."); } } } diff --git a/DependencyInjection/AddS3StorageService.cs b/DependencyInjection/AddS3StorageService.cs index b7cd735..1333b9d 100644 --- a/DependencyInjection/AddS3StorageService.cs +++ b/DependencyInjection/AddS3StorageService.cs @@ -6,37 +6,36 @@ using Serilog; using System.Configuration; -namespace LivestreamRecorderService.DependencyInjection +namespace LivestreamRecorderService.DependencyInjection; + +public static partial class Extensions { - public static partial class Extensions + public static IServiceCollection AddS3StorageService(this IServiceCollection services) { - public static IServiceCollection AddS3StorageService(this IServiceCollection services) + try { - try - { - var s3Options = services.BuildServiceProvider().GetRequiredService>().Value; - if (string.IsNullOrEmpty(s3Options.Endpoint) - || string.IsNullOrEmpty(s3Options.AccessKey) - || string.IsNullOrEmpty(s3Options.SecretKey) - || string.IsNullOrEmpty(s3Options.BucketName_Public) - || string.IsNullOrEmpty(s3Options.BucketName_Private)) - throw new ConfigurationErrorsException(); + var s3Options = services.BuildServiceProvider().GetRequiredService>().Value; + if (string.IsNullOrEmpty(s3Options.Endpoint) + || string.IsNullOrEmpty(s3Options.AccessKey) + || string.IsNullOrEmpty(s3Options.SecretKey) + || string.IsNullOrEmpty(s3Options.BucketName_Public) + || string.IsNullOrEmpty(s3Options.BucketName_Private)) + throw new ConfigurationErrorsException(); - var minio = new MinioClient().WithEndpoint(s3Options.Endpoint) - .WithCredentials(s3Options.AccessKey, s3Options.SecretKey) - .WithSSL(s3Options.Secure) - .Build(); + var minio = new MinioClient().WithEndpoint(s3Options.Endpoint) + .WithCredentials(s3Options.AccessKey, s3Options.SecretKey) + .WithSSL(s3Options.Secure) + .Build(); - services.AddSingleton(minio); - services.AddSingleton(); + services.AddSingleton(minio); + services.AddSingleton(); - return services; - } - catch (ConfigurationErrorsException) - { - Log.Fatal("Missing S3. Please set S3 in appsettings.json."); - throw new ConfigurationErrorsException("Missing S3. Please set S3 in appsettings.json."); - } + return services; + } + catch (ConfigurationErrorsException) + { + Log.Fatal("Missing S3. Please set S3 in appsettings.json."); + throw new ConfigurationErrorsException("Missing S3. Please set S3 in appsettings.json."); } } } diff --git a/DependencyInjection/AddTwitchService.cs b/DependencyInjection/AddTwitchService.cs index 23e5dcf..6957a62 100644 --- a/DependencyInjection/AddTwitchService.cs +++ b/DependencyInjection/AddTwitchService.cs @@ -6,51 +6,50 @@ using TwitchLib.Api.Core; using TwitchLib.Api.Interfaces; -namespace LivestreamRecorderService.DependencyInjection +namespace LivestreamRecorderService.DependencyInjection; + +public static partial class Extensions { - public static partial class Extensions + public static IServiceCollection AddTwitchService(this IServiceCollection services, IConfiguration configuration) { - public static IServiceCollection AddTwitchService(this IServiceCollection services, IConfiguration configuration) + try { - try - { - IConfigurationSection config = configuration.GetSection(TwitchOption.ConfigurationSectionName); - var twitchOptions = config.Get(); - if (null == twitchOptions) throw new ConfigurationErrorsException(); - - services.AddOptions() - .Bind(config) - .ValidateDataAnnotations() - .ValidateOnStart(); + IConfigurationSection config = configuration.GetSection(TwitchOption.ConfigurationSectionName); + var twitchOptions = config.Get(); + if (null == twitchOptions) throw new ConfigurationErrorsException(); - if (!twitchOptions.Enabled) - { - return services; - } + services.AddOptions() + .Bind(config) + .ValidateDataAnnotations() + .ValidateOnStart(); - if (string.IsNullOrEmpty(twitchOptions.ClientId) - || string.IsNullOrEmpty(twitchOptions.ClientSecret)) - throw new ConfigurationErrorsException(); - - services.AddSingleton(s => - { - var api = new TwitchAPI( - loggerFactory: s.GetRequiredService(), - settings: new ApiSettings() - { - ClientId = twitchOptions.ClientId, - Secret = twitchOptions.ClientSecret - }); - return api; - }); - services.AddScoped(); + if (!twitchOptions.Enabled) + { return services; } - catch (ConfigurationErrorsException) + + if (string.IsNullOrEmpty(twitchOptions.ClientId) + || string.IsNullOrEmpty(twitchOptions.ClientSecret)) + throw new ConfigurationErrorsException(); + + services.AddSingleton(s => { - Log.Fatal("Missing Twitch ClientId or ClientSecret. Please set Twitch:ClientId and Twitch:ClientSecret in appsettings.json."); - throw new ConfigurationErrorsException("Missing Twitch ClientId or ClientSecret. Please set Twitch:ClientId and Twitch:ClientSecret in appsettings.json."); - } + var api = new TwitchAPI( + loggerFactory: s.GetRequiredService(), + settings: new ApiSettings() + { + ClientId = twitchOptions.ClientId, + Secret = twitchOptions.ClientSecret + }); + return api; + }); + services.AddScoped(); + return services; + } + catch (ConfigurationErrorsException) + { + Log.Fatal("Missing Twitch ClientId or ClientSecret. Please set Twitch:ClientId and Twitch:ClientSecret in appsettings.json."); + throw new ConfigurationErrorsException("Missing Twitch ClientId or ClientSecret. Please set Twitch:ClientId and Twitch:ClientSecret in appsettings.json."); } } } diff --git a/Helper/ImageHelper.cs b/Helper/ImageHelper.cs index f1efcc4..14af578 100644 --- a/Helper/ImageHelper.cs +++ b/Helper/ImageHelper.cs @@ -12,7 +12,7 @@ public static async Task ConvertToAvifAsync(string path) IConversion conversion = FFmpeg.Conversions.New() .AddStream(mediaInfo.Streams) - .AddParameter($"-c:v libaom-av1 -still-picture 1") + .AddParameter("-c:v libaom-av1 -still-picture 1") .SetOutput(outputPath) .SetOverwriteOutput(true); conversion.OnProgress += (_, e) diff --git a/Helper/NameHelper.cs b/Helper/NameHelper.cs index bf175b9..30a3ed0 100644 --- a/Helper/NameHelper.cs +++ b/Helper/NameHelper.cs @@ -1,4 +1,5 @@ -using LivestreamRecorder.DB.Models; +using System.Globalization; +using LivestreamRecorder.DB.Models; using LivestreamRecorderService.Interfaces.Job.Downloader; namespace LivestreamRecorderService.Helper; @@ -11,15 +12,15 @@ public static string GetInstanceName(string id) .Split(".", StringSplitOptions.RemoveEmptyEntries).First() .Replace("_", "") .Replace(":", "") - .ToLower(); + .ToLower(new CultureInfo("en-US")); public static string GetFileName(Video video, string Platform) => Platform switch { "Youtube" or IYtarchiveService.name or IYtdlpService.name => $"{video.id}.mp4", "Twitch" or IStreamlinkService.name => $"{video.id}.mp4", - "Twitcasting" or ITwitcastingRecorderService.name => $"{video.ChannelId}_{DateTime.Now:yyyyMMddHHmmss}.mp4", - "FC2" or IFC2LiveDLService.name => $"{video.ChannelId}_{DateTime.Now:yyyyMMddHHmmss}.mp4", + "Twitcasting" or ITwitcastingRecorderService.name => $"{video.ChannelId}_{DateTime.UtcNow:yyyyMMddHHmmss}.mp4", + "FC2" or IFC2LiveDLService.name => $"{video.ChannelId}_{DateTime.UtcNow:yyyyMMddHHmmss}.mp4", _ => throw new NotImplementedException(), }; diff --git a/Helper/YoutubeDLHelper.cs b/Helper/YoutubeDLHelper.cs index 91896c5..007164f 100644 --- a/Helper/YoutubeDLHelper.cs +++ b/Helper/YoutubeDLHelper.cs @@ -29,6 +29,7 @@ internal static partial class YoutubeDL "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", Justification = $"{nameof(SourceGenerationContext)} is set.")] #pragma warning disable CA1068 // CancellationToken 參數必須位於最後 + // skipcq: CS-R1073 public static async Task> RunVideoDataFetch_Alt(this YoutubeDLSharp.YoutubeDL ytdl, string url, CancellationToken ct = default, bool flat = true, bool fetchComments = false, OptionSet overrideOptions = null) #pragma warning restore CA1068 // CancellationToken 參數必須位於最後 { @@ -56,6 +57,7 @@ public static async Task> RunVideoDataFetch_Alt(this Y optionSet = optionSet.OverrideOptions(overrideOptions); } + // skipcq: CS-W1028 YtdlpVideoData videoData = null; YoutubeDLProcess youtubeDLProcess = new(ytdl.YoutubeDLPath); youtubeDLProcess.OutputReceived += (o, e) => diff --git a/Models/Options/TwitchOption.cs b/Models/Options/TwitchOption.cs index 8ab23f6..f1f532f 100644 --- a/Models/Options/TwitchOption.cs +++ b/Models/Options/TwitchOption.cs @@ -1,16 +1,15 @@ using System.ComponentModel.DataAnnotations; -namespace LivestreamRecorderService.Models.Options +namespace LivestreamRecorderService.Models.Options; + +public class TwitchOption { - public class TwitchOption - { #pragma warning disable IDE1006 // 命名樣式 - public const string ConfigurationSectionName = "Twitch"; + public const string ConfigurationSectionName = "Twitch"; #pragma warning restore IDE1006 // 命名樣式 - [Required] - public bool Enabled { get; set; } = false; - public string? ClientId { get; set; } - public string? ClientSecret { get; set; } - } + [Required] + public bool Enabled { get; set; } = false; + public string? ClientId { get; set; } + public string? ClientSecret { get; set; } } diff --git a/ScopedServices/ChannelService.cs b/ScopedServices/ChannelService.cs index fa9b064..19d68c9 100644 --- a/ScopedServices/ChannelService.cs +++ b/ScopedServices/ChannelService.cs @@ -26,6 +26,6 @@ public async Task UpdateChannelLatestVideoAsync(Video video) _unitOfWork_Public.Commit(); } - public Task GetByChannelIdAndSource(string channelId, string source) + public Task GetByChannelIdAndSourceAsync(string channelId, string source) => channelRepository.GetChannelByIdAndSourceAsync(channelId, source); } diff --git a/ScopedServices/PlatformService/FC2Service.cs b/ScopedServices/PlatformService/FC2Service.cs index c195573..e3da6bc 100644 --- a/ScopedServices/PlatformService/FC2Service.cs +++ b/ScopedServices/PlatformService/FC2Service.cs @@ -67,7 +67,8 @@ public override async Task UpdateVideosDataAsync(Channel channel, CancellationTo logger.LogTrace("{channelId} is down.", channel.id); return; } - else if (!string.IsNullOrEmpty(videoId)) + + if (!string.IsNullOrEmpty(videoId)) { Video? video = await videoRepository.GetVideoByIdAndChannelIdAsync(videoId, channel.id); if (null != video) @@ -90,6 +91,9 @@ public override async Task UpdateVideosDataAsync(Channel channel, CancellationTo logger.LogWarning("{videoId} has already been archived. It is possible that an internet disconnect occurred during the process. Changed its state back to Recording.", video.id); video.Status = VideoStatus.WaitingToRecord; break; + default: + // All cases should be handled + break; } } else @@ -147,7 +151,7 @@ await fC2LiveDLService.InitJobAsync(url: $"https://live.fc2.com/{NameHelper.Chan })); if (null != discordService) { - await discordService.SendStartRecordingMessage(video, channel); + await discordService.SendStartRecordingMessageAsync(video, channel); } } } @@ -244,7 +248,7 @@ public override async Task UpdateVideoDataAsync(Video video, CancellationToken c if (video.Status >= VideoStatus.Archived && video.Status < VideoStatus.Expired) { video.Status = VideoStatus.Missing; - video.Note = $"Video missing because archived not found."; + video.Note = "Video missing because archived not found."; logger.LogInformation("Can not found archived, change video status to {status}", Enum.GetName(typeof(VideoStatus), video.Status)); } } diff --git a/ScopedServices/PlatformService/PlatformService.cs b/ScopedServices/PlatformService/PlatformService.cs index 3815495..bf0d5cf 100644 --- a/ScopedServices/PlatformService/PlatformService.cs +++ b/ScopedServices/PlatformService/PlatformService.cs @@ -28,7 +28,7 @@ public abstract class PlatformService : IPlatformService private string _ffmpegPath = "/usr/local/bin/ffmpeg"; private string _ytdlPath = "/venv/bin/yt-dlp"; - public PlatformService( + protected PlatformService( IChannelRepository channelRepository, IStorageService storageService, IHttpClientFactory httpClientFactory, @@ -92,7 +92,7 @@ public bool StepInterval(int elapsedTime) var res = await ytdl.RunVideoDataFetch_Alt(url, overrideOptions: optionSet, ct: cancellation); if (!res.Success) { - throw new Exception(string.Join(' ', res.ErrorOutput)); + throw new InvalidOperationException($"Failed to fetch video data from yt-dlp for URL: {url}. Errors: {string.Join(' ', res.ErrorOutput)}"); } YtdlpVideoData videoData = res.Data; @@ -126,10 +126,10 @@ public bool StepInterval(int elapsedTime) try { - var res = await ytdl.RunWithOptions(new string[] { url }, optionSet, ct: cancellation); + var res = await ytdl.RunWithOptions([url], optionSet, ct: cancellation); if (!res.Success) { - throw new Exception(string.Join(' ', res.ErrorOutput)); + throw new InvalidOperationException($"Failed to fetch video data from yt-dlp for URL: {url}. Errors: {string.Join(' ', res.ErrorOutput)}"); } string[] videoIds = res.Data; diff --git a/ScopedServices/PlatformService/TwitcastingService.cs b/ScopedServices/PlatformService/TwitcastingService.cs index a2e4e55..a9dc4b7 100644 --- a/ScopedServices/PlatformService/TwitcastingService.cs +++ b/ScopedServices/PlatformService/TwitcastingService.cs @@ -81,6 +81,9 @@ public override async Task UpdateVideosDataAsync(Channel channel, CancellationTo logger.LogWarning("{videoId} has already been archived. It is possible that an internet disconnect occurred during the process. Changed its state back to Recording.", video.id); video.Status = VideoStatus.WaitingToRecord; break; + default: + logger.LogWarning("{videoId} is in {status}.", video.id, Enum.GetName(typeof(VideoStatus), video.Status)); + return; } } else @@ -124,7 +127,7 @@ await twitcastingRecorderService.InitJobAsync(url: videoId, if (null != discordService) { - await discordService.SendStartRecordingMessage(video, channel); + await discordService.SendStartRecordingMessageAsync(video, channel); } } } @@ -290,10 +293,10 @@ public override async Task UpdateVideoDataAsync(Video video, CancellationToken c if (video.SourceStatus != VideoStatus.Reject) { video.SourceStatus = VideoStatus.Reject; - video.Note = $"Video source is detected access required."; + video.Note = "Video source is detected access required."; if (null != discordService) { - await discordService.SendDeletedMessage(video, channel); + await discordService.SendDeletedMessageAsync(video, channel); } logger.LogInformation("Video source is {status} because it is detected access required.", Enum.GetName(typeof(VideoStatus), video.SourceStatus)); } @@ -311,7 +314,7 @@ public override async Task UpdateVideoDataAsync(Video video, CancellationToken c if (null != discordService) { // First detected - await discordService.SendDeletedMessage(video, channel); + await discordService.SendDeletedMessageAsync(video, channel); } } video.SourceStatus = VideoStatus.Deleted; @@ -335,7 +338,7 @@ public override async Task UpdateVideoDataAsync(Video video, CancellationToken c if (video.Status >= VideoStatus.Archived && video.Status < VideoStatus.Expired) { video.Status = VideoStatus.Missing; - video.Note = $"Video missing because archived not found."; + video.Note = "Video missing because archived not found."; logger.LogInformation("Can not found archived, change video status to {status}", Enum.GetName(typeof(VideoStatus), video.Status)); } } diff --git a/ScopedServices/PlatformService/TwitchService.cs b/ScopedServices/PlatformService/TwitchService.cs index 04d25ba..165fbb6 100644 --- a/ScopedServices/PlatformService/TwitchService.cs +++ b/ScopedServices/PlatformService/TwitchService.cs @@ -76,6 +76,9 @@ public override async Task UpdateVideosDataAsync(Channel channel, CancellationTo case VideoStatus.Skipped: logger.LogTrace("{videoId} is rejected for recording.", video.id); return; + default: + logger.LogWarning("{videoId} is in {status}, skip.", video.id, Enum.GetName(typeof(VideoStatus), video.Status)); + return; } } else @@ -117,7 +120,7 @@ await streamlinkService.InitJobAsync(url: video.id, logger.LogInformation("{channelId} is now lived! Start recording.", channel.id); if (null != discordService) { - await discordService.SendStartRecordingMessage(video, channel); + await discordService.SendStartRecordingMessageAsync(video, channel); } } @@ -150,7 +153,7 @@ public override async Task UpdateVideoDataAsync(Video video, CancellationToken c if (video.Status >= VideoStatus.Archived && video.Status < VideoStatus.Expired) { video.Status = VideoStatus.Missing; - video.Note = $"Video missing because archived not found."; + video.Note = "Video missing because archived not found."; logger.LogInformation("Can not found archived, change video status to {status}", Enum.GetName(typeof(VideoStatus), video.Status)); } } diff --git a/ScopedServices/PlatformService/YoutubeService.cs b/ScopedServices/PlatformService/YoutubeService.cs index bbe7daa..cdedf40 100644 --- a/ScopedServices/PlatformService/YoutubeService.cs +++ b/ScopedServices/PlatformService/YoutubeService.cs @@ -173,12 +173,12 @@ public override async Task UpdateVideoDataAsync(Video video, CancellationToken c if (channel?.SkipNotLiveStream == true) { - video.Note = $"Video skipped because it is not live stream."; + video.Note = "Video skipped because it is not live stream."; // First detected if (video.Status != VideoStatus.Skipped && null != discordService) { - await discordService.SendSkippedMessage(video, channel); + await discordService.SendSkippedMessageAsync(video, channel); } video.Status = VideoStatus.Skipped; logger.LogInformation("Change video {videoId} status to {videoStatus}", video.id, Enum.GetName(typeof(VideoStatus), video.Status)); @@ -213,6 +213,7 @@ public override async Task UpdateVideoDataAsync(Video video, CancellationToken c logger.LogWarning("Video {videoId} is currently in post_live status. Please wait for YouTube to prepare the video for download. If the admin still wants to download it, please manually change the video status to \"WaitingToDownload\".", video.id); goto case "_live"; + // skipcq: CS-W1001 case "was_live": switch (video.Status) { @@ -220,7 +221,7 @@ public override async Task UpdateVideoDataAsync(Video video, CancellationToken c // Will fall in here when adding a new channel. case VideoStatus.Unknown: video.Status = VideoStatus.Expired; - video.Note = $"Video expired because it is an old live stream."; + video.Note = "Video expired because it is an old live stream."; logger.LogInformation("Change video {videoId} status to {videoStatus}", video.id, Enum.GetName(typeof(VideoStatus), video.Status)); video.Thumbnail = await DownloadThumbnailAsync(videoData.Thumbnail, video.id, cancellation); break; @@ -258,12 +259,12 @@ public override async Task UpdateVideoDataAsync(Video video, CancellationToken c if (video.Status == VideoStatus.Unknown && channel?.SkipNotLiveStream == true) { - video.Note = $"Video skipped because it is not live stream."; + video.Note = "Video skipped because it is not live stream."; // First detected if (video.Status != VideoStatus.Skipped && null != discordService) { - await discordService.SendSkippedMessage(video, channel); + await discordService.SendSkippedMessageAsync(video, channel); } video.Status = VideoStatus.Skipped; logger.LogInformation("Change video {videoId} status to {videoStatus}", video.id, Enum.GetName(typeof(VideoStatus), video.Status)); @@ -282,7 +283,7 @@ public override async Task UpdateVideoDataAsync(Video video, CancellationToken c // Old unarchived video. // Will fall in here when adding a new channel. video.Status = VideoStatus.Expired; - video.Note = $"Video expired because it is an old video."; + video.Note = "Video expired because it is an old video."; logger.LogInformation("Change video {videoId} status to {videoStatus}", video.id, Enum.GetName(typeof(VideoStatus), video.Status)); video.Thumbnail = await DownloadThumbnailAsync(videoData.Thumbnail, video.id, cancellation); break; @@ -322,7 +323,7 @@ public override async Task UpdateVideoDataAsync(Video video, CancellationToken c video.SourceStatus = VideoStatus.Deleted; if (null != discordService) { - await discordService.SendDeletedMessage(video, channel); + await discordService.SendDeletedMessageAsync(video, channel); } } @@ -355,7 +356,7 @@ public override async Task UpdateVideoDataAsync(Video video, CancellationToken c video.SourceStatus = VideoStatus.Missing; if (null != discordService) { - await discordService.SendSkippedMessage(video, channel); + await discordService.SendSkippedMessageAsync(video, channel); } } video.Status = VideoStatus.Missing; @@ -365,7 +366,7 @@ public override async Task UpdateVideoDataAsync(Video video, CancellationToken c if (video.Status >= VideoStatus.Archived && video.Status < VideoStatus.Expired) { video.Status = VideoStatus.Missing; - video.Note = $"Video missing because archived not found."; + video.Note = "Video missing because archived not found."; logger.LogWarning("Can not found archived, change video status to {status}", Enum.GetName(typeof(VideoStatus), video.Status)); } } @@ -398,13 +399,13 @@ public override async Task UpdateVideoDataAsync(Video video, CancellationToken c && (null == videoData.Subtitles.LiveChat || videoData.Subtitles.LiveChat.Count == 0)) { - video.Note = $"Video source is Edited because it has been restored from rejection or deletion."; + video.Note = "Video source is Edited because it has been restored from rejection or deletion."; if (video.SourceStatus != VideoStatus.Edited) { video.SourceStatus = VideoStatus.Edited; if (null != discordService) { - await discordService.SendDeletedMessage(video, channel); + await discordService.SendDeletedMessageAsync(video, channel); } } video.SourceStatus = VideoStatus.Edited; @@ -420,12 +421,12 @@ public override async Task UpdateVideoDataAsync(Video video, CancellationToken c // Not archived if (video.Status < VideoStatus.Archived) { - video.Note = $"Video is Skipped because it is detected access required or copyright notice."; + video.Note = "Video is Skipped because it is detected access required or copyright notice."; // First detected if (video.Status != VideoStatus.Skipped && null != discordService) { - await discordService.SendSkippedMessage(video, channel); + await discordService.SendSkippedMessageAsync(video, channel); } video.Status = VideoStatus.Skipped; logger.LogInformation("Video is {status} because it is detected access required or copyright notice.", Enum.GetName(typeof(VideoStatus), video.Status)); @@ -434,16 +435,18 @@ public override async Task UpdateVideoDataAsync(Video video, CancellationToken c else if (video.SourceStatus != VideoStatus.Reject) { video.SourceStatus = VideoStatus.Reject; - video.Note = $"Video source is detected access required or copyright notice."; + video.Note = "Video source is detected access required or copyright notice."; if (null != discordService) { - await discordService.SendDeletedMessage(video, channel); + await discordService.SendDeletedMessageAsync(video, channel); } } video.SourceStatus = VideoStatus.Reject; logger.LogInformation("Video source is {status} because it is detected access required or copyright notice.", Enum.GetName(typeof(VideoStatus), video.SourceStatus)); - + break; + default: + logger.LogWarning("Video {videoId} has a Unknown availability!", video.id); break; } @@ -451,14 +454,14 @@ public override async Task UpdateVideoDataAsync(Video video, CancellationToken c { await ytarchiveService.InitJobAsync(url: video.id, video: video, - useCookiesFile: channel?.UseCookiesFile ?? false, + useCookiesFile: channel?.UseCookiesFile == true, cancellation: cancellation); video.Status = VideoStatus.Recording; logger.LogInformation("{videoId} is now lived! Start recording.", video.id); if (null != discordService) { - await discordService.SendStartRecordingMessage(video, channel); + await discordService.SendStartRecordingMessageAsync(video, channel); } } diff --git a/ScopedServices/VideoService.cs b/ScopedServices/VideoService.cs index 23403c8..53a32df 100644 --- a/ScopedServices/VideoService.cs +++ b/ScopedServices/VideoService.cs @@ -76,17 +76,14 @@ public async Task TransferVideoFromSharedVolumeToStorageAsync(Video video, Cance { await UpdateVideoStatusAsync(video, VideoStatus.Uploading); - string instanceName; switch (_serviceOptions.StorageService) { case ServiceName.AzureBlobStorage: - instanceName = azureUploaderService.GetInstanceName(video.id); await azureUploaderService.InitJobAsync(url: video.id, video: video, cancellation: cancellation); break; case ServiceName.S3: - instanceName = s3UploaderService.GetInstanceName(video.id); await s3UploaderService.InitJobAsync(url: video.id, video: video, cancellation: cancellation); @@ -98,7 +95,7 @@ await s3UploaderService.InitJobAsync(url: video.id, catch (Exception e) { await UpdateVideoStatusAsync(video, VideoStatus.Error); - await UpdateVideoNoteAsync(video, $"Exception happened when uploading files to storage. Please contact admin if you see this message."); + await UpdateVideoNoteAsync(video, "Exception happened when uploading files to storage. Please contact admin if you see this message."); logger.LogError(e, "Exception happened when uploading files to storage: {videoId}", video.id); } } diff --git a/SingletonServices/ACI/ACIService.cs b/SingletonServices/ACI/ACIService.cs index 7bc61fa..1d4fffb 100644 --- a/SingletonServices/ACI/ACIService.cs +++ b/SingletonServices/ACI/ACIService.cs @@ -53,7 +53,8 @@ public async Task RemoveCompletedJobsAsync(Video video, CancellationToken cancel logger.LogError("Failed to get ACI instance for {videoId} when removing completed jobs. Please check if the ACI exists.", video.id); return; } - else if (video.Source is "Twitcasting" or "Twitch" or "FC2" + + if (video.Source is "Twitcasting" or "Twitch" or "FC2" && !resource.Data.Name.StartsWith(IAzureUploaderService.name) && !resource.Data.Name.StartsWith(IS3UploaderService.name)) { @@ -66,14 +67,14 @@ public async Task RemoveCompletedJobsAsync(Video video, CancellationToken cancel if (await IsJobFailedAsync(video, cancellation)) { logger.LogError("ACI status FAILED! {videoId} {jobName}", video.id, jobName); - throw new Exception($"ACI status FAILED! {jobName}"); + throw new InvalidOperationException($"ACI status FAILED! {jobName}"); } var status = (await resource.DeleteAsync(Azure.WaitUntil.Completed, cancellation)).GetRawResponse(); if (status.IsError) { logger.LogError("Failed to delete job {jobName} {videoId} {status}", jobName, video.id, status.ReasonPhrase); - throw new Exception($"Failed to delete job {jobName} {video.id} {status.ReasonPhrase}"); + throw new InvalidOperationException($"Failed to delete job {jobName} {video.id} {status.ReasonPhrase}"); } logger.LogInformation("Delete ACI {jobName} for video {videoId}", jobName, video.id); } diff --git a/SingletonServices/ACI/ACIServiceBase.cs b/SingletonServices/ACI/ACIServiceBase.cs index 86bb636..8a26da5 100644 --- a/SingletonServices/ACI/ACIServiceBase.cs +++ b/SingletonServices/ACI/ACIServiceBase.cs @@ -1,4 +1,5 @@ -using Azure; +using System.Globalization; +using Azure; using Azure.ResourceManager; using Azure.ResourceManager.ContainerInstance; using Azure.ResourceManager.Resources; @@ -129,7 +130,7 @@ protected async Task> CreateResourceAsync( } public string GetInstanceName(string videoId) - => (Name + NameHelper.GetInstanceName(videoId)).ToLower(); + => (Name + NameHelper.GetInstanceName(videoId)).ToLower(new CultureInfo("en-US")); private async Task StartOldJobAsync(GenericResource job, Video video, diff --git a/SingletonServices/ACI/Downloader/FC2LiveDLService.cs b/SingletonServices/ACI/Downloader/FC2LiveDLService.cs index d13f1e9..21a1719 100644 --- a/SingletonServices/ACI/Downloader/FC2LiveDLService.cs +++ b/SingletonServices/ACI/Downloader/FC2LiveDLService.cs @@ -42,6 +42,7 @@ protected override Task> CreateNewJobAsync( { return doWithImage("ghcr.io/recorder-moe/fc2-live-dl:latest"); } + // skipcq: CS-R1008 catch (Exception) { // Use DockerHub as fallback diff --git a/SingletonServices/ACI/Downloader/StreamlinkService.cs b/SingletonServices/ACI/Downloader/StreamlinkService.cs index 2fd391f..402012f 100644 --- a/SingletonServices/ACI/Downloader/StreamlinkService.cs +++ b/SingletonServices/ACI/Downloader/StreamlinkService.cs @@ -42,6 +42,7 @@ protected override Task> CreateNewJobAsync( { return doWithImage("ghcr.io/recorder-moe/streamlink:latest"); } + // skipcq: CS-R1008 catch (Exception) { // Use DockerHub as fallback @@ -64,7 +65,7 @@ Task> doWithImage(string imageName) }, commandOverrideArray = new { - value = new string[] { + value = new[] { "/bin/sh", "-c", $"streamlink --twitch-disable-ads -o '/download/{filename}' -f 'twitch.tv/{NameHelper.ChangeId.ChannelId.PlatformType(video.ChannelId, Name)}' best && cd /download && for file in *.mp4; do ffmpeg -i \"$file\" -map 0:v:0 -map 0:a:0 -c copy -movflags +faststart 'temp.mp4' && mv 'temp.mp4' \"/sharedvolume/$file\"; done" } diff --git a/SingletonServices/ACI/Downloader/TwitcastingRecorderService.cs b/SingletonServices/ACI/Downloader/TwitcastingRecorderService.cs index b7b4796..2b33b56 100644 --- a/SingletonServices/ACI/Downloader/TwitcastingRecorderService.cs +++ b/SingletonServices/ACI/Downloader/TwitcastingRecorderService.cs @@ -41,6 +41,7 @@ protected override Task> CreateNewJobAsync( { return doWithImage("ghcr.io/recorder-moe/twitcasting-recorder:latest"); } + // skipcq: CS-R1008 catch (Exception) { // Use DockerHub as fallback @@ -64,7 +65,7 @@ Task> doWithImage(string imageName) }, commandOverrideArray = new { - value = new string[] { + value = new[] { "dumb-init", "--", "/bin/sh", "-c", $"/bin/sh /app/record_twitcast.sh {NameHelper.ChangeId.ChannelId.PlatformType(video.ChannelId, Name)} once -o {Path.GetFileNameWithoutExtension(filename)} && mv /download/*.mp4 /sharedvolume/" diff --git a/SingletonServices/ACI/Downloader/YtarchiveService.cs b/SingletonServices/ACI/Downloader/YtarchiveService.cs index 50ecd7f..6896fb3 100644 --- a/SingletonServices/ACI/Downloader/YtarchiveService.cs +++ b/SingletonServices/ACI/Downloader/YtarchiveService.cs @@ -30,6 +30,7 @@ protected override Task> CreateNewJobAsync( { return doWithImage("ghcr.io/recorder-moe/ytarchive:latest"); } + // skipcq: CS-R1008 catch (Exception) { // Use DockerHub as fallback diff --git a/SingletonServices/ACI/Downloader/YtdlpService.cs b/SingletonServices/ACI/Downloader/YtdlpService.cs index c320e0d..0256c1c 100644 --- a/SingletonServices/ACI/Downloader/YtdlpService.cs +++ b/SingletonServices/ACI/Downloader/YtdlpService.cs @@ -30,6 +30,7 @@ protected override Task> CreateNewJobAsync( { return doWithImage("ghcr.io/recorder-moe/yt-dlp:latest"); } + // skipcq: CS-R1008 catch (Exception) { // Use DockerHub as fallback diff --git a/SingletonServices/ACI/Uploader/AzureUploaderService.cs b/SingletonServices/ACI/Uploader/AzureUploaderService.cs index f7c1247..cbbbd2e 100644 --- a/SingletonServices/ACI/Uploader/AzureUploaderService.cs +++ b/SingletonServices/ACI/Uploader/AzureUploaderService.cs @@ -28,6 +28,7 @@ protected override Task> CreateNewJobAsync( { return doWithImage("ghcr.io/recorder-moe/azure-uploader:latest"); } + // skipcq: CS-R1008 catch (Exception) { // Use DockerHub as fallback @@ -50,7 +51,7 @@ Task> doWithImage(string imageName) }, commandOverrideArray = new { - value = new string[] { + value = new[] { "/bin/sh", "-c", $"/app/azure-uploader.sh {video.Filename?.Replace(".mp4", "")}" } diff --git a/SingletonServices/ACI/Uploader/S3UploaderService.cs b/SingletonServices/ACI/Uploader/S3UploaderService.cs index d44b3d6..973fee4 100644 --- a/SingletonServices/ACI/Uploader/S3UploaderService.cs +++ b/SingletonServices/ACI/Uploader/S3UploaderService.cs @@ -31,6 +31,7 @@ protected override Task> CreateNewJobAsync( { return doWithImage("ghcr.io/recorder-moe/s3-uploader:latest"); } + // skipcq: CS-R1008 catch (Exception) { // Use DockerHub as fallback @@ -53,7 +54,7 @@ Task> doWithImage(string imageName) }, commandOverrideArray = new { - value = new string[] { + value = new[] { "/bin/sh", "-c", $"/app/s3-uploader.sh {video.Filename?.Replace(".mp4", "")}" } diff --git a/SingletonServices/DiscordService.cs b/SingletonServices/DiscordService.cs index ddfc1a7..29066ba 100644 --- a/SingletonServices/DiscordService.cs +++ b/SingletonServices/DiscordService.cs @@ -50,6 +50,7 @@ public DiscordService( /// /// /// + // skipcq: CS-R1073 private Task DiscordWebhookClient_Log(LogMessage arg) => Task.Run(() => { @@ -70,7 +71,7 @@ private Task DiscordWebhookClient_Log(LogMessage arg) case LogSeverity.Verbose: _logger.LogTrace("{message}", arg); break; - case LogSeverity.Debug: + // case LogSeverity.Debug: default: _logger.LogDebug("{message}", arg); break; @@ -78,7 +79,7 @@ private Task DiscordWebhookClient_Log(LogMessage arg) }); #endregion - public Task SendStartRecordingMessage(Video video, Channel? channel) + public Task SendStartRecordingMessageAsync(Video video, Channel? channel) { var embedBuilder = GetEmbedBuilder(video, channel); embedBuilder.WithTitle("Start Recording"); @@ -89,7 +90,7 @@ public Task SendStartRecordingMessage(Video video, Channel? channel) return SendMessageAsync(embedBuilder.Build(), componentBuilder.Build()); } - public Task SendArchivedMessage(Video video, Channel? channel) + public Task SendArchivedMessageAsync(Video video, Channel? channel) { var embedBuilder = GetEmbedBuilder(video, channel); embedBuilder.WithTitle("Video archived"); @@ -100,7 +101,7 @@ public Task SendArchivedMessage(Video video, Channel? channel) return SendMessageAsync(embedBuilder.Build(), componentBuilder.Build()); } - public Task SendSkippedMessage(Video video, Channel? channel) + public Task SendSkippedMessageAsync(Video video, Channel? channel) { var embedBuilder = GetEmbedBuilder(video, channel); embedBuilder.WithTitle("Video skipped"); @@ -111,7 +112,7 @@ public Task SendSkippedMessage(Video video, Channel? channel) return SendMessageAsync(embedBuilder.Build(), componentBuilder.Build(), video.Note ?? ""); } - public Task SendDeletedMessage(Video video, Channel? channel) + public Task SendDeletedMessageAsync(Video video, Channel? channel) { var embedBuilder = GetEmbedBuilder(video, channel); embedBuilder.WithTitle("Source " + Enum.GetName(typeof(VideoStatus), video.SourceStatus ?? VideoStatus.Unknown)); diff --git a/SingletonServices/Kubernetes/Downloader/FC2LiveDLService.cs b/SingletonServices/Kubernetes/Downloader/FC2LiveDLService.cs index 026df7b..26b28de 100644 --- a/SingletonServices/Kubernetes/Downloader/FC2LiveDLService.cs +++ b/SingletonServices/Kubernetes/Downloader/FC2LiveDLService.cs @@ -26,6 +26,7 @@ protected override Task CreateNewJobAsync(string _, { return doWithImage("ghcr.io/recorder-moe/fc2-live-dl:latest"); } + // skipcq: CS-R1008 catch (Exception) { // Use DockerHub as fallback diff --git a/SingletonServices/Kubernetes/Downloader/StreamlinkService.cs b/SingletonServices/Kubernetes/Downloader/StreamlinkService.cs index 982c734..b570a21 100644 --- a/SingletonServices/Kubernetes/Downloader/StreamlinkService.cs +++ b/SingletonServices/Kubernetes/Downloader/StreamlinkService.cs @@ -26,6 +26,7 @@ protected override Task CreateNewJobAsync(string _, { return doWithImage("ghcr.io/recorder-moe/streamlink:latest"); } + // skipcq: CS-R1008 catch (Exception) { // Use DockerHub as fallback @@ -50,7 +51,7 @@ Task doWithImage(string imageName) }, commandOverrideArray = new { - value = new string[] { + value = new[] { "/bin/sh", "-c", $"streamlink --twitch-disable-ads -o '/download/{filename}' -f 'twitch.tv/{NameHelper.ChangeId.ChannelId.PlatformType(video.ChannelId, Name)}' best && cd /download && for file in *.mp4; do ffmpeg -i \"$file\" -map 0:v:0 -map 0:a:0 -c copy -movflags +faststart 'temp.mp4' && mv 'temp.mp4' \"/sharedvolume/$file\"; done" } diff --git a/SingletonServices/Kubernetes/Downloader/TwitcastingRecorderService.cs b/SingletonServices/Kubernetes/Downloader/TwitcastingRecorderService.cs index cc04527..e9c9c99 100644 --- a/SingletonServices/Kubernetes/Downloader/TwitcastingRecorderService.cs +++ b/SingletonServices/Kubernetes/Downloader/TwitcastingRecorderService.cs @@ -26,6 +26,7 @@ protected override Task CreateNewJobAsync(string _, { return doWithImage("ghcr.io/recorder-moe/twitcasting-recorder:latest"); } + // skipcq: CS-R1008 catch (Exception) { // Use DockerHub as fallback @@ -50,7 +51,7 @@ Task doWithImage(string imageName) }, commandOverrideArray = new { - value = new string[] { + value = new[] { "dumb-init", "--", "/bin/sh", "-c", $"/bin/sh /app/record_twitcast.sh {NameHelper.ChangeId.ChannelId.PlatformType(video.ChannelId, Name)} once -o {Path.GetFileNameWithoutExtension(filename)} && mv /download/*.mp4 /sharedvolume/" diff --git a/SingletonServices/Kubernetes/Downloader/YtarchiveService.cs b/SingletonServices/Kubernetes/Downloader/YtarchiveService.cs index 7b69d56..eb91c90 100644 --- a/SingletonServices/Kubernetes/Downloader/YtarchiveService.cs +++ b/SingletonServices/Kubernetes/Downloader/YtarchiveService.cs @@ -28,6 +28,7 @@ protected override Task CreateNewJobAsync(string url, { return doWithImage("ghcr.io/recorder-moe/ytarchive:latest"); } + // skipcq: CS-R1008 catch (Exception) { // Use DockerHub as fallback diff --git a/SingletonServices/Kubernetes/Downloader/YtdlpService.cs b/SingletonServices/Kubernetes/Downloader/YtdlpService.cs index 5a0871b..f883fca 100644 --- a/SingletonServices/Kubernetes/Downloader/YtdlpService.cs +++ b/SingletonServices/Kubernetes/Downloader/YtdlpService.cs @@ -28,6 +28,7 @@ protected override Task CreateNewJobAsync(string url, { return doWithImage("ghcr.io/recorder-moe/yt-dlp:latest"); } + // skipcq: CS-R1008 catch (Exception) { // Use DockerHub as fallback diff --git a/SingletonServices/Kubernetes/KubernetesService.cs b/SingletonServices/Kubernetes/KubernetesService.cs index 0ac602f..a4293f2 100644 --- a/SingletonServices/Kubernetes/KubernetesService.cs +++ b/SingletonServices/Kubernetes/KubernetesService.cs @@ -36,10 +36,9 @@ public KubernetesService( KubernetesNamespace = options.Value.Namespace ?? KubernetesNamespace; EnsureNamespaceExists(KubernetesNamespace); - if (_serviceOption.SharedVolumeService == ServiceName.AzureFileShare) - { - if (!CheckSecretExists()) CreateSecret(); - } + if (_serviceOption.SharedVolumeService == ServiceName.AzureFileShare + && !CheckSecretExists()) + CreateSecret(); if (_serviceOption.SharedVolumeService == ServiceName.CustomPVC) { @@ -76,7 +75,7 @@ public async Task RemoveCompletedJobsAsync(Video video, CancellationToken cancel if (jobs.Count == 0) { _logger.LogError("Failed to retrieve K8s job for {videoId} while removing completed job. Please verify if any job exists.", video.id); - throw new Exception($"No K8s jobs found! {video.id}"); + throw new InvalidOperationException($"No K8s jobs found! {video.id}"); } if (jobs.Count > 1) @@ -90,7 +89,7 @@ public async Task RemoveCompletedJobsAsync(Video video, CancellationToken cancel if (await IsJobFailedAsync(video, cancellation)) { _logger.LogError("K8s job status FAILED! {videoId} {jobName}", video.id, jobName); - throw new Exception($"K8s job status FAILED! {jobName}"); + throw new InvalidOperationException($"K8s job status FAILED! {jobName}"); } var status = await _client.DeleteNamespacedJobAsync(name: jobName, @@ -100,7 +99,7 @@ public async Task RemoveCompletedJobsAsync(Video video, CancellationToken cancel if (status.Status != "Success") { _logger.LogError("Failed to delete job {jobName} {videoId} {status}", jobName, video.id, status.Message); - throw new Exception($"Failed to delete job {jobName} {video.id} {status.Message}"); + throw new InvalidOperationException($"Failed to delete job {jobName} {video.id} {status.Message}"); } _logger.LogInformation("K8s job {jobName} {videoId} removed", jobName, video.id); } diff --git a/SingletonServices/Kubernetes/KubernetesServiceBase.cs b/SingletonServices/Kubernetes/KubernetesServiceBase.cs index 2dc27dc..f23245b 100644 --- a/SingletonServices/Kubernetes/KubernetesServiceBase.cs +++ b/SingletonServices/Kubernetes/KubernetesServiceBase.cs @@ -8,6 +8,7 @@ using LivestreamRecorderService.Models.Options; using Microsoft.Extensions.Options; using System.Configuration; +using System.Globalization; namespace LivestreamRecorderService.SingletonServices.Kubernetes; @@ -137,7 +138,7 @@ private V1Volume GetSharedVolumeDefinition() }; public string GetInstanceName(string videoId) - => (Name + NameHelper.GetInstanceName(videoId)).ToLower(); + => (Name + NameHelper.GetInstanceName(videoId)).ToLower(new CultureInfo("en-US")); // Must be override protected abstract Task CreateNewJobAsync(string id, diff --git a/SingletonServices/Kubernetes/Uploader/AzureUploaderService.cs b/SingletonServices/Kubernetes/Uploader/AzureUploaderService.cs index 668b54f..9fbc97a 100644 --- a/SingletonServices/Kubernetes/Uploader/AzureUploaderService.cs +++ b/SingletonServices/Kubernetes/Uploader/AzureUploaderService.cs @@ -42,6 +42,7 @@ protected override Task CreateNewJobAsync(string _, { return doWithImage("ghcr.io/recorder-moe/azure-uploader:latest"); } + // skipcq: CS-R1008 catch (Exception) { // Use DockerHub as fallback @@ -64,7 +65,7 @@ Task doWithImage(string imageName) }, commandOverrideArray = new { - value = new string[] { + value = new[] { "/bin/sh", "-c", $"/app/azure-uploader.sh {video.Filename?.Replace(".mp4", "")}" } diff --git a/SingletonServices/Kubernetes/Uploader/S3UploaderService.cs b/SingletonServices/Kubernetes/Uploader/S3UploaderService.cs index e927e4b..49c9dfd 100644 --- a/SingletonServices/Kubernetes/Uploader/S3UploaderService.cs +++ b/SingletonServices/Kubernetes/Uploader/S3UploaderService.cs @@ -29,6 +29,7 @@ protected override Task CreateNewJobAsync(string _, { return doWithImage("ghcr.io/recorder-moe/s3-uploader:latest"); } + // skipcq: CS-R1008 catch (Exception) { // Use DockerHub as fallback @@ -51,7 +52,7 @@ Task doWithImage(string imageName) }, commandOverrideArray = new { - value = new string[] { + value = new[] { "/bin/sh", "-c", $"/app/s3-uploader.sh {video.Filename?.Replace(".mp4", "")}" } diff --git a/SingletonServices/RecordService.cs b/SingletonServices/RecordService.cs index b06d7a4..6a3b7c7 100644 --- a/SingletonServices/RecordService.cs +++ b/SingletonServices/RecordService.cs @@ -32,7 +32,7 @@ public async Task HandledFailedJobsAsync(VideoService videoService, Cancellation // Only check videos that started recording/download more than 3 minutes ago // to avoid checking videos that are not finished deployment yet. .Where(p => null != p.Timestamps.ActualStartTime - && DateTime.Now.Subtract(p.Timestamps.ActualStartTime.Value).TotalMinutes >= 3) + && DateTime.UtcNow.Subtract(p.Timestamps.ActualStartTime.Value).TotalMinutes >= 3) .ToList(); if (videos.Count > 0) @@ -52,7 +52,7 @@ public async Task HandledFailedJobsAsync(VideoService videoService, Cancellation break; default: await videoService.UpdateVideoStatusAsync(video, VideoStatus.Error); - await videoService.UpdateVideoNoteAsync(video, $"This recording FAILED! Please contact admin if you see this message."); + await videoService.UpdateVideoNoteAsync(video, "This recording FAILED! Please contact admin if you see this message."); logger.LogWarning("{videoId} is failed.", video.id); break; } @@ -82,7 +82,7 @@ public async Task CreateStartRecordJobAsync(VideoService videoService, ChannelSe foreach (var video in videos) { using var _ = LogContext.PushProperty("videoId", video.id); - var channel = await channelService.GetByChannelIdAndSource(video.ChannelId, video.Source); + var channel = await channelService.GetByChannelIdAndSourceAsync(video.ChannelId, video.Source); logger.LogInformation("Start to create recording job: {videoId}", video.id); try { @@ -154,7 +154,7 @@ public async Task CreateStartDownloadJobAsync(VideoService videoService, Channel foreach (var video in videos) { using var _ = LogContext.PushProperty("videoId", video.id); - var channel = await channelService.GetByChannelIdAndSource(video.ChannelId, video.Source); + var channel = await channelService.GetByChannelIdAndSourceAsync(video.ChannelId, video.Source); logger.LogInformation("Start to create downloading job: {videoId}", video.id); try { @@ -286,7 +286,7 @@ public async Task PcocessFinishedVideoAsync(VideoService videoService, ChannelSe catch (Exception e) { await videoService.UpdateVideoStatusAsync(video, VideoStatus.Error); - await videoService.UpdateVideoNoteAsync(video, $"This recording is FAILED! Please contact admin if you see this message."); + await videoService.UpdateVideoNoteAsync(video, "This recording is FAILED! Please contact admin if you see this message."); logger.LogError(e, "Recording FAILED: {videoId}", video.id); return; } @@ -296,7 +296,7 @@ public async Task ProcessUploadedVideoAsync(VideoService videoService, ChannelSe { using var _ = LogContext.PushProperty("videoId", video.id); - await discordService.SendArchivedMessage(video, await channelService.GetByChannelIdAndSource(video.ChannelId, video.Source)); + await discordService.SendArchivedMessageAsync(video, await channelService.GetByChannelIdAndSourceAsync(video.ChannelId, video.Source)); logger.LogInformation("Video {videoId} is successfully uploaded to Storage.", video.id); await videoService.UpdateVideoStatusAsync(video, VideoStatus.Archived); @@ -307,7 +307,7 @@ public async Task ProcessUploadedVideoAsync(VideoService videoService, ChannelSe catch (Exception e) { await videoService.UpdateVideoStatusAsync(video, VideoStatus.Error); - await videoService.UpdateVideoNoteAsync(video, $"This recording is FAILED! Please contact admin if you see this message."); + await videoService.UpdateVideoNoteAsync(video, "This recording is FAILED! Please contact admin if you see this message."); logger.LogError(e, "Uploading FAILED: {videoId}", video.id); return; } diff --git a/Workers/HeartbeatWorker.cs b/Workers/HeartbeatWorker.cs index a72de91..6745b74 100644 --- a/Workers/HeartbeatWorker.cs +++ b/Workers/HeartbeatWorker.cs @@ -20,7 +20,7 @@ protected override async Task ExecuteAsync(CancellationToken stoppingToken) while (!stoppingToken.IsCancellationRequested) { - using var __ = LogContext.PushProperty("WorkerRunId", $"{nameof(HeartbeatWorker)}_{DateTime.Now:yyyyMMddHHmmssfff}"); + using var __ = LogContext.PushProperty("WorkerRunId", $"{nameof(HeartbeatWorker)}_{DateTime.UtcNow:yyyyMMddHHmmssfff}"); await SendHeartbeatAsync(); diff --git a/Workers/MonitorWorker.cs b/Workers/MonitorWorker.cs index fb2ba1f..280dd03 100644 --- a/Workers/MonitorWorker.cs +++ b/Workers/MonitorWorker.cs @@ -24,7 +24,7 @@ protected override async Task ExecuteAsync(CancellationToken stoppingToken) while (!stoppingToken.IsCancellationRequested) { - using var __ = LogContext.PushProperty("WorkerRunId", $"{nameof(MonitorWorker)}_{DateTime.Now:yyyyMMddHHmmssfff}"); + using var __ = LogContext.PushProperty("WorkerRunId", $"{nameof(MonitorWorker)}_{DateTime.UtcNow:yyyyMMddHHmmssfff}"); #region DI using (var scope = serviceProvider.CreateScope()) @@ -82,10 +82,8 @@ async Task UpdateScheduledVideosStatus() logger.LogTrace("No Scheduled videos for {platform}", platformService.PlatformName); return; } - else - { - logger.LogDebug("Get {videoCount} Scheduled/Pending videos for {platform}", videos.Count, platformService.PlatformName); - } + + logger.LogDebug("Get {videoCount} Scheduled/Pending videos for {platform}", videos.Count, platformService.PlatformName); foreach (var video in videos) { diff --git a/Workers/RecordWorker.cs b/Workers/RecordWorker.cs index 2434d5e..723e411 100644 --- a/Workers/RecordWorker.cs +++ b/Workers/RecordWorker.cs @@ -21,7 +21,7 @@ protected override async Task ExecuteAsync(CancellationToken stoppingToken) logger.LogTrace("{Worker} starts...", nameof(RecordWorker)); while (!stoppingToken.IsCancellationRequested) { - using var ____ = LogContext.PushProperty("WorkerRunId", $"{nameof(RecordWorker)}_{DateTime.Now:yyyyMMddHHmmssfff}"); + using var ____ = LogContext.PushProperty("WorkerRunId", $"{nameof(RecordWorker)}_{DateTime.UtcNow:yyyyMMddHHmmssfff}"); #region DI using (var scope = serviceProvider.CreateScope()) { diff --git a/Workers/UpdateChannelInfoWorker.cs b/Workers/UpdateChannelInfoWorker.cs index 69300b2..c727d71 100644 --- a/Workers/UpdateChannelInfoWorker.cs +++ b/Workers/UpdateChannelInfoWorker.cs @@ -21,7 +21,7 @@ protected override async Task ExecuteAsync(CancellationToken stoppingToken) while (!stoppingToken.IsCancellationRequested) { - using var __ = LogContext.PushProperty("WorkerRunId", $"{nameof(UpdateChannelInfoWorker)}_{DateTime.Now:yyyyMMddHHmmssfff}"); + using var __ = LogContext.PushProperty("WorkerRunId", $"{nameof(UpdateChannelInfoWorker)}_{DateTime.UtcNow:yyyyMMddHHmmssfff}"); #region DI using var scope = serviceProvider.CreateScope(); diff --git a/Workers/UpdateVideoStatusWorker.cs b/Workers/UpdateVideoStatusWorker.cs index 3a8ec42..d56839b 100644 --- a/Workers/UpdateVideoStatusWorker.cs +++ b/Workers/UpdateVideoStatusWorker.cs @@ -35,7 +35,7 @@ protected override async Task ExecuteAsync(CancellationToken stoppingToken) var expireDate = DateTime.Today; while (!stoppingToken.IsCancellationRequested) { - using var __ = LogContext.PushProperty("WorkerRunId", $"{nameof(UpdateVideoStatusWorker)}_{DateTime.Now:yyyyMMddHHmmssfff}"); + using var __ = LogContext.PushProperty("WorkerRunId", $"{nameof(UpdateVideoStatusWorker)}_{DateTime.UtcNow:yyyyMMddHHmmssfff}"); #region DI using (var scope = serviceProvider.CreateScope()) @@ -71,7 +71,7 @@ .. videoRepository.Where(p => p.Status >= VideoStatus.Archived } // Expire videos once a day - if (DateTime.Now > expireDate) + if (DateTime.UtcNow > expireDate) { expireDate = expireDate.AddDays(1); await ExpireVideosAsync(videoService, stoppingToken); @@ -106,7 +106,7 @@ private async Task UpdateVideoAsync(int i, List