diff --git a/Emby.Dlna/DlnaManager.cs b/Emby.Dlna/DlnaManager.cs index d5629684c3..5612376a55 100644 --- a/Emby.Dlna/DlnaManager.cs +++ b/Emby.Dlna/DlnaManager.cs @@ -126,7 +126,6 @@ namespace Emby.Dlna var builder = new StringBuilder(); builder.AppendLine("No matching device profile found. The default will need to be used."); - builder.AppendFormat(CultureInfo.InvariantCulture, "DeviceDescription:{0}", profile.DeviceDescription ?? string.Empty).AppendLine(); builder.AppendFormat(CultureInfo.InvariantCulture, "FriendlyName:{0}", profile.FriendlyName ?? string.Empty).AppendLine(); builder.AppendFormat(CultureInfo.InvariantCulture, "Manufacturer:{0}", profile.Manufacturer ?? string.Empty).AppendLine(); builder.AppendFormat(CultureInfo.InvariantCulture, "ManufacturerUrl:{0}", profile.ManufacturerUrl ?? string.Empty).AppendLine(); @@ -141,17 +140,9 @@ namespace Emby.Dlna private bool IsMatch(DeviceIdentification deviceInfo, DeviceIdentification profileInfo) { - if (!string.IsNullOrEmpty(profileInfo.DeviceDescription)) - { - if (deviceInfo.DeviceDescription == null || !IsRegexMatch(deviceInfo.DeviceDescription, profileInfo.DeviceDescription)) - { - return false; - } - } - if (!string.IsNullOrEmpty(profileInfo.FriendlyName)) { - if (deviceInfo.FriendlyName == null || !IsRegexMatch(deviceInfo.FriendlyName, profileInfo.FriendlyName)) + if (deviceInfo.FriendlyName == null || !IsRegexOrSubstringMatch(deviceInfo.FriendlyName, profileInfo.FriendlyName)) { return false; } @@ -159,7 +150,7 @@ namespace Emby.Dlna if (!string.IsNullOrEmpty(profileInfo.Manufacturer)) { - if (deviceInfo.Manufacturer == null || !IsRegexMatch(deviceInfo.Manufacturer, profileInfo.Manufacturer)) + if (deviceInfo.Manufacturer == null || !IsRegexOrSubstringMatch(deviceInfo.Manufacturer, profileInfo.Manufacturer)) { return false; } @@ -167,7 +158,7 @@ namespace Emby.Dlna if (!string.IsNullOrEmpty(profileInfo.ManufacturerUrl)) { - if (deviceInfo.ManufacturerUrl == null || !IsRegexMatch(deviceInfo.ManufacturerUrl, profileInfo.ManufacturerUrl)) + if (deviceInfo.ManufacturerUrl == null || !IsRegexOrSubstringMatch(deviceInfo.ManufacturerUrl, profileInfo.ManufacturerUrl)) { return false; } @@ -175,7 +166,7 @@ namespace Emby.Dlna if (!string.IsNullOrEmpty(profileInfo.ModelDescription)) { - if (deviceInfo.ModelDescription == null || !IsRegexMatch(deviceInfo.ModelDescription, profileInfo.ModelDescription)) + if (deviceInfo.ModelDescription == null || !IsRegexOrSubstringMatch(deviceInfo.ModelDescription, profileInfo.ModelDescription)) { return false; } @@ -183,7 +174,7 @@ namespace Emby.Dlna if (!string.IsNullOrEmpty(profileInfo.ModelName)) { - if (deviceInfo.ModelName == null || !IsRegexMatch(deviceInfo.ModelName, profileInfo.ModelName)) + if (deviceInfo.ModelName == null || !IsRegexOrSubstringMatch(deviceInfo.ModelName, profileInfo.ModelName)) { return false; } @@ -191,7 +182,7 @@ namespace Emby.Dlna if (!string.IsNullOrEmpty(profileInfo.ModelNumber)) { - if (deviceInfo.ModelNumber == null || !IsRegexMatch(deviceInfo.ModelNumber, profileInfo.ModelNumber)) + if (deviceInfo.ModelNumber == null || !IsRegexOrSubstringMatch(deviceInfo.ModelNumber, profileInfo.ModelNumber)) { return false; } @@ -199,7 +190,7 @@ namespace Emby.Dlna if (!string.IsNullOrEmpty(profileInfo.ModelUrl)) { - if (deviceInfo.ModelUrl == null || !IsRegexMatch(deviceInfo.ModelUrl, profileInfo.ModelUrl)) + if (deviceInfo.ModelUrl == null || !IsRegexOrSubstringMatch(deviceInfo.ModelUrl, profileInfo.ModelUrl)) { return false; } @@ -207,7 +198,7 @@ namespace Emby.Dlna if (!string.IsNullOrEmpty(profileInfo.SerialNumber)) { - if (deviceInfo.SerialNumber == null || !IsRegexMatch(deviceInfo.SerialNumber, profileInfo.SerialNumber)) + if (deviceInfo.SerialNumber == null || !IsRegexOrSubstringMatch(deviceInfo.SerialNumber, profileInfo.SerialNumber)) { return false; } @@ -216,11 +207,11 @@ namespace Emby.Dlna return true; } - private bool IsRegexMatch(string input, string pattern) + private bool IsRegexOrSubstringMatch(string input, string pattern) { try { - return Regex.IsMatch(input, pattern); + return input.Contains(pattern, StringComparison.OrdinalIgnoreCase) || Regex.IsMatch(input, pattern, RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); } catch (ArgumentException ex) { @@ -511,8 +502,7 @@ namespace Emby.Dlna public string GetServerDescriptionXml(IHeaderDictionary headers, string serverUuId, string serverAddress) { - var profile = GetProfile(headers) ?? - GetDefaultProfile(); + var profile = GetDefaultProfile(); var serverId = _appHost.SystemId; diff --git a/Emby.Dlna/PlayTo/SsdpHttpClient.cs b/Emby.Dlna/PlayTo/SsdpHttpClient.cs index 8683c89971..c8c36fc972 100644 --- a/Emby.Dlna/PlayTo/SsdpHttpClient.cs +++ b/Emby.Dlna/PlayTo/SsdpHttpClient.cs @@ -101,7 +101,7 @@ namespace Emby.Dlna.PlayTo LoadOptions.PreserveWhitespace); } - private Task PostSoapDataAsync( + private async Task PostSoapDataAsync( string url, string soapAction, string postData, @@ -126,7 +126,7 @@ namespace Emby.Dlna.PlayTo options.Content = new StringContent(postData, Encoding.UTF8, MediaTypeNames.Text.Xml); - return _httpClientFactory.CreateClient(NamedClient.Default).SendAsync(options, HttpCompletionOption.ResponseHeadersRead, cancellationToken); + return await _httpClientFactory.CreateClient(NamedClient.Default).SendAsync(options, HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); } } } diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index c37e87d969..642e2fdbe9 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -238,8 +238,14 @@ namespace Emby.Server.Implementations public IServerConfigurationManager ServerConfigurationManager => (IServerConfigurationManager)ConfigurationManager; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// + /// Instance of the interface. + /// Instance of the interface. + /// Instance of the interface. + /// Instance of the interface. + /// Instance of the interface. + /// Instance of the interface. public ApplicationHost( IServerApplicationPaths applicationPaths, ILoggerFactory loggerFactory, @@ -1137,7 +1143,8 @@ namespace Emby.Server.Implementations Id = SystemId, OperatingSystem = OperatingSystem.Id.ToString(), ServerName = FriendlyName, - LocalAddress = localAddress + LocalAddress = localAddress, + StartupWizardCompleted = ConfigurationManager.CommonConfiguration.IsStartupWizardCompleted }; } @@ -1431,10 +1438,6 @@ namespace Emby.Server.Implementations } } - public virtual void EnableLoopback(string appName) - { - } - private bool _disposed = false; /// diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs index f9ae55af85..655ff58537 100644 --- a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs +++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs @@ -468,13 +468,15 @@ namespace Emby.Server.Implementations.LiveTv.Listings imageIdString = imageIdString.TrimEnd(',') + "]"; - using var message = new HttpRequestMessage(HttpMethod.Post, ApiUrl + "/metadata/programs"); - message.Content = new StringContent(imageIdString, Encoding.UTF8, MediaTypeNames.Application.Json); + using var message = new HttpRequestMessage(HttpMethod.Post, ApiUrl + "/metadata/programs") + { + Content = new StringContent(imageIdString, Encoding.UTF8, MediaTypeNames.Application.Json) + }; try { using var innerResponse2 = await Send(message, true, info, cancellationToken).ConfigureAwait(false); - await using var response = await innerResponse2.Content.ReadAsStreamAsync(); + await using var response = await innerResponse2.Content.ReadAsStreamAsync().ConfigureAwait(false); return await _jsonSerializer.DeserializeFromStreamAsync>( response).ConfigureAwait(false); } diff --git a/Emby.Server.Implementations/Localization/Core/zh-TW.json b/Emby.Server.Implementations/Localization/Core/zh-TW.json index a21cdad953..01108fe84d 100644 --- a/Emby.Server.Implementations/Localization/Core/zh-TW.json +++ b/Emby.Server.Implementations/Localization/Core/zh-TW.json @@ -1,6 +1,6 @@ { "Albums": "專輯", - "AppDeviceValues": "軟體: {0}, 裝置: {1}", + "AppDeviceValues": "軟體:{0},裝置:{1}", "Application": "應用程式", "Artists": "演出者", "AuthenticationSucceededWithUserName": "{0} 成功授權", @@ -11,7 +11,7 @@ "Collections": "合輯", "DeviceOfflineWithName": "{0} 已經斷線", "DeviceOnlineWithName": "{0} 已經連線", - "FailedLoginAttemptWithUserName": "來自 {0} 的失敗登入嘗試", + "FailedLoginAttemptWithUserName": "來自使用者 {0} 的失敗登入", "Favorites": "我的最愛", "Folders": "資料夾", "Genres": "風格", @@ -28,8 +28,8 @@ "HomeVideos": "自製影片", "ItemAddedWithName": "{0} 已新增至媒體庫", "ItemRemovedWithName": "{0} 已從媒體庫移除", - "LabelIpAddressValue": "IP 位置: {0}", - "LabelRunningTimeValue": "運行時間: {0}", + "LabelIpAddressValue": "IP 位址:{0}", + "LabelRunningTimeValue": "運行時間:{0}", "Latest": "最新", "MessageApplicationUpdated": "Jellyfin Server 已經更新", "MessageApplicationUpdatedTo": "Jellyfin Server 已經更新至 {0}", @@ -42,18 +42,18 @@ "NameInstallFailed": "{0} 安裝失敗", "NameSeasonNumber": "第 {0} 季", "NameSeasonUnknown": "未知季數", - "NewVersionIsAvailable": "新版本的Jellyfin Server 軟體已經推出可供下載。", + "NewVersionIsAvailable": "新版本的 Jellyfin Server 軟體已經可供下載。", "NotificationOptionApplicationUpdateAvailable": "有可用的應用程式更新", - "NotificationOptionApplicationUpdateInstalled": "應用程式已更新", + "NotificationOptionApplicationUpdateInstalled": "軟體更新已安裝", "NotificationOptionAudioPlayback": "音樂開始播放", "NotificationOptionAudioPlaybackStopped": "音樂停止播放", "NotificationOptionCameraImageUploaded": "相機相片已上傳", "NotificationOptionInstallationFailed": "安裝失敗", "NotificationOptionNewLibraryContent": "已新增新內容", - "NotificationOptionPluginError": "插件安裝錯誤", - "NotificationOptionPluginInstalled": "插件已安裝", - "NotificationOptionPluginUninstalled": "插件已移除", - "NotificationOptionPluginUpdateInstalled": "插件已更新", + "NotificationOptionPluginError": "外掛安裝失敗", + "NotificationOptionPluginInstalled": "外掛已安裝", + "NotificationOptionPluginUninstalled": "外掛已移除", + "NotificationOptionPluginUpdateInstalled": "外掛已更新", "NotificationOptionServerRestartRequired": "伺服器需要重新啟動", "NotificationOptionTaskFailed": "排程任務失敗", "NotificationOptionUserLockedOut": "使用者已鎖定", @@ -61,14 +61,14 @@ "NotificationOptionVideoPlaybackStopped": "影片停止播放", "Photos": "相片", "Playlists": "播放清單", - "Plugin": "插件", + "Plugin": "外掛", "PluginInstalledWithName": "{0} 已安裝", "PluginUninstalledWithName": "{0} 已移除", "PluginUpdatedWithName": "{0} 已更新", "ProviderValue": "提供商: {0}", - "ScheduledTaskFailedWithName": "{0} 已失敗", - "ScheduledTaskStartedWithName": "{0} 已開始", - "ServerNameNeedsToBeRestarted": "{0} 需要重新啟動", + "ScheduledTaskFailedWithName": "排程任務 {0} 已失敗", + "ScheduledTaskStartedWithName": "排程任務 {0} 已開始", + "ServerNameNeedsToBeRestarted": "伺服器 {0} 需要重新啟動", "Shows": "節目", "Songs": "歌曲", "StartupEmbyServerIsLoading": "Jellyfin Server正在啟動,請稍後再試一次。", @@ -78,10 +78,10 @@ "User": "使用者", "UserCreatedWithName": "使用者 {0} 已建立", "UserDeletedWithName": "使用者 {0} 已移除", - "UserDownloadingItemWithValues": "{0} 正在下載 {1}", + "UserDownloadingItemWithValues": "使用者 {0} 正在下載 {1}", "UserLockedOutWithName": "使用者 {0} 已鎖定", - "UserOfflineFromDevice": "{0} 已從 {1} 斷線", - "UserOnlineFromDevice": "{0} 已連線,來自 {1}", + "UserOfflineFromDevice": "使用者 {0} 已從 {1} 斷線", + "UserOnlineFromDevice": "使用者 {0} 已從 {1} 連線", "UserPasswordChangedWithName": "使用者 {0} 的密碼已變更", "UserPolicyUpdatedWithName": "使用者條約已更新為 {0}", "UserStartedPlayingItemWithValues": "{0}正在使用 {2} 播放 {1}", @@ -95,23 +95,23 @@ "TaskDownloadMissingSubtitlesDescription": "在網路上透過中繼資料搜尋遺失的字幕。", "TaskDownloadMissingSubtitles": "下載遺失的字幕", "TaskRefreshChannels": "重新整理頻道", - "TaskUpdatePlugins": "更新插件", + "TaskUpdatePlugins": "更新外掛", "TaskRefreshPeople": "重新整理人員", - "TaskCleanLogsDescription": "刪除超過{0}天的紀錄檔案。", + "TaskCleanLogsDescription": "刪除超過 {0} 天的舊紀錄檔。", "TaskCleanLogs": "清空紀錄資料夾", - "TaskRefreshLibraryDescription": "掃描媒體庫內新的檔案並重新整理描述資料。", - "TaskRefreshLibrary": "掃描媒體庫", + "TaskRefreshLibraryDescription": "重新掃描媒體庫的新檔案並更新描述資料。", + "TaskRefreshLibrary": "重新掃描媒體庫", "TaskRefreshChapterImages": "擷取章節圖片", - "TaskCleanCacheDescription": "刪除系統長時間不需要的快取。", + "TaskCleanCacheDescription": "刪除系統已不需要的快取。", "TaskCleanCache": "清除快取資料夾", "TasksLibraryCategory": "媒體庫", - "TaskRefreshChannelsDescription": "重新整理網絡頻道資料。", + "TaskRefreshChannelsDescription": "重新整理網路頻道資料。", "TaskCleanTranscodeDescription": "刪除超過一天的轉碼檔案。", "TaskCleanTranscode": "清除轉碼資料夾", - "TaskUpdatePluginsDescription": "下載並安裝配置為自動更新的插件的更新。", + "TaskUpdatePluginsDescription": "為設置自動更新的外掛下載並安裝更新。", "TaskRefreshPeopleDescription": "更新媒體庫中演員和導演的中繼資料。", - "TaskRefreshChapterImagesDescription": "為有章節的視頻創建縮圖。", - "TasksChannelsCategory": "網絡頻道", + "TaskRefreshChapterImagesDescription": "為有章節的影片建立縮圖。", + "TasksChannelsCategory": "網路頻道", "TasksApplicationCategory": "應用程式", "TasksMaintenanceCategory": "維修" } diff --git a/Emby.Server.Implementations/Udp/UdpServer.cs b/Emby.Server.Implementations/Udp/UdpServer.cs index 73fcbcec3a..b7a59cee2d 100644 --- a/Emby.Server.Implementations/Udp/UdpServer.cs +++ b/Emby.Server.Implementations/Udp/UdpServer.cs @@ -68,12 +68,6 @@ namespace Emby.Server.Implementations.Udp { _logger.LogError(ex, "Error sending response message"); } - - var parts = messageText.Split('|'); - if (parts.Length > 1) - { - _appHost.EnableLoopback(parts[1]); - } } else { diff --git a/Emby.Server.Implementations/Updates/InstallationManager.cs b/Emby.Server.Implementations/Updates/InstallationManager.cs index f121a34938..e19158f8e9 100644 --- a/Emby.Server.Implementations/Updates/InstallationManager.cs +++ b/Emby.Server.Implementations/Updates/InstallationManager.cs @@ -183,7 +183,8 @@ namespace Emby.Server.Implementations.Updates IEnumerable availablePackages, string name = null, Guid guid = default, - Version minVersion = null) + Version minVersion = null, + Version specificVersion = null) { var package = FilterPackages(availablePackages, name, guid).FirstOrDefault(); @@ -197,7 +198,11 @@ namespace Emby.Server.Implementations.Updates var availableVersions = package.versions .Where(x => Version.Parse(x.targetAbi) <= appVer); - if (minVersion != null) + if (specificVersion != null) + { + availableVersions = availableVersions.Where(x => new Version(x.version) == specificVersion); + } + else if (minVersion != null) { availableVersions = availableVersions.Where(x => new Version(x.version) >= minVersion); } @@ -227,8 +232,8 @@ namespace Emby.Server.Implementations.Updates { foreach (var plugin in _applicationHost.Plugins) { - var compatibleversions = GetCompatibleVersions(pluginCatalog, plugin.Name, plugin.Id, plugin.Version); - var version = compatibleversions.FirstOrDefault(y => y.Version > plugin.Version); + var compatibleVersions = GetCompatibleVersions(pluginCatalog, plugin.Name, plugin.Id, minVersion: plugin.Version); + var version = compatibleVersions.FirstOrDefault(y => y.Version > plugin.Version); if (version != null && CompletedInstallations.All(x => x.Guid != version.Guid)) { yield return version; diff --git a/Jellyfin.Api/Controllers/LiveTvController.cs b/Jellyfin.Api/Controllers/LiveTvController.cs index bbae42157b..6b1fde4387 100644 --- a/Jellyfin.Api/Controllers/LiveTvController.cs +++ b/Jellyfin.Api/Controllers/LiveTvController.cs @@ -448,7 +448,7 @@ namespace Jellyfin.Api.Controllers [HttpGet("Timers/{timerId}")] [ProducesResponseType(StatusCodes.Status200OK)] [Authorize(Policy = Policies.DefaultAuthorization)] - public async Task> GetTimer(string timerId) + public async Task> GetTimer([FromRoute, Required] string timerId) { return await _liveTvManager.GetTimer(timerId, CancellationToken.None).ConfigureAwait(false); } diff --git a/Jellyfin.Api/Controllers/PackageController.cs b/Jellyfin.Api/Controllers/PackageController.cs index 7e406b418b..9509f8708b 100644 --- a/Jellyfin.Api/Controllers/PackageController.cs +++ b/Jellyfin.Api/Controllers/PackageController.cs @@ -49,9 +49,10 @@ namespace Jellyfin.Api.Controllers { var packages = await _installationManager.GetAvailablePackages().ConfigureAwait(false); var result = _installationManager.FilterPackages( - packages, - name, - string.IsNullOrEmpty(assemblyGuid) ? default : Guid.Parse(assemblyGuid)).FirstOrDefault(); + packages, + name, + string.IsNullOrEmpty(assemblyGuid) ? default : Guid.Parse(assemblyGuid)) + .FirstOrDefault(); return result; } @@ -93,7 +94,8 @@ namespace Jellyfin.Api.Controllers packages, name, string.IsNullOrEmpty(assemblyGuid) ? Guid.Empty : Guid.Parse(assemblyGuid), - string.IsNullOrEmpty(version) ? null : Version.Parse(version)).FirstOrDefault(); + specificVersion: string.IsNullOrEmpty(version) ? null : Version.Parse(version)) + .FirstOrDefault(); if (package == null) { diff --git a/Jellyfin.Api/Helpers/FileStreamResponseHelpers.cs b/Jellyfin.Api/Helpers/FileStreamResponseHelpers.cs index 884bfbe447..6b516977e8 100644 --- a/Jellyfin.Api/Helpers/FileStreamResponseHelpers.cs +++ b/Jellyfin.Api/Helpers/FileStreamResponseHelpers.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.IO; using System.Net.Http; using System.Threading; @@ -123,10 +123,9 @@ namespace Jellyfin.Api.Helpers state.Dispose(); } - var memoryStream = new MemoryStream(); - await new ProgressiveFileCopier(outputPath, job, transcodingJobHelper, CancellationToken.None).WriteToAsync(memoryStream, CancellationToken.None).ConfigureAwait(false); - memoryStream.Position = 0; - return new FileStreamResult(memoryStream, contentType); + await new ProgressiveFileCopier(outputPath, job, transcodingJobHelper, CancellationToken.None) + .WriteToAsync(httpContext.Response.Body, CancellationToken.None).ConfigureAwait(false); + return new FileStreamResult(httpContext.Response.Body, contentType); } finally { diff --git a/Jellyfin.Api/Helpers/StreamingHelpers.cs b/Jellyfin.Api/Helpers/StreamingHelpers.cs index c11799bc8c..89ab2da627 100644 --- a/Jellyfin.Api/Helpers/StreamingHelpers.cs +++ b/Jellyfin.Api/Helpers/StreamingHelpers.cs @@ -92,7 +92,6 @@ namespace Jellyfin.Api.Helpers } var enableDlnaHeaders = !string.IsNullOrWhiteSpace(streamingRequest.Params) || - streamingRequest.StreamOptions.ContainsKey("dlnaheaders") || string.Equals(httpRequest.Headers["GetContentFeatures.DLNA.ORG"], "1", StringComparison.OrdinalIgnoreCase); var state = new StreamState(mediaSourceManager, transcodingJobType, transcodingJobHelper) diff --git a/Jellyfin.Server.Implementations/JellyfinDb.cs b/Jellyfin.Server.Implementations/JellyfinDb.cs index 08e4db3880..45e71f16eb 100644 --- a/Jellyfin.Server.Implementations/JellyfinDb.cs +++ b/Jellyfin.Server.Implementations/JellyfinDb.cs @@ -1,6 +1,5 @@ #pragma warning disable CS1591 -using System; using System.Linq; using Jellyfin.Data.Entities; using Jellyfin.Data.Interfaces; @@ -9,7 +8,7 @@ using Microsoft.EntityFrameworkCore; namespace Jellyfin.Server.Implementations { /// - public partial class JellyfinDb : DbContext + public class JellyfinDb : DbContext { /// /// Initializes a new instance of the class. @@ -138,47 +137,20 @@ namespace Jellyfin.Server.Implementations return base.SaveChanges(); } - /// - public override void Dispose() - { - foreach (var entry in ChangeTracker.Entries()) - { - entry.State = EntityState.Detached; - } - - GC.SuppressFinalize(this); - base.Dispose(); - } - - /// - protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) - { - CustomInit(optionsBuilder); - } - /// protected override void OnModelCreating(ModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); - OnModelCreatingImpl(modelBuilder); modelBuilder.HasDefaultSchema("jellyfin"); - /*modelBuilder.Entity().HasIndex(t => t.Kind); - - modelBuilder.Entity().HasIndex(t => t.Name) - .IsUnique(); + modelBuilder.Entity() + .HasIndex(entity => entity.UserId) + .IsUnique(false); - modelBuilder.Entity().HasIndex(t => t.UrlId) - .IsUnique();*/ - - OnModelCreatedImpl(modelBuilder); + modelBuilder.Entity() + .HasIndex(entity => new { entity.UserId, entity.Client }) + .IsUnique(); } - - partial void CustomInit(DbContextOptionsBuilder optionsBuilder); - - partial void OnModelCreatingImpl(ModelBuilder modelBuilder); - - partial void OnModelCreatedImpl(ModelBuilder modelBuilder); } } diff --git a/Jellyfin.Server.Implementations/Migrations/20200905220533_FixDisplayPreferencesIndex.Designer.cs b/Jellyfin.Server.Implementations/Migrations/20200905220533_FixDisplayPreferencesIndex.Designer.cs new file mode 100644 index 0000000000..2234f9d5fd --- /dev/null +++ b/Jellyfin.Server.Implementations/Migrations/20200905220533_FixDisplayPreferencesIndex.Designer.cs @@ -0,0 +1,461 @@ +#pragma warning disable CS1591 + +// +using System; +using Jellyfin.Server.Implementations; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +namespace Jellyfin.Server.Implementations.Migrations +{ + [DbContext(typeof(JellyfinDb))] + [Migration("20200905220533_FixDisplayPreferencesIndex")] + partial class FixDisplayPreferencesIndex + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasDefaultSchema("jellyfin") + .HasAnnotation("ProductVersion", "3.1.7"); + + modelBuilder.Entity("Jellyfin.Data.Entities.AccessSchedule", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("DayOfWeek") + .HasColumnType("INTEGER"); + + b.Property("EndHour") + .HasColumnType("REAL"); + + b.Property("StartHour") + .HasColumnType("REAL"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AccessSchedules"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.ActivityLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("DateCreated") + .HasColumnType("TEXT"); + + b.Property("ItemId") + .HasColumnType("TEXT") + .HasMaxLength(256); + + b.Property("LogSeverity") + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT") + .HasMaxLength(512); + + b.Property("Overview") + .HasColumnType("TEXT") + .HasMaxLength(512); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("INTEGER"); + + b.Property("ShortOverview") + .HasColumnType("TEXT") + .HasMaxLength(512); + + b.Property("Type") + .IsRequired() + .HasColumnType("TEXT") + .HasMaxLength(256); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("ActivityLogs"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.DisplayPreferences", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ChromecastVersion") + .HasColumnType("INTEGER"); + + b.Property("Client") + .IsRequired() + .HasColumnType("TEXT") + .HasMaxLength(32); + + b.Property("DashboardTheme") + .HasColumnType("TEXT") + .HasMaxLength(32); + + b.Property("EnableNextVideoInfoOverlay") + .HasColumnType("INTEGER"); + + b.Property("IndexBy") + .HasColumnType("INTEGER"); + + b.Property("ScrollDirection") + .HasColumnType("INTEGER"); + + b.Property("ShowBackdrop") + .HasColumnType("INTEGER"); + + b.Property("ShowSidebar") + .HasColumnType("INTEGER"); + + b.Property("SkipBackwardLength") + .HasColumnType("INTEGER"); + + b.Property("SkipForwardLength") + .HasColumnType("INTEGER"); + + b.Property("TvHome") + .HasColumnType("TEXT") + .HasMaxLength(32); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.HasIndex("UserId", "Client") + .IsUnique(); + + b.ToTable("DisplayPreferences"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.HomeSection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("DisplayPreferencesId") + .HasColumnType("INTEGER"); + + b.Property("Order") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("DisplayPreferencesId"); + + b.ToTable("HomeSection"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.ImageInfo", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("LastModified") + .HasColumnType("TEXT"); + + b.Property("Path") + .IsRequired() + .HasColumnType("TEXT") + .HasMaxLength(512); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("ImageInfos"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.ItemDisplayPreferences", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Client") + .IsRequired() + .HasColumnType("TEXT") + .HasMaxLength(32); + + b.Property("IndexBy") + .HasColumnType("INTEGER"); + + b.Property("ItemId") + .HasColumnType("TEXT"); + + b.Property("RememberIndexing") + .HasColumnType("INTEGER"); + + b.Property("RememberSorting") + .HasColumnType("INTEGER"); + + b.Property("SortBy") + .IsRequired() + .HasColumnType("TEXT") + .HasMaxLength(64); + + b.Property("SortOrder") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("ViewType") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("ItemDisplayPreferences"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.Permission", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Kind") + .HasColumnType("INTEGER"); + + b.Property("Permission_Permissions_Guid") + .HasColumnType("TEXT"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("INTEGER"); + + b.Property("Value") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("Permission_Permissions_Guid"); + + b.ToTable("Permissions"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.Preference", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Kind") + .HasColumnType("INTEGER"); + + b.Property("Preference_Preferences_Guid") + .HasColumnType("TEXT"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("INTEGER"); + + b.Property("Value") + .IsRequired() + .HasColumnType("TEXT") + .HasMaxLength(65535); + + b.HasKey("Id"); + + b.HasIndex("Preference_Preferences_Guid"); + + b.ToTable("Preferences"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AudioLanguagePreference") + .HasColumnType("TEXT") + .HasMaxLength(255); + + b.Property("AuthenticationProviderId") + .IsRequired() + .HasColumnType("TEXT") + .HasMaxLength(255); + + b.Property("DisplayCollectionsView") + .HasColumnType("INTEGER"); + + b.Property("DisplayMissingEpisodes") + .HasColumnType("INTEGER"); + + b.Property("EasyPassword") + .HasColumnType("TEXT") + .HasMaxLength(65535); + + b.Property("EnableAutoLogin") + .HasColumnType("INTEGER"); + + b.Property("EnableLocalPassword") + .HasColumnType("INTEGER"); + + b.Property("EnableNextEpisodeAutoPlay") + .HasColumnType("INTEGER"); + + b.Property("EnableUserPreferenceAccess") + .HasColumnType("INTEGER"); + + b.Property("HidePlayedInLatest") + .HasColumnType("INTEGER"); + + b.Property("InternalId") + .HasColumnType("INTEGER"); + + b.Property("InvalidLoginAttemptCount") + .HasColumnType("INTEGER"); + + b.Property("LastActivityDate") + .HasColumnType("TEXT"); + + b.Property("LastLoginDate") + .HasColumnType("TEXT"); + + b.Property("LoginAttemptsBeforeLockout") + .HasColumnType("INTEGER"); + + b.Property("MaxParentalAgeRating") + .HasColumnType("INTEGER"); + + b.Property("MustUpdatePassword") + .HasColumnType("INTEGER"); + + b.Property("Password") + .HasColumnType("TEXT") + .HasMaxLength(65535); + + b.Property("PasswordResetProviderId") + .IsRequired() + .HasColumnType("TEXT") + .HasMaxLength(255); + + b.Property("PlayDefaultAudioTrack") + .HasColumnType("INTEGER"); + + b.Property("RememberAudioSelections") + .HasColumnType("INTEGER"); + + b.Property("RememberSubtitleSelections") + .HasColumnType("INTEGER"); + + b.Property("RemoteClientBitrateLimit") + .HasColumnType("INTEGER"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("INTEGER"); + + b.Property("SubtitleLanguagePreference") + .HasColumnType("TEXT") + .HasMaxLength(255); + + b.Property("SubtitleMode") + .HasColumnType("INTEGER"); + + b.Property("SyncPlayAccess") + .HasColumnType("INTEGER"); + + b.Property("Username") + .IsRequired() + .HasColumnType("TEXT") + .HasMaxLength(255); + + b.HasKey("Id"); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.AccessSchedule", b => + { + b.HasOne("Jellyfin.Data.Entities.User", null) + .WithMany("AccessSchedules") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.DisplayPreferences", b => + { + b.HasOne("Jellyfin.Data.Entities.User", null) + .WithOne("DisplayPreferences") + .HasForeignKey("Jellyfin.Data.Entities.DisplayPreferences", "UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.HomeSection", b => + { + b.HasOne("Jellyfin.Data.Entities.DisplayPreferences", null) + .WithMany("HomeSections") + .HasForeignKey("DisplayPreferencesId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.ImageInfo", b => + { + b.HasOne("Jellyfin.Data.Entities.User", null) + .WithOne("ProfileImage") + .HasForeignKey("Jellyfin.Data.Entities.ImageInfo", "UserId"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.ItemDisplayPreferences", b => + { + b.HasOne("Jellyfin.Data.Entities.User", null) + .WithMany("ItemDisplayPreferences") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.Permission", b => + { + b.HasOne("Jellyfin.Data.Entities.User", null) + .WithMany("Permissions") + .HasForeignKey("Permission_Permissions_Guid"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.Preference", b => + { + b.HasOne("Jellyfin.Data.Entities.User", null) + .WithMany("Preferences") + .HasForeignKey("Preference_Preferences_Guid"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Jellyfin.Server.Implementations/Migrations/20200905220533_FixDisplayPreferencesIndex.cs b/Jellyfin.Server.Implementations/Migrations/20200905220533_FixDisplayPreferencesIndex.cs new file mode 100644 index 0000000000..33c5bb4ca1 --- /dev/null +++ b/Jellyfin.Server.Implementations/Migrations/20200905220533_FixDisplayPreferencesIndex.cs @@ -0,0 +1,51 @@ +#pragma warning disable CS1591 +#pragma warning disable SA1601 + +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Jellyfin.Server.Implementations.Migrations +{ + public partial class FixDisplayPreferencesIndex : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropIndex( + name: "IX_DisplayPreferences_UserId", + schema: "jellyfin", + table: "DisplayPreferences"); + + migrationBuilder.CreateIndex( + name: "IX_DisplayPreferences_UserId", + schema: "jellyfin", + table: "DisplayPreferences", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_DisplayPreferences_UserId_Client", + schema: "jellyfin", + table: "DisplayPreferences", + columns: new[] { "UserId", "Client" }, + unique: true); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropIndex( + name: "IX_DisplayPreferences_UserId", + schema: "jellyfin", + table: "DisplayPreferences"); + + migrationBuilder.DropIndex( + name: "IX_DisplayPreferences_UserId_Client", + schema: "jellyfin", + table: "DisplayPreferences"); + + migrationBuilder.CreateIndex( + name: "IX_DisplayPreferences_UserId", + schema: "jellyfin", + table: "DisplayPreferences", + column: "UserId", + unique: true); + } + } +} diff --git a/Jellyfin.Server.Implementations/Migrations/JellyfinDbModelSnapshot.cs b/Jellyfin.Server.Implementations/Migrations/JellyfinDbModelSnapshot.cs index a6e6a23249..ccfcf96b1f 100644 --- a/Jellyfin.Server.Implementations/Migrations/JellyfinDbModelSnapshot.cs +++ b/Jellyfin.Server.Implementations/Migrations/JellyfinDbModelSnapshot.cs @@ -15,7 +15,7 @@ namespace Jellyfin.Server.Implementations.Migrations #pragma warning disable 612, 618 modelBuilder .HasDefaultSchema("jellyfin") - .HasAnnotation("ProductVersion", "3.1.6"); + .HasAnnotation("ProductVersion", "3.1.7"); modelBuilder.Entity("Jellyfin.Data.Entities.AccessSchedule", b => { @@ -136,7 +136,9 @@ namespace Jellyfin.Server.Implementations.Migrations b.HasKey("Id"); - b.HasIndex("UserId") + b.HasIndex("UserId"); + + b.HasIndex("UserId", "Client") .IsUnique(); b.ToTable("DisplayPreferences"); diff --git a/Jellyfin.Server/Configuration/CorsPolicyProvider.cs b/Jellyfin.Server/Configuration/CorsPolicyProvider.cs new file mode 100644 index 0000000000..0d04b6bb13 --- /dev/null +++ b/Jellyfin.Server/Configuration/CorsPolicyProvider.cs @@ -0,0 +1,49 @@ +using System; +using System.Threading.Tasks; +using MediaBrowser.Controller.Configuration; +using Microsoft.AspNetCore.Cors.Infrastructure; +using Microsoft.AspNetCore.Http; + +namespace Jellyfin.Server.Configuration +{ + /// + /// Cors policy provider. + /// + public class CorsPolicyProvider : ICorsPolicyProvider + { + private readonly IServerConfigurationManager _serverConfigurationManager; + + /// + /// Initializes a new instance of the class. + /// + /// Instance of the interface. + public CorsPolicyProvider(IServerConfigurationManager serverConfigurationManager) + { + _serverConfigurationManager = serverConfigurationManager; + } + + /// + public Task GetPolicyAsync(HttpContext context, string policyName) + { + var corsHosts = _serverConfigurationManager.Configuration.CorsHosts; + var builder = new CorsPolicyBuilder() + .AllowAnyMethod() + .AllowAnyHeader(); + + // No hosts configured or only default configured. + if (corsHosts.Length == 0 + || (corsHosts.Length == 1 + && string.Equals(corsHosts[0], CorsConstants.AnyOrigin, StringComparison.Ordinal))) + { + builder.AllowAnyOrigin(); + } + else + { + builder.WithOrigins(corsHosts) + .AllowCredentials(); + } + + return Task.FromResult(builder.Build()); + } + } +} diff --git a/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs b/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs index 31454265a0..71148701b8 100644 --- a/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs +++ b/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs @@ -15,14 +15,15 @@ using Jellyfin.Api.Auth.LocalAccessPolicy; using Jellyfin.Api.Auth.RequiresElevationPolicy; using Jellyfin.Api.Constants; using Jellyfin.Api.Controllers; +using Jellyfin.Server.Configuration; using Jellyfin.Server.Filters; using Jellyfin.Server.Formatters; -using Jellyfin.Server.Models; using MediaBrowser.Common.Json; using MediaBrowser.Model.Entities; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Cors.Infrastructure; using Microsoft.AspNetCore.HttpOverrides; using Microsoft.Extensions.DependencyInjection; using Microsoft.OpenApi.Models; @@ -139,10 +140,8 @@ namespace Jellyfin.Server.Extensions public static IMvcBuilder AddJellyfinApi(this IServiceCollection serviceCollection, IEnumerable pluginAssemblies) { IMvcBuilder mvcBuilder = serviceCollection - .AddCors(options => - { - options.AddPolicy(ServerCorsPolicy.DefaultPolicyName, ServerCorsPolicy.DefaultPolicy); - }) + .AddCors() + .AddTransient() .Configure(options => { options.ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto; diff --git a/Jellyfin.Server/Jellyfin.Server.csproj b/Jellyfin.Server/Jellyfin.Server.csproj index c3bec1c71c..0ac309a0b0 100644 --- a/Jellyfin.Server/Jellyfin.Server.csproj +++ b/Jellyfin.Server/Jellyfin.Server.csproj @@ -54,7 +54,7 @@ - + diff --git a/Jellyfin.Server/Middleware/BaseUrlRedirectionMiddleware.cs b/Jellyfin.Server/Middleware/BaseUrlRedirectionMiddleware.cs index ae3a3a1c54..9316737bdf 100644 --- a/Jellyfin.Server/Middleware/BaseUrlRedirectionMiddleware.cs +++ b/Jellyfin.Server/Middleware/BaseUrlRedirectionMiddleware.cs @@ -44,7 +44,11 @@ namespace Jellyfin.Server.Middleware var localPath = httpContext.Request.Path.ToString(); var baseUrlPrefix = serverConfigurationManager.Configuration.BaseUrl; - if (!localPath.StartsWith(baseUrlPrefix, StringComparison.OrdinalIgnoreCase)) + if (string.Equals(localPath, baseUrlPrefix + "/", StringComparison.OrdinalIgnoreCase) + || string.Equals(localPath, baseUrlPrefix, StringComparison.OrdinalIgnoreCase) + || string.Equals(localPath, "/", StringComparison.OrdinalIgnoreCase) + || string.IsNullOrEmpty(localPath) + || !localPath.StartsWith(baseUrlPrefix, StringComparison.OrdinalIgnoreCase)) { // Always redirect back to the default path if the base prefix is invalid or missing _logger.LogDebug("Normalizing an URL at {LocalPath}", localPath); diff --git a/Jellyfin.Server/Middleware/ExceptionMiddleware.cs b/Jellyfin.Server/Middleware/ExceptionMiddleware.cs index 63effafc19..fb1ee3b2bb 100644 --- a/Jellyfin.Server/Middleware/ExceptionMiddleware.cs +++ b/Jellyfin.Server/Middleware/ExceptionMiddleware.cs @@ -125,6 +125,7 @@ namespace Jellyfin.Server.Middleware switch (ex) { case ArgumentException _: return StatusCodes.Status400BadRequest; + case AuthenticationException _: case SecurityException _: return StatusCodes.Status401Unauthorized; case DirectoryNotFoundException _: case FileNotFoundException _: diff --git a/Jellyfin.Server/Models/ServerCorsPolicy.cs b/Jellyfin.Server/Models/ServerCorsPolicy.cs deleted file mode 100644 index ae010c042e..0000000000 --- a/Jellyfin.Server/Models/ServerCorsPolicy.cs +++ /dev/null @@ -1,30 +0,0 @@ -using Microsoft.AspNetCore.Cors.Infrastructure; - -namespace Jellyfin.Server.Models -{ - /// - /// Server Cors Policy. - /// - public static class ServerCorsPolicy - { - /// - /// Default policy name. - /// - public const string DefaultPolicyName = "DefaultCorsPolicy"; - - /// - /// Default Policy. Allow Everything. - /// - public static readonly CorsPolicy DefaultPolicy = new CorsPolicy - { - // Allow any origin - Origins = { "*" }, - - // Allow any method - Methods = { "*" }, - - // Allow any header - Headers = { "*" } - }; - } -} \ No newline at end of file diff --git a/Jellyfin.Server/Startup.cs b/Jellyfin.Server/Startup.cs index 597323b864..9e969c0c16 100644 --- a/Jellyfin.Server/Startup.cs +++ b/Jellyfin.Server/Startup.cs @@ -5,7 +5,6 @@ using Jellyfin.Api.TypeConverters; using Jellyfin.Server.Extensions; using Jellyfin.Server.Implementations; using Jellyfin.Server.Middleware; -using Jellyfin.Server.Models; using MediaBrowser.Common.Net; using MediaBrowser.Controller; using MediaBrowser.Controller.Configuration; @@ -94,11 +93,7 @@ namespace Jellyfin.Server IWebHostEnvironment env, IConfiguration appConfig) { - // Only add base url redirection if a base url is set. - if (!string.IsNullOrEmpty(_serverConfigurationManager.Configuration.BaseUrl)) - { - app.UseBaseUrlRedirection(); - } + app.UseBaseUrlRedirection(); // Wrap rest of configuration so everything only listens on BaseUrl. app.Map(_serverConfigurationManager.Configuration.BaseUrl, mainApp => @@ -116,7 +111,7 @@ namespace Jellyfin.Server mainApp.UseResponseCompression(); - mainApp.UseCors(ServerCorsPolicy.DefaultPolicyName); + mainApp.UseCors(); if (_serverConfigurationManager.Configuration.RequireHttps && _serverApplicationHost.ListenWithHttps) diff --git a/MediaBrowser.Common/Updates/IInstallationManager.cs b/MediaBrowser.Common/Updates/IInstallationManager.cs index 4b4030bc27..169aca2ca0 100644 --- a/MediaBrowser.Common/Updates/IInstallationManager.cs +++ b/MediaBrowser.Common/Updates/IInstallationManager.cs @@ -73,12 +73,14 @@ namespace MediaBrowser.Common.Updates /// The name. /// The guid of the plugin. /// The minimum required version of the plugin. + /// The specific version of the plugin to install. /// All compatible versions ordered from newest to oldest. IEnumerable GetCompatibleVersions( IEnumerable availablePackages, string name = null, Guid guid = default, - Version minVersion = null); + Version minVersion = null, + Version specificVersion = null); /// /// Returns the available plugin updates. diff --git a/MediaBrowser.Controller/IServerApplicationHost.cs b/MediaBrowser.Controller/IServerApplicationHost.cs index 9f4c00e1c8..cfad17fb72 100644 --- a/MediaBrowser.Controller/IServerApplicationHost.cs +++ b/MediaBrowser.Controller/IServerApplicationHost.cs @@ -114,8 +114,6 @@ namespace MediaBrowser.Controller /// is false. void LaunchUrl(string url); - void EnableLoopback(string appName); - IEnumerable GetWakeOnLanInfo(); string ExpandVirtualPath(string path); diff --git a/MediaBrowser.Model/Configuration/ServerConfiguration.cs b/MediaBrowser.Model/Configuration/ServerConfiguration.cs index 97748bd0ca..68dc1cc83d 100644 --- a/MediaBrowser.Model/Configuration/ServerConfiguration.cs +++ b/MediaBrowser.Model/Configuration/ServerConfiguration.cs @@ -263,6 +263,11 @@ namespace MediaBrowser.Model.Configuration /// public long SlowResponseThresholdMs { get; set; } + /// + /// Gets or sets the cors hosts. + /// + public string[] CorsHosts { get; set; } + /// /// Initializes a new instance of the class. /// @@ -372,6 +377,7 @@ namespace MediaBrowser.Model.Configuration EnableSlowResponseWarning = true; SlowResponseThresholdMs = 500; + CorsHosts = new[] { "*" }; } } diff --git a/MediaBrowser.Model/Dlna/DeviceIdentification.cs b/MediaBrowser.Model/Dlna/DeviceIdentification.cs index 85cc9e3c14..43407383a8 100644 --- a/MediaBrowser.Model/Dlna/DeviceIdentification.cs +++ b/MediaBrowser.Model/Dlna/DeviceIdentification.cs @@ -37,12 +37,6 @@ namespace MediaBrowser.Model.Dlna /// The model description. public string ModelDescription { get; set; } - /// - /// Gets or sets the device description. - /// - /// The device description. - public string DeviceDescription { get; set; } - /// /// Gets or sets the model URL. /// diff --git a/MediaBrowser.Model/System/PublicSystemInfo.cs b/MediaBrowser.Model/System/PublicSystemInfo.cs index b6196a43fc..d2f7556a51 100644 --- a/MediaBrowser.Model/System/PublicSystemInfo.cs +++ b/MediaBrowser.Model/System/PublicSystemInfo.cs @@ -24,7 +24,7 @@ namespace MediaBrowser.Model.System public string Version { get; set; } /// - /// The product name. This is the AssemblyProduct name. + /// Gets or sets the product name. This is the AssemblyProduct name. /// public string ProductName { get; set; } @@ -39,5 +39,11 @@ namespace MediaBrowser.Model.System /// /// The id. public string Id { get; set; } + + /// + /// Gets or sets a value indicating whether the startup wizard is completed. + /// + /// The startup completion status. + public bool StartupWizardCompleted { get; set; } } } diff --git a/MediaBrowser.Providers/Folders/CollectionFolderMetadataService.cs b/MediaBrowser.Providers/Folders/CollectionFolderMetadataService.cs index 46f368f72b..e0f3131fdb 100644 --- a/MediaBrowser.Providers/Folders/CollectionFolderMetadataService.cs +++ b/MediaBrowser.Providers/Folders/CollectionFolderMetadataService.cs @@ -1,6 +1,5 @@ #pragma warning disable CS1591 - using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; diff --git a/MediaBrowser.Providers/LiveTv/ProgramMetadataService.cs b/MediaBrowser.Providers/LiveTv/LiveTvMetadataService.cs similarity index 100% rename from MediaBrowser.Providers/LiveTv/ProgramMetadataService.cs rename to MediaBrowser.Providers/LiveTv/LiveTvMetadataService.cs diff --git a/MediaBrowser.Providers/Manager/ImageSaver.cs b/MediaBrowser.Providers/Manager/ImageSaver.cs index 413d297cb5..19a42d506c 100644 --- a/MediaBrowser.Providers/Manager/ImageSaver.cs +++ b/MediaBrowser.Providers/Manager/ImageSaver.cs @@ -7,7 +7,6 @@ using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; -using Jellyfin.Data.Entities; using MediaBrowser.Common.Configuration; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; @@ -59,6 +58,16 @@ namespace MediaBrowser.Providers.Manager _logger = logger; } + private bool EnableExtraThumbsDuplication + { + get + { + var config = _config.GetConfiguration("xbmcmetadata"); + + return config.EnableExtraThumbsDuplication; + } + } + /// /// Saves the image. /// @@ -69,7 +78,7 @@ namespace MediaBrowser.Providers.Manager /// Index of the image. /// The cancellation token. /// Task. - /// mimeType + /// mimeType. public Task SaveImage(BaseItem item, Stream source, string mimeType, ImageType type, int? imageIndex, CancellationToken cancellationToken) { return SaveImage(item, source, mimeType, type, imageIndex, null, cancellationToken); @@ -312,7 +321,7 @@ namespace MediaBrowser.Providers.Manager /// /// imageIndex /// or - /// imageIndex + /// imageIndex. /// private ItemImageInfo GetCurrentImage(BaseItem item, ImageType type, int imageIndex) { @@ -328,7 +337,8 @@ namespace MediaBrowser.Providers.Manager /// The path. /// imageIndex /// or - /// imageIndex + /// imageIndex. + /// private void SetImagePath(BaseItem item, ImageType type, int? imageIndex, string path) { item.SetImagePath(type, imageIndex ?? 0, _fileSystem.GetFileInfo(path)); @@ -346,7 +356,7 @@ namespace MediaBrowser.Providers.Manager /// /// imageIndex /// or - /// imageIndex + /// imageIndex. /// private string GetStandardSavePath(BaseItem item, ImageType type, int? imageIndex, string mimeType, bool saveLocally) { @@ -500,7 +510,7 @@ namespace MediaBrowser.Providers.Manager /// Index of the image. /// Type of the MIME. /// IEnumerable{System.String}. - /// imageIndex + /// imageIndex. private string[] GetCompatibleSavePaths(BaseItem item, ImageType type, int? imageIndex, string mimeType) { var season = item as Season; @@ -604,16 +614,6 @@ namespace MediaBrowser.Providers.Manager return new[] { GetStandardSavePath(item, type, imageIndex, mimeType, true) }; } - private bool EnableExtraThumbsDuplication - { - get - { - var config = _config.GetConfiguration("xbmcmetadata"); - - return config.EnableExtraThumbsDuplication; - } - } - /// /// Gets the save path for item in mixed folder. /// diff --git a/MediaBrowser.Providers/Manager/ItemImageProvider.cs b/MediaBrowser.Providers/Manager/ItemImageProvider.cs index 9227b6d937..d0bdbd7c95 100644 --- a/MediaBrowser.Providers/Manager/ItemImageProvider.cs +++ b/MediaBrowser.Providers/Manager/ItemImageProvider.cs @@ -28,6 +28,22 @@ namespace MediaBrowser.Providers.Manager private readonly IProviderManager _providerManager; private readonly IFileSystem _fileSystem; + /// + /// Image types that are only one per item. + /// + private readonly ImageType[] _singularImages = + { + ImageType.Primary, + ImageType.Art, + ImageType.Banner, + ImageType.Box, + ImageType.BoxRear, + ImageType.Disc, + ImageType.Logo, + ImageType.Menu, + ImageType.Thumb + }; + public ItemImageProvider(ILogger logger, IProviderManager providerManager, IFileSystem fileSystem) { _logger = logger; @@ -175,22 +191,6 @@ namespace MediaBrowser.Providers.Manager } } - /// - /// Image types that are only one per item. - /// - private readonly ImageType[] _singularImages = - { - ImageType.Primary, - ImageType.Art, - ImageType.Banner, - ImageType.Box, - ImageType.BoxRear, - ImageType.Disc, - ImageType.Logo, - ImageType.Menu, - ImageType.Thumb - }; - private bool HasImage(BaseItem item, ImageType type) { return item.HasImage(type); @@ -378,7 +378,6 @@ namespace MediaBrowser.Providers.Manager } else { - var newDateModified = _fileSystem.GetLastWriteTimeUtc(image.FileInfo); // If date changed then we need to reset saved image dimensions @@ -441,7 +440,9 @@ namespace MediaBrowser.Providers.Manager return changed; } - private async Task DownloadImage(BaseItem item, LibraryOptions libraryOptions, + private async Task DownloadImage( + BaseItem item, + LibraryOptions libraryOptions, IRemoteImageProvider provider, RefreshResult result, IEnumerable images, @@ -522,11 +523,6 @@ namespace MediaBrowser.Providers.Manager return false; } - // if (!item.IsSaveLocalMetadataEnabled()) - //{ - // return true; - //} - return true; } @@ -539,13 +535,15 @@ namespace MediaBrowser.Providers.Manager private void SaveImageStub(BaseItem item, ImageType imageType, IEnumerable urls, int newIndex) { - var path = string.Join("|", urls.Take(1).ToArray()); + var path = string.Join('|', urls.Take(1)); - item.SetImage(new ItemImageInfo - { - Path = path, - Type = imageType - }, newIndex); + item.SetImage( + new ItemImageInfo + { + Path = path, + Type = imageType + }, + newIndex); } private async Task DownloadBackdrops(BaseItem item, LibraryOptions libraryOptions, ImageType imageType, int limit, IRemoteImageProvider provider, RefreshResult result, IEnumerable images, int minWidth, CancellationToken cancellationToken) diff --git a/MediaBrowser.Providers/Manager/MetadataService.cs b/MediaBrowser.Providers/Manager/MetadataService.cs index d0de584276..42785b0574 100644 --- a/MediaBrowser.Providers/Manager/MetadataService.cs +++ b/MediaBrowser.Providers/Manager/MetadataService.cs @@ -21,12 +21,6 @@ namespace MediaBrowser.Providers.Manager where TItemType : BaseItem, IHasLookupInfo, new() where TIdType : ItemLookupInfo, new() { - protected readonly IServerConfigurationManager ServerConfigurationManager; - protected readonly ILogger> Logger; - protected readonly IProviderManager ProviderManager; - protected readonly IFileSystem FileSystem; - protected readonly ILibraryManager LibraryManager; - protected MetadataService(IServerConfigurationManager serverConfigurationManager, ILogger> logger, IProviderManager providerManager, IFileSystem fileSystem, ILibraryManager libraryManager) { ServerConfigurationManager = serverConfigurationManager; @@ -36,6 +30,26 @@ namespace MediaBrowser.Providers.Manager LibraryManager = libraryManager; } + protected IServerConfigurationManager ServerConfigurationManager { get; } + + protected ILogger> Logger { get; } + + protected IProviderManager ProviderManager { get; } + + protected IFileSystem FileSystem { get; } + + protected ILibraryManager LibraryManager { get; } + + protected virtual bool EnableUpdatingPremiereDateFromChildren => false; + + protected virtual bool EnableUpdatingGenresFromChildren => false; + + protected virtual bool EnableUpdatingStudiosFromChildren => false; + + protected virtual bool EnableUpdatingOfficialRatingFromChildren => false; + + public virtual int Order => 0; + private FileSystemMetadata TryGetFile(string path, IDirectoryService directoryService) { try @@ -442,14 +456,6 @@ namespace MediaBrowser.Providers.Manager return updateType; } - protected virtual bool EnableUpdatingPremiereDateFromChildren => false; - - protected virtual bool EnableUpdatingGenresFromChildren => false; - - protected virtual bool EnableUpdatingStudiosFromChildren => false; - - protected virtual bool EnableUpdatingOfficialRatingFromChildren => false; - private ItemUpdateType UpdatePremiereDate(TItemType item, IList children) { var updateType = ItemUpdateType.None; @@ -658,7 +664,8 @@ namespace MediaBrowser.Providers.Manager return type == typeof(TItemType); } - protected virtual async Task RefreshWithProviders(MetadataResult metadata, + protected virtual async Task RefreshWithProviders( + MetadataResult metadata, TIdType id, MetadataRefreshOptions options, List providers, @@ -773,7 +780,7 @@ namespace MediaBrowser.Providers.Manager else { // TODO: If the new metadata from above has some blank data, this can cause old data to get filled into those empty fields - MergeData(metadata, temp, new MetadataField[] { }, false, false); + MergeData(metadata, temp, Array.Empty(), false, false); MergeData(temp, metadata, item.LockedFields, true, false); } } @@ -900,24 +907,23 @@ namespace MediaBrowser.Providers.Manager } } - protected abstract void MergeData(MetadataResult source, + protected abstract void MergeData( + MetadataResult source, MetadataResult target, MetadataField[] lockedFields, bool replaceData, bool mergeMetadataSettings); - public virtual int Order => 0; - private bool HasChanged(BaseItem item, IHasItemChangeMonitor changeMonitor, IDirectoryService directoryService) { try { var hasChanged = changeMonitor.HasChanged(item, directoryService); - // if (hasChanged) - //{ - // logger.LogDebug("{0} reports change to {1}", changeMonitor.GetType().Name, item.Path ?? item.Name); - //} + if (hasChanged) + { + Logger.LogDebug("{0} reports change to {1}", changeMonitor.GetType().Name, item.Path ?? item.Name); + } return hasChanged; } @@ -928,13 +934,4 @@ namespace MediaBrowser.Providers.Manager } } } - - public class RefreshResult - { - public ItemUpdateType UpdateType { get; set; } - - public string ErrorMessage { get; set; } - - public int Failures { get; set; } - } } diff --git a/MediaBrowser.Providers/Manager/ProviderManager.cs b/MediaBrowser.Providers/Manager/ProviderManager.cs index 171b824ca6..b6fb4267f4 100644 --- a/MediaBrowser.Providers/Manager/ProviderManager.cs +++ b/MediaBrowser.Providers/Manager/ProviderManager.cs @@ -9,7 +9,6 @@ using System.Net.Http; using System.Net.Mime; using System.Threading; using System.Threading.Tasks; -using Jellyfin.Data.Entities; using Jellyfin.Data.Events; using MediaBrowser.Common.Net; using MediaBrowser.Common.Progress; @@ -905,8 +904,7 @@ namespace MediaBrowser.Providers.Manager return provider.GetImageResponse(url, cancellationToken); } - /// - public IEnumerable GetExternalIds(IHasProviderIds item) + private IEnumerable GetExternalIds(IHasProviderIds item) { return _externalIds.Where(i => { diff --git a/MediaBrowser.Providers/Manager/ProviderUtils.cs b/MediaBrowser.Providers/Manager/ProviderUtils.cs index a4fd6ca84e..70a5a6ac13 100644 --- a/MediaBrowser.Providers/Manager/ProviderUtils.cs +++ b/MediaBrowser.Providers/Manager/ProviderUtils.cs @@ -26,12 +26,12 @@ namespace MediaBrowser.Providers.Manager if (source == null) { - throw new ArgumentNullException(nameof(source)); + throw new ArgumentException("Item cannot be null.", nameof(sourceResult)); } if (target == null) { - throw new ArgumentNullException(nameof(target)); + throw new ArgumentException("Item cannot be null.", nameof(targetResult)); } if (!lockedFields.Contains(MetadataField.Name)) diff --git a/MediaBrowser.Providers/Manager/RefreshResult.cs b/MediaBrowser.Providers/Manager/RefreshResult.cs new file mode 100644 index 0000000000..72fc61e424 --- /dev/null +++ b/MediaBrowser.Providers/Manager/RefreshResult.cs @@ -0,0 +1,15 @@ +#pragma warning disable CS1591 + +using MediaBrowser.Controller.Library; + +namespace MediaBrowser.Providers.Manager +{ + public class RefreshResult + { + public ItemUpdateType UpdateType { get; set; } + + public string ErrorMessage { get; set; } + + public int Failures { get; set; } + } +} diff --git a/MediaBrowser.Providers/MediaInfo/AudioImageProvider.cs b/MediaBrowser.Providers/MediaInfo/AudioImageProvider.cs index f69ec9744a..64ad1bddfe 100644 --- a/MediaBrowser.Providers/MediaInfo/AudioImageProvider.cs +++ b/MediaBrowser.Providers/MediaInfo/AudioImageProvider.cs @@ -34,6 +34,10 @@ namespace MediaBrowser.Providers.MediaInfo _fileSystem = fileSystem; } + public string AudioImagesPath => Path.Combine(_config.ApplicationPaths.CachePath, "extracted-audio-images"); + + public string Name => "Image Extractor"; + public IEnumerable GetSupportedImages(BaseItem item) { return new List { ImageType.Primary }; @@ -97,11 +101,11 @@ namespace MediaBrowser.Providers.MediaInfo if (item.GetType() == typeof(Audio)) { - var albumArtist = item.AlbumArtists.FirstOrDefault(); - - if (!string.IsNullOrWhiteSpace(item.Album) && !string.IsNullOrWhiteSpace(albumArtist)) + if (item.AlbumArtists.Count > 0 + && !string.IsNullOrWhiteSpace(item.Album) + && !string.IsNullOrWhiteSpace(item.AlbumArtists[0])) { - filename = (item.Album + "-" + albumArtist).GetMD5().ToString("N", CultureInfo.InvariantCulture); + filename = (item.Album + "-" + item.AlbumArtists[0]).GetMD5().ToString("N", CultureInfo.InvariantCulture); } else { @@ -121,10 +125,6 @@ namespace MediaBrowser.Providers.MediaInfo return Path.Join(AudioImagesPath, prefix, filename); } - public string AudioImagesPath => Path.Combine(_config.ApplicationPaths.CachePath, "extracted-audio-images"); - - public string Name => "Image Extractor"; - public bool Supports(BaseItem item) { if (item.IsShortcut) diff --git a/MediaBrowser.Providers/MediaInfo/FFProbeAudioInfo.cs b/MediaBrowser.Providers/MediaInfo/FFProbeAudioInfo.cs index 77f03580ab..9454636669 100644 --- a/MediaBrowser.Providers/MediaInfo/FFProbeAudioInfo.cs +++ b/MediaBrowser.Providers/MediaInfo/FFProbeAudioInfo.cs @@ -37,7 +37,9 @@ namespace MediaBrowser.Providers.MediaInfo _mediaSourceManager = mediaSourceManager; } - public async Task Probe(T item, MetadataRefreshOptions options, + public async Task Probe( + T item, + MetadataRefreshOptions options, CancellationToken cancellationToken) where T : Audio { @@ -52,19 +54,21 @@ namespace MediaBrowser.Providers.MediaInfo protocol = _mediaSourceManager.GetPathProtocol(path); } - var result = await _mediaEncoder.GetMediaInfo(new MediaInfoRequest - { - MediaType = DlnaProfileType.Audio, - MediaSource = new MediaSourceInfo + var result = await _mediaEncoder.GetMediaInfo( + new MediaInfoRequest { - Path = path, - Protocol = protocol - } - }, cancellationToken).ConfigureAwait(false); + MediaType = DlnaProfileType.Audio, + MediaSource = new MediaSourceInfo + { + Path = path, + Protocol = protocol + } + }, + cancellationToken).ConfigureAwait(false); cancellationToken.ThrowIfCancellationRequested(); - Fetch(item, cancellationToken, result); + Fetch(item, result, cancellationToken); } return ItemUpdateType.MetadataImport; @@ -74,10 +78,9 @@ namespace MediaBrowser.Providers.MediaInfo /// Fetches the specified audio. /// /// The audio. - /// The cancellation token. /// The media information. - /// Task. - protected void Fetch(Audio audio, CancellationToken cancellationToken, Model.MediaInfo.MediaInfo mediaInfo) + /// The cancellation token. + protected void Fetch(Audio audio, Model.MediaInfo.MediaInfo mediaInfo, CancellationToken cancellationToken) { var mediaStreams = mediaInfo.MediaStreams; diff --git a/MediaBrowser.Providers/MediaInfo/FFProbeProvider.cs b/MediaBrowser.Providers/MediaInfo/FFProbeProvider.cs index 9926275ae7..c61187fdf6 100644 --- a/MediaBrowser.Providers/MediaInfo/FFProbeProvider.cs +++ b/MediaBrowser.Providers/MediaInfo/FFProbeProvider.cs @@ -5,8 +5,6 @@ using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; -using MediaBrowser.Common.Configuration; -using MediaBrowser.Controller.Channels; using MediaBrowser.Controller.Chapters; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; @@ -20,9 +18,7 @@ using MediaBrowser.Controller.Providers; using MediaBrowser.Controller.Subtitles; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Globalization; -using MediaBrowser.Model.IO; using MediaBrowser.Model.MediaInfo; -using MediaBrowser.Model.Serialization; using Microsoft.Extensions.Logging; namespace MediaBrowser.Providers.MediaInfo @@ -50,9 +46,43 @@ namespace MediaBrowser.Providers.MediaInfo private readonly IChapterManager _chapterManager; private readonly ILibraryManager _libraryManager; private readonly IMediaSourceManager _mediaSourceManager; + private readonly SubtitleResolver _subtitleResolver; + + private readonly Task _cachedTask = Task.FromResult(ItemUpdateType.None); + + public FFProbeProvider( + ILogger logger, + IMediaSourceManager mediaSourceManager, + IMediaEncoder mediaEncoder, + IItemRepository itemRepo, + IBlurayExaminer blurayExaminer, + ILocalizationManager localization, + IEncodingManager encodingManager, + IServerConfigurationManager config, + ISubtitleManager subtitleManager, + IChapterManager chapterManager, + ILibraryManager libraryManager) + { + _logger = logger; + _mediaEncoder = mediaEncoder; + _itemRepo = itemRepo; + _blurayExaminer = blurayExaminer; + _localization = localization; + _encodingManager = encodingManager; + _config = config; + _subtitleManager = subtitleManager; + _chapterManager = chapterManager; + _libraryManager = libraryManager; + _mediaSourceManager = mediaSourceManager; + + _subtitleResolver = new SubtitleResolver(BaseItem.LocalizationManager); + } public string Name => "ffprobe"; + // Run last + public int Order => 100; + public bool HasChanged(BaseItem item, IDirectoryService directoryService) { var video = item as Video; @@ -117,37 +147,6 @@ namespace MediaBrowser.Providers.MediaInfo return FetchAudioInfo(item, options, cancellationToken); } - private SubtitleResolver _subtitleResolver; - - public FFProbeProvider( - ILogger logger, - IMediaSourceManager mediaSourceManager, - IMediaEncoder mediaEncoder, - IItemRepository itemRepo, - IBlurayExaminer blurayExaminer, - ILocalizationManager localization, - IEncodingManager encodingManager, - IServerConfigurationManager config, - ISubtitleManager subtitleManager, - IChapterManager chapterManager, - ILibraryManager libraryManager) - { - _logger = logger; - _mediaEncoder = mediaEncoder; - _itemRepo = itemRepo; - _blurayExaminer = blurayExaminer; - _localization = localization; - _encodingManager = encodingManager; - _config = config; - _subtitleManager = subtitleManager; - _chapterManager = chapterManager; - _libraryManager = libraryManager; - _mediaSourceManager = mediaSourceManager; - - _subtitleResolver = new SubtitleResolver(BaseItem.LocalizationManager); - } - - private readonly Task _cachedTask = Task.FromResult(ItemUpdateType.None); public Task FetchVideoInfo(T item, MetadataRefreshOptions options, CancellationToken cancellationToken) where T : Video { @@ -234,8 +233,5 @@ namespace MediaBrowser.Providers.MediaInfo return prober.Probe(item, options, cancellationToken); } - - // Run last - public int Order => 100; } } diff --git a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs index 53a6bb6195..776dee7802 100644 --- a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs +++ b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs @@ -539,17 +539,18 @@ namespace MediaBrowser.Providers.MediaInfo if (enableSubtitleDownloading && enabled) { - var downloadedLanguages = await new SubtitleDownloader(_logger, - _subtitleManager) - .DownloadSubtitles(video, - currentStreams.Concat(externalSubtitleStreams).ToList(), - skipIfEmbeddedSubtitlesPresent, - skipIfAudioTrackMatches, - requirePerfectMatch, - subtitleDownloadLanguages, - libraryOptions.DisabledSubtitleFetchers, - libraryOptions.SubtitleFetcherOrder, - cancellationToken).ConfigureAwait(false); + var downloadedLanguages = await new SubtitleDownloader( + _logger, + _subtitleManager).DownloadSubtitles( + video, + currentStreams.Concat(externalSubtitleStreams).ToList(), + skipIfEmbeddedSubtitlesPresent, + skipIfAudioTrackMatches, + requirePerfectMatch, + subtitleDownloadLanguages, + libraryOptions.DisabledSubtitleFetchers, + libraryOptions.SubtitleFetcherOrder, + cancellationToken).ConfigureAwait(false); // Rescan if (downloadedLanguages.Count > 0) diff --git a/MediaBrowser.Providers/MediaInfo/SubtitleDownloader.cs b/MediaBrowser.Providers/MediaInfo/SubtitleDownloader.cs index acddb73d0b..912aedb0db 100644 --- a/MediaBrowser.Providers/MediaInfo/SubtitleDownloader.cs +++ b/MediaBrowser.Providers/MediaInfo/SubtitleDownloader.cs @@ -42,8 +42,16 @@ namespace MediaBrowser.Providers.MediaInfo foreach (var lang in languages) { - var downloaded = await DownloadSubtitles(video, mediaStreams, skipIfEmbeddedSubtitlesPresent, - skipIfAudioTrackMatches, requirePerfectMatch, lang, disabledSubtitleFetchers, subtitleFetcherOrder, cancellationToken).ConfigureAwait(false); + var downloaded = await DownloadSubtitles( + video, + mediaStreams, + skipIfEmbeddedSubtitlesPresent, + skipIfAudioTrackMatches, + requirePerfectMatch, + lang, + disabledSubtitleFetchers, + subtitleFetcherOrder, + cancellationToken).ConfigureAwait(false); if (downloaded) { @@ -54,7 +62,8 @@ namespace MediaBrowser.Providers.MediaInfo return downloadedLanguages; } - public Task DownloadSubtitles(Video video, + public Task DownloadSubtitles( + Video video, List mediaStreams, bool skipIfEmbeddedSubtitlesPresent, bool skipIfAudioTrackMatches, @@ -90,11 +99,21 @@ namespace MediaBrowser.Providers.MediaInfo return Task.FromResult(false); } - return DownloadSubtitles(video, mediaStreams, skipIfEmbeddedSubtitlesPresent, skipIfAudioTrackMatches, - requirePerfectMatch, lang, disabledSubtitleFetchers, subtitleFetcherOrder, mediaType, cancellationToken); + return DownloadSubtitles( + video, + mediaStreams, + skipIfEmbeddedSubtitlesPresent, + skipIfAudioTrackMatches, + requirePerfectMatch, + lang, + disabledSubtitleFetchers, + subtitleFetcherOrder, + mediaType, + cancellationToken); } - private async Task DownloadSubtitles(Video video, + private async Task DownloadSubtitles( + Video video, List mediaStreams, bool skipIfEmbeddedSubtitlesPresent, bool skipIfAudioTrackMatches, diff --git a/MediaBrowser.Providers/MediaInfo/SubtitleResolver.cs b/MediaBrowser.Providers/MediaInfo/SubtitleResolver.cs index 43659b68cc..e9f999c6d0 100644 --- a/MediaBrowser.Providers/MediaInfo/SubtitleResolver.cs +++ b/MediaBrowser.Providers/MediaInfo/SubtitleResolver.cs @@ -66,9 +66,10 @@ namespace MediaBrowser.Providers.MediaInfo return streams; } - public List GetExternalSubtitleFiles(Video video, - IDirectoryService directoryService, - bool clearCache) + public List GetExternalSubtitleFiles( + Video video, + IDirectoryService directoryService, + bool clearCache) { var list = new List(); @@ -87,7 +88,9 @@ namespace MediaBrowser.Providers.MediaInfo return list; } - private void AddExternalSubtitleStreams(List streams, string folder, + private void AddExternalSubtitleStreams( + List streams, + string folder, string videoPath, int startIndex, IDirectoryService directoryService, @@ -98,7 +101,8 @@ namespace MediaBrowser.Providers.MediaInfo AddExternalSubtitleStreams(streams, videoPath, startIndex, files); } - public void AddExternalSubtitleStreams(List streams, + public void AddExternalSubtitleStreams( + List streams, string videoPath, int startIndex, string[] files) @@ -185,8 +189,8 @@ namespace MediaBrowser.Providers.MediaInfo private string NormalizeFilenameForSubtitleComparison(string filename) { // Try to account for sloppy file naming - filename = filename.Replace("_", string.Empty); - filename = filename.Replace(" ", string.Empty); + filename = filename.Replace("_", string.Empty, StringComparison.Ordinal); + filename = filename.Replace(" ", string.Empty, StringComparison.Ordinal); // can't normalize this due to languages such as pt-br // filename = filename.Replace("-", string.Empty); diff --git a/MediaBrowser.Providers/MediaInfo/SubtitleScheduledTask.cs b/MediaBrowser.Providers/MediaInfo/SubtitleScheduledTask.cs index 91ab7b4acb..d231bfa2fc 100644 --- a/MediaBrowser.Providers/MediaInfo/SubtitleScheduledTask.cs +++ b/MediaBrowser.Providers/MediaInfo/SubtitleScheduledTask.cs @@ -12,11 +12,10 @@ using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Subtitles; using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Globalization; using MediaBrowser.Model.Providers; -using MediaBrowser.Model.Serialization; using MediaBrowser.Model.Tasks; using Microsoft.Extensions.Logging; -using MediaBrowser.Model.Globalization; namespace MediaBrowser.Providers.MediaInfo { @@ -25,29 +24,37 @@ namespace MediaBrowser.Providers.MediaInfo private readonly ILibraryManager _libraryManager; private readonly IServerConfigurationManager _config; private readonly ISubtitleManager _subtitleManager; - private readonly IMediaSourceManager _mediaSourceManager; private readonly ILogger _logger; - private readonly IJsonSerializer _json; private readonly ILocalizationManager _localization; public SubtitleScheduledTask( ILibraryManager libraryManager, - IJsonSerializer json, IServerConfigurationManager config, ISubtitleManager subtitleManager, ILogger logger, - IMediaSourceManager mediaSourceManager, ILocalizationManager localization) { _libraryManager = libraryManager; _config = config; _subtitleManager = subtitleManager; _logger = logger; - _mediaSourceManager = mediaSourceManager; - _json = json; _localization = localization; } + public string Name => _localization.GetLocalizedString("TaskDownloadMissingSubtitles"); + + public string Description => _localization.GetLocalizedString("TaskDownloadMissingSubtitlesDescription"); + + public string Category => _localization.GetLocalizedString("TasksLibraryCategory"); + + public string Key => "DownloadSubtitles"; + + public bool IsHidden => false; + + public bool IsEnabled => true; + + public bool IsLogged => true; + private SubtitleOptions GetOptions() { return _config.GetConfiguration("subtitles"); @@ -66,23 +73,23 @@ namespace MediaBrowser.Providers.MediaInfo var libraryOptions = _libraryManager.GetLibraryOptions(library); string[] subtitleDownloadLanguages; - bool SkipIfEmbeddedSubtitlesPresent; - bool SkipIfAudioTrackMatches; - bool RequirePerfectMatch; + bool skipIfEmbeddedSubtitlesPresent; + bool skipIfAudioTrackMatches; + bool requirePerfectMatch; if (libraryOptions.SubtitleDownloadLanguages == null) { subtitleDownloadLanguages = options.DownloadLanguages; - SkipIfEmbeddedSubtitlesPresent = options.SkipIfEmbeddedSubtitlesPresent; - SkipIfAudioTrackMatches = options.SkipIfAudioTrackMatches; - RequirePerfectMatch = options.RequirePerfectMatch; + skipIfEmbeddedSubtitlesPresent = options.SkipIfEmbeddedSubtitlesPresent; + skipIfAudioTrackMatches = options.SkipIfAudioTrackMatches; + requirePerfectMatch = options.RequirePerfectMatch; } else { subtitleDownloadLanguages = libraryOptions.SubtitleDownloadLanguages; - SkipIfEmbeddedSubtitlesPresent = libraryOptions.SkipSubtitlesIfEmbeddedSubtitlesPresent; - SkipIfAudioTrackMatches = libraryOptions.SkipSubtitlesIfAudioTrackMatches; - RequirePerfectMatch = libraryOptions.RequirePerfectSubtitleMatch; + skipIfEmbeddedSubtitlesPresent = libraryOptions.SkipSubtitlesIfEmbeddedSubtitlesPresent; + skipIfAudioTrackMatches = libraryOptions.SkipSubtitlesIfAudioTrackMatches; + requirePerfectMatch = libraryOptions.RequirePerfectSubtitleMatch; } foreach (var lang in subtitleDownloadLanguages) @@ -98,12 +105,12 @@ namespace MediaBrowser.Providers.MediaInfo Recursive = true }; - if (SkipIfAudioTrackMatches) + if (skipIfAudioTrackMatches) { query.HasNoAudioTrackWithLanguage = lang; } - if (SkipIfEmbeddedSubtitlesPresent) + if (skipIfEmbeddedSubtitlesPresent) { // Exclude if it already has any subtitles of the same language query.HasNoSubtitleTrackWithLanguage = lang; @@ -160,36 +167,37 @@ namespace MediaBrowser.Providers.MediaInfo var libraryOptions = _libraryManager.GetLibraryOptions(video); string[] subtitleDownloadLanguages; - bool SkipIfEmbeddedSubtitlesPresent; - bool SkipIfAudioTrackMatches; - bool RequirePerfectMatch; + bool skipIfEmbeddedSubtitlesPresent; + bool skipIfAudioTrackMatches; + bool requirePerfectMatch; if (libraryOptions.SubtitleDownloadLanguages == null) { subtitleDownloadLanguages = options.DownloadLanguages; - SkipIfEmbeddedSubtitlesPresent = options.SkipIfEmbeddedSubtitlesPresent; - SkipIfAudioTrackMatches = options.SkipIfAudioTrackMatches; - RequirePerfectMatch = options.RequirePerfectMatch; + skipIfEmbeddedSubtitlesPresent = options.SkipIfEmbeddedSubtitlesPresent; + skipIfAudioTrackMatches = options.SkipIfAudioTrackMatches; + requirePerfectMatch = options.RequirePerfectMatch; } else { subtitleDownloadLanguages = libraryOptions.SubtitleDownloadLanguages; - SkipIfEmbeddedSubtitlesPresent = libraryOptions.SkipSubtitlesIfEmbeddedSubtitlesPresent; - SkipIfAudioTrackMatches = libraryOptions.SkipSubtitlesIfAudioTrackMatches; - RequirePerfectMatch = libraryOptions.RequirePerfectSubtitleMatch; + skipIfEmbeddedSubtitlesPresent = libraryOptions.SkipSubtitlesIfEmbeddedSubtitlesPresent; + skipIfAudioTrackMatches = libraryOptions.SkipSubtitlesIfAudioTrackMatches; + requirePerfectMatch = libraryOptions.RequirePerfectSubtitleMatch; } - var downloadedLanguages = await new SubtitleDownloader(_logger, - _subtitleManager) - .DownloadSubtitles(video, - mediaStreams, - SkipIfEmbeddedSubtitlesPresent, - SkipIfAudioTrackMatches, - RequirePerfectMatch, - subtitleDownloadLanguages, - libraryOptions.DisabledSubtitleFetchers, - libraryOptions.SubtitleFetcherOrder, - cancellationToken).ConfigureAwait(false); + var downloadedLanguages = await new SubtitleDownloader( + _logger, + _subtitleManager).DownloadSubtitles( + video, + mediaStreams, + skipIfEmbeddedSubtitlesPresent, + skipIfAudioTrackMatches, + requirePerfectMatch, + subtitleDownloadLanguages, + libraryOptions.DisabledSubtitleFetchers, + libraryOptions.SubtitleFetcherOrder, + cancellationToken).ConfigureAwait(false); // Rescan if (downloadedLanguages.Count > 0) @@ -203,25 +211,11 @@ namespace MediaBrowser.Providers.MediaInfo public IEnumerable GetDefaultTriggers() { - return new[] { - + return new[] + { // Every so often new TaskTriggerInfo { Type = TaskTriggerInfo.TriggerInterval, IntervalTicks = TimeSpan.FromHours(24).Ticks} }; } - - public string Name => _localization.GetLocalizedString("TaskDownloadMissingSubtitles"); - - public string Description => _localization.GetLocalizedString("TaskDownloadMissingSubtitlesDescription"); - - public string Category => _localization.GetLocalizedString("TasksLibraryCategory"); - - public string Key => "DownloadSubtitles"; - - public bool IsHidden => false; - - public bool IsEnabled => true; - - public bool IsLogged => true; } } diff --git a/MediaBrowser.Providers/MediaInfo/VideoImageProvider.cs b/MediaBrowser.Providers/MediaInfo/VideoImageProvider.cs index e23854d900..fc38d3832a 100644 --- a/MediaBrowser.Providers/MediaInfo/VideoImageProvider.cs +++ b/MediaBrowser.Providers/MediaInfo/VideoImageProvider.cs @@ -29,6 +29,11 @@ namespace MediaBrowser.Providers.MediaInfo _fileSystem = fileSystem; } + public string Name => "Screen Grabber"; + + // Make sure this comes after internet image providers + public int Order => 100; + public IEnumerable GetSupportedImages(BaseItem item) { return new List { ImageType.Primary }; @@ -127,8 +132,6 @@ namespace MediaBrowser.Providers.MediaInfo }; } - public string Name => "Screen Grabber"; - public bool Supports(BaseItem item) { if (item.IsShortcut) @@ -150,7 +153,5 @@ namespace MediaBrowser.Providers.MediaInfo return false; } - // Make sure this comes after internet image providers - public int Order => 100; } } diff --git a/MediaBrowser.Providers/Movies/MovieExternalIds.cs b/MediaBrowser.Providers/Movies/ImdbExternalId.cs similarity index 68% rename from MediaBrowser.Providers/Movies/MovieExternalIds.cs rename to MediaBrowser.Providers/Movies/ImdbExternalId.cs index 14080841c2..a8d74aa0b5 100644 --- a/MediaBrowser.Providers/Movies/MovieExternalIds.cs +++ b/MediaBrowser.Providers/Movies/ImdbExternalId.cs @@ -36,22 +36,4 @@ namespace MediaBrowser.Providers.Movies return item is Movie || item is MusicVideo || item is Series || item is Episode || item is Trailer; } } - - public class ImdbPersonExternalId : IExternalId - { - /// - public string ProviderName => "IMDb"; - - /// - public string Key => MetadataProvider.Imdb.ToString(); - - /// - public ExternalIdMediaType? Type => ExternalIdMediaType.Person; - - /// - public string UrlFormatString => "https://www.imdb.com/name/{0}"; - - /// - public bool Supports(IHasProviderIds item) => item is Person; - } } diff --git a/MediaBrowser.Providers/Movies/ImdbPersonExternalId.cs b/MediaBrowser.Providers/Movies/ImdbPersonExternalId.cs new file mode 100644 index 0000000000..8151ab4715 --- /dev/null +++ b/MediaBrowser.Providers/Movies/ImdbPersonExternalId.cs @@ -0,0 +1,27 @@ +#pragma warning disable CS1591 + +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Providers; + +namespace MediaBrowser.Providers.Movies +{ + public class ImdbPersonExternalId : IExternalId + { + /// + public string ProviderName => "IMDb"; + + /// + public string Key => MetadataProvider.Imdb.ToString(); + + /// + public ExternalIdMediaType? Type => ExternalIdMediaType.Person; + + /// + public string UrlFormatString => "https://www.imdb.com/name/{0}"; + + /// + public bool Supports(IHasProviderIds item) => item is Person; + } +} diff --git a/MediaBrowser.Providers/Music/Extensions.cs b/MediaBrowser.Providers/Music/AlbumInfoExtensions.cs similarity index 100% rename from MediaBrowser.Providers/Music/Extensions.cs rename to MediaBrowser.Providers/Music/AlbumInfoExtensions.cs diff --git a/MediaBrowser.Providers/Music/MusicExternalIds.cs b/MediaBrowser.Providers/Music/ImvdbId.cs similarity index 100% rename from MediaBrowser.Providers/Music/MusicExternalIds.cs rename to MediaBrowser.Providers/Music/ImvdbId.cs diff --git a/MediaBrowser.Providers/Playlists/PlaylistItemsProvider.cs b/MediaBrowser.Providers/Playlists/PlaylistItemsProvider.cs index 5cc0a527ea..067d585cba 100644 --- a/MediaBrowser.Providers/Playlists/PlaylistItemsProvider.cs +++ b/MediaBrowser.Providers/Playlists/PlaylistItemsProvider.cs @@ -10,7 +10,6 @@ using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Playlists; using MediaBrowser.Controller.Providers; -using MediaBrowser.Model.IO; using Microsoft.Extensions.Logging; using PlaylistsNET.Content; @@ -23,16 +22,17 @@ namespace MediaBrowser.Providers.Playlists IHasItemChangeMonitor { private readonly ILogger _logger; - private IFileSystem _fileSystem; - public PlaylistItemsProvider(IFileSystem fileSystem, ILogger logger) + public PlaylistItemsProvider(ILogger logger) { - _fileSystem = fileSystem; _logger = logger; } public string Name => "Playlist Reader"; + // Run last + public int Order => 100; + public Task FetchAsync(Playlist item, MetadataRefreshOptions options, CancellationToken cancellationToken) { var path = item.Path; @@ -163,7 +163,5 @@ namespace MediaBrowser.Providers.Playlists return false; } - // Run last - public int Order => 100; } } diff --git a/MediaBrowser.Providers/Plugins/AudioDb/ArtistProvider.cs b/MediaBrowser.Providers/Plugins/AudioDb/ArtistProvider.cs index 670c0cd05a..72dad8a25a 100644 --- a/MediaBrowser.Providers/Plugins/AudioDb/ArtistProvider.cs +++ b/MediaBrowser.Providers/Plugins/AudioDb/ArtistProvider.cs @@ -23,16 +23,14 @@ namespace MediaBrowser.Providers.Plugins.AudioDb { public class AudioDbArtistProvider : IRemoteMetadataProvider, IHasOrder { + private const string ApiKey = "195003"; + public const string BaseUrl = "https://www.theaudiodb.com/api/v1/json/" + ApiKey; + private readonly IServerConfigurationManager _config; private readonly IFileSystem _fileSystem; private readonly IHttpClientFactory _httpClientFactory; private readonly IJsonSerializer _json; - public static AudioDbArtistProvider Current; - - private const string ApiKey = "195003"; - public const string BaseUrl = "https://www.theaudiodb.com/api/v1/json/" + ApiKey; - public AudioDbArtistProvider(IServerConfigurationManager config, IFileSystem fileSystem, IHttpClientFactory httpClientFactory, IJsonSerializer json) { _config = config; @@ -42,6 +40,8 @@ namespace MediaBrowser.Providers.Plugins.AudioDb Current = this; } + public static AudioDbArtistProvider Current { get; private set; } + /// public string Name => "TheAudioDB"; diff --git a/MediaBrowser.Providers/Plugins/AudioDb/AudioDbAlbumExternalId.cs b/MediaBrowser.Providers/Plugins/AudioDb/AudioDbAlbumExternalId.cs new file mode 100644 index 0000000000..138cfef19a --- /dev/null +++ b/MediaBrowser.Providers/Plugins/AudioDb/AudioDbAlbumExternalId.cs @@ -0,0 +1,27 @@ +#pragma warning disable CS1591 + +using MediaBrowser.Controller.Entities.Audio; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Providers; + +namespace MediaBrowser.Providers.Plugins.AudioDb +{ + public class AudioDbAlbumExternalId : IExternalId + { + /// + public string ProviderName => "TheAudioDb"; + + /// + public string Key => MetadataProvider.AudioDbAlbum.ToString(); + + /// + public ExternalIdMediaType? Type => null; + + /// + public string UrlFormatString => "https://www.theaudiodb.com/album/{0}"; + + /// + public bool Supports(IHasProviderIds item) => item is MusicAlbum; + } +} diff --git a/MediaBrowser.Providers/Plugins/AudioDb/AudioDbArtistExternalId.cs b/MediaBrowser.Providers/Plugins/AudioDb/AudioDbArtistExternalId.cs new file mode 100644 index 0000000000..8aceb48c0c --- /dev/null +++ b/MediaBrowser.Providers/Plugins/AudioDb/AudioDbArtistExternalId.cs @@ -0,0 +1,27 @@ +#pragma warning disable CS1591 + +using MediaBrowser.Controller.Entities.Audio; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Providers; + +namespace MediaBrowser.Providers.Plugins.AudioDb +{ + public class AudioDbArtistExternalId : IExternalId + { + /// + public string ProviderName => "TheAudioDb"; + + /// + public string Key => MetadataProvider.AudioDbArtist.ToString(); + + /// + public ExternalIdMediaType? Type => ExternalIdMediaType.Artist; + + /// + public string UrlFormatString => "https://www.theaudiodb.com/artist/{0}"; + + /// + public bool Supports(IHasProviderIds item) => item is MusicArtist; + } +} diff --git a/MediaBrowser.Providers/Plugins/AudioDb/AudioDbOtherAlbumExternalId.cs b/MediaBrowser.Providers/Plugins/AudioDb/AudioDbOtherAlbumExternalId.cs new file mode 100644 index 0000000000..014481da24 --- /dev/null +++ b/MediaBrowser.Providers/Plugins/AudioDb/AudioDbOtherAlbumExternalId.cs @@ -0,0 +1,27 @@ +#pragma warning disable CS1591 + +using MediaBrowser.Controller.Entities.Audio; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Providers; + +namespace MediaBrowser.Providers.Plugins.AudioDb +{ + public class AudioDbOtherAlbumExternalId : IExternalId + { + /// + public string ProviderName => "TheAudioDb"; + + /// + public string Key => MetadataProvider.AudioDbAlbum.ToString(); + + /// + public ExternalIdMediaType? Type => ExternalIdMediaType.Album; + + /// + public string UrlFormatString => "https://www.theaudiodb.com/album/{0}"; + + /// + public bool Supports(IHasProviderIds item) => item is Audio; + } +} diff --git a/MediaBrowser.Providers/Plugins/AudioDb/AudioDbOtherArtistExternalId.cs b/MediaBrowser.Providers/Plugins/AudioDb/AudioDbOtherArtistExternalId.cs new file mode 100644 index 0000000000..7875391043 --- /dev/null +++ b/MediaBrowser.Providers/Plugins/AudioDb/AudioDbOtherArtistExternalId.cs @@ -0,0 +1,27 @@ +#pragma warning disable CS1591 + +using MediaBrowser.Controller.Entities.Audio; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Providers; + +namespace MediaBrowser.Providers.Plugins.AudioDb +{ + public class AudioDbOtherArtistExternalId : IExternalId + { + /// + public string ProviderName => "TheAudioDb"; + + /// + public string Key => MetadataProvider.AudioDbArtist.ToString(); + + /// + public ExternalIdMediaType? Type => ExternalIdMediaType.OtherArtist; + + /// + public string UrlFormatString => "https://www.theaudiodb.com/artist/{0}"; + + /// + public bool Supports(IHasProviderIds item) => item is Audio || item is MusicAlbum; + } +} diff --git a/MediaBrowser.Providers/Plugins/AudioDb/ExternalIds.cs b/MediaBrowser.Providers/Plugins/AudioDb/ExternalIds.cs deleted file mode 100644 index 1cc1f0fa18..0000000000 --- a/MediaBrowser.Providers/Plugins/AudioDb/ExternalIds.cs +++ /dev/null @@ -1,81 +0,0 @@ -#pragma warning disable CS1591 - -using MediaBrowser.Controller.Entities.Audio; -using MediaBrowser.Controller.Providers; -using MediaBrowser.Model.Entities; -using MediaBrowser.Model.Providers; - -namespace MediaBrowser.Providers.Plugins.AudioDb -{ - public class AudioDbAlbumExternalId : IExternalId - { - /// - public string ProviderName => "TheAudioDb"; - - /// - public string Key => MetadataProvider.AudioDbAlbum.ToString(); - - /// - public ExternalIdMediaType? Type => null; - - /// - public string UrlFormatString => "https://www.theaudiodb.com/album/{0}"; - - /// - public bool Supports(IHasProviderIds item) => item is MusicAlbum; - } - - public class AudioDbOtherAlbumExternalId : IExternalId - { - /// - public string ProviderName => "TheAudioDb"; - - /// - public string Key => MetadataProvider.AudioDbAlbum.ToString(); - - /// - public ExternalIdMediaType? Type => ExternalIdMediaType.Album; - - /// - public string UrlFormatString => "https://www.theaudiodb.com/album/{0}"; - - /// - public bool Supports(IHasProviderIds item) => item is Audio; - } - - public class AudioDbArtistExternalId : IExternalId - { - /// - public string ProviderName => "TheAudioDb"; - - /// - public string Key => MetadataProvider.AudioDbArtist.ToString(); - - /// - public ExternalIdMediaType? Type => ExternalIdMediaType.Artist; - - /// - public string UrlFormatString => "https://www.theaudiodb.com/artist/{0}"; - - /// - public bool Supports(IHasProviderIds item) => item is MusicArtist; - } - - public class AudioDbOtherArtistExternalId : IExternalId - { - /// - public string ProviderName => "TheAudioDb"; - - /// - public string Key => MetadataProvider.AudioDbArtist.ToString(); - - /// - public ExternalIdMediaType? Type => ExternalIdMediaType.OtherArtist; - - /// - public string UrlFormatString => "https://www.theaudiodb.com/artist/{0}"; - - /// - public bool Supports(IHasProviderIds item) => item is Audio || item is MusicAlbum; - } -} diff --git a/MediaBrowser.Providers/Plugins/AudioDb/Plugin.cs b/MediaBrowser.Providers/Plugins/AudioDb/Plugin.cs index 54054d0153..b5bd72ff0d 100644 --- a/MediaBrowser.Providers/Plugins/AudioDb/Plugin.cs +++ b/MediaBrowser.Providers/Plugins/AudioDb/Plugin.cs @@ -11,6 +11,12 @@ namespace MediaBrowser.Providers.Plugins.AudioDb { public class Plugin : BasePlugin, IHasWebPages { + public Plugin(IApplicationPaths applicationPaths, IXmlSerializer xmlSerializer) + : base(applicationPaths, xmlSerializer) + { + Instance = this; + } + public static Plugin Instance { get; private set; } public override Guid Id => new Guid("a629c0da-fac5-4c7e-931a-7174223f14c8"); @@ -22,12 +28,6 @@ namespace MediaBrowser.Providers.Plugins.AudioDb // TODO remove when plugin removed from server. public override string ConfigurationFileName => "Jellyfin.Plugin.AudioDb.xml"; - public Plugin(IApplicationPaths applicationPaths, IXmlSerializer xmlSerializer) - : base(applicationPaths, xmlSerializer) - { - Instance = this; - } - public IEnumerable GetPages() { yield return new PluginPageInfo diff --git a/MediaBrowser.Providers/Plugins/MusicBrainz/AlbumProvider.cs b/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzAlbumProvider.cs similarity index 98% rename from MediaBrowser.Providers/Plugins/MusicBrainz/AlbumProvider.cs rename to MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzAlbumProvider.cs index 8414c93281..46f8988f27 100644 --- a/MediaBrowser.Providers/Plugins/MusicBrainz/AlbumProvider.cs +++ b/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzAlbumProvider.cs @@ -8,7 +8,6 @@ using System.IO; using System.Linq; using System.Net; using System.Net.Http; -using System.Net.Http.Headers; using System.Text; using System.Threading; using System.Threading.Tasks; @@ -26,14 +25,6 @@ namespace MediaBrowser.Providers.Music { public class MusicBrainzAlbumProvider : IRemoteMetadataProvider, IHasOrder { - /// - /// The Jellyfin user-agent is unrestricted but source IP must not exceed - /// one request per second, therefore we rate limit to avoid throttling. - /// Be prudent, use a value slightly above the minimun required. - /// https://musicbrainz.org/doc/XML_Web_Service/Rate_Limiting - /// - private readonly long _musicBrainzQueryIntervalMs; - /// /// For each single MB lookup/search, this is the maximum number of /// attempts that shall be made whilst receiving a 503 Server @@ -41,7 +32,13 @@ namespace MediaBrowser.Providers.Music /// private const uint MusicBrainzQueryAttempts = 5u; - internal static MusicBrainzAlbumProvider Current; + /// + /// The Jellyfin user-agent is unrestricted but source IP must not exceed + /// one request per second, therefore we rate limit to avoid throttling. + /// Be prudent, use a value slightly above the minimun required. + /// https://musicbrainz.org/doc/XML_Web_Service/Rate_Limiting + /// + private readonly long _musicBrainzQueryIntervalMs; private readonly IHttpClientFactory _httpClientFactory; private readonly IApplicationHost _appHost; @@ -69,6 +66,8 @@ namespace MediaBrowser.Providers.Music Current = this; } + internal static MusicBrainzAlbumProvider Current { get; private set; } + /// public string Name => "MusicBrainz"; @@ -112,7 +111,7 @@ namespace MediaBrowser.Providers.Music else { // I'm sure there is a better way but for now it resolves search for 12" Mixes - var queryName = searchInfo.Name.Replace("\"", string.Empty); + var queryName = searchInfo.Name.Replace("\"", string.Empty, StringComparison.Ordinal); url = string.Format( CultureInfo.InvariantCulture, @@ -277,7 +276,9 @@ namespace MediaBrowser.Providers.Music private async Task GetReleaseResult(string albumName, string artistId, CancellationToken cancellationToken) { - var url = string.Format(CultureInfo.InvariantCulture, "/ws/2/release/?query=\"{0}\" AND arid:{1}", + var url = string.Format( + CultureInfo.InvariantCulture, + "/ws/2/release/?query=\"{0}\" AND arid:{1}", WebUtility.UrlEncode(albumName), artistId); @@ -496,7 +497,7 @@ namespace MediaBrowser.Providers.Music } } - private static ValueTuple ParseArtistCredit(XmlReader reader) + private static (string, string) ParseArtistCredit(XmlReader reader) { reader.MoveToContent(); reader.Read(); @@ -531,7 +532,7 @@ namespace MediaBrowser.Providers.Music } } - return new ValueTuple(); + return default; } private static (string, string) ParseArtistNameCredit(XmlReader reader) diff --git a/MediaBrowser.Providers/Plugins/Omdb/OmdbImageProvider.cs b/MediaBrowser.Providers/Plugins/Omdb/OmdbImageProvider.cs index 8b8fea09e8..8f4240dc11 100644 --- a/MediaBrowser.Providers/Plugins/Omdb/OmdbImageProvider.cs +++ b/MediaBrowser.Providers/Plugins/Omdb/OmdbImageProvider.cs @@ -36,6 +36,12 @@ namespace MediaBrowser.Providers.Plugins.Omdb _appHost = appHost; } + public string Name => "The Open Movie Database"; + + // After other internet providers, because they're better + // But before fallback providers like screengrab + public int Order => 90; + public IEnumerable GetSupportedImages(BaseItem item) { return new List @@ -86,15 +92,9 @@ namespace MediaBrowser.Providers.Plugins.Omdb return _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(url, cancellationToken); } - public string Name => "The Open Movie Database"; - public bool Supports(BaseItem item) { return item is Movie || item is Trailer || item is Episode; } - - // After other internet providers, because they're better - // But before fallback providers like screengrab - public int Order => 90; } } diff --git a/MediaBrowser.Providers/Plugins/Omdb/OmdbItemProvider.cs b/MediaBrowser.Providers/Plugins/Omdb/OmdbItemProvider.cs index d53eba7e94..705359d2c7 100644 --- a/MediaBrowser.Providers/Plugins/Omdb/OmdbItemProvider.cs +++ b/MediaBrowser.Providers/Plugins/Omdb/OmdbItemProvider.cs @@ -49,6 +49,8 @@ namespace MediaBrowser.Providers.Plugins.Omdb _appHost = appHost; } + public string Name => "The Open Movie Database"; + // After primary option public int Order => 2; @@ -199,8 +201,6 @@ namespace MediaBrowser.Providers.Plugins.Omdb return GetSearchResults(searchInfo, "movie", cancellationToken); } - public string Name => "The Open Movie Database"; - public async Task> GetMetadata(SeriesInfo info, CancellationToken cancellationToken) { var result = new MetadataResult @@ -263,14 +263,14 @@ namespace MediaBrowser.Providers.Plugins.Omdb { var results = await GetSearchResultsInternal(info, "movie", false, cancellationToken).ConfigureAwait(false); var first = results.FirstOrDefault(); - return first == null ? null : first.GetProviderId(MetadataProvider.Imdb); + return first?.GetProviderId(MetadataProvider.Imdb); } private async Task GetSeriesImdbId(SeriesInfo info, CancellationToken cancellationToken) { var results = await GetSearchResultsInternal(info, "series", false, cancellationToken).ConfigureAwait(false); var first = results.FirstOrDefault(); - return first == null ? null : first.GetProviderId(MetadataProvider.Imdb); + return first?.GetProviderId(MetadataProvider.Imdb); } public Task GetImageResponse(string url, CancellationToken cancellationToken) @@ -278,7 +278,7 @@ namespace MediaBrowser.Providers.Plugins.Omdb return _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(url, cancellationToken); } - class SearchResult + private class SearchResult { public string Title { get; set; } diff --git a/MediaBrowser.Providers/Plugins/Tmdb/BoxSets/TmdbBoxSetProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/BoxSets/TmdbBoxSetProvider.cs index e627550f13..e7328b5535 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/BoxSets/TmdbBoxSetProvider.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/BoxSets/TmdbBoxSetProvider.cs @@ -37,7 +37,6 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.BoxSets private readonly IJsonSerializer _json; private readonly IServerConfigurationManager _config; private readonly IFileSystem _fileSystem; - private readonly ILocalizationManager _localization; private readonly IHttpClientFactory _httpClientFactory; private readonly ILibraryManager _libraryManager; @@ -46,7 +45,6 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.BoxSets IJsonSerializer json, IServerConfigurationManager config, IFileSystem fileSystem, - ILocalizationManager localization, IHttpClientFactory httpClientFactory, ILibraryManager libraryManager) { @@ -54,7 +52,6 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.BoxSets _json = json; _config = config; _fileSystem = fileSystem; - _localization = localization; _httpClientFactory = httpClientFactory; _libraryManager = libraryManager; Current = this; @@ -177,7 +174,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.BoxSets private async Task FetchMainResult(string id, string language, CancellationToken cancellationToken) { - var url = string.Format(GetCollectionInfo3, id, TmdbUtils.ApiKey); + var url = string.Format(CultureInfo.InvariantCulture, GetCollectionInfo3, id, TmdbUtils.ApiKey); if (!string.IsNullOrEmpty(language)) { @@ -195,7 +192,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.BoxSets requestMessage.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue(header)); } - using var mainResponse = await TmdbMovieProvider.Current.GetMovieDbResponse(requestMessage); + using var mainResponse = await TmdbMovieProvider.Current.GetMovieDbResponse(requestMessage, cancellationToken).ConfigureAwait(false); await using var stream = await mainResponse.Content.ReadAsStreamAsync().ConfigureAwait(false); var mainResult = await _json.DeserializeFromStreamAsync(stream).ConfigureAwait(false); @@ -205,7 +202,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.BoxSets { if (!string.IsNullOrEmpty(language) && !string.Equals(language, "en", StringComparison.OrdinalIgnoreCase)) { - url = string.Format(GetCollectionInfo3, id, TmdbUtils.ApiKey) + "&language=en"; + url = string.Format(CultureInfo.InvariantCulture, GetCollectionInfo3, id, TmdbUtils.ApiKey) + "&language=en"; if (!string.IsNullOrEmpty(language)) { diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieProvider.cs index f8bc19395f..5d383722a0 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieProvider.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieProvider.cs @@ -155,7 +155,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies requestMessage.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue(header)); } - using var response = await GetMovieDbResponse(requestMessage).ConfigureAwait(false); + using var response = await GetMovieDbResponse(requestMessage, cancellationToken).ConfigureAwait(false); await using var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false); _tmdbSettings = await _jsonSerializer.DeserializeFromStreamAsync(stream).ConfigureAwait(false); return _tmdbSettings; @@ -335,7 +335,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies requestMessage.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue(header)); } - using var mainResponse = await GetMovieDbResponse(requestMessage).ConfigureAwait(false); + using var mainResponse = await GetMovieDbResponse(requestMessage, cancellationToken).ConfigureAwait(false); if (mainResponse.StatusCode == HttpStatusCode.NotFound) { return null; @@ -368,7 +368,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies langRequestMessage.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue(header)); } - using var langResponse = await GetMovieDbResponse(langRequestMessage).ConfigureAwait(false); + using var langResponse = await GetMovieDbResponse(langRequestMessage, cancellationToken).ConfigureAwait(false); await using var langStream = await langResponse.Content.ReadAsStreamAsync().ConfigureAwait(false); var langResult = await _jsonSerializer.DeserializeFromStreamAsync(stream).ConfigureAwait(false); @@ -381,10 +381,10 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies /// /// Gets the movie db response. /// - internal Task GetMovieDbResponse(HttpRequestMessage message) + internal Task GetMovieDbResponse(HttpRequestMessage message, CancellationToken cancellationToken = default) { message.Headers.UserAgent.ParseAdd(_appHost.ApplicationUserAgent); - return _httpClientFactory.CreateClient(NamedClient.Default).SendAsync(message); + return _httpClientFactory.CreateClient(NamedClient.Default).SendAsync(message, cancellationToken); } /// diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbSearch.cs b/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbSearch.cs index 2a6c6d0350..d885cd90b3 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbSearch.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbSearch.cs @@ -198,7 +198,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies requestMessage.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue(header)); } - using var response = await TmdbMovieProvider.Current.GetMovieDbResponse(requestMessage).ConfigureAwait(false); + using var response = await TmdbMovieProvider.Current.GetMovieDbResponse(requestMessage, cancellationToken).ConfigureAwait(false); await using var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false); var searchResults = await _json.DeserializeFromStreamAsync>(stream).ConfigureAwait(false); @@ -261,7 +261,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies requestMessage.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue(header)); } - using var response = await TmdbMovieProvider.Current.GetMovieDbResponse(requestMessage).ConfigureAwait(false); + using var response = await TmdbMovieProvider.Current.GetMovieDbResponse(requestMessage, cancellationToken).ConfigureAwait(false); await using var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false); var searchResults = await _json.DeserializeFromStreamAsync>(stream).ConfigureAwait(false); diff --git a/MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonProvider.cs index 6bf04b81ae..8d1a854d6c 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonProvider.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonProvider.cs @@ -110,7 +110,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.People requestMessage.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue(header)); } - var response = await TmdbMovieProvider.Current.GetMovieDbResponse(requestMessage).ConfigureAwait(false); + var response = await TmdbMovieProvider.Current.GetMovieDbResponse(requestMessage, cancellationToken).ConfigureAwait(false); await using var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false); var result2 = await _jsonSerializer.DeserializeFromStreamAsync>(stream).ConfigureAwait(false) @@ -243,7 +243,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.People requestMessage.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue(header)); } - using var response = await TmdbMovieProvider.Current.GetMovieDbResponse(requestMessage).ConfigureAwait(false); + using var response = await TmdbMovieProvider.Current.GetMovieDbResponse(requestMessage, cancellationToken).ConfigureAwait(false); Directory.CreateDirectory(Path.GetDirectoryName(dataFilePath)); await using var fs = new FileStream(dataFilePath, FileMode.Create, FileAccess.Write, FileShare.Read, IODefaults.FileStreamBufferSize, true); await response.Content.CopyToAsync(fs).ConfigureAwait(false); diff --git a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeProvider.cs index 90e3cea932..ed4739acbf 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeProvider.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeProvider.cs @@ -109,7 +109,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV item.ParentIndexNumber = info.ParentIndexNumber; item.IndexNumberEnd = info.IndexNumberEnd; - if (response.External_Ids.Tvdb_Id > 0) + if (response.External_Ids != null && response.External_Ids.Tvdb_Id > 0) { item.SetProviderId(MetadataProvider.Tvdb, response.External_Ids.Tvdb_Id.Value.ToString(CultureInfo.InvariantCulture)); } diff --git a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeProviderBase.cs b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeProviderBase.cs index 30b7674e33..55b0f0409e 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeProviderBase.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeProviderBase.cs @@ -113,7 +113,13 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV internal async Task FetchMainResult(string urlPattern, string id, int seasonNumber, int episodeNumber, string language, CancellationToken cancellationToken) { - var url = string.Format(urlPattern, id, seasonNumber.ToString(CultureInfo.InvariantCulture), episodeNumber, TmdbUtils.ApiKey); + var url = string.Format( + CultureInfo.InvariantCulture, + urlPattern, + id, + seasonNumber.ToString(CultureInfo.InvariantCulture), + episodeNumber, + TmdbUtils.ApiKey); if (!string.IsNullOrEmpty(language)) { @@ -132,7 +138,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV requestMessage.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue(header)); } - using var response = await TmdbMovieProvider.Current.GetMovieDbResponse(requestMessage); + using var response = await TmdbMovieProvider.Current.GetMovieDbResponse(requestMessage, cancellationToken).ConfigureAwait(false); await using var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false); return await _jsonSerializer.DeserializeFromStreamAsync(stream).ConfigureAwait(false); } diff --git a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeasonProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeasonProvider.cs index 73ed13267d..40f1c4e69f 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeasonProvider.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeasonProvider.cs @@ -75,7 +75,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV result.Item.Overview = seasonInfo.Overview; - if (seasonInfo.External_Ids.Tvdb_Id > 0) + if (seasonInfo.External_Ids != null && seasonInfo.External_Ids.Tvdb_Id > 0) { result.Item.SetProviderId(MetadataProvider.Tvdb, seasonInfo.External_Ids.Tvdb_Id.Value.ToString(CultureInfo.InvariantCulture)); } @@ -200,7 +200,12 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV internal async Task FetchMainResult(string id, int seasonNumber, string language, CancellationToken cancellationToken) { - var url = string.Format(GetTvInfo3, id, seasonNumber.ToString(CultureInfo.InvariantCulture), TmdbUtils.ApiKey); + var url = string.Format( + CultureInfo.InvariantCulture, + GetTvInfo3, + id, + seasonNumber.ToString(CultureInfo.InvariantCulture), + TmdbUtils.ApiKey); if (!string.IsNullOrEmpty(language)) { @@ -219,7 +224,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV requestMessage.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue(header)); } - using var response = await TmdbMovieProvider.Current.GetMovieDbResponse(requestMessage).ConfigureAwait(false); + using var response = await TmdbMovieProvider.Current.GetMovieDbResponse(requestMessage, cancellationToken).ConfigureAwait(false); await using var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false); return await _jsonSerializer.DeserializeFromStreamAsync(stream).ConfigureAwait(false); } diff --git a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesProvider.cs index aaba6ffc06..fa0873b9de 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesProvider.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesProvider.cs @@ -92,7 +92,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV remoteResult.SetProviderId(MetadataProvider.Tmdb, obj.Id.ToString(_usCulture)); remoteResult.SetProviderId(MetadataProvider.Imdb, obj.External_Ids.Imdb_Id); - if (obj.External_Ids.Tvdb_Id > 0) + if (obj.External_Ids != null && obj.External_Ids.Tvdb_Id > 0) { remoteResult.SetProviderId(MetadataProvider.Tvdb, obj.External_Ids.Tvdb_Id.Value.ToString(_usCulture)); } @@ -405,7 +405,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV internal async Task FetchMainResult(string id, string language, CancellationToken cancellationToken) { - var url = string.Format(GetTvInfo3, id, TmdbUtils.ApiKey); + var url = string.Format(CultureInfo.InvariantCulture, GetTvInfo3, id, TmdbUtils.ApiKey); if (!string.IsNullOrEmpty(language)) { @@ -421,7 +421,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV mainRequestMessage.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue(header)); } - using var mainResponse = await TmdbMovieProvider.Current.GetMovieDbResponse(mainRequestMessage); + using var mainResponse = await TmdbMovieProvider.Current.GetMovieDbResponse(mainRequestMessage, cancellationToken).ConfigureAwait(false); await using var mainStream = await mainResponse.Content.ReadAsStreamAsync().ConfigureAwait(false); var mainResult = await _jsonSerializer.DeserializeFromStreamAsync(mainStream).ConfigureAwait(false); @@ -440,7 +440,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV { _logger.LogInformation("MovieDbSeriesProvider couldn't find meta for language {Language}. Trying English...", language); - url = string.Format(GetTvInfo3, id, TmdbUtils.ApiKey) + "&language=en"; + url = string.Format(CultureInfo.InvariantCulture, GetTvInfo3, id, TmdbUtils.ApiKey) + "&language=en"; if (!string.IsNullOrEmpty(language)) { @@ -454,7 +454,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV mainRequestMessage.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue(header)); } - using var response = await TmdbMovieProvider.Current.GetMovieDbResponse(requestMessage); + using var response = await TmdbMovieProvider.Current.GetMovieDbResponse(requestMessage, cancellationToken).ConfigureAwait(false); await using var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false); var englishResult = await _jsonSerializer.DeserializeFromStreamAsync(stream).ConfigureAwait(false); @@ -504,7 +504,9 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV private async Task FindByExternalId(string id, string externalSource, CancellationToken cancellationToken) { - var url = string.Format(TmdbUtils.BaseTmdbApiUrl + @"3/find/{0}?api_key={1}&external_source={2}", + var url = string.Format( + CultureInfo.InvariantCulture, + TmdbUtils.BaseTmdbApiUrl + @"3/find/{0}?api_key={1}&external_source={2}", id, TmdbUtils.ApiKey, externalSource); @@ -515,7 +517,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV requestMessage.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue(header)); } - using var response = await TmdbMovieProvider.Current.GetMovieDbResponse(requestMessage); + using var response = await TmdbMovieProvider.Current.GetMovieDbResponse(requestMessage, cancellationToken).ConfigureAwait(false); await using var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false); var result = await _jsonSerializer.DeserializeFromStreamAsync(stream).ConfigureAwait(false); diff --git a/MediaBrowser.Providers/TV/DummySeasonProvider.cs b/MediaBrowser.Providers/TV/DummySeasonProvider.cs index 0c09cdef6c..a0f7f6cfd7 100644 --- a/MediaBrowser.Providers/TV/DummySeasonProvider.cs +++ b/MediaBrowser.Providers/TV/DummySeasonProvider.cs @@ -124,7 +124,8 @@ namespace MediaBrowser.Providers.TV /// /// Adds the season. /// - public async Task AddSeason(Series series, + public async Task AddSeason( + Series series, int? seasonNumber, bool isVirtualItem, CancellationToken cancellationToken) @@ -211,11 +212,14 @@ namespace MediaBrowser.Providers.TV { _logger.LogInformation("Removing virtual season {0} {1}", series.Name, seasonToRemove.IndexNumber); - _libraryManager.DeleteItem(seasonToRemove, new DeleteOptions - { - DeleteFileLocation = true + _libraryManager.DeleteItem( + seasonToRemove, + new DeleteOptions + { + DeleteFileLocation = true - }, false); + }, + false); hasChanges = true; } diff --git a/MediaBrowser.Providers/TV/MissingEpisodeProvider.cs b/MediaBrowser.Providers/TV/MissingEpisodeProvider.cs index 09850beb0d..616c61ec0a 100644 --- a/MediaBrowser.Providers/TV/MissingEpisodeProvider.cs +++ b/MediaBrowser.Providers/TV/MissingEpisodeProvider.cs @@ -159,7 +159,7 @@ namespace MediaBrowser.Providers.TV var now = DateTime.UtcNow.AddDays(-UnairedEpisodeThresholdDays); - if (airDate < now && addMissingEpisodes || airDate > now) + if ((airDate < now && addMissingEpisodes) || airDate > now) { // tvdb has a lot of nearly blank episodes _logger.LogInformation("Creating virtual missing/unaired episode {0} {1}x{2}", series.Name, tuple.seasonNumber, tuple.episodenumber); @@ -232,10 +232,13 @@ namespace MediaBrowser.Providers.TV foreach (var episodeToRemove in episodesToRemove) { - _libraryManager.DeleteItem(episodeToRemove, new DeleteOptions - { - DeleteFileLocation = true - }, false); + _libraryManager.DeleteItem( + episodeToRemove, + new DeleteOptions + { + DeleteFileLocation = true + }, + false); hasChanges = true; } @@ -246,7 +249,7 @@ namespace MediaBrowser.Providers.TV /// /// Removes the obsolete or missing seasons. /// - /// + /// All recursive children. /// The episode lookup. /// . private bool RemoveObsoleteOrMissingSeasons( @@ -297,10 +300,13 @@ namespace MediaBrowser.Providers.TV foreach (var seasonToRemove in seasonsToRemove) { - _libraryManager.DeleteItem(seasonToRemove, new DeleteOptions - { - DeleteFileLocation = true - }, false); + _libraryManager.DeleteItem( + seasonToRemove, + new DeleteOptions + { + DeleteFileLocation = true + }, + false); hasChanges = true; } @@ -354,7 +360,10 @@ namespace MediaBrowser.Providers.TV /// /// /// Episode. - private Episode GetExistingEpisode(IEnumerable existingEpisodes, IReadOnlyDictionary seasonCounts, (int seasonNumber, int episodeNumber, DateTime firstAired) episodeTuple) + private Episode GetExistingEpisode( + IEnumerable existingEpisodes, + IReadOnlyDictionary seasonCounts, + (int seasonNumber, int episodeNumber, DateTime firstAired) episodeTuple) { var seasonNumber = episodeTuple.seasonNumber; var episodeNumber = episodeTuple.episodeNumber; diff --git a/MediaBrowser.Providers/TV/TvExternalIds.cs b/MediaBrowser.Providers/TV/TvExternalIds.cs deleted file mode 100644 index a6040edd15..0000000000 --- a/MediaBrowser.Providers/TV/TvExternalIds.cs +++ /dev/null @@ -1,82 +0,0 @@ -#pragma warning disable CS1591 - -using MediaBrowser.Controller.Entities.TV; -using MediaBrowser.Controller.Providers; -using MediaBrowser.Model.Entities; -using MediaBrowser.Model.Providers; -using MediaBrowser.Providers.Plugins.TheTvdb; - -namespace MediaBrowser.Providers.TV -{ - public class Zap2ItExternalId : IExternalId - { - /// - public string ProviderName => "Zap2It"; - - /// - public string Key => MetadataProvider.Zap2It.ToString(); - - /// - public ExternalIdMediaType? Type => null; - - /// - public string UrlFormatString => "http://tvlistings.zap2it.com/overview.html?programSeriesId={0}"; - - /// - public bool Supports(IHasProviderIds item) => item is Series; - } - - public class TvdbExternalId : IExternalId - { - /// - public string ProviderName => "TheTVDB"; - - /// - public string Key => MetadataProvider.Tvdb.ToString(); - - /// - public ExternalIdMediaType? Type => null; - - /// - public string UrlFormatString => TvdbUtils.TvdbBaseUrl + "?tab=series&id={0}"; - - /// - public bool Supports(IHasProviderIds item) => item is Series; - } - - public class TvdbSeasonExternalId : IExternalId - { - /// - public string ProviderName => "TheTVDB"; - - /// - public string Key => MetadataProvider.Tvdb.ToString(); - - /// - public ExternalIdMediaType? Type => ExternalIdMediaType.Season; - - /// - public string UrlFormatString => null; - - /// - public bool Supports(IHasProviderIds item) => item is Season; - } - - public class TvdbEpisodeExternalId : IExternalId - { - /// - public string ProviderName => "TheTVDB"; - - /// - public string Key => MetadataProvider.Tvdb.ToString(); - - /// - public ExternalIdMediaType? Type => ExternalIdMediaType.Episode; - - /// - public string UrlFormatString => TvdbUtils.TvdbBaseUrl + "?tab=episode&id={0}"; - - /// - public bool Supports(IHasProviderIds item) => item is Episode; - } -} diff --git a/MediaBrowser.Providers/TV/TvdbEpisodeExternalId.cs b/MediaBrowser.Providers/TV/TvdbEpisodeExternalId.cs new file mode 100644 index 0000000000..40c5f2d785 --- /dev/null +++ b/MediaBrowser.Providers/TV/TvdbEpisodeExternalId.cs @@ -0,0 +1,28 @@ +#pragma warning disable CS1591 + +using MediaBrowser.Controller.Entities.TV; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Providers; +using MediaBrowser.Providers.Plugins.TheTvdb; + +namespace MediaBrowser.Providers.TV +{ + public class TvdbEpisodeExternalId : IExternalId + { + /// + public string ProviderName => "TheTVDB"; + + /// + public string Key => MetadataProvider.Tvdb.ToString(); + + /// + public ExternalIdMediaType? Type => ExternalIdMediaType.Episode; + + /// + public string UrlFormatString => TvdbUtils.TvdbBaseUrl + "?tab=episode&id={0}"; + + /// + public bool Supports(IHasProviderIds item) => item is Episode; + } +} diff --git a/MediaBrowser.Providers/TV/TvdbExternalId.cs b/MediaBrowser.Providers/TV/TvdbExternalId.cs new file mode 100644 index 0000000000..4c54de9f82 --- /dev/null +++ b/MediaBrowser.Providers/TV/TvdbExternalId.cs @@ -0,0 +1,28 @@ +#pragma warning disable CS1591 + +using MediaBrowser.Controller.Entities.TV; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Providers; +using MediaBrowser.Providers.Plugins.TheTvdb; + +namespace MediaBrowser.Providers.TV +{ + public class TvdbExternalId : IExternalId + { + /// + public string ProviderName => "TheTVDB"; + + /// + public string Key => MetadataProvider.Tvdb.ToString(); + + /// + public ExternalIdMediaType? Type => null; + + /// + public string UrlFormatString => TvdbUtils.TvdbBaseUrl + "?tab=series&id={0}"; + + /// + public bool Supports(IHasProviderIds item) => item is Series; + } +} diff --git a/MediaBrowser.Providers/TV/TvdbSeasonExternalId.cs b/MediaBrowser.Providers/TV/TvdbSeasonExternalId.cs new file mode 100644 index 0000000000..807ebb3eee --- /dev/null +++ b/MediaBrowser.Providers/TV/TvdbSeasonExternalId.cs @@ -0,0 +1,28 @@ +#pragma warning disable CS1591 + +using MediaBrowser.Controller.Entities.TV; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Providers; +using MediaBrowser.Providers.Plugins.TheTvdb; + +namespace MediaBrowser.Providers.TV +{ + public class TvdbSeasonExternalId : IExternalId + { + /// + public string ProviderName => "TheTVDB"; + + /// + public string Key => MetadataProvider.Tvdb.ToString(); + + /// + public ExternalIdMediaType? Type => ExternalIdMediaType.Season; + + /// + public string UrlFormatString => null; + + /// + public bool Supports(IHasProviderIds item) => item is Season; + } +} diff --git a/MediaBrowser.Providers/TV/Zap2ItExternalId.cs b/MediaBrowser.Providers/TV/Zap2ItExternalId.cs new file mode 100644 index 0000000000..c9f314af94 --- /dev/null +++ b/MediaBrowser.Providers/TV/Zap2ItExternalId.cs @@ -0,0 +1,28 @@ +#pragma warning disable CS1591 + +using MediaBrowser.Controller.Entities.TV; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Providers; +using MediaBrowser.Providers.Plugins.TheTvdb; + +namespace MediaBrowser.Providers.TV +{ + public class Zap2ItExternalId : IExternalId + { + /// + public string ProviderName => "Zap2It"; + + /// + public string Key => MetadataProvider.Zap2It.ToString(); + + /// + public ExternalIdMediaType? Type => null; + + /// + public string UrlFormatString => "http://tvlistings.zap2it.com/overview.html?programSeriesId={0}"; + + /// + public bool Supports(IHasProviderIds item) => item is Series; + } +} diff --git a/MediaBrowser.sln b/MediaBrowser.sln index fd45dca2af..25402aee13 100644 --- a/MediaBrowser.sln +++ b/MediaBrowser.sln @@ -208,6 +208,5 @@ Global {A2FD0A10-8F62-4F9D-B171-FFDF9F0AFA9D} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6} {2E3A1B4B-4225-4AAA-8B29-0181A84E7AEE} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6} {462584F7-5023-4019-9EAC-B98CA458C0A0} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6} - {7C93C84F-105C-48E5-A878-406FA0A5B296} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6} EndGlobalSection EndGlobal