aboutsummaryrefslogtreecommitdiff
path: root/Emby.Server.Implementations
diff options
context:
space:
mode:
Diffstat (limited to 'Emby.Server.Implementations')
-rw-r--r--Emby.Server.Implementations/ApplicationHost.cs20
-rw-r--r--Emby.Server.Implementations/Channels/ChannelManager.cs67
-rw-r--r--Emby.Server.Implementations/Channels/RefreshChannelsScheduledTask.cs2
-rw-r--r--Emby.Server.Implementations/Data/SqliteItemRepository.cs346
-rw-r--r--Emby.Server.Implementations/Data/SqliteUserDataRepository.cs10
-rw-r--r--Emby.Server.Implementations/Dto/DtoService.cs37
-rw-r--r--Emby.Server.Implementations/Emby.Server.Implementations.csproj10
-rw-r--r--Emby.Server.Implementations/EntryPoints/UdpServerEntryPoint.cs13
-rw-r--r--Emby.Server.Implementations/IO/ManagedFileSystem.cs4
-rw-r--r--Emby.Server.Implementations/Images/BaseDynamicImageProvider.cs2
-rw-r--r--Emby.Server.Implementations/Images/BaseFolderImageProvider.cs2
-rw-r--r--Emby.Server.Implementations/Images/CollectionFolderImageProvider.cs4
-rw-r--r--Emby.Server.Implementations/Images/DynamicImageProvider.cs10
-rw-r--r--Emby.Server.Implementations/Library/LibraryManager.cs108
-rw-r--r--Emby.Server.Implementations/Library/MediaSourceManager.cs2
-rw-r--r--Emby.Server.Implementations/Library/MediaStreamSelector.cs90
-rw-r--r--Emby.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs31
-rw-r--r--Emby.Server.Implementations/Library/Resolvers/ExtraResolver.cs10
-rw-r--r--Emby.Server.Implementations/Library/Resolvers/GenericVideoResolver.cs8
-rw-r--r--Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs16
-rw-r--r--Emby.Server.Implementations/Library/Resolvers/TV/EpisodeResolver.cs6
-rw-r--r--Emby.Server.Implementations/Library/SearchEngine.cs10
-rw-r--r--Emby.Server.Implementations/Library/SplashscreenPostScanTask.cs79
-rw-r--r--Emby.Server.Implementations/LiveTv/EmbyTV/DirectRecorder.cs20
-rw-r--r--Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs126
-rw-r--r--Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs35
-rw-r--r--Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs33
-rw-r--r--Emby.Server.Implementations/LiveTv/LiveTvDtoService.cs6
-rw-r--r--Emby.Server.Implementations/LiveTv/LiveTvManager.cs106
-rw-r--r--Emby.Server.Implementations/LiveTv/RefreshGuideScheduledTask.cs2
-rw-r--r--Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs2
-rw-r--r--Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs5
-rw-r--r--Emby.Server.Implementations/LiveTv/TunerHosts/LiveStream.cs18
-rw-r--r--Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs2
-rw-r--r--Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs2
-rw-r--r--Emby.Server.Implementations/Localization/Core/cs.json4
-rw-r--r--Emby.Server.Implementations/Localization/Core/fa.json3
-rw-r--r--Emby.Server.Implementations/Localization/Core/fr.json2
-rw-r--r--Emby.Server.Implementations/Localization/Core/ro.json4
-rw-r--r--Emby.Server.Implementations/Localization/Core/th.json3
-rw-r--r--Emby.Server.Implementations/Localization/Core/ug.json9
-rw-r--r--Emby.Server.Implementations/Localization/Core/vi.json2
-rw-r--r--Emby.Server.Implementations/Localization/LocalizationManager.cs8
-rw-r--r--Emby.Server.Implementations/Net/UdpSocket.cs4
-rw-r--r--Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs6
-rw-r--r--Emby.Server.Implementations/ScheduledTasks/Tasks/ChapterImagesTask.cs9
-rw-r--r--Emby.Server.Implementations/ScheduledTasks/Tasks/CleanActivityLogTask.cs4
-rw-r--r--Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteCacheFileTask.cs17
-rw-r--r--Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteLogFileTask.cs9
-rw-r--r--Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteTranscodeFileTask.cs15
-rw-r--r--Emby.Server.Implementations/ScheduledTasks/Tasks/OptimizeDatabaseTask.cs9
-rw-r--r--Emby.Server.Implementations/ScheduledTasks/Tasks/PeopleValidationTask.cs11
-rw-r--r--Emby.Server.Implementations/ScheduledTasks/Tasks/PluginUpdateTask.cs9
-rw-r--r--Emby.Server.Implementations/ScheduledTasks/Tasks/RefreshMediaLibraryTask.cs13
-rw-r--r--Emby.Server.Implementations/ScheduledTasks/Triggers/DailyTrigger.cs17
-rw-r--r--Emby.Server.Implementations/ScheduledTasks/Triggers/IntervalTrigger.cs17
-rw-r--r--Emby.Server.Implementations/ScheduledTasks/Triggers/WeeklyTrigger.cs17
-rw-r--r--Emby.Server.Implementations/TV/TVSeriesManager.cs45
-rw-r--r--Emby.Server.Implementations/Udp/UdpServer.cs10
59 files changed, 677 insertions, 814 deletions
diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs
index 8ed51a194..82294644b 100644
--- a/Emby.Server.Implementations/ApplicationHost.cs
+++ b/Emby.Server.Implementations/ApplicationHost.cs
@@ -44,9 +44,9 @@ 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;
using Jellyfin.Networking.Configuration;
using Jellyfin.Networking.Manager;
using MediaBrowser.Common;
@@ -103,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
@@ -149,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,
@@ -184,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;
@@ -259,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; }
@@ -973,7 +974,7 @@ namespace Emby.Server.Implementations
yield return typeof(IServerApplicationHost).Assembly;
// Include composable parts in the Providers assembly
- yield return typeof(ProviderUtils).Assembly;
+ yield return typeof(ProviderManager).Assembly;
// Include composable parts in the Photos assembly
yield return typeof(PhotoProvider).Assembly;
@@ -999,6 +1000,9 @@ namespace Emby.Server.Implementations
// Network
yield return typeof(NetworkManager).Assembly;
+ // Hls
+ yield return typeof(DynamicHlsPlaylistGenerator).Assembly;
+
foreach (var i in GetAssembliesWithPartsInternal())
{
yield return i;
diff --git a/Emby.Server.Implementations/Channels/ChannelManager.cs b/Emby.Server.Implementations/Channels/ChannelManager.cs
index 43c8a451b..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.
@@ -264,11 +265,10 @@ namespace Emby.Server.Implementations.Channels
}
}
- return new QueryResult<Channel>
- {
- Items = all,
- TotalRecordCount = totalCount
- };
+ return new QueryResult<Channel>(
+ query.StartIndex,
+ totalCount,
+ all);
}
/// <inheritdoc />
@@ -285,11 +285,10 @@ namespace Emby.Server.Implementations.Channels
// TODO Fix The co-variant conversion (internalResult.Items) between Folder[] and BaseItem[], this can generate runtime issues.
var returnItems = _dtoService.GetBaseItemDtos(internalResult.Items, dtoOptions, user);
- var result = new QueryResult<BaseItemDto>
- {
- Items = returnItems,
- TotalRecordCount = internalResult.TotalRecordCount
- };
+ var result = new QueryResult<BaseItemDto>(
+ query.StartIndex,
+ internalResult.TotalRecordCount,
+ returnItems);
return result;
}
@@ -333,7 +332,7 @@ namespace Emby.Server.Implementations.Channels
private Channel GetChannelEntity(IChannel channel)
{
- return GetChannel(GetInternalChannelId(channel.Name)) ?? GetChannel(channel, CancellationToken.None).Result;
+ return GetChannel(GetInternalChannelId(channel.Name)) ?? GetChannel(channel, CancellationToken.None).GetAwaiter().GetResult();
}
private MediaSourceInfo[] GetSavedMediaSources(BaseItem item)
@@ -620,11 +619,10 @@ namespace Emby.Server.Implementations.Channels
var returnItems = _dtoService.GetBaseItemDtos(items, query.DtoOptions, query.User);
- var result = new QueryResult<BaseItemDto>
- {
- Items = returnItems,
- TotalRecordCount = totalRecordCount
- };
+ var result = new QueryResult<BaseItemDto>(
+ query.StartIndex,
+ totalRecordCount,
+ returnItems);
return result;
}
@@ -786,11 +784,10 @@ namespace Emby.Server.Implementations.Channels
var returnItems = _dtoService.GetBaseItemDtos(internalResult.Items, query.DtoOptions, query.User);
- var result = new QueryResult<BaseItemDto>
- {
- Items = returnItems,
- TotalRecordCount = internalResult.TotalRecordCount
- };
+ var result = new QueryResult<BaseItemDto>(
+ query.StartIndex,
+ internalResult.TotalRecordCount,
+ returnItems);
return result;
}
@@ -1217,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 5ab9e02fe..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)
{
@@ -2810,11 +2788,10 @@ namespace Emby.Server.Implementations.Data
if (!query.EnableTotalRecordCount || (!query.Limit.HasValue && (query.StartIndex ?? 0) == 0))
{
var returnList = GetItemList(query);
- return new QueryResult<BaseItem>
- {
- Items = returnList,
- TotalRecordCount = returnList.Count
- };
+ return new QueryResult<BaseItem>(
+ query.StartIndex,
+ returnList.Count,
+ returnList);
}
var now = DateTime.UtcNow;
@@ -2978,6 +2955,7 @@ namespace Emby.Server.Implementations.Data
ReadTransactionMode);
}
+ result.StartIndex = query.StartIndex ?? 0;
result.Items = list;
return result;
}
@@ -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>
- {
- Items = returnList,
- TotalRecordCount = returnList.Count
- };
- }
-
- 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.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));
}
@@ -5599,6 +5364,7 @@ AND Type = @InternalPersonType)");
result.TotalRecordCount = list.Count;
}
+ result.StartIndex = query.StartIndex ?? 0;
result.Items = list;
return result;
@@ -5682,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));
}
@@ -5758,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));
}
@@ -5891,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)
{
@@ -5913,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));
}
@@ -5945,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;
@@ -6253,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 7ba34e74a..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;
@@ -458,11 +457,6 @@ namespace Emby.Server.Implementations.Dto
}
}
- private string GetDtoId(BaseItem item)
- {
- return item.Id.ToString("N", CultureInfo.InvariantCulture);
- }
-
private void SetMusicVideoProperties(BaseItemDto dto, MusicVideo item)
{
if (!string.IsNullOrEmpty(item.Album))
@@ -584,7 +578,7 @@ namespace Emby.Server.Implementations.Dto
if (dictionary.TryGetValue(person.Name, out Person entity))
{
baseItemPerson.PrimaryImageTag = GetTagAndFillBlurhash(dto, entity, ImageType.Primary);
- baseItemPerson.Id = entity.Id.ToString("N", CultureInfo.InvariantCulture);
+ baseItemPerson.Id = entity.Id;
if (dto.ImageBlurHashes != null)
{
// Only add BlurHash for the person's image.
@@ -743,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;
}
@@ -894,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)
@@ -975,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();
@@ -1102,12 +1092,13 @@ namespace Emby.Server.Implementations.Dto
if (options.ContainsField(ItemFields.LocalTrailerCount))
{
- allExtras ??= item.GetExtras().ToArray();
- dto.LocalTrailerCount = allExtras.Count(i => i.ExtraType == ExtraType.Trailer);
-
if (item is IHasTrailers hasTrailers)
{
- dto.LocalTrailerCount += hasTrailers.GetTrailerCount();
+ dto.LocalTrailerCount = hasTrailers.GetTrailerCount();
+ }
+ else
+ {
+ dto.LocalTrailerCount = (allExtras ?? item.GetExtras()).Count(i => i.ExtraType == ExtraType.Trailer);
}
}
@@ -1323,7 +1314,7 @@ namespace Emby.Server.Implementations.Dto
if (image != null)
{
- dto.ParentLogoItemId = GetDtoId(parent);
+ dto.ParentLogoItemId = parent.Id;
dto.ParentLogoImageTag = GetTagAndFillBlurhash(dto, parent, image);
}
}
@@ -1334,7 +1325,7 @@ namespace Emby.Server.Implementations.Dto
if (image != null)
{
- dto.ParentArtItemId = GetDtoId(parent);
+ dto.ParentArtItemId = parent.Id;
dto.ParentArtImageTag = GetTagAndFillBlurhash(dto, parent, image);
}
}
@@ -1345,7 +1336,7 @@ namespace Emby.Server.Implementations.Dto
if (image != null)
{
- dto.ParentThumbItemId = GetDtoId(parent);
+ dto.ParentThumbItemId = parent.Id;
dto.ParentThumbImageTag = GetTagAndFillBlurhash(dto, parent, image);
}
}
@@ -1356,7 +1347,7 @@ namespace Emby.Server.Implementations.Dto
if (images.Count > 0)
{
- dto.ParentBackdropItemId = GetDtoId(parent);
+ dto.ParentBackdropItemId = parent.Id;
dto.ParentBackdropImageTags = GetTagsAndFillBlurhashes(dto, parent, ImageType.Backdrop, images);
}
}
diff --git a/Emby.Server.Implementations/Emby.Server.Implementations.csproj b/Emby.Server.Implementations/Emby.Server.Implementations.csproj
index 1e09a98cf..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" />
@@ -55,6 +55,10 @@
<!-- Code Analyzers-->
<ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
+ <PackageReference Include="Microsoft.CodeAnalysis.BannedApiAnalyzers" Version="3.3.3">
+ <PrivateAssets>all</PrivateAssets>
+ <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
+ </PackageReference>
<PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" />
<PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.376" PrivateAssets="All" />
<PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
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/BaseDynamicImageProvider.cs b/Emby.Server.Implementations/Images/BaseDynamicImageProvider.cs
index 758986945..57c2f1a5e 100644
--- a/Emby.Server.Implementations/Images/BaseDynamicImageProvider.cs
+++ b/Emby.Server.Implementations/Images/BaseDynamicImageProvider.cs
@@ -135,7 +135,7 @@ namespace Emby.Server.Implementations.Images
protected virtual IEnumerable<string> GetStripCollageImagePaths(BaseItem primaryItem, IEnumerable<BaseItem> items)
{
- var useBackdrop = primaryItem is CollectionFolder;
+ var useBackdrop = primaryItem is CollectionFolder || primaryItem is UserView;
return items
.Select(i =>
{
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/Images/CollectionFolderImageProvider.cs b/Emby.Server.Implementations/Images/CollectionFolderImageProvider.cs
index 7958eb8f5..8a0e627b9 100644
--- a/Emby.Server.Implementations/Images/CollectionFolderImageProvider.cs
+++ b/Emby.Server.Implementations/Images/CollectionFolderImageProvider.cs
@@ -42,6 +42,10 @@ namespace Emby.Server.Implementations.Images
{
includeItemTypes = new[] { BaseItemKind.MusicAlbum };
}
+ else if (string.Equals(viewType, CollectionType.MusicVideos, StringComparison.Ordinal))
+ {
+ includeItemTypes = new[] { BaseItemKind.MusicVideo };
+ }
else if (string.Equals(viewType, CollectionType.Books, StringComparison.Ordinal))
{
includeItemTypes = new[] { BaseItemKind.Book, BaseItemKind.AudioBook };
diff --git a/Emby.Server.Implementations/Images/DynamicImageProvider.cs b/Emby.Server.Implementations/Images/DynamicImageProvider.cs
index 575680653..9f9a4902a 100644
--- a/Emby.Server.Implementations/Images/DynamicImageProvider.cs
+++ b/Emby.Server.Implementations/Images/DynamicImageProvider.cs
@@ -84,16 +84,20 @@ namespace Emby.Server.Implementations.Images
}).GroupBy(x => x.Id)
.Select(x => x.First());
+ List<BaseItem> returnItems;
if (isUsingCollectionStrip)
{
- return items
+ returnItems = items
.Where(i => i.HasImage(ImageType.Primary) || i.HasImage(ImageType.Thumb))
.ToList();
+ returnItems.Shuffle();
+ return returnItems;
}
-
- return items
+ returnItems = items
.Where(i => i.HasImage(ImageType.Primary))
.ToList();
+ returnItems.Shuffle();
+ return returnItems;
}
protected override bool Supports(BaseItem item)
diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs
index bd0c178fd..0770bdbc3 100644
--- a/Emby.Server.Implementations/Library/LibraryManager.cs
+++ b/Emby.Server.Implementations/Library/LibraryManager.cs
@@ -45,8 +45,8 @@ 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.DependencyInjection;
using Microsoft.Extensions.Logging;
using Episode = MediaBrowser.Controller.Entities.TV.Episode;
using EpisodeInfo = Emby.Naming.TV.EpisodeInfo;
@@ -100,7 +100,7 @@ namespace Emby.Server.Implementations.Library
/// Initializes a new instance of the <see cref="LibraryManager" /> class.
/// </summary>
/// <param name="appHost">The application host.</param>
- /// <param name="logger">The logger.</param>
+ /// <param name="loggerFactory">The logger factory.</param>
/// <param name="taskManager">The task manager.</param>
/// <param name="userManager">The user manager.</param>
/// <param name="configurationManager">The configuration manager.</param>
@@ -116,7 +116,7 @@ namespace Emby.Server.Implementations.Library
/// <param name="namingOptions">The naming options.</param>
public LibraryManager(
IServerApplicationHost appHost,
- ILogger<LibraryManager> logger,
+ ILoggerFactory loggerFactory,
ITaskManager taskManager,
IUserManager userManager,
IServerConfigurationManager configurationManager,
@@ -132,7 +132,7 @@ namespace Emby.Server.Implementations.Library
NamingOptions namingOptions)
{
_appHost = appHost;
- _logger = logger;
+ _logger = loggerFactory.CreateLogger<LibraryManager>();
_taskManager = taskManager;
_userManager = userManager;
_configurationManager = configurationManager;
@@ -147,7 +147,7 @@ namespace Emby.Server.Implementations.Library
_memoryCache = memoryCache;
_namingOptions = namingOptions;
- _extraResolver = new ExtraResolver(namingOptions);
+ _extraResolver = new ExtraResolver(loggerFactory.CreateLogger<ExtraResolver>(), namingOptions);
_configurationManager.ConfigurationUpdated += ConfigurationUpdated;
@@ -680,9 +680,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 +1005,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 +1029,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>
@@ -1360,10 +1343,10 @@ namespace Emby.Server.Implementations.Library
return _itemRepository.GetItems(query);
}
- return new QueryResult<BaseItem>
- {
- Items = _itemRepository.GetItemList(query)
- };
+ return new QueryResult<BaseItem>(
+ query.StartIndex,
+ null,
+ _itemRepository.GetItemList(query));
}
public List<Guid> GetItemIds(InternalItemsQuery query)
@@ -1493,10 +1476,10 @@ namespace Emby.Server.Implementations.Library
return _itemRepository.GetItems(query);
}
- return new QueryResult<BaseItem>
- {
- Items = _itemRepository.GetItemList(query)
- };
+ return new QueryResult<BaseItem>(
+ query.StartIndex,
+ null,
+ _itemRepository.GetItemList(query));
}
private void SetTopParentIdsOrAncestors(InternalItemsQuery query, List<BaseItem> parents)
@@ -1651,27 +1634,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>
@@ -2014,16 +1976,16 @@ namespace Emby.Server.Implementations.Library
public Task UpdateItemAsync(BaseItem item, BaseItem parent, ItemUpdateType updateReason, CancellationToken cancellationToken)
=> UpdateItemsAsync(new[] { item }, parent, updateReason, cancellationToken);
- public Task RunMetadataSavers(BaseItem item, ItemUpdateType updateReason)
+ public async Task RunMetadataSavers(BaseItem item, ItemUpdateType updateReason)
{
if (item.IsFileProtocol)
{
- ProviderManager.SaveMetadata(item, updateReason);
+ await ProviderManager.SaveMetadataAsync(item, updateReason).ConfigureAwait(false);
}
item.DateLastSaved = DateTime.UtcNow;
- return UpdateImagesAsync(item, updateReason >= ItemUpdateType.ImageUpdate);
+ await UpdateImagesAsync(item, updateReason >= ItemUpdateType.ImageUpdate).ConfigureAwait(false);
}
/// <summary>
@@ -2469,24 +2431,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 +2438,7 @@ namespace Emby.Server.Implementations.Library
return GetItemById(parentId.Value);
}
- if (userId.HasValue && userId != Guid.Empty)
+ if (userId.HasValue && !userId.Equals(default))
{
return GetUserRootFolder();
}
@@ -2686,7 +2630,7 @@ namespace Emby.Server.Implementations.Library
};
}
- public IEnumerable<BaseItem> FindExtras(BaseItem owner, List<FileSystemMetadata> fileSystemChildren, IDirectoryService directoryService)
+ public IEnumerable<BaseItem> FindExtras(BaseItem owner, IReadOnlyList<FileSystemMetadata> fileSystemChildren, IDirectoryService directoryService)
{
var ownerVideoInfo = VideoResolver.Resolve(owner.Path, owner.IsFolder, _namingOptions);
if (ownerVideoInfo == null)
@@ -2784,16 +2728,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/Library/Resolvers/BaseVideoResolver.cs b/Emby.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs
index 9222a9479..3d6b9f3b6 100644
--- a/Emby.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs
+++ b/Emby.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs
@@ -12,6 +12,7 @@ using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities;
+using Microsoft.Extensions.Logging;
namespace Emby.Server.Implementations.Library.Resolvers
{
@@ -22,8 +23,11 @@ namespace Emby.Server.Implementations.Library.Resolvers
public abstract class BaseVideoResolver<T> : MediaBrowser.Controller.Resolvers.ItemResolver<T>
where T : Video, new()
{
- protected BaseVideoResolver(NamingOptions namingOptions)
+ private readonly ILogger _logger;
+
+ protected BaseVideoResolver(ILogger logger, NamingOptions namingOptions)
{
+ _logger = logger;
NamingOptions = namingOptions;
}
@@ -156,19 +160,26 @@ namespace Emby.Server.Implementations.Library.Resolvers
}
else
{
- // use disc-utils, both DVDs and BDs use UDF filesystem
- using (var videoFileStream = File.Open(video.Path, FileMode.Open, FileAccess.Read))
- using (UdfReader udfReader = new UdfReader(videoFileStream))
+ try
{
- if (udfReader.DirectoryExists("VIDEO_TS"))
- {
- video.IsoType = IsoType.Dvd;
- }
- else if (udfReader.DirectoryExists("BDMV"))
+ // use disc-utils, both DVDs and BDs use UDF filesystem
+ using (var videoFileStream = File.Open(video.Path, FileMode.Open, FileAccess.Read))
+ using (UdfReader udfReader = new UdfReader(videoFileStream))
{
- video.IsoType = IsoType.BluRay;
+ if (udfReader.DirectoryExists("VIDEO_TS"))
+ {
+ video.IsoType = IsoType.Dvd;
+ }
+ else if (udfReader.DirectoryExists("BDMV"))
+ {
+ video.IsoType = IsoType.BluRay;
+ }
}
}
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Error opening UDF/ISO image: {Value}", video.Path ?? video.Name);
+ }
}
}
}
diff --git a/Emby.Server.Implementations/Library/Resolvers/ExtraResolver.cs b/Emby.Server.Implementations/Library/Resolvers/ExtraResolver.cs
index 3d06ceb5e..408e640f9 100644
--- a/Emby.Server.Implementations/Library/Resolvers/ExtraResolver.cs
+++ b/Emby.Server.Implementations/Library/Resolvers/ExtraResolver.cs
@@ -6,6 +6,7 @@ using Emby.Naming.Video;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Resolvers;
using MediaBrowser.Model.Entities;
+using Microsoft.Extensions.Logging;
using static Emby.Naming.Video.ExtraRuleResolver;
namespace Emby.Server.Implementations.Library.Resolvers
@@ -20,14 +21,15 @@ namespace Emby.Server.Implementations.Library.Resolvers
private readonly IItemResolver[] _videoResolvers;
/// <summary>
- /// Initializes an new instance of the <see cref="ExtraResolver"/> class.
+ /// Initializes a new instance of the <see cref="ExtraResolver"/> class.
/// </summary>
+ /// <param name="logger">The logger.</param>
/// <param name="namingOptions">An instance of <see cref="NamingOptions"/>.</param>
- public ExtraResolver(NamingOptions namingOptions)
+ public ExtraResolver(ILogger<ExtraResolver> logger, NamingOptions namingOptions)
{
_namingOptions = namingOptions;
- _trailerResolvers = new IItemResolver[] { new GenericVideoResolver<Trailer>(namingOptions) };
- _videoResolvers = new IItemResolver[] { new GenericVideoResolver<Video>(namingOptions) };
+ _trailerResolvers = new IItemResolver[] { new GenericVideoResolver<Trailer>(logger, namingOptions) };
+ _videoResolvers = new IItemResolver[] { new GenericVideoResolver<Video>(logger, namingOptions) };
}
/// <summary>
diff --git a/Emby.Server.Implementations/Library/Resolvers/GenericVideoResolver.cs b/Emby.Server.Implementations/Library/Resolvers/GenericVideoResolver.cs
index b8554bd51..5e33b402d 100644
--- a/Emby.Server.Implementations/Library/Resolvers/GenericVideoResolver.cs
+++ b/Emby.Server.Implementations/Library/Resolvers/GenericVideoResolver.cs
@@ -1,7 +1,8 @@
-#nullable disable
+#nullable disable
using Emby.Naming.Common;
using MediaBrowser.Controller.Entities;
+using Microsoft.Extensions.Logging;
namespace Emby.Server.Implementations.Library.Resolvers
{
@@ -15,9 +16,10 @@ namespace Emby.Server.Implementations.Library.Resolvers
/// <summary>
/// Initializes a new instance of the <see cref="GenericVideoResolver{T}"/> class.
/// </summary>
+ /// <param name="logger">The logger.</param>
/// <param name="namingOptions">The naming options.</param>
- public GenericVideoResolver(NamingOptions namingOptions)
- : base(namingOptions)
+ public GenericVideoResolver(ILogger logger, NamingOptions namingOptions)
+ : base(logger, namingOptions)
{
}
}
diff --git a/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs b/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs
index 1a9295dc8..140c4272e 100644
--- a/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs
+++ b/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs
@@ -17,6 +17,7 @@ using MediaBrowser.Controller.Providers;
using MediaBrowser.Controller.Resolvers;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.IO;
+using Microsoft.Extensions.Logging;
namespace Emby.Server.Implementations.Library.Resolvers.Movies
{
@@ -32,7 +33,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
CollectionType.Movies,
CollectionType.HomeVideos,
CollectionType.MusicVideos,
- CollectionType.Movies,
+ CollectionType.TvShows,
CollectionType.Photos
};
@@ -40,9 +41,10 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
/// Initializes a new instance of the <see cref="MovieResolver"/> class.
/// </summary>
/// <param name="imageProcessor">The image processor.</param>
+ /// <param name="logger">The logger.</param>
/// <param name="namingOptions">The naming options.</param>
- public MovieResolver(IImageProcessor imageProcessor, NamingOptions namingOptions)
- : base(namingOptions)
+ public MovieResolver(IImageProcessor imageProcessor, ILogger<MovieResolver> logger, NamingOptions namingOptions)
+ : base(logger, namingOptions)
{
_imageProcessor = imageProcessor;
}
@@ -128,10 +130,9 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
return movie?.ExtraType == null ? movie : null;
}
- // Owned items will be caught by the video extra resolver
if (args.Parent == null)
{
- return null;
+ return base.Resolve(args);
}
if (IsInvalid(args.Parent, collectionType))
@@ -222,6 +223,11 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
return ResolveVideos<Movie>(parent, files, true, collectionType, true);
}
+ if (string.Equals(collectionType, CollectionType.TvShows, StringComparison.OrdinalIgnoreCase))
+ {
+ return ResolveVideos<Episode>(parent, files, true, collectionType, true);
+ }
+
return null;
}
diff --git a/Emby.Server.Implementations/Library/Resolvers/TV/EpisodeResolver.cs b/Emby.Server.Implementations/Library/Resolvers/TV/EpisodeResolver.cs
index be9905647..bfa73af2f 100644
--- a/Emby.Server.Implementations/Library/Resolvers/TV/EpisodeResolver.cs
+++ b/Emby.Server.Implementations/Library/Resolvers/TV/EpisodeResolver.cs
@@ -6,6 +6,7 @@ using Emby.Naming.Common;
using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Library;
using MediaBrowser.Model.Entities;
+using Microsoft.Extensions.Logging;
namespace Emby.Server.Implementations.Library.Resolvers.TV
{
@@ -17,9 +18,10 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV
/// <summary>
/// Initializes a new instance of the <see cref="EpisodeResolver"/> class.
/// </summary>
+ /// <param name="logger">The logger.</param>
/// <param name="namingOptions">The naming options.</param>
- public EpisodeResolver(NamingOptions namingOptions)
- : base(namingOptions)
+ public EpisodeResolver(ILogger<EpisodeResolver> logger, NamingOptions namingOptions)
+ : base(logger, namingOptions)
{
}
diff --git a/Emby.Server.Implementations/Library/SearchEngine.cs b/Emby.Server.Implementations/Library/SearchEngine.cs
index 55911933a..70d9cbc98 100644
--- a/Emby.Server.Implementations/Library/SearchEngine.cs
+++ b/Emby.Server.Implementations/Library/SearchEngine.cs
@@ -48,12 +48,10 @@ namespace Emby.Server.Implementations.Library
results = results.GetRange(0, Math.Min(query.Limit.Value, results.Count));
}
- return new QueryResult<SearchHintInfo>
- {
- TotalRecordCount = totalRecordCount,
-
- Items = results
- };
+ return new QueryResult<SearchHintInfo>(
+ query.StartIndex,
+ totalRecordCount,
+ results);
}
private static void AddIfMissing(List<BaseItemKind> list, BaseItemKind value)
diff --git a/Emby.Server.Implementations/Library/SplashscreenPostScanTask.cs b/Emby.Server.Implementations/Library/SplashscreenPostScanTask.cs
new file mode 100644
index 000000000..320685b1f
--- /dev/null
+++ b/Emby.Server.Implementations/Library/SplashscreenPostScanTask.cs
@@ -0,0 +1,79 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using Jellyfin.Data.Enums;
+using MediaBrowser.Controller.Drawing;
+using MediaBrowser.Controller.Dto;
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.Persistence;
+using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.Querying;
+using Microsoft.Extensions.Logging;
+
+namespace Emby.Server.Implementations.Library;
+
+/// <summary>
+/// The splashscreen post scan task.
+/// </summary>
+public class SplashscreenPostScanTask : ILibraryPostScanTask
+{
+ private readonly IItemRepository _itemRepository;
+ private readonly IImageEncoder _imageEncoder;
+ private readonly ILogger<SplashscreenPostScanTask> _logger;
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="SplashscreenPostScanTask"/> class.
+ /// </summary>
+ /// <param name="itemRepository">Instance of the <see cref="IItemRepository"/> interface.</param>
+ /// <param name="imageEncoder">Instance of the <see cref="IImageEncoder"/> interface.</param>
+ /// <param name="logger">Instance of the <see cref="ILogger{SplashscreenPostScanTask}"/> interface.</param>
+ public SplashscreenPostScanTask(
+ IItemRepository itemRepository,
+ IImageEncoder imageEncoder,
+ ILogger<SplashscreenPostScanTask> logger)
+ {
+ _itemRepository = itemRepository;
+ _imageEncoder = imageEncoder;
+ _logger = logger;
+ }
+
+ /// <inheritdoc />
+ public Task Run(IProgress<double> progress, CancellationToken cancellationToken)
+ {
+ var posters = GetItemsWithImageType(ImageType.Primary).Select(x => x.GetImages(ImageType.Primary).First().Path).ToList();
+ var backdrops = GetItemsWithImageType(ImageType.Thumb).Select(x => x.GetImages(ImageType.Thumb).First().Path).ToList();
+ if (backdrops.Count == 0)
+ {
+ // Thumb images fit better because they include the title in the image but are not provided with TMDb.
+ // Using backdrops as a fallback to generate an image at all
+ _logger.LogDebug("No thumb images found. Using backdrops to generate splashscreen");
+ backdrops = GetItemsWithImageType(ImageType.Backdrop).Select(x => x.GetImages(ImageType.Backdrop).First().Path).ToList();
+ }
+
+ _imageEncoder.CreateSplashscreen(posters, backdrops);
+ return Task.CompletedTask;
+ }
+
+ private IReadOnlyList<BaseItem> GetItemsWithImageType(ImageType imageType)
+ {
+ // TODO make included libraries configurable
+ return _itemRepository.GetItemList(new InternalItemsQuery
+ {
+ CollapseBoxSetItems = false,
+ Recursive = true,
+ DtoOptions = new DtoOptions(false),
+ ImageTypes = new[] { imageType },
+ Limit = 30,
+ // TODO max parental rating configurable
+ MaxParentalRating = 10,
+ OrderBy = new[]
+ {
+ (ItemSortBy.Random, SortOrder.Ascending)
+ },
+ IncludeItemTypes = new[] { BaseItemKind.Movie, BaseItemKind.Series }
+ });
+ }
+}
diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/DirectRecorder.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/DirectRecorder.cs
index 6937cc097..b2d25fdae 100644
--- a/Emby.Server.Implementations/LiveTv/EmbyTV/DirectRecorder.cs
+++ b/Emby.Server.Implementations/LiveTv/EmbyTV/DirectRecorder.cs
@@ -46,7 +46,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
{
Directory.CreateDirectory(Path.GetDirectoryName(targetFile) ?? throw new ArgumentException("Path can't be a root directory.", nameof(targetFile)));
- using (var output = new FileStream(targetFile, FileMode.CreateNew, FileAccess.Write, FileShare.Read, IODefaults.FileStreamBufferSize, FileOptions.Asynchronous))
+ await using (var output = new FileStream(targetFile, FileMode.CreateNew, FileAccess.Write, FileShare.Read, IODefaults.FileStreamBufferSize, FileOptions.Asynchronous))
{
onStarted();
@@ -56,14 +56,16 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
using var durationToken = new CancellationTokenSource(duration);
using var cancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, durationToken.Token);
var linkedCancellationToken = cancellationTokenSource.Token;
-
- await using var fileStream = new ProgressiveFileStream(directStreamProvider.GetStream());
- await _streamHelper.CopyToAsync(
- fileStream,
- output,
- IODefaults.CopyToBufferSize,
- 1000,
- linkedCancellationToken).ConfigureAwait(false);
+ var fileStream = new ProgressiveFileStream(directStreamProvider.GetStream());
+ await using (fileStream.ConfigureAwait(false))
+ {
+ await _streamHelper.CopyToAsync(
+ fileStream,
+ output,
+ IODefaults.CopyToBufferSize,
+ 1000,
+ linkedCancellationToken).ConfigureAwait(false);
+ }
}
_logger.LogInformation("Recording completed: {FilePath}", targetFile);
diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs
index e7834ffd6..bba584854 100644
--- a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs
+++ b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs
@@ -1819,16 +1819,16 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
if (timer.IsProgramSeries)
{
- SaveSeriesNfo(timer, seriesPath);
- SaveVideoNfo(timer, recordingPath, program, false);
+ await SaveSeriesNfoAsync(timer, seriesPath).ConfigureAwait(false);
+ await SaveVideoNfoAsync(timer, recordingPath, program, false).ConfigureAwait(false);
}
else if (!timer.IsMovie || timer.IsSports || timer.IsNews)
{
- SaveVideoNfo(timer, recordingPath, program, true);
+ await SaveVideoNfoAsync(timer, recordingPath, program, true).ConfigureAwait(false);
}
else
{
- SaveVideoNfo(timer, recordingPath, program, false);
+ await SaveVideoNfoAsync(timer, recordingPath, program, false).ConfigureAwait(false);
}
await SaveRecordingImages(recordingPath, program).ConfigureAwait(false);
@@ -1839,7 +1839,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
}
}
- private void SaveSeriesNfo(TimerInfo timer, string seriesPath)
+ private async Task SaveSeriesNfoAsync(TimerInfo timer, string seriesPath)
{
var nfoPath = Path.Combine(seriesPath, "tvshow.nfo");
@@ -1848,61 +1848,62 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
return;
}
- using (var stream = new FileStream(nfoPath, FileMode.CreateNew, FileAccess.Write, FileShare.None))
+ await using (var stream = new FileStream(nfoPath, FileMode.CreateNew, FileAccess.Write, FileShare.None))
{
var settings = new XmlWriterSettings
{
Indent = true,
- Encoding = Encoding.UTF8
+ Encoding = Encoding.UTF8,
+ Async = true
};
- using (var writer = XmlWriter.Create(stream, settings))
+ await using (var writer = XmlWriter.Create(stream, settings))
{
- writer.WriteStartDocument(true);
- writer.WriteStartElement("tvshow");
+ await writer.WriteStartDocumentAsync(true).ConfigureAwait(false);
+ await writer.WriteStartElementAsync(null, "tvshow", null).ConfigureAwait(false);
string id;
if (timer.SeriesProviderIds.TryGetValue(MetadataProvider.Tvdb.ToString(), out id))
{
- writer.WriteElementString("id", id);
+ await writer.WriteElementStringAsync(null, "id", null, id).ConfigureAwait(false);
}
if (timer.SeriesProviderIds.TryGetValue(MetadataProvider.Imdb.ToString(), out id))
{
- writer.WriteElementString("imdb_id", id);
+ await writer.WriteElementStringAsync(null, "imdb_id", null, id).ConfigureAwait(false);
}
if (timer.SeriesProviderIds.TryGetValue(MetadataProvider.Tmdb.ToString(), out id))
{
- writer.WriteElementString("tmdbid", id);
+ await writer.WriteElementStringAsync(null, "tmdbid", null, id).ConfigureAwait(false);
}
if (timer.SeriesProviderIds.TryGetValue(MetadataProvider.Zap2It.ToString(), out id))
{
- writer.WriteElementString("zap2itid", id);
+ await writer.WriteElementStringAsync(null, "zap2itid", null, id).ConfigureAwait(false);
}
if (!string.IsNullOrWhiteSpace(timer.Name))
{
- writer.WriteElementString("title", timer.Name);
+ await writer.WriteElementStringAsync(null, "title", null, timer.Name).ConfigureAwait(false);
}
if (!string.IsNullOrWhiteSpace(timer.OfficialRating))
{
- writer.WriteElementString("mpaa", timer.OfficialRating);
+ await writer.WriteElementStringAsync(null, "mpaa", null, timer.OfficialRating).ConfigureAwait(false);
}
foreach (var genre in timer.Genres)
{
- writer.WriteElementString("genre", genre);
+ await writer.WriteElementStringAsync(null, "genre", null, genre).ConfigureAwait(false);
}
- writer.WriteEndElement();
- writer.WriteEndDocument();
+ await writer.WriteEndElementAsync().ConfigureAwait(false);
+ await writer.WriteEndDocumentAsync().ConfigureAwait(false);
}
}
}
- private void SaveVideoNfo(TimerInfo timer, string recordingPath, BaseItem item, bool lockData)
+ private async Task SaveVideoNfoAsync(TimerInfo timer, string recordingPath, BaseItem item, bool lockData)
{
var nfoPath = Path.ChangeExtension(recordingPath, ".nfo");
@@ -1911,29 +1912,30 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
return;
}
- using (var stream = new FileStream(nfoPath, FileMode.CreateNew, FileAccess.Write, FileShare.None))
+ await using (var stream = new FileStream(nfoPath, FileMode.CreateNew, FileAccess.Write, FileShare.None))
{
var settings = new XmlWriterSettings
{
Indent = true,
- Encoding = Encoding.UTF8
+ Encoding = Encoding.UTF8,
+ Async = true
};
var options = _config.GetNfoConfiguration();
var isSeriesEpisode = timer.IsProgramSeries;
- using (var writer = XmlWriter.Create(stream, settings))
+ await using (var writer = XmlWriter.Create(stream, settings))
{
- writer.WriteStartDocument(true);
+ await writer.WriteStartDocumentAsync(true).ConfigureAwait(false);
if (isSeriesEpisode)
{
- writer.WriteStartElement("episodedetails");
+ await writer.WriteStartElementAsync(null, "episodedetails", null).ConfigureAwait(false);
if (!string.IsNullOrWhiteSpace(timer.EpisodeTitle))
{
- writer.WriteElementString("title", timer.EpisodeTitle);
+ await writer.WriteElementStringAsync(null, "title", null, timer.EpisodeTitle).ConfigureAwait(false);
}
var premiereDate = item.PremiereDate ?? (!timer.IsRepeat ? DateTime.UtcNow : null);
@@ -1942,76 +1944,84 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
{
var formatString = options.ReleaseDateFormat;
- writer.WriteElementString(
+ await writer.WriteElementStringAsync(
+ null,
"aired",
- premiereDate.Value.ToLocalTime().ToString(formatString, CultureInfo.InvariantCulture));
+ null,
+ premiereDate.Value.ToLocalTime().ToString(formatString, CultureInfo.InvariantCulture)).ConfigureAwait(false);
}
if (item.IndexNumber.HasValue)
{
- writer.WriteElementString("episode", item.IndexNumber.Value.ToString(CultureInfo.InvariantCulture));
+ await writer.WriteElementStringAsync(null, "episode", null, item.IndexNumber.Value.ToString(CultureInfo.InvariantCulture)).ConfigureAwait(false);
}
if (item.ParentIndexNumber.HasValue)
{
- writer.WriteElementString("season", item.ParentIndexNumber.Value.ToString(CultureInfo.InvariantCulture));
+ await writer.WriteElementStringAsync(null, "season", null, item.ParentIndexNumber.Value.ToString(CultureInfo.InvariantCulture)).ConfigureAwait(false);
}
}
else
{
- writer.WriteStartElement("movie");
+ await writer.WriteStartElementAsync(null, "movie", null);
if (!string.IsNullOrWhiteSpace(item.Name))
{
- writer.WriteElementString("title", item.Name);
+ await writer.WriteElementStringAsync(null, "title", null, item.Name).ConfigureAwait(false);
}
if (!string.IsNullOrWhiteSpace(item.OriginalTitle))
{
- writer.WriteElementString("originaltitle", item.OriginalTitle);
+ await writer.WriteElementStringAsync(null, "originaltitle", null, item.OriginalTitle).ConfigureAwait(false);
}
if (item.PremiereDate.HasValue)
{
var formatString = options.ReleaseDateFormat;
- writer.WriteElementString(
+ await writer.WriteElementStringAsync(
+ null,
"premiered",
- item.PremiereDate.Value.ToLocalTime().ToString(formatString, CultureInfo.InvariantCulture));
- writer.WriteElementString(
+ null,
+ item.PremiereDate.Value.ToLocalTime().ToString(formatString, CultureInfo.InvariantCulture)).ConfigureAwait(false);
+ await writer.WriteElementStringAsync(
+ null,
"releasedate",
- item.PremiereDate.Value.ToLocalTime().ToString(formatString, CultureInfo.InvariantCulture));
+ null,
+ item.PremiereDate.Value.ToLocalTime().ToString(formatString, CultureInfo.InvariantCulture)).ConfigureAwait(false);
}
}
- writer.WriteElementString(
+ await writer.WriteElementStringAsync(
+ null,
"dateadded",
- DateTime.Now.ToString(DateAddedFormat, CultureInfo.InvariantCulture));
+ null,
+ DateTime.Now.ToString(DateAddedFormat, CultureInfo.InvariantCulture)).ConfigureAwait(false);
if (item.ProductionYear.HasValue)
{
- writer.WriteElementString("year", item.ProductionYear.Value.ToString(CultureInfo.InvariantCulture));
+ await writer.WriteElementStringAsync(null, "year", null, item.ProductionYear.Value.ToString(CultureInfo.InvariantCulture)).ConfigureAwait(false);
}
if (!string.IsNullOrEmpty(item.OfficialRating))
{
- writer.WriteElementString("mpaa", item.OfficialRating);
+ await writer.WriteElementStringAsync(null, "mpaa", null, item.OfficialRating).ConfigureAwait(false);
}
var overview = (item.Overview ?? string.Empty)
.StripHtml()
.Replace("&quot;", "'", StringComparison.Ordinal);
- writer.WriteElementString("plot", overview);
+ await writer.WriteElementStringAsync(null, "plot", null, overview).ConfigureAwait(false);
if (item.CommunityRating.HasValue)
{
- writer.WriteElementString("rating", item.CommunityRating.Value.ToString(CultureInfo.InvariantCulture));
+ await writer.WriteElementStringAsync(null, "rating", null, item.CommunityRating.Value.ToString(CultureInfo.InvariantCulture)).ConfigureAwait(false);
}
foreach (var genre in item.Genres)
{
- writer.WriteElementString("genre", genre);
+ await writer.WriteElementStringAsync(null, "genre", null, genre).ConfigureAwait(false);
}
var people = item.Id.Equals(Guid.Empty) ? new List<PersonInfo>() : _libraryManager.GetPeople(item);
@@ -2023,7 +2033,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
foreach (var person in directors)
{
- writer.WriteElementString("director", person);
+ await writer.WriteElementStringAsync(null, "director", null, person).ConfigureAwait(false);
}
var writers = people
@@ -2034,19 +2044,19 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
foreach (var person in writers)
{
- writer.WriteElementString("writer", person);
+ await writer.WriteElementStringAsync(null, "writer", null, person).ConfigureAwait(false);
}
foreach (var person in writers)
{
- writer.WriteElementString("credits", person);
+ await writer.WriteElementStringAsync(null, "credits", null, person).ConfigureAwait(false);
}
var tmdbCollection = item.GetProviderId(MetadataProvider.TmdbCollection);
if (!string.IsNullOrEmpty(tmdbCollection))
{
- writer.WriteElementString("collectionnumber", tmdbCollection);
+ await writer.WriteElementStringAsync(null, "collectionnumber", null, tmdbCollection).ConfigureAwait(false);
}
var imdb = item.GetProviderId(MetadataProvider.Imdb);
@@ -2054,10 +2064,10 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
{
if (!isSeriesEpisode)
{
- writer.WriteElementString("id", imdb);
+ await writer.WriteElementStringAsync(null, "id", null, imdb).ConfigureAwait(false);
}
- writer.WriteElementString("imdbid", imdb);
+ await writer.WriteElementStringAsync(null, "imdbid", null, imdb).ConfigureAwait(false);
// No need to lock if we have identified the content already
lockData = false;
@@ -2066,7 +2076,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
var tvdb = item.GetProviderId(MetadataProvider.Tvdb);
if (!string.IsNullOrEmpty(tvdb))
{
- writer.WriteElementString("tvdbid", tvdb);
+ await writer.WriteElementStringAsync(null, "tvdbid", null, tvdb).ConfigureAwait(false);
// No need to lock if we have identified the content already
lockData = false;
@@ -2075,7 +2085,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
var tmdb = item.GetProviderId(MetadataProvider.Tmdb);
if (!string.IsNullOrEmpty(tmdb))
{
- writer.WriteElementString("tmdbid", tmdb);
+ await writer.WriteElementStringAsync(null, "tmdbid", null, tmdb).ConfigureAwait(false);
// No need to lock if we have identified the content already
lockData = false;
@@ -2083,26 +2093,26 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
if (lockData)
{
- writer.WriteElementString("lockdata", true.ToString(CultureInfo.InvariantCulture).ToLowerInvariant());
+ await writer.WriteElementStringAsync(null, "lockdata", null, "true").ConfigureAwait(false);
}
if (item.CriticRating.HasValue)
{
- writer.WriteElementString("criticrating", item.CriticRating.Value.ToString(CultureInfo.InvariantCulture));
+ await writer.WriteElementStringAsync(null, "criticrating", null, item.CriticRating.Value.ToString(CultureInfo.InvariantCulture)).ConfigureAwait(false);
}
if (!string.IsNullOrWhiteSpace(item.Tagline))
{
- writer.WriteElementString("tagline", item.Tagline);
+ await writer.WriteElementStringAsync(null, "tagline", null, item.Tagline).ConfigureAwait(false);
}
foreach (var studio in item.Studios)
{
- writer.WriteElementString("studio", studio);
+ await writer.WriteElementStringAsync(null, "studio", null, studio).ConfigureAwait(false);
}
- writer.WriteEndElement();
- writer.WriteEndDocument();
+ await writer.WriteEndElementAsync().ConfigureAwait(false);
+ await writer.WriteEndDocumentAsync().ConfigureAwait(false);
}
}
}
diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs
index 7fa47e7db..582e61d79 100644
--- a/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs
+++ b/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs
@@ -24,18 +24,19 @@ 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;
private readonly IServerApplicationPaths _appPaths;
- private readonly TaskCompletionSource<bool> _taskCompletionSource = new TaskCompletionSource<bool>();
+ private readonly TaskCompletionSource<bool> _taskCompletionSource = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
private readonly IServerConfigurationManager _serverConfigurationManager;
private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.Options;
private bool _hasExited;
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/LiveTvDtoService.cs b/Emby.Server.Implementations/LiveTv/LiveTvDtoService.cs
index 323b96021..fbce7af2d 100644
--- a/Emby.Server.Implementations/LiveTv/LiveTvDtoService.cs
+++ b/Emby.Server.Implementations/LiveTv/LiveTvDtoService.cs
@@ -176,7 +176,7 @@ namespace Emby.Server.Implementations.LiveTv
try
{
dto.ParentThumbImageTag = _imageProcessor.GetImageCacheTag(librarySeries, image);
- dto.ParentThumbItemId = librarySeries.Id.ToString("N", CultureInfo.InvariantCulture);
+ dto.ParentThumbItemId = librarySeries.Id;
}
catch (Exception ex)
{
@@ -193,7 +193,7 @@ namespace Emby.Server.Implementations.LiveTv
{
_imageProcessor.GetImageCacheTag(librarySeries, image)
};
- dto.ParentBackdropItemId = librarySeries.Id.ToString("N", CultureInfo.InvariantCulture);
+ dto.ParentBackdropItemId = librarySeries.Id;
}
catch (Exception ex)
{
@@ -240,7 +240,7 @@ namespace Emby.Server.Implementations.LiveTv
_imageProcessor.GetImageCacheTag(program, image)
};
- dto.ParentBackdropItemId = program.Id.ToString("N", CultureInfo.InvariantCulture);
+ dto.ParentBackdropItemId = program.Id;
}
catch (Exception ex)
{
diff --git a/Emby.Server.Implementations/LiveTv/LiveTvManager.cs b/Emby.Server.Implementations/LiveTv/LiveTvManager.cs
index aa3598c8b..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
}
- });
+ };
}
}
@@ -857,11 +855,10 @@ namespace Emby.Server.Implementations.LiveTv
var returnArray = _dtoService.GetBaseItemDtos(queryResult.Items, options, user);
- return new QueryResult<BaseItemDto>
- {
- Items = returnArray,
- TotalRecordCount = queryResult.TotalRecordCount
- };
+ return new QueryResult<BaseItemDto>(
+ query.StartIndex,
+ queryResult.TotalRecordCount,
+ returnArray);
}
public QueryResult<BaseItem> GetRecommendedProgramsInternal(InternalItemsQuery query, DtoOptions options, CancellationToken cancellationToken)
@@ -910,29 +907,27 @@ namespace Emby.Server.Implementations.LiveTv
programs = programs.Take(query.Limit.Value);
}
- return new QueryResult<BaseItem>
- {
- Items = programs.ToArray(),
- TotalRecordCount = totalCount
- };
+ return new QueryResult<BaseItem>(
+ query.StartIndex,
+ totalCount,
+ programs.ToArray());
}
- public QueryResult<BaseItemDto> GetRecommendedPrograms(InternalItemsQuery query, DtoOptions options, CancellationToken cancellationToken)
+ public Task<QueryResult<BaseItemDto>> GetRecommendedProgramsAsync(InternalItemsQuery query, DtoOptions options, CancellationToken cancellationToken)
{
if (!(query.IsAiring ?? false))
{
- return GetPrograms(query, options, cancellationToken).Result;
+ return GetPrograms(query, options, cancellationToken);
}
RemoveFields(options);
var internalResult = GetRecommendedProgramsInternal(query, options, cancellationToken);
- return new QueryResult<BaseItemDto>
- {
- Items = _dtoService.GetBaseItemDtos(internalResult.Items, options, query.User),
- TotalRecordCount = internalResult.TotalRecordCount
- };
+ return Task.FromResult(new QueryResult<BaseItemDto>(
+ query.StartIndex,
+ internalResult.TotalRecordCount,
+ _dtoService.GetBaseItemDtos(internalResult.Items, options, query.User)));
}
private int GetRecommendationScore(LiveTvProgram program, User user, bool factorChannelWatchCount)
@@ -1541,11 +1536,10 @@ namespace Emby.Server.Implementations.LiveTv
var returnArray = _dtoService.GetBaseItemDtos(internalResult.Items, options, user);
- return new QueryResult<BaseItemDto>
- {
- Items = returnArray,
- TotalRecordCount = internalResult.TotalRecordCount
- };
+ return new QueryResult<BaseItemDto>(
+ query.StartIndex,
+ internalResult.TotalRecordCount,
+ returnArray);
}
private async Task<QueryResult<TimerInfo>> GetTimersInternal(TimerQuery query, CancellationToken cancellationToken)
@@ -1615,11 +1609,7 @@ namespace Emby.Server.Implementations.LiveTv
.OrderBy(i => i.StartDate)
.ToArray();
- return new QueryResult<TimerInfo>
- {
- Items = returnArray,
- TotalRecordCount = returnArray.Length
- };
+ return new QueryResult<TimerInfo>(returnArray);
}
public async Task<QueryResult<TimerInfoDto>> GetTimers(TimerQuery query, CancellationToken cancellationToken)
@@ -1701,11 +1691,7 @@ namespace Emby.Server.Implementations.LiveTv
.OrderBy(i => i.StartDate)
.ToArray();
- return new QueryResult<TimerInfoDto>
- {
- Items = returnArray,
- TotalRecordCount = returnArray.Length
- };
+ return new QueryResult<TimerInfoDto>(returnArray);
}
public async Task CancelTimer(string id)
@@ -1801,11 +1787,7 @@ namespace Emby.Server.Implementations.LiveTv
.Select(i => i.Item1)
.ToArray();
- return new QueryResult<SeriesTimerInfo>
- {
- Items = returnArray,
- TotalRecordCount = returnArray.Length
- };
+ return new QueryResult<SeriesTimerInfo>(returnArray);
}
public async Task<QueryResult<SeriesTimerInfoDto>> GetSeriesTimers(SeriesTimerQuery query, CancellationToken cancellationToken)
@@ -1855,11 +1837,7 @@ namespace Emby.Server.Implementations.LiveTv
})
.ToArray();
- return new QueryResult<SeriesTimerInfoDto>
- {
- Items = returnArray,
- TotalRecordCount = returnArray.Length
- };
+ return new QueryResult<SeriesTimerInfoDto>(returnArray);
}
public BaseItem GetLiveTvChannel(TimerInfo timer, ILiveTvService service)
@@ -2112,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/HdHomerun/HdHomerunUdpStream.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs
index a5edd35cc..6195c7648 100644
--- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs
+++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs
@@ -133,7 +133,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
}
}
- var taskCompletionSource = new TaskCompletionSource<bool>();
+ var taskCompletionSource = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
_ = StartStreaming(
udpClient,
@@ -186,7 +186,8 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
{
var resolved = false;
- using (var fileStream = new FileStream(file, FileMode.Create, FileAccess.Write, FileShare.Read))
+ var fileStream = new FileStream(file, FileMode.Create, FileAccess.Write, FileShare.Read);
+ await using (fileStream.ConfigureAwait(false))
{
while (true)
{
diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/LiveStream.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/LiveStream.cs
index 5581ba87c..2748794b3 100644
--- a/Emby.Server.Implementations/LiveTv/TunerHosts/LiveStream.cs
+++ b/Emby.Server.Implementations/LiveTv/TunerHosts/LiveStream.cs
@@ -97,7 +97,14 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
public Stream GetStream()
{
- var stream = GetInputStream(TempFilePath);
+ var stream = new FileStream(
+ TempFilePath,
+ FileMode.Open,
+ FileAccess.Read,
+ FileShare.ReadWrite,
+ IODefaults.FileStreamBufferSize,
+ FileOptions.SequentialScan | FileOptions.Asynchronous);
+
bool seekFile = (DateTime.UtcNow - DateOpened).TotalSeconds > 10;
if (seekFile)
{
@@ -107,15 +114,6 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
return stream;
}
- protected FileStream GetInputStream(string path)
- => new FileStream(
- path,
- FileMode.Open,
- FileAccess.Read,
- FileShare.ReadWrite,
- IODefaults.FileStreamBufferSize,
- FileOptions.SequentialScan | FileOptions.Asynchronous);
-
protected async Task DeleteTempFiles(string path, int retryCount = 0)
{
if (retryCount == 0)
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/LiveTv/TunerHosts/SharedHttpStream.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs
index ab4beb15b..e84e1e074 100644
--- a/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs
+++ b/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs
@@ -73,7 +73,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
SetTempFilePath("ts");
- var taskCompletionSource = new TaskCompletionSource<bool>();
+ var taskCompletionSource = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
_ = StartStreaming(response, taskCompletionSource, LiveStreamCancellationTokenSource.Token);
diff --git a/Emby.Server.Implementations/Localization/Core/cs.json b/Emby.Server.Implementations/Localization/Core/cs.json
index 25f51db16..7ee8d1040 100644
--- a/Emby.Server.Implementations/Localization/Core/cs.json
+++ b/Emby.Server.Implementations/Localization/Core/cs.json
@@ -23,7 +23,7 @@
"HeaderFavoriteShows": "Oblíbené seriály",
"HeaderFavoriteSongs": "Oblíbená hudba",
"HeaderLiveTV": "Televize",
- "HeaderNextUp": "Nadcházející",
+ "HeaderNextUp": "Další díly",
"HeaderRecordingGroups": "Skupiny nahrávek",
"HomeVideos": "Domácí videa",
"Inherit": "Zdědit",
@@ -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/fr.json b/Emby.Server.Implementations/Localization/Core/fr.json
index bfafe7650..e56ae6071 100644
--- a/Emby.Server.Implementations/Localization/Core/fr.json
+++ b/Emby.Server.Implementations/Localization/Core/fr.json
@@ -1,7 +1,7 @@
{
"Albums": "Albums",
"AppDeviceValues": "Application : {0}, Appareil : {1}",
- "Application": "Applications",
+ "Application": "Application",
"Artists": "Artistes",
"AuthenticationSucceededWithUserName": "{0} authentifié avec succès",
"Books": "Livres",
diff --git a/Emby.Server.Implementations/Localization/Core/ro.json b/Emby.Server.Implementations/Localization/Core/ro.json
index f8fad7b63..8af5449a7 100644
--- a/Emby.Server.Implementations/Localization/Core/ro.json
+++ b/Emby.Server.Implementations/Localization/Core/ro.json
@@ -117,5 +117,7 @@
"TaskCleanActivityLog": "Curăță Jurnalul de Activitate",
"Undefined": "Nedefinit",
"Forced": "Forțat",
- "Default": "Implicit"
+ "Default": "Implicit",
+ "TaskOptimizeDatabaseDescription": "Compactează baza de date și trunchiază spațiul liber. Rularea acestei sarcini după scanarea bibliotecii sau după efectuarea altor modificări care implică modificări ale bazei de date poate îmbunătăți performanța.",
+ "TaskOptimizeDatabase": "Optimizează baza de date"
}
diff --git a/Emby.Server.Implementations/Localization/Core/th.json b/Emby.Server.Implementations/Localization/Core/th.json
index 89fbb84b6..bed67fa4f 100644
--- a/Emby.Server.Implementations/Localization/Core/th.json
+++ b/Emby.Server.Implementations/Localization/Core/th.json
@@ -118,5 +118,6 @@
"TaskCleanActivityLog": "ล้างบันทึกกิจกรรม",
"Undefined": "ไม่ได้กำหนด",
"Forced": "บังคับใช้",
- "TaskOptimizeDatabase": "ปรับฐานข้อมูลให้เหมาะสม"
+ "TaskOptimizeDatabase": "ปรับปรุงประสิทธิภาพฐานข้อมูล",
+ "TaskOptimizeDatabaseDescription": "ลดขนาดการจัดเก็บฐานข้อมูล ใช้งานคำสั่งนี้หลังจากสแกนไลบรารีหรือหลังจากการเปลี่ยนแปลงฐานข้อมูล อาจจะทำให้ระบบทำงานเร็วขึ้น"
}
diff --git a/Emby.Server.Implementations/Localization/Core/ug.json b/Emby.Server.Implementations/Localization/Core/ug.json
new file mode 100644
index 000000000..aea93c7fa
--- /dev/null
+++ b/Emby.Server.Implementations/Localization/Core/ug.json
@@ -0,0 +1,9 @@
+{
+ "ChapterNameValue": "باب {0}",
+ "Channels": "قانال",
+ "CameraImageUploadedFrom": "{0} ئورۇندىن يېڭى سۈرەت چىقىرىلدى",
+ "Books": "كىتاب",
+ "AuthenticationSucceededWithUserName": "تىزىملىتىش مۇۋەپپەقىيەتلىك بول",
+ "Artists": "سەنئەتكار",
+ "Albums": "پىلاستىنكا"
+}
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/Net/UdpSocket.cs b/Emby.Server.Implementations/Net/UdpSocket.cs
index 0c451ccb6..bbbca4fc0 100644
--- a/Emby.Server.Implementations/Net/UdpSocket.cs
+++ b/Emby.Server.Implementations/Net/UdpSocket.cs
@@ -159,7 +159,7 @@ namespace Emby.Server.Implementations.Net
{
ThrowIfDisposed();
- var taskCompletion = new TaskCompletionSource<SocketReceiveResult>();
+ var taskCompletion = new TaskCompletionSource<SocketReceiveResult>(TaskCreationOptions.RunContinuationsAsynchronously);
bool isResultSet = false;
Action<IAsyncResult> callback = callbackResult =>
@@ -195,7 +195,7 @@ namespace Emby.Server.Implementations.Net
{
ThrowIfDisposed();
- var taskCompletion = new TaskCompletionSource<int>();
+ var taskCompletion = new TaskCompletionSource<int>(TaskCreationOptions.RunContinuationsAsynchronously);
bool isResultSet = false;
Action<IAsyncResult> callback = callbackResult =>
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 f7b3cfedc..065008157 100644
--- a/Emby.Server.Implementations/ScheduledTasks/Tasks/RefreshMediaLibraryTask.cs
+++ b/Emby.Server.Implementations/ScheduledTasks/Tasks/RefreshMediaLibraryTask.cs
@@ -25,8 +25,8 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks
/// <summary>
/// Initializes a new instance of the <see cref="RefreshMediaLibraryTask" /> class.
/// </summary>
- /// <param name="libraryManager">The library manager.</param>
- /// <param name="localization">The localization manager.</param>
+ /// <param name="libraryManager">Instance of the <see cref="ILibraryManager"/> interface.</param>
+ /// <param name="localization">Instance of the <see cref="ILocalizationManager"/> interface.</param>
public RefreshMediaLibraryTask(ILibraryManager libraryManager, ILocalizationManager localization)
{
_libraryManager = libraryManager;
@@ -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 c994ffc90..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
})
@@ -304,11 +324,10 @@ namespace Emby.Server.Implementations.TV
items = items.Take(query.Limit.Value);
}
- return new QueryResult<BaseItem>
- {
- TotalRecordCount = totalCount,
- Items = items.ToArray()
- };
+ return new QueryResult<BaseItem>(
+ query.StartIndex,
+ totalCount,
+ items.ToArray());
}
}
}
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;
}