diff options
Diffstat (limited to 'Emby.Server.Implementations')
47 files changed, 461 insertions, 799 deletions
diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index ad6cbe167..7647827fb 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -191,7 +191,7 @@ namespace Emby.Server.Implementations /// Gets or sets the application paths. /// </summary> /// <value>The application paths.</value> - protected ServerApplicationPaths ApplicationPaths { get; set; } + protected IServerApplicationPaths ApplicationPaths { get; set; } /// <summary> /// Gets or sets all concrete types. @@ -235,7 +235,7 @@ namespace Emby.Server.Implementations /// Initializes a new instance of the <see cref="ApplicationHost" /> class. /// </summary> public ApplicationHost( - ServerApplicationPaths applicationPaths, + IServerApplicationPaths applicationPaths, ILoggerFactory loggerFactory, IStartupOptions options, IFileSystem fileSystem, @@ -553,8 +553,6 @@ namespace Emby.Server.Implementations serviceCollection.AddSingleton<IUserDataRepository, SqliteUserDataRepository>(); serviceCollection.AddSingleton<IUserDataManager, UserDataManager>(); - serviceCollection.AddSingleton<IDisplayPreferencesRepository, SqliteDisplayPreferencesRepository>(); - serviceCollection.AddSingleton<IItemRepository, SqliteItemRepository>(); serviceCollection.AddSingleton<IAuthenticationRepository, AuthenticationRepository>(); @@ -651,7 +649,6 @@ namespace Emby.Server.Implementations _httpServer = Resolve<IHttpServer>(); _httpClient = Resolve<IHttpClient>(); - ((SqliteDisplayPreferencesRepository)Resolve<IDisplayPreferencesRepository>()).Initialize(); ((AuthenticationRepository)Resolve<IAuthenticationRepository>()).Initialize(); SetStaticProperties(); @@ -796,7 +793,6 @@ namespace Emby.Server.Implementations Resolve<IMediaSourceManager>().AddParts(GetExports<IMediaSourceProvider>()); Resolve<INotificationManager>().AddParts(GetExports<INotificationService>(), GetExports<INotificationTypeFactory>()); - Resolve<IUserManager>().AddParts(GetExports<IAuthenticationProvider>(), GetExports<IPasswordResetProvider>()); Resolve<IIsoManager>().AddParts(GetExports<IIsoMounter>()); } diff --git a/Emby.Server.Implementations/Channels/ChannelManager.cs b/Emby.Server.Implementations/Channels/ChannelManager.cs index c803d9d82..8d6292867 100644 --- a/Emby.Server.Implementations/Channels/ChannelManager.cs +++ b/Emby.Server.Implementations/Channels/ChannelManager.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Concurrent; using System.Collections.Generic; using System.Globalization; using System.IO; @@ -7,6 +6,7 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; using Jellyfin.Data.Entities; +using Jellyfin.Data.Enums; using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Progress; using MediaBrowser.Controller.Channels; @@ -22,6 +22,7 @@ using MediaBrowser.Model.Entities; using MediaBrowser.Model.IO; using MediaBrowser.Model.Querying; using MediaBrowser.Model.Serialization; +using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.Logging; using Episode = MediaBrowser.Controller.Entities.TV.Episode; using Movie = MediaBrowser.Controller.Entities.Movies.Movie; @@ -45,10 +46,7 @@ namespace Emby.Server.Implementations.Channels private readonly IFileSystem _fileSystem; private readonly IJsonSerializer _jsonSerializer; private readonly IProviderManager _providerManager; - - private readonly ConcurrentDictionary<string, Tuple<DateTime, List<MediaSourceInfo>>> _channelItemMediaInfo = - new ConcurrentDictionary<string, Tuple<DateTime, List<MediaSourceInfo>>>(); - + private readonly IMemoryCache _memoryCache; private readonly SemaphoreSlim _resourcePool = new SemaphoreSlim(1, 1); /// <summary> @@ -63,6 +61,7 @@ namespace Emby.Server.Implementations.Channels /// <param name="userDataManager">The user data manager.</param> /// <param name="jsonSerializer">The JSON serializer.</param> /// <param name="providerManager">The provider manager.</param> + /// <param name="memoryCache">The memory cache.</param> public ChannelManager( IUserManager userManager, IDtoService dtoService, @@ -72,7 +71,8 @@ namespace Emby.Server.Implementations.Channels IFileSystem fileSystem, IUserDataManager userDataManager, IJsonSerializer jsonSerializer, - IProviderManager providerManager) + IProviderManager providerManager, + IMemoryCache memoryCache) { _userManager = userManager; _dtoService = dtoService; @@ -83,6 +83,7 @@ namespace Emby.Server.Implementations.Channels _userDataManager = userDataManager; _jsonSerializer = jsonSerializer; _providerManager = providerManager; + _memoryCache = memoryCache; } internal IChannel[] Channels { get; private set; } @@ -417,20 +418,15 @@ namespace Emby.Server.Implementations.Channels private async Task<IEnumerable<MediaSourceInfo>> GetChannelItemMediaSourcesInternal(IRequiresMediaInfoCallback channel, string id, CancellationToken cancellationToken) { - if (_channelItemMediaInfo.TryGetValue(id, out Tuple<DateTime, List<MediaSourceInfo>> cachedInfo)) + if (_memoryCache.TryGetValue(id, out List<MediaSourceInfo> cachedInfo)) { - if ((DateTime.UtcNow - cachedInfo.Item1).TotalMinutes < 5) - { - return cachedInfo.Item2; - } + return cachedInfo; } var mediaInfo = await channel.GetChannelItemMediaInfo(id, cancellationToken) .ConfigureAwait(false); var list = mediaInfo.ToList(); - - var item2 = new Tuple<DateTime, List<MediaSourceInfo>>(DateTime.UtcNow, list); - _channelItemMediaInfo.AddOrUpdate(id, item2, (key, oldValue) => item2); + _memoryCache.CreateEntry(id).SetValue(list).SetAbsoluteExpiration(DateTimeOffset.UtcNow.AddMinutes(5)); return list; } diff --git a/Emby.Server.Implementations/Data/SqliteDisplayPreferencesRepository.cs b/Emby.Server.Implementations/Data/SqliteDisplayPreferencesRepository.cs deleted file mode 100644 index 5597155a8..000000000 --- a/Emby.Server.Implementations/Data/SqliteDisplayPreferencesRepository.cs +++ /dev/null @@ -1,225 +0,0 @@ -#pragma warning disable CS1591 - -using System; -using System.Collections.Generic; -using System.Globalization; -using System.IO; -using System.Text.Json; -using System.Threading; -using MediaBrowser.Common.Configuration; -using MediaBrowser.Common.Extensions; -using MediaBrowser.Common.Json; -using MediaBrowser.Controller.Persistence; -using MediaBrowser.Model.Entities; -using MediaBrowser.Model.IO; -using Microsoft.Extensions.Logging; -using SQLitePCL.pretty; - -namespace Emby.Server.Implementations.Data -{ - /// <summary> - /// Class SQLiteDisplayPreferencesRepository. - /// </summary> - public class SqliteDisplayPreferencesRepository : BaseSqliteRepository, IDisplayPreferencesRepository - { - private readonly IFileSystem _fileSystem; - - private readonly JsonSerializerOptions _jsonOptions; - - public SqliteDisplayPreferencesRepository(ILogger<SqliteDisplayPreferencesRepository> logger, IApplicationPaths appPaths, IFileSystem fileSystem) - : base(logger) - { - _fileSystem = fileSystem; - - _jsonOptions = JsonDefaults.GetOptions(); - - DbFilePath = Path.Combine(appPaths.DataPath, "displaypreferences.db"); - } - - /// <summary> - /// Gets the name of the repository. - /// </summary> - /// <value>The name.</value> - public string Name => "SQLite"; - - public void Initialize() - { - try - { - InitializeInternal(); - } - catch (Exception ex) - { - Logger.LogError(ex, "Error loading database file. Will reset and retry."); - - _fileSystem.DeleteFile(DbFilePath); - - InitializeInternal(); - } - } - - /// <summary> - /// Opens the connection to the database. - /// </summary> - /// <returns>Task.</returns> - private void InitializeInternal() - { - string[] queries = - { - "create table if not exists userdisplaypreferences (id GUID NOT NULL, userId GUID NOT NULL, client text NOT NULL, data BLOB NOT NULL)", - "create unique index if not exists userdisplaypreferencesindex on userdisplaypreferences (id, userId, client)" - }; - - using (var connection = GetConnection()) - { - connection.RunQueries(queries); - } - } - - /// <summary> - /// Save the display preferences associated with an item in the repo. - /// </summary> - /// <param name="displayPreferences">The display preferences.</param> - /// <param name="userId">The user id.</param> - /// <param name="client">The client.</param> - /// <param name="cancellationToken">The cancellation token.</param> - /// <exception cref="ArgumentNullException">item</exception> - public void SaveDisplayPreferences(DisplayPreferences displayPreferences, Guid userId, string client, CancellationToken cancellationToken) - { - if (displayPreferences == null) - { - throw new ArgumentNullException(nameof(displayPreferences)); - } - - if (string.IsNullOrEmpty(displayPreferences.Id)) - { - throw new ArgumentException("Display preferences has an invalid Id", nameof(displayPreferences)); - } - - cancellationToken.ThrowIfCancellationRequested(); - - using (var connection = GetConnection()) - { - connection.RunInTransaction( - db => SaveDisplayPreferences(displayPreferences, userId, client, db), - TransactionMode); - } - } - - private void SaveDisplayPreferences(DisplayPreferences displayPreferences, Guid userId, string client, IDatabaseConnection connection) - { - var serialized = JsonSerializer.SerializeToUtf8Bytes(displayPreferences, _jsonOptions); - - using (var statement = connection.PrepareStatement("replace into userdisplaypreferences (id, userid, client, data) values (@id, @userId, @client, @data)")) - { - statement.TryBind("@id", new Guid(displayPreferences.Id).ToByteArray()); - statement.TryBind("@userId", userId.ToByteArray()); - statement.TryBind("@client", client); - statement.TryBind("@data", serialized); - - statement.MoveNext(); - } - } - - /// <summary> - /// Save all display preferences associated with a user in the repo. - /// </summary> - /// <param name="displayPreferences">The display preferences.</param> - /// <param name="userId">The user id.</param> - /// <param name="cancellationToken">The cancellation token.</param> - /// <exception cref="ArgumentNullException">item</exception> - public void SaveAllDisplayPreferences(IEnumerable<DisplayPreferences> displayPreferences, Guid userId, CancellationToken cancellationToken) - { - if (displayPreferences == null) - { - throw new ArgumentNullException(nameof(displayPreferences)); - } - - cancellationToken.ThrowIfCancellationRequested(); - - using (var connection = GetConnection()) - { - connection.RunInTransaction( - db => - { - foreach (var displayPreference in displayPreferences) - { - SaveDisplayPreferences(displayPreference, userId, displayPreference.Client, db); - } - }, - TransactionMode); - } - } - - /// <summary> - /// Gets the display preferences. - /// </summary> - /// <param name="displayPreferencesId">The display preferences id.</param> - /// <param name="userId">The user id.</param> - /// <param name="client">The client.</param> - /// <returns>Task{DisplayPreferences}.</returns> - /// <exception cref="ArgumentNullException">item</exception> - public DisplayPreferences GetDisplayPreferences(string displayPreferencesId, Guid userId, string client) - { - if (string.IsNullOrEmpty(displayPreferencesId)) - { - throw new ArgumentNullException(nameof(displayPreferencesId)); - } - - var guidId = displayPreferencesId.GetMD5(); - - using (var connection = GetConnection(true)) - { - using (var statement = connection.PrepareStatement("select data from userdisplaypreferences where id = @id and userId=@userId and client=@client")) - { - statement.TryBind("@id", guidId.ToByteArray()); - statement.TryBind("@userId", userId.ToByteArray()); - statement.TryBind("@client", client); - - foreach (var row in statement.ExecuteQuery()) - { - return Get(row); - } - } - } - - return new DisplayPreferences - { - Id = guidId.ToString("N", CultureInfo.InvariantCulture) - }; - } - - /// <summary> - /// Gets all display preferences for the given user. - /// </summary> - /// <param name="userId">The user id.</param> - /// <returns>Task{DisplayPreferences}.</returns> - /// <exception cref="ArgumentNullException">item</exception> - public IEnumerable<DisplayPreferences> GetAllDisplayPreferences(Guid userId) - { - var list = new List<DisplayPreferences>(); - - using (var connection = GetConnection(true)) - using (var statement = connection.PrepareStatement("select data from userdisplaypreferences where userId=@userId")) - { - statement.TryBind("@userId", userId.ToByteArray()); - - foreach (var row in statement.ExecuteQuery()) - { - list.Add(Get(row)); - } - } - - return list; - } - - private DisplayPreferences Get(IReadOnlyList<IResultSetValue> row) - => JsonSerializer.Deserialize<DisplayPreferences>(row[0].ToBlob(), _jsonOptions); - - public void SaveDisplayPreferences(DisplayPreferences displayPreferences, string userId, string client, CancellationToken cancellationToken) - => SaveDisplayPreferences(displayPreferences, new Guid(userId), client, cancellationToken); - - public DisplayPreferences GetDisplayPreferences(string displayPreferencesId, string userId, string client) - => GetDisplayPreferences(displayPreferencesId, new Guid(userId), client); - } -} diff --git a/Emby.Server.Implementations/Data/SqliteItemRepository.cs b/Emby.Server.Implementations/Data/SqliteItemRepository.cs index a6390b1ef..1c6d3cb94 100644 --- a/Emby.Server.Implementations/Data/SqliteItemRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteItemRepository.cs @@ -9,6 +9,7 @@ using System.Text; using System.Text.Json; using System.Threading; using Emby.Server.Implementations.Playlists; +using Jellyfin.Data.Enums; using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Json; using MediaBrowser.Controller; @@ -400,6 +401,8 @@ namespace Emby.Server.Implementations.Data "OwnerId" }; + private static readonly string _retriveItemColumnsSelectQuery = $"select {string.Join(',', _retriveItemColumns)} from TypedBaseItems where guid = @guid"; + private static readonly string[] _mediaStreamSaveColumns = { "ItemId", @@ -439,6 +442,12 @@ namespace Emby.Server.Implementations.Data "ColorTransfer" }; + private static readonly string _mediaStreamSaveColumnsInsertQuery = + $"insert into mediastreams ({string.Join(',', _mediaStreamSaveColumns)}) values "; + + private static readonly string _mediaStreamSaveColumnsSelectQuery = + $"select {string.Join(',', _mediaStreamSaveColumns)} from mediastreams where ItemId=@ItemId"; + private static readonly string[] _mediaAttachmentSaveColumns = { "ItemId", @@ -450,102 +459,15 @@ namespace Emby.Server.Implementations.Data "MIMEType" }; - private static readonly string _mediaAttachmentInsertPrefix; - - private static string GetSaveItemCommandText() - { - var saveColumns = new[] - { - "guid", - "type", - "data", - "Path", - "StartDate", - "EndDate", - "ChannelId", - "IsMovie", - "IsSeries", - "EpisodeTitle", - "IsRepeat", - "CommunityRating", - "CustomRating", - "IndexNumber", - "IsLocked", - "Name", - "OfficialRating", - "MediaType", - "Overview", - "ParentIndexNumber", - "PremiereDate", - "ProductionYear", - "ParentId", - "Genres", - "InheritedParentalRatingValue", - "SortName", - "ForcedSortName", - "RunTimeTicks", - "Size", - "DateCreated", - "DateModified", - "PreferredMetadataLanguage", - "PreferredMetadataCountryCode", - "Width", - "Height", - "DateLastRefreshed", - "DateLastSaved", - "IsInMixedFolder", - "LockedFields", - "Studios", - "Audio", - "ExternalServiceId", - "Tags", - "IsFolder", - "UnratedType", - "TopParentId", - "TrailerTypes", - "CriticRating", - "CleanName", - "PresentationUniqueKey", - "OriginalTitle", - "PrimaryVersionId", - "DateLastMediaAdded", - "Album", - "IsVirtualItem", - "SeriesName", - "UserDataKey", - "SeasonName", - "SeasonId", - "SeriesId", - "ExternalSeriesId", - "Tagline", - "ProviderIds", - "Images", - "ProductionLocations", - "ExtraIds", - "TotalBitrate", - "ExtraType", - "Artists", - "AlbumArtists", - "ExternalId", - "SeriesPresentationUniqueKey", - "ShowId", - "OwnerId" - }; - - var saveItemCommandCommandText = "replace into TypedBaseItems (" + string.Join(",", saveColumns) + ") values ("; - - for (var i = 0; i < saveColumns.Length; i++) - { - if (i != 0) - { - saveItemCommandCommandText += ","; - } + private static readonly string _mediaAttachmentSaveColumnsSelectQuery = + $"select {string.Join(',', _mediaAttachmentSaveColumns)} from mediaattachments where ItemId=@ItemId"; - saveItemCommandCommandText += "@" + saveColumns[i]; - } + private static readonly string _mediaAttachmentInsertPrefix; - return saveItemCommandCommandText + ")"; - } + private const string SaveItemCommandText = + @"replace into TypedBaseItems + (guid,type,data,Path,StartDate,EndDate,ChannelId,IsMovie,IsSeries,EpisodeTitle,IsRepeat,CommunityRating,CustomRating,IndexNumber,IsLocked,Name,OfficialRating,MediaType,Overview,ParentIndexNumber,PremiereDate,ProductionYear,ParentId,Genres,InheritedParentalRatingValue,SortName,ForcedSortName,RunTimeTicks,Size,DateCreated,DateModified,PreferredMetadataLanguage,PreferredMetadataCountryCode,Width,Height,DateLastRefreshed,DateLastSaved,IsInMixedFolder,LockedFields,Studios,Audio,ExternalServiceId,Tags,IsFolder,UnratedType,TopParentId,TrailerTypes,CriticRating,CleanName,PresentationUniqueKey,OriginalTitle,PrimaryVersionId,DateLastMediaAdded,Album,IsVirtualItem,SeriesName,UserDataKey,SeasonName,SeasonId,SeriesId,ExternalSeriesId,Tagline,ProviderIds,Images,ProductionLocations,ExtraIds,TotalBitrate,ExtraType,Artists,AlbumArtists,ExternalId,SeriesPresentationUniqueKey,ShowId,OwnerId) + values (@guid,@type,@data,@Path,@StartDate,@EndDate,@ChannelId,@IsMovie,@IsSeries,@EpisodeTitle,@IsRepeat,@CommunityRating,@CustomRating,@IndexNumber,@IsLocked,@Name,@OfficialRating,@MediaType,@Overview,@ParentIndexNumber,@PremiereDate,@ProductionYear,@ParentId,@Genres,@InheritedParentalRatingValue,@SortName,@ForcedSortName,@RunTimeTicks,@Size,@DateCreated,@DateModified,@PreferredMetadataLanguage,@PreferredMetadataCountryCode,@Width,@Height,@DateLastRefreshed,@DateLastSaved,@IsInMixedFolder,@LockedFields,@Studios,@Audio,@ExternalServiceId,@Tags,@IsFolder,@UnratedType,@TopParentId,@TrailerTypes,@CriticRating,@CleanName,@PresentationUniqueKey,@OriginalTitle,@PrimaryVersionId,@DateLastMediaAdded,@Album,@IsVirtualItem,@SeriesName,@UserDataKey,@SeasonName,@SeasonId,@SeriesId,@ExternalSeriesId,@Tagline,@ProviderIds,@Images,@ProductionLocations,@ExtraIds,@TotalBitrate,@ExtraType,@Artists,@AlbumArtists,@ExternalId,@SeriesPresentationUniqueKey,@ShowId,@OwnerId)"; /// <summary> /// Save a standard item in the repo. @@ -636,7 +558,7 @@ namespace Emby.Server.Implementations.Data { var statements = PrepareAll(db, new string[] { - GetSaveItemCommandText(), + SaveItemCommandText, "delete from AncestorIds where ItemId=@ItemId" }).ToList(); @@ -1110,7 +1032,8 @@ namespace Emby.Server.Implementations.Data continue; } - str.Append(ToValueString(i) + "|"); + str.Append(ToValueString(i)) + .Append('|'); } str.Length -= 1; // Remove last | @@ -1225,7 +1148,7 @@ namespace Emby.Server.Implementations.Data using (var connection = GetConnection(true)) { - using (var statement = PrepareStatement(connection, "select " + string.Join(",", _retriveItemColumns) + " from TypedBaseItems where guid = @guid")) + using (var statement = PrepareStatement(connection, _retriveItemColumnsSelectQuery)) { statement.TryBind("@guid", id); @@ -2471,7 +2394,7 @@ namespace Emby.Server.Implementations.Data var item = query.SimilarTo; var builder = new StringBuilder(); - builder.Append("("); + builder.Append('('); if (string.IsNullOrEmpty(item.OfficialRating)) { @@ -2509,7 +2432,7 @@ namespace Emby.Server.Implementations.Data if (!string.IsNullOrEmpty(query.SearchTerm)) { var builder = new StringBuilder(); - builder.Append("("); + builder.Append('('); builder.Append("((CleanName like @SearchTermStartsWith or (OriginalTitle not null and OriginalTitle like @SearchTermStartsWith)) * 10)"); @@ -2775,82 +2698,82 @@ namespace Emby.Server.Implementations.Data private string FixUnicodeChars(string buffer) { - if (buffer.IndexOf('\u2013') > -1) + if (buffer.IndexOf('\u2013', StringComparison.Ordinal) > -1) { buffer = buffer.Replace('\u2013', '-'); // en dash } - if (buffer.IndexOf('\u2014') > -1) + if (buffer.IndexOf('\u2014', StringComparison.Ordinal) > -1) { buffer = buffer.Replace('\u2014', '-'); // em dash } - if (buffer.IndexOf('\u2015') > -1) + if (buffer.IndexOf('\u2015', StringComparison.Ordinal) > -1) { buffer = buffer.Replace('\u2015', '-'); // horizontal bar } - if (buffer.IndexOf('\u2017') > -1) + if (buffer.IndexOf('\u2017', StringComparison.Ordinal) > -1) { buffer = buffer.Replace('\u2017', '_'); // double low line } - if (buffer.IndexOf('\u2018') > -1) + if (buffer.IndexOf('\u2018', StringComparison.Ordinal) > -1) { buffer = buffer.Replace('\u2018', '\''); // left single quotation mark } - if (buffer.IndexOf('\u2019') > -1) + if (buffer.IndexOf('\u2019', StringComparison.Ordinal) > -1) { buffer = buffer.Replace('\u2019', '\''); // right single quotation mark } - if (buffer.IndexOf('\u201a') > -1) + if (buffer.IndexOf('\u201a', StringComparison.Ordinal) > -1) { buffer = buffer.Replace('\u201a', ','); // single low-9 quotation mark } - if (buffer.IndexOf('\u201b') > -1) + if (buffer.IndexOf('\u201b', StringComparison.Ordinal) > -1) { buffer = buffer.Replace('\u201b', '\''); // single high-reversed-9 quotation mark } - if (buffer.IndexOf('\u201c') > -1) + if (buffer.IndexOf('\u201c', StringComparison.Ordinal) > -1) { buffer = buffer.Replace('\u201c', '\"'); // left double quotation mark } - if (buffer.IndexOf('\u201d') > -1) + if (buffer.IndexOf('\u201d', StringComparison.Ordinal) > -1) { buffer = buffer.Replace('\u201d', '\"'); // right double quotation mark } - if (buffer.IndexOf('\u201e') > -1) + if (buffer.IndexOf('\u201e', StringComparison.Ordinal) > -1) { buffer = buffer.Replace('\u201e', '\"'); // double low-9 quotation mark } - if (buffer.IndexOf('\u2026') > -1) + if (buffer.IndexOf('\u2026', StringComparison.Ordinal) > -1) { - buffer = buffer.Replace("\u2026", "..."); // horizontal ellipsis + buffer = buffer.Replace("\u2026", "...", StringComparison.Ordinal); // horizontal ellipsis } - if (buffer.IndexOf('\u2032') > -1) + if (buffer.IndexOf('\u2032', StringComparison.Ordinal) > -1) { buffer = buffer.Replace('\u2032', '\''); // prime } - if (buffer.IndexOf('\u2033') > -1) + if (buffer.IndexOf('\u2033', StringComparison.Ordinal) > -1) { buffer = buffer.Replace('\u2033', '\"'); // double prime } - if (buffer.IndexOf('\u0060') > -1) + if (buffer.IndexOf('\u0060', StringComparison.Ordinal) > -1) { buffer = buffer.Replace('\u0060', '\''); // grave accent } - if (buffer.IndexOf('\u00B4') > -1) + if (buffer.IndexOf('\u00B4', StringComparison.Ordinal) > -1) { buffer = buffer.Replace('\u00B4', '\''); // acute accent } @@ -2999,7 +2922,6 @@ namespace Emby.Server.Implementations.Data { connection.RunInTransaction(db => { - var statements = PrepareAll(db, statementTexts).ToList(); if (!isReturningZeroItems) @@ -4669,8 +4591,12 @@ namespace Emby.Server.Implementations.Data if (query.BlockUnratedItems.Length > 1) { - var inClause = string.Join(",", query.BlockUnratedItems.Select(i => "'" + i.ToString() + "'")); - whereClauses.Add(string.Format("(InheritedParentalRatingValue > 0 or UnratedType not in ({0}))", inClause)); + var inClause = string.Join(',', query.BlockUnratedItems.Select(i => "'" + i.ToString() + "'")); + whereClauses.Add( + string.Format( + CultureInfo.InvariantCulture, + "(InheritedParentalRatingValue > 0 or UnratedType not in ({0}))", + inClause)); } if (query.ExcludeInheritedTags.Length > 0) @@ -4679,7 +4605,7 @@ namespace Emby.Server.Implementations.Data if (statement == null) { int index = 0; - string excludedTags = string.Join(",", query.ExcludeInheritedTags.Select(t => paramName + index++)); + string excludedTags = string.Join(',', query.ExcludeInheritedTags.Select(t => paramName + index++)); whereClauses.Add("((select CleanValue from itemvalues where ItemId=Guid and Type=6 and cleanvalue in (" + excludedTags + ")) is null)"); } else @@ -5238,7 +5164,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type { if (i > 0) { - insertText.Append(","); + insertText.Append(','); } insertText.AppendFormat("(@ItemId, @AncestorId{0}, @AncestorIdText{0})", i.ToString(CultureInfo.InvariantCulture)); @@ -5890,10 +5816,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type throw new ArgumentNullException(nameof(query)); } - var cmdText = "select " - + string.Join(",", _mediaStreamSaveColumns) - + " from mediastreams where" - + " ItemId=@ItemId"; + var cmdText = _mediaStreamSaveColumnsSelectQuery; if (query.Type.HasValue) { @@ -5972,15 +5895,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type while (startIndex < streams.Count) { - var insertText = new StringBuilder("insert into mediastreams ("); - foreach (var column in _mediaStreamSaveColumns) - { - insertText.Append(column).Append(','); - } - - // Remove last comma - insertText.Length--; - insertText.Append(") values "); + var insertText = new StringBuilder(_mediaStreamSaveColumnsInsertQuery); var endIndex = Math.Min(streams.Count, startIndex + Limit); @@ -6247,10 +6162,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type throw new ArgumentNullException(nameof(query)); } - var cmdText = "select " - + string.Join(",", _mediaAttachmentSaveColumns) - + " from mediaattachments where" - + " ItemId=@ItemId"; + var cmdText = _mediaAttachmentSaveColumnsSelectQuery; if (query.Index.HasValue) { @@ -6331,7 +6243,10 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type foreach (var column in _mediaAttachmentSaveColumns.Skip(1)) { - insertText.Append("@" + column + index + ","); + insertText.Append('@') + .Append(column) + .Append(index) + .Append(','); } insertText.Length -= 1; diff --git a/Emby.Server.Implementations/Devices/DeviceManager.cs b/Emby.Server.Implementations/Devices/DeviceManager.cs index e75745cc6..2921a7f0e 100644 --- a/Emby.Server.Implementations/Devices/DeviceManager.cs +++ b/Emby.Server.Implementations/Devices/DeviceManager.cs @@ -5,8 +5,8 @@ using System.Collections.Generic; using System.Globalization; using System.IO; using System.Linq; -using Jellyfin.Data.Enums; using Jellyfin.Data.Entities; +using Jellyfin.Data.Enums; using MediaBrowser.Common.Extensions; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Devices; @@ -17,16 +17,17 @@ using MediaBrowser.Model.Events; using MediaBrowser.Model.Querying; using MediaBrowser.Model.Serialization; using MediaBrowser.Model.Session; +using Microsoft.Extensions.Caching.Memory; namespace Emby.Server.Implementations.Devices { public class DeviceManager : IDeviceManager { + private readonly IMemoryCache _memoryCache; private readonly IJsonSerializer _json; private readonly IUserManager _userManager; private readonly IServerConfigurationManager _config; private readonly IAuthenticationRepository _authRepo; - private readonly Dictionary<string, ClientCapabilities> _capabilitiesCache; private readonly object _capabilitiesSyncLock = new object(); public event EventHandler<GenericEventArgs<Tuple<string, DeviceOptions>>> DeviceOptionsUpdated; @@ -35,13 +36,14 @@ namespace Emby.Server.Implementations.Devices IAuthenticationRepository authRepo, IJsonSerializer json, IUserManager userManager, - IServerConfigurationManager config) + IServerConfigurationManager config, + IMemoryCache memoryCache) { _json = json; _userManager = userManager; _config = config; + _memoryCache = memoryCache; _authRepo = authRepo; - _capabilitiesCache = new Dictionary<string, ClientCapabilities>(StringComparer.OrdinalIgnoreCase); } public void SaveCapabilities(string deviceId, ClientCapabilities capabilities) @@ -51,8 +53,7 @@ namespace Emby.Server.Implementations.Devices lock (_capabilitiesSyncLock) { - _capabilitiesCache[deviceId] = capabilities; - + _memoryCache.CreateEntry(deviceId).SetValue(capabilities); _json.SerializeToFile(capabilities, path); } } @@ -71,13 +72,13 @@ namespace Emby.Server.Implementations.Devices public ClientCapabilities GetCapabilities(string id) { - lock (_capabilitiesSyncLock) + if (_memoryCache.TryGetValue(id, out ClientCapabilities result)) { - if (_capabilitiesCache.TryGetValue(id, out var result)) - { - return result; - } + return result; + } + lock (_capabilitiesSyncLock) + { var path = Path.Combine(GetDevicePath(id), "capabilities.json"); try { diff --git a/Emby.Server.Implementations/Emby.Server.Implementations.csproj b/Emby.Server.Implementations/Emby.Server.Implementations.csproj index adae920dc..a1103b051 100644 --- a/Emby.Server.Implementations/Emby.Server.Implementations.csproj +++ b/Emby.Server.Implementations/Emby.Server.Implementations.csproj @@ -24,7 +24,7 @@ <ItemGroup> <PackageReference Include="IPNetwork2" Version="2.5.211" /> - <PackageReference Include="Jellyfin.XmlTv" Version="10.6.0-pre1" /> + <PackageReference Include="Jellyfin.XmlTv" Version="10.6.2" /> <PackageReference Include="Microsoft.AspNetCore.Hosting" Version="2.2.7" /> <PackageReference Include="Microsoft.AspNetCore.Hosting.Abstractions" Version="2.2.0" /> <PackageReference Include="Microsoft.AspNetCore.Hosting.Server.Abstractions" Version="2.2.0" /> @@ -37,10 +37,10 @@ <PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="3.1.6" /> <PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="3.1.6" /> <PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="3.1.6" /> - <PackageReference Include="Mono.Nat" Version="2.0.1" /> + <PackageReference Include="Mono.Nat" Version="2.0.2" /> <PackageReference Include="prometheus-net.DotNetRuntime" Version="3.3.1" /> <PackageReference Include="ServiceStack.Text.Core" Version="5.9.0" /> - <PackageReference Include="sharpcompress" Version="0.25.1" /> + <PackageReference Include="sharpcompress" Version="0.26.0" /> <PackageReference Include="SQLitePCL.pretty.netstandard" Version="2.1.0" /> <PackageReference Include="DotNet.Glob" Version="3.0.9" /> </ItemGroup> @@ -53,7 +53,7 @@ <TargetFramework>netstandard2.1</TargetFramework> <GenerateAssemblyInfo>false</GenerateAssemblyInfo> <GenerateDocumentationFile>true</GenerateDocumentationFile> - <TreatWarningsAsErrors Condition=" '$(Configuration)' == 'Release'" >true</TreatWarningsAsErrors> + <TreatWarningsAsErrors Condition=" '$(Configuration)' == 'Release'">true</TreatWarningsAsErrors> </PropertyGroup> <!-- Code Analyzers--> diff --git a/Emby.Server.Implementations/HttpServer/FileWriter.cs b/Emby.Server.Implementations/HttpServer/FileWriter.cs index 590eee1b4..6fce8de44 100644 --- a/Emby.Server.Implementations/HttpServer/FileWriter.cs +++ b/Emby.Server.Implementations/HttpServer/FileWriter.cs @@ -29,7 +29,6 @@ namespace Emby.Server.Implementations.HttpServer private readonly IStreamHelper _streamHelper; private readonly ILogger _logger; - private readonly IFileSystem _fileSystem; /// <summary> /// The _options. @@ -49,7 +48,6 @@ namespace Emby.Server.Implementations.HttpServer } _streamHelper = streamHelper; - _fileSystem = fileSystem; Path = path; _logger = logger; diff --git a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs index c3428ee62..0d4a789b5 100644 --- a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs +++ b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs @@ -449,7 +449,7 @@ namespace Emby.Server.Implementations.HttpServer if (string.Equals(httpReq.Verb, "OPTIONS", StringComparison.OrdinalIgnoreCase)) { httpRes.StatusCode = 200; - foreach(var (key, value) in GetDefaultCorsHeaders(httpReq)) + foreach (var (key, value) in GetDefaultCorsHeaders(httpReq)) { httpRes.Headers.Add(key, value); } @@ -486,7 +486,7 @@ namespace Emby.Server.Implementations.HttpServer var handler = GetServiceHandler(httpReq); if (handler != null) { - await handler.ProcessRequestAsync(this, httpReq, httpRes, _logger, cancellationToken).ConfigureAwait(false); + await handler.ProcessRequestAsync(this, httpReq, httpRes, cancellationToken).ConfigureAwait(false); } else { diff --git a/Emby.Server.Implementations/HttpServer/Security/AuthService.cs b/Emby.Server.Implementations/HttpServer/Security/AuthService.cs index 6a2d8fdbb..76c1d9bac 100644 --- a/Emby.Server.Implementations/HttpServer/Security/AuthService.cs +++ b/Emby.Server.Implementations/HttpServer/Security/AuthService.cs @@ -13,26 +13,22 @@ using MediaBrowser.Controller.Security; using MediaBrowser.Controller.Session; using MediaBrowser.Model.Services; using Microsoft.AspNetCore.Http; -using Microsoft.Extensions.Logging; namespace Emby.Server.Implementations.HttpServer.Security { public class AuthService : IAuthService { - private readonly ILogger<AuthService> _logger; private readonly IAuthorizationContext _authorizationContext; private readonly ISessionManager _sessionManager; private readonly IServerConfigurationManager _config; private readonly INetworkManager _networkManager; public AuthService( - ILogger<AuthService> logger, IAuthorizationContext authorizationContext, IServerConfigurationManager config, ISessionManager sessionManager, INetworkManager networkManager) { - _logger = logger; _authorizationContext = authorizationContext; _config = config; _sessionManager = sessionManager; diff --git a/Emby.Server.Implementations/IO/FileRefresher.cs b/Emby.Server.Implementations/IO/FileRefresher.cs index ef93779aa..fe74f1de7 100644 --- a/Emby.Server.Implementations/IO/FileRefresher.cs +++ b/Emby.Server.Implementations/IO/FileRefresher.cs @@ -21,6 +21,7 @@ namespace Emby.Server.Implementations.IO private readonly List<string> _affectedPaths = new List<string>(); private readonly object _timerLock = new object(); private Timer _timer; + private bool _disposed; public FileRefresher(string path, IServerConfigurationManager configurationManager, ILibraryManager libraryManager, ILogger logger) { @@ -213,11 +214,11 @@ namespace Emby.Server.Implementations.IO } } - private bool _disposed; public void Dispose() { _disposed = true; DisposeTimer(); + GC.SuppressFinalize(this); } } } diff --git a/Emby.Server.Implementations/Images/ArtistImageProvider.cs b/Emby.Server.Implementations/Images/ArtistImageProvider.cs index 52896720e..bf57382ed 100644 --- a/Emby.Server.Implementations/Images/ArtistImageProvider.cs +++ b/Emby.Server.Implementations/Images/ArtistImageProvider.cs @@ -11,7 +11,6 @@ using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Entities.Movies; using MediaBrowser.Controller.Entities.TV; -using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Playlists; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Entities; @@ -25,14 +24,9 @@ namespace Emby.Server.Implementations.Images /// </summary> public class ArtistImageProvider : BaseDynamicImageProvider<MusicArtist> { - /// <summary> - /// The library manager. - /// </summary> - private readonly ILibraryManager _libraryManager; - - public ArtistImageProvider(IFileSystem fileSystem, IProviderManager providerManager, IApplicationPaths applicationPaths, IImageProcessor imageProcessor, ILibraryManager libraryManager) : base(fileSystem, providerManager, applicationPaths, imageProcessor) + public ArtistImageProvider(IFileSystem fileSystem, IProviderManager providerManager, IApplicationPaths applicationPaths, IImageProcessor imageProcessor) + : base(fileSystem, providerManager, applicationPaths, imageProcessor) { - _libraryManager = libraryManager; } /// <summary> diff --git a/Emby.Server.Implementations/Images/CollectionFolderImageProvider.cs b/Emby.Server.Implementations/Images/CollectionFolderImageProvider.cs index da88b8d8a..161b4c452 100644 --- a/Emby.Server.Implementations/Images/CollectionFolderImageProvider.cs +++ b/Emby.Server.Implementations/Images/CollectionFolderImageProvider.cs @@ -3,7 +3,7 @@ using System; using System.Collections.Generic; using System.IO; -using Emby.Server.Implementations.Images; +using Jellyfin.Data.Enums; using MediaBrowser.Common.Configuration; using MediaBrowser.Controller.Drawing; using MediaBrowser.Controller.Dto; diff --git a/Emby.Server.Implementations/Images/FolderImageProvider.cs b/Emby.Server.Implementations/Images/FolderImageProvider.cs index e9523386e..0224ab32a 100644 --- a/Emby.Server.Implementations/Images/FolderImageProvider.cs +++ b/Emby.Server.Implementations/Images/FolderImageProvider.cs @@ -1,7 +1,7 @@ #pragma warning disable CS1591 using System.Collections.Generic; -using Emby.Server.Implementations.Images; +using Jellyfin.Data.Enums; using MediaBrowser.Common.Configuration; using MediaBrowser.Controller.Drawing; using MediaBrowser.Controller.Dto; diff --git a/Emby.Server.Implementations/Images/GenreImageProvider.cs b/Emby.Server.Implementations/Images/GenreImageProvider.cs index d2aeccdb2..1cd4cd66b 100644 --- a/Emby.Server.Implementations/Images/GenreImageProvider.cs +++ b/Emby.Server.Implementations/Images/GenreImageProvider.cs @@ -1,6 +1,7 @@ #pragma warning disable CS1591 using System.Collections.Generic; +using Jellyfin.Data.Enums; using MediaBrowser.Common.Configuration; using MediaBrowser.Controller.Drawing; using MediaBrowser.Controller.Dto; diff --git a/Emby.Server.Implementations/Library/CoreResolutionIgnoreRule.cs b/Emby.Server.Implementations/Library/CoreResolutionIgnoreRule.cs index 77b2c0a69..3380e29d4 100644 --- a/Emby.Server.Implementations/Library/CoreResolutionIgnoreRule.cs +++ b/Emby.Server.Implementations/Library/CoreResolutionIgnoreRule.cs @@ -77,7 +77,7 @@ namespace Emby.Server.Implementations.Library if (parent != null) { // Don't resolve these into audio files - if (string.Equals(Path.GetFileNameWithoutExtension(filename), BaseItem.ThemeSongFilename) + if (string.Equals(Path.GetFileNameWithoutExtension(filename), BaseItem.ThemeSongFilename, StringComparison.Ordinal) && _libraryManager.IsAudioFile(filename)) { return true; diff --git a/Emby.Server.Implementations/Library/ExclusiveLiveStream.cs b/Emby.Server.Implementations/Library/ExclusiveLiveStream.cs index ab39a7223..236453e80 100644 --- a/Emby.Server.Implementations/Library/ExclusiveLiveStream.cs +++ b/Emby.Server.Implementations/Library/ExclusiveLiveStream.cs @@ -11,6 +11,17 @@ namespace Emby.Server.Implementations.Library { public class ExclusiveLiveStream : ILiveStream { + private readonly Func<Task> _closeFn; + + public ExclusiveLiveStream(MediaSourceInfo mediaSource, Func<Task> closeFn) + { + MediaSource = mediaSource; + EnableStreamSharing = false; + _closeFn = closeFn; + ConsumerCount = 1; + UniqueId = Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture); + } + public int ConsumerCount { get; set; } public string OriginalStreamId { get; set; } @@ -21,18 +32,7 @@ namespace Emby.Server.Implementations.Library public MediaSourceInfo MediaSource { get; set; } - public string UniqueId { get; private set; } - - private Func<Task> _closeFn; - - public ExclusiveLiveStream(MediaSourceInfo mediaSource, Func<Task> closeFn) - { - MediaSource = mediaSource; - EnableStreamSharing = false; - _closeFn = closeFn; - ConsumerCount = 1; - UniqueId = Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture); - } + public string UniqueId { get; } public Task Close() { diff --git a/Emby.Server.Implementations/Library/IgnorePatterns.cs b/Emby.Server.Implementations/Library/IgnorePatterns.cs index 6e6ef1359..e30a67593 100644 --- a/Emby.Server.Implementations/Library/IgnorePatterns.cs +++ b/Emby.Server.Implementations/Library/IgnorePatterns.cs @@ -18,7 +18,21 @@ namespace Emby.Server.Implementations.Library { "**/small.jpg", "**/albumart.jpg", - "**/*sample*", + + // We have neither non-greedy matching or character group repetitions, working around that here. + // https://github.com/dazinator/DotNet.Glob#patterns + // .*/sample\..{1,5} + "**/sample.?", + "**/sample.??", + "**/sample.???", // Matches sample.mkv + "**/sample.????", // Matches sample.webm + "**/sample.?????", + "**/*.sample.?", + "**/*.sample.??", + "**/*.sample.???", + "**/*.sample.????", + "**/*.sample.?????", + "**/sample/*", // Directories "**/metadata/**", @@ -64,10 +78,13 @@ namespace Emby.Server.Implementations.Library "**/.grab/**", "**/.grab", - // Unix hidden files and directories - "**/.*/**", + // Unix hidden files "**/.*", + // Mac - if you ever remove the above. + // "**/._*", + // "**/.DS_Store", + // thumbs.db "**/thumbs.db", diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs index c27b73c74..04b530fce 100644 --- a/Emby.Server.Implementations/Library/LibraryManager.cs +++ b/Emby.Server.Implementations/Library/LibraryManager.cs @@ -1,7 +1,6 @@ #pragma warning disable CS1591 using System; -using System.Collections.Concurrent; using System.Collections.Generic; using System.Globalization; using System.IO; @@ -46,11 +45,11 @@ using MediaBrowser.Model.Net; using MediaBrowser.Model.Querying; using MediaBrowser.Model.Tasks; using MediaBrowser.Providers.MediaInfo; +using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.Logging; using Episode = MediaBrowser.Controller.Entities.TV.Episode; using Genre = MediaBrowser.Controller.Entities.Genre; using Person = MediaBrowser.Controller.Entities.Person; -using SortOrder = MediaBrowser.Model.Entities.SortOrder; using VideoResolver = Emby.Naming.Video.VideoResolver; namespace Emby.Server.Implementations.Library @@ -60,7 +59,10 @@ namespace Emby.Server.Implementations.Library /// </summary> public class LibraryManager : ILibraryManager { + private const string ShortcutFileExtension = ".mblink"; + private readonly ILogger<LibraryManager> _logger; + private readonly IMemoryCache _memoryCache; private readonly ITaskManager _taskManager; private readonly IUserManager _userManager; private readonly IUserDataManager _userDataRepository; @@ -72,66 +74,26 @@ namespace Emby.Server.Implementations.Library private readonly IMediaEncoder _mediaEncoder; private readonly IFileSystem _fileSystem; private readonly IItemRepository _itemRepository; - private readonly ConcurrentDictionary<Guid, BaseItem> _libraryItemsCache; private readonly IImageProcessor _imageProcessor; - private NamingOptions _namingOptions; - private string[] _videoFileExtensions; - - private ILibraryMonitor LibraryMonitor => _libraryMonitorFactory.Value; - - private IProviderManager ProviderManager => _providerManagerFactory.Value; - - private IUserViewManager UserViewManager => _userviewManagerFactory.Value; - - /// <summary> - /// Gets or sets the postscan tasks. - /// </summary> - /// <value>The postscan tasks.</value> - private ILibraryPostScanTask[] PostscanTasks { get; set; } - - /// <summary> - /// Gets or sets the intro providers. - /// </summary> - /// <value>The intro providers.</value> - private IIntroProvider[] IntroProviders { get; set; } - - /// <summary> - /// Gets or sets the list of entity resolution ignore rules. - /// </summary> - /// <value>The entity resolution ignore rules.</value> - private IResolverIgnoreRule[] EntityResolutionIgnoreRules { get; set; } - - /// <summary> - /// Gets or sets the list of currently registered entity resolvers. - /// </summary> - /// <value>The entity resolvers enumerable.</value> - private IItemResolver[] EntityResolvers { get; set; } - - private IMultiItemResolver[] MultiItemResolvers { get; set; } - /// <summary> - /// Gets or sets the comparers. + /// The _root folder sync lock. /// </summary> - /// <value>The comparers.</value> - private IBaseItemComparer[] Comparers { get; set; } + private readonly object _rootFolderSyncLock = new object(); + private readonly object _userRootFolderSyncLock = new object(); - /// <summary> - /// Occurs when [item added]. - /// </summary> - public event EventHandler<ItemChangeEventArgs> ItemAdded; + private readonly TimeSpan _viewRefreshInterval = TimeSpan.FromHours(24); - /// <summary> - /// Occurs when [item updated]. - /// </summary> - public event EventHandler<ItemChangeEventArgs> ItemUpdated; + private NamingOptions _namingOptions; + private string[] _videoFileExtensions; /// <summary> - /// Occurs when [item removed]. + /// The _root folder. /// </summary> - public event EventHandler<ItemChangeEventArgs> ItemRemoved; + private volatile AggregateFolder _rootFolder; + private volatile UserRootFolder _userRootFolder; - public bool IsScanRunning { get; private set; } + private bool _wizardCompleted; /// <summary> /// Initializes a new instance of the <see cref="LibraryManager" /> class. @@ -149,6 +111,7 @@ namespace Emby.Server.Implementations.Library /// <param name="mediaEncoder">The media encoder.</param> /// <param name="itemRepository">The item repository.</param> /// <param name="imageProcessor">The image processor.</param> + /// <param name="memoryCache">The memory cache.</param> public LibraryManager( IServerApplicationHost appHost, ILogger<LibraryManager> logger, @@ -162,7 +125,8 @@ namespace Emby.Server.Implementations.Library Lazy<IUserViewManager> userviewManagerFactory, IMediaEncoder mediaEncoder, IItemRepository itemRepository, - IImageProcessor imageProcessor) + IImageProcessor imageProcessor, + IMemoryCache memoryCache) { _appHost = appHost; _logger = logger; @@ -177,8 +141,7 @@ namespace Emby.Server.Implementations.Library _mediaEncoder = mediaEncoder; _itemRepository = itemRepository; _imageProcessor = imageProcessor; - - _libraryItemsCache = new ConcurrentDictionary<Guid, BaseItem>(); + _memoryCache = memoryCache; _configurationManager.ConfigurationUpdated += ConfigurationUpdated; @@ -186,37 +149,19 @@ namespace Emby.Server.Implementations.Library } /// <summary> - /// Adds the parts. + /// Occurs when [item added]. /// </summary> - /// <param name="rules">The rules.</param> - /// <param name="resolvers">The resolvers.</param> - /// <param name="introProviders">The intro providers.</param> - /// <param name="itemComparers">The item comparers.</param> - /// <param name="postscanTasks">The post scan tasks.</param> - public void AddParts( - IEnumerable<IResolverIgnoreRule> rules, - IEnumerable<IItemResolver> resolvers, - IEnumerable<IIntroProvider> introProviders, - IEnumerable<IBaseItemComparer> itemComparers, - IEnumerable<ILibraryPostScanTask> postscanTasks) - { - EntityResolutionIgnoreRules = rules.ToArray(); - EntityResolvers = resolvers.OrderBy(i => i.Priority).ToArray(); - MultiItemResolvers = EntityResolvers.OfType<IMultiItemResolver>().ToArray(); - IntroProviders = introProviders.ToArray(); - Comparers = itemComparers.ToArray(); - PostscanTasks = postscanTasks.ToArray(); - } + public event EventHandler<ItemChangeEventArgs> ItemAdded; /// <summary> - /// The _root folder. + /// Occurs when [item updated]. /// </summary> - private volatile AggregateFolder _rootFolder; + public event EventHandler<ItemChangeEventArgs> ItemUpdated; /// <summary> - /// The _root folder sync lock. + /// Occurs when [item removed]. /// </summary> - private readonly object _rootFolderSyncLock = new object(); + public event EventHandler<ItemChangeEventArgs> ItemRemoved; /// <summary> /// Gets the root folder. @@ -241,7 +186,68 @@ namespace Emby.Server.Implementations.Library } } - private bool _wizardCompleted; + private ILibraryMonitor LibraryMonitor => _libraryMonitorFactory.Value; + + private IProviderManager ProviderManager => _providerManagerFactory.Value; + + private IUserViewManager UserViewManager => _userviewManagerFactory.Value; + + /// <summary> + /// Gets or sets the postscan tasks. + /// </summary> + /// <value>The postscan tasks.</value> + private ILibraryPostScanTask[] PostscanTasks { get; set; } + + /// <summary> + /// Gets or sets the intro providers. + /// </summary> + /// <value>The intro providers.</value> + private IIntroProvider[] IntroProviders { get; set; } + + /// <summary> + /// Gets or sets the list of entity resolution ignore rules. + /// </summary> + /// <value>The entity resolution ignore rules.</value> + private IResolverIgnoreRule[] EntityResolutionIgnoreRules { get; set; } + + /// <summary> + /// Gets or sets the list of currently registered entity resolvers. + /// </summary> + /// <value>The entity resolvers enumerable.</value> + private IItemResolver[] EntityResolvers { get; set; } + + private IMultiItemResolver[] MultiItemResolvers { get; set; } + + /// <summary> + /// Gets or sets the comparers. + /// </summary> + /// <value>The comparers.</value> + private IBaseItemComparer[] Comparers { get; set; } + + public bool IsScanRunning { get; private set; } + + /// <summary> + /// Adds the parts. + /// </summary> + /// <param name="rules">The rules.</param> + /// <param name="resolvers">The resolvers.</param> + /// <param name="introProviders">The intro providers.</param> + /// <param name="itemComparers">The item comparers.</param> + /// <param name="postscanTasks">The post scan tasks.</param> + public void AddParts( + IEnumerable<IResolverIgnoreRule> rules, + IEnumerable<IItemResolver> resolvers, + IEnumerable<IIntroProvider> introProviders, + IEnumerable<IBaseItemComparer> itemComparers, + IEnumerable<ILibraryPostScanTask> postscanTasks) + { + EntityResolutionIgnoreRules = rules.ToArray(); + EntityResolvers = resolvers.OrderBy(i => i.Priority).ToArray(); + MultiItemResolvers = EntityResolvers.OfType<IMultiItemResolver>().ToArray(); + IntroProviders = introProviders.ToArray(); + Comparers = itemComparers.ToArray(); + PostscanTasks = postscanTasks.ToArray(); + } /// <summary> /// Records the configuration values. @@ -293,7 +299,7 @@ namespace Emby.Server.Implementations.Library } } - _libraryItemsCache.AddOrUpdate(item.Id, item, delegate { return item; }); + _memoryCache.CreateEntry(item.Id).SetValue(item); } public void DeleteItem(BaseItem item, DeleteOptions options) @@ -441,7 +447,7 @@ namespace Emby.Server.Implementations.Library _itemRepository.DeleteItem(child.Id); } - _libraryItemsCache.TryRemove(item.Id, out BaseItem removed); + _memoryCache.Remove(item.Id); ReportItemRemoved(item, parent); } @@ -511,8 +517,8 @@ namespace Emby.Server.Implementations.Library { // Try to normalize paths located underneath program-data in an attempt to make them more portable key = key.Substring(_configurationManager.ApplicationPaths.ProgramDataPath.Length) - .TrimStart(new[] { '/', '\\' }) - .Replace("/", "\\"); + .TrimStart('/', '\\') + .Replace('/', '\\'); } if (forceCaseInsensitive || !_configurationManager.Configuration.EnableCaseSensitiveItemIds) @@ -775,14 +781,11 @@ namespace Emby.Server.Implementations.Library return rootFolder; } - private volatile UserRootFolder _userRootFolder; - private readonly object _syncLock = new object(); - public Folder GetUserRootFolder() { if (_userRootFolder == null) { - lock (_syncLock) + lock (_userRootFolderSyncLock) { if (_userRootFolder == null) { @@ -1245,7 +1248,7 @@ namespace Emby.Server.Implementations.Library throw new ArgumentException("Guid can't be empty", nameof(id)); } - if (_libraryItemsCache.TryGetValue(id, out BaseItem item)) + if (_memoryCache.TryGetValue(id, out BaseItem item)) { return item; } @@ -1332,7 +1335,7 @@ namespace Emby.Server.Implementations.Library return new QueryResult<BaseItem> { - Items = _itemRepository.GetItemList(query).ToArray() + Items = _itemRepository.GetItemList(query) }; } @@ -1463,11 +1466,9 @@ namespace Emby.Server.Implementations.Library return _itemRepository.GetItems(query); } - var list = _itemRepository.GetItemList(query); - return new QueryResult<BaseItem> { - Items = list + Items = _itemRepository.GetItemList(query) }; } @@ -1590,7 +1591,6 @@ namespace Emby.Server.Implementations.Library public async Task<IEnumerable<Video>> GetIntros(BaseItem item, User user) { var tasks = IntroProviders - .OrderBy(i => i.GetType().Name.Contains("Default", StringComparison.OrdinalIgnoreCase) ? 1 : 0) .Take(1) .Select(i => GetIntros(i, item, user)); @@ -1876,7 +1876,8 @@ namespace Emby.Server.Implementations.Library } var outdated = forceUpdate ? item.ImageInfos.Where(i => i.Path != null).ToArray() : item.ImageInfos.Where(ImageNeedsRefresh).ToArray(); - if (outdated.Length == 0) + // Skip image processing if current or live tv source + if (outdated.Length == 0 || item.SourceType != SourceType.Library) { RegisterItem(item); return; @@ -1945,12 +1946,9 @@ namespace Emby.Server.Implementations.Library /// <summary> /// Updates the item. /// </summary> - public void UpdateItems(IEnumerable<BaseItem> items, BaseItem parent, ItemUpdateType updateReason, CancellationToken cancellationToken) + public void UpdateItems(IReadOnlyList<BaseItem> items, BaseItem parent, ItemUpdateType updateReason, CancellationToken cancellationToken) { - // Don't iterate multiple times - var itemsList = items.ToList(); - - foreach (var item in itemsList) + foreach (var item in items) { if (item.IsFileProtocol) { @@ -1962,11 +1960,11 @@ namespace Emby.Server.Implementations.Library UpdateImages(item, updateReason >= ItemUpdateType.ImageUpdate); } - _itemRepository.SaveItems(itemsList, cancellationToken); + _itemRepository.SaveItems(items, cancellationToken); if (ItemUpdated != null) { - foreach (var item in itemsList) + foreach (var item in items) { // With the live tv guide this just creates too much noise if (item.SourceType != SourceType.Library) @@ -2189,8 +2187,6 @@ namespace Emby.Server.Implementations.Library .FirstOrDefault(i => !string.IsNullOrEmpty(i)); } - private readonly TimeSpan _viewRefreshInterval = TimeSpan.FromHours(24); - public UserView GetNamedView( User user, string name, @@ -2488,14 +2484,9 @@ namespace Emby.Server.Implementations.Library var isFolder = episode.VideoType == VideoType.BluRay || episode.VideoType == VideoType.Dvd; - var episodeInfo = episode.IsFileProtocol ? - resolver.Resolve(episode.Path, isFolder, null, null, isAbsoluteNaming) : - new Naming.TV.EpisodeInfo(); - - if (episodeInfo == null) - { - episodeInfo = new Naming.TV.EpisodeInfo(); - } + var episodeInfo = episode.IsFileProtocol + ? resolver.Resolve(episode.Path, isFolder, null, null, isAbsoluteNaming) ?? new Naming.TV.EpisodeInfo() + : new Naming.TV.EpisodeInfo(); try { @@ -2503,11 +2494,13 @@ namespace Emby.Server.Implementations.Library if (libraryOptions.EnableEmbeddedEpisodeInfos && string.Equals(episodeInfo.Container, "mp4", StringComparison.OrdinalIgnoreCase)) { // Read from metadata - var mediaInfo = _mediaEncoder.GetMediaInfo(new MediaInfoRequest - { - MediaSource = episode.GetMediaSources(false)[0], - MediaType = DlnaProfileType.Video - }, CancellationToken.None).GetAwaiter().GetResult(); + var mediaInfo = _mediaEncoder.GetMediaInfo( + new MediaInfoRequest + { + MediaSource = episode.GetMediaSources(false)[0], + MediaType = DlnaProfileType.Video + }, + CancellationToken.None).GetAwaiter().GetResult(); if (mediaInfo.ParentIndexNumber > 0) { episodeInfo.SeasonNumber = mediaInfo.ParentIndexNumber; @@ -2665,7 +2658,7 @@ namespace Emby.Server.Implementations.Library var videos = videoListResolver.Resolve(fileSystemChildren); - var currentVideo = videos.FirstOrDefault(i => string.Equals(owner.Path, i.Files.First().Path, StringComparison.OrdinalIgnoreCase)); + var currentVideo = videos.FirstOrDefault(i => string.Equals(owner.Path, i.Files[0].Path, StringComparison.OrdinalIgnoreCase)); if (currentVideo != null) { @@ -2682,9 +2675,7 @@ namespace Emby.Server.Implementations.Library .Select(video => { // Try to retrieve it from the db. If we don't find it, use the resolved version - var dbItem = GetItemById(video.Id) as Trailer; - - if (dbItem != null) + if (GetItemById(video.Id) is Trailer dbItem) { video = dbItem; } @@ -3011,8 +3002,6 @@ namespace Emby.Server.Implementations.Library }); } - private const string ShortcutFileExtension = ".mblink"; - public void AddMediaPath(string virtualFolderName, MediaPathInfo pathInfo) { AddMediaPathInternal(virtualFolderName, pathInfo, true); @@ -3206,7 +3195,8 @@ namespace Emby.Server.Implementations.Library if (!Directory.Exists(virtualFolderPath)) { - throw new FileNotFoundException(string.Format("The media collection {0} does not exist", virtualFolderName)); + throw new FileNotFoundException( + string.Format(CultureInfo.InvariantCulture, "The media collection {0} does not exist", virtualFolderName)); } var shortcut = _fileSystem.GetFilePaths(virtualFolderPath, true) diff --git a/Emby.Server.Implementations/Library/LiveStreamHelper.cs b/Emby.Server.Implementations/Library/LiveStreamHelper.cs index 9b9f53049..041619d1e 100644 --- a/Emby.Server.Implementations/Library/LiveStreamHelper.cs +++ b/Emby.Server.Implementations/Library/LiveStreamHelper.cs @@ -23,9 +23,8 @@ namespace Emby.Server.Implementations.Library { private readonly IMediaEncoder _mediaEncoder; private readonly ILogger _logger; - - private IJsonSerializer _json; - private IApplicationPaths _appPaths; + private readonly IJsonSerializer _json; + private readonly IApplicationPaths _appPaths; public LiveStreamHelper(IMediaEncoder mediaEncoder, ILogger logger, IJsonSerializer json, IApplicationPaths appPaths) { @@ -72,13 +71,14 @@ namespace Emby.Server.Implementations.Library mediaSource.AnalyzeDurationMs = 3000; - mediaInfo = await _mediaEncoder.GetMediaInfo(new MediaInfoRequest - { - MediaSource = mediaSource, - MediaType = isAudio ? DlnaProfileType.Audio : DlnaProfileType.Video, - ExtractChapters = false - - }, cancellationToken).ConfigureAwait(false); + mediaInfo = await _mediaEncoder.GetMediaInfo( + new MediaInfoRequest + { + MediaSource = mediaSource, + MediaType = isAudio ? DlnaProfileType.Audio : DlnaProfileType.Video, + ExtractChapters = false + }, + cancellationToken).ConfigureAwait(false); if (cacheFilePath != null) { @@ -126,7 +126,7 @@ namespace Emby.Server.Implementations.Library mediaSource.RunTimeTicks = null; } - var audioStream = mediaStreams.FirstOrDefault(i => i.Type == MediaBrowser.Model.Entities.MediaStreamType.Audio); + var audioStream = mediaStreams.FirstOrDefault(i => i.Type == MediaStreamType.Audio); if (audioStream == null || audioStream.Index == -1) { @@ -137,7 +137,7 @@ namespace Emby.Server.Implementations.Library mediaSource.DefaultAudioStreamIndex = audioStream.Index; } - var videoStream = mediaStreams.FirstOrDefault(i => i.Type == MediaBrowser.Model.Entities.MediaStreamType.Video); + var videoStream = mediaStreams.FirstOrDefault(i => i.Type == MediaStreamType.Video); if (videoStream != null) { if (!videoStream.BitRate.HasValue) diff --git a/Emby.Server.Implementations/Library/MediaSourceManager.cs b/Emby.Server.Implementations/Library/MediaSourceManager.cs index ceb36b389..67cf8bf5b 100644 --- a/Emby.Server.Implementations/Library/MediaSourceManager.cs +++ b/Emby.Server.Implementations/Library/MediaSourceManager.cs @@ -29,6 +29,9 @@ namespace Emby.Server.Implementations.Library { public class MediaSourceManager : IMediaSourceManager, IDisposable { + // Do not use a pipe here because Roku http requests to the server will fail, without any explicit error message. + private const char LiveStreamIdDelimeter = '_'; + private readonly IItemRepository _itemRepo; private readonly IUserManager _userManager; private readonly ILibraryManager _libraryManager; @@ -40,6 +43,9 @@ namespace Emby.Server.Implementations.Library private readonly ILocalizationManager _localizationManager; private readonly IApplicationPaths _appPaths; + private readonly Dictionary<string, ILiveStream> _openStreams = new Dictionary<string, ILiveStream>(StringComparer.OrdinalIgnoreCase); + private readonly SemaphoreSlim _liveStreamSemaphore = new SemaphoreSlim(1, 1); + private IMediaSourceProvider[] _providers; public MediaSourceManager( @@ -368,7 +374,6 @@ namespace Emby.Server.Implementations.Library } } - var preferredSubs = string.IsNullOrEmpty(user.SubtitleLanguagePreference) ? Array.Empty<string>() : NormalizeLanguage(user.SubtitleLanguagePreference); @@ -451,9 +456,6 @@ namespace Emby.Server.Implementations.Library .ToList(); } - private readonly Dictionary<string, ILiveStream> _openStreams = new Dictionary<string, ILiveStream>(StringComparer.OrdinalIgnoreCase); - private readonly SemaphoreSlim _liveStreamSemaphore = new SemaphoreSlim(1, 1); - public async Task<Tuple<LiveStreamResponse, IDirectStreamProvider>> OpenLiveStreamInternal(LiveStreamRequest request, CancellationToken cancellationToken) { await _liveStreamSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false); @@ -619,12 +621,14 @@ namespace Emby.Server.Implementations.Library if (liveStreamInfo is IDirectStreamProvider) { - var info = await _mediaEncoder.GetMediaInfo(new MediaInfoRequest - { - MediaSource = mediaSource, - ExtractChapters = false, - MediaType = DlnaProfileType.Video - }, cancellationToken).ConfigureAwait(false); + var info = await _mediaEncoder.GetMediaInfo( + new MediaInfoRequest + { + MediaSource = mediaSource, + ExtractChapters = false, + MediaType = DlnaProfileType.Video + }, + cancellationToken).ConfigureAwait(false); mediaSource.MediaStreams = info.MediaStreams; mediaSource.Container = info.Container; @@ -855,24 +859,21 @@ namespace Emby.Server.Implementations.Library } } - // Do not use a pipe here because Roku http requests to the server will fail, without any explicit error message. - private const char LiveStreamIdDelimeter = '_'; - - private Tuple<IMediaSourceProvider, string> GetProvider(string key) + private (IMediaSourceProvider, string) GetProvider(string key) { if (string.IsNullOrEmpty(key)) { - throw new ArgumentException("key"); + throw new ArgumentException("Key can't be empty.", nameof(key)); } var keys = key.Split(new[] { LiveStreamIdDelimeter }, 2); var provider = _providers.FirstOrDefault(i => string.Equals(i.GetType().FullName.GetMD5().ToString("N", CultureInfo.InvariantCulture), keys[0], StringComparison.OrdinalIgnoreCase)); - var splitIndex = key.IndexOf(LiveStreamIdDelimeter); + var splitIndex = key.IndexOf(LiveStreamIdDelimeter, StringComparison.Ordinal); var keyId = key.Substring(splitIndex + 1); - return new Tuple<IMediaSourceProvider, string>(provider, keyId); + return (provider, keyId); } /// <summary> @@ -881,9 +882,9 @@ namespace Emby.Server.Implementations.Library public void Dispose() { Dispose(true); + GC.SuppressFinalize(this); } - private readonly object _disposeLock = new object(); /// <summary> /// Releases unmanaged and - optionally - managed resources. /// </summary> @@ -892,15 +893,12 @@ namespace Emby.Server.Implementations.Library { if (dispose) { - lock (_disposeLock) + foreach (var key in _openStreams.Keys.ToList()) { - foreach (var key in _openStreams.Keys.ToList()) - { - var task = CloseLiveStream(key); - - Task.WaitAll(task); - } + CloseLiveStream(key).GetAwaiter().GetResult(); } + + _liveStreamSemaphore.Dispose(); } } } diff --git a/Emby.Server.Implementations/Library/MediaStreamSelector.cs b/Emby.Server.Implementations/Library/MediaStreamSelector.cs index ca904c4ec..179e0ed98 100644 --- a/Emby.Server.Implementations/Library/MediaStreamSelector.cs +++ b/Emby.Server.Implementations/Library/MediaStreamSelector.cs @@ -89,7 +89,7 @@ namespace Emby.Server.Implementations.Library } // load forced subs if we have found no suitable full subtitles - stream = stream ?? streams.FirstOrDefault(s => s.IsForced && string.Equals(s.Language, audioTrackLanguage, StringComparison.OrdinalIgnoreCase)); + stream ??= streams.FirstOrDefault(s => s.IsForced && string.Equals(s.Language, audioTrackLanguage, StringComparison.OrdinalIgnoreCase)); if (stream != null) { diff --git a/Emby.Server.Implementations/Library/MusicManager.cs b/Emby.Server.Implementations/Library/MusicManager.cs index 0bdc59914..877fdec86 100644 --- a/Emby.Server.Implementations/Library/MusicManager.cs +++ b/Emby.Server.Implementations/Library/MusicManager.cs @@ -4,12 +4,12 @@ using System; using System.Collections.Generic; using System.Linq; using Jellyfin.Data.Entities; +using Jellyfin.Data.Enums; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Playlists; -using MediaBrowser.Model.Entities; using MediaBrowser.Model.Querying; using MusicAlbum = MediaBrowser.Controller.Entities.Audio.MusicAlbum; diff --git a/Emby.Server.Implementations/Library/Resolvers/TV/SeasonResolver.cs b/Emby.Server.Implementations/Library/Resolvers/TV/SeasonResolver.cs index c8e41001a..3332e1806 100644 --- a/Emby.Server.Implementations/Library/Resolvers/TV/SeasonResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/TV/SeasonResolver.cs @@ -1,6 +1,5 @@ using System.Globalization; using Emby.Naming.TV; -using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Library; using MediaBrowser.Model.Globalization; @@ -13,7 +12,6 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV /// </summary> public class SeasonResolver : FolderResolver<Season> { - private readonly IServerConfigurationManager _config; private readonly ILibraryManager _libraryManager; private readonly ILocalizationManager _localization; private readonly ILogger<SeasonResolver> _logger; @@ -21,17 +19,14 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV /// <summary> /// Initializes a new instance of the <see cref="SeasonResolver"/> class. /// </summary> - /// <param name="config">The config.</param> /// <param name="libraryManager">The library manager.</param> /// <param name="localization">The localization.</param> /// <param name="logger">The logger.</param> public SeasonResolver( - IServerConfigurationManager config, ILibraryManager libraryManager, ILocalizationManager localization, ILogger<SeasonResolver> logger) { - _config = config; _libraryManager = libraryManager; _localization = localization; _logger = logger; diff --git a/Emby.Server.Implementations/Library/SearchEngine.cs b/Emby.Server.Implementations/Library/SearchEngine.cs index 3df9cc06f..9a69bce0e 100644 --- a/Emby.Server.Implementations/Library/SearchEngine.cs +++ b/Emby.Server.Implementations/Library/SearchEngine.cs @@ -4,12 +4,12 @@ using System; using System.Collections.Generic; using System.Linq; using Jellyfin.Data.Entities; +using Jellyfin.Data.Enums; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Extensions; using MediaBrowser.Controller.Library; -using MediaBrowser.Model.Entities; using MediaBrowser.Model.Querying; using MediaBrowser.Model.Search; using Microsoft.Extensions.Logging; @@ -20,13 +20,11 @@ namespace Emby.Server.Implementations.Library { public class SearchEngine : ISearchEngine { - private readonly ILogger<SearchEngine> _logger; private readonly ILibraryManager _libraryManager; private readonly IUserManager _userManager; - public SearchEngine(ILogger<SearchEngine> logger, ILibraryManager libraryManager, IUserManager userManager) + public SearchEngine(ILibraryManager libraryManager, IUserManager userManager) { - _logger = logger; _libraryManager = libraryManager; _userManager = userManager; } @@ -34,11 +32,7 @@ namespace Emby.Server.Implementations.Library public QueryResult<SearchHintInfo> GetSearchHints(SearchQuery query) { User user = null; - - if (query.UserId.Equals(Guid.Empty)) - { - } - else + if (query.UserId != Guid.Empty) { user = _userManager.GetUserById(query.UserId); } @@ -48,19 +42,19 @@ namespace Emby.Server.Implementations.Library if (query.StartIndex.HasValue) { - results = results.Skip(query.StartIndex.Value).ToList(); + results = results.GetRange(query.StartIndex.Value, totalRecordCount - query.StartIndex.Value); } if (query.Limit.HasValue) { - results = results.Take(query.Limit.Value).ToList(); + results = results.GetRange(0, query.Limit.Value); } return new QueryResult<SearchHintInfo> { TotalRecordCount = totalRecordCount, - Items = results.ToArray() + Items = results }; } @@ -85,7 +79,7 @@ namespace Emby.Server.Implementations.Library if (string.IsNullOrEmpty(searchTerm)) { - throw new ArgumentNullException("SearchTerm can't be empty.", nameof(searchTerm)); + throw new ArgumentException("SearchTerm can't be empty.", nameof(query)); } searchTerm = searchTerm.Trim().RemoveDiacritics(); diff --git a/Emby.Server.Implementations/Library/UserDataManager.cs b/Emby.Server.Implementations/Library/UserDataManager.cs index 175b3af57..f9e5e6bbc 100644 --- a/Emby.Server.Implementations/Library/UserDataManager.cs +++ b/Emby.Server.Implementations/Library/UserDataManager.cs @@ -13,7 +13,6 @@ using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Persistence; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; -using Microsoft.Extensions.Logging; using Book = MediaBrowser.Controller.Entities.Book; namespace Emby.Server.Implementations.Library @@ -28,18 +27,15 @@ namespace Emby.Server.Implementations.Library private readonly ConcurrentDictionary<string, UserItemData> _userData = new ConcurrentDictionary<string, UserItemData>(StringComparer.OrdinalIgnoreCase); - private readonly ILogger<UserDataManager> _logger; private readonly IServerConfigurationManager _config; private readonly IUserManager _userManager; private readonly IUserDataRepository _repository; public UserDataManager( - ILogger<UserDataManager> logger, IServerConfigurationManager config, IUserManager userManager, IUserDataRepository repository) { - _logger = logger; _config = config; _userManager = userManager; _repository = repository; diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs index 7b0fcbc9e..80e09f0a3 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs @@ -12,6 +12,7 @@ using System.Threading; using System.Threading.Tasks; using System.Xml; using Emby.Server.Implementations.Library; +using Jellyfin.Data.Enums; using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Net; diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs index 3709f8fe4..77a7069eb 100644 --- a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs +++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs @@ -461,7 +461,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings private async Task<List<ScheduleDirect.ShowImages>> GetImageForPrograms( ListingsProviderInfo info, List<string> programIds, - CancellationToken cancellationToken) + CancellationToken cancellationToken) { if (programIds.Count == 0) { @@ -474,7 +474,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings { var imageId = i.Substring(0, 10); - if (!imageIdString.Contains(imageId)) + if (!imageIdString.Contains(imageId, StringComparison.Ordinal)) { imageIdString += "\"" + imageId + "\","; } diff --git a/Emby.Server.Implementations/LiveTv/LiveTvManager.cs b/Emby.Server.Implementations/LiveTv/LiveTvManager.cs index 4c1de3bcc..1b075d86a 100644 --- a/Emby.Server.Implementations/LiveTv/LiveTvManager.cs +++ b/Emby.Server.Implementations/LiveTv/LiveTvManager.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Globalization; using System.Linq; +using System.Text.Json; using System.Threading; using System.Threading.Tasks; using Emby.Server.Implementations.Library; @@ -28,7 +29,6 @@ using MediaBrowser.Model.Globalization; using MediaBrowser.Model.IO; using MediaBrowser.Model.LiveTv; using MediaBrowser.Model.Querying; -using MediaBrowser.Model.Serialization; using MediaBrowser.Model.Tasks; using Microsoft.Extensions.Logging; using Episode = MediaBrowser.Controller.Entities.TV.Episode; @@ -54,7 +54,6 @@ namespace Emby.Server.Implementations.LiveTv private readonly ILibraryManager _libraryManager; private readonly ITaskManager _taskManager; private readonly ILocalizationManager _localization; - private readonly IJsonSerializer _jsonSerializer; private readonly IFileSystem _fileSystem; private readonly IChannelManager _channelManager; private readonly LiveTvDtoService _tvDtoService; @@ -73,7 +72,6 @@ namespace Emby.Server.Implementations.LiveTv ILibraryManager libraryManager, ITaskManager taskManager, ILocalizationManager localization, - IJsonSerializer jsonSerializer, IFileSystem fileSystem, IChannelManager channelManager, LiveTvDtoService liveTvDtoService) @@ -85,7 +83,6 @@ namespace Emby.Server.Implementations.LiveTv _libraryManager = libraryManager; _taskManager = taskManager; _localization = localization; - _jsonSerializer = jsonSerializer; _fileSystem = fileSystem; _dtoService = dtoService; _userDataManager = userDataManager; @@ -2234,7 +2231,7 @@ namespace Emby.Server.Implementations.LiveTv public async Task<TunerHostInfo> SaveTunerHost(TunerHostInfo info, bool dataSourceChanged = true) { - info = _jsonSerializer.DeserializeFromString<TunerHostInfo>(_jsonSerializer.SerializeToString(info)); + info = JsonSerializer.Deserialize<TunerHostInfo>(JsonSerializer.Serialize(info)); var provider = _tunerHosts.FirstOrDefault(i => string.Equals(info.Type, i.Type, StringComparison.OrdinalIgnoreCase)); @@ -2278,7 +2275,7 @@ namespace Emby.Server.Implementations.LiveTv { // Hack to make the object a pure ListingsProviderInfo instead of an AddListingProvider // ServerConfiguration.SaveConfiguration crashes during xml serialization for AddListingProvider - info = _jsonSerializer.DeserializeFromString<ListingsProviderInfo>(_jsonSerializer.SerializeToString(info)); + info = JsonSerializer.Deserialize<ListingsProviderInfo>(JsonSerializer.Serialize(info)); var provider = _listingProviders.FirstOrDefault(i => string.Equals(info.Type, i.Type, StringComparison.OrdinalIgnoreCase)); diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs index 2e2488e6e..00420bd2a 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs @@ -195,7 +195,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun while (!sr.EndOfStream) { string line = StripXML(sr.ReadLine()); - if (line.Contains("Channel")) + if (line.Contains("Channel", StringComparison.Ordinal)) { LiveTvTunerStatus status; var index = line.IndexOf("Channel", StringComparison.OrdinalIgnoreCase); @@ -226,6 +226,11 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun private static string StripXML(string source) { + if (string.IsNullOrEmpty(source)) + { + return string.Empty; + } + char[] buffer = new char[source.Length]; int bufferIndex = 0; bool inside = false; @@ -270,7 +275,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun for (int i = 0; i < model.TunerCount; ++i) { - var name = string.Format("Tuner {0}", i + 1); + var name = string.Format(CultureInfo.InvariantCulture, "Tuner {0}", i + 1); var currentChannel = "none"; // @todo Get current channel and map back to Station Id var isAvailable = await manager.CheckTunerAvailability(ipInfo, i, cancellationToken).ConfigureAwait(false); var status = isAvailable ? LiveTvTunerStatus.Available : LiveTvTunerStatus.LiveTv; diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs index 57c5b7500..d4a88e299 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs @@ -77,7 +77,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun } } - public class HdHomerunManager : IDisposable + public sealed class HdHomerunManager : IDisposable { public const int HdHomeRunPort = 65001; @@ -105,6 +105,8 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun StopStreaming(socket).GetAwaiter().GetResult(); } } + + GC.SuppressFinalize(this); } public async Task<bool> CheckTunerAvailability(IPAddress remoteIp, int tuner, CancellationToken cancellationToken) @@ -162,7 +164,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun } _activeTuner = i; - var lockKeyString = string.Format("{0:d}", lockKeyValue); + var lockKeyString = string.Format(CultureInfo.InvariantCulture, "{0:d}", lockKeyValue); var lockkeyMsg = CreateSetMessage(i, "lockkey", lockKeyString, null); await stream.WriteAsync(lockkeyMsg, 0, lockkeyMsg.Length, cancellationToken).ConfigureAwait(false); int receivedBytes = await stream.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false); @@ -173,8 +175,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun continue; } - var commandList = commands.GetCommands(); - foreach (var command in commandList) + foreach (var command in commands.GetCommands()) { var channelMsg = CreateSetMessage(i, command.Item1, command.Item2, lockKeyValue); await stream.WriteAsync(channelMsg, 0, channelMsg.Length, cancellationToken).ConfigureAwait(false); @@ -188,7 +189,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun } } - var targetValue = string.Format("rtp://{0}:{1}", localIp, localPort); + var targetValue = string.Format(CultureInfo.InvariantCulture, "rtp://{0}:{1}", localIp, localPort); var targetMsg = CreateSetMessage(i, "target", targetValue, lockKeyValue); await stream.WriteAsync(targetMsg, 0, targetMsg.Length, cancellationToken).ConfigureAwait(false); diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs index c798c0a85..875977219 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs @@ -158,15 +158,14 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts private string GetChannelNumber(string extInf, Dictionary<string, string> attributes, string mediaUrl) { var nameParts = extInf.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); - var nameInExtInf = nameParts.Length > 1 ? nameParts[nameParts.Length - 1].Trim() : null; + var nameInExtInf = nameParts.Length > 1 ? nameParts[^1].AsSpan().Trim() : ReadOnlySpan<char>.Empty; string numberString = null; string attributeValue; - double doubleValue; if (attributes.TryGetValue("tvg-chno", out attributeValue)) { - if (double.TryParse(attributeValue, NumberStyles.Any, CultureInfo.InvariantCulture, out doubleValue)) + if (double.TryParse(attributeValue, NumberStyles.Any, CultureInfo.InvariantCulture, out _)) { numberString = attributeValue; } @@ -176,36 +175,36 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts { if (attributes.TryGetValue("tvg-id", out attributeValue)) { - if (double.TryParse(attributeValue, NumberStyles.Any, CultureInfo.InvariantCulture, out doubleValue)) + if (double.TryParse(attributeValue, NumberStyles.Any, CultureInfo.InvariantCulture, out _)) { numberString = attributeValue; } else if (attributes.TryGetValue("channel-id", out attributeValue)) { - if (double.TryParse(attributeValue, NumberStyles.Any, CultureInfo.InvariantCulture, out doubleValue)) + if (double.TryParse(attributeValue, NumberStyles.Any, CultureInfo.InvariantCulture, out _)) { numberString = attributeValue; } } } - if (String.IsNullOrWhiteSpace(numberString)) + if (string.IsNullOrWhiteSpace(numberString)) { // Using this as a fallback now as this leads to Problems with channels like "5 USA" // where 5 isnt ment to be the channel number // Check for channel number with the format from SatIp // #EXTINF:0,84. VOX Schweiz // #EXTINF:0,84.0 - VOX Schweiz - if (!string.IsNullOrWhiteSpace(nameInExtInf)) + if (!nameInExtInf.IsEmpty && !nameInExtInf.IsWhiteSpace()) { var numberIndex = nameInExtInf.IndexOf(' '); if (numberIndex > 0) { - var numberPart = nameInExtInf.Substring(0, numberIndex).Trim(new[] { ' ', '.' }); + var numberPart = nameInExtInf.Slice(0, numberIndex).Trim(new[] { ' ', '.' }); - if (double.TryParse(numberPart, NumberStyles.Any, CultureInfo.InvariantCulture, out var number)) + if (double.TryParse(numberPart, NumberStyles.Any, CultureInfo.InvariantCulture, out _)) { - numberString = numberPart; + numberString = numberPart.ToString(); } } } @@ -231,7 +230,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts { try { - numberString = Path.GetFileNameWithoutExtension(mediaUrl.Split('/').Last()); + numberString = Path.GetFileNameWithoutExtension(mediaUrl.Split('/')[^1]); if (!IsValidChannelNumber(numberString)) { @@ -258,7 +257,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts return false; } - if (!double.TryParse(numberString, NumberStyles.Any, CultureInfo.InvariantCulture, out var value)) + if (!double.TryParse(numberString, NumberStyles.Any, CultureInfo.InvariantCulture, out _)) { return false; } @@ -281,7 +280,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts { var numberPart = nameInExtInf.Substring(0, numberIndex).Trim(new[] { ' ', '.' }); - if (double.TryParse(numberPart, NumberStyles.Any, CultureInfo.InvariantCulture, out var number)) + if (double.TryParse(numberPart, NumberStyles.Any, CultureInfo.InvariantCulture, out _)) { // channel.Number = number.ToString(); nameInExtInf = nameInExtInf.Substring(numberIndex + 1).Trim(new[] { ' ', '-' }); diff --git a/Emby.Server.Implementations/Localization/Core/de.json b/Emby.Server.Implementations/Localization/Core/de.json index a6e9779c9..eec880208 100644 --- a/Emby.Server.Implementations/Localization/Core/de.json +++ b/Emby.Server.Implementations/Localization/Core/de.json @@ -101,8 +101,8 @@ "TaskCleanTranscode": "Lösche Transkodier Pfad", "TaskUpdatePluginsDescription": "Lädt Updates für Plugins herunter, welche dazu eingestellt sind automatisch zu updaten und installiert sie.", "TaskUpdatePlugins": "Update Plugins", - "TaskRefreshPeopleDescription": "Erneuert Metadaten für Schausteller und Regisseure in deinen Bibliotheken.", - "TaskRefreshPeople": "Erneuere Schausteller", + "TaskRefreshPeopleDescription": "Erneuert Metadaten für Schauspieler und Regisseure in deinen Bibliotheken.", + "TaskRefreshPeople": "Erneuere Schauspieler", "TaskCleanLogsDescription": "Lösche Log Dateien die älter als {0} Tage sind.", "TaskCleanLogs": "Lösche Log Pfad", "TaskRefreshLibraryDescription": "Scanne alle Bibliotheken für hinzugefügte Datein und erneuere Metadaten.", diff --git a/Emby.Server.Implementations/Localization/Core/zh-TW.json b/Emby.Server.Implementations/Localization/Core/zh-TW.json index a22f66df9..a21cdad95 100644 --- a/Emby.Server.Implementations/Localization/Core/zh-TW.json +++ b/Emby.Server.Implementations/Localization/Core/zh-TW.json @@ -92,7 +92,7 @@ "HeaderRecordingGroups": "錄製組", "Inherit": "繼承", "SubtitleDownloadFailureFromForItem": "無法為 {1} 從 {0} 下載字幕", - "TaskDownloadMissingSubtitlesDescription": "在網路上透過描述資料搜尋遺失的字幕。", + "TaskDownloadMissingSubtitlesDescription": "在網路上透過中繼資料搜尋遺失的字幕。", "TaskDownloadMissingSubtitles": "下載遺失的字幕", "TaskRefreshChannels": "重新整理頻道", "TaskUpdatePlugins": "更新插件", diff --git a/Emby.Server.Implementations/Localization/LocalizationManager.cs b/Emby.Server.Implementations/Localization/LocalizationManager.cs index 62a23118f..90e2766b8 100644 --- a/Emby.Server.Implementations/Localization/LocalizationManager.cs +++ b/Emby.Server.Implementations/Localization/LocalizationManager.cs @@ -247,7 +247,7 @@ namespace Emby.Server.Implementations.Localization } // Try splitting by : to handle "Germany: FSK 18" - var index = rating.IndexOf(':'); + var index = rating.IndexOf(':', StringComparison.Ordinal); if (index != -1) { rating = rating.Substring(index).TrimStart(':').Trim(); @@ -312,12 +312,12 @@ namespace Emby.Server.Implementations.Localization throw new ArgumentNullException(nameof(culture)); } - const string prefix = "Core"; - var key = prefix + culture; + const string Prefix = "Core"; + var key = Prefix + culture; return _dictionaries.GetOrAdd( key, - f => GetDictionary(prefix, culture, DefaultCulture + ".json").GetAwaiter().GetResult()); + f => GetDictionary(Prefix, culture, DefaultCulture + ".json").GetAwaiter().GetResult()); } private async Task<Dictionary<string, string>> GetDictionary(string prefix, string culture, string baseFilename) diff --git a/Emby.Server.Implementations/Net/UdpSocket.cs b/Emby.Server.Implementations/Net/UdpSocket.cs index b51c03446..4e25768cf 100644 --- a/Emby.Server.Implementations/Net/UdpSocket.cs +++ b/Emby.Server.Implementations/Net/UdpSocket.cs @@ -15,13 +15,11 @@ namespace Emby.Server.Implementations.Net public sealed class UdpSocket : ISocket, IDisposable { private Socket _socket; - private int _localPort; + private readonly int _localPort; private bool _disposed = false; public Socket Socket => _socket; - public IPAddress LocalIPAddress { get; } - private readonly SocketAsyncEventArgs _receiveSocketAsyncEventArgs = new SocketAsyncEventArgs() { SocketFlags = SocketFlags.None @@ -51,18 +49,33 @@ namespace Emby.Server.Implementations.Net InitReceiveSocketAsyncEventArgs(); } + public UdpSocket(Socket socket, IPEndPoint endPoint) + { + if (socket == null) + { + throw new ArgumentNullException(nameof(socket)); + } + + _socket = socket; + _socket.Connect(endPoint); + + InitReceiveSocketAsyncEventArgs(); + } + + public IPAddress LocalIPAddress { get; } + private void InitReceiveSocketAsyncEventArgs() { var receiveBuffer = new byte[8192]; _receiveSocketAsyncEventArgs.SetBuffer(receiveBuffer, 0, receiveBuffer.Length); - _receiveSocketAsyncEventArgs.Completed += _receiveSocketAsyncEventArgs_Completed; + _receiveSocketAsyncEventArgs.Completed += OnReceiveSocketAsyncEventArgsCompleted; var sendBuffer = new byte[8192]; _sendSocketAsyncEventArgs.SetBuffer(sendBuffer, 0, sendBuffer.Length); - _sendSocketAsyncEventArgs.Completed += _sendSocketAsyncEventArgs_Completed; + _sendSocketAsyncEventArgs.Completed += OnSendSocketAsyncEventArgsCompleted; } - private void _receiveSocketAsyncEventArgs_Completed(object sender, SocketAsyncEventArgs e) + private void OnReceiveSocketAsyncEventArgsCompleted(object sender, SocketAsyncEventArgs e) { var tcs = _currentReceiveTaskCompletionSource; if (tcs != null) @@ -86,7 +99,7 @@ namespace Emby.Server.Implementations.Net } } - private void _sendSocketAsyncEventArgs_Completed(object sender, SocketAsyncEventArgs e) + private void OnSendSocketAsyncEventArgsCompleted(object sender, SocketAsyncEventArgs e) { var tcs = _currentSendTaskCompletionSource; if (tcs != null) @@ -104,19 +117,6 @@ namespace Emby.Server.Implementations.Net } } - public UdpSocket(Socket socket, IPEndPoint endPoint) - { - if (socket == null) - { - throw new ArgumentNullException(nameof(socket)); - } - - _socket = socket; - _socket.Connect(endPoint); - - InitReceiveSocketAsyncEventArgs(); - } - public IAsyncResult BeginReceive(byte[] buffer, int offset, int count, AsyncCallback callback) { ThrowIfDisposed(); @@ -247,6 +247,7 @@ namespace Emby.Server.Implementations.Net } } + /// <inheritdoc /> public void Dispose() { if (_disposed) @@ -255,6 +256,8 @@ namespace Emby.Server.Implementations.Net } _socket?.Dispose(); + _receiveSocketAsyncEventArgs.Dispose(); + _sendSocketAsyncEventArgs.Dispose(); _currentReceiveTaskCompletionSource?.TrySetCanceled(); _currentSendTaskCompletionSource?.TrySetCanceled(); diff --git a/Emby.Server.Implementations/Networking/NetworkManager.cs b/Emby.Server.Implementations/Networking/NetworkManager.cs index 50cb44b28..089ec30e6 100644 --- a/Emby.Server.Implementations/Networking/NetworkManager.cs +++ b/Emby.Server.Implementations/Networking/NetworkManager.cs @@ -165,7 +165,7 @@ namespace Emby.Server.Implementations.Networking (octet[0] == 127) || // RFC1122 (octet[0] == 169 && octet[1] == 254)) // RFC3927 { - return false; + return true; } if (checkSubnets && IsInPrivateAddressSpaceAndLocalSubnet(endpoint)) @@ -390,7 +390,7 @@ namespace Emby.Server.Implementations.Networking var host = uri.DnsSafeHost; _logger.LogDebug("Resolving host {0}", host); - address = GetIpAddresses(host).Result.FirstOrDefault(); + address = GetIpAddresses(host).GetAwaiter().GetResult().FirstOrDefault(); if (address != null) { diff --git a/Emby.Server.Implementations/Playlists/PlaylistManager.cs b/Emby.Server.Implementations/Playlists/PlaylistManager.cs index 5dd1af4b8..38ceadedb 100644 --- a/Emby.Server.Implementations/Playlists/PlaylistManager.cs +++ b/Emby.Server.Implementations/Playlists/PlaylistManager.cs @@ -349,16 +349,14 @@ namespace Emby.Server.Implementations.Playlists AlbumTitle = child.Album }; - var hasAlbumArtist = child as IHasAlbumArtist; - if (hasAlbumArtist != null) + if (child is IHasAlbumArtist hasAlbumArtist) { - entry.AlbumArtist = hasAlbumArtist.AlbumArtists.FirstOrDefault(); + entry.AlbumArtist = hasAlbumArtist.AlbumArtists.Count > 0 ? hasAlbumArtist.AlbumArtists[0] : null; } - var hasArtist = child as IHasArtist; - if (hasArtist != null) + if (child is IHasArtist hasArtist) { - entry.TrackArtist = hasArtist.Artists.FirstOrDefault(); + entry.TrackArtist = hasArtist.Artists.Count > 0 ? hasArtist.Artists[0] : null; } if (child.RunTimeTicks.HasValue) @@ -385,16 +383,14 @@ namespace Emby.Server.Implementations.Playlists AlbumTitle = child.Album }; - var hasAlbumArtist = child as IHasAlbumArtist; - if (hasAlbumArtist != null) + if (child is IHasAlbumArtist hasAlbumArtist) { - entry.AlbumArtist = hasAlbumArtist.AlbumArtists.FirstOrDefault(); + entry.AlbumArtist = hasAlbumArtist.AlbumArtists.Count > 0 ? hasAlbumArtist.AlbumArtists[0] : null; } - var hasArtist = child as IHasArtist; - if (hasArtist != null) + if (child is IHasArtist hasArtist) { - entry.TrackArtist = hasArtist.Artists.FirstOrDefault(); + entry.TrackArtist = hasArtist.Artists.Count > 0 ? hasArtist.Artists[0] : null; } if (child.RunTimeTicks.HasValue) @@ -411,8 +407,10 @@ namespace Emby.Server.Implementations.Playlists if (string.Equals(".m3u", extension, StringComparison.OrdinalIgnoreCase)) { - var playlist = new M3uPlaylist(); - playlist.IsExtended = true; + var playlist = new M3uPlaylist + { + IsExtended = true + }; foreach (var child in item.GetLinkedChildren()) { var entry = new M3uPlaylistEntry() @@ -422,10 +420,9 @@ namespace Emby.Server.Implementations.Playlists Album = child.Album }; - var hasAlbumArtist = child as IHasAlbumArtist; - if (hasAlbumArtist != null) + if (child is IHasAlbumArtist hasAlbumArtist) { - entry.AlbumArtist = hasAlbumArtist.AlbumArtists.FirstOrDefault(); + entry.AlbumArtist = hasAlbumArtist.AlbumArtists.Count > 0 ? hasAlbumArtist.AlbumArtists[0] : null; } if (child.RunTimeTicks.HasValue) @@ -453,10 +450,9 @@ namespace Emby.Server.Implementations.Playlists Album = child.Album }; - var hasAlbumArtist = child as IHasAlbumArtist; - if (hasAlbumArtist != null) + if (child is IHasAlbumArtist hasAlbumArtist) { - entry.AlbumArtist = hasAlbumArtist.AlbumArtists.FirstOrDefault(); + entry.AlbumArtist = hasAlbumArtist.AlbumArtists.Count > 0 ? hasAlbumArtist.AlbumArtists[0] : null; } if (child.RunTimeTicks.HasValue) @@ -514,7 +510,7 @@ namespace Emby.Server.Implementations.Playlists if (!folderPath.EndsWith(Path.DirectorySeparatorChar)) { - folderPath = folderPath + Path.DirectorySeparatorChar; + folderPath += Path.DirectorySeparatorChar; } var folderUri = new Uri(folderPath); @@ -537,32 +533,12 @@ namespace Emby.Server.Implementations.Playlists return relativePath; } - private static string UnEscape(string content) - { - if (content == null) - { - return content; - } - - return content.Replace("&", "&").Replace("'", "'").Replace(""", "\"").Replace(">", ">").Replace("<", "<"); - } - - private static string Escape(string content) - { - if (content == null) - { - return null; - } - - return content.Replace("&", "&").Replace("'", "'").Replace("\"", """).Replace(">", ">").Replace("<", "<"); - } - public Folder GetPlaylistsFolder(Guid userId) { - var typeName = "PlaylistsFolder"; + const string TypeName = "PlaylistsFolder"; - return _libraryManager.RootFolder.Children.OfType<Folder>().FirstOrDefault(i => string.Equals(i.GetType().Name, typeName, StringComparison.Ordinal)) ?? - _libraryManager.GetUserRootFolder().Children.OfType<Folder>().FirstOrDefault(i => string.Equals(i.GetType().Name, typeName, StringComparison.Ordinal)); + return _libraryManager.RootFolder.Children.OfType<Folder>().FirstOrDefault(i => string.Equals(i.GetType().Name, TypeName, StringComparison.Ordinal)) ?? + _libraryManager.GetUserRootFolder().Children.OfType<Folder>().FirstOrDefault(i => string.Equals(i.GetType().Name, TypeName, StringComparison.Ordinal)); } } } diff --git a/Emby.Server.Implementations/ScheduledTasks/TaskManager.cs b/Emby.Server.Implementations/ScheduledTasks/TaskManager.cs index 3fe15ec68..81096026b 100644 --- a/Emby.Server.Implementations/ScheduledTasks/TaskManager.cs +++ b/Emby.Server.Implementations/ScheduledTasks/TaskManager.cs @@ -7,7 +7,6 @@ using System.Linq; using System.Threading.Tasks; using MediaBrowser.Common.Configuration; using MediaBrowser.Model.Events; -using MediaBrowser.Model.IO; using MediaBrowser.Model.Serialization; using MediaBrowser.Model.Tasks; using Microsoft.Extensions.Logging; @@ -37,7 +36,6 @@ namespace Emby.Server.Implementations.ScheduledTasks private readonly IJsonSerializer _jsonSerializer; private readonly IApplicationPaths _applicationPaths; private readonly ILogger<TaskManager> _logger; - private readonly IFileSystem _fileSystem; /// <summary> /// Initializes a new instance of the <see cref="TaskManager" /> class. @@ -45,17 +43,14 @@ namespace Emby.Server.Implementations.ScheduledTasks /// <param name="applicationPaths">The application paths.</param> /// <param name="jsonSerializer">The json serializer.</param> /// <param name="logger">The logger.</param> - /// <param name="fileSystem">The filesystem manager.</param> public TaskManager( IApplicationPaths applicationPaths, IJsonSerializer jsonSerializer, - ILogger<TaskManager> logger, - IFileSystem fileSystem) + ILogger<TaskManager> logger) { _applicationPaths = applicationPaths; _jsonSerializer = jsonSerializer; _logger = logger; - _fileSystem = fileSystem; ScheduledTasks = Array.Empty<IScheduledTaskWorker>(); } diff --git a/Emby.Server.Implementations/ScheduledTasks/Tasks/ChapterImagesTask.cs b/Emby.Server.Implementations/ScheduledTasks/Tasks/ChapterImagesTask.cs index 3854be703..8439f8a99 100644 --- a/Emby.Server.Implementations/ScheduledTasks/Tasks/ChapterImagesTask.cs +++ b/Emby.Server.Implementations/ScheduledTasks/Tasks/ChapterImagesTask.cs @@ -14,7 +14,6 @@ using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Entities; using MediaBrowser.Model.IO; using MediaBrowser.Model.Tasks; -using Microsoft.Extensions.Logging; using MediaBrowser.Model.Globalization; namespace Emby.Server.Implementations.ScheduledTasks @@ -25,11 +24,6 @@ namespace Emby.Server.Implementations.ScheduledTasks public class ChapterImagesTask : IScheduledTask { /// <summary> - /// The _logger. - /// </summary> - private readonly ILogger<ChapterImagesTask> _logger; - - /// <summary> /// The _library manager. /// </summary> private readonly ILibraryManager _libraryManager; @@ -46,7 +40,6 @@ namespace Emby.Server.Implementations.ScheduledTasks /// Initializes a new instance of the <see cref="ChapterImagesTask" /> class. /// </summary> public ChapterImagesTask( - ILoggerFactory loggerFactory, ILibraryManager libraryManager, IItemRepository itemRepo, IApplicationPaths appPaths, @@ -54,7 +47,6 @@ namespace Emby.Server.Implementations.ScheduledTasks IFileSystem fileSystem, ILocalizationManager localization) { - _logger = loggerFactory.CreateLogger<ChapterImagesTask>(); _libraryManager = libraryManager; _itemRepo = itemRepo; _appPaths = appPaths; diff --git a/Emby.Server.Implementations/Services/ServiceController.cs b/Emby.Server.Implementations/Services/ServiceController.cs index 857df591a..47e7261e8 100644 --- a/Emby.Server.Implementations/Services/ServiceController.cs +++ b/Emby.Server.Implementations/Services/ServiceController.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.Threading.Tasks; using Emby.Server.Implementations.HttpServer; using MediaBrowser.Model.Services; @@ -91,12 +92,22 @@ namespace Emby.Server.Implementations.Services { if (restPath.Path[0] != '/') { - throw new ArgumentException(string.Format("Route '{0}' on '{1}' must start with a '/'", restPath.Path, restPath.RequestType.GetMethodName())); + throw new ArgumentException( + string.Format( + CultureInfo.InvariantCulture, + "Route '{0}' on '{1}' must start with a '/'", + restPath.Path, + restPath.RequestType.GetMethodName())); } if (restPath.Path.IndexOfAny(InvalidRouteChars) != -1) { - throw new ArgumentException(string.Format("Route '{0}' on '{1}' contains invalid chars. ", restPath.Path, restPath.RequestType.GetMethodName())); + throw new ArgumentException( + string.Format( + CultureInfo.InvariantCulture, + "Route '{0}' on '{1}' contains invalid chars. ", + restPath.Path, + restPath.RequestType.GetMethodName())); } if (RestPathMap.TryGetValue(restPath.FirstMatchHashKey, out List<RestPath> pathsAtFirstMatch)) @@ -179,8 +190,7 @@ namespace Emby.Server.Implementations.Services var service = httpHost.CreateInstance(serviceType); - var serviceRequiresContext = service as IRequiresRequest; - if (serviceRequiresContext != null) + if (service is IRequiresRequest serviceRequiresContext) { serviceRequiresContext.Request = req; } @@ -189,5 +199,4 @@ namespace Emby.Server.Implementations.Services return ServiceExecGeneral.Execute(serviceType, req, service, requestDto, requestType.GetMethodName()); } } - } diff --git a/Emby.Server.Implementations/Services/ServiceExec.cs b/Emby.Server.Implementations/Services/ServiceExec.cs index cbc4b754d..7b970627e 100644 --- a/Emby.Server.Implementations/Services/ServiceExec.cs +++ b/Emby.Server.Implementations/Services/ServiceExec.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.Linq; using System.Linq.Expressions; using System.Reflection; @@ -105,7 +106,13 @@ namespace Emby.Server.Implementations.Services } var expectedMethodName = actionName.Substring(0, 1) + actionName.Substring(1).ToLowerInvariant(); - throw new NotImplementedException(string.Format("Could not find method named {1}({0}) or Any({0}) on Service {2}", requestDto.GetType().GetMethodName(), expectedMethodName, serviceType.GetMethodName())); + throw new NotImplementedException( + string.Format( + CultureInfo.InvariantCulture, + "Could not find method named {1}({0}) or Any({0}) on Service {2}", + requestDto.GetType().GetMethodName(), + expectedMethodName, + serviceType.GetMethodName())); } private static async Task<object> GetTaskResult(Task task) diff --git a/Emby.Server.Implementations/Services/ServiceHandler.cs b/Emby.Server.Implementations/Services/ServiceHandler.cs index a42f88ea0..b4166f771 100644 --- a/Emby.Server.Implementations/Services/ServiceHandler.cs +++ b/Emby.Server.Implementations/Services/ServiceHandler.cs @@ -2,10 +2,12 @@ using System; using System.Collections.Generic; +using System.Net.Mime; using System.Reflection; using System.Threading; using System.Threading.Tasks; using Emby.Server.Implementations.HttpServer; +using MediaBrowser.Common.Extensions; using MediaBrowser.Model.Services; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Logging; @@ -44,7 +46,7 @@ namespace Emby.Server.Implementations.Services var pos = pathInfo.LastIndexOf('.'); if (pos != -1) { - var format = pathInfo.Substring(pos + 1); + var format = pathInfo.AsSpan().Slice(pos + 1); contentType = GetFormatContentType(format); if (contentType != null) { @@ -55,18 +57,21 @@ namespace Emby.Server.Implementations.Services return pathInfo; } - private static string GetFormatContentType(string format) + private static string GetFormatContentType(ReadOnlySpan<char> format) { - // built-in formats - switch (format) + if (format.Equals("json", StringComparison.Ordinal)) { - case "json": return "application/json"; - case "xml": return "application/xml"; - default: return null; + return MediaTypeNames.Application.Json; } + else if (format.Equals("xml", StringComparison.Ordinal)) + { + return MediaTypeNames.Application.Xml; + } + + return null; } - public async Task ProcessRequestAsync(HttpListenerHost httpHost, IRequest httpReq, HttpResponse httpRes, ILogger logger, CancellationToken cancellationToken) + public async Task ProcessRequestAsync(HttpListenerHost httpHost, IRequest httpReq, HttpResponse httpRes, CancellationToken cancellationToken) { httpReq.Items["__route"] = _restPath; @@ -75,10 +80,11 @@ namespace Emby.Server.Implementations.Services httpReq.ResponseContentType = _responseContentType; } - var request = await CreateRequest(httpHost, httpReq, _restPath, logger).ConfigureAwait(false); + var request = await CreateRequest(httpHost, httpReq, _restPath).ConfigureAwait(false); httpHost.ApplyRequestFilters(httpReq, httpRes, request); + httpRes.HttpContext.SetServiceStackRequest(httpReq); var response = await httpHost.ServiceController.Execute(httpHost, request, httpReq).ConfigureAwait(false); // Apply response filters @@ -90,7 +96,7 @@ namespace Emby.Server.Implementations.Services await ResponseHelper.WriteToResponse(httpRes, httpReq, response, cancellationToken).ConfigureAwait(false); } - public static async Task<object> CreateRequest(HttpListenerHost host, IRequest httpReq, RestPath restPath, ILogger logger) + public static async Task<object> CreateRequest(HttpListenerHost host, IRequest httpReq, RestPath restPath) { var requestType = restPath.RequestType; diff --git a/Emby.Server.Implementations/Services/ServicePath.cs b/Emby.Server.Implementations/Services/ServicePath.cs index 89538ae72..442b2ab1c 100644 --- a/Emby.Server.Implementations/Services/ServicePath.cs +++ b/Emby.Server.Implementations/Services/ServicePath.cs @@ -156,7 +156,7 @@ namespace Emby.Server.Implementations.Services { var component = components[i]; - if (component.StartsWith(VariablePrefix)) + if (component.StartsWith(VariablePrefix, StringComparison.Ordinal)) { var variableName = component.Substring(1, component.Length - 2); if (variableName[variableName.Length - 1] == WildCardChar) @@ -488,7 +488,8 @@ namespace Emby.Server.Implementations.Services sb.Append(value); for (var j = pathIx + 1; j < requestComponents.Length; j++) { - sb.Append(PathSeperatorChar + requestComponents[j]); + sb.Append(PathSeperatorChar) + .Append(requestComponents[j]); } value = sb.ToString(); @@ -505,7 +506,8 @@ namespace Emby.Server.Implementations.Services pathIx++; while (!string.Equals(requestComponents[pathIx], stopLiteral, StringComparison.OrdinalIgnoreCase)) { - sb.Append(PathSeperatorChar + requestComponents[pathIx++]); + sb.Append(PathSeperatorChar) + .Append(requestComponents[pathIx++]); } value = sb.ToString(); diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs index ca9f95c70..862a7296c 100644 --- a/Emby.Server.Implementations/Session/SessionManager.cs +++ b/Emby.Server.Implementations/Session/SessionManager.cs @@ -848,8 +848,8 @@ namespace Emby.Server.Implementations.Session /// </summary> /// <param name="info">The info.</param> /// <returns>Task.</returns> - /// <exception cref="ArgumentNullException">info</exception> - /// <exception cref="ArgumentOutOfRangeException">positionTicks</exception> + /// <exception cref="ArgumentNullException"><c>info</c> is <c>null</c>.</exception> + /// <exception cref="ArgumentOutOfRangeException"><c>info.PositionTicks</c> is <c>null</c> or negative.</exception> public async Task OnPlaybackStopped(PlaybackStopInfo info) { CheckDisposed(); diff --git a/Emby.Server.Implementations/Session/SessionWebSocketListener.cs b/Emby.Server.Implementations/Session/SessionWebSocketListener.cs index b9db6ecd0..8bebd37dc 100644 --- a/Emby.Server.Implementations/Session/SessionWebSocketListener.cs +++ b/Emby.Server.Implementations/Session/SessionWebSocketListener.cs @@ -93,7 +93,7 @@ namespace Emby.Server.Implementations.Session if (session != null) { EnsureController(session, e.Argument); - await KeepAliveWebSocket(e.Argument); + await KeepAliveWebSocket(e.Argument).ConfigureAwait(false); } else { @@ -177,7 +177,7 @@ namespace Emby.Server.Implementations.Session // Notify WebSocket about timeout try { - await SendForceKeepAlive(webSocket); + await SendForceKeepAlive(webSocket).ConfigureAwait(false); } catch (WebSocketException exception) { @@ -233,6 +233,7 @@ namespace Emby.Server.Implementations.Session if (_keepAliveCancellationToken != null) { _keepAliveCancellationToken.Cancel(); + _keepAliveCancellationToken.Dispose(); _keepAliveCancellationToken = null; } } @@ -268,7 +269,7 @@ namespace Emby.Server.Implementations.Session lost = _webSockets.Where(i => (DateTime.UtcNow - i.LastKeepAliveDate).TotalSeconds >= WebSocketLostTimeout).ToList(); } - if (inactive.Any()) + if (inactive.Count > 0) { _logger.LogInformation("Sending ForceKeepAlive message to {0} inactive WebSockets.", inactive.Count); } @@ -277,7 +278,7 @@ namespace Emby.Server.Implementations.Session { try { - await SendForceKeepAlive(webSocket); + await SendForceKeepAlive(webSocket).ConfigureAwait(false); } catch (WebSocketException exception) { @@ -288,7 +289,7 @@ namespace Emby.Server.Implementations.Session lock (_webSocketsLock) { - if (lost.Any()) + if (lost.Count > 0) { _logger.LogInformation("Lost {0} WebSockets.", lost.Count); foreach (var webSocket in lost) @@ -298,7 +299,7 @@ namespace Emby.Server.Implementations.Session } } - if (!_webSockets.Any()) + if (_webSockets.Count == 0) { StopKeepAlive(); } @@ -312,11 +313,13 @@ namespace Emby.Server.Implementations.Session /// <returns>Task.</returns> private Task SendForceKeepAlive(IWebSocketConnection webSocket) { - return webSocket.SendAsync(new WebSocketMessage<int> - { - MessageType = "ForceKeepAlive", - Data = WebSocketLostTimeout - }, CancellationToken.None); + return webSocket.SendAsync( + new WebSocketMessage<int> + { + MessageType = "ForceKeepAlive", + Data = WebSocketLostTimeout + }, + CancellationToken.None); } /// <summary> @@ -330,12 +333,11 @@ namespace Emby.Server.Implementations.Session { while (!cancellationToken.IsCancellationRequested) { - await callback(); - Task task = Task.Delay(interval, cancellationToken); + await callback().ConfigureAwait(false); try { - await task; + await Task.Delay(interval, cancellationToken).ConfigureAwait(false); } catch (TaskCanceledException) { diff --git a/Emby.Server.Implementations/Sorting/AiredEpisodeOrderComparer.cs b/Emby.Server.Implementations/Sorting/AiredEpisodeOrderComparer.cs index 2b7d818be..1f68a9c81 100644 --- a/Emby.Server.Implementations/Sorting/AiredEpisodeOrderComparer.cs +++ b/Emby.Server.Implementations/Sorting/AiredEpisodeOrderComparer.cs @@ -154,8 +154,8 @@ namespace Emby.Server.Implementations.Sorting private static int CompareEpisodes(Episode x, Episode y) { - var xValue = (x.ParentIndexNumber ?? -1) * 1000 + (x.IndexNumber ?? -1); - var yValue = (y.ParentIndexNumber ?? -1) * 1000 + (y.IndexNumber ?? -1); + var xValue = ((x.ParentIndexNumber ?? -1) * 1000) + (x.IndexNumber ?? -1); + var yValue = ((y.ParentIndexNumber ?? -1) * 1000) + (y.IndexNumber ?? -1); return xValue.CompareTo(yValue); } diff --git a/Emby.Server.Implementations/SyncPlay/SyncPlayController.cs b/Emby.Server.Implementations/SyncPlay/SyncPlayController.cs index 39d17833f..80b977731 100644 --- a/Emby.Server.Implementations/SyncPlay/SyncPlayController.cs +++ b/Emby.Server.Implementations/SyncPlay/SyncPlayController.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -27,14 +28,17 @@ namespace Emby.Server.Implementations.SyncPlay /// All sessions will receive the message. /// </summary> AllGroup = 0, + /// <summary> /// Only the specified session will receive the message. /// </summary> CurrentSession = 1, + /// <summary> /// All sessions, except the current one, will receive the message. /// </summary> AllExceptCurrentSession = 2, + /// <summary> /// Only sessions that are not buffering will receive the message. /// </summary> @@ -56,15 +60,6 @@ namespace Emby.Server.Implementations.SyncPlay /// </summary> private readonly GroupInfo _group = new GroupInfo(); - /// <inheritdoc /> - public Guid GetGroupId() => _group.GroupId; - - /// <inheritdoc /> - public Guid GetPlayingItemId() => _group.PlayingItem.Id; - - /// <inheritdoc /> - public bool IsGroupEmpty() => _group.IsEmpty(); - /// <summary> /// Initializes a new instance of the <see cref="SyncPlayController" /> class. /// </summary> @@ -78,6 +73,15 @@ namespace Emby.Server.Implementations.SyncPlay _syncPlayManager = syncPlayManager; } + /// <inheritdoc /> + public Guid GetGroupId() => _group.GroupId; + + /// <inheritdoc /> + public Guid GetPlayingItemId() => _group.PlayingItem.Id; + + /// <inheritdoc /> + public bool IsGroupEmpty() => _group.IsEmpty(); + /// <summary> /// Converts DateTime to UTC string. /// </summary> @@ -85,7 +89,7 @@ namespace Emby.Server.Implementations.SyncPlay /// <value>The UTC string.</value> private string DateToUTCString(DateTime date) { - return date.ToUniversalTime().ToString("o"); + return date.ToUniversalTime().ToString("o", CultureInfo.InvariantCulture); } /// <summary> @@ -94,23 +98,23 @@ namespace Emby.Server.Implementations.SyncPlay /// <param name="from">The current session.</param> /// <param name="type">The filtering type.</param> /// <value>The array of sessions matching the filter.</value> - private SessionInfo[] FilterSessions(SessionInfo from, BroadcastType type) + private IEnumerable<SessionInfo> FilterSessions(SessionInfo from, BroadcastType type) { switch (type) { case BroadcastType.CurrentSession: return new SessionInfo[] { from }; case BroadcastType.AllGroup: - return _group.Participants.Values.Select( - session => session.Session).ToArray(); + return _group.Participants.Values + .Select(session => session.Session); case BroadcastType.AllExceptCurrentSession: - return _group.Participants.Values.Select( - session => session.Session).Where( - session => !session.Id.Equals(from.Id)).ToArray(); + return _group.Participants.Values + .Select(session => session.Session) + .Where(session => !session.Id.Equals(from.Id, StringComparison.Ordinal)); case BroadcastType.AllReady: - return _group.Participants.Values.Where( - session => !session.IsBuffering).Select( - session => session.Session).ToArray(); + return _group.Participants.Values + .Where(session => !session.IsBuffering) + .Select(session => session.Session); default: return Array.Empty<SessionInfo>(); } @@ -128,10 +132,9 @@ namespace Emby.Server.Implementations.SyncPlay { IEnumerable<Task> GetTasks() { - SessionInfo[] sessions = FilterSessions(from, type); - foreach (var session in sessions) + foreach (var session in FilterSessions(from, type)) { - yield return _sessionManager.SendSyncPlayGroupUpdate(session.Id.ToString(), message, cancellationToken); + yield return _sessionManager.SendSyncPlayGroupUpdate(session.Id, message, cancellationToken); } } @@ -150,10 +153,9 @@ namespace Emby.Server.Implementations.SyncPlay { IEnumerable<Task> GetTasks() { - SessionInfo[] sessions = FilterSessions(from, type); - foreach (var session in sessions) + foreach (var session in FilterSessions(from, type)) { - yield return _sessionManager.SendSyncPlayCommand(session.Id.ToString(), message, cancellationToken); + yield return _sessionManager.SendSyncPlayCommand(session.Id, message, cancellationToken); } } @@ -236,9 +238,11 @@ namespace Emby.Server.Implementations.SyncPlay } else { - var playRequest = new PlayRequest(); - playRequest.ItemIds = new Guid[] { _group.PlayingItem.Id }; - playRequest.StartPositionTicks = _group.PositionTicks; + var playRequest = new PlayRequest + { + ItemIds = new Guid[] { _group.PlayingItem.Id }, + StartPositionTicks = _group.PositionTicks + }; var update = NewSyncPlayGroupUpdate(GroupUpdateType.PrepareSession, playRequest); SendGroupUpdate(session, BroadcastType.CurrentSession, update, cancellationToken); } |
