diff options
Diffstat (limited to 'Emby.Server.Implementations')
119 files changed, 824 insertions, 2287 deletions
diff --git a/Emby.Server.Implementations/Activity/ActivityLogEntryPoint.cs b/Emby.Server.Implementations/Activity/ActivityLogEntryPoint.cs index 6917efefa..84bec9201 100644 --- a/Emby.Server.Implementations/Activity/ActivityLogEntryPoint.cs +++ b/Emby.Server.Implementations/Activity/ActivityLogEntryPoint.cs @@ -88,25 +88,26 @@ namespace Emby.Server.Implementations.Activity _subManager.SubtitleDownloadFailure += OnSubtitleDownloadFailure; - _userManager.UserCreated += OnUserCreated; - _userManager.UserPasswordChanged += OnUserPasswordChanged; - _userManager.UserDeleted += OnUserDeleted; - _userManager.UserPolicyUpdated += OnUserPolicyUpdated; - _userManager.UserLockedOut += OnUserLockedOut; + _userManager.OnUserCreated += OnUserCreated; + _userManager.OnUserPasswordChanged += OnUserPasswordChanged; + _userManager.OnUserDeleted += OnUserDeleted; + _userManager.OnUserLockedOut += OnUserLockedOut; return Task.CompletedTask; } - private async void OnUserLockedOut(object sender, GenericEventArgs<MediaBrowser.Controller.Entities.User> e) + private async void OnUserLockedOut(object sender, GenericEventArgs<User> e) { await CreateLogEntry(new ActivityLog( string.Format( CultureInfo.InvariantCulture, _localization.GetLocalizedString("UserLockedOutWithName"), - e.Argument.Name), + e.Argument.Username), NotificationType.UserLockedOut.ToString(), - e.Argument.Id)) - .ConfigureAwait(false); + e.Argument.Id) + { + LogSeverity = LogLevel.Error + }).ConfigureAwait(false); } private async void OnSubtitleDownloadFailure(object sender, SubtitleDownloadFailureEventArgs e) @@ -152,7 +153,7 @@ namespace Emby.Server.Implementations.Activity string.Format( CultureInfo.InvariantCulture, _localization.GetLocalizedString("UserStoppedPlayingItemWithValues"), - user.Name, + user.Username, GetItemName(item), e.DeviceName), GetPlaybackStoppedNotificationType(item.MediaType), @@ -187,7 +188,7 @@ namespace Emby.Server.Implementations.Activity string.Format( CultureInfo.InvariantCulture, _localization.GetLocalizedString("UserStartedPlayingItemWithValues"), - user.Name, + user.Username, GetItemName(item), e.DeviceName), GetPlaybackNotificationType(item.MediaType), @@ -304,49 +305,37 @@ namespace Emby.Server.Implementations.Activity }).ConfigureAwait(false); } - private async void OnUserPolicyUpdated(object sender, GenericEventArgs<MediaBrowser.Controller.Entities.User> e) - { - await CreateLogEntry(new ActivityLog( - string.Format( - CultureInfo.InvariantCulture, - _localization.GetLocalizedString("UserPolicyUpdatedWithName"), - e.Argument.Name), - "UserPolicyUpdated", - e.Argument.Id)) - .ConfigureAwait(false); - } - - private async void OnUserDeleted(object sender, GenericEventArgs<MediaBrowser.Controller.Entities.User> e) + private async void OnUserDeleted(object sender, GenericEventArgs<User> e) { await CreateLogEntry(new ActivityLog( string.Format( CultureInfo.InvariantCulture, _localization.GetLocalizedString("UserDeletedWithName"), - e.Argument.Name), + e.Argument.Username), "UserDeleted", Guid.Empty)) .ConfigureAwait(false); } - private async void OnUserPasswordChanged(object sender, GenericEventArgs<MediaBrowser.Controller.Entities.User> e) + private async void OnUserPasswordChanged(object sender, GenericEventArgs<User> e) { await CreateLogEntry(new ActivityLog( string.Format( CultureInfo.InvariantCulture, _localization.GetLocalizedString("UserPasswordChangedWithName"), - e.Argument.Name), + e.Argument.Username), "UserPasswordChanged", e.Argument.Id)) .ConfigureAwait(false); } - private async void OnUserCreated(object sender, GenericEventArgs<MediaBrowser.Controller.Entities.User> e) + private async void OnUserCreated(object sender, GenericEventArgs<User> e) { await CreateLogEntry(new ActivityLog( string.Format( CultureInfo.InvariantCulture, _localization.GetLocalizedString("UserCreatedWithName"), - e.Argument.Name), + e.Argument.Username), "UserCreated", e.Argument.Id)) .ConfigureAwait(false); @@ -510,11 +499,10 @@ namespace Emby.Server.Implementations.Activity _subManager.SubtitleDownloadFailure -= OnSubtitleDownloadFailure; - _userManager.UserCreated -= OnUserCreated; - _userManager.UserPasswordChanged -= OnUserPasswordChanged; - _userManager.UserDeleted -= OnUserDeleted; - _userManager.UserPolicyUpdated -= OnUserPolicyUpdated; - _userManager.UserLockedOut -= OnUserLockedOut; + _userManager.OnUserCreated -= OnUserCreated; + _userManager.OnUserPasswordChanged -= OnUserPasswordChanged; + _userManager.OnUserDeleted -= OnUserDeleted; + _userManager.OnUserLockedOut -= OnUserLockedOut; } /// <summary> diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 184c6f399..23f0571a1 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -562,11 +562,8 @@ namespace Emby.Server.Implementations serviceCollection.AddSingleton<IAuthenticationRepository, AuthenticationRepository>(); - serviceCollection.AddSingleton<IUserRepository, SqliteUserRepository>(); - // TODO: Refactor to eliminate the circular dependency here so that Lazy<T> isn't required serviceCollection.AddTransient(provider => new Lazy<IDtoService>(provider.GetRequiredService<IDtoService>)); - serviceCollection.AddSingleton<IUserManager, UserManager>(); // TODO: Refactor to eliminate the circular dependency here so that Lazy<T> isn't required // TODO: Add StartupOptions.FFmpegPath to IConfiguration and remove this custom activation @@ -659,15 +656,11 @@ namespace Emby.Server.Implementations ((SqliteDisplayPreferencesRepository)Resolve<IDisplayPreferencesRepository>()).Initialize(); ((AuthenticationRepository)Resolve<IAuthenticationRepository>()).Initialize(); - ((SqliteUserRepository)Resolve<IUserRepository>()).Initialize(); SetStaticProperties(); - var userManager = (UserManager)Resolve<IUserManager>(); - userManager.Initialize(); - var userDataRepo = (SqliteUserDataRepository)Resolve<IUserDataRepository>(); - ((SqliteItemRepository)Resolve<IItemRepository>()).Initialize(userDataRepo, userManager); + ((SqliteItemRepository)Resolve<IItemRepository>()).Initialize(userDataRepo, Resolve<IUserManager>()); FindParts(); } @@ -750,7 +743,6 @@ namespace Emby.Server.Implementations BaseItem.ProviderManager = Resolve<IProviderManager>(); BaseItem.LocalizationManager = Resolve<ILocalizationManager>(); BaseItem.ItemRepository = Resolve<IItemRepository>(); - User.UserManager = Resolve<IUserManager>(); BaseItem.FileSystem = _fileSystemManager; BaseItem.UserDataManager = Resolve<IUserDataManager>(); BaseItem.ChannelManager = Resolve<IChannelManager>(); @@ -964,7 +956,7 @@ namespace Emby.Server.Implementations } /// <summary> - /// Notifies that the kernel that a change has been made that requires a restart + /// Notifies that the kernel that a change has been made that requires a restart. /// </summary> public void NotifyPendingRestart() { diff --git a/Emby.Server.Implementations/Channels/ChannelManager.cs b/Emby.Server.Implementations/Channels/ChannelManager.cs index f9eea66b4..c803d9d82 100644 --- a/Emby.Server.Implementations/Channels/ChannelManager.cs +++ b/Emby.Server.Implementations/Channels/ChannelManager.cs @@ -6,6 +6,7 @@ using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; +using Jellyfin.Data.Entities; using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Progress; using MediaBrowser.Controller.Channels; @@ -13,8 +14,6 @@ using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Dto; 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.Providers; using MediaBrowser.Model.Channels; @@ -24,6 +23,11 @@ using MediaBrowser.Model.IO; using MediaBrowser.Model.Querying; using MediaBrowser.Model.Serialization; using Microsoft.Extensions.Logging; +using Episode = MediaBrowser.Controller.Entities.TV.Episode; +using Movie = MediaBrowser.Controller.Entities.Movies.Movie; +using MusicAlbum = MediaBrowser.Controller.Entities.Audio.MusicAlbum; +using Season = MediaBrowser.Controller.Entities.TV.Season; +using Series = MediaBrowser.Controller.Entities.TV.Series; namespace Emby.Server.Implementations.Channels { @@ -791,7 +795,8 @@ namespace Emby.Server.Implementations.Channels return result; } - private async Task<ChannelItemResult> GetChannelItems(IChannel channel, + private async Task<ChannelItemResult> GetChannelItems( + IChannel channel, User user, string externalFolderId, ChannelItemSortField? sortField, @@ -1067,7 +1072,7 @@ namespace Emby.Server.Implementations.Channels } // was used for status - //if (!string.Equals(item.ExternalEtag ?? string.Empty, info.Etag ?? string.Empty, StringComparison.Ordinal)) + // if (!string.Equals(item.ExternalEtag ?? string.Empty, info.Etag ?? string.Empty, StringComparison.Ordinal)) //{ // item.ExternalEtag = info.Etag; // forceUpdate = true; diff --git a/Emby.Server.Implementations/Collections/CollectionManager.cs b/Emby.Server.Implementations/Collections/CollectionManager.cs index cba3975cf..8fb9520d6 100644 --- a/Emby.Server.Implementations/Collections/CollectionManager.cs +++ b/Emby.Server.Implementations/Collections/CollectionManager.cs @@ -5,6 +5,7 @@ using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; +using Jellyfin.Data.Entities; using MediaBrowser.Common.Configuration; using MediaBrowser.Controller.Collections; using MediaBrowser.Controller.Configuration; diff --git a/Emby.Server.Implementations/Data/BaseSqliteRepository.cs b/Emby.Server.Implementations/Data/BaseSqliteRepository.cs index f816fd54f..8a3716380 100644 --- a/Emby.Server.Implementations/Data/BaseSqliteRepository.cs +++ b/Emby.Server.Implementations/Data/BaseSqliteRepository.cs @@ -162,7 +162,6 @@ namespace Emby.Server.Implementations.Data } return false; - }, ReadTransactionMode); } @@ -248,12 +247,12 @@ namespace Emby.Server.Implementations.Data public enum SynchronousMode { /// <summary> - /// SQLite continues without syncing as soon as it has handed data off to the operating system + /// SQLite continues without syncing as soon as it has handed data off to the operating system. /// </summary> Off = 0, /// <summary> - /// SQLite database engine will still sync at the most critical moments + /// SQLite database engine will still sync at the most critical moments. /// </summary> Normal = 1, diff --git a/Emby.Server.Implementations/Data/CleanDatabaseScheduledTask.cs b/Emby.Server.Implementations/Data/CleanDatabaseScheduledTask.cs index 6c9bcff0f..3de9d6371 100644 --- a/Emby.Server.Implementations/Data/CleanDatabaseScheduledTask.cs +++ b/Emby.Server.Implementations/Data/CleanDatabaseScheduledTask.cs @@ -51,7 +51,6 @@ namespace Emby.Server.Implementations.Data _libraryManager.DeleteItem(item, new DeleteOptions { DeleteFileLocation = false - }); } diff --git a/Emby.Server.Implementations/Data/SqliteDisplayPreferencesRepository.cs b/Emby.Server.Implementations/Data/SqliteDisplayPreferencesRepository.cs index d474f1c6b..5597155a8 100644 --- a/Emby.Server.Implementations/Data/SqliteDisplayPreferencesRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteDisplayPreferencesRepository.cs @@ -1,4 +1,4 @@ -#pragma warning disable CS1591 +#pragma warning disable CS1591 using System; using System.Collections.Generic; @@ -59,7 +59,7 @@ namespace Emby.Server.Implementations.Data } /// <summary> - /// Opens the connection to the database + /// Opens the connection to the database. /// </summary> /// <returns>Task.</returns> private void InitializeInternal() @@ -77,7 +77,7 @@ namespace Emby.Server.Implementations.Data } /// <summary> - /// Save the display preferences associated with an item in the repo + /// 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> @@ -122,7 +122,7 @@ namespace Emby.Server.Implementations.Data } /// <summary> - /// Save all display preferences associated with a user in the repo + /// 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> diff --git a/Emby.Server.Implementations/Data/SqliteItemRepository.cs b/Emby.Server.Implementations/Data/SqliteItemRepository.cs index 43a593f11..da37a8c86 100644 --- a/Emby.Server.Implementations/Data/SqliteItemRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteItemRepository.cs @@ -102,7 +102,7 @@ namespace Emby.Server.Implementations.Data protected override TempStoreMode TempStore => TempStoreMode.Memory; /// <summary> - /// Opens the connection to the database + /// Opens the connection to the database. /// </summary> public void Initialize(SqliteUserDataRepository userDataRepo, IUserManager userManager) { @@ -321,7 +321,6 @@ namespace Emby.Server.Implementations.Data AddColumn(db, "MediaStreams", "ColorPrimaries", "TEXT", existingColumnNames); AddColumn(db, "MediaStreams", "ColorSpace", "TEXT", existingColumnNames); AddColumn(db, "MediaStreams", "ColorTransfer", "TEXT", existingColumnNames); - }, TransactionMode); connection.RunQueries(postQueries); @@ -549,7 +548,7 @@ namespace Emby.Server.Implementations.Data } /// <summary> - /// Save a standard item in the repo + /// Save a standard item in the repo. /// </summary> /// <param name="item">The item.</param> /// <param name="cancellationToken">The cancellation token.</param> @@ -794,6 +793,7 @@ namespace Emby.Server.Implementations.Data { saveItemStatement.TryBindNull("@Width"); } + if (item.Height > 0) { saveItemStatement.TryBind("@Height", item.Height); @@ -933,6 +933,7 @@ namespace Emby.Server.Implementations.Data { saveItemStatement.TryBindNull("@SeriesName"); } + if (string.IsNullOrWhiteSpace(userDataKey)) { saveItemStatement.TryBindNull("@UserDataKey"); @@ -1008,6 +1009,7 @@ namespace Emby.Server.Implementations.Data { artists = string.Join("|", hasArtists.Artists); } + saveItemStatement.TryBind("@Artists", artists); string albumArtists = null; @@ -1107,6 +1109,7 @@ namespace Emby.Server.Implementations.Data { continue; } + str.Append(ToValueString(i) + "|"); } @@ -1205,7 +1208,7 @@ namespace Emby.Server.Implementations.Data } /// <summary> - /// Internal retrieve from items or users table + /// Internal retrieve from items or users table. /// </summary> /// <param name="id">The id.</param> /// <returns>BaseItem.</returns> @@ -1367,6 +1370,7 @@ namespace Emby.Server.Implementations.Data hasStartDate.StartDate = reader[index].ReadDateTime(); } } + index++; } @@ -1374,12 +1378,14 @@ namespace Emby.Server.Implementations.Data { item.EndDate = reader[index].TryReadDateTime(); } + index++; if (!reader.IsDBNull(index)) { item.ChannelId = new Guid(reader.GetString(index)); } + index++; if (enableProgramAttributes) @@ -1390,24 +1396,28 @@ namespace Emby.Server.Implementations.Data { hasProgramAttributes.IsMovie = reader.GetBoolean(index); } + index++; if (!reader.IsDBNull(index)) { hasProgramAttributes.IsSeries = reader.GetBoolean(index); } + index++; if (!reader.IsDBNull(index)) { hasProgramAttributes.EpisodeTitle = reader.GetString(index); } + index++; if (!reader.IsDBNull(index)) { hasProgramAttributes.IsRepeat = reader.GetBoolean(index); } + index++; } else @@ -1420,6 +1430,7 @@ namespace Emby.Server.Implementations.Data { item.CommunityRating = reader.GetFloat(index); } + index++; if (HasField(query, ItemFields.CustomRating)) @@ -1428,6 +1439,7 @@ namespace Emby.Server.Implementations.Data { item.CustomRating = reader.GetString(index); } + index++; } @@ -1435,6 +1447,7 @@ namespace Emby.Server.Implementations.Data { item.IndexNumber = reader.GetInt32(index); } + index++; if (HasField(query, ItemFields.Settings)) @@ -1443,18 +1456,21 @@ namespace Emby.Server.Implementations.Data { item.IsLocked = reader.GetBoolean(index); } + index++; if (!reader.IsDBNull(index)) { item.PreferredMetadataLanguage = reader.GetString(index); } + index++; if (!reader.IsDBNull(index)) { item.PreferredMetadataCountryCode = reader.GetString(index); } + index++; } @@ -1464,6 +1480,7 @@ namespace Emby.Server.Implementations.Data { item.Width = reader.GetInt32(index); } + index++; } @@ -1473,6 +1490,7 @@ namespace Emby.Server.Implementations.Data { item.Height = reader.GetInt32(index); } + index++; } @@ -1482,6 +1500,7 @@ namespace Emby.Server.Implementations.Data { item.DateLastRefreshed = reader[index].ReadDateTime(); } + index++; } @@ -1489,18 +1508,21 @@ namespace Emby.Server.Implementations.Data { item.Name = reader.GetString(index); } + index++; if (!reader.IsDBNull(index)) { item.Path = RestorePath(reader.GetString(index)); } + index++; if (!reader.IsDBNull(index)) { item.PremiereDate = reader[index].TryReadDateTime(); } + index++; if (HasField(query, ItemFields.Overview)) @@ -1509,6 +1531,7 @@ namespace Emby.Server.Implementations.Data { item.Overview = reader.GetString(index); } + index++; } @@ -1516,18 +1539,21 @@ namespace Emby.Server.Implementations.Data { item.ParentIndexNumber = reader.GetInt32(index); } + index++; if (!reader.IsDBNull(index)) { item.ProductionYear = reader.GetInt32(index); } + index++; if (!reader.IsDBNull(index)) { item.OfficialRating = reader.GetString(index); } + index++; if (HasField(query, ItemFields.SortName)) @@ -1536,6 +1562,7 @@ namespace Emby.Server.Implementations.Data { item.ForcedSortName = reader.GetString(index); } + index++; } @@ -1543,12 +1570,14 @@ namespace Emby.Server.Implementations.Data { item.RunTimeTicks = reader.GetInt64(index); } + index++; if (!reader.IsDBNull(index)) { item.Size = reader.GetInt64(index); } + index++; if (HasField(query, ItemFields.DateCreated)) @@ -1557,6 +1586,7 @@ namespace Emby.Server.Implementations.Data { item.DateCreated = reader[index].ReadDateTime(); } + index++; } @@ -1564,6 +1594,7 @@ namespace Emby.Server.Implementations.Data { item.DateModified = reader[index].ReadDateTime(); } + index++; item.Id = reader.GetGuid(index); @@ -1575,6 +1606,7 @@ namespace Emby.Server.Implementations.Data { item.Genres = reader.GetString(index).Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries); } + index++; } @@ -1582,6 +1614,7 @@ namespace Emby.Server.Implementations.Data { item.ParentId = reader.GetGuid(index); } + index++; if (!reader.IsDBNull(index)) @@ -1591,6 +1624,7 @@ namespace Emby.Server.Implementations.Data item.Audio = audio; } } + index++; // TODO: Even if not needed by apps, the server needs it internally @@ -1604,6 +1638,7 @@ namespace Emby.Server.Implementations.Data liveTvChannel.ServiceName = reader.GetString(index); } } + index++; } @@ -1611,6 +1646,7 @@ namespace Emby.Server.Implementations.Data { item.IsInMixedFolder = reader.GetBoolean(index); } + index++; if (HasField(query, ItemFields.DateLastSaved)) @@ -1619,6 +1655,7 @@ namespace Emby.Server.Implementations.Data { item.DateLastSaved = reader[index].ReadDateTime(); } + index++; } @@ -1636,8 +1673,10 @@ namespace Emby.Server.Implementations.Data } } } + item.LockedFields = GetLockedFields(reader.GetString(index)).ToArray(); } + index++; } @@ -1647,6 +1686,7 @@ namespace Emby.Server.Implementations.Data { item.Studios = reader.GetString(index).Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries); } + index++; } @@ -1656,6 +1696,7 @@ namespace Emby.Server.Implementations.Data { item.Tags = reader.GetString(index).Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries); } + index++; } @@ -1675,9 +1716,11 @@ namespace Emby.Server.Implementations.Data } } } + trailer.TrailerTypes = GetTrailerTypes(reader.GetString(index)).ToArray(); } } + index++; } @@ -1687,6 +1730,7 @@ namespace Emby.Server.Implementations.Data { item.OriginalTitle = reader.GetString(index); } + index++; } @@ -1697,6 +1741,7 @@ namespace Emby.Server.Implementations.Data video.PrimaryVersionId = reader.GetString(index); } } + index++; if (HasField(query, ItemFields.DateLastMediaAdded)) @@ -1705,6 +1750,7 @@ namespace Emby.Server.Implementations.Data { folder.DateLastMediaAdded = reader[index].TryReadDateTime(); } + index++; } @@ -1712,18 +1758,21 @@ namespace Emby.Server.Implementations.Data { item.Album = reader.GetString(index); } + index++; if (!reader.IsDBNull(index)) { item.CriticRating = reader.GetFloat(index); } + index++; if (!reader.IsDBNull(index)) { item.IsVirtualItem = reader.GetBoolean(index); } + index++; if (item is IHasSeries hasSeriesName) @@ -1733,6 +1782,7 @@ namespace Emby.Server.Implementations.Data hasSeriesName.SeriesName = reader.GetString(index); } } + index++; if (hasEpisodeAttributes) @@ -1743,6 +1793,7 @@ namespace Emby.Server.Implementations.Data { episode.SeasonName = reader.GetString(index); } + index++; if (!reader.IsDBNull(index)) { @@ -1753,6 +1804,7 @@ namespace Emby.Server.Implementations.Data { index++; } + index++; } @@ -1766,6 +1818,7 @@ namespace Emby.Server.Implementations.Data hasSeries.SeriesId = reader.GetGuid(index); } } + index++; } @@ -1775,6 +1828,7 @@ namespace Emby.Server.Implementations.Data { item.PresentationUniqueKey = reader.GetString(index); } + index++; } @@ -1784,6 +1838,7 @@ namespace Emby.Server.Implementations.Data { item.InheritedParentalRatingValue = reader.GetInt32(index); } + index++; } @@ -1793,6 +1848,7 @@ namespace Emby.Server.Implementations.Data { item.ExternalSeriesId = reader.GetString(index); } + index++; } @@ -1802,6 +1858,7 @@ namespace Emby.Server.Implementations.Data { item.Tagline = reader.GetString(index); } + index++; } @@ -1809,6 +1866,7 @@ namespace Emby.Server.Implementations.Data { DeserializeProviderIds(reader.GetString(index), item); } + index++; if (query.DtoOptions.EnableImages) @@ -1817,6 +1875,7 @@ namespace Emby.Server.Implementations.Data { DeserializeImages(reader.GetString(index), item); } + index++; } @@ -1826,6 +1885,7 @@ namespace Emby.Server.Implementations.Data { item.ProductionLocations = reader.GetString(index).Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries).ToArray(); } + index++; } @@ -1835,6 +1895,7 @@ namespace Emby.Server.Implementations.Data { item.ExtraIds = SplitToGuids(reader.GetString(index)); } + index++; } @@ -1842,6 +1903,7 @@ namespace Emby.Server.Implementations.Data { item.TotalBitrate = reader.GetInt32(index); } + index++; if (!reader.IsDBNull(index)) @@ -1851,6 +1913,7 @@ namespace Emby.Server.Implementations.Data item.ExtraType = extraType; } } + index++; if (hasArtistFields) @@ -1859,12 +1922,14 @@ namespace Emby.Server.Implementations.Data { hasArtists.Artists = reader.GetString(index).Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries); } + index++; if (item is IHasAlbumArtist hasAlbumArtists && !reader.IsDBNull(index)) { hasAlbumArtists.AlbumArtists = reader.GetString(index).Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries); } + index++; } @@ -1872,6 +1937,7 @@ namespace Emby.Server.Implementations.Data { item.ExternalId = reader.GetString(index); } + index++; if (HasField(query, ItemFields.SeriesPresentationUniqueKey)) @@ -1883,6 +1949,7 @@ namespace Emby.Server.Implementations.Data hasSeries.SeriesPresentationUniqueKey = reader.GetString(index); } } + index++; } @@ -1892,6 +1959,7 @@ namespace Emby.Server.Implementations.Data { program.ShowId = reader.GetString(index); } + index++; } @@ -1899,6 +1967,7 @@ namespace Emby.Server.Implementations.Data { item.OwnerId = reader.GetGuid(index); } + index++; return item; @@ -1919,7 +1988,7 @@ namespace Emby.Server.Implementations.Data } /// <summary> - /// Gets chapters for an item + /// Gets chapters for an item. /// </summary> /// <param name="item">The item.</param> /// <returns>IEnumerable{ChapterInfo}.</returns> @@ -1947,7 +2016,7 @@ namespace Emby.Server.Implementations.Data } /// <summary> - /// Gets a single chapter for an item + /// Gets a single chapter for an item. /// </summary> /// <param name="item">The item.</param> /// <param name="index">The index.</param> @@ -2044,7 +2113,6 @@ namespace Emby.Server.Implementations.Data db.Execute("delete from " + ChaptersTableName + " where ItemId=@ItemId", idBlob); InsertChapters(idBlob, chapters, db); - }, TransactionMode); } } @@ -2475,6 +2543,7 @@ namespace Emby.Server.Implementations.Data { statement.TryBind("@SearchTermStartsWith", searchTerm + "%"); } + if (commandText.IndexOf("@SearchTermContains", StringComparison.OrdinalIgnoreCase) != -1) { statement.TryBind("@SearchTermContains", "%" + searchTerm + "%"); @@ -2745,6 +2814,7 @@ namespace Emby.Server.Implementations.Data { items[i] = newItem; } + return; } } @@ -2837,6 +2907,7 @@ namespace Emby.Server.Implementations.Data { statementTexts.Add(commandText); } + if (query.EnableTotalRecordCount) { commandText = string.Empty; @@ -3241,6 +3312,7 @@ namespace Emby.Server.Implementations.Data { statementTexts.Add(commandText); } + if (query.EnableTotalRecordCount) { commandText = string.Empty; @@ -3594,11 +3666,13 @@ namespace Emby.Server.Implementations.Data whereClauses.Add("IndexNumber=@IndexNumber"); statement?.TryBind("@IndexNumber", query.IndexNumber.Value); } + if (query.ParentIndexNumber.HasValue) { whereClauses.Add("ParentIndexNumber=@ParentIndexNumber"); statement?.TryBind("@ParentIndexNumber", query.ParentIndexNumber.Value); } + if (query.ParentIndexNumberNotEquals.HasValue) { whereClauses.Add("(ParentIndexNumber<>@ParentIndexNumberNotEquals or ParentIndexNumber is null)"); @@ -3884,6 +3958,7 @@ namespace Emby.Server.Implementations.Data { statement.TryBind(paramName, artistId.ToByteArray()); } + index++; } @@ -3904,6 +3979,7 @@ namespace Emby.Server.Implementations.Data { statement.TryBind(paramName, artistId.ToByteArray()); } + index++; } @@ -3924,8 +4000,10 @@ namespace Emby.Server.Implementations.Data { statement.TryBind(paramName, artistId.ToByteArray()); } + index++; } + var clause = "(" + string.Join(" OR ", clauses) + ")"; whereClauses.Add(clause); } @@ -3943,8 +4021,10 @@ namespace Emby.Server.Implementations.Data { statement.TryBind(paramName, albumId.ToByteArray()); } + index++; } + var clause = "(" + string.Join(" OR ", clauses) + ")"; whereClauses.Add(clause); } @@ -3962,8 +4042,10 @@ namespace Emby.Server.Implementations.Data { statement.TryBind(paramName, artistId.ToByteArray()); } + index++; } + var clause = "(" + string.Join(" OR ", clauses) + ")"; whereClauses.Add(clause); } @@ -3981,8 +4063,10 @@ namespace Emby.Server.Implementations.Data { statement.TryBind(paramName, genreId.ToByteArray()); } + index++; } + var clause = "(" + string.Join(" OR ", clauses) + ")"; whereClauses.Add(clause); } @@ -3998,8 +4082,10 @@ namespace Emby.Server.Implementations.Data { statement.TryBind("@Genre" + index, GetCleanValue(item)); } + index++; } + var clause = "(" + string.Join(" OR ", clauses) + ")"; whereClauses.Add(clause); } @@ -4015,8 +4101,10 @@ namespace Emby.Server.Implementations.Data { statement.TryBind("@Tag" + index, GetCleanValue(item)); } + index++; } + var clause = "(" + string.Join(" OR ", clauses) + ")"; whereClauses.Add(clause); } @@ -4032,8 +4120,10 @@ namespace Emby.Server.Implementations.Data { statement.TryBind("@ExcludeTag" + index, GetCleanValue(item)); } + index++; } + var clause = "(" + string.Join(" OR ", clauses) + ")"; whereClauses.Add(clause); } @@ -4052,8 +4142,10 @@ namespace Emby.Server.Implementations.Data { statement.TryBind(paramName, studioId.ToByteArray()); } + index++; } + var clause = "(" + string.Join(" OR ", clauses) + ")"; whereClauses.Add(clause); } @@ -4069,8 +4161,10 @@ namespace Emby.Server.Implementations.Data { statement.TryBind("@OfficialRating" + index, item); } + index++; } + var clause = "(" + string.Join(" OR ", clauses) + ")"; whereClauses.Add(clause); } @@ -4245,6 +4339,7 @@ namespace Emby.Server.Implementations.Data statement.TryBind("@IsVirtualItem", isVirtualItem.Value); } } + if (query.IsSpecialSeason.HasValue) { if (query.IsSpecialSeason.Value) @@ -4256,6 +4351,7 @@ namespace Emby.Server.Implementations.Data whereClauses.Add("IndexNumber <> 0"); } } + if (query.IsUnaired.HasValue) { if (query.IsUnaired.Value) @@ -4267,6 +4363,7 @@ namespace Emby.Server.Implementations.Data whereClauses.Add("PremiereDate < DATETIME('now')"); } } + var queryMediaTypes = query.MediaTypes.Where(IsValidMediaType).ToArray(); if (queryMediaTypes.Length == 1) { @@ -4282,6 +4379,7 @@ namespace Emby.Server.Implementations.Data whereClauses.Add("MediaType in (" + val + ")"); } + if (query.ItemIds.Length > 0) { var includeIds = new List<string>(); @@ -4294,11 +4392,13 @@ namespace Emby.Server.Implementations.Data { statement.TryBind("@IncludeId" + index, id); } + index++; } whereClauses.Add("(" + string.Join(" OR ", includeIds) + ")"); } + if (query.ExcludeItemIds.Length > 0) { var excludeIds = new List<string>(); @@ -4311,6 +4411,7 @@ namespace Emby.Server.Implementations.Data { statement.TryBind("@ExcludeId" + index, id); } + index++; } @@ -4335,6 +4436,7 @@ namespace Emby.Server.Implementations.Data { statement.TryBind(paramName, "%" + pair.Key + "=" + pair.Value + "%"); } + index++; break; @@ -4360,7 +4462,7 @@ namespace Emby.Server.Implementations.Data // TODO this seems to be an idea for a better schema where ProviderIds are their own table // buut this is not implemented - //hasProviderIds.Add("(COALESCE((select value from ProviderIds where ItemId=Guid and Name = '" + pair.Key + "'), '') <> " + paramName + ")"); + // hasProviderIds.Add("(COALESCE((select value from ProviderIds where ItemId=Guid and Name = '" + pair.Key + "'), '') <> " + paramName + ")"); // TODO this is a really BAD way to do it since the pair: // Tmdb, 1234 matches Tmdb=1234 but also Tmdb=1234567 @@ -4377,6 +4479,7 @@ namespace Emby.Server.Implementations.Data { statement.TryBind(paramName, "%" + pair.Key + "=" + pair.Value + "%"); } + index++; break; @@ -4427,6 +4530,7 @@ namespace Emby.Server.Implementations.Data { whereClauses.Add("(TopParentId=@TopParentId)"); } + if (statement != null) { statement.TryBind("@TopParentId", queryTopParentIds[0].ToString("N", CultureInfo.InvariantCulture)); @@ -4464,11 +4568,13 @@ namespace Emby.Server.Implementations.Data statement.TryBind("@AncestorId", query.AncestorIds[0]); } } + if (query.AncestorIds.Length > 1) { var inClause = string.Join(",", query.AncestorIds.Select(i => "'" + i.ToString("N", CultureInfo.InvariantCulture) + "'")); whereClauses.Add(string.Format("Guid in (select itemId from AncestorIds where AncestorIdText in ({0}))", inClause)); } + if (!string.IsNullOrWhiteSpace(query.AncestorWithPresentationUniqueKey)) { var inClause = "select guid from TypedBaseItems where PresentationUniqueKey=@AncestorWithPresentationUniqueKey"; @@ -4497,6 +4603,7 @@ namespace Emby.Server.Implementations.Data statement.TryBind("@UnratedType", query.BlockUnratedItems[0].ToString()); } } + if (query.BlockUnratedItems.Length > 1) { var inClause = string.Join(",", query.BlockUnratedItems.Select(i => "'" + i.ToString() + "'")); @@ -4789,7 +4896,6 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type connection.RunInTransaction(db => { connection.ExecuteAll(sql); - }, TransactionMode); } } @@ -4972,6 +5078,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type statement.TryBind("@ItemId", query.ItemId.ToByteArray()); } } + if (!query.AppearsInItemId.Equals(Guid.Empty)) { whereClauses.Add("Name in (Select Name from People where ItemId=@AppearsInItemId)"); @@ -4980,6 +5087,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type statement.TryBind("@AppearsInItemId", query.AppearsInItemId.ToByteArray()); } } + var queryPersonTypes = query.PersonTypes.Where(IsValidPersonType).ToList(); if (queryPersonTypes.Count == 1) @@ -4996,6 +5104,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type whereClauses.Add("PersonType in (" + val + ")"); } + var queryExcludePersonTypes = query.ExcludePersonTypes.Where(IsValidPersonType).ToList(); if (queryExcludePersonTypes.Count == 1) @@ -5012,6 +5121,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type whereClauses.Add("PersonType not in (" + val + ")"); } + if (query.MaxListOrder.HasValue) { whereClauses.Add("ListOrder<=@MaxListOrder"); @@ -5020,6 +5130,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type statement.TryBind("@MaxListOrder", query.MaxListOrder.Value); } } + if (!string.IsNullOrWhiteSpace(query.NameContains)) { whereClauses.Add("Name like @NameContains"); @@ -5159,6 +5270,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type var typeString = string.Join(",", withItemTypes.Select(i => "'" + i + "'")); commandText += " AND ItemId In (select guid from typedbaseitems where type in (" + typeString + "))"; } + if (excludeItemTypes.Count > 0) { var typeString = string.Join(",", excludeItemTypes.Select(i => "'" + i + "'")); @@ -5180,7 +5292,6 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type } } } - } LogQueryTime("GetItemValueNames", commandText, now); @@ -5631,7 +5742,6 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type db.Execute("delete from People where ItemId=@ItemId", itemIdBlob); InsertPeople(itemIdBlob, people, db); - }, TransactionMode); } } @@ -5788,7 +5898,6 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type db.Execute("delete from mediastreams where ItemId=@ItemId", itemIdBlob); InsertMediaStreams(itemIdBlob, streams, db); - }, TransactionMode); } } @@ -6134,7 +6243,6 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type db.Execute("delete from mediaattachments where ItemId=@ItemId", itemIdBlob); InsertMediaAttachments(itemIdBlob, attachments, db, cancellationToken); - }, TransactionMode); } } diff --git a/Emby.Server.Implementations/Data/SqliteUserDataRepository.cs b/Emby.Server.Implementations/Data/SqliteUserDataRepository.cs index 6ee6230fc..4a78aac8e 100644 --- a/Emby.Server.Implementations/Data/SqliteUserDataRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteUserDataRepository.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.IO; using System.Threading; +using Jellyfin.Data.Entities; using MediaBrowser.Common.Configuration; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; @@ -134,10 +135,12 @@ namespace Emby.Server.Implementations.Data { throw new ArgumentNullException(nameof(userData)); } + if (internalUserId <= 0) { throw new ArgumentNullException(nameof(internalUserId)); } + if (string.IsNullOrEmpty(key)) { throw new ArgumentNullException(nameof(key)); @@ -152,6 +155,7 @@ namespace Emby.Server.Implementations.Data { throw new ArgumentNullException(nameof(userData)); } + if (internalUserId <= 0) { throw new ArgumentNullException(nameof(internalUserId)); @@ -234,7 +238,7 @@ namespace Emby.Server.Implementations.Data } /// <summary> - /// Persist all user data for the specified user + /// Persist all user data for the specified user. /// </summary> private void PersistAllUserData(long internalUserId, UserItemData[] userDataList, CancellationToken cancellationToken) { @@ -308,7 +312,7 @@ namespace Emby.Server.Implementations.Data } /// <summary> - /// Return all user-data associated with the given user + /// Return all user-data associated with the given user. /// </summary> /// <param name="internalUserId"></param> /// <returns></returns> @@ -338,7 +342,7 @@ namespace Emby.Server.Implementations.Data } /// <summary> - /// Read a row from the specified reader into the provided userData object + /// Read a row from the specified reader into the provided userData object. /// </summary> /// <param name="reader"></param> private UserItemData ReadRow(IReadOnlyList<IResultSetValue> reader) @@ -346,7 +350,7 @@ namespace Emby.Server.Implementations.Data var userData = new UserItemData(); userData.Key = reader[0].ToString(); - //userData.UserId = reader[1].ReadGuidFromBlob(); + // userData.UserId = reader[1].ReadGuidFromBlob(); if (reader[2].SQLiteType != SQLiteType.Null) { diff --git a/Emby.Server.Implementations/Data/SqliteUserRepository.cs b/Emby.Server.Implementations/Data/SqliteUserRepository.cs deleted file mode 100644 index 0c3f26974..000000000 --- a/Emby.Server.Implementations/Data/SqliteUserRepository.cs +++ /dev/null @@ -1,240 +0,0 @@ -#pragma warning disable CS1591 - -using System; -using System.Collections.Generic; -using System.IO; -using System.Text.Json; -using MediaBrowser.Common.Json; -using MediaBrowser.Controller; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Persistence; -using Microsoft.Extensions.Logging; -using SQLitePCL.pretty; - -namespace Emby.Server.Implementations.Data -{ - /// <summary> - /// Class SQLiteUserRepository - /// </summary> - public class SqliteUserRepository : BaseSqliteRepository, IUserRepository - { - private readonly JsonSerializerOptions _jsonOptions; - - public SqliteUserRepository( - ILogger<SqliteUserRepository> logger, - IServerApplicationPaths appPaths) - : base(logger) - { - _jsonOptions = JsonDefaults.GetOptions(); - - DbFilePath = Path.Combine(appPaths.DataPath, "users.db"); - } - - /// <summary> - /// Gets the name of the repository - /// </summary> - /// <value>The name.</value> - public string Name => "SQLite"; - - /// <summary> - /// Opens the connection to the database. - /// </summary> - public void Initialize() - { - using (var connection = GetConnection()) - { - var localUsersTableExists = TableExists(connection, "LocalUsersv2"); - - connection.RunQueries(new[] { - "create table if not exists LocalUsersv2 (Id INTEGER PRIMARY KEY, guid GUID NOT NULL, data BLOB NOT NULL)", - "drop index if exists idx_users" - }); - - if (!localUsersTableExists && TableExists(connection, "Users")) - { - TryMigrateToLocalUsersTable(connection); - } - - RemoveEmptyPasswordHashes(connection); - } - } - - private void TryMigrateToLocalUsersTable(ManagedConnection connection) - { - try - { - connection.RunQueries(new[] - { - "INSERT INTO LocalUsersv2 (guid, data) SELECT guid,data from users" - }); - } - catch (Exception ex) - { - Logger.LogError(ex, "Error migrating users database"); - } - } - - private void RemoveEmptyPasswordHashes(ManagedConnection connection) - { - foreach (var user in RetrieveAllUsers(connection)) - { - // If the user password is the sha1 hash of the empty string, remove it - if (!string.Equals(user.Password, "DA39A3EE5E6B4B0D3255BFEF95601890AFD80709", StringComparison.Ordinal) - && !string.Equals(user.Password, "$SHA1$DA39A3EE5E6B4B0D3255BFEF95601890AFD80709", StringComparison.Ordinal)) - { - continue; - } - - user.Password = null; - var serialized = JsonSerializer.SerializeToUtf8Bytes(user, _jsonOptions); - - connection.RunInTransaction(db => - { - using (var statement = db.PrepareStatement("update LocalUsersv2 set data=@data where Id=@InternalId")) - { - statement.TryBind("@InternalId", user.InternalId); - statement.TryBind("@data", serialized); - statement.MoveNext(); - } - }, TransactionMode); - } - } - - /// <summary> - /// Save a user in the repo - /// </summary> - public void CreateUser(User user) - { - if (user == null) - { - throw new ArgumentNullException(nameof(user)); - } - - var serialized = JsonSerializer.SerializeToUtf8Bytes(user, _jsonOptions); - - using (var connection = GetConnection()) - { - connection.RunInTransaction(db => - { - using (var statement = db.PrepareStatement("insert into LocalUsersv2 (guid, data) values (@guid, @data)")) - { - statement.TryBind("@guid", user.Id.ToByteArray()); - statement.TryBind("@data", serialized); - - statement.MoveNext(); - } - - var createdUser = GetUser(user.Id, connection); - - if (createdUser == null) - { - throw new ApplicationException("created user should never be null"); - } - - user.InternalId = createdUser.InternalId; - - }, TransactionMode); - } - } - - public void UpdateUser(User user) - { - if (user == null) - { - throw new ArgumentNullException(nameof(user)); - } - - var serialized = JsonSerializer.SerializeToUtf8Bytes(user, _jsonOptions); - - using (var connection = GetConnection()) - { - connection.RunInTransaction(db => - { - using (var statement = db.PrepareStatement("update LocalUsersv2 set data=@data where Id=@InternalId")) - { - statement.TryBind("@InternalId", user.InternalId); - statement.TryBind("@data", serialized); - statement.MoveNext(); - } - - }, TransactionMode); - } - } - - private User GetUser(Guid guid, ManagedConnection connection) - { - using (var statement = connection.PrepareStatement("select id,guid,data from LocalUsersv2 where guid=@guid")) - { - statement.TryBind("@guid", guid); - - foreach (var row in statement.ExecuteQuery()) - { - return GetUser(row); - } - } - - return null; - } - - private User GetUser(IReadOnlyList<IResultSetValue> row) - { - var id = row[0].ToInt64(); - var guid = row[1].ReadGuidFromBlob(); - - var user = JsonSerializer.Deserialize<User>(row[2].ToBlob(), _jsonOptions); - user.InternalId = id; - user.Id = guid; - return user; - } - - /// <summary> - /// Retrieve all users from the database - /// </summary> - /// <returns>IEnumerable{User}.</returns> - public List<User> RetrieveAllUsers() - { - using (var connection = GetConnection(true)) - { - return new List<User>(RetrieveAllUsers(connection)); - } - } - - /// <summary> - /// Retrieve all users from the database - /// </summary> - /// <returns>IEnumerable{User}.</returns> - private IEnumerable<User> RetrieveAllUsers(ManagedConnection connection) - { - foreach (var row in connection.Query("select id,guid,data from LocalUsersv2")) - { - yield return GetUser(row); - } - } - - /// <summary> - /// Deletes the user. - /// </summary> - /// <param name="user">The user.</param> - /// <returns>Task.</returns> - /// <exception cref="ArgumentNullException">user</exception> - public void DeleteUser(User user) - { - if (user == null) - { - throw new ArgumentNullException(nameof(user)); - } - - using (var connection = GetConnection()) - { - connection.RunInTransaction(db => - { - using (var statement = db.PrepareStatement("delete from LocalUsersv2 where Id=@id")) - { - statement.TryBind("@id", user.InternalId); - statement.MoveNext(); - } - }, TransactionMode); - } - } - } -} diff --git a/Emby.Server.Implementations/Devices/DeviceManager.cs b/Emby.Server.Implementations/Devices/DeviceManager.cs index e39e0aa78..e75745cc6 100644 --- a/Emby.Server.Implementations/Devices/DeviceManager.cs +++ b/Emby.Server.Implementations/Devices/DeviceManager.cs @@ -5,10 +5,11 @@ using System.Collections.Generic; using System.Globalization; using System.IO; using System.Linq; +using Jellyfin.Data.Enums; +using Jellyfin.Data.Entities; using MediaBrowser.Common.Extensions; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Devices; -using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Security; using MediaBrowser.Model.Devices; @@ -16,7 +17,6 @@ using MediaBrowser.Model.Events; using MediaBrowser.Model.Querying; using MediaBrowser.Model.Serialization; using MediaBrowser.Model.Session; -using MediaBrowser.Model.Users; namespace Emby.Server.Implementations.Devices { @@ -27,11 +27,10 @@ namespace Emby.Server.Implementations.Devices 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; - private readonly object _capabilitiesSyncLock = new object(); - public DeviceManager( IAuthenticationRepository authRepo, IJsonSerializer json, @@ -113,7 +112,7 @@ namespace Emby.Server.Implementations.Devices { IEnumerable<AuthenticationInfo> sessions = _authRepo.Get(new AuthenticationInfoQuery { - //UserId = query.UserId + // UserId = query.UserId HasUser = true }).Items; @@ -170,12 +169,18 @@ namespace Emby.Server.Implementations.Devices { throw new ArgumentException("user not found"); } + if (string.IsNullOrEmpty(deviceId)) { throw new ArgumentNullException(nameof(deviceId)); } - if (!CanAccessDevice(user.Policy, deviceId)) + if (user.HasPermission(PermissionKind.EnableAllDevices) || user.HasPermission(PermissionKind.IsAdministrator)) + { + return true; + } + + if (!user.GetPreference(PreferenceKind.EnabledDevices).Contains(deviceId, StringComparer.OrdinalIgnoreCase)) { var capabilities = GetCapabilities(deviceId); @@ -187,20 +192,5 @@ namespace Emby.Server.Implementations.Devices return true; } - - private static bool CanAccessDevice(UserPolicy policy, string id) - { - if (policy.EnableAllDevices) - { - return true; - } - - if (policy.IsAdministrator) - { - return true; - } - - return policy.EnabledDevices.Contains(id, StringComparer.OrdinalIgnoreCase); - } } } diff --git a/Emby.Server.Implementations/Dto/DtoService.cs b/Emby.Server.Implementations/Dto/DtoService.cs index f9841082c..3d5460b8c 100644 --- a/Emby.Server.Implementations/Dto/DtoService.cs +++ b/Emby.Server.Implementations/Dto/DtoService.cs @@ -6,14 +6,14 @@ using System.Globalization; using System.IO; using System.Linq; using System.Threading.Tasks; +using Jellyfin.Data.Entities; +using Jellyfin.Data.Enums; using MediaBrowser.Common; using MediaBrowser.Controller.Channels; using MediaBrowser.Controller.Drawing; using MediaBrowser.Controller.Dto; 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.LiveTv; using MediaBrowser.Controller.Persistence; @@ -24,6 +24,14 @@ using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Querying; using Microsoft.Extensions.Logging; +using Book = MediaBrowser.Controller.Entities.Book; +using Episode = MediaBrowser.Controller.Entities.TV.Episode; +using Movie = MediaBrowser.Controller.Entities.Movies.Movie; +using MusicAlbum = MediaBrowser.Controller.Entities.Audio.MusicAlbum; +using Person = MediaBrowser.Controller.Entities.Person; +using Photo = MediaBrowser.Controller.Entities.Photo; +using Season = MediaBrowser.Controller.Entities.TV.Season; +using Series = MediaBrowser.Controller.Entities.TV.Series; namespace Emby.Server.Implementations.Dto { @@ -66,7 +74,7 @@ namespace Emby.Server.Implementations.Dto } /// <summary> - /// Converts a BaseItem to a DTOBaseItem + /// Converts a BaseItem to a DTOBaseItem. /// </summary> /// <param name="item">The item.</param> /// <param name="fields">The fields.</param> @@ -269,6 +277,7 @@ namespace Emby.Server.Implementations.Dto dto.EpisodeTitle = dto.Name; dto.Name = dto.SeriesName; } + liveTvManager.AddInfoToRecordingDto(item, dto, activeRecording, user); } @@ -284,6 +293,7 @@ namespace Emby.Server.Implementations.Dto { continue; } + var containers = container.Split(new[] { ',' }); if (containers.Length < 2) { @@ -384,7 +394,7 @@ namespace Emby.Server.Implementations.Dto if (options.ContainsField(ItemFields.ChildCount)) { - dto.ChildCount = dto.ChildCount ?? GetChildCount(folder, user); + dto.ChildCount ??= GetChildCount(folder, user); } } @@ -398,7 +408,6 @@ namespace Emby.Server.Implementations.Dto dto.DateLastMediaAdded = folder.DateLastMediaAdded; } } - else { if (options.EnableUserData) @@ -414,7 +423,7 @@ namespace Emby.Server.Implementations.Dto if (options.ContainsField(ItemFields.BasicSyncInfo)) { - var userCanSync = user != null && user.Policy.EnableContentDownloading; + var userCanSync = user != null && user.HasPermission(PermissionKind.EnableContentDownloading); if (userCanSync && item.SupportsExternalTransfer) { dto.SupportsSync = true; @@ -435,7 +444,7 @@ namespace Emby.Server.Implementations.Dto } /// <summary> - /// Gets client-side Id of a server-side BaseItem + /// Gets client-side Id of a server-side BaseItem. /// </summary> /// <param name="item">The item.</param> /// <returns>System.String.</returns> @@ -449,6 +458,7 @@ namespace Emby.Server.Implementations.Dto { dto.SeriesName = item.SeriesName; } + private static void SetPhotoProperties(BaseItemDto dto, Photo item) { dto.CameraMake = item.CameraMake; @@ -530,7 +540,7 @@ namespace Emby.Server.Implementations.Dto } /// <summary> - /// Attaches People DTO's to a DTOBaseItem + /// Attaches People DTO's to a DTOBaseItem. /// </summary> /// <param name="dto">The dto.</param> /// <param name="item">The item.</param> @@ -547,22 +557,27 @@ namespace Emby.Server.Implementations.Dto { return 0; } + if (i.IsType(PersonType.GuestStar)) { return 1; } + if (i.IsType(PersonType.Director)) { return 2; } + if (i.IsType(PersonType.Writer)) { return 3; } + if (i.IsType(PersonType.Producer)) { return 4; } + if (i.IsType(PersonType.Composer)) { return 4; @@ -586,7 +601,6 @@ namespace Emby.Server.Implementations.Dto _logger.LogError(ex, "Error getting person {Name}", c); return null; } - }).Where(i => i != null) .GroupBy(i => i.Name, StringComparer.OrdinalIgnoreCase) .Select(x => x.First()) @@ -719,7 +733,7 @@ namespace Emby.Server.Implementations.Dto } /// <summary> - /// Sets simple property values on a DTOBaseItem + /// Sets simple property values on a DTOBaseItem. /// </summary> /// <param name="dto">The dto.</param> /// <param name="item">The item.</param> @@ -939,7 +953,7 @@ namespace Emby.Server.Implementations.Dto dto.AlbumPrimaryImageTag = GetTagAndFillBlurhash(dto, albumParent, ImageType.Primary); } - //if (options.ContainsField(ItemFields.MediaSourceCount)) + // if (options.ContainsField(ItemFields.MediaSourceCount)) //{ // Songs always have one //} @@ -949,13 +963,13 @@ namespace Emby.Server.Implementations.Dto { dto.Artists = hasArtist.Artists; - //var artistItems = _libraryManager.GetArtists(new InternalItemsQuery + // var artistItems = _libraryManager.GetArtists(new InternalItemsQuery //{ // EnableTotalRecordCount = false, // ItemIds = new[] { item.Id.ToString("N", CultureInfo.InvariantCulture) } //}); - //dto.ArtistItems = artistItems.Items + // dto.ArtistItems = artistItems.Items // .Select(i => // { // var artist = i.Item1; @@ -968,7 +982,7 @@ namespace Emby.Server.Implementations.Dto // .ToList(); // Include artists that are not in the database yet, e.g., just added via metadata editor - //var foundArtists = artistItems.Items.Select(i => i.Item1.Name).ToList(); + // var foundArtists = artistItems.Items.Select(i => i.Item1.Name).ToList(); dto.ArtistItems = hasArtist.Artists //.Except(foundArtists, new DistinctNameComparer()) .Select(i => @@ -993,7 +1007,6 @@ namespace Emby.Server.Implementations.Dto } return null; - }).Where(i => i != null).ToArray(); } @@ -1002,13 +1015,13 @@ namespace Emby.Server.Implementations.Dto { dto.AlbumArtist = hasAlbumArtist.AlbumArtists.FirstOrDefault(); - //var artistItems = _libraryManager.GetAlbumArtists(new InternalItemsQuery + // var artistItems = _libraryManager.GetAlbumArtists(new InternalItemsQuery //{ // EnableTotalRecordCount = false, // ItemIds = new[] { item.Id.ToString("N", CultureInfo.InvariantCulture) } //}); - //dto.AlbumArtists = artistItems.Items + // dto.AlbumArtists = artistItems.Items // .Select(i => // { // var artist = i.Item1; @@ -1044,7 +1057,6 @@ namespace Emby.Server.Implementations.Dto } return null; - }).Where(i => i != null).ToArray(); } @@ -1158,7 +1170,7 @@ namespace Emby.Server.Implementations.Dto // this block will add the series poster for episodes without a poster // TODO maybe remove the if statement entirely - //if (options.ContainsField(ItemFields.SeriesPrimaryImage)) + // if (options.ContainsField(ItemFields.SeriesPrimaryImage)) { episodeSeries = episodeSeries ?? episode.Series; if (episodeSeries != null) @@ -1204,7 +1216,7 @@ namespace Emby.Server.Implementations.Dto // this block will add the series poster for seasons without a poster // TODO maybe remove the if statement entirely - //if (options.ContainsField(ItemFields.SeriesPrimaryImage)) + // if (options.ContainsField(ItemFields.SeriesPrimaryImage)) { series = series ?? season.Series; if (series != null) @@ -1342,6 +1354,7 @@ namespace Emby.Server.Implementations.Dto dto.ParentLogoImageTag = GetTagAndFillBlurhash(dto, parent, image); } } + if (artLimit > 0 && !(imageTags != null && imageTags.ContainsKey(ImageType.Art)) && dto.ParentArtItemId == null) { var image = allImages.FirstOrDefault(i => i.Type == ImageType.Art); @@ -1352,6 +1365,7 @@ namespace Emby.Server.Implementations.Dto dto.ParentArtImageTag = GetTagAndFillBlurhash(dto, parent, image); } } + if (thumbLimit > 0 && !(imageTags != null && imageTags.ContainsKey(ImageType.Thumb)) && (dto.ParentThumbItemId == null || parent is Series) && !(parent is ICollectionFolder) && !(parent is UserView)) { var image = allImages.FirstOrDefault(i => i.Type == ImageType.Thumb); @@ -1362,6 +1376,7 @@ namespace Emby.Server.Implementations.Dto dto.ParentThumbImageTag = GetTagAndFillBlurhash(dto, parent, image); } } + if (backdropLimit > 0 && !((dto.BackdropImageTags != null && dto.BackdropImageTags.Length > 0) || (dto.ParentBackdropImageTags != null && dto.ParentBackdropImageTags.Length > 0))) { var images = allImages.Where(i => i.Type == ImageType.Backdrop).Take(backdropLimit).ToList(); diff --git a/Emby.Server.Implementations/Emby.Server.Implementations.csproj b/Emby.Server.Implementations/Emby.Server.Implementations.csproj index e71e437ac..e75b66293 100644 --- a/Emby.Server.Implementations/Emby.Server.Implementations.csproj +++ b/Emby.Server.Implementations/Emby.Server.Implementations.csproj @@ -24,7 +24,7 @@ </ItemGroup> <ItemGroup> - <PackageReference Include="IPNetwork2" Version="2.4.0.126" /> + <PackageReference Include="IPNetwork2" Version="2.5.211" /> <PackageReference Include="Jellyfin.XmlTv" Version="10.4.3" /> <PackageReference Include="Microsoft.AspNetCore.Hosting" Version="2.2.7" /> <PackageReference Include="Microsoft.AspNetCore.Hosting.Abstractions" Version="2.2.0" /> @@ -41,7 +41,7 @@ <PackageReference Include="Mono.Nat" Version="2.0.1" /> <PackageReference Include="prometheus-net.DotNetRuntime" Version="3.3.1" /> <PackageReference Include="ServiceStack.Text.Core" Version="5.9.0" /> - <PackageReference Include="sharpcompress" Version="0.25.0" /> + <PackageReference Include="sharpcompress" Version="0.25.1" /> <PackageReference Include="SQLitePCL.pretty.netstandard" Version="2.1.0" /> <PackageReference Include="DotNet.Glob" Version="3.0.9" /> </ItemGroup> diff --git a/Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs b/Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs index b9992ae73..c1068522a 100644 --- a/Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs +++ b/Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs @@ -6,6 +6,7 @@ using System.Globalization; using System.Linq; using System.Threading; using System.Threading.Tasks; +using Jellyfin.Data.Entities; using MediaBrowser.Controller.Channels; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Audio; @@ -131,7 +132,6 @@ namespace Emby.Server.Implementations.EntryPoints } catch { - } } } diff --git a/Emby.Server.Implementations/EntryPoints/RecordingNotifier.cs b/Emby.Server.Implementations/EntryPoints/RecordingNotifier.cs index b64439802..632735910 100644 --- a/Emby.Server.Implementations/EntryPoints/RecordingNotifier.cs +++ b/Emby.Server.Implementations/EntryPoints/RecordingNotifier.cs @@ -4,6 +4,7 @@ using System; using System.Linq; using System.Threading; using System.Threading.Tasks; +using Jellyfin.Data.Enums; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.LiveTv; using MediaBrowser.Controller.Plugins; @@ -64,7 +65,7 @@ namespace Emby.Server.Implementations.EntryPoints private async Task SendMessage(string name, TimerEventInfo info) { - var users = _userManager.Users.Where(i => i.Policy.EnableLiveTvAccess).Select(i => i.Id).ToList(); + var users = _userManager.Users.Where(i => i.HasPermission(PermissionKind.EnableLiveTvAccess)).Select(i => i.Id).ToList(); try { diff --git a/Emby.Server.Implementations/EntryPoints/RefreshUsersMetadata.cs b/Emby.Server.Implementations/EntryPoints/RefreshUsersMetadata.cs deleted file mode 100644 index 54f4b67e6..000000000 --- a/Emby.Server.Implementations/EntryPoints/RefreshUsersMetadata.cs +++ /dev/null @@ -1,77 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.Providers; -using MediaBrowser.Model.IO; -using MediaBrowser.Model.Tasks; - -namespace Emby.Server.Implementations.EntryPoints -{ - /// <summary> - /// Class RefreshUsersMetadata. - /// </summary> - public class RefreshUsersMetadata : IScheduledTask, IConfigurableScheduledTask - { - /// <summary> - /// The user manager. - /// </summary> - private readonly IUserManager _userManager; - private readonly IFileSystem _fileSystem; - - /// <summary> - /// Initializes a new instance of the <see cref="RefreshUsersMetadata" /> class. - /// </summary> - public RefreshUsersMetadata(IUserManager userManager, IFileSystem fileSystem) - { - _userManager = userManager; - _fileSystem = fileSystem; - } - - /// <inheritdoc /> - public string Name => "Refresh Users"; - - /// <inheritdoc /> - public string Key => "RefreshUsers"; - - /// <inheritdoc /> - public string Description => "Refresh user infos"; - - /// <inheritdoc /> - public string Category => "Library"; - - /// <inheritdoc /> - public bool IsHidden => true; - - /// <inheritdoc /> - public bool IsEnabled => true; - - /// <inheritdoc /> - public bool IsLogged => true; - - /// <inheritdoc /> - public async Task Execute(CancellationToken cancellationToken, IProgress<double> progress) - { - foreach (var user in _userManager.Users) - { - cancellationToken.ThrowIfCancellationRequested(); - - await user.RefreshMetadata(new MetadataRefreshOptions(new DirectoryService(_fileSystem)), cancellationToken).ConfigureAwait(false); - } - } - - /// <inheritdoc /> - public IEnumerable<TaskTriggerInfo> GetDefaultTriggers() - { - return new[] - { - new TaskTriggerInfo - { - IntervalTicks = TimeSpan.FromDays(1).Ticks, - Type = TaskTriggerInfo.TriggerInterval - } - }; - } - } -} diff --git a/Emby.Server.Implementations/EntryPoints/ServerEventNotifier.cs b/Emby.Server.Implementations/EntryPoints/ServerEventNotifier.cs index 11ba6f748..826d4d8dc 100644 --- a/Emby.Server.Implementations/EntryPoints/ServerEventNotifier.cs +++ b/Emby.Server.Implementations/EntryPoints/ServerEventNotifier.cs @@ -3,10 +3,10 @@ using System.Collections.Generic; using System.Globalization; using System.Threading; using System.Threading.Tasks; +using Jellyfin.Data.Entities; using MediaBrowser.Common.Plugins; using MediaBrowser.Common.Updates; using MediaBrowser.Controller; -using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Plugins; using MediaBrowser.Controller.Session; @@ -68,10 +68,8 @@ namespace Emby.Server.Implementations.EntryPoints /// <inheritdoc /> public Task RunAsync() { - _userManager.UserDeleted += OnUserDeleted; - _userManager.UserUpdated += OnUserUpdated; - _userManager.UserPolicyUpdated += OnUserPolicyUpdated; - _userManager.UserConfigurationUpdated += OnUserConfigurationUpdated; + _userManager.OnUserDeleted += OnUserDeleted; + _userManager.OnUserUpdated += OnUserUpdated; _appHost.HasPendingRestartChanged += OnHasPendingRestartChanged; @@ -153,20 +151,6 @@ namespace Emby.Server.Implementations.EntryPoints await SendMessageToUserSession(e.Argument, "UserDeleted", e.Argument.Id.ToString("N", CultureInfo.InvariantCulture)).ConfigureAwait(false); } - private async void OnUserPolicyUpdated(object sender, GenericEventArgs<User> e) - { - var dto = _userManager.GetUserDto(e.Argument); - - await SendMessageToUserSession(e.Argument, "UserPolicyUpdated", dto).ConfigureAwait(false); - } - - private async void OnUserConfigurationUpdated(object sender, GenericEventArgs<User> e) - { - var dto = _userManager.GetUserDto(e.Argument); - - await SendMessageToUserSession(e.Argument, "UserConfigurationUpdated", dto).ConfigureAwait(false); - } - private async Task SendMessageToAdminSessions<T>(string name, T data) { try @@ -175,7 +159,6 @@ namespace Emby.Server.Implementations.EntryPoints } catch (Exception) { - } } @@ -191,7 +174,6 @@ namespace Emby.Server.Implementations.EntryPoints } catch (Exception) { - } } @@ -210,10 +192,8 @@ namespace Emby.Server.Implementations.EntryPoints { if (dispose) { - _userManager.UserDeleted -= OnUserDeleted; - _userManager.UserUpdated -= OnUserUpdated; - _userManager.UserPolicyUpdated -= OnUserPolicyUpdated; - _userManager.UserConfigurationUpdated -= OnUserConfigurationUpdated; + _userManager.OnUserDeleted -= OnUserDeleted; + _userManager.OnUserUpdated -= OnUserUpdated; _installationManager.PluginUninstalled -= OnPluginUninstalled; _installationManager.PackageInstalling -= OnPackageInstalling; diff --git a/Emby.Server.Implementations/EntryPoints/UdpServerEntryPoint.cs b/Emby.Server.Implementations/EntryPoints/UdpServerEntryPoint.cs index ecdce89ce..b207397bd 100644 --- a/Emby.Server.Implementations/EntryPoints/UdpServerEntryPoint.cs +++ b/Emby.Server.Implementations/EntryPoints/UdpServerEntryPoint.cs @@ -43,7 +43,6 @@ namespace Emby.Server.Implementations.EntryPoints _logger = logger; _appHost = appHost; _config = configuration; - } /// <inheritdoc /> diff --git a/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs b/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs index 87977494a..25adc5812 100644 --- a/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs +++ b/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs @@ -140,7 +140,7 @@ namespace Emby.Server.Implementations.HttpClientManager => SendAsync(options, HttpMethod.Get); /// <summary> - /// Performs a GET request and returns the resulting stream + /// Performs a GET request and returns the resulting stream. /// </summary> /// <param name="options">The options.</param> /// <returns>Task{Stream}.</returns> diff --git a/Emby.Server.Implementations/HttpServer/FileWriter.cs b/Emby.Server.Implementations/HttpServer/FileWriter.cs index 0b61e40b0..590eee1b4 100644 --- a/Emby.Server.Implementations/HttpServer/FileWriter.cs +++ b/Emby.Server.Implementations/HttpServer/FileWriter.cs @@ -32,12 +32,12 @@ namespace Emby.Server.Implementations.HttpServer private readonly IFileSystem _fileSystem; /// <summary> - /// The _options + /// The _options. /// </summary> private readonly IDictionary<string, string> _options = new Dictionary<string, string>(); /// <summary> - /// The _requested ranges + /// The _requested ranges. /// </summary> private List<KeyValuePair<long, long?>> _requestedRanges; diff --git a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs index 1b6e4b554..c3428ee62 100644 --- a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs +++ b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs @@ -453,6 +453,7 @@ namespace Emby.Server.Implementations.HttpServer { httpRes.Headers.Add(key, value); } + httpRes.ContentType = "text/plain"; await httpRes.WriteAsync(string.Empty, cancellationToken).ConfigureAwait(false); return; @@ -591,7 +592,7 @@ namespace Emby.Server.Implementations.HttpServer } /// <summary> - /// Get the default CORS headers + /// Get the default CORS headers. /// </summary> /// <param name="req"></param> /// <returns></returns> diff --git a/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs b/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs index cc4797790..ad31b3e1e 100644 --- a/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs +++ b/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs @@ -580,7 +580,6 @@ namespace Emby.Server.Implementations.HttpServer } catch (NotSupportedException) { - } } @@ -693,7 +692,7 @@ namespace Emby.Server.Implementations.HttpServer /// <summary> - /// When the browser sends the IfModifiedDate, it's precision is limited to seconds, so this will account for that + /// When the browser sends the IfModifiedDate, it's precision is limited to seconds, so this will account for that. /// </summary> /// <param name="date">The date.</param> /// <returns>DateTime.</returns> diff --git a/Emby.Server.Implementations/HttpServer/RangeRequestWriter.cs b/Emby.Server.Implementations/HttpServer/RangeRequestWriter.cs index 8b9028f6b..540340272 100644 --- a/Emby.Server.Implementations/HttpServer/RangeRequestWriter.cs +++ b/Emby.Server.Implementations/HttpServer/RangeRequestWriter.cs @@ -20,31 +20,37 @@ namespace Emby.Server.Implementations.HttpServer /// </summary> /// <value>The source stream.</value> private Stream SourceStream { get; set; } + private string RangeHeader { get; set; } + private bool IsHeadRequest { get; set; } private long RangeStart { get; set; } + private long RangeEnd { get; set; } + private long RangeLength { get; set; } + private long TotalContentLength { get; set; } public Action OnComplete { get; set; } + private readonly ILogger _logger; private const int BufferSize = 81920; /// <summary> - /// The _options + /// The _options. /// </summary> private readonly Dictionary<string, string> _options = new Dictionary<string, string>(); /// <summary> - /// The us culture + /// The us culture. /// </summary> private static readonly CultureInfo UsCulture = new CultureInfo("en-US"); /// <summary> - /// Additional HTTP Headers + /// Additional HTTP Headers. /// </summary> /// <value>The headers.</value> public IDictionary<string, string> Headers => _options; @@ -110,7 +116,7 @@ namespace Emby.Server.Implementations.HttpServer } /// <summary> - /// The _requested ranges + /// The _requested ranges. /// </summary> private List<KeyValuePair<long, long?>> _requestedRanges; /// <summary> @@ -139,6 +145,7 @@ namespace Emby.Server.Implementations.HttpServer { start = long.Parse(vals[0], UsCulture); } + if (!string.IsNullOrEmpty(vals[1])) { end = long.Parse(vals[1], UsCulture); diff --git a/Emby.Server.Implementations/HttpServer/ResponseFilter.cs b/Emby.Server.Implementations/HttpServer/ResponseFilter.cs index 85c3db9b2..3ab5dbc16 100644 --- a/Emby.Server.Implementations/HttpServer/ResponseFilter.cs +++ b/Emby.Server.Implementations/HttpServer/ResponseFilter.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; using System.Globalization; using System.Text; using MediaBrowser.Controller.Net; diff --git a/Emby.Server.Implementations/HttpServer/Security/AuthService.cs b/Emby.Server.Implementations/HttpServer/Security/AuthService.cs index 1b68617c1..2e6ff65a6 100644 --- a/Emby.Server.Implementations/HttpServer/Security/AuthService.cs +++ b/Emby.Server.Implementations/HttpServer/Security/AuthService.cs @@ -3,10 +3,11 @@ using System; using System.Linq; using Emby.Server.Implementations.SocketSharp; +using Jellyfin.Data.Entities; +using Jellyfin.Data.Enums; using MediaBrowser.Common.Net; using MediaBrowser.Controller.Authentication; using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Security; using MediaBrowser.Controller.Session; @@ -90,7 +91,8 @@ namespace Emby.Server.Implementations.HttpServer.Security !string.IsNullOrEmpty(auth.Client) && !string.IsNullOrEmpty(auth.Device)) { - _sessionManager.LogSessionActivity(auth.Client, + _sessionManager.LogSessionActivity( + auth.Client, auth.Version, auth.DeviceId, auth.Device, @@ -104,21 +106,21 @@ namespace Emby.Server.Implementations.HttpServer.Security private void ValidateUserAccess( User user, IRequest request, - IAuthenticationAttributes authAttribtues, + IAuthenticationAttributes authAttributes, AuthorizationInfo auth) { - if (user.Policy.IsDisabled) + if (user.HasPermission(PermissionKind.IsDisabled)) { throw new SecurityException("User account has been disabled."); } - if (!user.Policy.EnableRemoteAccess && !_networkManager.IsInLocalNetwork(request.RemoteIp)) + if (!user.HasPermission(PermissionKind.EnableRemoteAccess) && !_networkManager.IsInLocalNetwork(request.RemoteIp)) { throw new SecurityException("User account has been disabled."); } - if (!user.Policy.IsAdministrator - && !authAttribtues.EscapeParentalControl + if (!user.HasPermission(PermissionKind.IsAdministrator) + && !authAttributes.EscapeParentalControl && !user.IsParentalScheduleAllowed()) { request.Response.Headers.Add("X-Application-Error-Code", "ParentalControl"); @@ -138,6 +140,7 @@ namespace Emby.Server.Implementations.HttpServer.Security { return true; } + if (authAttribtues.AllowLocalOnly && request.IsLocal) { return true; @@ -180,7 +183,7 @@ namespace Emby.Server.Implementations.HttpServer.Security { if (roles.Contains("admin", StringComparer.OrdinalIgnoreCase)) { - if (user == null || !user.Policy.IsAdministrator) + if (user == null || !user.HasPermission(PermissionKind.IsAdministrator)) { throw new SecurityException("User does not have admin access."); } @@ -188,7 +191,7 @@ namespace Emby.Server.Implementations.HttpServer.Security if (roles.Contains("delete", StringComparer.OrdinalIgnoreCase)) { - if (user == null || !user.Policy.EnableContentDeletion) + if (user == null || !user.HasPermission(PermissionKind.EnableContentDeletion)) { throw new SecurityException("User does not have delete access."); } @@ -196,7 +199,7 @@ namespace Emby.Server.Implementations.HttpServer.Security if (roles.Contains("download", StringComparer.OrdinalIgnoreCase)) { - if (user == null || !user.Policy.EnableContentDownloading) + if (user == null || !user.HasPermission(PermissionKind.EnableContentDownloading)) { throw new SecurityException("User does not have download access."); } @@ -223,7 +226,7 @@ namespace Emby.Server.Implementations.HttpServer.Security throw new AuthenticationException("Access token is invalid or expired."); } - //if (!string.IsNullOrEmpty(info.UserId)) + // if (!string.IsNullOrEmpty(info.UserId)) //{ // var user = _userManager.GetUserById(info.UserId); diff --git a/Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs b/Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs index 129faeaab..bbade00ff 100644 --- a/Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs +++ b/Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs @@ -71,6 +71,7 @@ namespace Emby.Server.Implementations.HttpServer.Security { token = httpReq.Headers["X-MediaBrowser-Token"]; } + if (string.IsNullOrEmpty(token)) { token = httpReq.QueryString["api_key"]; @@ -116,7 +117,6 @@ namespace Emby.Server.Implementations.HttpServer.Security { info.Device = tokenInfo.DeviceName; } - else if (!string.Equals(info.Device, tokenInfo.DeviceName, StringComparison.OrdinalIgnoreCase)) { if (allowTokenInfoUpdate) @@ -149,9 +149,9 @@ namespace Emby.Server.Implementations.HttpServer.Security { info.User = _userManager.GetUserById(tokenInfo.UserId); - if (info.User != null && !string.Equals(info.User.Name, tokenInfo.UserName, StringComparison.OrdinalIgnoreCase)) + if (info.User != null && !string.Equals(info.User.Username, tokenInfo.UserName, StringComparison.OrdinalIgnoreCase)) { - tokenInfo.UserName = info.User.Name; + tokenInfo.UserName = info.User.Username; updateToken = true; } } @@ -161,6 +161,7 @@ namespace Emby.Server.Implementations.HttpServer.Security _authRepo.Update(tokenInfo); } } + httpReq.Items["OriginalAuthenticationInfo"] = tokenInfo; } diff --git a/Emby.Server.Implementations/HttpServer/Security/SessionContext.cs b/Emby.Server.Implementations/HttpServer/Security/SessionContext.cs index 166952c64..03fcfa53d 100644 --- a/Emby.Server.Implementations/HttpServer/Security/SessionContext.cs +++ b/Emby.Server.Implementations/HttpServer/Security/SessionContext.cs @@ -1,7 +1,7 @@ #pragma warning disable CS1591 using System; -using MediaBrowser.Controller.Entities; +using Jellyfin.Data.Entities; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Security; diff --git a/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs b/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs index c64d57339..316cd84cf 100644 --- a/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs +++ b/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs @@ -234,10 +234,12 @@ namespace Emby.Server.Implementations.HttpServer private Task SendKeepAliveResponse() { LastKeepAliveDate = DateTime.UtcNow; - return SendAsync(new WebSocketMessage<string> - { - MessageType = "KeepAlive" - }, CancellationToken.None); + return SendAsync( + new WebSocketMessage<string> + { + MessageId = Guid.NewGuid(), + MessageType = "KeepAlive" + }, CancellationToken.None); } /// <inheritdoc /> diff --git a/Emby.Server.Implementations/IO/LibraryMonitor.cs b/Emby.Server.Implementations/IO/LibraryMonitor.cs index 49bca7dac..a32b03aaa 100644 --- a/Emby.Server.Implementations/IO/LibraryMonitor.cs +++ b/Emby.Server.Implementations/IO/LibraryMonitor.cs @@ -266,7 +266,6 @@ namespace Emby.Server.Implementations.IO { DisposeWatcher(newWatcher, false); } - } catch (Exception ex) { @@ -393,7 +392,6 @@ namespace Emby.Server.Implementations.IO } return false; - })) { monitorPath = false; diff --git a/Emby.Server.Implementations/IO/ManagedFileSystem.cs b/Emby.Server.Implementations/IO/ManagedFileSystem.cs index 2bcfc82b6..a3a3f91b7 100644 --- a/Emby.Server.Implementations/IO/ManagedFileSystem.cs +++ b/Emby.Server.Implementations/IO/ManagedFileSystem.cs @@ -237,7 +237,7 @@ namespace Emby.Server.Implementations.IO { result.IsDirectory = info is DirectoryInfo || (info.Attributes & FileAttributes.Directory) == FileAttributes.Directory; - //if (!result.IsDirectory) + // if (!result.IsDirectory) //{ // result.IsHidden = (info.Attributes & FileAttributes.Hidden) == FileAttributes.Hidden; //} @@ -628,6 +628,7 @@ namespace Emby.Server.Implementations.IO { return false; } + return extensions.Contains(ext, StringComparer.OrdinalIgnoreCase); }); } @@ -682,6 +683,7 @@ namespace Emby.Server.Implementations.IO { return false; } + return extensions.Contains(ext, StringComparer.OrdinalIgnoreCase); }); } diff --git a/Emby.Server.Implementations/Images/CollectionFolderImageProvider.cs b/Emby.Server.Implementations/Images/CollectionFolderImageProvider.cs index dc8062b45..da88b8d8a 100644 --- a/Emby.Server.Implementations/Images/CollectionFolderImageProvider.cs +++ b/Emby.Server.Implementations/Images/CollectionFolderImageProvider.cs @@ -71,7 +71,6 @@ namespace Emby.Server.Implementations.Images new ValueTuple<string, SortOrder>(ItemSortBy.Random, SortOrder.Ascending) }, IncludeItemTypes = includeItemTypes - }); } diff --git a/Emby.Server.Implementations/Images/DynamicImageProvider.cs b/Emby.Server.Implementations/Images/DynamicImageProvider.cs index ca0aa4a9f..462eb03a8 100644 --- a/Emby.Server.Implementations/Images/DynamicImageProvider.cs +++ b/Emby.Server.Implementations/Images/DynamicImageProvider.cs @@ -78,7 +78,6 @@ namespace Emby.Server.Implementations.Images } return i; - }).GroupBy(x => x.Id) .Select(x => x.First()); diff --git a/Emby.Server.Implementations/Library/CoreResolutionIgnoreRule.cs b/Emby.Server.Implementations/Library/CoreResolutionIgnoreRule.cs index 218e5a0c6..e140009ea 100644 --- a/Emby.Server.Implementations/Library/CoreResolutionIgnoreRule.cs +++ b/Emby.Server.Implementations/Library/CoreResolutionIgnoreRule.cs @@ -8,7 +8,7 @@ using MediaBrowser.Model.IO; namespace Emby.Server.Implementations.Library { /// <summary> - /// Provides the core resolver ignore rules + /// Provides the core resolver ignore rules. /// </summary> public class CoreResolutionIgnoreRule : IResolverIgnoreRule { diff --git a/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs b/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs deleted file mode 100644 index 02f150607..000000000 --- a/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs +++ /dev/null @@ -1,139 +0,0 @@ -using System; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using MediaBrowser.Common; -using MediaBrowser.Common.Cryptography; -using MediaBrowser.Controller.Authentication; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Model.Cryptography; - -namespace Emby.Server.Implementations.Library -{ - /// <summary> - /// The default authentication provider. - /// </summary> - public class DefaultAuthenticationProvider : IAuthenticationProvider, IRequiresResolvedUser - { - private readonly ICryptoProvider _cryptographyProvider; - - /// <summary> - /// Initializes a new instance of the <see cref="DefaultAuthenticationProvider"/> class. - /// </summary> - /// <param name="cryptographyProvider">The cryptography provider.</param> - public DefaultAuthenticationProvider(ICryptoProvider cryptographyProvider) - { - _cryptographyProvider = cryptographyProvider; - } - - /// <inheritdoc /> - public string Name => "Default"; - - /// <inheritdoc /> - public bool IsEnabled => true; - - /// <inheritdoc /> - // This is dumb and an artifact of the backwards way auth providers were designed. - // This version of authenticate was never meant to be called, but needs to be here for interface compat - // Only the providers that don't provide local user support use this - public Task<ProviderAuthenticationResult> Authenticate(string username, string password) - { - throw new NotImplementedException(); - } - - /// <inheritdoc /> - // This is the version that we need to use for local users. Because reasons. - public Task<ProviderAuthenticationResult> Authenticate(string username, string password, User resolvedUser) - { - if (resolvedUser == null) - { - throw new AuthenticationException($"Specified user does not exist."); - } - - bool success = false; - - // As long as jellyfin supports passwordless users, we need this little block here to accommodate - if (!HasPassword(resolvedUser) && string.IsNullOrEmpty(password)) - { - return Task.FromResult(new ProviderAuthenticationResult - { - Username = username - }); - } - - byte[] passwordbytes = Encoding.UTF8.GetBytes(password); - - PasswordHash readyHash = PasswordHash.Parse(resolvedUser.Password); - if (_cryptographyProvider.GetSupportedHashMethods().Contains(readyHash.Id) - || _cryptographyProvider.DefaultHashMethod == readyHash.Id) - { - byte[] calculatedHash = _cryptographyProvider.ComputeHash( - readyHash.Id, - passwordbytes, - readyHash.Salt.ToArray()); - - if (readyHash.Hash.SequenceEqual(calculatedHash)) - { - success = true; - } - } - else - { - throw new AuthenticationException($"Requested crypto method not available in provider: {readyHash.Id}"); - } - - if (!success) - { - throw new AuthenticationException("Invalid username or password"); - } - - return Task.FromResult(new ProviderAuthenticationResult - { - Username = username - }); - } - - /// <inheritdoc /> - public bool HasPassword(User user) - => !string.IsNullOrEmpty(user.Password); - - /// <inheritdoc /> - public Task ChangePassword(User user, string newPassword) - { - if (string.IsNullOrEmpty(newPassword)) - { - user.Password = null; - return Task.CompletedTask; - } - - PasswordHash newPasswordHash = _cryptographyProvider.CreatePasswordHash(newPassword); - user.Password = newPasswordHash.ToString(); - - return Task.CompletedTask; - } - - /// <inheritdoc /> - public void ChangeEasyPassword(User user, string newPassword, string newPasswordHash) - { - if (newPassword != null) - { - newPasswordHash = _cryptographyProvider.CreatePasswordHash(newPassword).ToString(); - } - - if (string.IsNullOrWhiteSpace(newPasswordHash)) - { - throw new ArgumentNullException(nameof(newPasswordHash)); - } - - user.EasyPassword = newPasswordHash; - } - - /// <inheritdoc /> - public string GetEasyPasswordHash(User user) - { - return string.IsNullOrEmpty(user.EasyPassword) - ? null - : Hex.Encode(PasswordHash.Parse(user.EasyPassword).Hash); - } - } -} diff --git a/Emby.Server.Implementations/Library/DefaultPasswordResetProvider.cs b/Emby.Server.Implementations/Library/DefaultPasswordResetProvider.cs deleted file mode 100644 index 6c6fbd86f..000000000 --- a/Emby.Server.Implementations/Library/DefaultPasswordResetProvider.cs +++ /dev/null @@ -1,140 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Security.Cryptography; -using System.Threading.Tasks; -using MediaBrowser.Common.Extensions; -using MediaBrowser.Controller.Authentication; -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Library; -using MediaBrowser.Model.Serialization; -using MediaBrowser.Model.Users; - -namespace Emby.Server.Implementations.Library -{ - /// <summary> - /// The default password reset provider. - /// </summary> - public class DefaultPasswordResetProvider : IPasswordResetProvider - { - private const string BaseResetFileName = "passwordreset"; - - private readonly IJsonSerializer _jsonSerializer; - private readonly IUserManager _userManager; - - private readonly string _passwordResetFileBase; - private readonly string _passwordResetFileBaseDir; - - /// <summary> - /// Initializes a new instance of the <see cref="DefaultPasswordResetProvider"/> class. - /// </summary> - /// <param name="configurationManager">The configuration manager.</param> - /// <param name="jsonSerializer">The JSON serializer.</param> - /// <param name="userManager">The user manager.</param> - public DefaultPasswordResetProvider( - IServerConfigurationManager configurationManager, - IJsonSerializer jsonSerializer, - IUserManager userManager) - { - _passwordResetFileBaseDir = configurationManager.ApplicationPaths.ProgramDataPath; - _passwordResetFileBase = Path.Combine(_passwordResetFileBaseDir, BaseResetFileName); - _jsonSerializer = jsonSerializer; - _userManager = userManager; - } - - /// <inheritdoc /> - public string Name => "Default Password Reset Provider"; - - /// <inheritdoc /> - public bool IsEnabled => true; - - /// <inheritdoc /> - public async Task<PinRedeemResult> RedeemPasswordResetPin(string pin) - { - SerializablePasswordReset spr; - List<string> usersreset = new List<string>(); - foreach (var resetfile in Directory.EnumerateFiles(_passwordResetFileBaseDir, $"{BaseResetFileName}*")) - { - using (var str = File.OpenRead(resetfile)) - { - spr = await _jsonSerializer.DeserializeFromStreamAsync<SerializablePasswordReset>(str).ConfigureAwait(false); - } - - if (spr.ExpirationDate < DateTime.Now) - { - File.Delete(resetfile); - } - else if (string.Equals( - spr.Pin.Replace("-", string.Empty, StringComparison.Ordinal), - pin.Replace("-", string.Empty, StringComparison.Ordinal), - StringComparison.InvariantCultureIgnoreCase)) - { - var resetUser = _userManager.GetUserByName(spr.UserName); - if (resetUser == null) - { - throw new ResourceNotFoundException($"User with a username of {spr.UserName} not found"); - } - - await _userManager.ChangePassword(resetUser, pin).ConfigureAwait(false); - usersreset.Add(resetUser.Name); - File.Delete(resetfile); - } - } - - if (usersreset.Count < 1) - { - throw new ResourceNotFoundException($"No Users found with a password reset request matching pin {pin}"); - } - else - { - return new PinRedeemResult - { - Success = true, - UsersReset = usersreset.ToArray() - }; - } - } - - /// <inheritdoc /> - public async Task<ForgotPasswordResult> StartForgotPasswordProcess(MediaBrowser.Controller.Entities.User user, bool isInNetwork) - { - string pin = string.Empty; - using (var cryptoRandom = RandomNumberGenerator.Create()) - { - byte[] bytes = new byte[4]; - cryptoRandom.GetBytes(bytes); - pin = BitConverter.ToString(bytes); - } - - DateTime expireTime = DateTime.Now.AddMinutes(30); - string filePath = _passwordResetFileBase + user.InternalId + ".json"; - SerializablePasswordReset spr = new SerializablePasswordReset - { - ExpirationDate = expireTime, - Pin = pin, - PinFile = filePath, - UserName = user.Name - }; - - using (FileStream fileStream = File.OpenWrite(filePath)) - { - _jsonSerializer.SerializeToStream(spr, fileStream); - await fileStream.FlushAsync().ConfigureAwait(false); - } - - return new ForgotPasswordResult - { - Action = ForgotPasswordAction.PinCode, - PinExpirationDate = expireTime, - PinFile = filePath - }; - } - - private class SerializablePasswordReset : PasswordPinCreationResult - { - public string Pin { get; set; } - - public string UserName { get; set; } - } - } -} diff --git a/Emby.Server.Implementations/Library/ExclusiveLiveStream.cs b/Emby.Server.Implementations/Library/ExclusiveLiveStream.cs index 9a7186898..ab39a7223 100644 --- a/Emby.Server.Implementations/Library/ExclusiveLiveStream.cs +++ b/Emby.Server.Implementations/Library/ExclusiveLiveStream.cs @@ -12,11 +12,13 @@ namespace Emby.Server.Implementations.Library public class ExclusiveLiveStream : ILiveStream { public int ConsumerCount { get; set; } + public string OriginalStreamId { get; set; } public string TunerHostId => null; public bool EnableStreamSharing { get; set; } + public MediaSourceInfo MediaSource { get; set; } public string UniqueId { get; private set; } diff --git a/Emby.Server.Implementations/Library/IgnorePatterns.cs b/Emby.Server.Implementations/Library/IgnorePatterns.cs index d12b5855b..8c4098948 100644 --- a/Emby.Server.Implementations/Library/IgnorePatterns.cs +++ b/Emby.Server.Implementations/Library/IgnorePatterns.cs @@ -4,12 +4,12 @@ using DotNet.Globbing; namespace Emby.Server.Implementations.Library { /// <summary> - /// Glob patterns for files to ignore + /// Glob patterns for files to ignore. /// </summary> public static class IgnorePatterns { /// <summary> - /// Files matching these glob patterns will be ignored + /// Files matching these glob patterns will be ignored. /// </summary> public static readonly string[] Patterns = new string[] { @@ -64,7 +64,7 @@ namespace Emby.Server.Implementations.Library private static readonly Glob[] _globs = Patterns.Select(p => Glob.Parse(p, _globOptions)).ToArray(); /// <summary> - /// Returns true if the supplied path should be ignored + /// Returns true if the supplied path should be ignored. /// </summary> public static bool ShouldIgnore(string path) { diff --git a/Emby.Server.Implementations/Library/InvalidAuthProvider.cs b/Emby.Server.Implementations/Library/InvalidAuthProvider.cs deleted file mode 100644 index dc61aacd7..000000000 --- a/Emby.Server.Implementations/Library/InvalidAuthProvider.cs +++ /dev/null @@ -1,54 +0,0 @@ -using System.Threading.Tasks; -using MediaBrowser.Controller.Authentication; -using MediaBrowser.Controller.Entities; - -namespace Emby.Server.Implementations.Library -{ - /// <summary> - /// An invalid authentication provider. - /// </summary> - public class InvalidAuthProvider : IAuthenticationProvider - { - /// <inheritdoc /> - public string Name => "InvalidOrMissingAuthenticationProvider"; - - /// <inheritdoc /> - public bool IsEnabled => true; - - /// <inheritdoc /> - public Task<ProviderAuthenticationResult> Authenticate(string username, string password) - { - throw new AuthenticationException("User Account cannot login with this provider. The Normal provider for this user cannot be found"); - } - - /// <inheritdoc /> - public bool HasPassword(User user) - { - return true; - } - - /// <inheritdoc /> - public Task ChangePassword(User user, string newPassword) - { - return Task.CompletedTask; - } - - /// <inheritdoc /> - public void ChangeEasyPassword(User user, string newPassword, string newPasswordHash) - { - // Nothing here - } - - /// <inheritdoc /> - public string GetPasswordHash(User user) - { - return string.Empty; - } - - /// <inheritdoc /> - public string GetEasyPasswordHash(User user) - { - return string.Empty; - } - } -} diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs index a57219143..893af4cae 100644 --- a/Emby.Server.Implementations/Library/LibraryManager.cs +++ b/Emby.Server.Implementations/Library/LibraryManager.cs @@ -17,6 +17,8 @@ using Emby.Server.Implementations.Library.Resolvers; using Emby.Server.Implementations.Library.Validators; using Emby.Server.Implementations.Playlists; using Emby.Server.Implementations.ScheduledTasks; +using Jellyfin.Data.Entities; +using Jellyfin.Data.Enums; using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Progress; using MediaBrowser.Controller; @@ -25,7 +27,6 @@ using MediaBrowser.Controller.Drawing; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Audio; -using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.IO; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.LiveTv; @@ -46,6 +47,9 @@ using MediaBrowser.Model.Querying; using MediaBrowser.Model.Tasks; using MediaBrowser.Providers.MediaInfo; 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; @@ -93,13 +97,13 @@ namespace Emby.Server.Implementations.Library private IIntroProvider[] IntroProviders { get; set; } /// <summary> - /// Gets or sets the list of entity resolution ignore rules + /// 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 + /// Gets or sets the list of currently registered entity resolvers. /// </summary> /// <value>The entity resolvers enumerable.</value> private IItemResolver[] EntityResolvers { get; set; } @@ -205,12 +209,12 @@ namespace Emby.Server.Implementations.Library } /// <summary> - /// The _root folder + /// The _root folder. /// </summary> private volatile AggregateFolder _rootFolder; /// <summary> - /// The _root folder sync lock + /// The _root folder sync lock. /// </summary> private readonly object _rootFolderSyncLock = new object(); @@ -623,7 +627,7 @@ namespace Emby.Server.Implementations.Library } /// <summary> - /// Determines whether a path should be ignored based on its contents - called after the contents have been read + /// Determines whether a path should be ignored based on its contents - called after the contents have been read. /// </summary> /// <param name="args">The args.</param> /// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns> @@ -905,7 +909,7 @@ namespace Emby.Server.Implementations.Library } /// <summary> - /// Gets a Genre + /// Gets a Genre. /// </summary> /// <param name="name">The name.</param> /// <returns>Task{Genre}.</returns> @@ -986,7 +990,7 @@ namespace Emby.Server.Implementations.Library } /// <summary> - /// Reloads the root media folder + /// Reloads the root media folder. /// </summary> /// <param name="progress">The progress.</param> /// <param name="cancellationToken">The cancellation token.</param> @@ -1539,7 +1543,8 @@ namespace Emby.Server.Implementations.Library } // Handle grouping - if (user != null && !string.IsNullOrEmpty(view.ViewType) && UserView.IsEligibleForGrouping(view.ViewType) && user.Configuration.GroupedFolders.Length > 0) + if (user != null && !string.IsNullOrEmpty(view.ViewType) && UserView.IsEligibleForGrouping(view.ViewType) + && user.GetPreference(PreferenceKind.GroupedFolders).Length > 0) { return GetUserRootFolder() .GetChildren(user, true) @@ -2590,7 +2595,7 @@ namespace Emby.Server.Implementations.Library Anime series don't generally have a season in their file name, however, tvdb needs a season to correctly get the metadata. Hence, a null season needs to be filled with something. */ - //FIXME perhaps this would be better for tvdb parser to ask for season 1 if no season is specified + // FIXME perhaps this would be better for tvdb parser to ask for season 1 if no season is specified episode.ParentIndexNumber = 1; } @@ -2779,10 +2784,12 @@ namespace Emby.Server.Implementations.Library { throw new ArgumentNullException(nameof(path)); } + if (string.IsNullOrWhiteSpace(from)) { throw new ArgumentNullException(nameof(from)); } + if (string.IsNullOrWhiteSpace(to)) { throw new ArgumentNullException(nameof(to)); @@ -2856,7 +2863,6 @@ namespace Emby.Server.Implementations.Library _logger.LogError(ex, "Error getting person"); return null; } - }).Where(i => i != null).ToList(); } @@ -2986,7 +2992,7 @@ namespace Emby.Server.Implementations.Library private static bool ValidateNetworkPath(string path) { - //if (Environment.OSVersion.Platform == PlatformID.Win32NT) + // if (Environment.OSVersion.Platform == PlatformID.Win32NT) //{ // // We can't validate protocol-based paths, so just allow them // if (path.IndexOf("://", StringComparison.OrdinalIgnoreCase) == -1) diff --git a/Emby.Server.Implementations/Library/LiveStreamHelper.cs b/Emby.Server.Implementations/Library/LiveStreamHelper.cs index ed7d8aa40..9b9f53049 100644 --- a/Emby.Server.Implementations/Library/LiveStreamHelper.cs +++ b/Emby.Server.Implementations/Library/LiveStreamHelper.cs @@ -50,7 +50,7 @@ namespace Emby.Server.Implementations.Library { mediaInfo = _json.DeserializeFromFile<MediaInfo>(cacheFilePath); - //_logger.LogDebug("Found cached media info"); + // _logger.LogDebug("Found cached media info"); } catch { @@ -85,7 +85,7 @@ namespace Emby.Server.Implementations.Library Directory.CreateDirectory(Path.GetDirectoryName(cacheFilePath)); _json.SerializeToFile(mediaInfo, cacheFilePath); - //_logger.LogDebug("Saved media info to {0}", cacheFilePath); + // _logger.LogDebug("Saved media info to {0}", cacheFilePath); } } @@ -148,17 +148,14 @@ namespace Emby.Server.Implementations.Library { videoStream.BitRate = 30000000; } - else if (width >= 1900) { videoStream.BitRate = 20000000; } - else if (width >= 1200) { videoStream.BitRate = 8000000; } - else if (width >= 700) { videoStream.BitRate = 2000000; diff --git a/Emby.Server.Implementations/Library/MediaSourceManager.cs b/Emby.Server.Implementations/Library/MediaSourceManager.cs index 00826c7cb..ceb36b389 100644 --- a/Emby.Server.Implementations/Library/MediaSourceManager.cs +++ b/Emby.Server.Implementations/Library/MediaSourceManager.cs @@ -7,6 +7,8 @@ using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; +using Jellyfin.Data.Entities; +using Jellyfin.Data.Enums; using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Extensions; using MediaBrowser.Controller.Entities; @@ -14,7 +16,6 @@ using MediaBrowser.Controller.Library; using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.Controller.Persistence; using MediaBrowser.Controller.Providers; -using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; @@ -190,10 +191,7 @@ namespace Emby.Server.Implementations.Library { if (string.Equals(item.MediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase)) { - if (!user.Policy.EnableAudioPlaybackTranscoding) - { - source.SupportsTranscoding = false; - } + source.SupportsTranscoding = user.HasPermission(PermissionKind.EnableAudioPlaybackTranscoding); } } } @@ -207,22 +205,27 @@ namespace Emby.Server.Implementations.Library { return MediaProtocol.Rtsp; } + if (path.StartsWith("Rtmp", StringComparison.OrdinalIgnoreCase)) { return MediaProtocol.Rtmp; } + if (path.StartsWith("Http", StringComparison.OrdinalIgnoreCase)) { return MediaProtocol.Http; } + if (path.StartsWith("rtp", StringComparison.OrdinalIgnoreCase)) { return MediaProtocol.Rtp; } + if (path.StartsWith("ftp", StringComparison.OrdinalIgnoreCase)) { return MediaProtocol.Ftp; } + if (path.StartsWith("udp", StringComparison.OrdinalIgnoreCase)) { return MediaProtocol.Udp; @@ -352,7 +355,9 @@ namespace Emby.Server.Implementations.Library private void SetDefaultSubtitleStreamIndex(MediaSourceInfo source, UserItemData userData, User user, bool allowRememberingSelection) { - if (userData.SubtitleStreamIndex.HasValue && user.Configuration.RememberSubtitleSelections && user.Configuration.SubtitleMode != SubtitlePlaybackMode.None && allowRememberingSelection) + if (userData.SubtitleStreamIndex.HasValue + && user.RememberSubtitleSelections + && user.SubtitleMode != SubtitlePlaybackMode.None && allowRememberingSelection) { var index = userData.SubtitleStreamIndex.Value; // Make sure the saved index is still valid @@ -363,26 +368,27 @@ namespace Emby.Server.Implementations.Library } } - var preferredSubs = string.IsNullOrEmpty(user.Configuration.SubtitleLanguagePreference) - ? Array.Empty<string>() : NormalizeLanguage(user.Configuration.SubtitleLanguagePreference); + + var preferredSubs = string.IsNullOrEmpty(user.SubtitleLanguagePreference) + ? Array.Empty<string>() : NormalizeLanguage(user.SubtitleLanguagePreference); var defaultAudioIndex = source.DefaultAudioStreamIndex; var audioLangage = defaultAudioIndex == null ? null : source.MediaStreams.Where(i => i.Type == MediaStreamType.Audio && i.Index == defaultAudioIndex).Select(i => i.Language).FirstOrDefault(); - source.DefaultSubtitleStreamIndex = MediaStreamSelector.GetDefaultSubtitleStreamIndex(source.MediaStreams, + source.DefaultSubtitleStreamIndex = MediaStreamSelector.GetDefaultSubtitleStreamIndex( + source.MediaStreams, preferredSubs, - user.Configuration.SubtitleMode, + user.SubtitleMode, audioLangage); - MediaStreamSelector.SetSubtitleStreamScores(source.MediaStreams, preferredSubs, - user.Configuration.SubtitleMode, audioLangage); + MediaStreamSelector.SetSubtitleStreamScores(source.MediaStreams, preferredSubs, user.SubtitleMode, audioLangage); } private void SetDefaultAudioStreamIndex(MediaSourceInfo source, UserItemData userData, User user, bool allowRememberingSelection) { - if (userData.AudioStreamIndex.HasValue && user.Configuration.RememberAudioSelections && allowRememberingSelection) + if (userData.AudioStreamIndex.HasValue && user.RememberAudioSelections && allowRememberingSelection) { var index = userData.AudioStreamIndex.Value; // Make sure the saved index is still valid @@ -393,11 +399,11 @@ namespace Emby.Server.Implementations.Library } } - var preferredAudio = string.IsNullOrEmpty(user.Configuration.AudioLanguagePreference) + var preferredAudio = string.IsNullOrEmpty(user.AudioLanguagePreference) ? Array.Empty<string>() - : NormalizeLanguage(user.Configuration.AudioLanguagePreference); + : NormalizeLanguage(user.AudioLanguagePreference); - source.DefaultAudioStreamIndex = MediaStreamSelector.GetDefaultAudioStreamIndex(source.MediaStreams, preferredAudio, user.Configuration.PlayDefaultAudioTrack); + source.DefaultAudioStreamIndex = MediaStreamSelector.GetDefaultAudioStreamIndex(source.MediaStreams, preferredAudio, user.PlayDefaultAudioTrack); } public void SetDefaultAudioAndSubtitleStreamIndexes(BaseItem item, MediaSourceInfo source, User user) @@ -435,7 +441,6 @@ namespace Emby.Server.Implementations.Library } return 1; - }).ThenBy(i => i.Video3DFormat.HasValue ? 1 : 0) .ThenByDescending(i => { @@ -534,7 +539,7 @@ namespace Emby.Server.Implementations.Library mediaSource.RunTimeTicks = null; } - var audioStream = mediaSource.MediaStreams.FirstOrDefault(i => i.Type == MediaBrowser.Model.Entities.MediaStreamType.Audio); + var audioStream = mediaSource.MediaStreams.FirstOrDefault(i => i.Type == MediaStreamType.Audio); if (audioStream == null || audioStream.Index == -1) { @@ -545,7 +550,7 @@ namespace Emby.Server.Implementations.Library mediaSource.DefaultAudioStreamIndex = audioStream.Index; } - var videoStream = mediaSource.MediaStreams.FirstOrDefault(i => i.Type == MediaBrowser.Model.Entities.MediaStreamType.Video); + var videoStream = mediaSource.MediaStreams.FirstOrDefault(i => i.Type == MediaStreamType.Video); if (videoStream != null) { if (!videoStream.BitRate.HasValue) @@ -556,17 +561,14 @@ namespace Emby.Server.Implementations.Library { videoStream.BitRate = 30000000; } - else if (width >= 1900) { videoStream.BitRate = 20000000; } - else if (width >= 1200) { videoStream.BitRate = 8000000; } - else if (width >= 700) { videoStream.BitRate = 2000000; @@ -622,7 +624,6 @@ namespace Emby.Server.Implementations.Library MediaSource = mediaSource, ExtractChapters = false, MediaType = DlnaProfileType.Video - }, cancellationToken).ConfigureAwait(false); mediaSource.MediaStreams = info.MediaStreams; @@ -648,7 +649,7 @@ namespace Emby.Server.Implementations.Library { mediaInfo = _jsonSerializer.DeserializeFromFile<MediaInfo>(cacheFilePath); - //_logger.LogDebug("Found cached media info"); + // _logger.LogDebug("Found cached media info"); } catch (Exception ex) { @@ -670,20 +671,21 @@ namespace Emby.Server.Implementations.Library mediaSource.AnalyzeDurationMs = 3000; } - mediaInfo = await _mediaEncoder.GetMediaInfo(new MediaInfoRequest + mediaInfo = await _mediaEncoder.GetMediaInfo( + new MediaInfoRequest { MediaSource = mediaSource, MediaType = isAudio ? DlnaProfileType.Audio : DlnaProfileType.Video, ExtractChapters = false - - }, cancellationToken).ConfigureAwait(false); + }, + cancellationToken).ConfigureAwait(false); if (cacheFilePath != null) { Directory.CreateDirectory(Path.GetDirectoryName(cacheFilePath)); _jsonSerializer.SerializeToFile(mediaInfo, cacheFilePath); - //_logger.LogDebug("Saved media info to {0}", cacheFilePath); + // _logger.LogDebug("Saved media info to {0}", cacheFilePath); } } @@ -749,17 +751,14 @@ namespace Emby.Server.Implementations.Library { videoStream.BitRate = 30000000; } - else if (width >= 1900) { videoStream.BitRate = 20000000; } - else if (width >= 1200) { videoStream.BitRate = 8000000; } - else if (width >= 700) { videoStream.BitRate = 2000000; diff --git a/Emby.Server.Implementations/Library/MediaStreamSelector.cs b/Emby.Server.Implementations/Library/MediaStreamSelector.cs index e27145a1d..ca904c4ec 100644 --- a/Emby.Server.Implementations/Library/MediaStreamSelector.cs +++ b/Emby.Server.Implementations/Library/MediaStreamSelector.cs @@ -3,7 +3,7 @@ using System; using System.Collections.Generic; using System.Linq; -using MediaBrowser.Model.Configuration; +using Jellyfin.Data.Enums; using MediaBrowser.Model.Entities; namespace Emby.Server.Implementations.Library diff --git a/Emby.Server.Implementations/Library/MusicManager.cs b/Emby.Server.Implementations/Library/MusicManager.cs index 1ec578371..0bdc59914 100644 --- a/Emby.Server.Implementations/Library/MusicManager.cs +++ b/Emby.Server.Implementations/Library/MusicManager.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; using System.Linq; +using Jellyfin.Data.Entities; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Audio; @@ -10,6 +11,7 @@ using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Playlists; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Querying; +using MusicAlbum = MediaBrowser.Controller.Entities.Audio.MusicAlbum; namespace Emby.Server.Implementations.Library { @@ -75,7 +77,6 @@ namespace Emby.Server.Implementations.Library { return Guid.Empty; } - }).Where(i => !i.Equals(Guid.Empty)).ToArray(); return GetInstantMixFromGenreIds(genreIds, user, dtoOptions); @@ -105,32 +106,27 @@ namespace Emby.Server.Implementations.Library return GetInstantMixFromGenreIds(new[] { item.Id }, user, dtoOptions); } - var playlist = item as Playlist; - if (playlist != null) + if (item is Playlist playlist) { return GetInstantMixFromPlaylist(playlist, user, dtoOptions); } - var album = item as MusicAlbum; - if (album != null) + if (item is MusicAlbum album) { return GetInstantMixFromAlbum(album, user, dtoOptions); } - var artist = item as MusicArtist; - if (artist != null) + if (item is MusicArtist artist) { return GetInstantMixFromArtist(artist, user, dtoOptions); } - var song = item as Audio; - if (song != null) + if (item is Audio song) { return GetInstantMixFromSong(song, user, dtoOptions); } - var folder = item as Folder; - if (folder != null) + if (item is Folder folder) { return GetInstantMixFromFolder(folder, user, dtoOptions); } diff --git a/Emby.Server.Implementations/Library/ResolverHelper.cs b/Emby.Server.Implementations/Library/ResolverHelper.cs index 7ca15b4e5..4e4cac75b 100644 --- a/Emby.Server.Implementations/Library/ResolverHelper.cs +++ b/Emby.Server.Implementations/Library/ResolverHelper.cs @@ -107,7 +107,7 @@ namespace Emby.Server.Implementations.Library } /// <summary> - /// Ensures DateCreated and DateModified have values + /// Ensures DateCreated and DateModified have values. /// </summary> /// <param name="fileSystem">The file system.</param> /// <param name="item">The item.</param> diff --git a/Emby.Server.Implementations/Library/Resolvers/Audio/AudioResolver.cs b/Emby.Server.Implementations/Library/Resolvers/Audio/AudioResolver.cs index fefc8e789..03059e6d3 100644 --- a/Emby.Server.Implementations/Library/Resolvers/Audio/AudioResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/Audio/AudioResolver.cs @@ -209,8 +209,8 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio Name = parseName ? resolvedItem.Name : Path.GetFileNameWithoutExtension(firstMedia.Path), - //AdditionalParts = resolvedItem.Files.Skip(1).Select(i => i.Path).ToArray(), - //LocalAlternateVersions = resolvedItem.AlternateVersions.Select(i => i.Path).ToArray() + // AdditionalParts = resolvedItem.Files.Skip(1).Select(i => i.Path).ToArray(), + // LocalAlternateVersions = resolvedItem.AlternateVersions.Select(i => i.Path).ToArray() }; result.Items.Add(libraryItem); diff --git a/Emby.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs b/Emby.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs index ebfe95d0a..79b6dded3 100644 --- a/Emby.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs @@ -92,7 +92,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio // Args points to an album if parent is an Artist folder or it directly contains music if (args.IsDirectory) { - // if (args.Parent is MusicArtist) return true; //saves us from testing children twice + // if (args.Parent is MusicArtist) return true; // saves us from testing children twice if (ContainsMusic(args.FileSystemChildren, true, args.DirectoryService, _logger, _fileSystem, _libraryManager)) { return true; diff --git a/Emby.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs b/Emby.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs index fb75593bd..2f5e46038 100644 --- a/Emby.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs @@ -292,7 +292,7 @@ namespace Emby.Server.Implementations.Library.Resolvers } return true; - //var blurayExtensions = new[] + // var blurayExtensions = new[] //{ // ".mts", // ".m2ts", @@ -300,7 +300,7 @@ namespace Emby.Server.Implementations.Library.Resolvers // ".mpls" //}; - //return directoryService.GetFiles(fullPath).Any(i => blurayExtensions.Contains(i.Extension ?? string.Empty, StringComparer.OrdinalIgnoreCase)); + // return directoryService.GetFiles(fullPath).Any(i => blurayExtensions.Contains(i.Extension ?? string.Empty, StringComparer.OrdinalIgnoreCase)); } } } diff --git a/Emby.Server.Implementations/Library/Resolvers/ItemResolver.cs b/Emby.Server.Implementations/Library/Resolvers/ItemResolver.cs index 32ccc7fdd..9ca76095b 100644 --- a/Emby.Server.Implementations/Library/Resolvers/ItemResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/ItemResolver.cs @@ -28,7 +28,7 @@ namespace Emby.Server.Implementations.Library.Resolvers public virtual ResolverPriority Priority => ResolverPriority.First; /// <summary> - /// Sets initial values on the newly resolved item + /// Sets initial values on the newly resolved item. /// </summary> /// <param name="item">The item.</param> /// <param name="args">The args.</param> diff --git a/Emby.Server.Implementations/Library/Resolvers/SpecialFolderResolver.cs b/Emby.Server.Implementations/Library/Resolvers/SpecialFolderResolver.cs index 1030ed39d..99f304190 100644 --- a/Emby.Server.Implementations/Library/Resolvers/SpecialFolderResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/SpecialFolderResolver.cs @@ -41,10 +41,12 @@ namespace Emby.Server.Implementations.Library.Resolvers { return new AggregateFolder(); } + if (string.Equals(args.Path, _appPaths.DefaultUserViewsPath, StringComparison.OrdinalIgnoreCase)) { - return new UserRootFolder(); //if we got here and still a root - must be user root + return new UserRootFolder(); // if we got here and still a root - must be user root } + if (args.IsVf) { return new CollectionFolder @@ -73,7 +75,6 @@ namespace Emby.Server.Implementations.Library.Resolvers { return false; } - }) .Select(i => _fileSystem.GetFileNameWithoutExtension(i)) .FirstOrDefault(); diff --git a/Emby.Server.Implementations/Library/Resolvers/TV/EpisodeResolver.cs b/Emby.Server.Implementations/Library/Resolvers/TV/EpisodeResolver.cs index 7f477a0f0..2f7af60c0 100644 --- a/Emby.Server.Implementations/Library/Resolvers/TV/EpisodeResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/TV/EpisodeResolver.cs @@ -55,6 +55,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV episode.SeriesId = series.Id; episode.SeriesName = series.Name; } + if (season != null) { episode.SeasonId = season.Id; diff --git a/Emby.Server.Implementations/Library/Resolvers/TV/SeasonResolver.cs b/Emby.Server.Implementations/Library/Resolvers/TV/SeasonResolver.cs index 7f8800a64..c43a0ec6b 100644 --- a/Emby.Server.Implementations/Library/Resolvers/TV/SeasonResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/TV/SeasonResolver.cs @@ -94,7 +94,6 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV _localization.GetLocalizedString("NameSeasonNumber"), seasonNumber, args.GetLibraryOptions().PreferredMetadataLanguage); - } return season; diff --git a/Emby.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs b/Emby.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs index 2f206f74c..732bfd94d 100644 --- a/Emby.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs @@ -59,7 +59,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV var collectionType = args.GetCollectionType(); if (string.Equals(collectionType, CollectionType.TvShows, StringComparison.OrdinalIgnoreCase)) { - //if (args.ContainsFileSystemEntryByName("tvshow.nfo")) + // if (args.ContainsFileSystemEntryByName("tvshow.nfo")) //{ // return new Series // { diff --git a/Emby.Server.Implementations/Library/SearchEngine.cs b/Emby.Server.Implementations/Library/SearchEngine.cs index bde77ee8e..3df9cc06f 100644 --- a/Emby.Server.Implementations/Library/SearchEngine.cs +++ b/Emby.Server.Implementations/Library/SearchEngine.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; using System.Linq; +using Jellyfin.Data.Entities; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Audio; @@ -12,6 +13,8 @@ using MediaBrowser.Model.Entities; using MediaBrowser.Model.Querying; using MediaBrowser.Model.Search; using Microsoft.Extensions.Logging; +using Genre = MediaBrowser.Controller.Entities.Genre; +using Person = MediaBrowser.Controller.Entities.Person; namespace Emby.Server.Implementations.Library { @@ -191,6 +194,7 @@ namespace Emby.Server.Implementations.Library { searchQuery.AncestorIds = new[] { searchQuery.ParentId }; } + searchQuery.ParentId = Guid.Empty; searchQuery.IncludeItemsByName = true; searchQuery.IncludeItemTypes = Array.Empty<string>(); @@ -204,7 +208,6 @@ namespace Emby.Server.Implementations.Library return mediaItems.Select(i => new SearchHintInfo { Item = i - }).ToList(); } } diff --git a/Emby.Server.Implementations/Library/UserDataManager.cs b/Emby.Server.Implementations/Library/UserDataManager.cs index a463d37f0..175b3af57 100644 --- a/Emby.Server.Implementations/Library/UserDataManager.cs +++ b/Emby.Server.Implementations/Library/UserDataManager.cs @@ -5,6 +5,7 @@ using System.Collections.Concurrent; using System.Collections.Generic; using System.Globalization; using System.Threading; +using Jellyfin.Data.Entities; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; @@ -13,6 +14,7 @@ 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 { @@ -101,7 +103,7 @@ namespace Emby.Server.Implementations.Library } /// <summary> - /// Retrieve all user data for the given user + /// Retrieve all user data for the given user. /// </summary> /// <param name="userId"></param> /// <returns></returns> @@ -186,7 +188,7 @@ namespace Emby.Server.Implementations.Library } /// <summary> - /// Converts a UserItemData to a DTOUserItemData + /// Converts a UserItemData to a DTOUserItemData. /// </summary> /// <param name="data">The data.</param> /// <returns>DtoUserItemData.</returns> diff --git a/Emby.Server.Implementations/Library/UserManager.cs b/Emby.Server.Implementations/Library/UserManager.cs deleted file mode 100644 index 531e00666..000000000 --- a/Emby.Server.Implementations/Library/UserManager.cs +++ /dev/null @@ -1,1107 +0,0 @@ -#pragma warning disable CS1591 - -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Globalization; -using System.IO; -using System.Linq; -using System.Text; -using System.Text.RegularExpressions; -using System.Threading; -using System.Threading.Tasks; -using MediaBrowser.Common.Cryptography; -using MediaBrowser.Common.Events; -using MediaBrowser.Common.Net; -using MediaBrowser.Controller; -using MediaBrowser.Controller.Authentication; -using MediaBrowser.Controller.Devices; -using MediaBrowser.Controller.Drawing; -using MediaBrowser.Controller.Dto; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.Net; -using MediaBrowser.Controller.Persistence; -using MediaBrowser.Controller.Plugins; -using MediaBrowser.Controller.Providers; -using MediaBrowser.Controller.Security; -using MediaBrowser.Controller.Session; -using MediaBrowser.Model.Configuration; -using MediaBrowser.Model.Cryptography; -using MediaBrowser.Model.Dto; -using MediaBrowser.Model.Entities; -using MediaBrowser.Model.Events; -using MediaBrowser.Model.IO; -using MediaBrowser.Model.Serialization; -using MediaBrowser.Model.Users; -using Microsoft.Extensions.Logging; - -namespace Emby.Server.Implementations.Library -{ - /// <summary> - /// Class UserManager. - /// </summary> - public class UserManager : IUserManager - { - private readonly object _policySyncLock = new object(); - private readonly object _configSyncLock = new object(); - - private readonly ILogger<UserManager> _logger; - private readonly IUserRepository _userRepository; - private readonly IXmlSerializer _xmlSerializer; - private readonly IJsonSerializer _jsonSerializer; - private readonly INetworkManager _networkManager; - private readonly IImageProcessor _imageProcessor; - private readonly Lazy<IDtoService> _dtoServiceFactory; - private readonly IServerApplicationHost _appHost; - private readonly IFileSystem _fileSystem; - private readonly ICryptoProvider _cryptoProvider; - - private ConcurrentDictionary<Guid, User> _users; - - private IAuthenticationProvider[] _authenticationProviders; - private DefaultAuthenticationProvider _defaultAuthenticationProvider; - - private InvalidAuthProvider _invalidAuthProvider; - - private IPasswordResetProvider[] _passwordResetProviders; - private DefaultPasswordResetProvider _defaultPasswordResetProvider; - - private IDtoService DtoService => _dtoServiceFactory.Value; - - public UserManager( - ILogger<UserManager> logger, - IUserRepository userRepository, - IXmlSerializer xmlSerializer, - INetworkManager networkManager, - IImageProcessor imageProcessor, - Lazy<IDtoService> dtoServiceFactory, - IServerApplicationHost appHost, - IJsonSerializer jsonSerializer, - IFileSystem fileSystem, - ICryptoProvider cryptoProvider) - { - _logger = logger; - _userRepository = userRepository; - _xmlSerializer = xmlSerializer; - _networkManager = networkManager; - _imageProcessor = imageProcessor; - _dtoServiceFactory = dtoServiceFactory; - _appHost = appHost; - _jsonSerializer = jsonSerializer; - _fileSystem = fileSystem; - _cryptoProvider = cryptoProvider; - _users = null; - } - - public event EventHandler<GenericEventArgs<User>> UserPasswordChanged; - - /// <summary> - /// Occurs when [user updated]. - /// </summary> - public event EventHandler<GenericEventArgs<User>> UserUpdated; - - public event EventHandler<GenericEventArgs<User>> UserPolicyUpdated; - - public event EventHandler<GenericEventArgs<User>> UserConfigurationUpdated; - - public event EventHandler<GenericEventArgs<User>> UserLockedOut; - - public event EventHandler<GenericEventArgs<User>> UserCreated; - - /// <summary> - /// Occurs when [user deleted]. - /// </summary> - public event EventHandler<GenericEventArgs<User>> UserDeleted; - - /// <inheritdoc /> - public IEnumerable<User> Users => _users.Values; - - /// <inheritdoc /> - public IEnumerable<Guid> UsersIds => _users.Keys; - - /// <summary> - /// Called when [user updated]. - /// </summary> - /// <param name="user">The user.</param> - private void OnUserUpdated(User user) - { - UserUpdated?.Invoke(this, new GenericEventArgs<User>(user)); - } - - /// <summary> - /// Called when [user deleted]. - /// </summary> - /// <param name="user">The user.</param> - private void OnUserDeleted(User user) - { - UserDeleted?.Invoke(this, new GenericEventArgs<User>(user)); - } - - public NameIdPair[] GetAuthenticationProviders() - { - return _authenticationProviders - .Where(i => i.IsEnabled) - .OrderBy(i => i is DefaultAuthenticationProvider ? 0 : 1) - .ThenBy(i => i.Name) - .Select(i => new NameIdPair - { - Name = i.Name, - Id = GetAuthenticationProviderId(i) - }) - .ToArray(); - } - - public NameIdPair[] GetPasswordResetProviders() - { - return _passwordResetProviders - .Where(i => i.IsEnabled) - .OrderBy(i => i is DefaultPasswordResetProvider ? 0 : 1) - .ThenBy(i => i.Name) - .Select(i => new NameIdPair - { - Name = i.Name, - Id = GetPasswordResetProviderId(i) - }) - .ToArray(); - } - - public void AddParts(IEnumerable<IAuthenticationProvider> authenticationProviders, IEnumerable<IPasswordResetProvider> passwordResetProviders) - { - _authenticationProviders = authenticationProviders.ToArray(); - - _defaultAuthenticationProvider = _authenticationProviders.OfType<DefaultAuthenticationProvider>().First(); - - _invalidAuthProvider = _authenticationProviders.OfType<InvalidAuthProvider>().First(); - - _passwordResetProviders = passwordResetProviders.ToArray(); - - _defaultPasswordResetProvider = passwordResetProviders.OfType<DefaultPasswordResetProvider>().First(); - } - - /// <inheritdoc /> - public User GetUserById(Guid id) - { - if (id == Guid.Empty) - { - throw new ArgumentException("Guid can't be empty", nameof(id)); - } - - _users.TryGetValue(id, out User user); - return user; - } - - public User GetUserByName(string name) - { - if (string.IsNullOrWhiteSpace(name)) - { - throw new ArgumentException("Invalid username", nameof(name)); - } - - return Users.FirstOrDefault(u => string.Equals(u.Name, name, StringComparison.OrdinalIgnoreCase)); - } - - public void Initialize() - { - LoadUsers(); - - var users = Users; - - // If there are no local users with admin rights, make them all admins - if (!users.Any(i => i.Policy.IsAdministrator)) - { - foreach (var user in users) - { - user.Policy.IsAdministrator = true; - UpdateUserPolicy(user, user.Policy, false); - } - } - } - - public static bool IsValidUsername(string username) - { - // This is some regex that matches only on unicode "word" characters, as well as -, _ and @ - // In theory this will cut out most if not all 'control' characters which should help minimize any weirdness - // Usernames can contain letters (a-z + whatever else unicode is cool with), numbers (0-9), at-signs (@), dashes (-), underscores (_), apostrophes ('), and periods (.) - return Regex.IsMatch(username, @"^[\w\-'._@]*$"); - } - - private static bool IsValidUsernameCharacter(char i) - => IsValidUsername(i.ToString(CultureInfo.InvariantCulture)); - - public string MakeValidUsername(string username) - { - if (IsValidUsername(username)) - { - return username; - } - - // Usernames can contain letters (a-z), numbers (0-9), dashes (-), underscores (_), apostrophes ('), and periods (.) - var builder = new StringBuilder(); - - foreach (var c in username) - { - if (IsValidUsernameCharacter(c)) - { - builder.Append(c); - } - } - - return builder.ToString(); - } - - public async Task<User> AuthenticateUser( - string username, - string password, - string hashedPassword, - string remoteEndPoint, - bool isUserSession) - { - if (string.IsNullOrWhiteSpace(username)) - { - _logger.LogInformation("Authentication request without username has been denied (IP: {IP}).", remoteEndPoint); - throw new ArgumentNullException(nameof(username)); - } - - var user = Users.FirstOrDefault(i => string.Equals(username, i.Name, StringComparison.OrdinalIgnoreCase)); - - var success = false; - IAuthenticationProvider authenticationProvider = null; - - if (user != null) - { - var authResult = await AuthenticateLocalUser(username, password, hashedPassword, user, remoteEndPoint).ConfigureAwait(false); - authenticationProvider = authResult.authenticationProvider; - success = authResult.success; - } - else - { - // user is null - var authResult = await AuthenticateLocalUser(username, password, hashedPassword, null, remoteEndPoint).ConfigureAwait(false); - authenticationProvider = authResult.authenticationProvider; - string updatedUsername = authResult.username; - success = authResult.success; - - if (success - && authenticationProvider != null - && !(authenticationProvider is DefaultAuthenticationProvider)) - { - // Trust the username returned by the authentication provider - username = updatedUsername; - - // Search the database for the user again - // the authentication provider might have created it - user = Users - .FirstOrDefault(i => string.Equals(username, i.Name, StringComparison.OrdinalIgnoreCase)); - - if (authenticationProvider is IHasNewUserPolicy hasNewUserPolicy) - { - var policy = hasNewUserPolicy.GetNewUserPolicy(); - UpdateUserPolicy(user, policy, true); - } - } - } - - if (success && user != null && authenticationProvider != null) - { - var providerId = GetAuthenticationProviderId(authenticationProvider); - - if (!string.Equals(providerId, user.Policy.AuthenticationProviderId, StringComparison.OrdinalIgnoreCase)) - { - user.Policy.AuthenticationProviderId = providerId; - UpdateUserPolicy(user, user.Policy, true); - } - } - - if (user == null) - { - _logger.LogInformation("Authentication request for {UserName} has been denied (IP: {IP}).", username, remoteEndPoint); - throw new AuthenticationException("Invalid username or password entered."); - } - - if (user.Policy.IsDisabled) - { - _logger.LogInformation("Authentication request for {UserName} has been denied because this account is currently disabled (IP: {IP}).", username, remoteEndPoint); - throw new SecurityException($"The {user.Name} account is currently disabled. Please consult with your administrator."); - } - - if (!user.Policy.EnableRemoteAccess && !_networkManager.IsInLocalNetwork(remoteEndPoint)) - { - _logger.LogInformation("Authentication request for {UserName} forbidden: remote access disabled and user not in local network (IP: {IP}).", username, remoteEndPoint); - throw new SecurityException("Forbidden."); - } - - if (!user.IsParentalScheduleAllowed()) - { - _logger.LogInformation("Authentication request for {UserName} is not allowed at this time due parental restrictions (IP: {IP}).", username, remoteEndPoint); - throw new SecurityException("User is not allowed access at this time."); - } - - // Update LastActivityDate and LastLoginDate, then save - if (success) - { - if (isUserSession) - { - user.LastActivityDate = user.LastLoginDate = DateTime.UtcNow; - UpdateUser(user); - } - - ResetInvalidLoginAttemptCount(user); - _logger.LogInformation("Authentication request for {UserName} has succeeded.", user.Name); - } - else - { - IncrementInvalidLoginAttemptCount(user); - _logger.LogInformation("Authentication request for {UserName} has been denied (IP: {IP}).", user.Name, remoteEndPoint); - } - - return success ? user : null; - } - -#nullable enable - - private static string GetAuthenticationProviderId(IAuthenticationProvider provider) - { - return provider.GetType().FullName; - } - - private static string GetPasswordResetProviderId(IPasswordResetProvider provider) - { - return provider.GetType().FullName; - } - - private IAuthenticationProvider GetAuthenticationProvider(User user) - { - return GetAuthenticationProviders(user)[0]; - } - - private IPasswordResetProvider GetPasswordResetProvider(User user) - { - return GetPasswordResetProviders(user)[0]; - } - - private IAuthenticationProvider[] GetAuthenticationProviders(User? user) - { - var authenticationProviderId = user?.Policy.AuthenticationProviderId; - - var providers = _authenticationProviders.Where(i => i.IsEnabled).ToArray(); - - if (!string.IsNullOrEmpty(authenticationProviderId)) - { - providers = providers.Where(i => string.Equals(authenticationProviderId, GetAuthenticationProviderId(i), StringComparison.OrdinalIgnoreCase)).ToArray(); - } - - if (providers.Length == 0) - { - // Assign the user to the InvalidAuthProvider since no configured auth provider was valid/found - _logger.LogWarning("User {UserName} was found with invalid/missing Authentication Provider {AuthenticationProviderId}. Assigning user to InvalidAuthProvider until this is corrected", user?.Name, user?.Policy.AuthenticationProviderId); - providers = new IAuthenticationProvider[] { _invalidAuthProvider }; - } - - return providers; - } - - private IPasswordResetProvider[] GetPasswordResetProviders(User? user) - { - var passwordResetProviderId = user?.Policy.PasswordResetProviderId; - - var providers = _passwordResetProviders.Where(i => i.IsEnabled).ToArray(); - - if (!string.IsNullOrEmpty(passwordResetProviderId)) - { - providers = providers.Where(i => string.Equals(passwordResetProviderId, GetPasswordResetProviderId(i), StringComparison.OrdinalIgnoreCase)).ToArray(); - } - - if (providers.Length == 0) - { - providers = new IPasswordResetProvider[] { _defaultPasswordResetProvider }; - } - - return providers; - } - - private async Task<(string username, bool success)> AuthenticateWithProvider( - IAuthenticationProvider provider, - string username, - string password, - User? resolvedUser) - { - try - { - var authenticationResult = provider is IRequiresResolvedUser requiresResolvedUser - ? await requiresResolvedUser.Authenticate(username, password, resolvedUser).ConfigureAwait(false) - : await provider.Authenticate(username, password).ConfigureAwait(false); - - if (authenticationResult.Username != username) - { - _logger.LogDebug("Authentication provider provided updated username {1}", authenticationResult.Username); - username = authenticationResult.Username; - } - - return (username, true); - } - catch (AuthenticationException ex) - { - _logger.LogError(ex, "Error authenticating with provider {Provider}", provider.Name); - - return (username, false); - } - } - - private async Task<(IAuthenticationProvider? authenticationProvider, string username, bool success)> AuthenticateLocalUser( - string username, - string password, - string hashedPassword, - User? user, - string remoteEndPoint) - { - bool success = false; - IAuthenticationProvider? authenticationProvider = null; - - foreach (var provider in GetAuthenticationProviders(user)) - { - var providerAuthResult = await AuthenticateWithProvider(provider, username, password, user).ConfigureAwait(false); - var updatedUsername = providerAuthResult.username; - success = providerAuthResult.success; - - if (success) - { - authenticationProvider = provider; - username = updatedUsername; - break; - } - } - - if (!success - && _networkManager.IsInLocalNetwork(remoteEndPoint) - && user?.Configuration.EnableLocalPassword == true - && !string.IsNullOrEmpty(user.EasyPassword)) - { - // Check easy password - var passwordHash = PasswordHash.Parse(user.EasyPassword); - var hash = _cryptoProvider.ComputeHash( - passwordHash.Id, - Encoding.UTF8.GetBytes(password), - passwordHash.Salt.ToArray()); - success = passwordHash.Hash.SequenceEqual(hash); - } - - return (authenticationProvider, username, success); - } - - private void ResetInvalidLoginAttemptCount(User user) - { - user.Policy.InvalidLoginAttemptCount = 0; - UpdateUserPolicy(user, user.Policy, false); - } - - private void IncrementInvalidLoginAttemptCount(User user) - { - int invalidLogins = ++user.Policy.InvalidLoginAttemptCount; - int maxInvalidLogins = user.Policy.LoginAttemptsBeforeLockout; - if (maxInvalidLogins > 0 - && invalidLogins >= maxInvalidLogins) - { - user.Policy.IsDisabled = true; - UserLockedOut?.Invoke(this, new GenericEventArgs<User>(user)); - _logger.LogWarning( - "Disabling user {UserName} due to {Attempts} unsuccessful login attempts.", - user.Name, - invalidLogins); - } - - UpdateUserPolicy(user, user.Policy, false); - } - - /// <summary> - /// Loads the users from the repository. - /// </summary> - private void LoadUsers() - { - var users = _userRepository.RetrieveAllUsers(); - - // There always has to be at least one user. - if (users.Count != 0) - { - _users = new ConcurrentDictionary<Guid, User>( - users.Select(x => new KeyValuePair<Guid, User>(x.Id, x))); - return; - } - - var defaultName = Environment.UserName; - if (string.IsNullOrWhiteSpace(defaultName)) - { - defaultName = "MyJellyfinUser"; - } - - _logger.LogWarning("No users, creating one with username {UserName}", defaultName); - - var name = MakeValidUsername(defaultName); - - var user = InstantiateNewUser(name); - - user.DateLastSaved = DateTime.UtcNow; - - _userRepository.CreateUser(user); - - user.Policy.IsAdministrator = true; - user.Policy.EnableContentDeletion = true; - user.Policy.EnableRemoteControlOfOtherUsers = true; - UpdateUserPolicy(user, user.Policy, false); - - _users = new ConcurrentDictionary<Guid, User>(); - _users[user.Id] = user; - } - -#nullable restore - - public UserDto GetUserDto(User user, string remoteEndPoint = null) - { - if (user == null) - { - throw new ArgumentNullException(nameof(user)); - } - - bool hasConfiguredPassword = GetAuthenticationProvider(user).HasPassword(user); - bool hasConfiguredEasyPassword = !string.IsNullOrEmpty(GetAuthenticationProvider(user).GetEasyPasswordHash(user)); - - bool hasPassword = user.Configuration.EnableLocalPassword && !string.IsNullOrEmpty(remoteEndPoint) && _networkManager.IsInLocalNetwork(remoteEndPoint) ? - hasConfiguredEasyPassword : - hasConfiguredPassword; - - UserDto dto = new UserDto - { - Id = user.Id, - Name = user.Name, - HasPassword = hasPassword, - HasConfiguredPassword = hasConfiguredPassword, - HasConfiguredEasyPassword = hasConfiguredEasyPassword, - LastActivityDate = user.LastActivityDate, - LastLoginDate = user.LastLoginDate, - Configuration = user.Configuration, - ServerId = _appHost.SystemId, - Policy = user.Policy - }; - - if (!hasPassword && _users.Count == 1) - { - dto.EnableAutoLogin = true; - } - - ItemImageInfo image = user.GetImageInfo(ImageType.Primary, 0); - - if (image != null) - { - dto.PrimaryImageTag = GetImageCacheTag(user, image); - - try - { - DtoService.AttachPrimaryImageAspectRatio(dto, user); - } - catch (Exception ex) - { - // Have to use a catch-all unfortunately because some .net image methods throw plain Exceptions - _logger.LogError(ex, "Error generating PrimaryImageAspectRatio for {User}", user.Name); - } - } - - return dto; - } - - public UserDto GetOfflineUserDto(User user) - { - var dto = GetUserDto(user); - - dto.ServerName = _appHost.FriendlyName; - - return dto; - } - - private string GetImageCacheTag(BaseItem item, ItemImageInfo image) - { - try - { - return _imageProcessor.GetImageCacheTag(item, image); - } - catch (Exception ex) - { - _logger.LogError(ex, "Error getting {ImageType} image info for {ImagePath}", image.Type, image.Path); - return null; - } - } - - /// <summary> - /// Refreshes metadata for each user - /// </summary> - /// <param name="cancellationToken">The cancellation token.</param> - /// <returns>Task.</returns> - public async Task RefreshUsersMetadata(CancellationToken cancellationToken) - { - foreach (var user in Users) - { - await user.RefreshMetadata(new MetadataRefreshOptions(new DirectoryService(_fileSystem)), cancellationToken).ConfigureAwait(false); - } - } - - /// <summary> - /// Renames the user. - /// </summary> - /// <param name="user">The user.</param> - /// <param name="newName">The new name.</param> - /// <returns>Task.</returns> - /// <exception cref="ArgumentNullException">user</exception> - /// <exception cref="ArgumentException"></exception> - public async Task RenameUser(User user, string newName) - { - if (user == null) - { - throw new ArgumentNullException(nameof(user)); - } - - if (string.IsNullOrWhiteSpace(newName)) - { - throw new ArgumentException("Invalid username", nameof(newName)); - } - - if (user.Name.Equals(newName, StringComparison.Ordinal)) - { - throw new ArgumentException("The new and old names must be different."); - } - - if (Users.Any( - u => u.Id != user.Id && u.Name.Equals(newName, StringComparison.OrdinalIgnoreCase))) - { - throw new ArgumentException(string.Format( - CultureInfo.InvariantCulture, - "A user with the name '{0}' already exists.", - newName)); - } - - await user.Rename(newName).ConfigureAwait(false); - - OnUserUpdated(user); - } - - /// <summary> - /// Updates the user. - /// </summary> - /// <param name="user">The user.</param> - /// <exception cref="ArgumentNullException">user</exception> - /// <exception cref="ArgumentException"></exception> - public void UpdateUser(User user) - { - if (user == null) - { - throw new ArgumentNullException(nameof(user)); - } - - if (user.Id == Guid.Empty) - { - throw new ArgumentException("Id can't be empty.", nameof(user)); - } - - if (!_users.ContainsKey(user.Id)) - { - throw new ArgumentException( - string.Format( - CultureInfo.InvariantCulture, - "A user '{0}' with Id {1} does not exist.", - user.Name, - user.Id), - nameof(user)); - } - - user.DateModified = DateTime.UtcNow; - user.DateLastSaved = DateTime.UtcNow; - - _userRepository.UpdateUser(user); - - OnUserUpdated(user); - } - - /// <summary> - /// Creates the user. - /// </summary> - /// <param name="name">The name.</param> - /// <returns>User.</returns> - /// <exception cref="ArgumentNullException">name</exception> - /// <exception cref="ArgumentException"></exception> - public User CreateUser(string name) - { - if (string.IsNullOrWhiteSpace(name)) - { - throw new ArgumentNullException(nameof(name)); - } - - if (!IsValidUsername(name)) - { - throw new ArgumentException("Usernames can contain unicode symbols, numbers (0-9), dashes (-), underscores (_), apostrophes ('), and periods (.)"); - } - - if (Users.Any(u => u.Name.Equals(name, StringComparison.OrdinalIgnoreCase))) - { - throw new ArgumentException(string.Format("A user with the name '{0}' already exists.", name)); - } - - var user = InstantiateNewUser(name); - - _users[user.Id] = user; - - user.DateLastSaved = DateTime.UtcNow; - - _userRepository.CreateUser(user); - - EventHelper.QueueEventIfNotNull(UserCreated, this, new GenericEventArgs<User>(user), _logger); - - return user; - } - - /// <inheritdoc /> - /// <exception cref="ArgumentNullException">The <c>user</c> is <c>null</c>.</exception> - /// <exception cref="ArgumentException">The <c>user</c> doesn't exist, or is the last administrator.</exception> - /// <exception cref="InvalidOperationException">The <c>user</c> can't be deleted; there are no other users.</exception> - public void DeleteUser(User user) - { - if (user == null) - { - throw new ArgumentNullException(nameof(user)); - } - - if (!_users.ContainsKey(user.Id)) - { - throw new ArgumentException(string.Format( - CultureInfo.InvariantCulture, - "The user cannot be deleted because there is no user with the Name {0} and Id {1}.", - user.Name, - user.Id)); - } - - if (_users.Count == 1) - { - throw new InvalidOperationException(string.Format( - CultureInfo.InvariantCulture, - "The user '{0}' cannot be deleted because there must be at least one user in the system.", - user.Name)); - } - - if (user.Policy.IsAdministrator - && Users.Count(i => i.Policy.IsAdministrator) == 1) - { - throw new ArgumentException( - string.Format( - CultureInfo.InvariantCulture, - "The user '{0}' cannot be deleted because there must be at least one admin user in the system.", - user.Name), - nameof(user)); - } - - var configPath = GetConfigurationFilePath(user); - - _userRepository.DeleteUser(user); - - // Delete user config dir - lock (_configSyncLock) - lock (_policySyncLock) - { - try - { - Directory.Delete(user.ConfigurationDirectoryPath, true); - } - catch (IOException ex) - { - _logger.LogError(ex, "Error deleting user config dir: {Path}", user.ConfigurationDirectoryPath); - } - } - - _users.TryRemove(user.Id, out _); - - OnUserDeleted(user); - } - - /// <summary> - /// Resets the password by clearing it. - /// </summary> - /// <returns>Task.</returns> - public Task ResetPassword(User user) - { - return ChangePassword(user, string.Empty); - } - - public void ResetEasyPassword(User user) - { - ChangeEasyPassword(user, string.Empty, null); - } - - public async Task ChangePassword(User user, string newPassword) - { - if (user == null) - { - throw new ArgumentNullException(nameof(user)); - } - - await GetAuthenticationProvider(user).ChangePassword(user, newPassword).ConfigureAwait(false); - - UpdateUser(user); - - UserPasswordChanged?.Invoke(this, new GenericEventArgs<User>(user)); - } - - public void ChangeEasyPassword(User user, string newPassword, string newPasswordHash) - { - if (user == null) - { - throw new ArgumentNullException(nameof(user)); - } - - GetAuthenticationProvider(user).ChangeEasyPassword(user, newPassword, newPasswordHash); - - UpdateUser(user); - - UserPasswordChanged?.Invoke(this, new GenericEventArgs<User>(user)); - } - - /// <summary> - /// Instantiates the new user. - /// </summary> - /// <param name="name">The name.</param> - /// <returns>User.</returns> - private static User InstantiateNewUser(string name) - { - return new User - { - Name = name, - Id = Guid.NewGuid(), - DateCreated = DateTime.UtcNow, - DateModified = DateTime.UtcNow - }; - } - - public async Task<ForgotPasswordResult> StartForgotPasswordProcess(string enteredUsername, bool isInNetwork) - { - var user = string.IsNullOrWhiteSpace(enteredUsername) ? - null : - GetUserByName(enteredUsername); - - var action = ForgotPasswordAction.InNetworkRequired; - - if (user != null && isInNetwork) - { - var passwordResetProvider = GetPasswordResetProvider(user); - return await passwordResetProvider.StartForgotPasswordProcess(user, isInNetwork).ConfigureAwait(false); - } - else - { - return new ForgotPasswordResult - { - Action = action, - PinFile = string.Empty - }; - } - } - - public async Task<PinRedeemResult> RedeemPasswordResetPin(string pin) - { - foreach (var provider in _passwordResetProviders) - { - var result = await provider.RedeemPasswordResetPin(pin).ConfigureAwait(false); - if (result.Success) - { - return result; - } - } - - return new PinRedeemResult - { - Success = false, - UsersReset = Array.Empty<string>() - }; - } - - public UserPolicy GetUserPolicy(User user) - { - var path = GetPolicyFilePath(user); - if (!File.Exists(path)) - { - return GetDefaultPolicy(); - } - - try - { - lock (_policySyncLock) - { - return (UserPolicy)_xmlSerializer.DeserializeFromFile(typeof(UserPolicy), path); - } - } - catch (Exception ex) - { - _logger.LogError(ex, "Error reading policy file: {Path}", path); - - return GetDefaultPolicy(); - } - } - - private static UserPolicy GetDefaultPolicy() - { - return new UserPolicy - { - EnableContentDownloading = true, - EnableSyncTranscoding = true - }; - } - - public void UpdateUserPolicy(Guid userId, UserPolicy userPolicy) - { - var user = GetUserById(userId); - UpdateUserPolicy(user, userPolicy, true); - } - - private void UpdateUserPolicy(User user, UserPolicy userPolicy, bool fireEvent) - { - // The xml serializer will output differently if the type is not exact - if (userPolicy.GetType() != typeof(UserPolicy)) - { - var json = _jsonSerializer.SerializeToString(userPolicy); - userPolicy = _jsonSerializer.DeserializeFromString<UserPolicy>(json); - } - - var path = GetPolicyFilePath(user); - - Directory.CreateDirectory(Path.GetDirectoryName(path)); - - lock (_policySyncLock) - { - _xmlSerializer.SerializeToFile(userPolicy, path); - user.Policy = userPolicy; - } - - if (fireEvent) - { - UserPolicyUpdated?.Invoke(this, new GenericEventArgs<User>(user)); - } - } - - private static string GetPolicyFilePath(User user) - { - return Path.Combine(user.ConfigurationDirectoryPath, "policy.xml"); - } - - private static string GetConfigurationFilePath(User user) - { - return Path.Combine(user.ConfigurationDirectoryPath, "config.xml"); - } - - public UserConfiguration GetUserConfiguration(User user) - { - var path = GetConfigurationFilePath(user); - - if (!File.Exists(path)) - { - return new UserConfiguration(); - } - - try - { - lock (_configSyncLock) - { - return (UserConfiguration)_xmlSerializer.DeserializeFromFile(typeof(UserConfiguration), path); - } - } - catch (Exception ex) - { - _logger.LogError(ex, "Error reading policy file: {Path}", path); - - return new UserConfiguration(); - } - } - - public void UpdateConfiguration(Guid userId, UserConfiguration config) - { - var user = GetUserById(userId); - UpdateConfiguration(user, config); - } - - public void UpdateConfiguration(User user, UserConfiguration config) - { - UpdateConfiguration(user, config, true); - } - - private void UpdateConfiguration(User user, UserConfiguration config, bool fireEvent) - { - var path = GetConfigurationFilePath(user); - - // The xml serializer will output differently if the type is not exact - if (config.GetType() != typeof(UserConfiguration)) - { - var json = _jsonSerializer.SerializeToString(config); - config = _jsonSerializer.DeserializeFromString<UserConfiguration>(json); - } - - Directory.CreateDirectory(Path.GetDirectoryName(path)); - - lock (_configSyncLock) - { - _xmlSerializer.SerializeToFile(config, path); - user.Configuration = config; - } - - if (fireEvent) - { - UserConfigurationUpdated?.Invoke(this, new GenericEventArgs<User>(user)); - } - } - } - - public class DeviceAccessEntryPoint : IServerEntryPoint - { - private IUserManager _userManager; - private IAuthenticationRepository _authRepo; - private IDeviceManager _deviceManager; - private ISessionManager _sessionManager; - - public DeviceAccessEntryPoint(IUserManager userManager, IAuthenticationRepository authRepo, IDeviceManager deviceManager, ISessionManager sessionManager) - { - _userManager = userManager; - _authRepo = authRepo; - _deviceManager = deviceManager; - _sessionManager = sessionManager; - } - - public Task RunAsync() - { - _userManager.UserPolicyUpdated += _userManager_UserPolicyUpdated; - - return Task.CompletedTask; - } - - private void _userManager_UserPolicyUpdated(object sender, GenericEventArgs<User> e) - { - var user = e.Argument; - if (!user.Policy.EnableAllDevices) - { - UpdateDeviceAccess(user); - } - } - - private void UpdateDeviceAccess(User user) - { - var existing = _authRepo.Get(new AuthenticationInfoQuery - { - UserId = user.Id - - }).Items; - - foreach (var authInfo in existing) - { - if (!string.IsNullOrEmpty(authInfo.DeviceId) && !_deviceManager.CanAccessDevice(user, authInfo.DeviceId)) - { - _sessionManager.Logout(authInfo); - } - } - } - - public void Dispose() - { - - } - } -} diff --git a/Emby.Server.Implementations/Library/UserViewManager.cs b/Emby.Server.Implementations/Library/UserViewManager.cs index 322819b05..f51657c63 100644 --- a/Emby.Server.Implementations/Library/UserViewManager.cs +++ b/Emby.Server.Implementations/Library/UserViewManager.cs @@ -5,6 +5,8 @@ using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Threading; +using Jellyfin.Data.Entities; +using Jellyfin.Data.Enums; using MediaBrowser.Controller.Channels; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Dto; @@ -17,6 +19,8 @@ using MediaBrowser.Model.Entities; using MediaBrowser.Model.Globalization; using MediaBrowser.Model.Library; using MediaBrowser.Model.Querying; +using Genre = MediaBrowser.Controller.Entities.Genre; +using Person = MediaBrowser.Controller.Entities.Person; namespace Emby.Server.Implementations.Library { @@ -125,12 +129,12 @@ namespace Emby.Server.Implementations.Library if (!query.IncludeHidden) { - list = list.Where(i => !user.Configuration.MyMediaExcludes.Contains(i.Id.ToString("N", CultureInfo.InvariantCulture))).ToList(); + list = list.Where(i => !user.GetPreference(PreferenceKind.MyMediaExcludes).Contains(i.Id.ToString("N", CultureInfo.InvariantCulture))).ToList(); } var sorted = _libraryManager.Sort(list, user, new[] { ItemSortBy.SortName }, SortOrder.Ascending).ToList(); - var orders = user.Configuration.OrderedViews.ToList(); + var orders = user.GetPreference(PreferenceKind.OrderedViews).ToList(); return list .OrderBy(i => @@ -165,7 +169,13 @@ namespace Emby.Server.Implementations.Library return GetUserSubViewWithName(name, parentId, type, sortName); } - private Folder GetUserView(List<ICollectionFolder> parents, string viewType, string localizationKey, string sortName, User user, string[] presetViews) + private Folder GetUserView( + List<ICollectionFolder> parents, + string viewType, + string localizationKey, + string sortName, + Jellyfin.Data.Entities.User user, + string[] presetViews) { if (parents.Count == 1 && parents.All(i => string.Equals(i.CollectionType, viewType, StringComparison.OrdinalIgnoreCase))) { @@ -270,7 +280,8 @@ namespace Emby.Server.Implementations.Library { parents = _libraryManager.GetUserRootFolder().GetChildren(user, true) .Where(i => i is Folder) - .Where(i => !user.Configuration.LatestItemsExcludes.Contains(i.Id.ToString("N", CultureInfo.InvariantCulture))) + .Where(i => !user.GetPreference(PreferenceKind.LatestItemExcludes) + .Contains(i.Id.ToString("N", CultureInfo.InvariantCulture))) .ToList(); } @@ -331,12 +342,11 @@ namespace Emby.Server.Implementations.Library var excludeItemTypes = includeItemTypes.Length == 0 && mediaTypes.Count == 0 ? new[] { - typeof(Person).Name, - typeof(Studio).Name, - typeof(Year).Name, - typeof(MusicGenre).Name, - typeof(Genre).Name - + nameof(Person), + nameof(Studio), + nameof(Year), + nameof(MusicGenre), + nameof(Genre) } : Array.Empty<string>(); var query = new InternalItemsQuery(user) diff --git a/Emby.Server.Implementations/Library/Validators/ArtistsValidator.cs b/Emby.Server.Implementations/Library/Validators/ArtistsValidator.cs index 8a6bd5e78..d4c8c35e6 100644 --- a/Emby.Server.Implementations/Library/Validators/ArtistsValidator.cs +++ b/Emby.Server.Implementations/Library/Validators/ArtistsValidator.cs @@ -98,7 +98,6 @@ namespace Emby.Server.Implementations.Library.Validators _libraryManager.DeleteItem(item, new DeleteOptions { DeleteFileLocation = false - }, false); } diff --git a/Emby.Server.Implementations/Library/Validators/StudiosValidator.cs b/Emby.Server.Implementations/Library/Validators/StudiosValidator.cs index 7a6cd11df..ca35adfff 100644 --- a/Emby.Server.Implementations/Library/Validators/StudiosValidator.cs +++ b/Emby.Server.Implementations/Library/Validators/StudiosValidator.cs @@ -92,7 +92,6 @@ namespace Emby.Server.Implementations.Library.Validators _libraryManager.DeleteItem(item, new DeleteOptions { DeleteFileLocation = false - }, false); } diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs index 9fb5aec29..7b0fcbc9e 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs @@ -1547,7 +1547,6 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV IsFolder = false, Recursive = true, DtoOptions = new DtoOptions(true) - }) .Where(i => i.IsFileProtocol && File.Exists(i.Path)) .Skip(seriesTimer.KeepUpTo - 1) diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs index 70dd8f321..d8ec107ec 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs @@ -183,7 +183,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV var subtitleArgs = CopySubtitles ? " -codec:s copy" : " -sn"; - //var outputParam = string.Equals(Path.GetExtension(targetFile), ".mp4", StringComparison.OrdinalIgnoreCase) ? + // var outputParam = string.Equals(Path.GetExtension(targetFile), ".mp4", StringComparison.OrdinalIgnoreCase) ? // " -f mp4 -movflags frag_keyframe+empty_moov" : // string.Empty; @@ -206,13 +206,13 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV { return "-codec:a:0 copy"; - //var audioChannels = 2; - //var audioStream = mediaStreams.FirstOrDefault(i => i.Type == MediaStreamType.Audio); - //if (audioStream != null) + // var audioChannels = 2; + // var audioStream = mediaStreams.FirstOrDefault(i => i.Type == MediaStreamType.Audio); + // if (audioStream != null) //{ // audioChannels = audioStream.Channels ?? audioChannels; //} - //return "-codec:a:0 aac -strict experimental -ab 320000"; + // return "-codec:a:0 aac -strict experimental -ab 320000"; } private static bool EncodeVideo(MediaSourceInfo mediaSource) diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/RecordingHelper.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/RecordingHelper.cs index 0b0ff6cb3..142c59542 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/RecordingHelper.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/RecordingHelper.cs @@ -56,7 +56,6 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV name += " " + info.EpisodeTitle; } } - else if (info.IsMovie && info.ProductionYear != null) { name += " (" + info.ProductionYear + ")"; diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs index a65b3ee03..3709f8fe4 100644 --- a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs +++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs @@ -145,7 +145,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings var programsInfo = new List<ProgramInfo>(); foreach (ScheduleDirect.Program schedule in dailySchedules.SelectMany(d => d.programs)) { - //_logger.LogDebug("Proccesing Schedule for statio ID " + stationID + + // _logger.LogDebug("Proccesing Schedule for statio ID " + stationID + // " which corresponds to channel " + channelNumber + " and program id " + // schedule.programID + " which says it has images? " + // programDict[schedule.programID].hasImageArtwork); @@ -178,7 +178,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings programEntry.backdropImage = GetProgramImage(ApiUrl, imagesWithoutText, true, WideAspect); - //programEntry.bannerImage = GetProgramImage(ApiUrl, data, "Banner", false) ?? + // programEntry.bannerImage = GetProgramImage(ApiUrl, data, "Banner", false) ?? // GetProgramImage(ApiUrl, data, "Banner-L1", false) ?? // GetProgramImage(ApiUrl, data, "Banner-LO", false) ?? // GetProgramImage(ApiUrl, data, "Banner-LOT", false); @@ -212,6 +212,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings { channelNumber = map.channel; } + if (string.IsNullOrWhiteSpace(channelNumber)) { channelNumber = map.atscMajor + "." + map.atscMinor; @@ -276,7 +277,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings CommunityRating = null, EpisodeTitle = episodeTitle, Audio = audioType, - //IsNew = programInfo.@new ?? false, + // IsNew = programInfo.@new ?? false, IsRepeat = programInfo.@new == null, IsSeries = string.Equals(details.entityType, "episode", StringComparison.OrdinalIgnoreCase), ImageUrl = details.primaryImage, @@ -400,6 +401,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings { date = DateTime.SpecifyKind(date, DateTimeKind.Utc); } + return date; } @@ -622,6 +624,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings _lastErrorResponse = DateTime.UtcNow; } } + throw; } finally @@ -701,7 +704,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings CancellationToken = cancellationToken, LogErrorResponseBody = true }; - //_logger.LogInformation("Obtaining token from Schedules Direct from addres: " + httpOptions.Url + " with body " + + // _logger.LogInformation("Obtaining token from Schedules Direct from addres: " + httpOptions.Url + " with body " + // httpOptions.RequestContent); using (var response = await Post(httpOptions, false, null).ConfigureAwait(false)) @@ -805,11 +808,13 @@ namespace Emby.Server.Implementations.LiveTv.Listings { throw new ArgumentException("Username is required"); } + if (string.IsNullOrEmpty(info.Password)) { throw new ArgumentException("Password is required"); } } + if (validateListings) { if (string.IsNullOrEmpty(info.ListingsId)) @@ -932,24 +937,35 @@ namespace Emby.Server.Implementations.LiveTv.Listings public class Token { public int code { get; set; } + public string message { get; set; } + public string serverID { get; set; } + public string token { get; set; } } + public class Lineup { public string lineup { get; set; } + public string name { get; set; } + public string transport { get; set; } + public string location { get; set; } + public string uri { get; set; } } public class Lineups { public int code { get; set; } + public string serverID { get; set; } + public string datetime { get; set; } + public List<Lineup> lineups { get; set; } } @@ -957,8 +973,11 @@ namespace Emby.Server.Implementations.LiveTv.Listings public class Headends { public string headend { get; set; } + public string transport { get; set; } + public string location { get; set; } + public List<Lineup> lineups { get; set; } } @@ -967,59 +986,83 @@ namespace Emby.Server.Implementations.LiveTv.Listings public class Map { public string stationID { get; set; } + public string channel { get; set; } + public string logicalChannelNumber { get; set; } + public int uhfVhf { get; set; } + public int atscMajor { get; set; } + public int atscMinor { get; set; } } public class Broadcaster { public string city { get; set; } + public string state { get; set; } + public string postalcode { get; set; } + public string country { get; set; } } public class Logo { public string URL { get; set; } + public int height { get; set; } + public int width { get; set; } + public string md5 { get; set; } } public class Station { public string stationID { get; set; } + public string name { get; set; } + public string callsign { get; set; } + public List<string> broadcastLanguage { get; set; } + public List<string> descriptionLanguage { get; set; } + public Broadcaster broadcaster { get; set; } + public string affiliate { get; set; } + public Logo logo { get; set; } + public bool? isCommercialFree { get; set; } } public class Metadata { public string lineup { get; set; } + public string modified { get; set; } + public string transport { get; set; } } public class Channel { public List<Map> map { get; set; } + public List<Station> stations { get; set; } + public Metadata metadata { get; set; } } public class RequestScheduleForChannel { public string stationID { get; set; } + public List<string> date { get; set; } } @@ -1029,29 +1072,43 @@ namespace Emby.Server.Implementations.LiveTv.Listings public class Rating { public string body { get; set; } + public string code { get; set; } } public class Multipart { public int partNumber { get; set; } + public int totalParts { get; set; } } public class Program { public string programID { get; set; } + public string airDateTime { get; set; } + public int duration { get; set; } + public string md5 { get; set; } + public List<string> audioProperties { get; set; } + public List<string> videoProperties { get; set; } + public List<Rating> ratings { get; set; } + public bool? @new { get; set; } + public Multipart multipart { get; set; } + public string liveTapeDelay { get; set; } + public bool premiere { get; set; } + public bool repeat { get; set; } + public string isPremiereOrFinale { get; set; } } @@ -1060,16 +1117,22 @@ namespace Emby.Server.Implementations.LiveTv.Listings public class MetadataSchedule { public string modified { get; set; } + public string md5 { get; set; } + public string startDate { get; set; } + public string endDate { get; set; } + public int days { get; set; } } public class Day { public string stationID { get; set; } + public List<Program> programs { get; set; } + public MetadataSchedule metadata { get; set; } public Day() @@ -1092,24 +1155,28 @@ namespace Emby.Server.Implementations.LiveTv.Listings public class Description100 { public string descriptionLanguage { get; set; } + public string description { get; set; } } public class Description1000 { public string descriptionLanguage { get; set; } + public string description { get; set; } } public class DescriptionsProgram { public List<Description100> description100 { get; set; } + public List<Description1000> description1000 { get; set; } } public class Gracenote { public int season { get; set; } + public int episode { get; set; } } @@ -1121,104 +1188,154 @@ namespace Emby.Server.Implementations.LiveTv.Listings public class ContentRating { public string body { get; set; } + public string code { get; set; } } public class Cast { public string billingOrder { get; set; } + public string role { get; set; } + public string nameId { get; set; } + public string personId { get; set; } + public string name { get; set; } + public string characterName { get; set; } } public class Crew { public string billingOrder { get; set; } + public string role { get; set; } + public string nameId { get; set; } + public string personId { get; set; } + public string name { get; set; } } public class QualityRating { public string ratingsBody { get; set; } + public string rating { get; set; } + public string minRating { get; set; } + public string maxRating { get; set; } + public string increment { get; set; } } public class Movie { public string year { get; set; } + public int duration { get; set; } + public List<QualityRating> qualityRating { get; set; } } public class Recommendation { public string programID { get; set; } + public string title120 { get; set; } } public class ProgramDetails { public string audience { get; set; } + public string programID { get; set; } + public List<Title> titles { get; set; } + public EventDetails eventDetails { get; set; } + public DescriptionsProgram descriptions { get; set; } + public string originalAirDate { get; set; } + public List<string> genres { get; set; } + public string episodeTitle150 { get; set; } + public List<MetadataPrograms> metadata { get; set; } + public List<ContentRating> contentRating { get; set; } + public List<Cast> cast { get; set; } + public List<Crew> crew { get; set; } + public string entityType { get; set; } + public string showType { get; set; } + public bool hasImageArtwork { get; set; } + public string primaryImage { get; set; } + public string thumbImage { get; set; } + public string backdropImage { get; set; } + public string bannerImage { get; set; } + public string imageID { get; set; } + public string md5 { get; set; } + public List<string> contentAdvisory { get; set; } + public Movie movie { get; set; } + public List<Recommendation> recommendations { get; set; } } public class Caption { public string content { get; set; } + public string lang { get; set; } } public class ImageData { public string width { get; set; } + public string height { get; set; } + public string uri { get; set; } + public string size { get; set; } + public string aspect { get; set; } + public string category { get; set; } + public string text { get; set; } + public string primary { get; set; } + public string tier { get; set; } + public Caption caption { get; set; } } public class ShowImages { public string programID { get; set; } + public List<ImageData> data { get; set; } } - } } } diff --git a/Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs b/Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs index 077b5c7e5..0a93c4674 100644 --- a/Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs +++ b/Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs @@ -224,6 +224,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings { uniqueString = "-" + programInfo.SeasonNumber.Value.ToString(CultureInfo.InvariantCulture); } + if (programInfo.EpisodeNumber.HasValue) { uniqueString = "-" + programInfo.EpisodeNumber.Value.ToString(CultureInfo.InvariantCulture); diff --git a/Emby.Server.Implementations/LiveTv/LiveTvManager.cs b/Emby.Server.Implementations/LiveTv/LiveTvManager.cs index a88e0f31f..4c1de3bcc 100644 --- a/Emby.Server.Implementations/LiveTv/LiveTvManager.cs +++ b/Emby.Server.Implementations/LiveTv/LiveTvManager.cs @@ -7,6 +7,8 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; using Emby.Server.Implementations.Library; +using Jellyfin.Data.Entities; +using Jellyfin.Data.Enums; using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Progress; @@ -14,8 +16,6 @@ using MediaBrowser.Controller.Channels; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Entities.Movies; -using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.LiveTv; using MediaBrowser.Controller.Persistence; @@ -31,6 +31,8 @@ using MediaBrowser.Model.Querying; using MediaBrowser.Model.Serialization; using MediaBrowser.Model.Tasks; using Microsoft.Extensions.Logging; +using Episode = MediaBrowser.Controller.Entities.TV.Episode; +using Movie = MediaBrowser.Controller.Entities.Movies.Movie; namespace Emby.Server.Implementations.LiveTv { @@ -404,8 +406,8 @@ namespace Emby.Server.Implementations.LiveTv if (!(service is EmbyTV.EmbyTV)) { // We can't trust that we'll be able to direct stream it through emby server, no matter what the provider says - //mediaSource.SupportsDirectPlay = false; - //mediaSource.SupportsDirectStream = false; + // mediaSource.SupportsDirectPlay = false; + // mediaSource.SupportsDirectStream = false; mediaSource.SupportsTranscoding = true; foreach (var stream in mediaSource.MediaStreams) { @@ -554,9 +556,10 @@ namespace Emby.Server.Implementations.LiveTv { forceUpdate = true; } + item.ParentId = channel.Id; - //item.ChannelType = channelType; + // item.ChannelType = channelType; item.Audio = info.Audio; item.ChannelId = channel.Id; @@ -573,6 +576,7 @@ namespace Emby.Server.Implementations.LiveTv { forceUpdate = true; } + item.ExternalSeriesId = seriesId; var isSeries = info.IsSeries || !string.IsNullOrEmpty(info.EpisodeTitle); @@ -587,30 +591,37 @@ namespace Emby.Server.Implementations.LiveTv { tags.Add("Live"); } + if (info.IsPremiere) { tags.Add("Premiere"); } + if (info.IsNews) { tags.Add("News"); } + if (info.IsSports) { tags.Add("Sports"); } + if (info.IsKids) { tags.Add("Kids"); } + if (info.IsRepeat) { tags.Add("Repeat"); } + if (info.IsMovie) { tags.Add("Movie"); } + if (isSeries) { tags.Add("Series"); @@ -633,6 +644,7 @@ namespace Emby.Server.Implementations.LiveTv { forceUpdate = true; } + item.IsSeries = isSeries; item.Name = info.Name; @@ -650,12 +662,14 @@ namespace Emby.Server.Implementations.LiveTv { forceUpdate = true; } + item.StartDate = info.StartDate; if (item.EndDate != info.EndDate) { forceUpdate = true; } + item.EndDate = info.EndDate; item.ProductionYear = info.ProductionYear; @@ -696,7 +710,6 @@ namespace Emby.Server.Implementations.LiveTv { Path = info.ThumbImageUrl, Type = ImageType.Thumb - }, 0); } } @@ -709,7 +722,6 @@ namespace Emby.Server.Implementations.LiveTv { Path = info.LogoImageUrl, Type = ImageType.Logo - }, 0); } } @@ -722,7 +734,6 @@ namespace Emby.Server.Implementations.LiveTv { Path = info.BackdropImageUrl, Type = ImageType.Backdrop - }, 0); } } @@ -760,7 +771,8 @@ namespace Emby.Server.Implementations.LiveTv var dto = _dtoService.GetBaseItemDto(program, new DtoOptions(), user); - var list = new List<Tuple<BaseItemDto, string, string>>() { + var list = new List<Tuple<BaseItemDto, string, string>> + { new Tuple<BaseItemDto, string, string>(dto, program.ExternalId, program.ExternalSeriesId) }; @@ -1168,7 +1180,6 @@ namespace Emby.Server.Implementations.LiveTv IncludeItemTypes = new string[] { typeof(LiveTvProgram).Name }, ChannelIds = new Guid[] { currentChannel.Id }, DtoOptions = new DtoOptions(true) - }).Cast<LiveTvProgram>().ToDictionary(i => i.Id); var newPrograms = new List<LiveTvProgram>(); @@ -1368,10 +1379,10 @@ namespace Emby.Server.Implementations.LiveTv // limit = (query.Limit ?? 10) * 2; limit = null; - //var allActivePaths = EmbyTV.EmbyTV.Current.GetAllActiveRecordings().Select(i => i.Path).ToArray(); - //var items = allActivePaths.Select(i => _libraryManager.FindByPath(i, false)).Where(i => i != null).ToArray(); + // var allActivePaths = EmbyTV.EmbyTV.Current.GetAllActiveRecordings().Select(i => i.Path).ToArray(); + // var items = allActivePaths.Select(i => _libraryManager.FindByPath(i, false)).Where(i => i != null).ToArray(); - //return new QueryResult<BaseItem> + // return new QueryResult<BaseItem> //{ // Items = items, // TotalRecordCount = items.Length @@ -1738,7 +1749,6 @@ namespace Emby.Server.Implementations.LiveTv var results = await GetTimers(new TimerQuery { Id = id - }, cancellationToken).ConfigureAwait(false); return results.Items.FirstOrDefault(i => string.Equals(i.Id, id, StringComparison.OrdinalIgnoreCase)); @@ -1790,7 +1800,6 @@ namespace Emby.Server.Implementations.LiveTv .Select(i => { return i.Item1; - }) .ToArray(); @@ -1845,7 +1854,6 @@ namespace Emby.Server.Implementations.LiveTv } return _tvDtoService.GetSeriesTimerInfoDto(i.Item1, i.Item2, channelName); - }) .ToArray(); @@ -1878,7 +1886,6 @@ namespace Emby.Server.Implementations.LiveTv OrderBy = new[] { (ItemSortBy.StartDate, SortOrder.Ascending) }, TopParentIds = new[] { GetInternalLiveTvFolder(CancellationToken.None).Id }, DtoOptions = options - }) : new List<BaseItem>(); RemoveFields(options); @@ -1956,7 +1963,7 @@ namespace Emby.Server.Implementations.LiveTv OriginalAirDate = program.PremiereDate, Overview = program.Overview, StartDate = program.StartDate, - //ImagePath = program.ExternalImagePath, + // ImagePath = program.ExternalImagePath, Name = program.Name, OfficialRating = program.OfficialRating }; @@ -2167,20 +2174,19 @@ namespace Emby.Server.Implementations.LiveTv var info = new LiveTvInfo { Services = services, - IsEnabled = services.Length > 0 + IsEnabled = services.Length > 0, + EnabledUsers = _userManager.Users + .Where(IsLiveTvEnabled) + .Select(i => i.Id.ToString("N", CultureInfo.InvariantCulture)) + .ToArray() }; - info.EnabledUsers = _userManager.Users - .Where(IsLiveTvEnabled) - .Select(i => i.Id.ToString("N", CultureInfo.InvariantCulture)) - .ToArray(); - return info; } private bool IsLiveTvEnabled(User user) { - return user.Policy.EnableLiveTvAccess && (Services.Count > 1 || GetConfiguration().TunerHosts.Length > 0); + return user.HasPermission(PermissionKind.EnableLiveTvAccess) && (Services.Count > 1 || GetConfiguration().TunerHosts.Length > 0); } public IEnumerable<User> GetEnabledUsers() @@ -2457,7 +2463,6 @@ namespace Emby.Server.Implementations.LiveTv UserId = user.Id, IsRecordingsFolder = true, RefreshLatestChannelItems = refreshChannels - }).Items); return folders.Cast<BaseItem>().ToList(); diff --git a/Emby.Server.Implementations/LiveTv/RefreshChannelsScheduledTask.cs b/Emby.Server.Implementations/LiveTv/RefreshChannelsScheduledTask.cs index 8e7d60a15..f1b61f7c7 100644 --- a/Emby.Server.Implementations/LiveTv/RefreshChannelsScheduledTask.cs +++ b/Emby.Server.Implementations/LiveTv/RefreshChannelsScheduledTask.cs @@ -35,7 +35,7 @@ namespace Emby.Server.Implementations.LiveTv } /// <summary> - /// Creates the triggers that define when the task will run + /// Creates the triggers that define when the task will run. /// </summary> /// <returns>IEnumerable{BaseTaskTrigger}.</returns> public IEnumerable<TaskTriggerInfo> GetDefaultTriggers() diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs index ba3594efd..a8d34d19c 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs @@ -54,7 +54,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts var result = await GetChannelsInternal(tuner, cancellationToken).ConfigureAwait(false); var list = result.ToList(); - //logger.LogInformation("Channels from {0}: {1}", tuner.Url, JsonSerializer.SerializeToString(list)); + // logger.LogInformation("Channels from {0}: {1}", tuner.Url, JsonSerializer.SerializeToString(list)); if (!string.IsNullOrEmpty(key) && list.Count > 0) { @@ -99,7 +99,6 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts } catch (IOException) { - } } } @@ -116,7 +115,6 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts } catch (IOException) { - } } } diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs index 25b2c674c..dff113a2a 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs @@ -111,7 +111,6 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun ChannelType = ChannelType.TV, IsLegacyTuner = (i.URL ?? string.Empty).StartsWith("hdhomerun", StringComparison.OrdinalIgnoreCase), Path = i.URL - }).Cast<ChannelInfo>().ToList(); } @@ -171,6 +170,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun _modelCache[cacheKey] = response; } } + return response; } @@ -202,6 +202,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun var name = line.Substring(0, index - 1); var currentChannel = line.Substring(index + 7); if (currentChannel != "none") { status = LiveTvTunerStatus.LiveTv; } else { status = LiveTvTunerStatus.Available; } + tuners.Add(new LiveTvTunerInfo { Name = name, @@ -230,11 +231,13 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun inside = true; continue; } + if (let == '>') { inside = false; continue; } + if (!inside) { buffer[bufferIndex] = let; @@ -332,12 +335,19 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun private class Channels { public string GuideNumber { get; set; } + public string GuideName { get; set; } + public string VideoCodec { get; set; } + public string AudioCodec { get; set; } + public string URL { get; set; } + public bool Favorite { get; set; } + public bool DRM { get; set; } + public int HD { get; set; } } @@ -481,7 +491,6 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun Height = height, BitRate = videoBitrate, NalLengthSize = nal - }, new MediaStream { @@ -502,8 +511,8 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun SupportsTranscoding = true, IsInfiniteStream = true, IgnoreDts = true, - //IgnoreIndex = true, - //ReadAtNativeFramerate = true + // IgnoreIndex = true, + // ReadAtNativeFramerate = true }; mediaSource.InferTotalBitrate(); @@ -659,13 +668,21 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun public class DiscoverResponse { public string FriendlyName { get; set; } + public string ModelNumber { get; set; } + public string FirmwareName { get; set; } + public string FirmwareVersion { get; set; } + public string DeviceID { get; set; } + public string DeviceAuth { get; set; } + public string BaseURL { get; set; } + public string LineupURL { get; set; } + public int TunerCount { get; set; } public bool SupportsTranscoding @@ -722,7 +739,6 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun } } } - } catch (OperationCanceledException) { diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs index 82b1f3cf1..6730751d5 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs @@ -117,17 +117,17 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun taskCompletionSource, LiveStreamCancellationTokenSource.Token).ConfigureAwait(false); - //OpenedMediaSource.Protocol = MediaProtocol.File; - //OpenedMediaSource.Path = tempFile; - //OpenedMediaSource.ReadAtNativeFramerate = true; + // OpenedMediaSource.Protocol = MediaProtocol.File; + // OpenedMediaSource.Path = tempFile; + // OpenedMediaSource.ReadAtNativeFramerate = true; MediaSource.Path = _appHost.GetLoopbackHttpApiUrl() + "/LiveTv/LiveStreamFiles/" + UniqueId + "/stream.ts"; MediaSource.Protocol = MediaProtocol.Http; - //OpenedMediaSource.SupportsDirectPlay = false; - //OpenedMediaSource.SupportsDirectStream = true; - //OpenedMediaSource.SupportsTranscoding = true; + // OpenedMediaSource.SupportsDirectPlay = false; + // OpenedMediaSource.SupportsDirectStream = true; + // OpenedMediaSource.SupportsTranscoding = true; - //await Task.Delay(5000).ConfigureAwait(false); + // await Task.Delay(5000).ConfigureAwait(false); await taskCompletionSource.Task.ConfigureAwait(false); } diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/LiveStream.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/LiveStream.cs index 4e4f1d7f6..0333e723b 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/LiveStream.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/LiveStream.cs @@ -58,12 +58,15 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts protected virtual int EmptyReadLimit => 1000; public MediaSourceInfo OriginalMediaSource { get; set; } + public MediaSourceInfo MediaSource { get; set; } public int ConsumerCount { get; set; } public string OriginalStreamId { get; set; } + public bool EnableStreamSharing { get; set; } + public string UniqueId { get; } public string TunerHostId { get; } @@ -220,11 +223,9 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts } catch (IOException) { - } catch (ArgumentException) { - } catch (Exception ex) { diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs index f7c9c736e..ff42a9747 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs @@ -127,7 +127,6 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts { using (var stream = await new M3uParser(Logger, _httpClient, _appHost).GetListingsStream(info.Url, CancellationToken.None).ConfigureAwait(false)) { - } } diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs index 59451fccd..c798c0a85 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs @@ -210,7 +210,6 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts } } } - } if (!IsValidChannelNumber(numberString)) @@ -284,7 +283,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts if (double.TryParse(numberPart, NumberStyles.Any, CultureInfo.InvariantCulture, out var number)) { - //channel.Number = number.ToString(); + // channel.Number = number.ToString(); nameInExtInf = nameInExtInf.Substring(numberIndex + 1).Trim(new[] { ' ', '-' }); } } diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs index 322fbbbaa..bc4dcd894 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs @@ -103,21 +103,21 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts _ = StartStreaming(response, taskCompletionSource, LiveStreamCancellationTokenSource.Token); - //OpenedMediaSource.Protocol = MediaProtocol.File; - //OpenedMediaSource.Path = tempFile; - //OpenedMediaSource.ReadAtNativeFramerate = true; + // OpenedMediaSource.Protocol = MediaProtocol.File; + // OpenedMediaSource.Path = tempFile; + // OpenedMediaSource.ReadAtNativeFramerate = true; MediaSource.Path = _appHost.GetLoopbackHttpApiUrl() + "/LiveTv/LiveStreamFiles/" + UniqueId + "/stream.ts"; MediaSource.Protocol = MediaProtocol.Http; - //OpenedMediaSource.Path = TempFilePath; - //OpenedMediaSource.Protocol = MediaProtocol.File; + // OpenedMediaSource.Path = TempFilePath; + // OpenedMediaSource.Protocol = MediaProtocol.File; - //OpenedMediaSource.Path = _tempFilePath; - //OpenedMediaSource.Protocol = MediaProtocol.File; - //OpenedMediaSource.SupportsDirectPlay = false; - //OpenedMediaSource.SupportsDirectStream = true; - //OpenedMediaSource.SupportsTranscoding = true; + // OpenedMediaSource.Path = _tempFilePath; + // OpenedMediaSource.Protocol = MediaProtocol.File; + // OpenedMediaSource.SupportsDirectPlay = false; + // OpenedMediaSource.SupportsDirectStream = true; + // OpenedMediaSource.SupportsTranscoding = true; await taskCompletionSource.Task.ConfigureAwait(false); if (taskCompletionSource.Task.Exception != null) { diff --git a/Emby.Server.Implementations/Localization/Core/bn.json b/Emby.Server.Implementations/Localization/Core/bn.json index 77007845f..1f309f3ff 100644 --- a/Emby.Server.Implementations/Localization/Core/bn.json +++ b/Emby.Server.Implementations/Localization/Core/bn.json @@ -62,13 +62,13 @@ "NotificationOptionPluginInstalled": "প্লাগিন ইন্সটল করা হয়েছে", "NotificationOptionPluginError": "প্লাগিন ব্যর্থ", "NotificationOptionNewLibraryContent": "নতুন কন্টেন্ট যোগ করা হয়েছে", - "NotificationOptionInstallationFailed": "ইন্সটল ব্যর্থ", + "NotificationOptionInstallationFailed": "ইন্সটল ব্যর্থ হয়েছে", "NotificationOptionCameraImageUploaded": "ক্যামেরার ছবি আপলোড হয়েছে", "NotificationOptionAudioPlaybackStopped": "গান বাজা বন্ধ হয়েছে", "NotificationOptionAudioPlayback": "গান বাজা শুরু হয়েছে", "NotificationOptionApplicationUpdateInstalled": "এপ্লিকেশনের আপডেট ইনস্টল করা হয়েছে", "NotificationOptionApplicationUpdateAvailable": "এপ্লিকেশনের আপডেট রয়েছে", - "NewVersionIsAvailable": "জেলিফিন সার্ভারের একটি নতুন ভার্শন ডাউনলোডের জন্য তৈরী", + "NewVersionIsAvailable": "জেলিফিন সার্ভারের একটি নতুন ভার্শন ডাউনলোডের জন্য তৈরী।", "NameSeasonUnknown": "সিজন অজানা", "NameSeasonNumber": "সিজন {0}", "NameInstallFailed": "{0} ইন্সটল ব্যর্থ", @@ -100,5 +100,18 @@ "TaskCleanCacheDescription": "সিস্টেমে আর প্রয়োজন নেই ক্যাশ, ফাইলগুলি মুছে ফেলুন।", "TaskCleanCache": "ক্লিন ক্যাশ ডিরেক্টরি", "TasksChannelsCategory": "ইন্টারনেট চ্যানেল", - "TasksApplicationCategory": "আবেদন" + "TasksApplicationCategory": "আবেদন", + "TaskDownloadMissingSubtitlesDescription": "মেটাডেটা কনফিগারেশনের উপর ভিত্তি করে অনুপস্থিত সাবটাইটেলগুলির জন্য ইন্টারনেট অনুসন্ধান করে।", + "TaskDownloadMissingSubtitles": "অনুপস্থিত সাবটাইটেলগুলি ডাউনলোড করুন", + "TaskRefreshChannelsDescription": "ইন্টারনেট চ্যানেল তথ্য রিফ্রেশ করুন।", + "TaskRefreshChannels": "চ্যানেল রিফ্রেশ করুন", + "TaskCleanTranscodeDescription": "এক দিনেরও বেশি পুরানো ট্রান্সকোড ফাইলগুলি মুছে ফেলুন।", + "TaskCleanTranscode": "ট্রান্সকোড ডিরেক্টরি ক্লিন করুন", + "TaskUpdatePluginsDescription": "স্বয়ংক্রিয়ভাবে আপডেট কনফিগার করা প্লাগইনগুলির জন্য আপডেট ডাউনলোড এবং ইনস্টল করুন।", + "TaskUpdatePlugins": "প্লাগইন আপডেট করুন", + "TaskRefreshPeopleDescription": "আপনার মিডিয়া লাইব্রেরিতে অভিনেতা এবং পরিচালকদের জন্য মেটাডাটা আপডেট করুন।", + "TaskRefreshPeople": "পিপল রিফ্রেশ করুন", + "TaskCleanLogsDescription": "{0} দিনের বেশী পুরানো লগ ফাইলগুলি মুছে ফেলুন।", + "TaskCleanLogs": "লগ ডিরেক্টরি ক্লিন করুন", + "TaskRefreshLibraryDescription": "নতুন ফাইলের জন্য মিডিয়া লাইব্রেরি স্ক্যান এবং মেটাডাটা রিফ্রেশ করুন।" } diff --git a/Emby.Server.Implementations/Localization/Core/fr-CA.json b/Emby.Server.Implementations/Localization/Core/fr-CA.json index 3dcfa6844..cd1c8144f 100644 --- a/Emby.Server.Implementations/Localization/Core/fr-CA.json +++ b/Emby.Server.Implementations/Localization/Core/fr-CA.json @@ -109,9 +109,10 @@ "TaskCleanLogs": "Nettoyer le répertoire des journaux", "TaskRefreshLibraryDescription": "Analyse votre bibliothèque média pour trouver de nouveaux fichiers et rafraîchit les métadonnées.", "TaskRefreshChapterImages": "Extraire les images de chapitre", - "TaskRefreshChapterImagesDescription": "Créer des vignettes pour les vidéos qui ont des chapitres", + "TaskRefreshChapterImagesDescription": "Créer des vignettes pour les vidéos qui ont des chapitres.", "TaskRefreshLibrary": "Analyser la bibliothèque de médias", "TaskCleanCache": "Nettoyer le répertoire des fichiers temporaires", "TasksApplicationCategory": "Application", - "TaskCleanCacheDescription": "Supprime les fichiers temporaires qui ne sont plus nécessaire pour le système." + "TaskCleanCacheDescription": "Supprime les fichiers temporaires qui ne sont plus nécessaire pour le système.", + "TasksChannelsCategory": "Canaux Internet" } diff --git a/Emby.Server.Implementations/Net/SocketFactory.cs b/Emby.Server.Implementations/Net/SocketFactory.cs index f347540c7..177721658 100644 --- a/Emby.Server.Implementations/Net/SocketFactory.cs +++ b/Emby.Server.Implementations/Net/SocketFactory.cs @@ -98,7 +98,6 @@ namespace Emby.Server.Implementations.Net } catch (SocketException) { - } try @@ -109,12 +108,11 @@ namespace Emby.Server.Implementations.Net } catch (SocketException) { - } try { - //retVal.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Broadcast, true); + // retVal.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Broadcast, true); retVal.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.MulticastTimeToLive, multicastTimeToLive); var localIp = IPAddress.Any; diff --git a/Emby.Server.Implementations/Networking/NetworkManager.cs b/Emby.Server.Implementations/Networking/NetworkManager.cs index 45864bb42..d12b5a937 100644 --- a/Emby.Server.Implementations/Networking/NetworkManager.cs +++ b/Emby.Server.Implementations/Networking/NetworkManager.cs @@ -167,7 +167,7 @@ namespace Emby.Server.Implementations.Networking foreach (var subnet_Match in subnets) { - //logger.LogDebug("subnet_Match:" + subnet_Match); + // logger.LogDebug("subnet_Match:" + subnet_Match); if (endpoint.StartsWith(subnet_Match + ".", StringComparison.OrdinalIgnoreCase)) { @@ -411,7 +411,7 @@ namespace Emby.Server.Implementations.Networking } /// <summary> - /// Gets a random port number that is currently available + /// Gets a random port number that is currently available. /// </summary> /// <returns>System.Int32.</returns> public int GetRandomUnusedTcpPort() diff --git a/Emby.Server.Implementations/Playlists/ManualPlaylistsFolder.cs b/Emby.Server.Implementations/Playlists/ManualPlaylistsFolder.cs index 889760586..358606b0d 100644 --- a/Emby.Server.Implementations/Playlists/ManualPlaylistsFolder.cs +++ b/Emby.Server.Implementations/Playlists/ManualPlaylistsFolder.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; using System.Text.Json.Serialization; +using Jellyfin.Data.Entities; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Playlists; using MediaBrowser.Model.Querying; @@ -44,7 +45,7 @@ namespace Emby.Server.Implementations.Playlists } query.Recursive = true; - query.IncludeItemTypes = new string[] { "Playlist" }; + query.IncludeItemTypes = new[] { "Playlist" }; query.Parent = null; return LibraryManager.GetItemsResult(query); } diff --git a/Emby.Server.Implementations/Playlists/PlaylistManager.cs b/Emby.Server.Implementations/Playlists/PlaylistManager.cs index 8d022d6c4..ac816ccd9 100644 --- a/Emby.Server.Implementations/Playlists/PlaylistManager.cs +++ b/Emby.Server.Implementations/Playlists/PlaylistManager.cs @@ -7,6 +7,7 @@ using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; +using Jellyfin.Data.Entities; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Audio; @@ -21,6 +22,8 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; using PlaylistsNET.Content; using PlaylistsNET.Models; +using Genre = MediaBrowser.Controller.Entities.Genre; +using MusicAlbum = MediaBrowser.Controller.Entities.Audio.MusicAlbum; namespace Emby.Server.Implementations.Playlists { @@ -398,6 +401,7 @@ namespace Emby.Server.Implementations.Playlists { entry.Duration = TimeSpan.FromTicks(child.RunTimeTicks.Value); } + playlist.PlaylistEntries.Add(entry); } @@ -463,7 +467,7 @@ namespace Emby.Server.Implementations.Playlists playlist.PlaylistEntries.Add(entry); } - string text = new M3u8Content().ToText(playlist); + string text = new M3uContent().ToText(playlist); File.WriteAllText(playlistPath, text); } diff --git a/Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs b/Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs index e58c335a8..8a900f42c 100644 --- a/Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs +++ b/Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs @@ -18,7 +18,7 @@ using Microsoft.Extensions.Logging; namespace Emby.Server.Implementations.ScheduledTasks { /// <summary> - /// Class ScheduledTaskWorker + /// Class ScheduledTaskWorker. /// </summary> public class ScheduledTaskWorker : IScheduledTaskWorker { @@ -111,11 +111,11 @@ namespace Emby.Server.Implementations.ScheduledTasks private bool _readFromFile = false; /// <summary> - /// The _last execution result + /// The _last execution result. /// </summary> private TaskResult _lastExecutionResult; /// <summary> - /// The _last execution result sync lock + /// The _last execution result sync lock. /// </summary> private readonly object _lastExecutionResultSyncLock = new object(); /// <summary> @@ -143,12 +143,14 @@ namespace Emby.Server.Implementations.ScheduledTasks Logger.LogError(ex, "Error deserializing {File}", path); } } + _readFromFile = true; } } return _lastExecutionResult; } + private set { _lastExecutionResult = value; @@ -182,7 +184,7 @@ namespace Emby.Server.Implementations.ScheduledTasks public string Category => ScheduledTask.Category; /// <summary> - /// Gets the current cancellation token + /// Gets the current cancellation token. /// </summary> /// <value>The current cancellation token source.</value> private CancellationTokenSource CurrentCancellationTokenSource { get; set; } @@ -261,6 +263,7 @@ namespace Emby.Server.Implementations.ScheduledTasks var triggers = InternalTriggers; return triggers.Select(i => i.Item1).ToArray(); } + set { if (value == null) @@ -278,7 +281,7 @@ namespace Emby.Server.Implementations.ScheduledTasks } /// <summary> - /// The _id + /// The _id. /// </summary> private string _id; @@ -358,7 +361,7 @@ namespace Emby.Server.Implementations.ScheduledTasks private Task _currentTask; /// <summary> - /// Executes the task + /// Executes the task. /// </summary> /// <param name="options">Task options.</param> /// <returns>Task.</returns> @@ -453,7 +456,7 @@ namespace Emby.Server.Implementations.ScheduledTasks } /// <summary> - /// Stops the task if it is currently executing + /// Stops the task if it is currently executing. /// </summary> /// <exception cref="InvalidOperationException">Cannot cancel a Task unless it is in the Running state.</exception> public void Cancel() @@ -640,6 +643,7 @@ namespace Emby.Server.Implementations.ScheduledTasks Logger.LogError(ex, "Error calling CancellationToken.Cancel();"); } } + var task = _currentTask; if (task != null) { @@ -675,6 +679,7 @@ namespace Emby.Server.Implementations.ScheduledTasks Logger.LogError(ex, "Error calling CancellationToken.Dispose();"); } } + if (wassRunning) { OnTaskCompleted(startTime, DateTime.UtcNow, TaskCompletionStatus.Aborted, null); @@ -683,7 +688,7 @@ namespace Emby.Server.Implementations.ScheduledTasks } /// <summary> - /// Converts a TaskTriggerInfo into a concrete BaseTaskTrigger + /// Converts a TaskTriggerInfo into a concrete BaseTaskTrigger. /// </summary> /// <param name="info">The info.</param> /// <returns>BaseTaskTrigger.</returns> @@ -753,7 +758,7 @@ namespace Emby.Server.Implementations.ScheduledTasks } /// <summary> - /// Disposes each trigger + /// Disposes each trigger. /// </summary> private void DisposeTriggers() { diff --git a/Emby.Server.Implementations/ScheduledTasks/TaskManager.cs b/Emby.Server.Implementations/ScheduledTasks/TaskManager.cs index 94220ac5d..0ad4253e4 100644 --- a/Emby.Server.Implementations/ScheduledTasks/TaskManager.cs +++ b/Emby.Server.Implementations/ScheduledTasks/TaskManager.cs @@ -15,7 +15,7 @@ using Microsoft.Extensions.Logging; namespace Emby.Server.Implementations.ScheduledTasks { /// <summary> - /// Class TaskManager + /// Class TaskManager. /// </summary> public class TaskManager : ITaskManager { @@ -23,13 +23,13 @@ namespace Emby.Server.Implementations.ScheduledTasks public event EventHandler<TaskCompletionEventArgs> TaskCompleted; /// <summary> - /// Gets the list of Scheduled Tasks + /// Gets the list of Scheduled Tasks. /// </summary> /// <value>The scheduled tasks.</value> public IScheduledTaskWorker[] ScheduledTasks { get; private set; } /// <summary> - /// The _task queue + /// The _task queue. /// </summary> private readonly ConcurrentQueue<Tuple<Type, TaskOptions>> _taskQueue = new ConcurrentQueue<Tuple<Type, TaskOptions>>(); @@ -81,7 +81,7 @@ namespace Emby.Server.Implementations.ScheduledTasks } /// <summary> - /// Cancels if running + /// Cancels if running. /// </summary> /// <typeparam name="T"></typeparam> public void CancelIfRunning<T>() diff --git a/Emby.Server.Implementations/ScheduledTasks/Tasks/ChapterImagesTask.cs b/Emby.Server.Implementations/ScheduledTasks/Tasks/ChapterImagesTask.cs index 9028222dd..3854be703 100644 --- a/Emby.Server.Implementations/ScheduledTasks/Tasks/ChapterImagesTask.cs +++ b/Emby.Server.Implementations/ScheduledTasks/Tasks/ChapterImagesTask.cs @@ -163,7 +163,7 @@ namespace Emby.Server.Implementations.ScheduledTasks } catch (ObjectDisposedException) { - //TODO Investigate and properly fix. + // TODO Investigate and properly fix. break; } } diff --git a/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteCacheFileTask.cs b/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteCacheFileTask.cs index 966b549b2..e29fcfb5f 100644 --- a/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteCacheFileTask.cs +++ b/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteCacheFileTask.cs @@ -13,7 +13,7 @@ using MediaBrowser.Model.Globalization; namespace Emby.Server.Implementations.ScheduledTasks.Tasks { /// <summary> - /// Deletes old cache files + /// Deletes old cache files. /// </summary> public class DeleteCacheFileTask : IScheduledTask, IConfigurableScheduledTask { @@ -44,7 +44,7 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks } /// <summary> - /// Creates the triggers that define when the task will run + /// Creates the triggers that define when the task will run. /// </summary> /// <returns>IEnumerable{BaseTaskTrigger}.</returns> public IEnumerable<TaskTriggerInfo> GetDefaultTriggers() @@ -57,7 +57,7 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks } /// <summary> - /// Returns the task to be executed + /// Returns the task to be executed. /// </summary> /// <param name="cancellationToken">The cancellation token.</param> /// <param name="progress">The progress.</param> @@ -93,7 +93,7 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks /// <summary> - /// Deletes the cache files from directory with a last write time less than a given date + /// Deletes the cache files from directory with a last write time less than a given date. /// </summary> /// <param name="cancellationToken">The task cancellation token.</param> /// <param name="directory">The directory.</param> diff --git a/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteTranscodeFileTask.cs b/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteTranscodeFileTask.cs index 53cf9a0a5..691408167 100644 --- a/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteTranscodeFileTask.cs +++ b/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteTranscodeFileTask.cs @@ -13,7 +13,7 @@ using MediaBrowser.Model.Globalization; namespace Emby.Server.Implementations.ScheduledTasks.Tasks { /// <summary> - /// Deletes all transcoding temp files + /// Deletes all transcoding temp files. /// </summary> public class DeleteTranscodeFileTask : IScheduledTask, IConfigurableScheduledTask { diff --git a/Emby.Server.Implementations/ScheduledTasks/Triggers/DailyTrigger.cs b/Emby.Server.Implementations/ScheduledTasks/Triggers/DailyTrigger.cs index c7819d4c0..eb628ec5f 100644 --- a/Emby.Server.Implementations/ScheduledTasks/Triggers/DailyTrigger.cs +++ b/Emby.Server.Implementations/ScheduledTasks/Triggers/DailyTrigger.cs @@ -28,7 +28,7 @@ namespace Emby.Server.Implementations.ScheduledTasks private Timer Timer { get; set; } /// <summary> - /// Stars waiting for the trigger action + /// Stars waiting for the trigger action. /// </summary> /// <param name="lastResult">The last result.</param> /// <param name="logger">The logger.</param> @@ -51,7 +51,7 @@ namespace Emby.Server.Implementations.ScheduledTasks } /// <summary> - /// Stops waiting for the trigger action + /// Stops waiting for the trigger action. /// </summary> public void Stop() { diff --git a/Emby.Server.Implementations/ScheduledTasks/Triggers/IntervalTrigger.cs b/Emby.Server.Implementations/ScheduledTasks/Triggers/IntervalTrigger.cs index 74cd4ef1e..247a6785a 100644 --- a/Emby.Server.Implementations/ScheduledTasks/Triggers/IntervalTrigger.cs +++ b/Emby.Server.Implementations/ScheduledTasks/Triggers/IntervalTrigger.cs @@ -7,7 +7,7 @@ using Microsoft.Extensions.Logging; namespace Emby.Server.Implementations.ScheduledTasks { /// <summary> - /// Represents a task trigger that runs repeatedly on an interval + /// Represents a task trigger that runs repeatedly on an interval. /// </summary> public class IntervalTrigger : ITaskTrigger { @@ -31,7 +31,7 @@ namespace Emby.Server.Implementations.ScheduledTasks private DateTime _lastStartDate; /// <summary> - /// Stars waiting for the trigger action + /// Stars waiting for the trigger action. /// </summary> /// <param name="lastResult">The last result.</param> /// <param name="logger">The logger.</param> @@ -70,7 +70,7 @@ namespace Emby.Server.Implementations.ScheduledTasks } /// <summary> - /// Stops waiting for the trigger action + /// Stops waiting for the trigger action. /// </summary> public void Stop() { diff --git a/Emby.Server.Implementations/ScheduledTasks/Triggers/StartupTrigger.cs b/Emby.Server.Implementations/ScheduledTasks/Triggers/StartupTrigger.cs index e171a9e9f..96e5d8897 100644 --- a/Emby.Server.Implementations/ScheduledTasks/Triggers/StartupTrigger.cs +++ b/Emby.Server.Implementations/ScheduledTasks/Triggers/StartupTrigger.cs @@ -25,7 +25,7 @@ namespace Emby.Server.Implementations.ScheduledTasks } /// <summary> - /// Stars waiting for the trigger action + /// Stars waiting for the trigger action. /// </summary> /// <param name="lastResult">The last result.</param> /// <param name="logger">The logger.</param> @@ -42,7 +42,7 @@ namespace Emby.Server.Implementations.ScheduledTasks } /// <summary> - /// Stops waiting for the trigger action + /// Stops waiting for the trigger action. /// </summary> public void Stop() { diff --git a/Emby.Server.Implementations/ScheduledTasks/Triggers/WeeklyTrigger.cs b/Emby.Server.Implementations/ScheduledTasks/Triggers/WeeklyTrigger.cs index ad0b57af6..4f1bf5c19 100644 --- a/Emby.Server.Implementations/ScheduledTasks/Triggers/WeeklyTrigger.cs +++ b/Emby.Server.Implementations/ScheduledTasks/Triggers/WeeklyTrigger.cs @@ -6,12 +6,12 @@ using Microsoft.Extensions.Logging; namespace Emby.Server.Implementations.ScheduledTasks { /// <summary> - /// Represents a task trigger that fires on a weekly basis + /// Represents a task trigger that fires on a weekly basis. /// </summary> public class WeeklyTrigger : ITaskTrigger { /// <summary> - /// Get the time of day to trigger the task to run + /// Get the time of day to trigger the task to run. /// </summary> /// <value>The time of day.</value> public TimeSpan TimeOfDay { get; set; } @@ -34,7 +34,7 @@ namespace Emby.Server.Implementations.ScheduledTasks private Timer Timer { get; set; } /// <summary> - /// Stars waiting for the trigger action + /// Stars waiting for the trigger action. /// </summary> /// <param name="lastResult">The last result.</param> /// <param name="logger">The logger.</param> @@ -77,7 +77,7 @@ namespace Emby.Server.Implementations.ScheduledTasks } /// <summary> - /// Stops waiting for the trigger action + /// Stops waiting for the trigger action. /// </summary> public void Stop() { diff --git a/Emby.Server.Implementations/Security/AuthenticationRepository.cs b/Emby.Server.Implementations/Security/AuthenticationRepository.cs index 750890ec8..9c1be9a1a 100644 --- a/Emby.Server.Implementations/Security/AuthenticationRepository.cs +++ b/Emby.Server.Implementations/Security/AuthenticationRepository.cs @@ -61,7 +61,6 @@ namespace Emby.Server.Implementations.Security AddColumn(db, "AccessTokens", "UserName", "TEXT", existingColumnNames); AddColumn(db, "AccessTokens", "DateLastActivity", "DATETIME", existingColumnNames); AddColumn(db, "AccessTokens", "AppVersion", "TEXT", existingColumnNames); - }, TransactionMode); connection.RunQueries(new[] @@ -107,7 +106,6 @@ namespace Emby.Server.Implementations.Security statement.MoveNext(); } - }, TransactionMode); } } @@ -367,7 +365,6 @@ namespace Emby.Server.Implementations.Security return result; } - }, ReadTransactionMode); } } @@ -398,7 +395,6 @@ namespace Emby.Server.Implementations.Security statement.MoveNext(); } - }, TransactionMode); } } diff --git a/Emby.Server.Implementations/Services/ResponseHelper.cs b/Emby.Server.Implementations/Services/ResponseHelper.cs index f2b1d06f3..3f1672e94 100644 --- a/Emby.Server.Implementations/Services/ResponseHelper.cs +++ b/Emby.Server.Implementations/Services/ResponseHelper.cs @@ -59,8 +59,8 @@ namespace Emby.Server.Implementations.Services } } - //ContentType='text/html' is the default for a HttpResponse - //Do not override if another has been set + // ContentType='text/html' is the default for a HttpResponse + // Do not override if another has been set if (response.ContentType == null || response.ContentType == "text/html") { response.ContentType = defaultContentType; diff --git a/Emby.Server.Implementations/Services/ServiceController.cs b/Emby.Server.Implementations/Services/ServiceController.cs index e688278b5..b84e47140 100644 --- a/Emby.Server.Implementations/Services/ServiceController.cs +++ b/Emby.Server.Implementations/Services/ServiceController.cs @@ -59,8 +59,8 @@ namespace Emby.Server.Implementations.Services ServiceExecGeneral.CreateServiceRunnersFor(requestType, actions); - //var returnMarker = GetTypeWithGenericTypeDefinitionOf(requestType, typeof(IReturn<>)); - //var responseType = returnMarker != null ? + // var returnMarker = GetTypeWithGenericTypeDefinitionOf(requestType, typeof(IReturn<>)); + // var responseType = returnMarker != null ? // GetGenericArguments(returnMarker)[0] // : mi.ReturnType != typeof(object) && mi.ReturnType != typeof(void) ? // mi.ReturnType @@ -182,7 +182,7 @@ namespace Emby.Server.Implementations.Services serviceRequiresContext.Request = req; } - //Executes the service and returns the result + // Executes the service and returns the result return ServiceExecGeneral.Execute(serviceType, req, service, requestDto, requestType.GetMethodName()); } } diff --git a/Emby.Server.Implementations/Services/ServiceHandler.cs b/Emby.Server.Implementations/Services/ServiceHandler.cs index 7f44357e1..a42f88ea0 100644 --- a/Emby.Server.Implementations/Services/ServiceHandler.cs +++ b/Emby.Server.Implementations/Services/ServiceHandler.cs @@ -180,7 +180,7 @@ namespace Emby.Server.Implementations.Services => string.Equals(method, expected, StringComparison.OrdinalIgnoreCase); /// <summary> - /// Duplicate params have their values joined together in a comma-delimited string + /// Duplicate params have their values joined together in a comma-delimited string. /// </summary> private static Dictionary<string, string> GetFlattenedRequestParams(HttpRequest request) { diff --git a/Emby.Server.Implementations/Services/ServiceMethod.cs b/Emby.Server.Implementations/Services/ServiceMethod.cs index 59ee5908f..5116cc04f 100644 --- a/Emby.Server.Implementations/Services/ServiceMethod.cs +++ b/Emby.Server.Implementations/Services/ServiceMethod.cs @@ -9,6 +9,7 @@ namespace Emby.Server.Implementations.Services public string Id { get; set; } public ActionInvokerFn ServiceAction { get; set; } + public MediaBrowser.Model.Services.IHasRequestFilter[] RequestFilters { get; set; } public static string Key(Type serviceType, string method, string requestDtoName) diff --git a/Emby.Server.Implementations/Services/ServicePath.cs b/Emby.Server.Implementations/Services/ServicePath.cs index 278379a92..3b7ffaf2c 100644 --- a/Emby.Server.Implementations/Services/ServicePath.cs +++ b/Emby.Server.Implementations/Services/ServicePath.cs @@ -62,7 +62,9 @@ namespace Emby.Server.Implementations.Services public string Path => this.restPath; public string Summary { get; private set; } + public string Description { get; private set; } + public bool IsHidden { get; private set; } public static string[] GetPathPartsForMatching(string pathInfo) @@ -118,7 +120,7 @@ namespace Emby.Server.Implementations.Services var componentsList = new List<string>(); - //We only split on '.' if the restPath has them. Allows for /{action}.{type} + // We only split on '.' if the restPath has them. Allows for /{action}.{type} var hasSeparators = new List<bool>(); foreach (var component in this.restPath.Split(PathSeperatorChar)) { @@ -159,6 +161,7 @@ namespace Emby.Server.Implementations.Services this.isWildcard[i] = true; variableName = variableName.Substring(0, variableName.Length - 1); } + this.variablesNames[i] = variableName; this.VariableArgsCount++; } @@ -298,12 +301,12 @@ namespace Emby.Server.Implementations.Services return -1; } - //Routes with least wildcard matches get the highest score + // Routes with least wildcard matches get the highest score var score = Math.Max((100 - wildcardMatchCount), 1) * 1000 - //Routes with less variable (and more literal) matches + // Routes with less variable (and more literal) matches + Math.Max((10 - VariableArgsCount), 1) * 100; - //Exact verb match is better than ANY + // Exact verb match is better than ANY if (Verbs.Length == 1 && string.Equals(httpMethod, Verbs[0], StringComparison.OrdinalIgnoreCase)) { score += 10; @@ -470,7 +473,7 @@ namespace Emby.Server.Implementations.Services + variableName + " on " + RequestType.GetMethodName()); } - var value = requestComponents.Length > pathIx ? requestComponents[pathIx] : null; //wildcard has arg mismatch + var value = requestComponents.Length > pathIx ? requestComponents[pathIx] : null; // wildcard has arg mismatch if (value != null && this.isWildcard[i]) { if (i == this.TotalComponentsCount - 1) @@ -519,8 +522,8 @@ namespace Emby.Server.Implementations.Services if (queryStringAndFormData != null) { - //Query String and form data can override variable path matches - //path variables < query string < form data + // Query String and form data can override variable path matches + // path variables < query string < form data foreach (var name in queryStringAndFormData) { requestKeyValuesMap[name.Key] = name.Value; diff --git a/Emby.Server.Implementations/Services/StringMapTypeDeserializer.cs b/Emby.Server.Implementations/Services/StringMapTypeDeserializer.cs index ab22fe019..165bb0fc4 100644 --- a/Emby.Server.Implementations/Services/StringMapTypeDeserializer.cs +++ b/Emby.Server.Implementations/Services/StringMapTypeDeserializer.cs @@ -22,7 +22,9 @@ namespace Emby.Server.Implementations.Services } public Action<object, object> PropertySetFn { get; private set; } + public Func<string, object> PropertyParseStringFn { get; private set; } + public Type PropertyType { get; private set; } } @@ -83,7 +85,7 @@ namespace Emby.Server.Implementations.Services if (propertySerializerEntry.PropertyType == typeof(bool)) { - //InputExtensions.cs#530 MVC Checkbox helper emits extra hidden input field, generating 2 values, first is the real value + // InputExtensions.cs#530 MVC Checkbox helper emits extra hidden input field, generating 2 values, first is the real value propertyTextValue = StringExtensions.LeftPart(propertyTextValue, ',').ToString(); } diff --git a/Emby.Server.Implementations/Services/SwaggerService.cs b/Emby.Server.Implementations/Services/SwaggerService.cs index 16142a70d..4f011a678 100644 --- a/Emby.Server.Implementations/Services/SwaggerService.cs +++ b/Emby.Server.Implementations/Services/SwaggerService.cs @@ -18,13 +18,21 @@ namespace Emby.Server.Implementations.Services public class SwaggerSpec { public string swagger { get; set; } + public string[] schemes { get; set; } + public SwaggerInfo info { get; set; } + public string host { get; set; } + public string basePath { get; set; } + public SwaggerTag[] tags { get; set; } + public IDictionary<string, Dictionary<string, SwaggerMethod>> paths { get; set; } + public Dictionary<string, SwaggerDefinition> definitions { get; set; } + public SwaggerComponents components { get; set; } } @@ -36,15 +44,20 @@ namespace Emby.Server.Implementations.Services public class SwaggerSecurityScheme { public string name { get; set; } + public string type { get; set; } + public string @in { get; set; } } public class SwaggerInfo { public string description { get; set; } + public string version { get; set; } + public string title { get; set; } + public string termsOfService { get; set; } public SwaggerConcactInfo contact { get; set; } @@ -53,36 +66,52 @@ namespace Emby.Server.Implementations.Services public class SwaggerConcactInfo { public string email { get; set; } + public string name { get; set; } + public string url { get; set; } } public class SwaggerTag { public string description { get; set; } + public string name { get; set; } } public class SwaggerMethod { public string summary { get; set; } + public string description { get; set; } + public string[] tags { get; set; } + public string operationId { get; set; } + public string[] consumes { get; set; } + public string[] produces { get; set; } + public SwaggerParam[] parameters { get; set; } + public Dictionary<string, SwaggerResponse> responses { get; set; } + public Dictionary<string, string[]>[] security { get; set; } } public class SwaggerParam { public string @in { get; set; } + public string name { get; set; } + public string description { get; set; } + public bool required { get; set; } + public string type { get; set; } + public string collectionFormat { get; set; } } @@ -97,15 +126,20 @@ namespace Emby.Server.Implementations.Services public class SwaggerDefinition { public string type { get; set; } + public Dictionary<string, SwaggerProperty> properties { get; set; } } public class SwaggerProperty { public string type { get; set; } + public string format { get; set; } + public string description { get; set; } + public string[] @enum { get; set; } + public string @default { get; set; } } diff --git a/Emby.Server.Implementations/Services/UrlExtensions.cs b/Emby.Server.Implementations/Services/UrlExtensions.cs index e3b6aa197..92e36b60e 100644 --- a/Emby.Server.Implementations/Services/UrlExtensions.cs +++ b/Emby.Server.Implementations/Services/UrlExtensions.cs @@ -9,7 +9,7 @@ namespace Emby.Server.Implementations.Services /// Donated by Ivan Korneliuk from his post: /// http://korneliuk.blogspot.com/2012/08/servicestack-reusing-dtos.html /// - /// Modified to only allow using routes matching the supplied HTTP Verb + /// Modified to only allow using routes matching the supplied HTTP Verb. /// </summary> public static class UrlExtensions { diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs index 506e6739f..75fdedd10 100644 --- a/Emby.Server.Implementations/Session/SessionManager.cs +++ b/Emby.Server.Implementations/Session/SessionManager.cs @@ -7,6 +7,8 @@ using System.Globalization; using System.Linq; using System.Threading; using System.Threading.Tasks; +using Jellyfin.Data.Entities; +using Jellyfin.Data.Enums; using MediaBrowser.Common.Events; using MediaBrowser.Common.Extensions; using MediaBrowser.Controller; @@ -15,7 +17,6 @@ using MediaBrowser.Controller.Devices; using MediaBrowser.Controller.Drawing; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Security; @@ -28,7 +29,9 @@ using MediaBrowser.Model.Library; using MediaBrowser.Model.Querying; using MediaBrowser.Model.Session; using MediaBrowser.Model.SyncPlay; +using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; +using Episode = MediaBrowser.Controller.Entities.TV.Episode; namespace Emby.Server.Implementations.Session { @@ -283,11 +286,18 @@ namespace Emby.Server.Implementations.Session if (user != null) { var userLastActivityDate = user.LastActivityDate ?? DateTime.MinValue; - user.LastActivityDate = activityDate; if ((activityDate - userLastActivityDate).TotalSeconds > 60) { - _userManager.UpdateUser(user); + try + { + user.LastActivityDate = activityDate; + _userManager.UpdateUser(user); + } + catch (DbUpdateConcurrencyException e) + { + _logger.LogWarning(e, "Error updating user's last activity date."); + } } } @@ -434,7 +444,13 @@ namespace Emby.Server.Implementations.Session /// <param name="remoteEndPoint">The remote end point.</param> /// <param name="user">The user.</param> /// <returns>SessionInfo.</returns> - private SessionInfo GetSessionInfo(string appName, string appVersion, string deviceId, string deviceName, string remoteEndPoint, User user) + private SessionInfo GetSessionInfo( + string appName, + string appVersion, + string deviceId, + string deviceName, + string remoteEndPoint, + User user) { CheckDisposed(); @@ -447,14 +463,13 @@ namespace Emby.Server.Implementations.Session CheckDisposed(); - var sessionInfo = _activeConnections.GetOrAdd(key, k => - { - return CreateSession(k, appName, appVersion, deviceId, deviceName, remoteEndPoint, user); - }); + var sessionInfo = _activeConnections.GetOrAdd( + key, + k => CreateSession(k, appName, appVersion, deviceId, deviceName, remoteEndPoint, user)); - sessionInfo.UserId = user == null ? Guid.Empty : user.Id; - sessionInfo.UserName = user?.Name; - sessionInfo.UserPrimaryImageTag = user == null ? null : GetImageCacheTag(user, ImageType.Primary); + sessionInfo.UserId = user?.Id ?? Guid.Empty; + sessionInfo.UserName = user?.Username; + sessionInfo.UserPrimaryImageTag = user?.ProfileImage == null ? null : GetImageCacheTag(user); sessionInfo.RemoteEndPoint = remoteEndPoint; sessionInfo.Client = appName; @@ -473,7 +488,14 @@ namespace Emby.Server.Implementations.Session return sessionInfo; } - private SessionInfo CreateSession(string key, string appName, string appVersion, string deviceId, string deviceName, string remoteEndPoint, User user) + private SessionInfo CreateSession( + string key, + string appName, + string appVersion, + string deviceId, + string deviceName, + string remoteEndPoint, + User user) { var sessionInfo = new SessionInfo(this, _logger) { @@ -483,11 +505,11 @@ namespace Emby.Server.Implementations.Session Id = key.GetMD5().ToString("N", CultureInfo.InvariantCulture) }; - var username = user?.Name; + var username = user?.Username; sessionInfo.UserId = user?.Id ?? Guid.Empty; sessionInfo.UserName = username; - sessionInfo.UserPrimaryImageTag = user == null ? null : GetImageCacheTag(user, ImageType.Primary); + sessionInfo.UserPrimaryImageTag = user?.ProfileImage == null ? null : GetImageCacheTag(user); sessionInfo.RemoteEndPoint = remoteEndPoint; if (string.IsNullOrEmpty(deviceName)) @@ -535,10 +557,7 @@ namespace Emby.Server.Implementations.Session private void StartIdleCheckTimer() { - if (_idleTimer == null) - { - _idleTimer = new Timer(CheckForIdlePlayback, null, TimeSpan.FromMinutes(5), TimeSpan.FromMinutes(5)); - } + _idleTimer ??= new Timer(CheckForIdlePlayback, null, TimeSpan.FromMinutes(5), TimeSpan.FromMinutes(5)); } private void StopIdleCheckTimer() @@ -786,7 +805,7 @@ namespace Emby.Server.Implementations.Session { var changed = false; - if (user.Configuration.RememberAudioSelections) + if (user.RememberAudioSelections) { if (data.AudioStreamIndex != info.AudioStreamIndex) { @@ -803,7 +822,7 @@ namespace Emby.Server.Implementations.Session } } - if (user.Configuration.RememberSubtitleSelections) + if (user.RememberSubtitleSelections) { if (data.SubtitleStreamIndex != info.SubtitleStreamIndex) { @@ -824,7 +843,7 @@ namespace Emby.Server.Implementations.Session } /// <summary> - /// Used to report that playback has ended for an item + /// Used to report that playback has ended for an item. /// </summary> /// <param name="info">The info.</param> /// <returns>Task.</returns> @@ -1114,13 +1133,13 @@ namespace Emby.Server.Implementations.Session if (items.Any(i => i.GetPlayAccess(user) != PlayAccess.Full)) { throw new ArgumentException( - string.Format(CultureInfo.InvariantCulture, "{0} is not allowed to play media.", user.Name)); + string.Format(CultureInfo.InvariantCulture, "{0} is not allowed to play media.", user.Username)); } } if (user != null && command.ItemIds.Length == 1 - && user.Configuration.EnableNextEpisodeAutoPlay + && user.EnableNextEpisodeAutoPlay && _libraryManager.GetItemById(command.ItemIds[0]) is Episode episode) { var series = episode.Series; @@ -1191,7 +1210,7 @@ namespace Emby.Server.Implementations.Session DtoOptions = new DtoOptions(false) { EnableImages = false, - Fields = new ItemFields[] + Fields = new[] { ItemFields.SortName } @@ -1353,7 +1372,7 @@ namespace Emby.Server.Implementations.Session list.Add(new SessionUserInfo { UserId = userId, - UserName = user.Name + UserName = user.Username }); session.AdditionalUsers = list.ToArray(); @@ -1513,7 +1532,7 @@ namespace Emby.Server.Implementations.Session DeviceName = deviceName, UserId = user.Id, AccessToken = Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture), - UserName = user.Name + UserName = user.Username }; _logger.LogInformation("Creating new access token for user {0}", user.Id); @@ -1710,15 +1729,15 @@ namespace Emby.Server.Implementations.Session return info; } - private string GetImageCacheTag(BaseItem item, ImageType type) + private string GetImageCacheTag(User user) { try { - return _imageProcessor.GetImageCacheTag(item, type); + return _imageProcessor.GetImageCacheTag(user); } - catch (Exception ex) + catch (Exception e) { - _logger.LogError(ex, "Error getting image information for {Type}", type); + _logger.LogError(e, "Error getting image information for profile image"); return null; } } @@ -1827,7 +1846,10 @@ namespace Emby.Server.Implementations.Session { CheckDisposed(); - var adminUserIds = _userManager.Users.Where(i => i.Policy.IsAdministrator).Select(i => i.Id).ToList(); + var adminUserIds = _userManager.Users + .Where(i => i.HasPermission(PermissionKind.IsAdministrator)) + .Select(i => i.Id) + .ToList(); return SendMessageToUserSessions(adminUserIds, name, data, cancellationToken); } diff --git a/Emby.Server.Implementations/Session/SessionWebSocketListener.cs b/Emby.Server.Implementations/Session/SessionWebSocketListener.cs index ef32c692c..b9db6ecd0 100644 --- a/Emby.Server.Implementations/Session/SessionWebSocketListener.cs +++ b/Emby.Server.Implementations/Session/SessionWebSocketListener.cs @@ -14,7 +14,7 @@ using Microsoft.Extensions.Logging; namespace Emby.Server.Implementations.Session { /// <summary> - /// Class SessionWebSocketListener + /// Class SessionWebSocketListener. /// </summary> public sealed class SessionWebSocketListener : IWebSocketListener, IDisposable { @@ -34,12 +34,12 @@ namespace Emby.Server.Implementations.Session public const float ForceKeepAliveFactor = 0.75f; /// <summary> - /// The _session manager + /// The _session manager. /// </summary> private readonly ISessionManager _sessionManager; /// <summary> - /// The _logger + /// The _logger. /// </summary> private readonly ILogger<SessionWebSocketListener> _logger; private readonly ILoggerFactory _loggerFactory; @@ -167,6 +167,7 @@ namespace Emby.Server.Implementations.Session _logger.LogWarning("Multiple attempts to keep alive single WebSocket {0}", webSocket); return; } + webSocket.Closed += OnWebSocketClosed; webSocket.LastKeepAliveDate = DateTime.UtcNow; diff --git a/Emby.Server.Implementations/Sorting/AiredEpisodeOrderComparer.cs b/Emby.Server.Implementations/Sorting/AiredEpisodeOrderComparer.cs index 67e31f7f6..2b7d818be 100644 --- a/Emby.Server.Implementations/Sorting/AiredEpisodeOrderComparer.cs +++ b/Emby.Server.Implementations/Sorting/AiredEpisodeOrderComparer.cs @@ -34,7 +34,7 @@ namespace Emby.Server.Implementations.Sorting if (val != 0) { - //return val; + // return val; } } diff --git a/Emby.Server.Implementations/Sorting/AlbumArtistComparer.cs b/Emby.Server.Implementations/Sorting/AlbumArtistComparer.cs index 0804b01fc..7657cc74e 100644 --- a/Emby.Server.Implementations/Sorting/AlbumArtistComparer.cs +++ b/Emby.Server.Implementations/Sorting/AlbumArtistComparer.cs @@ -8,7 +8,7 @@ using MediaBrowser.Model.Querying; namespace Emby.Server.Implementations.Sorting { /// <summary> - /// Class AlbumArtistComparer + /// Class AlbumArtistComparer. /// </summary> public class AlbumArtistComparer : IBaseItemComparer { diff --git a/Emby.Server.Implementations/Sorting/AlbumComparer.cs b/Emby.Server.Implementations/Sorting/AlbumComparer.cs index 3831a0d2d..7dfdd9ecf 100644 --- a/Emby.Server.Implementations/Sorting/AlbumComparer.cs +++ b/Emby.Server.Implementations/Sorting/AlbumComparer.cs @@ -7,7 +7,7 @@ using MediaBrowser.Model.Querying; namespace Emby.Server.Implementations.Sorting { /// <summary> - /// Class AlbumComparer + /// Class AlbumComparer. /// </summary> public class AlbumComparer : IBaseItemComparer { diff --git a/Emby.Server.Implementations/Sorting/CriticRatingComparer.cs b/Emby.Server.Implementations/Sorting/CriticRatingComparer.cs index adb78dec5..fa136c36d 100644 --- a/Emby.Server.Implementations/Sorting/CriticRatingComparer.cs +++ b/Emby.Server.Implementations/Sorting/CriticRatingComparer.cs @@ -5,7 +5,7 @@ using MediaBrowser.Model.Querying; namespace Emby.Server.Implementations.Sorting { /// <summary> - /// Class CriticRatingComparer + /// Class CriticRatingComparer. /// </summary> public class CriticRatingComparer : IBaseItemComparer { diff --git a/Emby.Server.Implementations/Sorting/DateCreatedComparer.cs b/Emby.Server.Implementations/Sorting/DateCreatedComparer.cs index 8501bd9ee..ea981e840 100644 --- a/Emby.Server.Implementations/Sorting/DateCreatedComparer.cs +++ b/Emby.Server.Implementations/Sorting/DateCreatedComparer.cs @@ -6,7 +6,7 @@ using MediaBrowser.Model.Querying; namespace Emby.Server.Implementations.Sorting { /// <summary> - /// Class DateCreatedComparer + /// Class DateCreatedComparer. /// </summary> public class DateCreatedComparer : IBaseItemComparer { diff --git a/Emby.Server.Implementations/Sorting/DateLastMediaAddedComparer.cs b/Emby.Server.Implementations/Sorting/DateLastMediaAddedComparer.cs index 5c1503ed2..03ff19d21 100644 --- a/Emby.Server.Implementations/Sorting/DateLastMediaAddedComparer.cs +++ b/Emby.Server.Implementations/Sorting/DateLastMediaAddedComparer.cs @@ -1,6 +1,7 @@ #pragma warning disable CS1591 using System; +using Jellyfin.Data.Entities; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Sorting; diff --git a/Emby.Server.Implementations/Sorting/DatePlayedComparer.cs b/Emby.Server.Implementations/Sorting/DatePlayedComparer.cs index 73f59f8cd..16bd2aff8 100644 --- a/Emby.Server.Implementations/Sorting/DatePlayedComparer.cs +++ b/Emby.Server.Implementations/Sorting/DatePlayedComparer.cs @@ -1,4 +1,5 @@ using System; +using Jellyfin.Data.Entities; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Sorting; @@ -7,7 +8,7 @@ using MediaBrowser.Model.Querying; namespace Emby.Server.Implementations.Sorting { /// <summary> - /// Class DatePlayedComparer + /// Class DatePlayedComparer. /// </summary> public class DatePlayedComparer : IUserBaseItemComparer { diff --git a/Emby.Server.Implementations/Sorting/IsFavoriteOrLikeComparer.cs b/Emby.Server.Implementations/Sorting/IsFavoriteOrLikeComparer.cs index aba14c6ca..0c4e82d01 100644 --- a/Emby.Server.Implementations/Sorting/IsFavoriteOrLikeComparer.cs +++ b/Emby.Server.Implementations/Sorting/IsFavoriteOrLikeComparer.cs @@ -1,5 +1,6 @@ #pragma warning disable CS1591 +using Jellyfin.Data.Entities; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Sorting; diff --git a/Emby.Server.Implementations/Sorting/IsPlayedComparer.cs b/Emby.Server.Implementations/Sorting/IsPlayedComparer.cs index 39d9bc68e..d95948406 100644 --- a/Emby.Server.Implementations/Sorting/IsPlayedComparer.cs +++ b/Emby.Server.Implementations/Sorting/IsPlayedComparer.cs @@ -1,5 +1,6 @@ #pragma warning disable CS1591 +using Jellyfin.Data.Entities; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Sorting; diff --git a/Emby.Server.Implementations/Sorting/IsUnplayedComparer.cs b/Emby.Server.Implementations/Sorting/IsUnplayedComparer.cs index 478df4035..1632c5a7a 100644 --- a/Emby.Server.Implementations/Sorting/IsUnplayedComparer.cs +++ b/Emby.Server.Implementations/Sorting/IsUnplayedComparer.cs @@ -1,5 +1,6 @@ #pragma warning disable CS1591 +using Jellyfin.Data.Entities; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Sorting; diff --git a/Emby.Server.Implementations/Sorting/NameComparer.cs b/Emby.Server.Implementations/Sorting/NameComparer.cs index 4eb1549f5..da020d8d8 100644 --- a/Emby.Server.Implementations/Sorting/NameComparer.cs +++ b/Emby.Server.Implementations/Sorting/NameComparer.cs @@ -6,7 +6,7 @@ using MediaBrowser.Model.Querying; namespace Emby.Server.Implementations.Sorting { /// <summary> - /// Class NameComparer + /// Class NameComparer. /// </summary> public class NameComparer : IBaseItemComparer { diff --git a/Emby.Server.Implementations/Sorting/PlayCountComparer.cs b/Emby.Server.Implementations/Sorting/PlayCountComparer.cs index eb74ce1bd..5c2830322 100644 --- a/Emby.Server.Implementations/Sorting/PlayCountComparer.cs +++ b/Emby.Server.Implementations/Sorting/PlayCountComparer.cs @@ -1,3 +1,4 @@ +using Jellyfin.Data.Entities; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Sorting; @@ -6,7 +7,7 @@ using MediaBrowser.Model.Querying; namespace Emby.Server.Implementations.Sorting { /// <summary> - /// Class PlayCountComparer + /// Class PlayCountComparer. /// </summary> public class PlayCountComparer : IUserBaseItemComparer { diff --git a/Emby.Server.Implementations/Sorting/PremiereDateComparer.cs b/Emby.Server.Implementations/Sorting/PremiereDateComparer.cs index 0c944a7a0..92ac04dc6 100644 --- a/Emby.Server.Implementations/Sorting/PremiereDateComparer.cs +++ b/Emby.Server.Implementations/Sorting/PremiereDateComparer.cs @@ -6,7 +6,7 @@ using MediaBrowser.Model.Querying; namespace Emby.Server.Implementations.Sorting { /// <summary> - /// Class PremiereDateComparer + /// Class PremiereDateComparer. /// </summary> public class PremiereDateComparer : IBaseItemComparer { @@ -44,6 +44,7 @@ namespace Emby.Server.Implementations.Sorting // Don't blow up if the item has a bad ProductionYear, just return MinValue } } + return DateTime.MinValue; } diff --git a/Emby.Server.Implementations/Sorting/ProductionYearComparer.cs b/Emby.Server.Implementations/Sorting/ProductionYearComparer.cs index 472a07eb3..e2857df0b 100644 --- a/Emby.Server.Implementations/Sorting/ProductionYearComparer.cs +++ b/Emby.Server.Implementations/Sorting/ProductionYearComparer.cs @@ -5,7 +5,7 @@ using MediaBrowser.Model.Querying; namespace Emby.Server.Implementations.Sorting { /// <summary> - /// Class ProductionYearComparer + /// Class ProductionYearComparer. /// </summary> public class ProductionYearComparer : IBaseItemComparer { diff --git a/Emby.Server.Implementations/Sorting/RandomComparer.cs b/Emby.Server.Implementations/Sorting/RandomComparer.cs index bde8b4534..7739d0418 100644 --- a/Emby.Server.Implementations/Sorting/RandomComparer.cs +++ b/Emby.Server.Implementations/Sorting/RandomComparer.cs @@ -6,7 +6,7 @@ using MediaBrowser.Model.Querying; namespace Emby.Server.Implementations.Sorting { /// <summary> - /// Class RandomComparer + /// Class RandomComparer. /// </summary> public class RandomComparer : IBaseItemComparer { diff --git a/Emby.Server.Implementations/Sorting/RuntimeComparer.cs b/Emby.Server.Implementations/Sorting/RuntimeComparer.cs index 1d2bdde26..f165123ea 100644 --- a/Emby.Server.Implementations/Sorting/RuntimeComparer.cs +++ b/Emby.Server.Implementations/Sorting/RuntimeComparer.cs @@ -6,7 +6,7 @@ using MediaBrowser.Model.Querying; namespace Emby.Server.Implementations.Sorting { /// <summary> - /// Class RuntimeComparer + /// Class RuntimeComparer. /// </summary> public class RuntimeComparer : IBaseItemComparer { diff --git a/Emby.Server.Implementations/Sorting/SortNameComparer.cs b/Emby.Server.Implementations/Sorting/SortNameComparer.cs index cc0571c78..93389fc3e 100644 --- a/Emby.Server.Implementations/Sorting/SortNameComparer.cs +++ b/Emby.Server.Implementations/Sorting/SortNameComparer.cs @@ -6,7 +6,7 @@ using MediaBrowser.Model.Querying; namespace Emby.Server.Implementations.Sorting { /// <summary> - /// Class SortNameComparer + /// Class SortNameComparer. /// </summary> public class SortNameComparer : IBaseItemComparer { diff --git a/Emby.Server.Implementations/SyncPlay/SyncPlayController.cs b/Emby.Server.Implementations/SyncPlay/SyncPlayController.cs index d0812a13f..b1f8fd330 100644 --- a/Emby.Server.Implementations/SyncPlay/SyncPlayController.cs +++ b/Emby.Server.Implementations/SyncPlay/SyncPlayController.cs @@ -102,20 +102,15 @@ namespace Emby.Server.Implementations.SyncPlay return new SessionInfo[] { from }; case BroadcastType.AllGroup: return _group.Participants.Values.Select( - session => session.Session - ).ToArray(); + session => session.Session).ToArray(); case BroadcastType.AllExceptCurrentSession: return _group.Participants.Values.Select( - session => session.Session - ).Where( - session => !session.Id.Equals(from.Id) - ).ToArray(); + session => session.Session).Where( + session => !session.Id.Equals(from.Id)).ToArray(); case BroadcastType.AllReady: return _group.Participants.Values.Where( - session => !session.IsBuffering - ).Select( - session => session.Session - ).ToArray(); + session => !session.IsBuffering).Select( + session => session.Session).ToArray(); default: return Array.Empty<SessionInfo>(); } @@ -314,8 +309,7 @@ namespace Emby.Server.Implementations.SyncPlay // Playback synchronization will mainly happen client side _group.IsPaused = false; _group.LastActivity = DateTime.UtcNow.AddMilliseconds( - delay - ); + delay); var command = NewSyncPlayCommand(SendCommandType.Play); SendCommand(session, BroadcastType.AllGroup, command, cancellationToken); @@ -449,8 +443,7 @@ namespace Emby.Server.Implementations.SyncPlay { // Client that was buffering is recovering, notifying others to resume _group.LastActivity = currentTime.AddMilliseconds( - delay - ); + delay); var command = NewSyncPlayCommand(SendCommandType.Play); SendCommand(session, BroadcastType.AllExceptCurrentSession, command, cancellationToken); } @@ -461,8 +454,7 @@ namespace Emby.Server.Implementations.SyncPlay delay = delay < _group.DefaulPing ? _group.DefaulPing : delay; _group.LastActivity = currentTime.AddMilliseconds( - delay - ); + delay); var command = NewSyncPlayCommand(SendCommandType.Play); SendCommand(session, BroadcastType.AllGroup, command, cancellationToken); diff --git a/Emby.Server.Implementations/SyncPlay/SyncPlayManager.cs b/Emby.Server.Implementations/SyncPlay/SyncPlayManager.cs index 1bfc9a9a5..45a43fd78 100644 --- a/Emby.Server.Implementations/SyncPlay/SyncPlayManager.cs +++ b/Emby.Server.Implementations/SyncPlay/SyncPlayManager.cs @@ -3,13 +3,13 @@ using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Threading; -using Microsoft.Extensions.Logging; -using MediaBrowser.Controller.Entities; +using Jellyfin.Data.Entities; +using Jellyfin.Data.Enums; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Session; using MediaBrowser.Controller.SyncPlay; -using MediaBrowser.Model.Configuration; using MediaBrowser.Model.SyncPlay; +using Microsoft.Extensions.Logging; namespace Emby.Server.Implementations.SyncPlay { @@ -109,14 +109,6 @@ namespace Emby.Server.Implementations.SyncPlay _disposed = true; } - private void CheckDisposed() - { - if (_disposed) - { - throw new ObjectDisposedException(GetType().Name); - } - } - private void OnSessionManagerSessionEnded(object sender, SessionEventArgs e) { var session = e.SessionInfo; @@ -149,38 +141,24 @@ namespace Emby.Server.Implementations.SyncPlay var item = _libraryManager.GetItemById(itemId); // Check ParentalRating access - var hasParentalRatingAccess = true; - if (user.Policy.MaxParentalRating.HasValue) - { - hasParentalRatingAccess = item.InheritedParentalRatingValue <= user.Policy.MaxParentalRating; - } + var hasParentalRatingAccess = !user.MaxParentalAgeRating.HasValue + || item.InheritedParentalRatingValue <= user.MaxParentalAgeRating; - if (!user.Policy.EnableAllFolders && hasParentalRatingAccess) + if (!user.HasPermission(PermissionKind.EnableAllFolders) && hasParentalRatingAccess) { var collections = _libraryManager.GetCollectionFolders(item).Select( - folder => folder.Id.ToString("N", CultureInfo.InvariantCulture) - ); - var intersect = collections.Intersect(user.Policy.EnabledFolders); - return intersect.Any(); - } - else - { - return hasParentalRatingAccess; + folder => folder.Id.ToString("N", CultureInfo.InvariantCulture)); + + return collections.Intersect(user.GetPreference(PreferenceKind.EnabledFolders)).Any(); } + + return hasParentalRatingAccess; } private Guid? GetSessionGroup(SessionInfo session) { - ISyncPlayController group; - _sessionToGroupMap.TryGetValue(session.Id, out group); - if (group != null) - { - return group.GetGroupId(); - } - else - { - return null; - } + _sessionToGroupMap.TryGetValue(session.Id, out var group); + return group?.GetGroupId(); } /// <inheritdoc /> @@ -188,7 +166,7 @@ namespace Emby.Server.Implementations.SyncPlay { var user = _userManager.GetUserById(session.UserId); - if (user.Policy.SyncPlayAccess != SyncPlayAccess.CreateAndJoinGroups) + if (user.SyncPlayAccess != SyncPlayAccess.CreateAndJoinGroups) { _logger.LogWarning("NewGroup: {0} does not have permission to create groups.", session.Id); @@ -196,7 +174,7 @@ namespace Emby.Server.Implementations.SyncPlay { Type = GroupUpdateType.CreateGroupDenied }; - _sessionManager.SendSyncPlayGroupUpdate(session.Id.ToString(), error, CancellationToken.None); + _sessionManager.SendSyncPlayGroupUpdate(session.Id, error, CancellationToken.None); return; } @@ -219,7 +197,7 @@ namespace Emby.Server.Implementations.SyncPlay { var user = _userManager.GetUserById(session.UserId); - if (user.Policy.SyncPlayAccess == SyncPlayAccess.None) + if (user.SyncPlayAccess == SyncPlayAccess.None) { _logger.LogWarning("JoinGroup: {0} does not have access to SyncPlay.", session.Id); @@ -227,7 +205,7 @@ namespace Emby.Server.Implementations.SyncPlay { Type = GroupUpdateType.JoinGroupDenied }; - _sessionManager.SendSyncPlayGroupUpdate(session.Id.ToString(), error, CancellationToken.None); + _sessionManager.SendSyncPlayGroupUpdate(session.Id, error, CancellationToken.None); return; } @@ -244,7 +222,7 @@ namespace Emby.Server.Implementations.SyncPlay { Type = GroupUpdateType.GroupDoesNotExist }; - _sessionManager.SendSyncPlayGroupUpdate(session.Id.ToString(), error, CancellationToken.None); + _sessionManager.SendSyncPlayGroupUpdate(session.Id, error, CancellationToken.None); return; } @@ -257,7 +235,7 @@ namespace Emby.Server.Implementations.SyncPlay GroupId = group.GetGroupId().ToString(), Type = GroupUpdateType.LibraryAccessDenied }; - _sessionManager.SendSyncPlayGroupUpdate(session.Id.ToString(), error, CancellationToken.None); + _sessionManager.SendSyncPlayGroupUpdate(session.Id, error, CancellationToken.None); return; } @@ -281,8 +259,7 @@ namespace Emby.Server.Implementations.SyncPlay // TODO: determine what happens to users that are in a group and get their permissions revoked lock (_groupsLock) { - ISyncPlayController group; - _sessionToGroupMap.TryGetValue(session.Id, out group); + _sessionToGroupMap.TryGetValue(session.Id, out var group); if (group == null) { @@ -292,7 +269,7 @@ namespace Emby.Server.Implementations.SyncPlay { Type = GroupUpdateType.NotInGroup }; - _sessionManager.SendSyncPlayGroupUpdate(session.Id.ToString(), error, CancellationToken.None); + _sessionManager.SendSyncPlayGroupUpdate(session.Id, error, CancellationToken.None); return; } @@ -311,7 +288,7 @@ namespace Emby.Server.Implementations.SyncPlay { var user = _userManager.GetUserById(session.UserId); - if (user.Policy.SyncPlayAccess == SyncPlayAccess.None) + if (user.SyncPlayAccess == SyncPlayAccess.None) { return new List<GroupInfoView>(); } @@ -320,19 +297,15 @@ namespace Emby.Server.Implementations.SyncPlay if (!filterItemId.Equals(Guid.Empty)) { return _groups.Values.Where( - group => group.GetPlayingItemId().Equals(filterItemId) && HasAccessToItem(user, group.GetPlayingItemId()) - ).Select( - group => group.GetInfo() - ).ToList(); + group => group.GetPlayingItemId().Equals(filterItemId) && HasAccessToItem(user, group.GetPlayingItemId())).Select( + group => group.GetInfo()).ToList(); } // Otherwise show all available groups else { return _groups.Values.Where( - group => HasAccessToItem(user, group.GetPlayingItemId()) - ).Select( - group => group.GetInfo() - ).ToList(); + group => HasAccessToItem(user, group.GetPlayingItemId())).Select( + group => group.GetInfo()).ToList(); } } @@ -341,7 +314,7 @@ namespace Emby.Server.Implementations.SyncPlay { var user = _userManager.GetUserById(session.UserId); - if (user.Policy.SyncPlayAccess == SyncPlayAccess.None) + if (user.SyncPlayAccess == SyncPlayAccess.None) { _logger.LogWarning("HandleRequest: {0} does not have access to SyncPlay.", session.Id); @@ -349,14 +322,13 @@ namespace Emby.Server.Implementations.SyncPlay { Type = GroupUpdateType.JoinGroupDenied }; - _sessionManager.SendSyncPlayGroupUpdate(session.Id.ToString(), error, CancellationToken.None); + _sessionManager.SendSyncPlayGroupUpdate(session.Id, error, CancellationToken.None); return; } lock (_groupsLock) { - ISyncPlayController group; - _sessionToGroupMap.TryGetValue(session.Id, out group); + _sessionToGroupMap.TryGetValue(session.Id, out var group); if (group == null) { @@ -366,7 +338,7 @@ namespace Emby.Server.Implementations.SyncPlay { Type = GroupUpdateType.NotInGroup }; - _sessionManager.SendSyncPlayGroupUpdate(session.Id.ToString(), error, CancellationToken.None); + _sessionManager.SendSyncPlayGroupUpdate(session.Id, error, CancellationToken.None); return; } @@ -393,8 +365,7 @@ namespace Emby.Server.Implementations.SyncPlay throw new InvalidOperationException("Session not in any group!"); } - ISyncPlayController tempGroup; - _sessionToGroupMap.Remove(session.Id, out tempGroup); + _sessionToGroupMap.Remove(session.Id, out var tempGroup); if (!tempGroup.GetGroupId().Equals(group.GetGroupId())) { diff --git a/Emby.Server.Implementations/TV/TVSeriesManager.cs b/Emby.Server.Implementations/TV/TVSeriesManager.cs index 383615f74..21c12ae79 100644 --- a/Emby.Server.Implementations/TV/TVSeriesManager.cs +++ b/Emby.Server.Implementations/TV/TVSeriesManager.cs @@ -4,13 +4,17 @@ using System; using System.Collections.Generic; using System.Globalization; using System.Linq; +using Jellyfin.Data.Entities; +using Jellyfin.Data.Enums; +using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.TV; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Querying; +using Episode = MediaBrowser.Controller.Entities.TV.Episode; +using Series = MediaBrowser.Controller.Entities.TV.Series; namespace Emby.Server.Implementations.TV { @@ -73,7 +77,8 @@ namespace Emby.Server.Implementations.TV { parents = _libraryManager.GetUserRootFolder().GetChildren(user, true) .Where(i => i is Folder) - .Where(i => !user.Configuration.LatestItemsExcludes.Contains(i.Id.ToString("N", CultureInfo.InvariantCulture))) + .Where(i => !user.GetPreference(PreferenceKind.LatestItemExcludes) + .Contains(i.Id.ToString("N", CultureInfo.InvariantCulture))) .ToArray(); } @@ -144,7 +149,7 @@ namespace Emby.Server.Implementations.TV var allNextUp = seriesKeys .Select(i => GetNextUp(i, currentUser, dtoOptions)); - //allNextUp = allNextUp.OrderByDescending(i => i.Item1); + // allNextUp = allNextUp.OrderByDescending(i => i.Item1); // If viewing all next up for all series, remove first episodes // But if that returns empty, keep those first episodes (avoid completely empty view) @@ -191,7 +196,7 @@ namespace Emby.Server.Implementations.TV { AncestorWithPresentationUniqueKey = null, SeriesPresentationUniqueKey = seriesKey, - IncludeItemTypes = new[] { typeof(Episode).Name }, + IncludeItemTypes = new[] { nameof(Episode) }, OrderBy = new[] { new ValueTuple<string, SortOrder>(ItemSortBy.SortName, SortOrder.Descending) }, IsPlayed = true, Limit = 1, @@ -204,7 +209,6 @@ namespace Emby.Server.Implementations.TV }, EnableImages = false } - }).FirstOrDefault(); Func<Episode> getEpisode = () => @@ -219,9 +223,8 @@ namespace Emby.Server.Implementations.TV IsPlayed = false, IsVirtualItem = false, ParentIndexNumberNotEquals = 0, - MinSortName = lastWatchedEpisode == null ? null : lastWatchedEpisode.SortName, + MinSortName = lastWatchedEpisode?.SortName, DtoOptions = dtoOptions - }).Cast<Episode>().FirstOrDefault(); }; @@ -253,6 +256,7 @@ namespace Emby.Server.Implementations.TV { items = items.Skip(query.StartIndex.Value); } + if (query.Limit.HasValue) { items = items.Take(query.Limit.Value); diff --git a/Emby.Server.Implementations/Udp/UdpServer.cs b/Emby.Server.Implementations/Udp/UdpServer.cs index a26f714b1..bf8a436b4 100644 --- a/Emby.Server.Implementations/Udp/UdpServer.cs +++ b/Emby.Server.Implementations/Udp/UdpServer.cs @@ -18,7 +18,7 @@ namespace Emby.Server.Implementations.Udp public sealed class UdpServer : IDisposable { /// <summary> - /// The _logger + /// The _logger. /// </summary> private readonly ILogger _logger; private readonly IServerApplicationHost _appHost; |
