diff options
Diffstat (limited to 'Emby.Server.Implementations')
38 files changed, 335 insertions, 584 deletions
diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index e7efc81d7..82294644b 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -44,7 +44,6 @@ using Emby.Server.Implementations.Serialization; using Emby.Server.Implementations.Session; using Emby.Server.Implementations.SyncPlay; using Emby.Server.Implementations.TV; -using Emby.Server.Implementations.Udp; using Emby.Server.Implementations.Updates; using Jellyfin.Api.Helpers; using Jellyfin.MediaEncoding.Hls.Playlist; @@ -104,6 +103,7 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Prometheus.DotNetRuntime; +using static MediaBrowser.Controller.Extensions.ConfigurationExtensions; using WebSocketManager = Emby.Server.Implementations.HttpServer.WebSocketManager; namespace Emby.Server.Implementations @@ -150,7 +150,7 @@ namespace Emby.Server.Implementations /// <param name="loggerFactory">Instance of the <see cref="ILoggerFactory"/> interface.</param> /// <param name="options">Instance of the <see cref="IStartupOptions"/> interface.</param> /// <param name="startupConfig">The <see cref="IConfiguration" /> interface.</param> - public ApplicationHost( + protected ApplicationHost( IServerApplicationPaths applicationPaths, ILoggerFactory loggerFactory, IStartupOptions options, @@ -185,6 +185,11 @@ namespace Emby.Server.Implementations public event EventHandler HasPendingRestartChanged; /// <summary> + /// Gets the value of the PublishedServerUrl setting. + /// </summary> + private string PublishedServerUrl => _startupConfig[AddressOverrideKey]; + + /// <summary> /// Gets a value indicating whether this instance can self restart. /// </summary> public bool CanSelfRestart => _startupOptions.RestartPath != null; @@ -260,11 +265,6 @@ namespace Emby.Server.Implementations /// </summary> public int HttpsPort { get; private set; } - /// <summary> - /// Gets the value of the PublishedServerUrl setting. - /// </summary> - public string PublishedServerUrl => _startupOptions.PublishedServerUrl ?? _startupConfig[UdpServer.AddressOverrideConfigKey]; - /// <inheritdoc /> public Version ApplicationVersion { get; } diff --git a/Emby.Server.Implementations/Channels/ChannelManager.cs b/Emby.Server.Implementations/Channels/ChannelManager.cs index a107e7a52..09429c73f 100644 --- a/Emby.Server.Implementations/Channels/ChannelManager.cs +++ b/Emby.Server.Implementations/Channels/ChannelManager.cs @@ -39,7 +39,7 @@ namespace Emby.Server.Implementations.Channels /// <summary> /// The LiveTV channel manager. /// </summary> - public class ChannelManager : IChannelManager + public class ChannelManager : IChannelManager, IDisposable { private readonly IUserManager _userManager; private readonly IUserDataManager _userDataManager; @@ -52,6 +52,7 @@ namespace Emby.Server.Implementations.Channels private readonly IMemoryCache _memoryCache; private readonly SemaphoreSlim _resourcePool = new SemaphoreSlim(1, 1); private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.Options; + private bool _disposed = false; /// <summary> /// Initializes a new instance of the <see cref="ChannelManager"/> class. @@ -1213,5 +1214,31 @@ namespace Emby.Server.Implementations.Channels return result; } + + /// <inheritdoc /> + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + /// <summary> + /// Releases unmanaged and optionally managed resources. + /// </summary> + /// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param> + protected virtual void Dispose(bool disposing) + { + if (_disposed) + { + return; + } + + if (disposing) + { + _resourcePool?.Dispose(); + } + + _disposed = true; + } } } diff --git a/Emby.Server.Implementations/Channels/RefreshChannelsScheduledTask.cs b/Emby.Server.Implementations/Channels/RefreshChannelsScheduledTask.cs index e5dde48d8..cfd08e653 100644 --- a/Emby.Server.Implementations/Channels/RefreshChannelsScheduledTask.cs +++ b/Emby.Server.Implementations/Channels/RefreshChannelsScheduledTask.cs @@ -62,7 +62,7 @@ namespace Emby.Server.Implementations.Channels public string Key => "RefreshInternetChannels"; /// <inheritdoc /> - public async Task Execute(CancellationToken cancellationToken, IProgress<double> progress) + public async Task ExecuteAsync(IProgress<double> progress, CancellationToken cancellationToken) { var manager = (ChannelManager)_channelManager; diff --git a/Emby.Server.Implementations/Data/SqliteItemRepository.cs b/Emby.Server.Implementations/Data/SqliteItemRepository.cs index 1da9b4650..b3b383bfd 100644 --- a/Emby.Server.Implementations/Data/SqliteItemRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteItemRepository.cs @@ -317,11 +317,6 @@ namespace Emby.Server.Implementations.Data IImageProcessor imageProcessor) : base(logger) { - if (config == null) - { - throw new ArgumentNullException(nameof(config)); - } - _config = config; _appHost = appHost; _localization = localization; @@ -334,9 +329,6 @@ namespace Emby.Server.Implementations.Data } /// <inheritdoc /> - public string Name => "SQLite"; - - /// <inheritdoc /> protected override int? CacheSize => 20000; /// <inheritdoc /> @@ -573,22 +565,6 @@ namespace Emby.Server.Implementations.Data userDataRepo.Initialize(userManager, WriteLock, WriteConnection); } - /// <summary> - /// Save a standard item in the repo. - /// </summary> - /// <param name="item">The item.</param> - /// <param name="cancellationToken">The cancellation token.</param> - /// <exception cref="ArgumentNullException"><paramref name="item"/> is <c>null</c>.</exception> - public void SaveItem(BaseItem item, CancellationToken cancellationToken) - { - if (item == null) - { - throw new ArgumentNullException(nameof(item)); - } - - SaveItems(new[] { item }, cancellationToken); - } - public void SaveImages(BaseItem item) { if (item == null) @@ -605,7 +581,7 @@ namespace Emby.Server.Implementations.Data { using (var saveImagesStatement = PrepareStatement(db, "Update TypedBaseItems set Images=@Images where guid=@Id")) { - saveImagesStatement.TryBind("@Id", item.Id.ToByteArray()); + saveImagesStatement.TryBind("@Id", item.Id); saveImagesStatement.TryBind("@Images", SerializeImages(item.ImageInfos)); saveImagesStatement.MoveNext(); @@ -750,7 +726,7 @@ namespace Emby.Server.Implementations.Data saveItemStatement.TryBindNull("@EndDate"); } - saveItemStatement.TryBind("@ChannelId", item.ChannelId.Equals(Guid.Empty) ? null : item.ChannelId.ToString("N", CultureInfo.InvariantCulture)); + saveItemStatement.TryBind("@ChannelId", item.ChannelId.Equals(default) ? null : item.ChannelId.ToString("N", CultureInfo.InvariantCulture)); if (item is IHasProgramAttributes hasProgramAttributes) { @@ -780,7 +756,7 @@ namespace Emby.Server.Implementations.Data saveItemStatement.TryBind("@ProductionYear", item.ProductionYear); var parentId = item.ParentId; - if (parentId.Equals(Guid.Empty)) + if (parentId.Equals(default)) { saveItemStatement.TryBindNull("@ParentId"); } @@ -975,7 +951,7 @@ namespace Emby.Server.Implementations.Data { saveItemStatement.TryBind("@SeasonName", episode.SeasonName); - var nullableSeasonId = episode.SeasonId == Guid.Empty ? (Guid?)null : episode.SeasonId; + var nullableSeasonId = episode.SeasonId.Equals(default) ? (Guid?)null : episode.SeasonId; saveItemStatement.TryBind("@SeasonId", nullableSeasonId); } @@ -987,7 +963,7 @@ namespace Emby.Server.Implementations.Data if (item is IHasSeries hasSeries) { - var nullableSeriesId = hasSeries.SeriesId.Equals(Guid.Empty) ? (Guid?)null : hasSeries.SeriesId; + var nullableSeriesId = hasSeries.SeriesId.Equals(default) ? (Guid?)null : hasSeries.SeriesId; saveItemStatement.TryBind("@SeriesId", nullableSeriesId); saveItemStatement.TryBind("@SeriesPresentationUniqueKey", hasSeries.SeriesPresentationUniqueKey); @@ -1060,7 +1036,7 @@ namespace Emby.Server.Implementations.Data } Guid ownerId = item.OwnerId; - if (ownerId == Guid.Empty) + if (ownerId.Equals(default)) { saveItemStatement.TryBindNull("@OwnerId"); } @@ -1198,13 +1174,15 @@ namespace Emby.Server.Implementations.Data bldr.Append(Delimiter) // Replace delimiters with other characters. // This can be removed when we migrate to a proper DB. - .Append(hash.Replace('*', '/').Replace('|', '\\')); + .Append(hash.Replace(Delimiter, '/').Replace('|', '\\')); } } internal ItemImageInfo ItemImageInfoFromValueString(ReadOnlySpan<char> value) { - var nextSegment = value.IndexOf('*'); + const char Delimiter = '*'; + + var nextSegment = value.IndexOf(Delimiter); if (nextSegment == -1) { return null; @@ -1212,7 +1190,7 @@ namespace Emby.Server.Implementations.Data ReadOnlySpan<char> path = value[..nextSegment]; value = value[(nextSegment + 1)..]; - nextSegment = value.IndexOf('*'); + nextSegment = value.IndexOf(Delimiter); if (nextSegment == -1) { return null; @@ -1220,7 +1198,7 @@ namespace Emby.Server.Implementations.Data ReadOnlySpan<char> dateModified = value[..nextSegment]; value = value[(nextSegment + 1)..]; - nextSegment = value.IndexOf('*'); + nextSegment = value.IndexOf(Delimiter); if (nextSegment == -1) { nextSegment = value.Length; @@ -1257,7 +1235,7 @@ namespace Emby.Server.Implementations.Data if (nextSegment + 1 < value.Length - 1) { value = value[(nextSegment + 1)..]; - nextSegment = value.IndexOf('*'); + nextSegment = value.IndexOf(Delimiter); if (nextSegment == -1 || nextSegment == value.Length) { return image; @@ -1266,7 +1244,7 @@ namespace Emby.Server.Implementations.Data ReadOnlySpan<char> widthSpan = value[..nextSegment]; value = value[(nextSegment + 1)..]; - nextSegment = value.IndexOf('*'); + nextSegment = value.IndexOf(Delimiter); if (nextSegment == -1) { nextSegment = value.Length; @@ -1292,7 +1270,7 @@ namespace Emby.Server.Implementations.Data var c = value[i]; blurHashSpan[i] = c switch { - '/' => '*', + '/' => Delimiter, '\\' => '|', _ => c }; @@ -1314,7 +1292,7 @@ namespace Emby.Server.Implementations.Data /// <exception cref="ArgumentException"><paramr name="id"/> is <seealso cref="Guid.Empty"/>.</exception> public BaseItem RetrieveItem(Guid id) { - if (id == Guid.Empty) + if (id.Equals(default)) { throw new ArgumentException("Guid can't be empty", nameof(id)); } @@ -2086,7 +2064,7 @@ namespace Emby.Server.Implementations.Data { CheckDisposed(); - if (id.Equals(Guid.Empty)) + if (id.Equals(default)) { throw new ArgumentNullException(nameof(id)); } @@ -2492,12 +2470,12 @@ namespace Emby.Server.Implementations.Data searchTerm = GetCleanValue(searchTerm); var commandText = statement.SQL; - if (commandText.IndexOf("@SearchTermStartsWith", StringComparison.OrdinalIgnoreCase) != -1) + if (commandText.Contains("@SearchTermStartsWith", StringComparison.OrdinalIgnoreCase)) { statement.TryBind("@SearchTermStartsWith", searchTerm + "%"); } - if (commandText.IndexOf("@SearchTermContains", StringComparison.OrdinalIgnoreCase) != -1) + if (commandText.Contains("@SearchTermContains", StringComparison.OrdinalIgnoreCase)) { statement.TryBind("@SearchTermContains", "%" + searchTerm + "%"); } @@ -2514,17 +2492,17 @@ namespace Emby.Server.Implementations.Data var commandText = statement.SQL; - if (commandText.IndexOf("@ItemOfficialRating", StringComparison.OrdinalIgnoreCase) != -1) + if (commandText.Contains("@ItemOfficialRating", StringComparison.OrdinalIgnoreCase)) { statement.TryBind("@ItemOfficialRating", item.OfficialRating); } - if (commandText.IndexOf("@ItemProductionYear", StringComparison.OrdinalIgnoreCase) != -1) + if (commandText.Contains("@ItemProductionYear", StringComparison.OrdinalIgnoreCase)) { statement.TryBind("@ItemProductionYear", item.ProductionYear ?? 0); } - if (commandText.IndexOf("@SimilarItemId", StringComparison.OrdinalIgnoreCase) != -1) + if (commandText.Contains("@SimilarItemId", StringComparison.OrdinalIgnoreCase)) { statement.TryBind("@SimilarItemId", item.Id); } @@ -2758,12 +2736,12 @@ namespace Emby.Server.Implementations.Data foreach (var providerId in newItem.ProviderIds) { - if (providerId.Key == MetadataProvider.TmdbCollection.ToString()) + if (string.Equals(providerId.Key, nameof(MetadataProvider.TmdbCollection), StringComparison.Ordinal)) { continue; } - if (item.GetProviderId(providerId.Key) == providerId.Value) + if (string.Equals(item.GetProviderId(providerId.Key), providerId.Value, StringComparison.Ordinal)) { if (newItem.SourceType == SourceType.Library) { @@ -3185,220 +3163,6 @@ namespace Emby.Server.Implementations.Data return list; } - public List<Tuple<Guid, string>> GetItemIdsWithPath(InternalItemsQuery query) - { - if (query == null) - { - throw new ArgumentNullException(nameof(query)); - } - - CheckDisposed(); - - var now = DateTime.UtcNow; - - var columns = new List<string> { "guid", "path" }; - SetFinalColumnsToSelect(query, columns); - var commandText = "select " + string.Join(',', columns) + FromText; - - var whereClauses = GetWhereClauses(query, null); - if (whereClauses.Count != 0) - { - commandText += " where " + string.Join(" AND ", whereClauses); - } - - commandText += GetGroupBy(query) - + GetOrderByText(query); - - if (query.Limit.HasValue || query.StartIndex.HasValue) - { - var offset = query.StartIndex ?? 0; - - if (query.Limit.HasValue || offset > 0) - { - commandText += " LIMIT " + (query.Limit ?? int.MaxValue).ToString(CultureInfo.InvariantCulture); - } - - if (offset > 0) - { - commandText += " OFFSET " + offset.ToString(CultureInfo.InvariantCulture); - } - } - - var list = new List<Tuple<Guid, string>>(); - using (var connection = GetConnection(true)) - { - using (var statement = PrepareStatement(connection, commandText)) - { - if (EnableJoinUserData(query)) - { - statement.TryBind("@UserId", query.User.InternalId); - } - - // Running this again will bind the params - GetWhereClauses(query, statement); - - foreach (var row in statement.ExecuteQuery()) - { - var id = row.GetGuid(0); - - row.TryGetString(1, out var path); - - list.Add(new Tuple<Guid, string>(id, path)); - } - } - } - - LogQueryTime("GetItemIdsWithPath", commandText, now); - - return list; - } - - public QueryResult<Guid> GetItemIds(InternalItemsQuery query) - { - if (query == null) - { - throw new ArgumentNullException(nameof(query)); - } - - CheckDisposed(); - - if (!query.EnableTotalRecordCount || (!query.Limit.HasValue && (query.StartIndex ?? 0) == 0)) - { - var returnList = GetItemIdsList(query); - return new QueryResult<Guid>( - query.StartIndex, - returnList.Count, - returnList); - } - - var now = DateTime.UtcNow; - - var columns = new List<string> { "guid" }; - SetFinalColumnsToSelect(query, columns); - var commandText = "select " - + string.Join(',', columns) - + FromText - + GetJoinUserDataText(query); - - var whereClauses = GetWhereClauses(query, null); - - var whereText = whereClauses.Count == 0 ? - string.Empty : - " where " + string.Join(" AND ", whereClauses); - - commandText += whereText - + GetGroupBy(query) - + GetOrderByText(query); - - if (query.Limit.HasValue || query.StartIndex.HasValue) - { - var offset = query.StartIndex ?? 0; - - if (query.Limit.HasValue || offset > 0) - { - commandText += " LIMIT " + (query.Limit ?? int.MaxValue).ToString(CultureInfo.InvariantCulture); - } - - if (offset > 0) - { - commandText += " OFFSET " + offset.ToString(CultureInfo.InvariantCulture); - } - } - - var isReturningZeroItems = query.Limit.HasValue && query.Limit <= 0; - - var statementTexts = new List<string>(); - if (!isReturningZeroItems) - { - statementTexts.Add(commandText); - } - - if (query.EnableTotalRecordCount) - { - commandText = string.Empty; - - List<string> columnsToSelect; - if (EnableGroupByPresentationUniqueKey(query)) - { - columnsToSelect = new List<string> { "count (distinct PresentationUniqueKey)" }; - } - else if (query.GroupBySeriesPresentationUniqueKey) - { - columnsToSelect = new List<string> { "count (distinct SeriesPresentationUniqueKey)" }; - } - else - { - columnsToSelect = new List<string> { "count (guid)" }; - } - - SetFinalColumnsToSelect(query, columnsToSelect); - commandText += " select " + string.Join(',', columnsToSelect) + FromText; - - commandText += GetJoinUserDataText(query) - + whereText; - statementTexts.Add(commandText); - } - - var list = new List<Guid>(); - var result = new QueryResult<Guid>(); - using (var connection = GetConnection(true)) - { - connection.RunInTransaction( - db => - { - var statements = PrepareAll(db, statementTexts); - - if (!isReturningZeroItems) - { - using (var statement = statements[0]) - { - if (EnableJoinUserData(query)) - { - statement.TryBind("@UserId", query.User.InternalId); - } - - BindSimilarParams(query, statement); - BindSearchParams(query, statement); - - // Running this again will bind the params - GetWhereClauses(query, statement); - - foreach (var row in statement.ExecuteQuery()) - { - list.Add(row[0].ReadGuidFromBlob()); - } - } - } - - if (query.EnableTotalRecordCount) - { - using (var statement = statements[statements.Length - 1]) - { - if (EnableJoinUserData(query)) - { - statement.TryBind("@UserId", query.User.InternalId); - } - - BindSimilarParams(query, statement); - BindSearchParams(query, statement); - - // Running this again will bind the params - GetWhereClauses(query, statement); - - result.TotalRecordCount = statement.ExecuteQuery().SelectScalarInt().First(); - } - } - }, - ReadTransactionMode); - } - - LogQueryTime("GetItemIds", commandText, now); - - result.StartIndex = query.StartIndex ?? 0; - result.Items = list; - return result; - } - private bool IsAlphaNumeric(string str) { if (string.IsNullOrWhiteSpace(str)) @@ -3665,7 +3429,7 @@ namespace Emby.Server.Implementations.Data whereClauses.Add($"ChannelId in ({inClause})"); } - if (!query.ParentId.Equals(Guid.Empty)) + if (!query.ParentId.Equals(default)) { whereClauses.Add("ParentId=@ParentId"); statement?.TryBind("@ParentId", query.ParentId); @@ -4025,7 +3789,7 @@ namespace Emby.Server.Implementations.Data clauses.Add("(guid in (select itemid from itemvalues where CleanValue = (select CleanName from TypedBaseItems where guid=" + paramName + ") and Type<=1))"); if (statement != null) { - statement.TryBind(paramName, artistId.ToByteArray()); + statement.TryBind(paramName, artistId); } index++; @@ -4046,7 +3810,7 @@ namespace Emby.Server.Implementations.Data clauses.Add("(guid in (select itemid from itemvalues where CleanValue = (select CleanName from TypedBaseItems where guid=" + paramName + ") and Type=1))"); if (statement != null) { - statement.TryBind(paramName, artistId.ToByteArray()); + statement.TryBind(paramName, artistId); } index++; @@ -4067,7 +3831,7 @@ namespace Emby.Server.Implementations.Data clauses.Add("((select CleanName from TypedBaseItems where guid=" + paramName + ") in (select CleanValue from itemvalues where ItemId=Guid and Type=0) AND (select CleanName from TypedBaseItems where guid=" + paramName + ") not in (select CleanValue from itemvalues where ItemId=Guid and Type=1))"); if (statement != null) { - statement.TryBind(paramName, artistId.ToByteArray()); + statement.TryBind(paramName, artistId); } index++; @@ -4088,7 +3852,7 @@ namespace Emby.Server.Implementations.Data clauses.Add("Album in (select Name from typedbaseitems where guid=" + paramName + ")"); if (statement != null) { - statement.TryBind(paramName, albumId.ToByteArray()); + statement.TryBind(paramName, albumId); } index++; @@ -4109,7 +3873,7 @@ namespace Emby.Server.Implementations.Data clauses.Add("(guid not in (select itemid from itemvalues where CleanValue = (select CleanName from TypedBaseItems where guid=" + paramName + ") and Type<=1))"); if (statement != null) { - statement.TryBind(paramName, artistId.ToByteArray()); + statement.TryBind(paramName, artistId); } index++; @@ -4130,7 +3894,7 @@ namespace Emby.Server.Implementations.Data clauses.Add("(guid in (select itemid from itemvalues where CleanValue = (select CleanName from TypedBaseItems where guid=" + paramName + ") and Type=2))"); if (statement != null) { - statement.TryBind(paramName, genreId.ToByteArray()); + statement.TryBind(paramName, genreId); } index++; @@ -4209,7 +3973,7 @@ namespace Emby.Server.Implementations.Data if (statement != null) { - statement.TryBind(paramName, studioId.ToByteArray()); + statement.TryBind(paramName, studioId); } index++; @@ -4494,7 +4258,7 @@ namespace Emby.Server.Implementations.Data var index = 0; foreach (var pair in query.ExcludeProviderIds) { - if (string.Equals(pair.Key, MetadataProvider.TmdbCollection.ToString(), StringComparison.OrdinalIgnoreCase)) + if (string.Equals(pair.Key, nameof(MetadataProvider.TmdbCollection), StringComparison.OrdinalIgnoreCase)) { continue; } @@ -4524,7 +4288,7 @@ namespace Emby.Server.Implementations.Data var index = 0; foreach (var pair in query.HasAnyProviderId) { - if (string.Equals(pair.Key, MetadataProvider.TmdbCollection.ToString(), StringComparison.OrdinalIgnoreCase)) + if (string.Equals(pair.Key, nameof(MetadataProvider.TmdbCollection), StringComparison.OrdinalIgnoreCase)) { continue; } @@ -4942,7 +4706,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type public void DeleteItem(Guid id) { - if (id == Guid.Empty) + if (id.Equals(default)) { throw new ArgumentNullException(nameof(id)); } @@ -4954,7 +4718,8 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type connection.RunInTransaction( db => { - var idBlob = id.ToByteArray(); + Span<byte> idBlob = stackalloc byte[16]; + id.TryWriteBytes(idBlob); // Delete people ExecuteWithSingleParam(db, "delete from People where ItemId=@Id", idBlob); @@ -5003,7 +4768,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type if (whereClauses.Count != 0) { - commandText.Append(" where ").Append(string.Join(" AND ", whereClauses)); + commandText.Append(" where ").AppendJoin(" AND ", whereClauses); } commandText.Append(" order by ListOrder"); @@ -5089,16 +4854,16 @@ AND Type = @InternalPersonType)"); statement?.TryBind("@InternalPersonType", typeof(Person).FullName); } - if (!query.ItemId.Equals(Guid.Empty)) + if (!query.ItemId.Equals(default)) { whereClauses.Add("ItemId=@ItemId"); - statement?.TryBind("@ItemId", query.ItemId.ToByteArray()); + statement?.TryBind("@ItemId", query.ItemId); } - if (!query.AppearsInItemId.Equals(Guid.Empty)) + if (!query.AppearsInItemId.Equals(default)) { whereClauses.Add("p.Name in (Select Name from People where ItemId=@AppearsInItemId)"); - statement?.TryBind("@AppearsInItemId", query.AppearsInItemId.ToByteArray()); + statement?.TryBind("@AppearsInItemId", query.AppearsInItemId); } var queryPersonTypes = query.PersonTypes.Where(IsValidPersonType).ToList(); @@ -5151,7 +4916,7 @@ AND Type = @InternalPersonType)"); private void UpdateAncestors(Guid itemId, List<Guid> ancestorIds, IDatabaseConnection db, IStatement deleteAncestorsStatement) { - if (itemId.Equals(Guid.Empty)) + if (itemId.Equals(default)) { throw new ArgumentNullException(nameof(itemId)); } @@ -5683,7 +5448,7 @@ AND Type = @InternalPersonType)"); private void UpdateItemValues(Guid itemId, List<(int MagicNumber, string Value)> values, IDatabaseConnection db) { - if (itemId.Equals(Guid.Empty)) + if (itemId.Equals(default)) { throw new ArgumentNullException(nameof(itemId)); } @@ -5759,7 +5524,7 @@ AND Type = @InternalPersonType)"); public void UpdatePeople(Guid itemId, List<PersonInfo> people) { - if (itemId.Equals(Guid.Empty)) + if (itemId.Equals(default)) { throw new ArgumentNullException(nameof(itemId)); } @@ -5892,7 +5657,7 @@ AND Type = @InternalPersonType)"); using (var statement = PrepareStatement(connection, cmdText)) { - statement.TryBind("@ItemId", query.ItemId.ToByteArray()); + statement.TryBind("@ItemId", query.ItemId); if (query.Type.HasValue) { @@ -5914,11 +5679,11 @@ AND Type = @InternalPersonType)"); } } - public void SaveMediaStreams(Guid id, List<MediaStream> streams, CancellationToken cancellationToken) + public void SaveMediaStreams(Guid id, IReadOnlyList<MediaStream> streams, CancellationToken cancellationToken) { CheckDisposed(); - if (id == Guid.Empty) + if (id.Equals(default)) { throw new ArgumentNullException(nameof(id)); } @@ -5946,7 +5711,7 @@ AND Type = @InternalPersonType)"); } } - private void InsertMediaStreams(byte[] idBlob, List<MediaStream> streams, IDatabaseConnection db) + private void InsertMediaStreams(byte[] idBlob, IReadOnlyList<MediaStream> streams, IDatabaseConnection db) { const int Limit = 10; var startIndex = 0; @@ -6254,7 +6019,7 @@ AND Type = @InternalPersonType)"); CancellationToken cancellationToken) { CheckDisposed(); - if (id == Guid.Empty) + if (id.Equals(default)) { throw new ArgumentException("Guid can't be empty.", nameof(id)); } diff --git a/Emby.Server.Implementations/Data/SqliteUserDataRepository.cs b/Emby.Server.Implementations/Data/SqliteUserDataRepository.cs index 80b8f9ebf..ba86dc156 100644 --- a/Emby.Server.Implementations/Data/SqliteUserDataRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteUserDataRepository.cs @@ -26,9 +26,6 @@ namespace Emby.Server.Implementations.Data DbFilePath = Path.Combine(appPaths.DataPath, "library.db"); } - /// <inheritdoc /> - public string Name => "SQLite"; - /// <summary> /// Opens the connection to the database. /// </summary> @@ -102,7 +99,7 @@ namespace Emby.Server.Implementations.Data continue; } - statement.TryBind("@UserId", user.Id.ToByteArray()); + statement.TryBind("@UserId", user.Id); statement.TryBind("@InternalUserId", user.InternalId); statement.MoveNext(); @@ -390,6 +387,7 @@ namespace Emby.Server.Implementations.Data return userData; } +#pragma warning disable CA2215 /// <inheritdoc/> /// <remarks> /// There is nothing to dispose here since <see cref="BaseSqliteRepository.WriteLock"/> and @@ -398,6 +396,10 @@ namespace Emby.Server.Implementations.Data /// </remarks> protected override void Dispose(bool dispose) { + // The write lock and connection for the item repository are shared with the user data repository + // since they point to the same database. The item repo has responsibility for disposing these two objects, + // so the user data repo should not attempt to dispose them as well } +#pragma warning restore CA2215 } } diff --git a/Emby.Server.Implementations/Dto/DtoService.cs b/Emby.Server.Implementations/Dto/DtoService.cs index f5ca006dd..2b2190b16 100644 --- a/Emby.Server.Implementations/Dto/DtoService.cs +++ b/Emby.Server.Implementations/Dto/DtoService.cs @@ -21,7 +21,6 @@ using MediaBrowser.Controller.LiveTv; using MediaBrowser.Controller.Persistence; using MediaBrowser.Controller.Playlists; using MediaBrowser.Controller.Providers; -using MediaBrowser.Model.Drawing; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Querying; @@ -738,8 +737,7 @@ namespace Emby.Server.Implementations.Dto dto.Tags = item.Tags; } - var hasAspectRatio = item as IHasAspectRatio; - if (hasAspectRatio != null) + if (item is IHasAspectRatio hasAspectRatio) { dto.AspectRatio = hasAspectRatio.AspectRatio; } @@ -889,15 +887,13 @@ namespace Emby.Server.Implementations.Dto dto.CommunityRating = item.CommunityRating; } - var supportsPlaceHolders = item as ISupportsPlaceHolders; - if (supportsPlaceHolders != null && supportsPlaceHolders.IsPlaceHolder) + if (item is ISupportsPlaceHolders supportsPlaceHolders && supportsPlaceHolders.IsPlaceHolder) { dto.IsPlaceHolder = supportsPlaceHolders.IsPlaceHolder; } // Add audio info - var audio = item as Audio; - if (audio != null) + if (item is Audio audio) { dto.Album = audio.Album; if (audio.ExtraType.HasValue) @@ -970,8 +966,7 @@ namespace Emby.Server.Implementations.Dto }).Where(i => i != null).ToArray(); } - var hasAlbumArtist = item as IHasAlbumArtist; - if (hasAlbumArtist != null) + if (item is IHasAlbumArtist hasAlbumArtist) { dto.AlbumArtist = hasAlbumArtist.AlbumArtists.FirstOrDefault(); diff --git a/Emby.Server.Implementations/Emby.Server.Implementations.csproj b/Emby.Server.Implementations/Emby.Server.Implementations.csproj index d59db6aa7..a5cc125ec 100644 --- a/Emby.Server.Implementations/Emby.Server.Implementations.csproj +++ b/Emby.Server.Implementations/Emby.Server.Implementations.csproj @@ -26,12 +26,12 @@ <PackageReference Include="DiscUtils.Udf" Version="0.16.13" /> <PackageReference Include="Jellyfin.XmlTv" Version="10.6.2" /> <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="6.0.0" /> - <PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="6.0.0" /> + <PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="6.0.1" /> <PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="6.0.0" /> <PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="6.0.0" /> - <PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="6.0.1" /> + <PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="6.0.2" /> <PackageReference Include="Mono.Nat" Version="3.0.2" /> - <PackageReference Include="prometheus-net.DotNetRuntime" Version="4.2.2" /> + <PackageReference Include="prometheus-net.DotNetRuntime" Version="4.2.3" /> <PackageReference Include="sharpcompress" Version="0.30.1" /> <PackageReference Include="SQLitePCL.pretty.netstandard" Version="3.1.0" /> <PackageReference Include="DotNet.Glob" Version="3.1.3" /> diff --git a/Emby.Server.Implementations/EntryPoints/UdpServerEntryPoint.cs b/Emby.Server.Implementations/EntryPoints/UdpServerEntryPoint.cs index feaccf9fa..34fdfbe8d 100644 --- a/Emby.Server.Implementations/EntryPoints/UdpServerEntryPoint.cs +++ b/Emby.Server.Implementations/EntryPoints/UdpServerEntryPoint.cs @@ -3,6 +3,8 @@ using System.Net.Sockets; using System.Threading; using System.Threading.Tasks; using Emby.Server.Implementations.Udp; +using Jellyfin.Networking.Configuration; +using MediaBrowser.Common.Configuration; using MediaBrowser.Controller; using MediaBrowser.Controller.Plugins; using Microsoft.Extensions.Configuration; @@ -26,6 +28,7 @@ namespace Emby.Server.Implementations.EntryPoints private readonly ILogger<UdpServerEntryPoint> _logger; private readonly IServerApplicationHost _appHost; private readonly IConfiguration _config; + private readonly IConfigurationManager _configurationManager; /// <summary> /// The UDP server. @@ -40,14 +43,17 @@ namespace Emby.Server.Implementations.EntryPoints /// <param name="logger">Instance of the <see cref="ILogger{UdpServerEntryPoint}"/> interface.</param> /// <param name="appHost">Instance of the <see cref="IServerApplicationHost"/> interface.</param> /// <param name="configuration">Instance of the <see cref="IConfiguration"/> interface.</param> + /// <param name="configurationManager">Instance of the <see cref="IConfigurationManager"/> interface.</param> public UdpServerEntryPoint( ILogger<UdpServerEntryPoint> logger, IServerApplicationHost appHost, - IConfiguration configuration) + IConfiguration configuration, + IConfigurationManager configurationManager) { _logger = logger; _appHost = appHost; _config = configuration; + _configurationManager = configurationManager; } /// <inheritdoc /> @@ -55,6 +61,11 @@ namespace Emby.Server.Implementations.EntryPoints { CheckDisposed(); + if (_configurationManager.GetNetworkConfiguration().AutoDiscovery) + { + return Task.CompletedTask; + } + try { _udpServer = new UdpServer(_logger, _appHost, _config, PortNumber); diff --git a/Emby.Server.Implementations/IO/ManagedFileSystem.cs b/Emby.Server.Implementations/IO/ManagedFileSystem.cs index 5c86dbbb7..4f8a52f41 100644 --- a/Emby.Server.Implementations/IO/ManagedFileSystem.cs +++ b/Emby.Server.Implementations/IO/ManagedFileSystem.cs @@ -581,7 +581,7 @@ namespace Emby.Server.Implementations.IO } /// <inheritdoc /> - public virtual List<FileSystemMetadata> GetDrives() + public virtual IEnumerable<FileSystemMetadata> GetDrives() { // check for ready state to avoid waiting for drives to timeout // some drives on linux have no actual size or are used for other purposes @@ -595,7 +595,7 @@ namespace Emby.Server.Implementations.IO Name = d.Name, FullName = d.RootDirectory.FullName, IsDirectory = true - }).ToList(); + }); } /// <inheritdoc /> diff --git a/Emby.Server.Implementations/Images/BaseFolderImageProvider.cs b/Emby.Server.Implementations/Images/BaseFolderImageProvider.cs index 1c69056d2..6fc7f1ac3 100644 --- a/Emby.Server.Implementations/Images/BaseFolderImageProvider.cs +++ b/Emby.Server.Implementations/Images/BaseFolderImageProvider.cs @@ -22,7 +22,7 @@ namespace Emby.Server.Implementations.Images { private readonly ILibraryManager _libraryManager; - public BaseFolderImageProvider(IFileSystem fileSystem, IProviderManager providerManager, IApplicationPaths applicationPaths, IImageProcessor imageProcessor, ILibraryManager libraryManager) + protected BaseFolderImageProvider(IFileSystem fileSystem, IProviderManager providerManager, IApplicationPaths applicationPaths, IImageProcessor imageProcessor, ILibraryManager libraryManager) : base(fileSystem, providerManager, applicationPaths, imageProcessor) { _libraryManager = libraryManager; diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs index 064fd2372..e3be5627f 100644 --- a/Emby.Server.Implementations/Library/LibraryManager.cs +++ b/Emby.Server.Implementations/Library/LibraryManager.cs @@ -45,7 +45,6 @@ using MediaBrowser.Model.IO; using MediaBrowser.Model.Library; using MediaBrowser.Model.Querying; using MediaBrowser.Model.Tasks; -using MediaBrowser.Providers.MediaInfo; using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.Logging; using Episode = MediaBrowser.Controller.Entities.TV.Episode; @@ -680,9 +679,7 @@ namespace Emby.Server.Implementations.Library if (result?.Items.Count > 0) { - var items = new List<BaseItem>(); - items.AddRange(result.Items); - + var items = result.Items; foreach (var item in items) { ResolverHelper.SetInitialItemValues(item, parent, this, directoryService); @@ -1007,14 +1004,8 @@ namespace Emby.Server.Implementations.Library return GetNewItemIdInternal(path, typeof(T), forceCaseInsensitiveId); } - /// <summary> - /// Validate and refresh the People sub-set of the IBN. - /// The items are stored in the db but not loaded into memory until actually requested by an operation. - /// </summary> - /// <param name="cancellationToken">The cancellation token.</param> - /// <param name="progress">The progress.</param> - /// <returns>Task.</returns> - public Task ValidatePeople(CancellationToken cancellationToken, IProgress<double> progress) + /// <inheritdoc /> + public Task ValidatePeopleAsync(IProgress<double> progress, CancellationToken cancellationToken) { // Ensure the location is available. Directory.CreateDirectory(_configurationManager.ApplicationPaths.PeoplePath); @@ -1037,15 +1028,6 @@ namespace Emby.Server.Implementations.Library } /// <summary> - /// Queues the library scan. - /// </summary> - public void QueueLibraryScan() - { - // Just run the scheduled task so that the user can see it - _taskManager.QueueScheduledTask<RefreshMediaLibraryTask>(); - } - - /// <summary> /// Validates the media library internal. /// </summary> /// <param name="progress">The progress.</param> @@ -1651,27 +1633,6 @@ namespace Emby.Server.Implementations.Library } /// <summary> - /// Gets all intro files. - /// </summary> - /// <returns>IEnumerable{System.String}.</returns> - public IEnumerable<string> GetAllIntroFiles() - { - return IntroProviders.SelectMany(i => - { - try - { - return i.GetAllIntroFiles().ToList(); - } - catch (Exception ex) - { - _logger.LogError(ex, "Error getting intro files"); - - return new List<string>(); - } - }); - } - - /// <summary> /// Resolves the intro. /// </summary> /// <param name="info">The info.</param> @@ -2469,24 +2430,6 @@ namespace Emby.Server.Implementations.Library return item; } - public void AddExternalSubtitleStreams( - List<MediaStream> streams, - string videoPath, - string[] files) - { - new SubtitleResolver(BaseItem.LocalizationManager).AddExternalSubtitleStreams(streams, videoPath, streams.Count, files); - } - - public BaseItem GetParentItem(string parentId, Guid? userId) - { - if (string.IsNullOrEmpty(parentId)) - { - return GetParentItem((Guid?)null, userId); - } - - return GetParentItem(new Guid(parentId), userId); - } - public BaseItem GetParentItem(Guid? parentId, Guid? userId) { if (parentId.HasValue) @@ -2494,7 +2437,7 @@ namespace Emby.Server.Implementations.Library return GetItemById(parentId.Value); } - if (userId.HasValue && userId != Guid.Empty) + if (userId.HasValue && !userId.Equals(default)) { return GetUserRootFolder(); } @@ -2784,16 +2727,6 @@ namespace Emby.Server.Implementations.Library return path; } - public string SubstitutePath(string path, string from, string to) - { - if (path.TryReplaceSubPath(from, to, out var newPath)) - { - return newPath; - } - - return path; - } - public List<PersonInfo> GetPeople(InternalPeopleQuery query) { return _itemRepository.GetPeople(query); diff --git a/Emby.Server.Implementations/Library/MediaSourceManager.cs b/Emby.Server.Implementations/Library/MediaSourceManager.cs index a414e7e16..eb95977ef 100644 --- a/Emby.Server.Implementations/Library/MediaSourceManager.cs +++ b/Emby.Server.Implementations/Library/MediaSourceManager.cs @@ -344,7 +344,7 @@ namespace Emby.Server.Implementations.Library return sources; } - private string[] NormalizeLanguage(string language) + private IReadOnlyList<string> NormalizeLanguage(string language) { if (string.IsNullOrEmpty(language)) { diff --git a/Emby.Server.Implementations/Library/MediaStreamSelector.cs b/Emby.Server.Implementations/Library/MediaStreamSelector.cs index da0c89c13..c5abb9a0a 100644 --- a/Emby.Server.Implementations/Library/MediaStreamSelector.cs +++ b/Emby.Server.Implementations/Library/MediaStreamSelector.cs @@ -1,5 +1,3 @@ -#nullable disable - #pragma warning disable CS1591 using System; @@ -13,10 +11,9 @@ namespace Emby.Server.Implementations.Library { public static class MediaStreamSelector { - public static int? GetDefaultAudioStreamIndex(List<MediaStream> streams, string[] preferredLanguages, bool preferDefaultTrack) + public static int? GetDefaultAudioStreamIndex(IReadOnlyList<MediaStream> streams, IReadOnlyList<string> preferredLanguages, bool preferDefaultTrack) { - streams = GetSortedStreams(streams, MediaStreamType.Audio, preferredLanguages) - .ToList(); + var sortedStreams = GetSortedStreams(streams, MediaStreamType.Audio, preferredLanguages); if (preferDefaultTrack) { @@ -28,24 +25,15 @@ namespace Emby.Server.Implementations.Library } } - var stream = streams.FirstOrDefault(); - - if (stream != null) - { - return stream.Index; - } - - return null; + return sortedStreams.FirstOrDefault()?.Index; } public static int? GetDefaultSubtitleStreamIndex( IEnumerable<MediaStream> streams, - string[] preferredLanguages, + IReadOnlyList<string> preferredLanguages, SubtitlePlaybackMode mode, string audioTrackLanguage) { - MediaStream stream = null; - if (mode == SubtitlePlaybackMode.None) { return null; @@ -59,6 +47,7 @@ namespace Emby.Server.Implementations.Library .ThenByDescending(x => x.IsDefault) .ToList(); + MediaStream? stream = null; if (mode == SubtitlePlaybackMode.Default) { // Prefer embedded metadata over smart logic @@ -95,26 +84,27 @@ namespace Emby.Server.Implementations.Library return stream?.Index; } - private static IEnumerable<MediaStream> GetSortedStreams(IEnumerable<MediaStream> streams, MediaStreamType type, string[] languagePreferences) + private static IEnumerable<MediaStream> GetSortedStreams(IEnumerable<MediaStream> streams, MediaStreamType type, IReadOnlyList<string> languagePreferences) { // Give some preference to external text subs for better performance - return streams.Where(i => i.Type == type) + return streams + .Where(i => i.Type == type) .OrderBy(i => - { - var index = FindIndex(languagePreferences, i.Language); - - return index == -1 ? 100 : index; - }) - .ThenBy(i => GetBooleanOrderBy(i.IsDefault)) - .ThenBy(i => GetBooleanOrderBy(i.SupportsExternalStream)) - .ThenBy(i => GetBooleanOrderBy(i.IsTextSubtitleStream)) - .ThenBy(i => GetBooleanOrderBy(i.IsExternal)) - .ThenBy(i => i.Index); + { + var index = languagePreferences.FindIndex(x => string.Equals(x, i.Language, StringComparison.OrdinalIgnoreCase)); + + return index == -1 ? 100 : index; + }) + .ThenBy(i => GetBooleanOrderBy(i.IsDefault)) + .ThenBy(i => GetBooleanOrderBy(i.SupportsExternalStream)) + .ThenBy(i => GetBooleanOrderBy(i.IsTextSubtitleStream)) + .ThenBy(i => GetBooleanOrderBy(i.IsExternal)) + .ThenBy(i => i.Index); } public static void SetSubtitleStreamScores( - List<MediaStream> streams, - string[] preferredLanguages, + IReadOnlyList<MediaStream> streams, + IReadOnlyList<string> preferredLanguages, SubtitlePlaybackMode mode, string audioTrackLanguage) { @@ -123,15 +113,14 @@ namespace Emby.Server.Implementations.Library return; } - streams = GetSortedStreams(streams, MediaStreamType.Subtitle, preferredLanguages) - .ToList(); + var sortedStreams = GetSortedStreams(streams, MediaStreamType.Subtitle, preferredLanguages); var filteredStreams = new List<MediaStream>(); if (mode == SubtitlePlaybackMode.Default) { // Prefer embedded metadata over smart logic - filteredStreams = streams.Where(s => s.IsForced || s.IsDefault) + filteredStreams = sortedStreams.Where(s => s.IsForced || s.IsDefault) .ToList(); } else if (mode == SubtitlePlaybackMode.Smart) @@ -139,54 +128,37 @@ namespace Emby.Server.Implementations.Library // Prefer smart logic over embedded metadata if (!preferredLanguages.Contains(audioTrackLanguage, StringComparison.OrdinalIgnoreCase)) { - filteredStreams = streams.Where(s => !s.IsForced && preferredLanguages.Contains(s.Language, StringComparison.OrdinalIgnoreCase)) + filteredStreams = sortedStreams.Where(s => !s.IsForced && preferredLanguages.Contains(s.Language, StringComparison.OrdinalIgnoreCase)) .ToList(); } } else if (mode == SubtitlePlaybackMode.Always) { // always load the most suitable full subtitles - filteredStreams = streams.Where(s => !s.IsForced) - .ToList(); + filteredStreams = sortedStreams.Where(s => !s.IsForced).ToList(); } else if (mode == SubtitlePlaybackMode.OnlyForced) { // always load the most suitable full subtitles - filteredStreams = streams.Where(s => s.IsForced).ToList(); + filteredStreams = sortedStreams.Where(s => s.IsForced).ToList(); } // load forced subs if we have found no suitable full subtitles - if (filteredStreams.Count == 0) - { - filteredStreams = streams - .Where(s => s.IsForced && string.Equals(s.Language, audioTrackLanguage, StringComparison.OrdinalIgnoreCase)) - .ToList(); - } + var iterStreams = filteredStreams.Count == 0 + ? sortedStreams.Where(s => s.IsForced && string.Equals(s.Language, audioTrackLanguage, StringComparison.OrdinalIgnoreCase)) + : filteredStreams; - foreach (var stream in filteredStreams) + foreach (var stream in iterStreams) { stream.Score = GetSubtitleScore(stream, preferredLanguages); } } - private static int FindIndex(string[] list, string value) - { - for (var i = 0; i < list.Length; i++) - { - if (string.Equals(list[i], value, StringComparison.OrdinalIgnoreCase)) - { - return i; - } - } - - return -1; - } - - private static int GetSubtitleScore(MediaStream stream, string[] languagePreferences) + private static int GetSubtitleScore(MediaStream stream, IReadOnlyList<string> languagePreferences) { var values = new List<int>(); - var index = FindIndex(languagePreferences, stream.Language); + var index = languagePreferences.FindIndex(x => string.Equals(x, stream.Language, StringComparison.OrdinalIgnoreCase)); values.Add(index == -1 ? 0 : 100 - index); diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs index f1d4b6097..582e61d79 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs @@ -24,7 +24,7 @@ using Microsoft.Extensions.Logging; namespace Emby.Server.Implementations.LiveTv.EmbyTV { - public class EncodedRecorder : IRecorder + public class EncodedRecorder : IRecorder, IDisposable { private readonly ILogger _logger; private readonly IMediaEncoder _mediaEncoder; @@ -36,6 +36,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV private Stream _logFileStream; private string _targetPath; private Process _process; + private bool _disposed = false; public EncodedRecorder( ILogger logger, @@ -323,5 +324,35 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV _logger.LogError(ex, "Error reading ffmpeg recording log"); } } + + /// <inheritdoc /> + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + /// <summary> + /// Releases unmanaged and optionally managed resources. + /// </summary> + /// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param> + protected virtual void Dispose(bool disposing) + { + if (_disposed) + { + return; + } + + if (disposing) + { + _logFileStream?.Dispose(); + _process?.Dispose(); + } + + _logFileStream = null; + _process = null; + + _disposed = true; + } } } diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs index a8440102d..ffa0d9b6a 100644 --- a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs +++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs @@ -28,7 +28,7 @@ using Microsoft.Extensions.Logging; namespace Emby.Server.Implementations.LiveTv.Listings { - public class SchedulesDirect : IListingsProvider + public class SchedulesDirect : IListingsProvider, IDisposable { private const string ApiUrl = "https://json.schedulesdirect.org/20141201"; @@ -39,6 +39,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings private readonly ConcurrentDictionary<string, NameValuePair> _tokens = new ConcurrentDictionary<string, NameValuePair>(); private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.Options; private DateTime _lastErrorResponse; + private bool _disposed = false; public SchedulesDirect( ILogger<SchedulesDirect> logger, @@ -58,8 +59,8 @@ namespace Emby.Server.Implementations.LiveTv.Listings { var dates = new List<string>(); - var start = new List<DateTime> { startDateUtc, startDateUtc.ToLocalTime() }.Min().Date; - var end = new List<DateTime> { endDateUtc, endDateUtc.ToLocalTime() }.Max().Date; + var start = new[] { startDateUtc, startDateUtc.ToLocalTime() }.Min().Date; + var end = new[] { endDateUtc, endDateUtc.ToLocalTime() }.Max().Date; while (start <= end) { @@ -822,5 +823,31 @@ namespace Emby.Server.Implementations.LiveTv.Listings return list; } + + /// <inheritdoc /> + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + /// <summary> + /// Releases unmanaged and optionally managed resources. + /// </summary> + /// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param> + protected virtual void Dispose(bool disposing) + { + if (_disposed) + { + return; + } + + if (disposing) + { + _tokenSemaphore?.Dispose(); + } + + _disposed = true; + } } } diff --git a/Emby.Server.Implementations/LiveTv/LiveTvManager.cs b/Emby.Server.Implementations/LiveTv/LiveTvManager.cs index 6a9a3077c..71a29e3cb 100644 --- a/Emby.Server.Implementations/LiveTv/LiveTvManager.cs +++ b/Emby.Server.Implementations/LiveTv/LiveTvManager.cs @@ -39,7 +39,7 @@ namespace Emby.Server.Implementations.LiveTv /// <summary> /// Class LiveTvManager. /// </summary> - public class LiveTvManager : ILiveTvManager, IDisposable + public class LiveTvManager : ILiveTvManager { private const int MaxGuideDays = 14; private const string ExternalServiceTag = "ExternalServiceId"; @@ -63,8 +63,6 @@ namespace Emby.Server.Implementations.LiveTv private ITunerHost[] _tunerHosts = Array.Empty<ITunerHost>(); private IListingsProvider[] _listingProviders = Array.Empty<IListingsProvider>(); - private bool _disposed = false; - public LiveTvManager( IServerConfigurationManager config, ILogger<LiveTvManager> logger, @@ -312,7 +310,7 @@ namespace Emby.Server.Implementations.LiveTv { if (isVideo) { - mediaSource.MediaStreams.AddRange(new List<MediaStream> + mediaSource.MediaStreams = new MediaStream[] { new MediaStream { @@ -329,11 +327,11 @@ namespace Emby.Server.Implementations.LiveTv // Set the index to -1 because we don't know the exact index of the audio stream within the container Index = -1 } - }); + }; } else { - mediaSource.MediaStreams.AddRange(new List<MediaStream> + mediaSource.MediaStreams = new MediaStream[] { new MediaStream { @@ -341,7 +339,7 @@ namespace Emby.Server.Implementations.LiveTv // Set the index to -1 because we don't know the exact index of the audio stream within the container Index = -1 } - }); + }; } } @@ -2092,36 +2090,6 @@ namespace Emby.Server.Implementations.LiveTv }; } - /// <inheritdoc /> - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - /// <summary> - /// Releases unmanaged and - optionally - managed resources. - /// </summary> - /// <param name="dispose"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param> - protected virtual void Dispose(bool dispose) - { - if (_disposed) - { - return; - } - - if (dispose) - { - // TODO: Dispose stuff - } - - _services = null; - _listingProviders = null; - _tunerHosts = null; - - _disposed = true; - } - private LiveTvServiceInfo[] GetServiceInfos() { return Services.Select(GetServiceInfo).ToArray(); diff --git a/Emby.Server.Implementations/LiveTv/RefreshGuideScheduledTask.cs b/Emby.Server.Implementations/LiveTv/RefreshGuideScheduledTask.cs index 15df0dcf1..72bbdd14a 100644 --- a/Emby.Server.Implementations/LiveTv/RefreshGuideScheduledTask.cs +++ b/Emby.Server.Implementations/LiveTv/RefreshGuideScheduledTask.cs @@ -50,7 +50,7 @@ namespace Emby.Server.Implementations.LiveTv public string Key => "RefreshGuide"; /// <inheritdoc /> - public Task Execute(CancellationToken cancellationToken, IProgress<double> progress) + public Task ExecuteAsync(IProgress<double> progress, CancellationToken cancellationToken) { var manager = (LiveTvManager)_liveTvManager; diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs index 532790019..e0eaa8e58 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs @@ -446,7 +446,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun { Path = url, Protocol = MediaProtocol.Udp, - MediaStreams = new List<MediaStream> + MediaStreams = new MediaStream[] { new MediaStream { diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs index dd83f9a53..2a468e14d 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs @@ -170,7 +170,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts { Path = path, Protocol = protocol, - MediaStreams = new List<MediaStream> + MediaStreams = new MediaStream[] { new MediaStream { diff --git a/Emby.Server.Implementations/Localization/Core/cs.json b/Emby.Server.Implementations/Localization/Core/cs.json index 65a31e676..7ee8d1040 100644 --- a/Emby.Server.Implementations/Localization/Core/cs.json +++ b/Emby.Server.Implementations/Localization/Core/cs.json @@ -77,7 +77,7 @@ "SubtitleDownloadFailureFromForItem": "Stažení titulků pro {1} z {0} selhalo", "Sync": "Synchronizace", "System": "Systém", - "TvShows": "TV seriály", + "TvShows": "Seriály", "User": "Uživatel", "UserCreatedWithName": "Uživatel {0} byl vytvořen", "UserDeletedWithName": "Uživatel {0} byl smazán", diff --git a/Emby.Server.Implementations/Localization/Core/fa.json b/Emby.Server.Implementations/Localization/Core/fa.json index 6960ff007..8e8eef867 100644 --- a/Emby.Server.Implementations/Localization/Core/fa.json +++ b/Emby.Server.Implementations/Localization/Core/fa.json @@ -119,5 +119,6 @@ "TaskCleanActivityLogDescription": "ورودیهای قدیمیتر از سن تنظیم شده در سیاهه فعالیت را حذف میکند.", "TaskCleanActivityLog": "پاکسازی سیاهه فعالیت", "Undefined": "تعریف نشده", - "TaskOptimizeDatabase": "بهینه سازی پایگاه داده" + "TaskOptimizeDatabase": "بهینه سازی پایگاه داده", + "TaskOptimizeDatabaseDescription": "فشرده سازی پایگاه داده و باز کردن فضای آزاد.اجرای این گزینه بعد از اسکن کردن کتابخانه یا تغییرات دیگر که روی پایگاه داده تأثیر میگذارند میتواند کارایی را بهبود ببخشد." } diff --git a/Emby.Server.Implementations/Localization/Core/vi.json b/Emby.Server.Implementations/Localization/Core/vi.json index d80f1760d..d0e08d8ee 100644 --- a/Emby.Server.Implementations/Localization/Core/vi.json +++ b/Emby.Server.Implementations/Localization/Core/vi.json @@ -13,7 +13,7 @@ "Songs": "Bài Hát", "Sync": "Đồng Bộ", "ValueSpecialEpisodeName": "Đặc Biệt - {0}", - "Albums": "", + "Albums": "Album", "Artists": "Ca Sĩ", "TaskDownloadMissingSubtitlesDescription": "Tìm kiếm phụ đề bị thiếu trên Internet dựa trên cấu hình dữ liệu mô tả.", "TaskDownloadMissingSubtitles": "Tải Xuống Phụ Đề Bị Thiếu", diff --git a/Emby.Server.Implementations/Localization/LocalizationManager.cs b/Emby.Server.Implementations/Localization/LocalizationManager.cs index dbd70342a..281dbb00b 100644 --- a/Emby.Server.Implementations/Localization/LocalizationManager.cs +++ b/Emby.Server.Implementations/Localization/LocalizationManager.cs @@ -147,13 +147,7 @@ namespace Emby.Server.Implementations.Localization threeletterNames = new[] { parts[0], parts[1] }; } - list.Add(new CultureDto - { - DisplayName = name, - Name = name, - ThreeLetterISOLanguageNames = threeletterNames, - TwoLetterISOLanguageName = twoCharName - }); + list.Add(new CultureDto(name, name, twoCharName, threeletterNames)); } } diff --git a/Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs b/Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs index 299f10544..2c4d6884d 100644 --- a/Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs +++ b/Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs @@ -414,7 +414,7 @@ namespace Emby.Server.Implementations.ScheduledTasks CurrentCancellationTokenSource.CancelAfter(TimeSpan.FromTicks(options.MaxRuntimeTicks.Value)); } - await ScheduledTask.Execute(CurrentCancellationTokenSource.Token, progress).ConfigureAwait(false); + await ScheduledTask.ExecuteAsync(progress, CurrentCancellationTokenSource.Token).ConfigureAwait(false); status = TaskCompletionStatus.Completed; } @@ -757,6 +757,10 @@ namespace Emby.Server.Implementations.ScheduledTasks var trigger = triggerInfo.Item2; trigger.Triggered -= OnTriggerTriggered; trigger.Stop(); + if (trigger is IDisposable disposable) + { + disposable.Dispose(); + } } } } diff --git a/Emby.Server.Implementations/ScheduledTasks/Tasks/ChapterImagesTask.cs b/Emby.Server.Implementations/ScheduledTasks/Tasks/ChapterImagesTask.cs index a5786a3d7..0bf0838fa 100644 --- a/Emby.Server.Implementations/ScheduledTasks/Tasks/ChapterImagesTask.cs +++ b/Emby.Server.Implementations/ScheduledTasks/Tasks/ChapterImagesTask.cs @@ -88,13 +88,8 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks }; } - /// <summary> - /// Returns the task to be executed. - /// </summary> - /// <param name="cancellationToken">The cancellation token.</param> - /// <param name="progress">The progress.</param> - /// <returns>Task.</returns> - public async Task Execute(CancellationToken cancellationToken, IProgress<double> progress) + /// <inheritdoc /> + public async Task ExecuteAsync(IProgress<double> progress, CancellationToken cancellationToken) { var videos = _libraryManager.GetItemList(new InternalItemsQuery { diff --git a/Emby.Server.Implementations/ScheduledTasks/Tasks/CleanActivityLogTask.cs b/Emby.Server.Implementations/ScheduledTasks/Tasks/CleanActivityLogTask.cs index 79886cb52..776079044 100644 --- a/Emby.Server.Implementations/ScheduledTasks/Tasks/CleanActivityLogTask.cs +++ b/Emby.Server.Implementations/ScheduledTasks/Tasks/CleanActivityLogTask.cs @@ -57,12 +57,12 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks public bool IsLogged => true; /// <inheritdoc /> - public Task Execute(CancellationToken cancellationToken, IProgress<double> progress) + public Task ExecuteAsync(IProgress<double> progress, CancellationToken cancellationToken) { var retentionDays = _serverConfigurationManager.Configuration.ActivityLogRetentionDays; if (!retentionDays.HasValue || retentionDays < 0) { - throw new Exception($"Activity Log Retention days must be at least 0. Currently: {retentionDays}"); + throw new InvalidOperationException($"Activity Log Retention days must be at least 0. Currently: {retentionDays}"); } var startDate = DateTime.UtcNow.AddDays(-retentionDays.Value); diff --git a/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteCacheFileTask.cs b/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteCacheFileTask.cs index 0941902fc..03935b384 100644 --- a/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteCacheFileTask.cs +++ b/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteCacheFileTask.cs @@ -79,19 +79,14 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks }; } - /// <summary> - /// Returns the task to be executed. - /// </summary> - /// <param name="cancellationToken">The cancellation token.</param> - /// <param name="progress">The progress.</param> - /// <returns>Task.</returns> - public Task Execute(CancellationToken cancellationToken, IProgress<double> progress) + /// <inheritdoc /> + public Task ExecuteAsync(IProgress<double> progress, CancellationToken cancellationToken) { var minDateModified = DateTime.UtcNow.AddDays(-30); try { - DeleteCacheFilesFromDirectory(cancellationToken, _applicationPaths.CachePath, minDateModified, progress); + DeleteCacheFilesFromDirectory(_applicationPaths.CachePath, minDateModified, progress, cancellationToken); } catch (DirectoryNotFoundException) { @@ -104,7 +99,7 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks try { - DeleteCacheFilesFromDirectory(cancellationToken, _applicationPaths.TempDirectory, minDateModified, progress); + DeleteCacheFilesFromDirectory(_applicationPaths.TempDirectory, minDateModified, progress, cancellationToken); } catch (DirectoryNotFoundException) { @@ -117,11 +112,11 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks /// <summary> /// Deletes the cache files from directory with a last write time less than a given date. /// </summary> - /// <param name="cancellationToken">The task cancellation token.</param> /// <param name="directory">The directory.</param> /// <param name="minDateModified">The min date modified.</param> /// <param name="progress">The progress.</param> - private void DeleteCacheFilesFromDirectory(CancellationToken cancellationToken, string directory, DateTime minDateModified, IProgress<double> progress) + /// <param name="cancellationToken">The task cancellation token.</param> + private void DeleteCacheFilesFromDirectory(string directory, DateTime minDateModified, IProgress<double> progress, CancellationToken cancellationToken) { var filesToDelete = _fileSystem.GetFiles(directory, true) .Where(f => _fileSystem.GetLastWriteTimeUtc(f) < minDateModified) diff --git a/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteLogFileTask.cs b/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteLogFileTask.cs index fedb5deb0..9739d7327 100644 --- a/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteLogFileTask.cs +++ b/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteLogFileTask.cs @@ -69,13 +69,8 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks }; } - /// <summary> - /// Returns the task to be executed. - /// </summary> - /// <param name="cancellationToken">The cancellation token.</param> - /// <param name="progress">The progress.</param> - /// <returns>Task.</returns> - public Task Execute(CancellationToken cancellationToken, IProgress<double> progress) + /// <inheritdoc /> + public Task ExecuteAsync(IProgress<double> progress, CancellationToken cancellationToken) { // Delete log files more than n days old var minDateModified = DateTime.UtcNow.AddDays(-_configurationManager.CommonConfiguration.LogFileRetentionDays); diff --git a/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteTranscodeFileTask.cs b/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteTranscodeFileTask.cs index 099d781cd..e4e565c64 100644 --- a/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteTranscodeFileTask.cs +++ b/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteTranscodeFileTask.cs @@ -78,18 +78,13 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks }; } - /// <summary> - /// Returns the task to be executed. - /// </summary> - /// <param name="cancellationToken">The cancellation token.</param> - /// <param name="progress">The progress.</param> - /// <returns>Task.</returns> - public Task Execute(CancellationToken cancellationToken, IProgress<double> progress) + /// <inheritdoc /> + public Task ExecuteAsync(IProgress<double> progress, CancellationToken cancellationToken) { var minDateModified = DateTime.UtcNow.AddDays(-1); progress.Report(50); - DeleteTempFilesFromDirectory(cancellationToken, _configurationManager.GetTranscodePath(), minDateModified, progress); + DeleteTempFilesFromDirectory(_configurationManager.GetTranscodePath(), minDateModified, progress, cancellationToken); return Task.CompletedTask; } @@ -97,11 +92,11 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks /// <summary> /// Deletes the transcoded temp files from directory with a last write time less than a given date. /// </summary> - /// <param name="cancellationToken">The task cancellation token.</param> /// <param name="directory">The directory.</param> /// <param name="minDateModified">The min date modified.</param> /// <param name="progress">The progress.</param> - private void DeleteTempFilesFromDirectory(CancellationToken cancellationToken, string directory, DateTime minDateModified, IProgress<double> progress) + /// <param name="cancellationToken">The task cancellation token.</param> + private void DeleteTempFilesFromDirectory(string directory, DateTime minDateModified, IProgress<double> progress, CancellationToken cancellationToken) { var filesToDelete = _fileSystem.GetFiles(directory, true) .Where(f => _fileSystem.GetLastWriteTimeUtc(f) < minDateModified) diff --git a/Emby.Server.Implementations/ScheduledTasks/Tasks/OptimizeDatabaseTask.cs b/Emby.Server.Implementations/ScheduledTasks/Tasks/OptimizeDatabaseTask.cs index 35a4aeef6..98e45fa46 100644 --- a/Emby.Server.Implementations/ScheduledTasks/Tasks/OptimizeDatabaseTask.cs +++ b/Emby.Server.Implementations/ScheduledTasks/Tasks/OptimizeDatabaseTask.cs @@ -69,13 +69,8 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks }; } - /// <summary> - /// Returns the task to be executed. - /// </summary> - /// <param name="cancellationToken">The cancellation token.</param> - /// <param name="progress">The progress.</param> - /// <returns>Task.</returns> - public Task Execute(CancellationToken cancellationToken, IProgress<double> progress) + /// <inheritdoc /> + public Task ExecuteAsync(IProgress<double> progress, CancellationToken cancellationToken) { _logger.LogInformation("Optimizing and vacuuming jellyfin.db..."); diff --git a/Emby.Server.Implementations/ScheduledTasks/Tasks/PeopleValidationTask.cs b/Emby.Server.Implementations/ScheduledTasks/Tasks/PeopleValidationTask.cs index 34780111b..7d60ea731 100644 --- a/Emby.Server.Implementations/ScheduledTasks/Tasks/PeopleValidationTask.cs +++ b/Emby.Server.Implementations/ScheduledTasks/Tasks/PeopleValidationTask.cs @@ -62,15 +62,10 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks }; } - /// <summary> - /// Returns the task to be executed. - /// </summary> - /// <param name="cancellationToken">The cancellation token.</param> - /// <param name="progress">The progress.</param> - /// <returns>Task.</returns> - public Task Execute(CancellationToken cancellationToken, IProgress<double> progress) + /// <inheritdoc /> + public Task ExecuteAsync(IProgress<double> progress, CancellationToken cancellationToken) { - return _libraryManager.ValidatePeople(cancellationToken, progress); + return _libraryManager.ValidatePeopleAsync(progress, cancellationToken); } } } diff --git a/Emby.Server.Implementations/ScheduledTasks/Tasks/PluginUpdateTask.cs b/Emby.Server.Implementations/ScheduledTasks/Tasks/PluginUpdateTask.cs index b3973cecb..443649e6e 100644 --- a/Emby.Server.Implementations/ScheduledTasks/Tasks/PluginUpdateTask.cs +++ b/Emby.Server.Implementations/ScheduledTasks/Tasks/PluginUpdateTask.cs @@ -68,13 +68,8 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks yield return new TaskTriggerInfo { Type = TaskTriggerInfo.TriggerInterval, IntervalTicks = TimeSpan.FromHours(24).Ticks }; } - /// <summary> - /// Update installed plugins. - /// </summary> - /// <param name="cancellationToken">The cancellation token.</param> - /// <param name="progress">The progress.</param> - /// <returns><see cref="Task" />.</returns> - public async Task Execute(CancellationToken cancellationToken, IProgress<double> progress) + /// <inheritdoc /> + public async Task ExecuteAsync(IProgress<double> progress, CancellationToken cancellationToken) { progress.Report(0); diff --git a/Emby.Server.Implementations/ScheduledTasks/Tasks/RefreshMediaLibraryTask.cs b/Emby.Server.Implementations/ScheduledTasks/Tasks/RefreshMediaLibraryTask.cs index 7c27ae384..065008157 100644 --- a/Emby.Server.Implementations/ScheduledTasks/Tasks/RefreshMediaLibraryTask.cs +++ b/Emby.Server.Implementations/ScheduledTasks/Tasks/RefreshMediaLibraryTask.cs @@ -58,13 +58,8 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks }; } - /// <summary> - /// Executes the internal. - /// </summary> - /// <param name="cancellationToken">The cancellation token.</param> - /// <param name="progress">The progress.</param> - /// <returns>Task.</returns> - public Task Execute(CancellationToken cancellationToken, IProgress<double> progress) + /// <inheritdoc /> + public Task ExecuteAsync(IProgress<double> progress, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); diff --git a/Emby.Server.Implementations/ScheduledTasks/Triggers/DailyTrigger.cs b/Emby.Server.Implementations/ScheduledTasks/Triggers/DailyTrigger.cs index dc5eb7391..63f11a22c 100644 --- a/Emby.Server.Implementations/ScheduledTasks/Triggers/DailyTrigger.cs +++ b/Emby.Server.Implementations/ScheduledTasks/Triggers/DailyTrigger.cs @@ -8,10 +8,11 @@ namespace Emby.Server.Implementations.ScheduledTasks.Triggers /// <summary> /// Represents a task trigger that fires everyday. /// </summary> - public sealed class DailyTrigger : ITaskTrigger + public sealed class DailyTrigger : ITaskTrigger, IDisposable { private readonly TimeSpan _timeOfDay; private Timer? _timer; + private bool _disposed = false; /// <summary> /// Initializes a new instance of the <see cref="DailyTrigger"/> class. @@ -71,6 +72,7 @@ namespace Emby.Server.Implementations.ScheduledTasks.Triggers private void DisposeTimer() { _timer?.Dispose(); + _timer = null; } /// <summary> @@ -80,5 +82,18 @@ namespace Emby.Server.Implementations.ScheduledTasks.Triggers { Triggered?.Invoke(this, EventArgs.Empty); } + + /// <inheritdoc /> + public void Dispose() + { + if (_disposed) + { + return; + } + + DisposeTimer(); + + _disposed = true; + } } } diff --git a/Emby.Server.Implementations/ScheduledTasks/Triggers/IntervalTrigger.cs b/Emby.Server.Implementations/ScheduledTasks/Triggers/IntervalTrigger.cs index 927f57e95..3eb800199 100644 --- a/Emby.Server.Implementations/ScheduledTasks/Triggers/IntervalTrigger.cs +++ b/Emby.Server.Implementations/ScheduledTasks/Triggers/IntervalTrigger.cs @@ -9,11 +9,12 @@ namespace Emby.Server.Implementations.ScheduledTasks.Triggers /// <summary> /// Represents a task trigger that runs repeatedly on an interval. /// </summary> - public sealed class IntervalTrigger : ITaskTrigger + public sealed class IntervalTrigger : ITaskTrigger, IDisposable { private readonly TimeSpan _interval; private DateTime _lastStartDate; private Timer? _timer; + private bool _disposed = false; /// <summary> /// Initializes a new instance of the <see cref="IntervalTrigger"/> class. @@ -89,6 +90,7 @@ namespace Emby.Server.Implementations.ScheduledTasks.Triggers private void DisposeTimer() { _timer?.Dispose(); + _timer = null; } /// <summary> @@ -104,5 +106,18 @@ namespace Emby.Server.Implementations.ScheduledTasks.Triggers Triggered(this, EventArgs.Empty); } } + + /// <inheritdoc /> + public void Dispose() + { + if (_disposed) + { + return; + } + + DisposeTimer(); + + _disposed = true; + } } } diff --git a/Emby.Server.Implementations/ScheduledTasks/Triggers/WeeklyTrigger.cs b/Emby.Server.Implementations/ScheduledTasks/Triggers/WeeklyTrigger.cs index 2392b20fd..fab49f2fb 100644 --- a/Emby.Server.Implementations/ScheduledTasks/Triggers/WeeklyTrigger.cs +++ b/Emby.Server.Implementations/ScheduledTasks/Triggers/WeeklyTrigger.cs @@ -8,11 +8,12 @@ namespace Emby.Server.Implementations.ScheduledTasks.Triggers /// <summary> /// Represents a task trigger that fires on a weekly basis. /// </summary> - public sealed class WeeklyTrigger : ITaskTrigger + public sealed class WeeklyTrigger : ITaskTrigger, IDisposable { private readonly TimeSpan _timeOfDay; private readonly DayOfWeek _dayOfWeek; private Timer? _timer; + private bool _disposed = false; /// <summary> /// Initializes a new instance of the <see cref="WeeklyTrigger"/> class. @@ -94,6 +95,7 @@ namespace Emby.Server.Implementations.ScheduledTasks.Triggers private void DisposeTimer() { _timer?.Dispose(); + _timer = null; } /// <summary> @@ -103,5 +105,18 @@ namespace Emby.Server.Implementations.ScheduledTasks.Triggers { Triggered?.Invoke(this, EventArgs.Empty); } + + /// <inheritdoc /> + public void Dispose() + { + if (_disposed) + { + return; + } + + DisposeTimer(); + + _disposed = true; + } } } diff --git a/Emby.Server.Implementations/TV/TVSeriesManager.cs b/Emby.Server.Implementations/TV/TVSeriesManager.cs index a18af27f3..a47650a32 100644 --- a/Emby.Server.Implementations/TV/TVSeriesManager.cs +++ b/Emby.Server.Implementations/TV/TVSeriesManager.cs @@ -140,7 +140,7 @@ namespace Emby.Server.Implementations.TV var currentUser = user; var allNextUp = seriesKeys - .Select(i => GetNextUp(i, currentUser, dtoOptions)); + .Select(i => GetNextUp(i, currentUser, dtoOptions, request.Rewatching)); // If viewing all next up for all series, remove first episodes // But if that returns empty, keep those first episodes (avoid completely empty view) @@ -186,9 +186,9 @@ namespace Emby.Server.Implementations.TV /// Gets the next up. /// </summary> /// <returns>Task{Episode}.</returns> - private Tuple<DateTime, Func<Episode>> GetNextUp(string seriesKey, User user, DtoOptions dtoOptions) + private Tuple<DateTime, Func<Episode>> GetNextUp(string seriesKey, User user, DtoOptions dtoOptions, bool rewatching) { - var lastWatchedEpisode = _libraryManager.GetItemList(new InternalItemsQuery(user) + var lastQuery = new InternalItemsQuery(user) { AncestorWithPresentationUniqueKey = null, SeriesPresentationUniqueKey = seriesKey, @@ -202,23 +202,43 @@ namespace Emby.Server.Implementations.TV Fields = new[] { ItemFields.SortName }, EnableImages = false } - }).Cast<Episode>().FirstOrDefault(); + }; + + if (rewatching) + { + // find last watched by date played, not by newest episode watched + lastQuery.OrderBy = new[] { (ItemSortBy.DatePlayed, SortOrder.Descending) }; + } + + var lastWatchedEpisode = _libraryManager.GetItemList(lastQuery).Cast<Episode>().FirstOrDefault(); Func<Episode> getEpisode = () => { - var nextEpisode = _libraryManager.GetItemList(new InternalItemsQuery(user) + var nextQuery = new InternalItemsQuery(user) { AncestorWithPresentationUniqueKey = null, SeriesPresentationUniqueKey = seriesKey, IncludeItemTypes = new[] { BaseItemKind.Episode }, OrderBy = new[] { (ItemSortBy.SortName, SortOrder.Ascending) }, Limit = 1, - IsPlayed = false, + IsPlayed = rewatching, IsVirtualItem = false, ParentIndexNumberNotEquals = 0, MinSortName = lastWatchedEpisode?.SortName, DtoOptions = dtoOptions - }).Cast<Episode>().FirstOrDefault(); + }; + + Episode nextEpisode; + if (rewatching) + { + nextQuery.Limit = 2; + // get watched episode after most recently watched + nextEpisode = _libraryManager.GetItemList(nextQuery).Cast<Episode>().ElementAtOrDefault(1); + } + else + { + nextEpisode = _libraryManager.GetItemList(nextQuery).Cast<Episode>().FirstOrDefault(); + } if (_configurationManager.Configuration.DisplaySpecialsWithinSeasons) { @@ -228,7 +248,7 @@ namespace Emby.Server.Implementations.TV SeriesPresentationUniqueKey = seriesKey, ParentIndexNumber = 0, IncludeItemTypes = new[] { BaseItemKind.Episode }, - IsPlayed = false, + IsPlayed = rewatching, IsVirtualItem = false, DtoOptions = dtoOptions }) diff --git a/Emby.Server.Implementations/Udp/UdpServer.cs b/Emby.Server.Implementations/Udp/UdpServer.cs index c8ab99de4..937e792f5 100644 --- a/Emby.Server.Implementations/Udp/UdpServer.cs +++ b/Emby.Server.Implementations/Udp/UdpServer.cs @@ -9,6 +9,7 @@ using MediaBrowser.Controller; using MediaBrowser.Model.ApiClient; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; +using static MediaBrowser.Controller.Extensions.ConfigurationExtensions; namespace Emby.Server.Implementations.Udp { @@ -18,11 +19,6 @@ namespace Emby.Server.Implementations.Udp public sealed class UdpServer : IDisposable { /// <summary> - /// Address Override Configuration Key. - /// </summary> - public const string AddressOverrideConfigKey = "PublishedServerUrl"; - - /// <summary> /// The _logger. /// </summary> private readonly ILogger _logger; @@ -60,7 +56,7 @@ namespace Emby.Server.Implementations.Udp private async Task RespondToV2Message(EndPoint endpoint, CancellationToken cancellationToken) { - string? localUrl = _config[AddressOverrideConfigKey]; + string? localUrl = _config[AddressOverrideKey]; if (string.IsNullOrEmpty(localUrl)) { localUrl = _appHost.GetSmartApiUrl(((IPEndPoint)endpoint).Address); @@ -68,7 +64,7 @@ namespace Emby.Server.Implementations.Udp if (string.IsNullOrEmpty(localUrl)) { - _logger.LogWarning("Unable to respond to udp request because the local ip address could not be determined."); + _logger.LogWarning("Unable to respond to server discovery request because the local ip address could not be determined."); return; } |
