From ab396225eaf486932fdb2f23eefa1cbfecbb27f4 Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Tue, 30 Jun 2020 21:44:41 -0400 Subject: [PATCH 01/60] Migrate Display Preferences to EF Core --- Emby.Dlna/ContentDirectory/ControlHandler.cs | 1 + .../ApplicationHost.cs | 3 - .../Channels/ChannelManager.cs | 1 + .../SqliteDisplayPreferencesRepository.cs | 225 ---------- .../Data/SqliteItemRepository.cs | 1 + .../Emby.Server.Implementations.csproj | 2 +- .../Images/CollectionFolderImageProvider.cs | 2 +- .../Images/FolderImageProvider.cs | 2 +- .../Images/GenreImageProvider.cs | 1 + .../Library/LibraryManager.cs | 1 - .../Library/MusicManager.cs | 2 +- .../Library/SearchEngine.cs | 2 +- .../LiveTv/EmbyTV/EmbyTV.cs | 1 + Jellyfin.Data/Entities/DisplayPreferences.cs | 72 ++++ Jellyfin.Data/Entities/HomeSection.cs | 21 + Jellyfin.Data/Entities/User.cs | 5 + Jellyfin.Data/Enums/HomeSectionType.cs | 53 +++ Jellyfin.Data/Enums/IndexingKind.cs | 20 + .../Enums}/ScrollDirection.cs | 8 +- .../Enums}/SortOrder.cs | 8 +- Jellyfin.Data/Enums/ViewType.cs | 38 ++ .../DisplayPreferencesManager.cs | 49 +++ Jellyfin.Server.Implementations/JellyfinDb.cs | 2 + ...30170339_AddDisplayPreferences.Designer.cs | 403 ++++++++++++++++++ .../20200630170339_AddDisplayPreferences.cs | 92 ++++ .../Migrations/JellyfinDbModelSnapshot.cs | 93 +++- Jellyfin.Server/CoreAppHost.cs | 2 + Jellyfin.Server/Migrations/MigrationRunner.cs | 3 +- .../Routines/MigrateDisplayPreferencesDb.cs | 118 +++++ MediaBrowser.Api/ChannelService.cs | 2 +- MediaBrowser.Api/DisplayPreferencesService.cs | 92 +++- MediaBrowser.Api/Movies/MoviesService.cs | 1 + MediaBrowser.Api/SuggestionsService.cs | 1 + MediaBrowser.Api/TvShowsService.cs | 1 + .../UserLibrary/BaseItemsRequest.cs | 5 +- .../Entities/UserViewBuilder.cs | 1 + .../IDisplayPreferencesManager.cs | 25 ++ .../Library/ILibraryManager.cs | 1 + .../IDisplayPreferencesRepository.cs | 53 --- MediaBrowser.Controller/Playlists/Playlist.cs | 1 + MediaBrowser.Model/Dlna/SortCriteria.cs | 2 +- ...references.cs => DisplayPreferencesDto.cs} | 12 +- .../LiveTv/LiveTvChannelQuery.cs | 2 +- MediaBrowser.Model/LiveTv/SeriesTimerQuery.cs | 2 +- 44 files changed, 1101 insertions(+), 331 deletions(-) delete mode 100644 Emby.Server.Implementations/Data/SqliteDisplayPreferencesRepository.cs create mode 100644 Jellyfin.Data/Entities/DisplayPreferences.cs create mode 100644 Jellyfin.Data/Entities/HomeSection.cs create mode 100644 Jellyfin.Data/Enums/HomeSectionType.cs create mode 100644 Jellyfin.Data/Enums/IndexingKind.cs rename {MediaBrowser.Model/Entities => Jellyfin.Data/Enums}/ScrollDirection.cs (53%) rename {MediaBrowser.Model/Entities => Jellyfin.Data/Enums}/SortOrder.cs (56%) create mode 100644 Jellyfin.Data/Enums/ViewType.cs create mode 100644 Jellyfin.Server.Implementations/DisplayPreferencesManager.cs create mode 100644 Jellyfin.Server.Implementations/Migrations/20200630170339_AddDisplayPreferences.Designer.cs create mode 100644 Jellyfin.Server.Implementations/Migrations/20200630170339_AddDisplayPreferences.cs create mode 100644 Jellyfin.Server/Migrations/Routines/MigrateDisplayPreferencesDb.cs create mode 100644 MediaBrowser.Controller/IDisplayPreferencesManager.cs delete mode 100644 MediaBrowser.Controller/Persistence/IDisplayPreferencesRepository.cs rename MediaBrowser.Model/Entities/{DisplayPreferences.cs => DisplayPreferencesDto.cs} (94%) diff --git a/Emby.Dlna/ContentDirectory/ControlHandler.cs b/Emby.Dlna/ContentDirectory/ControlHandler.cs index 291de5245b..00821bf780 100644 --- a/Emby.Dlna/ContentDirectory/ControlHandler.cs +++ b/Emby.Dlna/ContentDirectory/ControlHandler.cs @@ -11,6 +11,7 @@ using System.Xml; using Emby.Dlna.Didl; using Emby.Dlna.Service; using Jellyfin.Data.Entities; +using Jellyfin.Data.Enums; using MediaBrowser.Common.Extensions; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Drawing; diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index f6077400d6..f6f10beb09 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -554,8 +554,6 @@ namespace Emby.Server.Implementations serviceCollection.AddSingleton(); serviceCollection.AddSingleton(); - serviceCollection.AddSingleton(); - serviceCollection.AddSingleton(); serviceCollection.AddSingleton(); @@ -650,7 +648,6 @@ namespace Emby.Server.Implementations _httpServer = Resolve(); _httpClient = Resolve(); - ((SqliteDisplayPreferencesRepository)Resolve()).Initialize(); ((AuthenticationRepository)Resolve()).Initialize(); SetStaticProperties(); diff --git a/Emby.Server.Implementations/Channels/ChannelManager.cs b/Emby.Server.Implementations/Channels/ChannelManager.cs index c803d9d825..2a7cddd87b 100644 --- a/Emby.Server.Implementations/Channels/ChannelManager.cs +++ b/Emby.Server.Implementations/Channels/ChannelManager.cs @@ -7,6 +7,7 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; using Jellyfin.Data.Entities; +using Jellyfin.Data.Enums; using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Progress; using MediaBrowser.Controller.Channels; diff --git a/Emby.Server.Implementations/Data/SqliteDisplayPreferencesRepository.cs b/Emby.Server.Implementations/Data/SqliteDisplayPreferencesRepository.cs deleted file mode 100644 index 5597155a8d..0000000000 --- a/Emby.Server.Implementations/Data/SqliteDisplayPreferencesRepository.cs +++ /dev/null @@ -1,225 +0,0 @@ -#pragma warning disable CS1591 - -using System; -using System.Collections.Generic; -using System.Globalization; -using System.IO; -using System.Text.Json; -using System.Threading; -using MediaBrowser.Common.Configuration; -using MediaBrowser.Common.Extensions; -using MediaBrowser.Common.Json; -using MediaBrowser.Controller.Persistence; -using MediaBrowser.Model.Entities; -using MediaBrowser.Model.IO; -using Microsoft.Extensions.Logging; -using SQLitePCL.pretty; - -namespace Emby.Server.Implementations.Data -{ - /// - /// Class SQLiteDisplayPreferencesRepository. - /// - public class SqliteDisplayPreferencesRepository : BaseSqliteRepository, IDisplayPreferencesRepository - { - private readonly IFileSystem _fileSystem; - - private readonly JsonSerializerOptions _jsonOptions; - - public SqliteDisplayPreferencesRepository(ILogger logger, IApplicationPaths appPaths, IFileSystem fileSystem) - : base(logger) - { - _fileSystem = fileSystem; - - _jsonOptions = JsonDefaults.GetOptions(); - - DbFilePath = Path.Combine(appPaths.DataPath, "displaypreferences.db"); - } - - /// - /// Gets the name of the repository. - /// - /// The name. - public string Name => "SQLite"; - - public void Initialize() - { - try - { - InitializeInternal(); - } - catch (Exception ex) - { - Logger.LogError(ex, "Error loading database file. Will reset and retry."); - - _fileSystem.DeleteFile(DbFilePath); - - InitializeInternal(); - } - } - - /// - /// Opens the connection to the database. - /// - /// Task. - private void InitializeInternal() - { - string[] queries = - { - "create table if not exists userdisplaypreferences (id GUID NOT NULL, userId GUID NOT NULL, client text NOT NULL, data BLOB NOT NULL)", - "create unique index if not exists userdisplaypreferencesindex on userdisplaypreferences (id, userId, client)" - }; - - using (var connection = GetConnection()) - { - connection.RunQueries(queries); - } - } - - /// - /// Save the display preferences associated with an item in the repo. - /// - /// The display preferences. - /// The user id. - /// The client. - /// The cancellation token. - /// item - public void SaveDisplayPreferences(DisplayPreferences displayPreferences, Guid userId, string client, CancellationToken cancellationToken) - { - if (displayPreferences == null) - { - throw new ArgumentNullException(nameof(displayPreferences)); - } - - if (string.IsNullOrEmpty(displayPreferences.Id)) - { - throw new ArgumentException("Display preferences has an invalid Id", nameof(displayPreferences)); - } - - cancellationToken.ThrowIfCancellationRequested(); - - using (var connection = GetConnection()) - { - connection.RunInTransaction( - db => SaveDisplayPreferences(displayPreferences, userId, client, db), - TransactionMode); - } - } - - private void SaveDisplayPreferences(DisplayPreferences displayPreferences, Guid userId, string client, IDatabaseConnection connection) - { - var serialized = JsonSerializer.SerializeToUtf8Bytes(displayPreferences, _jsonOptions); - - using (var statement = connection.PrepareStatement("replace into userdisplaypreferences (id, userid, client, data) values (@id, @userId, @client, @data)")) - { - statement.TryBind("@id", new Guid(displayPreferences.Id).ToByteArray()); - statement.TryBind("@userId", userId.ToByteArray()); - statement.TryBind("@client", client); - statement.TryBind("@data", serialized); - - statement.MoveNext(); - } - } - - /// - /// Save all display preferences associated with a user in the repo. - /// - /// The display preferences. - /// The user id. - /// The cancellation token. - /// item - public void SaveAllDisplayPreferences(IEnumerable displayPreferences, Guid userId, CancellationToken cancellationToken) - { - if (displayPreferences == null) - { - throw new ArgumentNullException(nameof(displayPreferences)); - } - - cancellationToken.ThrowIfCancellationRequested(); - - using (var connection = GetConnection()) - { - connection.RunInTransaction( - db => - { - foreach (var displayPreference in displayPreferences) - { - SaveDisplayPreferences(displayPreference, userId, displayPreference.Client, db); - } - }, - TransactionMode); - } - } - - /// - /// Gets the display preferences. - /// - /// The display preferences id. - /// The user id. - /// The client. - /// Task{DisplayPreferences}. - /// item - public DisplayPreferences GetDisplayPreferences(string displayPreferencesId, Guid userId, string client) - { - if (string.IsNullOrEmpty(displayPreferencesId)) - { - throw new ArgumentNullException(nameof(displayPreferencesId)); - } - - var guidId = displayPreferencesId.GetMD5(); - - using (var connection = GetConnection(true)) - { - using (var statement = connection.PrepareStatement("select data from userdisplaypreferences where id = @id and userId=@userId and client=@client")) - { - statement.TryBind("@id", guidId.ToByteArray()); - statement.TryBind("@userId", userId.ToByteArray()); - statement.TryBind("@client", client); - - foreach (var row in statement.ExecuteQuery()) - { - return Get(row); - } - } - } - - return new DisplayPreferences - { - Id = guidId.ToString("N", CultureInfo.InvariantCulture) - }; - } - - /// - /// Gets all display preferences for the given user. - /// - /// The user id. - /// Task{DisplayPreferences}. - /// item - public IEnumerable GetAllDisplayPreferences(Guid userId) - { - var list = new List(); - - using (var connection = GetConnection(true)) - using (var statement = connection.PrepareStatement("select data from userdisplaypreferences where userId=@userId")) - { - statement.TryBind("@userId", userId.ToByteArray()); - - foreach (var row in statement.ExecuteQuery()) - { - list.Add(Get(row)); - } - } - - return list; - } - - private DisplayPreferences Get(IReadOnlyList row) - => JsonSerializer.Deserialize(row[0].ToBlob(), _jsonOptions); - - public void SaveDisplayPreferences(DisplayPreferences displayPreferences, string userId, string client, CancellationToken cancellationToken) - => SaveDisplayPreferences(displayPreferences, new Guid(userId), client, cancellationToken); - - public DisplayPreferences GetDisplayPreferences(string displayPreferencesId, string userId, string client) - => GetDisplayPreferences(displayPreferencesId, new Guid(userId), client); - } -} diff --git a/Emby.Server.Implementations/Data/SqliteItemRepository.cs b/Emby.Server.Implementations/Data/SqliteItemRepository.cs index a6390b1ef2..04e5e570f7 100644 --- a/Emby.Server.Implementations/Data/SqliteItemRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteItemRepository.cs @@ -9,6 +9,7 @@ using System.Text; using System.Text.Json; using System.Threading; using Emby.Server.Implementations.Playlists; +using Jellyfin.Data.Enums; using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Json; using MediaBrowser.Controller; diff --git a/Emby.Server.Implementations/Emby.Server.Implementations.csproj b/Emby.Server.Implementations/Emby.Server.Implementations.csproj index f7ad59c10f..548dc7085c 100644 --- a/Emby.Server.Implementations/Emby.Server.Implementations.csproj +++ b/Emby.Server.Implementations/Emby.Server.Implementations.csproj @@ -54,7 +54,7 @@ netstandard2.1 false true - true + true diff --git a/Emby.Server.Implementations/Images/CollectionFolderImageProvider.cs b/Emby.Server.Implementations/Images/CollectionFolderImageProvider.cs index da88b8d8ab..161b4c4528 100644 --- a/Emby.Server.Implementations/Images/CollectionFolderImageProvider.cs +++ b/Emby.Server.Implementations/Images/CollectionFolderImageProvider.cs @@ -3,7 +3,7 @@ using System; using System.Collections.Generic; using System.IO; -using Emby.Server.Implementations.Images; +using Jellyfin.Data.Enums; using MediaBrowser.Common.Configuration; using MediaBrowser.Controller.Drawing; using MediaBrowser.Controller.Dto; diff --git a/Emby.Server.Implementations/Images/FolderImageProvider.cs b/Emby.Server.Implementations/Images/FolderImageProvider.cs index e9523386ea..0224ab32a0 100644 --- a/Emby.Server.Implementations/Images/FolderImageProvider.cs +++ b/Emby.Server.Implementations/Images/FolderImageProvider.cs @@ -1,7 +1,7 @@ #pragma warning disable CS1591 using System.Collections.Generic; -using Emby.Server.Implementations.Images; +using Jellyfin.Data.Enums; using MediaBrowser.Common.Configuration; using MediaBrowser.Controller.Drawing; using MediaBrowser.Controller.Dto; diff --git a/Emby.Server.Implementations/Images/GenreImageProvider.cs b/Emby.Server.Implementations/Images/GenreImageProvider.cs index d2aeccdb21..1cd4cd66bd 100644 --- a/Emby.Server.Implementations/Images/GenreImageProvider.cs +++ b/Emby.Server.Implementations/Images/GenreImageProvider.cs @@ -1,6 +1,7 @@ #pragma warning disable CS1591 using System.Collections.Generic; +using Jellyfin.Data.Enums; using MediaBrowser.Common.Configuration; using MediaBrowser.Controller.Drawing; using MediaBrowser.Controller.Dto; diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs index 77d44e1313..4690f20946 100644 --- a/Emby.Server.Implementations/Library/LibraryManager.cs +++ b/Emby.Server.Implementations/Library/LibraryManager.cs @@ -50,7 +50,6 @@ using Microsoft.Extensions.Logging; using Episode = MediaBrowser.Controller.Entities.TV.Episode; using Genre = MediaBrowser.Controller.Entities.Genre; using Person = MediaBrowser.Controller.Entities.Person; -using SortOrder = MediaBrowser.Model.Entities.SortOrder; using VideoResolver = Emby.Naming.Video.VideoResolver; namespace Emby.Server.Implementations.Library diff --git a/Emby.Server.Implementations/Library/MusicManager.cs b/Emby.Server.Implementations/Library/MusicManager.cs index 0bdc599144..877fdec86e 100644 --- a/Emby.Server.Implementations/Library/MusicManager.cs +++ b/Emby.Server.Implementations/Library/MusicManager.cs @@ -4,12 +4,12 @@ using System; using System.Collections.Generic; using System.Linq; using Jellyfin.Data.Entities; +using Jellyfin.Data.Enums; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Playlists; -using MediaBrowser.Model.Entities; using MediaBrowser.Model.Querying; using MusicAlbum = MediaBrowser.Controller.Entities.Audio.MusicAlbum; diff --git a/Emby.Server.Implementations/Library/SearchEngine.cs b/Emby.Server.Implementations/Library/SearchEngine.cs index 3df9cc06f1..e3e554824a 100644 --- a/Emby.Server.Implementations/Library/SearchEngine.cs +++ b/Emby.Server.Implementations/Library/SearchEngine.cs @@ -4,12 +4,12 @@ using System; using System.Collections.Generic; using System.Linq; using Jellyfin.Data.Entities; +using Jellyfin.Data.Enums; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Extensions; using MediaBrowser.Controller.Library; -using MediaBrowser.Model.Entities; using MediaBrowser.Model.Querying; using MediaBrowser.Model.Search; using Microsoft.Extensions.Logging; diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs index 7b0fcbc9e5..80e09f0a34 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs @@ -12,6 +12,7 @@ using System.Threading; using System.Threading.Tasks; using System.Xml; using Emby.Server.Implementations.Library; +using Jellyfin.Data.Enums; using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Net; diff --git a/Jellyfin.Data/Entities/DisplayPreferences.cs b/Jellyfin.Data/Entities/DisplayPreferences.cs new file mode 100644 index 0000000000..668030149b --- /dev/null +++ b/Jellyfin.Data/Entities/DisplayPreferences.cs @@ -0,0 +1,72 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using Jellyfin.Data.Enums; + +namespace Jellyfin.Data.Entities +{ + public class DisplayPreferences + { + public DisplayPreferences(string client, Guid userId) + { + RememberIndexing = false; + ShowBackdrop = true; + Client = client; + UserId = userId; + + HomeSections = new HashSet(); + } + + protected DisplayPreferences() + { + } + + [Required] + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public int Id { get; protected set; } + + [Required] + public Guid UserId { get; set; } + + /// + /// Gets or sets the id of the associated item. + /// + /// + /// This is currently unused. In the future, this will allow us to have users set + /// display preferences per item. + /// + public Guid? ItemId { get; set; } + + [Required] + [MaxLength(64)] + [StringLength(64)] + public string Client { get; set; } + + [Required] + public bool RememberIndexing { get; set; } + + [Required] + public bool RememberSorting { get; set; } + + [Required] + public SortOrder SortOrder { get; set; } + + [Required] + public bool ShowSidebar { get; set; } + + [Required] + public bool ShowBackdrop { get; set; } + + public string SortBy { get; set; } + + public ViewType? ViewType { get; set; } + + [Required] + public ScrollDirection ScrollDirection { get; set; } + + public IndexingKind? IndexBy { get; set; } + + public virtual ICollection HomeSections { get; protected set; } + } +} diff --git a/Jellyfin.Data/Entities/HomeSection.cs b/Jellyfin.Data/Entities/HomeSection.cs new file mode 100644 index 0000000000..f39956a54e --- /dev/null +++ b/Jellyfin.Data/Entities/HomeSection.cs @@ -0,0 +1,21 @@ +using System; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using Jellyfin.Data.Enums; + +namespace Jellyfin.Data.Entities +{ + public class HomeSection + { + [Key] + [Required] + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public int Id { get; protected set; } + + public int DisplayPreferencesId { get; set; } + + public int Order { get; set; } + + public HomeSectionType Type { get; set; } + } +} diff --git a/Jellyfin.Data/Entities/User.cs b/Jellyfin.Data/Entities/User.cs index b89b0a8f45..d93144e3a5 100644 --- a/Jellyfin.Data/Entities/User.cs +++ b/Jellyfin.Data/Entities/User.cs @@ -349,6 +349,11 @@ namespace Jellyfin.Data.Entities /// public virtual ICollection AccessSchedules { get; protected set; } + /// + /// Gets or sets the list of item display preferences. + /// + public virtual ICollection DisplayPreferences { get; protected set; } + /* /// /// Gets or sets the list of groups this user is a member of. diff --git a/Jellyfin.Data/Enums/HomeSectionType.cs b/Jellyfin.Data/Enums/HomeSectionType.cs new file mode 100644 index 0000000000..be764c5924 --- /dev/null +++ b/Jellyfin.Data/Enums/HomeSectionType.cs @@ -0,0 +1,53 @@ +namespace Jellyfin.Data.Enums +{ + /// + /// An enum representing the different options for the home screen sections. + /// + public enum HomeSectionType + { + /// + /// My Media. + /// + SmallLibraryTiles = 0, + + /// + /// My Media Small. + /// + LibraryButtons = 1, + + /// + /// Active Recordings. + /// + ActiveRecordings = 2, + + /// + /// Continue Watching. + /// + Resume = 3, + + /// + /// Continue Listening. + /// + ResumeAudio = 4, + + /// + /// Latest Media. + /// + LatestMedia = 5, + + /// + /// Next Up. + /// + NextUp = 6, + + /// + /// Live TV. + /// + LiveTv = 7, + + /// + /// None. + /// + None = 8 + } +} diff --git a/Jellyfin.Data/Enums/IndexingKind.cs b/Jellyfin.Data/Enums/IndexingKind.cs new file mode 100644 index 0000000000..c4d8e70ca6 --- /dev/null +++ b/Jellyfin.Data/Enums/IndexingKind.cs @@ -0,0 +1,20 @@ +namespace Jellyfin.Data.Enums +{ + public enum IndexingKind + { + /// + /// Index by the premiere date. + /// + PremiereDate, + + /// + /// Index by the production year. + /// + ProductionYear, + + /// + /// Index by the community rating. + /// + CommunityRating + } +} diff --git a/MediaBrowser.Model/Entities/ScrollDirection.cs b/Jellyfin.Data/Enums/ScrollDirection.cs similarity index 53% rename from MediaBrowser.Model/Entities/ScrollDirection.cs rename to Jellyfin.Data/Enums/ScrollDirection.cs index a1de0edcbb..382f585ba0 100644 --- a/MediaBrowser.Model/Entities/ScrollDirection.cs +++ b/Jellyfin.Data/Enums/ScrollDirection.cs @@ -1,17 +1,17 @@ -namespace MediaBrowser.Model.Entities +namespace Jellyfin.Data.Enums { /// - /// Enum ScrollDirection. + /// An enum representing the axis that should be scrolled. /// public enum ScrollDirection { /// - /// The horizontal. + /// Horizontal scrolling direction. /// Horizontal, /// - /// The vertical. + /// Vertical scrolling direction. /// Vertical } diff --git a/MediaBrowser.Model/Entities/SortOrder.cs b/Jellyfin.Data/Enums/SortOrder.cs similarity index 56% rename from MediaBrowser.Model/Entities/SortOrder.cs rename to Jellyfin.Data/Enums/SortOrder.cs index f3abc06f33..309fa78775 100644 --- a/MediaBrowser.Model/Entities/SortOrder.cs +++ b/Jellyfin.Data/Enums/SortOrder.cs @@ -1,17 +1,17 @@ -namespace MediaBrowser.Model.Entities +namespace Jellyfin.Data.Enums { /// - /// Enum SortOrder. + /// An enum representing the sorting order. /// public enum SortOrder { /// - /// The ascending. + /// Sort in increasing order. /// Ascending, /// - /// The descending. + /// Sort in decreasing order. /// Descending } diff --git a/Jellyfin.Data/Enums/ViewType.cs b/Jellyfin.Data/Enums/ViewType.cs new file mode 100644 index 0000000000..595429ab1b --- /dev/null +++ b/Jellyfin.Data/Enums/ViewType.cs @@ -0,0 +1,38 @@ +namespace Jellyfin.Data.Enums +{ + /// + /// An enum representing the type of view for a library or collection. + /// + public enum ViewType + { + /// + /// Shows banners. + /// + Banner = 0, + + /// + /// Shows a list of content. + /// + List = 1, + + /// + /// Shows poster artwork. + /// + Poster = 2, + + /// + /// Shows poster artwork with a card containing the name and year. + /// + PosterCard = 3, + + /// + /// Shows a thumbnail. + /// + Thumb = 4, + + /// + /// Shows a thumbnail with a card containing the name and year. + /// + ThumbCard = 5 + } +} diff --git a/Jellyfin.Server.Implementations/DisplayPreferencesManager.cs b/Jellyfin.Server.Implementations/DisplayPreferencesManager.cs new file mode 100644 index 0000000000..132e74c6aa --- /dev/null +++ b/Jellyfin.Server.Implementations/DisplayPreferencesManager.cs @@ -0,0 +1,49 @@ +using System; +using System.Linq; +using Jellyfin.Data.Entities; +using MediaBrowser.Controller; + +namespace Jellyfin.Server.Implementations +{ + /// + /// Manages the storage and retrieval of display preferences through Entity Framework. + /// + public class DisplayPreferencesManager : IDisplayPreferencesManager + { + private readonly JellyfinDbProvider _dbProvider; + + /// + /// Initializes a new instance of the class. + /// + /// The Jellyfin db provider. + public DisplayPreferencesManager(JellyfinDbProvider dbProvider) + { + _dbProvider = dbProvider; + } + + /// + public DisplayPreferences GetDisplayPreferences(Guid userId, string client) + { + var dbContext = _dbProvider.CreateContext(); + var user = dbContext.Users.Find(userId); +#pragma warning disable CA1307 + var prefs = user.DisplayPreferences.FirstOrDefault(pref => string.Equals(pref.Client, client)); + + if (prefs == null) + { + prefs = new DisplayPreferences(client, userId); + user.DisplayPreferences.Add(prefs); + } + + return prefs; + } + + /// + public void SaveChanges(DisplayPreferences preferences) + { + var dbContext = _dbProvider.CreateContext(); + dbContext.Update(preferences); + dbContext.SaveChanges(); + } + } +} diff --git a/Jellyfin.Server.Implementations/JellyfinDb.cs b/Jellyfin.Server.Implementations/JellyfinDb.cs index 53120a763e..774970e942 100644 --- a/Jellyfin.Server.Implementations/JellyfinDb.cs +++ b/Jellyfin.Server.Implementations/JellyfinDb.cs @@ -27,6 +27,8 @@ namespace Jellyfin.Server.Implementations public virtual DbSet ActivityLogs { get; set; } + public virtual DbSet DisplayPreferences { get; set; } + public virtual DbSet ImageInfos { get; set; } public virtual DbSet Permissions { get; set; } diff --git a/Jellyfin.Server.Implementations/Migrations/20200630170339_AddDisplayPreferences.Designer.cs b/Jellyfin.Server.Implementations/Migrations/20200630170339_AddDisplayPreferences.Designer.cs new file mode 100644 index 0000000000..75f9bb7a3c --- /dev/null +++ b/Jellyfin.Server.Implementations/Migrations/20200630170339_AddDisplayPreferences.Designer.cs @@ -0,0 +1,403 @@ +#pragma warning disable CS1591 + +// +using System; +using Jellyfin.Server.Implementations; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +namespace Jellyfin.Server.Implementations.Migrations +{ + [DbContext(typeof(JellyfinDb))] + [Migration("20200630170339_AddDisplayPreferences")] + partial class AddDisplayPreferences + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasDefaultSchema("jellyfin") + .HasAnnotation("ProductVersion", "3.1.5"); + + modelBuilder.Entity("Jellyfin.Data.Entities.AccessSchedule", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("DayOfWeek") + .HasColumnType("INTEGER"); + + b.Property("EndHour") + .HasColumnType("REAL"); + + b.Property("StartHour") + .HasColumnType("REAL"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AccessSchedules"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.ActivityLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("DateCreated") + .HasColumnType("TEXT"); + + b.Property("ItemId") + .HasColumnType("TEXT") + .HasMaxLength(256); + + b.Property("LogSeverity") + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT") + .HasMaxLength(512); + + b.Property("Overview") + .HasColumnType("TEXT") + .HasMaxLength(512); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("INTEGER"); + + b.Property("ShortOverview") + .HasColumnType("TEXT") + .HasMaxLength(512); + + b.Property("Type") + .IsRequired() + .HasColumnType("TEXT") + .HasMaxLength(256); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("ActivityLogs"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.DisplayPreferences", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Client") + .IsRequired() + .HasColumnType("TEXT") + .HasMaxLength(64); + + b.Property("IndexBy") + .HasColumnType("INTEGER"); + + b.Property("ItemId") + .HasColumnType("TEXT"); + + b.Property("RememberIndexing") + .HasColumnType("INTEGER"); + + b.Property("RememberSorting") + .HasColumnType("INTEGER"); + + b.Property("ScrollDirection") + .HasColumnType("INTEGER"); + + b.Property("ShowBackdrop") + .HasColumnType("INTEGER"); + + b.Property("ShowSidebar") + .HasColumnType("INTEGER"); + + b.Property("SortBy") + .HasColumnType("TEXT"); + + b.Property("SortOrder") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("ViewType") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("DisplayPreferences"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.HomeSection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("DisplayPreferencesId") + .HasColumnType("INTEGER"); + + b.Property("Order") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("DisplayPreferencesId"); + + b.ToTable("HomeSection"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.ImageInfo", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("LastModified") + .HasColumnType("TEXT"); + + b.Property("Path") + .IsRequired() + .HasColumnType("TEXT") + .HasMaxLength(512); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("ImageInfos"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.Permission", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Kind") + .HasColumnType("INTEGER"); + + b.Property("Permission_Permissions_Guid") + .HasColumnType("TEXT"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("INTEGER"); + + b.Property("Value") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("Permission_Permissions_Guid"); + + b.ToTable("Permissions"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.Preference", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Kind") + .HasColumnType("INTEGER"); + + b.Property("Preference_Preferences_Guid") + .HasColumnType("TEXT"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("INTEGER"); + + b.Property("Value") + .IsRequired() + .HasColumnType("TEXT") + .HasMaxLength(65535); + + b.HasKey("Id"); + + b.HasIndex("Preference_Preferences_Guid"); + + b.ToTable("Preferences"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AudioLanguagePreference") + .HasColumnType("TEXT") + .HasMaxLength(255); + + b.Property("AuthenticationProviderId") + .IsRequired() + .HasColumnType("TEXT") + .HasMaxLength(255); + + b.Property("DisplayCollectionsView") + .HasColumnType("INTEGER"); + + b.Property("DisplayMissingEpisodes") + .HasColumnType("INTEGER"); + + b.Property("EasyPassword") + .HasColumnType("TEXT") + .HasMaxLength(65535); + + b.Property("EnableAutoLogin") + .HasColumnType("INTEGER"); + + b.Property("EnableLocalPassword") + .HasColumnType("INTEGER"); + + b.Property("EnableNextEpisodeAutoPlay") + .HasColumnType("INTEGER"); + + b.Property("EnableUserPreferenceAccess") + .HasColumnType("INTEGER"); + + b.Property("HidePlayedInLatest") + .HasColumnType("INTEGER"); + + b.Property("InternalId") + .HasColumnType("INTEGER"); + + b.Property("InvalidLoginAttemptCount") + .HasColumnType("INTEGER"); + + b.Property("LastActivityDate") + .HasColumnType("TEXT"); + + b.Property("LastLoginDate") + .HasColumnType("TEXT"); + + b.Property("LoginAttemptsBeforeLockout") + .HasColumnType("INTEGER"); + + b.Property("MaxParentalAgeRating") + .HasColumnType("INTEGER"); + + b.Property("MustUpdatePassword") + .HasColumnType("INTEGER"); + + b.Property("Password") + .HasColumnType("TEXT") + .HasMaxLength(65535); + + b.Property("PasswordResetProviderId") + .IsRequired() + .HasColumnType("TEXT") + .HasMaxLength(255); + + b.Property("PlayDefaultAudioTrack") + .HasColumnType("INTEGER"); + + b.Property("RememberAudioSelections") + .HasColumnType("INTEGER"); + + b.Property("RememberSubtitleSelections") + .HasColumnType("INTEGER"); + + b.Property("RemoteClientBitrateLimit") + .HasColumnType("INTEGER"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("INTEGER"); + + b.Property("SubtitleLanguagePreference") + .HasColumnType("TEXT") + .HasMaxLength(255); + + b.Property("SubtitleMode") + .HasColumnType("INTEGER"); + + b.Property("SyncPlayAccess") + .HasColumnType("INTEGER"); + + b.Property("Username") + .IsRequired() + .HasColumnType("TEXT") + .HasMaxLength(255); + + b.HasKey("Id"); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.AccessSchedule", b => + { + b.HasOne("Jellyfin.Data.Entities.User", null) + .WithMany("AccessSchedules") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.DisplayPreferences", b => + { + b.HasOne("Jellyfin.Data.Entities.User", null) + .WithMany("DisplayPreferences") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.HomeSection", b => + { + b.HasOne("Jellyfin.Data.Entities.DisplayPreferences", null) + .WithMany("HomeSections") + .HasForeignKey("DisplayPreferencesId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.ImageInfo", b => + { + b.HasOne("Jellyfin.Data.Entities.User", null) + .WithOne("ProfileImage") + .HasForeignKey("Jellyfin.Data.Entities.ImageInfo", "UserId"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.Permission", b => + { + b.HasOne("Jellyfin.Data.Entities.User", null) + .WithMany("Permissions") + .HasForeignKey("Permission_Permissions_Guid"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.Preference", b => + { + b.HasOne("Jellyfin.Data.Entities.User", null) + .WithMany("Preferences") + .HasForeignKey("Preference_Preferences_Guid"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Jellyfin.Server.Implementations/Migrations/20200630170339_AddDisplayPreferences.cs b/Jellyfin.Server.Implementations/Migrations/20200630170339_AddDisplayPreferences.cs new file mode 100644 index 0000000000..e9a493d9db --- /dev/null +++ b/Jellyfin.Server.Implementations/Migrations/20200630170339_AddDisplayPreferences.cs @@ -0,0 +1,92 @@ +#pragma warning disable CS1591 +#pragma warning disable SA1601 + +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Jellyfin.Server.Implementations.Migrations +{ + public partial class AddDisplayPreferences : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "DisplayPreferences", + schema: "jellyfin", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("Sqlite:Autoincrement", true), + UserId = table.Column(nullable: false), + ItemId = table.Column(nullable: true), + Client = table.Column(maxLength: 64, nullable: false), + RememberIndexing = table.Column(nullable: false), + RememberSorting = table.Column(nullable: false), + SortOrder = table.Column(nullable: false), + ShowSidebar = table.Column(nullable: false), + ShowBackdrop = table.Column(nullable: false), + SortBy = table.Column(nullable: true), + ViewType = table.Column(nullable: true), + ScrollDirection = table.Column(nullable: false), + IndexBy = table.Column(nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_DisplayPreferences", x => x.Id); + table.ForeignKey( + name: "FK_DisplayPreferences_Users_UserId", + column: x => x.UserId, + principalSchema: "jellyfin", + principalTable: "Users", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "HomeSection", + schema: "jellyfin", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("Sqlite:Autoincrement", true), + DisplayPreferencesId = table.Column(nullable: false), + Order = table.Column(nullable: false), + Type = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_HomeSection", x => x.Id); + table.ForeignKey( + name: "FK_HomeSection_DisplayPreferences_DisplayPreferencesId", + column: x => x.DisplayPreferencesId, + principalSchema: "jellyfin", + principalTable: "DisplayPreferences", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_DisplayPreferences_UserId", + schema: "jellyfin", + table: "DisplayPreferences", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_HomeSection_DisplayPreferencesId", + schema: "jellyfin", + table: "HomeSection", + column: "DisplayPreferencesId"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "HomeSection", + schema: "jellyfin"); + + migrationBuilder.DropTable( + name: "DisplayPreferences", + schema: "jellyfin"); + } + } +} diff --git a/Jellyfin.Server.Implementations/Migrations/JellyfinDbModelSnapshot.cs b/Jellyfin.Server.Implementations/Migrations/JellyfinDbModelSnapshot.cs index 51fad82249..69b544e5ba 100644 --- a/Jellyfin.Server.Implementations/Migrations/JellyfinDbModelSnapshot.cs +++ b/Jellyfin.Server.Implementations/Migrations/JellyfinDbModelSnapshot.cs @@ -15,7 +15,7 @@ namespace Jellyfin.Server.Implementations.Migrations #pragma warning disable 612, 618 modelBuilder .HasDefaultSchema("jellyfin") - .HasAnnotation("ProductVersion", "3.1.4"); + .HasAnnotation("ProductVersion", "3.1.5"); modelBuilder.Entity("Jellyfin.Data.Entities.AccessSchedule", b => { @@ -88,6 +88,79 @@ namespace Jellyfin.Server.Implementations.Migrations b.ToTable("ActivityLogs"); }); + modelBuilder.Entity("Jellyfin.Data.Entities.DisplayPreferences", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Client") + .IsRequired() + .HasColumnType("TEXT") + .HasMaxLength(64); + + b.Property("IndexBy") + .HasColumnType("INTEGER"); + + b.Property("ItemId") + .HasColumnType("TEXT"); + + b.Property("RememberIndexing") + .HasColumnType("INTEGER"); + + b.Property("RememberSorting") + .HasColumnType("INTEGER"); + + b.Property("ScrollDirection") + .HasColumnType("INTEGER"); + + b.Property("ShowBackdrop") + .HasColumnType("INTEGER"); + + b.Property("ShowSidebar") + .HasColumnType("INTEGER"); + + b.Property("SortBy") + .HasColumnType("TEXT"); + + b.Property("SortOrder") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("ViewType") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("DisplayPreferences"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.HomeSection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("DisplayPreferencesId") + .HasColumnType("INTEGER"); + + b.Property("Order") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("DisplayPreferencesId"); + + b.ToTable("HomeSection"); + }); + modelBuilder.Entity("Jellyfin.Data.Entities.ImageInfo", b => { b.Property("Id") @@ -282,6 +355,24 @@ namespace Jellyfin.Server.Implementations.Migrations .IsRequired(); }); + modelBuilder.Entity("Jellyfin.Data.Entities.DisplayPreferences", b => + { + b.HasOne("Jellyfin.Data.Entities.User", null) + .WithMany("DisplayPreferences") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.HomeSection", b => + { + b.HasOne("Jellyfin.Data.Entities.DisplayPreferences", null) + .WithMany("HomeSections") + .HasForeignKey("DisplayPreferencesId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + modelBuilder.Entity("Jellyfin.Data.Entities.ImageInfo", b => { b.HasOne("Jellyfin.Data.Entities.User", null) diff --git a/Jellyfin.Server/CoreAppHost.cs b/Jellyfin.Server/CoreAppHost.cs index 207eaa98d1..c5a7368aac 100644 --- a/Jellyfin.Server/CoreAppHost.cs +++ b/Jellyfin.Server/CoreAppHost.cs @@ -9,6 +9,7 @@ using Jellyfin.Server.Implementations; using Jellyfin.Server.Implementations.Activity; using Jellyfin.Server.Implementations.Users; using MediaBrowser.Common.Net; +using MediaBrowser.Controller; using MediaBrowser.Controller.Drawing; using MediaBrowser.Controller.Library; using MediaBrowser.Model.Activity; @@ -73,6 +74,7 @@ namespace Jellyfin.Server serviceCollection.AddSingleton(); serviceCollection.AddSingleton(); + serviceCollection.AddSingleton(); base.RegisterServices(serviceCollection); } diff --git a/Jellyfin.Server/Migrations/MigrationRunner.cs b/Jellyfin.Server/Migrations/MigrationRunner.cs index d633c554de..7f208952ce 100644 --- a/Jellyfin.Server/Migrations/MigrationRunner.cs +++ b/Jellyfin.Server/Migrations/MigrationRunner.cs @@ -21,7 +21,8 @@ namespace Jellyfin.Server.Migrations typeof(Routines.MigrateActivityLogDb), typeof(Routines.RemoveDuplicateExtras), typeof(Routines.AddDefaultPluginRepository), - typeof(Routines.MigrateUserDb) + typeof(Routines.MigrateUserDb), + typeof(Routines.MigrateDisplayPreferencesDb) }; /// diff --git a/Jellyfin.Server/Migrations/Routines/MigrateDisplayPreferencesDb.cs b/Jellyfin.Server/Migrations/Routines/MigrateDisplayPreferencesDb.cs new file mode 100644 index 0000000000..1ed23fe8e4 --- /dev/null +++ b/Jellyfin.Server/Migrations/Routines/MigrateDisplayPreferencesDb.cs @@ -0,0 +1,118 @@ +using System; +using System.IO; +using System.Text.Json; +using System.Text.Json.Serialization; +using Jellyfin.Data.Entities; +using Jellyfin.Data.Enums; +using Jellyfin.Server.Implementations; +using MediaBrowser.Controller; +using MediaBrowser.Model.Entities; +using Microsoft.Extensions.Logging; +using SQLitePCL.pretty; + +namespace Jellyfin.Server.Migrations.Routines +{ + /// + /// The migration routine for migrating the display preferences database to EF Core. + /// + public class MigrateDisplayPreferencesDb : IMigrationRoutine + { + private const string DbFilename = "displaypreferences.db"; + + private readonly ILogger _logger; + private readonly IServerApplicationPaths _paths; + private readonly JellyfinDbProvider _provider; + private readonly JsonSerializerOptions _jsonOptions; + + /// + /// Initializes a new instance of the class. + /// + /// The logger. + /// The server application paths. + /// The database provider. + public MigrateDisplayPreferencesDb(ILogger logger, IServerApplicationPaths paths, JellyfinDbProvider provider) + { + _logger = logger; + _paths = paths; + _provider = provider; + _jsonOptions = new JsonSerializerOptions(); + _jsonOptions.Converters.Add(new JsonStringEnumConverter()); + } + + /// + public Guid Id => Guid.Parse("06387815-C3CC-421F-A888-FB5F9992BEA8"); + + /// + public string Name => "MigrateDisplayPreferencesDatabase"; + + /// + public void Perform() + { + HomeSectionType[] defaults = + { + HomeSectionType.SmallLibraryTiles, + HomeSectionType.Resume, + HomeSectionType.ResumeAudio, + HomeSectionType.LiveTv, + HomeSectionType.NextUp, + HomeSectionType.LatestMedia, + HomeSectionType.None, + }; + + var dbFilePath = Path.Combine(_paths.DataPath, DbFilename); + using (var connection = SQLite3.Open(dbFilePath, ConnectionFlags.ReadOnly, null)) + { + var dbContext = _provider.CreateContext(); + + var results = connection.Query("SELECT * FROM userdisplaypreferences"); + foreach (var result in results) + { + var dto = JsonSerializer.Deserialize(result[3].ToString(), _jsonOptions); + + var displayPreferences = new DisplayPreferences(result[2].ToString(), new Guid(result[1].ToBlob())) + { + ViewType = Enum.TryParse(dto.ViewType, true, out var viewType) ? viewType : (ViewType?)null, + IndexBy = Enum.TryParse(dto.IndexBy, true, out var indexBy) ? indexBy : (IndexingKind?)null, + ShowBackdrop = dto.ShowBackdrop, + ShowSidebar = dto.ShowSidebar, + SortBy = dto.SortBy, + SortOrder = dto.SortOrder, + RememberIndexing = dto.RememberIndexing, + RememberSorting = dto.RememberSorting, + ScrollDirection = dto.ScrollDirection + }; + + for (int i = 0; i < 7; i++) + { + dto.CustomPrefs.TryGetValue("homesection" + i, out var homeSection); + + displayPreferences.HomeSections.Add(new HomeSection + { + Order = i, + Type = Enum.TryParse(homeSection, true, out var type) ? type : defaults[i] + }); + } + + dbContext.Add(displayPreferences); + } + + dbContext.SaveChanges(); + } + + try + { + File.Move(dbFilePath, dbFilePath + ".old"); + + var journalPath = dbFilePath + "-journal"; + if (File.Exists(journalPath)) + { + File.Move(journalPath, dbFilePath + ".old-journal"); + } + } + catch (IOException e) + { + _logger.LogError(e, "Error renaming legacy display preferences database to 'displaypreferences.db.old'"); + } + } + } +} diff --git a/MediaBrowser.Api/ChannelService.cs b/MediaBrowser.Api/ChannelService.cs index 8c336b1c9d..8d3a9ee5a0 100644 --- a/MediaBrowser.Api/ChannelService.cs +++ b/MediaBrowser.Api/ChannelService.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; +using Jellyfin.Data.Enums; using MediaBrowser.Api.UserLibrary; using MediaBrowser.Controller.Channels; using MediaBrowser.Controller.Configuration; @@ -11,7 +12,6 @@ using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Net; using MediaBrowser.Model.Channels; using MediaBrowser.Model.Dto; -using MediaBrowser.Model.Entities; using MediaBrowser.Model.Querying; using MediaBrowser.Model.Services; using Microsoft.Extensions.Logging; diff --git a/MediaBrowser.Api/DisplayPreferencesService.cs b/MediaBrowser.Api/DisplayPreferencesService.cs index c3ed40ad3c..e5dd038076 100644 --- a/MediaBrowser.Api/DisplayPreferencesService.cs +++ b/MediaBrowser.Api/DisplayPreferencesService.cs @@ -1,9 +1,10 @@ -using System.Threading; +using System; +using Jellyfin.Data.Entities; +using Jellyfin.Data.Enums; +using MediaBrowser.Controller; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Net; -using MediaBrowser.Controller.Persistence; using MediaBrowser.Model.Entities; -using MediaBrowser.Model.Serialization; using MediaBrowser.Model.Services; using Microsoft.Extensions.Logging; @@ -13,7 +14,7 @@ namespace MediaBrowser.Api /// Class UpdateDisplayPreferences. /// [Route("/DisplayPreferences/{DisplayPreferencesId}", "POST", Summary = "Updates a user's display preferences for an item")] - public class UpdateDisplayPreferences : DisplayPreferences, IReturnVoid + public class UpdateDisplayPreferences : DisplayPreferencesDto, IReturnVoid { /// /// Gets or sets the id. @@ -27,7 +28,7 @@ namespace MediaBrowser.Api } [Route("/DisplayPreferences/{Id}", "GET", Summary = "Gets a user's display preferences for an item")] - public class GetDisplayPreferences : IReturn + public class GetDisplayPreferences : IReturn { /// /// Gets or sets the id. @@ -50,28 +51,21 @@ namespace MediaBrowser.Api public class DisplayPreferencesService : BaseApiService { /// - /// The _display preferences manager. + /// The user manager. /// - private readonly IDisplayPreferencesRepository _displayPreferencesManager; - /// - /// The _json serializer. - /// - private readonly IJsonSerializer _jsonSerializer; + private readonly IDisplayPreferencesManager _displayPreferencesManager; /// /// Initializes a new instance of the class. /// - /// The json serializer. /// The display preferences manager. public DisplayPreferencesService( ILogger logger, IServerConfigurationManager serverConfigurationManager, IHttpResultFactory httpResultFactory, - IJsonSerializer jsonSerializer, - IDisplayPreferencesRepository displayPreferencesManager) + IDisplayPreferencesManager displayPreferencesManager) : base(logger, serverConfigurationManager, httpResultFactory) { - _jsonSerializer = jsonSerializer; _displayPreferencesManager = displayPreferencesManager; } @@ -81,9 +75,34 @@ namespace MediaBrowser.Api /// The request. public object Get(GetDisplayPreferences request) { - var result = _displayPreferencesManager.GetDisplayPreferences(request.Id, request.UserId, request.Client); + var result = _displayPreferencesManager.GetDisplayPreferences(Guid.Parse(request.UserId), request.Client); - return ToOptimizedResult(result); + if (result == null) + { + return null; + } + + var dto = new DisplayPreferencesDto + { + Client = result.Client, + Id = result.UserId.ToString(), + ViewType = result.ViewType?.ToString(), + SortBy = result.SortBy, + SortOrder = result.SortOrder, + IndexBy = result.IndexBy?.ToString(), + RememberIndexing = result.RememberIndexing, + RememberSorting = result.RememberSorting, + ScrollDirection = result.ScrollDirection, + ShowBackdrop = result.ShowBackdrop, + ShowSidebar = result.ShowSidebar + }; + + foreach (var homeSection in result.HomeSections) + { + dto.CustomPrefs["homesection" + homeSection.Order] = homeSection.Type.ToString().ToLowerInvariant(); + } + + return ToOptimizedResult(dto); } /// @@ -92,10 +111,43 @@ namespace MediaBrowser.Api /// The request. public void Post(UpdateDisplayPreferences request) { - // Serialize to json and then back so that the core doesn't see the request dto type - var displayPreferences = _jsonSerializer.DeserializeFromString(_jsonSerializer.SerializeToString(request)); + HomeSectionType[] defaults = + { + HomeSectionType.SmallLibraryTiles, + HomeSectionType.Resume, + HomeSectionType.ResumeAudio, + HomeSectionType.LiveTv, + HomeSectionType.NextUp, + HomeSectionType.LatestMedia, + HomeSectionType.None, + }; - _displayPreferencesManager.SaveDisplayPreferences(displayPreferences, request.UserId, request.Client, CancellationToken.None); + var prefs = _displayPreferencesManager.GetDisplayPreferences(Guid.Parse(request.UserId), request.Client); + + prefs.ViewType = Enum.TryParse(request.ViewType, true, out var viewType) ? viewType : (ViewType?)null; + prefs.IndexBy = Enum.TryParse(request.IndexBy, true, out var indexBy) ? indexBy : (IndexingKind?)null; + prefs.ShowBackdrop = request.ShowBackdrop; + prefs.ShowSidebar = request.ShowSidebar; + prefs.SortBy = request.SortBy; + prefs.SortOrder = request.SortOrder; + prefs.RememberIndexing = request.RememberIndexing; + prefs.RememberSorting = request.RememberSorting; + prefs.ScrollDirection = request.ScrollDirection; + prefs.HomeSections.Clear(); + + for (int i = 0; i < 7; i++) + { + if (request.CustomPrefs.TryGetValue("homesection" + i, out var homeSection)) + { + prefs.HomeSections.Add(new HomeSection + { + Order = i, + Type = Enum.TryParse(homeSection, true, out var type) ? type : defaults[i] + }); + } + } + + _displayPreferencesManager.SaveChanges(prefs); } } } diff --git a/MediaBrowser.Api/Movies/MoviesService.cs b/MediaBrowser.Api/Movies/MoviesService.cs index 34cccffa38..2ff322d293 100644 --- a/MediaBrowser.Api/Movies/MoviesService.cs +++ b/MediaBrowser.Api/Movies/MoviesService.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Globalization; using System.Linq; using Jellyfin.Data.Entities; +using Jellyfin.Data.Enums; using MediaBrowser.Common.Extensions; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Dto; diff --git a/MediaBrowser.Api/SuggestionsService.cs b/MediaBrowser.Api/SuggestionsService.cs index 17afa8e79c..b42e822e82 100644 --- a/MediaBrowser.Api/SuggestionsService.cs +++ b/MediaBrowser.Api/SuggestionsService.cs @@ -1,6 +1,7 @@ using System; using System.Linq; using Jellyfin.Data.Entities; +using Jellyfin.Data.Enums; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; diff --git a/MediaBrowser.Api/TvShowsService.cs b/MediaBrowser.Api/TvShowsService.cs index 165abd613d..799cea6480 100644 --- a/MediaBrowser.Api/TvShowsService.cs +++ b/MediaBrowser.Api/TvShowsService.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.Globalization; using System.Linq; +using Jellyfin.Data.Enums; using MediaBrowser.Common.Extensions; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Dto; diff --git a/MediaBrowser.Api/UserLibrary/BaseItemsRequest.cs b/MediaBrowser.Api/UserLibrary/BaseItemsRequest.cs index 344861a496..fc19575b30 100644 --- a/MediaBrowser.Api/UserLibrary/BaseItemsRequest.cs +++ b/MediaBrowser.Api/UserLibrary/BaseItemsRequest.cs @@ -1,5 +1,6 @@ using System; using System.Linq; +using Jellyfin.Data.Enums; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Querying; using MediaBrowser.Model.Services; @@ -466,8 +467,8 @@ namespace MediaBrowser.Api.UserLibrary var sortOrderValue = sortOrders.Length > sortOrderIndex ? sortOrders[sortOrderIndex] : null; var sortOrder = string.Equals(sortOrderValue, "Descending", StringComparison.OrdinalIgnoreCase) - ? MediaBrowser.Model.Entities.SortOrder.Descending - : MediaBrowser.Model.Entities.SortOrder.Ascending; + ? Jellyfin.Data.Enums.SortOrder.Descending + : Jellyfin.Data.Enums.SortOrder.Ascending; result[i] = new ValueTuple(vals[i], sortOrder); } diff --git a/MediaBrowser.Controller/Entities/UserViewBuilder.cs b/MediaBrowser.Controller/Entities/UserViewBuilder.cs index cb35d6e321..22bb7fd550 100644 --- a/MediaBrowser.Controller/Entities/UserViewBuilder.cs +++ b/MediaBrowser.Controller/Entities/UserViewBuilder.cs @@ -3,6 +3,7 @@ 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.Entities.Movies; using MediaBrowser.Controller.Library; diff --git a/MediaBrowser.Controller/IDisplayPreferencesManager.cs b/MediaBrowser.Controller/IDisplayPreferencesManager.cs new file mode 100644 index 0000000000..e27b0ec7c3 --- /dev/null +++ b/MediaBrowser.Controller/IDisplayPreferencesManager.cs @@ -0,0 +1,25 @@ +using System; +using Jellyfin.Data.Entities; + +namespace MediaBrowser.Controller +{ + /// + /// Manages the storage and retrieval of display preferences. + /// + public interface IDisplayPreferencesManager + { + /// + /// Gets the display preferences for the user and client. + /// + /// The user's id. + /// The client string. + /// The associated display preferences. + DisplayPreferences GetDisplayPreferences(Guid userId, string client); + + /// + /// Saves changes to the provided display preferences. + /// + /// The display preferences to save. + void SaveChanges(DisplayPreferences preferences); + } +} diff --git a/MediaBrowser.Controller/Library/ILibraryManager.cs b/MediaBrowser.Controller/Library/ILibraryManager.cs index 9d6646857e..b5eec18463 100644 --- a/MediaBrowser.Controller/Library/ILibraryManager.cs +++ b/MediaBrowser.Controller/Library/ILibraryManager.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using Jellyfin.Data.Entities; +using Jellyfin.Data.Enums; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Audio; diff --git a/MediaBrowser.Controller/Persistence/IDisplayPreferencesRepository.cs b/MediaBrowser.Controller/Persistence/IDisplayPreferencesRepository.cs deleted file mode 100644 index c2dcb66d7c..0000000000 --- a/MediaBrowser.Controller/Persistence/IDisplayPreferencesRepository.cs +++ /dev/null @@ -1,53 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Threading; -using MediaBrowser.Model.Entities; - -namespace MediaBrowser.Controller.Persistence -{ - /// - /// Interface IDisplayPreferencesRepository. - /// - public interface IDisplayPreferencesRepository : IRepository - { - /// - /// Saves display preferences for an item. - /// - /// The display preferences. - /// The user id. - /// The client. - /// The cancellation token. - void SaveDisplayPreferences( - DisplayPreferences displayPreferences, - string userId, - string client, - CancellationToken cancellationToken); - - /// - /// Saves all display preferences for a user. - /// - /// The display preferences. - /// The user id. - /// The cancellation token. - void SaveAllDisplayPreferences( - IEnumerable displayPreferences, - Guid userId, - CancellationToken cancellationToken); - - /// - /// Gets the display preferences. - /// - /// The display preferences id. - /// The user id. - /// The client. - /// Task{DisplayPreferences}. - DisplayPreferences GetDisplayPreferences(string displayPreferencesId, string userId, string client); - - /// - /// Gets all display preferences for the given user. - /// - /// The user id. - /// Task{DisplayPreferences}. - IEnumerable GetAllDisplayPreferences(Guid userId); - } -} diff --git a/MediaBrowser.Controller/Playlists/Playlist.cs b/MediaBrowser.Controller/Playlists/Playlist.cs index b1a638883a..0fd63770f4 100644 --- a/MediaBrowser.Controller/Playlists/Playlist.cs +++ b/MediaBrowser.Controller/Playlists/Playlist.cs @@ -6,6 +6,7 @@ using System.Text.Json.Serialization; using System.Threading; using System.Threading.Tasks; using Jellyfin.Data.Entities; +using Jellyfin.Data.Enums; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Audio; diff --git a/MediaBrowser.Model/Dlna/SortCriteria.cs b/MediaBrowser.Model/Dlna/SortCriteria.cs index 1f7fa76ade..53e4540cbb 100644 --- a/MediaBrowser.Model/Dlna/SortCriteria.cs +++ b/MediaBrowser.Model/Dlna/SortCriteria.cs @@ -1,6 +1,6 @@ #pragma warning disable CS1591 -using MediaBrowser.Model.Entities; +using Jellyfin.Data.Enums; namespace MediaBrowser.Model.Dlna { diff --git a/MediaBrowser.Model/Entities/DisplayPreferences.cs b/MediaBrowser.Model/Entities/DisplayPreferencesDto.cs similarity index 94% rename from MediaBrowser.Model/Entities/DisplayPreferences.cs rename to MediaBrowser.Model/Entities/DisplayPreferencesDto.cs index 7e5c5be3b6..1f7fe30300 100644 --- a/MediaBrowser.Model/Entities/DisplayPreferences.cs +++ b/MediaBrowser.Model/Entities/DisplayPreferencesDto.cs @@ -1,22 +1,18 @@ #nullable disable using System.Collections.Generic; +using Jellyfin.Data.Enums; namespace MediaBrowser.Model.Entities { /// /// Defines the display preferences for any item that supports them (usually Folders). /// - public class DisplayPreferences + public class DisplayPreferencesDto { /// - /// The image scale. + /// Initializes a new instance of the class. /// - private const double ImageScale = .9; - - /// - /// Initializes a new instance of the class. - /// - public DisplayPreferences() + public DisplayPreferencesDto() { RememberIndexing = false; PrimaryImageHeight = 250; diff --git a/MediaBrowser.Model/LiveTv/LiveTvChannelQuery.cs b/MediaBrowser.Model/LiveTv/LiveTvChannelQuery.cs index 2b2377fdaf..ab74aff28b 100644 --- a/MediaBrowser.Model/LiveTv/LiveTvChannelQuery.cs +++ b/MediaBrowser.Model/LiveTv/LiveTvChannelQuery.cs @@ -2,7 +2,7 @@ #pragma warning disable CS1591 using System; -using MediaBrowser.Model.Entities; +using Jellyfin.Data.Enums; namespace MediaBrowser.Model.LiveTv { diff --git a/MediaBrowser.Model/LiveTv/SeriesTimerQuery.cs b/MediaBrowser.Model/LiveTv/SeriesTimerQuery.cs index b899a464b4..dae885775c 100644 --- a/MediaBrowser.Model/LiveTv/SeriesTimerQuery.cs +++ b/MediaBrowser.Model/LiveTv/SeriesTimerQuery.cs @@ -1,6 +1,6 @@ #pragma warning disable CS1591 -using MediaBrowser.Model.Entities; +using Jellyfin.Data.Enums; namespace MediaBrowser.Model.LiveTv { From e96a36512a542168e3d50609b1c4058e965a9791 Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Thu, 9 Jul 2020 22:00:34 -0400 Subject: [PATCH 02/60] Document DisplayPreferences.cs --- Jellyfin.Data/Entities/DisplayPreferences.cs | 77 ++++++++++++++++++++ 1 file changed, 77 insertions(+) diff --git a/Jellyfin.Data/Entities/DisplayPreferences.cs b/Jellyfin.Data/Entities/DisplayPreferences.cs index 668030149b..928407e7a0 100644 --- a/Jellyfin.Data/Entities/DisplayPreferences.cs +++ b/Jellyfin.Data/Entities/DisplayPreferences.cs @@ -6,8 +6,16 @@ using Jellyfin.Data.Enums; namespace Jellyfin.Data.Entities { + /// + /// An entity representing a user's display preferences. + /// public class DisplayPreferences { + /// + /// Initializes a new instance of the class. + /// + /// The client string. + /// The user's id. public DisplayPreferences(string client, Guid userId) { RememberIndexing = false; @@ -18,14 +26,29 @@ namespace Jellyfin.Data.Entities HomeSections = new HashSet(); } + /// + /// Initializes a new instance of the class. + /// protected DisplayPreferences() { } + /// + /// Gets or sets the Id. + /// + /// + /// Required. + /// [Required] [DatabaseGenerated(DatabaseGeneratedOption.Identity)] public int Id { get; protected set; } + /// + /// Gets or sets the user Id. + /// + /// + /// Required. + /// [Required] public Guid UserId { get; set; } @@ -38,35 +61,89 @@ namespace Jellyfin.Data.Entities /// public Guid? ItemId { get; set; } + /// + /// Gets or sets the client string. + /// + /// + /// Required. Max Length = 64. + /// [Required] [MaxLength(64)] [StringLength(64)] public string Client { get; set; } + /// + /// Gets or sets a value indicating whether the indexing should be remembered. + /// + /// + /// Required. + /// [Required] public bool RememberIndexing { get; set; } + /// + /// Gets or sets a value indicating whether the sorting type should be remembered. + /// + /// + /// Required. + /// [Required] public bool RememberSorting { get; set; } + /// + /// Gets or sets the sort order. + /// + /// + /// Required. + /// [Required] public SortOrder SortOrder { get; set; } + /// + /// Gets or sets a value indicating whether to show the sidebar. + /// + /// + /// Required. + /// [Required] public bool ShowSidebar { get; set; } + /// + /// Gets or sets a value indicating whether to show the backdrop. + /// + /// + /// Required. + /// [Required] public bool ShowBackdrop { get; set; } + /// + /// Gets or sets what the view should be sorted by. + /// public string SortBy { get; set; } + /// + /// Gets or sets the view type. + /// public ViewType? ViewType { get; set; } + /// + /// Gets or sets the scroll direction. + /// + /// + /// Required. + /// [Required] public ScrollDirection ScrollDirection { get; set; } + /// + /// Gets or sets what the view should be indexed by. + /// public IndexingKind? IndexBy { get; set; } + /// + /// Gets or sets the home sections. + /// public virtual ICollection HomeSections { get; protected set; } } } From 3c7eb6b324ba9cf20244f136ee743bef91d4a8f3 Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Thu, 9 Jul 2020 22:28:59 -0400 Subject: [PATCH 03/60] Document HomeSection.cs --- Jellyfin.Data/Entities/HomeSection.cs | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/Jellyfin.Data/Entities/HomeSection.cs b/Jellyfin.Data/Entities/HomeSection.cs index f39956a54e..f19b6f3d41 100644 --- a/Jellyfin.Data/Entities/HomeSection.cs +++ b/Jellyfin.Data/Entities/HomeSection.cs @@ -1,21 +1,38 @@ -using System; -using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; using Jellyfin.Data.Enums; namespace Jellyfin.Data.Entities { + /// + /// An entity representing a section on the user's home page. + /// public class HomeSection { + /// + /// Gets or sets the id. + /// + /// + /// Identity. Required. + /// [Key] [Required] [DatabaseGenerated(DatabaseGeneratedOption.Identity)] public int Id { get; protected set; } + /// + /// Gets or sets the Id of the associated display preferences. + /// public int DisplayPreferencesId { get; set; } + /// + /// Gets or sets the order. + /// public int Order { get; set; } + /// + /// Gets or sets the type. + /// public HomeSectionType Type { get; set; } } } From f3263c7d8ef9aa284c52fa6bd68adc1ed7c435da Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Sat, 11 Jul 2020 14:11:55 -0400 Subject: [PATCH 04/60] Remove limit of 7 home sections --- MediaBrowser.Api/DisplayPreferencesService.cs | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/MediaBrowser.Api/DisplayPreferencesService.cs b/MediaBrowser.Api/DisplayPreferencesService.cs index e5dd038076..969bf564cc 100644 --- a/MediaBrowser.Api/DisplayPreferencesService.cs +++ b/MediaBrowser.Api/DisplayPreferencesService.cs @@ -1,10 +1,12 @@ using System; +using System.Linq; using Jellyfin.Data.Entities; using Jellyfin.Data.Enums; using MediaBrowser.Controller; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Net; using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Querying; using MediaBrowser.Model.Services; using Microsoft.Extensions.Logging; @@ -135,16 +137,19 @@ namespace MediaBrowser.Api prefs.ScrollDirection = request.ScrollDirection; prefs.HomeSections.Clear(); - for (int i = 0; i < 7; i++) + foreach (var key in request.CustomPrefs.Keys.Where(key => key.StartsWith("homesection"))) { - if (request.CustomPrefs.TryGetValue("homesection" + i, out var homeSection)) + var order = int.Parse(key.Substring("homesection".Length)); + if (!Enum.TryParse(request.CustomPrefs[key], true, out var type)) { - prefs.HomeSections.Add(new HomeSection - { - Order = i, - Type = Enum.TryParse(homeSection, true, out var type) ? type : defaults[i] - }); + type = order < 7 ? defaults[order] : HomeSectionType.None; } + + prefs.HomeSections.Add(new HomeSection + { + Order = order, + Type = type + }); } _displayPreferencesManager.SaveChanges(prefs); From 817e06813efd7f4911dc54dbb497d653418ae67b Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Sat, 11 Jul 2020 14:17:33 -0400 Subject: [PATCH 05/60] Remove unused using --- MediaBrowser.Api/DisplayPreferencesService.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/MediaBrowser.Api/DisplayPreferencesService.cs b/MediaBrowser.Api/DisplayPreferencesService.cs index 969bf564cc..5352bb36eb 100644 --- a/MediaBrowser.Api/DisplayPreferencesService.cs +++ b/MediaBrowser.Api/DisplayPreferencesService.cs @@ -6,7 +6,6 @@ using MediaBrowser.Controller; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Net; using MediaBrowser.Model.Entities; -using MediaBrowser.Model.Querying; using MediaBrowser.Model.Services; using Microsoft.Extensions.Logging; From fcfe22753749e86c4c5a0755fc6fa13ba053faf4 Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Thu, 16 Jul 2020 19:05:35 -0400 Subject: [PATCH 06/60] Updated documentation to indicate required elements. --- Jellyfin.Data/Entities/HomeSection.cs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/Jellyfin.Data/Entities/HomeSection.cs b/Jellyfin.Data/Entities/HomeSection.cs index f19b6f3d41..1a59cda53a 100644 --- a/Jellyfin.Data/Entities/HomeSection.cs +++ b/Jellyfin.Data/Entities/HomeSection.cs @@ -23,16 +23,25 @@ namespace Jellyfin.Data.Entities /// /// Gets or sets the Id of the associated display preferences. /// + /// + /// Required. + /// public int DisplayPreferencesId { get; set; } /// /// Gets or sets the order. /// + /// + /// Required. + /// public int Order { get; set; } /// /// Gets or sets the type. /// + /// + /// Required. + /// public HomeSectionType Type { get; set; } } } From 9e17db59cd3f4824dfe9e29a3a8b5267249748c0 Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Fri, 17 Jul 2020 12:48:22 -0400 Subject: [PATCH 07/60] Reorder HomeSectionType --- Jellyfin.Data/Enums/HomeSectionType.cs | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/Jellyfin.Data/Enums/HomeSectionType.cs b/Jellyfin.Data/Enums/HomeSectionType.cs index be764c5924..e597c9431a 100644 --- a/Jellyfin.Data/Enums/HomeSectionType.cs +++ b/Jellyfin.Data/Enums/HomeSectionType.cs @@ -5,49 +5,49 @@ /// public enum HomeSectionType { + /// + /// None. + /// + None = 0, + /// /// My Media. /// - SmallLibraryTiles = 0, + SmallLibraryTiles = 1, /// /// My Media Small. /// - LibraryButtons = 1, + LibraryButtons = 2, /// /// Active Recordings. /// - ActiveRecordings = 2, + ActiveRecordings = 3, /// /// Continue Watching. /// - Resume = 3, + Resume = 4, /// /// Continue Listening. /// - ResumeAudio = 4, + ResumeAudio = 5, /// /// Latest Media. /// - LatestMedia = 5, + LatestMedia = 6, /// /// Next Up. /// - NextUp = 6, + NextUp = 7, /// /// Live TV. /// - LiveTv = 7, - - /// - /// None. - /// - None = 8 + LiveTv = 8 } } From 2831062a3f1e8d40ecf28ebef9255a40be00480a Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Fri, 17 Jul 2020 14:46:17 -0400 Subject: [PATCH 08/60] Add max length for SortBy --- Jellyfin.Data/Entities/DisplayPreferences.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Jellyfin.Data/Entities/DisplayPreferences.cs b/Jellyfin.Data/Entities/DisplayPreferences.cs index 928407e7a0..6bc6b7de1e 100644 --- a/Jellyfin.Data/Entities/DisplayPreferences.cs +++ b/Jellyfin.Data/Entities/DisplayPreferences.cs @@ -120,6 +120,8 @@ namespace Jellyfin.Data.Entities /// /// Gets or sets what the view should be sorted by. /// + [MaxLength(64)] + [StringLength(64)] public string SortBy { get; set; } /// From d8060849376ae8e1cf309b04e01a24bdc6089c58 Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Fri, 17 Jul 2020 16:27:34 -0400 Subject: [PATCH 09/60] Remove superfluous annotations. --- Jellyfin.Data/Entities/DisplayPreferences.cs | 8 -------- Jellyfin.Data/Entities/HomeSection.cs | 1 - 2 files changed, 9 deletions(-) diff --git a/Jellyfin.Data/Entities/DisplayPreferences.cs b/Jellyfin.Data/Entities/DisplayPreferences.cs index 6bc6b7de1e..6cefda7880 100644 --- a/Jellyfin.Data/Entities/DisplayPreferences.cs +++ b/Jellyfin.Data/Entities/DisplayPreferences.cs @@ -39,7 +39,6 @@ namespace Jellyfin.Data.Entities /// /// Required. /// - [Required] [DatabaseGenerated(DatabaseGeneratedOption.Identity)] public int Id { get; protected set; } @@ -49,7 +48,6 @@ namespace Jellyfin.Data.Entities /// /// Required. /// - [Required] public Guid UserId { get; set; } /// @@ -78,7 +76,6 @@ namespace Jellyfin.Data.Entities /// /// Required. /// - [Required] public bool RememberIndexing { get; set; } /// @@ -87,7 +84,6 @@ namespace Jellyfin.Data.Entities /// /// Required. /// - [Required] public bool RememberSorting { get; set; } /// @@ -96,7 +92,6 @@ namespace Jellyfin.Data.Entities /// /// Required. /// - [Required] public SortOrder SortOrder { get; set; } /// @@ -105,7 +100,6 @@ namespace Jellyfin.Data.Entities /// /// Required. /// - [Required] public bool ShowSidebar { get; set; } /// @@ -114,7 +108,6 @@ namespace Jellyfin.Data.Entities /// /// Required. /// - [Required] public bool ShowBackdrop { get; set; } /// @@ -135,7 +128,6 @@ namespace Jellyfin.Data.Entities /// /// Required. /// - [Required] public ScrollDirection ScrollDirection { get; set; } /// diff --git a/Jellyfin.Data/Entities/HomeSection.cs b/Jellyfin.Data/Entities/HomeSection.cs index 1a59cda53a..0620462602 100644 --- a/Jellyfin.Data/Entities/HomeSection.cs +++ b/Jellyfin.Data/Entities/HomeSection.cs @@ -16,7 +16,6 @@ namespace Jellyfin.Data.Entities /// Identity. Required. /// [Key] - [Required] [DatabaseGenerated(DatabaseGeneratedOption.Identity)] public int Id { get; protected set; } From 27eefd49f1011a9be3be4f18069942cd484c1530 Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Fri, 17 Jul 2020 19:36:55 -0400 Subject: [PATCH 10/60] Add missing fields --- Jellyfin.Data/Entities/DisplayPreferences.cs | 32 +++++++++++++++++++ Jellyfin.Data/Enums/ChromecastVersion.cs | 18 +++++++++++ ...7233541_AddDisplayPreferences.Designer.cs} | 21 +++++++++--- ...> 20200717233541_AddDisplayPreferences.cs} | 13 ++++---- .../Migrations/JellyfinDbModelSnapshot.cs | 15 ++++++++- MediaBrowser.Api/DisplayPreferencesService.cs | 8 +++++ 6 files changed, 95 insertions(+), 12 deletions(-) create mode 100644 Jellyfin.Data/Enums/ChromecastVersion.cs rename Jellyfin.Server.Implementations/Migrations/{20200630170339_AddDisplayPreferences.Designer.cs => 20200717233541_AddDisplayPreferences.Designer.cs} (95%) rename Jellyfin.Server.Implementations/Migrations/{20200630170339_AddDisplayPreferences.cs => 20200717233541_AddDisplayPreferences.cs} (88%) diff --git a/Jellyfin.Data/Entities/DisplayPreferences.cs b/Jellyfin.Data/Entities/DisplayPreferences.cs index 6cefda7880..bcb872db37 100644 --- a/Jellyfin.Data/Entities/DisplayPreferences.cs +++ b/Jellyfin.Data/Entities/DisplayPreferences.cs @@ -135,6 +135,38 @@ namespace Jellyfin.Data.Entities /// public IndexingKind? IndexBy { get; set; } + /// + /// Gets or sets the length of time to skip forwards, in milliseconds. + /// + /// + /// Required. + /// + public int SkipForwardLength { get; set; } + + /// + /// Gets or sets the length of time to skip backwards, in milliseconds. + /// + /// + /// Required. + /// + public int SkipBackwardLength { get; set; } + + /// + /// Gets or sets the Chromecast Version. + /// + /// + /// Required. + /// + public ChromecastVersion ChromecastVersion { get; set; } + + /// + /// Gets or sets a value indicating whether the next video info overlay should be shown. + /// + /// + /// Required. + /// + public bool EnableNextVideoInfoOverlay { get; set; } + /// /// Gets or sets the home sections. /// diff --git a/Jellyfin.Data/Enums/ChromecastVersion.cs b/Jellyfin.Data/Enums/ChromecastVersion.cs new file mode 100644 index 0000000000..c549b6acc4 --- /dev/null +++ b/Jellyfin.Data/Enums/ChromecastVersion.cs @@ -0,0 +1,18 @@ +namespace Jellyfin.Data.Enums +{ + /// + /// An enum representing the version of Chromecast to be used by clients. + /// + public enum ChromecastVersion + { + /// + /// Stable Chromecast version. + /// + Stable, + + /// + /// Nightly Chromecast version. + /// + Nightly + } +} diff --git a/Jellyfin.Server.Implementations/Migrations/20200630170339_AddDisplayPreferences.Designer.cs b/Jellyfin.Server.Implementations/Migrations/20200717233541_AddDisplayPreferences.Designer.cs similarity index 95% rename from Jellyfin.Server.Implementations/Migrations/20200630170339_AddDisplayPreferences.Designer.cs rename to Jellyfin.Server.Implementations/Migrations/20200717233541_AddDisplayPreferences.Designer.cs index 75f9bb7a3c..cf6b166172 100644 --- a/Jellyfin.Server.Implementations/Migrations/20200630170339_AddDisplayPreferences.Designer.cs +++ b/Jellyfin.Server.Implementations/Migrations/20200717233541_AddDisplayPreferences.Designer.cs @@ -1,6 +1,4 @@ -#pragma warning disable CS1591 - -// +// using System; using Jellyfin.Server.Implementations; using Microsoft.EntityFrameworkCore; @@ -11,7 +9,7 @@ using Microsoft.EntityFrameworkCore.Storage.ValueConversion; namespace Jellyfin.Server.Implementations.Migrations { [DbContext(typeof(JellyfinDb))] - [Migration("20200630170339_AddDisplayPreferences")] + [Migration("20200717233541_AddDisplayPreferences")] partial class AddDisplayPreferences { protected override void BuildTargetModel(ModelBuilder modelBuilder) @@ -98,11 +96,17 @@ namespace Jellyfin.Server.Implementations.Migrations .ValueGeneratedOnAdd() .HasColumnType("INTEGER"); + b.Property("ChromecastVersion") + .HasColumnType("INTEGER"); + b.Property("Client") .IsRequired() .HasColumnType("TEXT") .HasMaxLength(64); + b.Property("EnableNextVideoInfoOverlay") + .HasColumnType("INTEGER"); + b.Property("IndexBy") .HasColumnType("INTEGER"); @@ -124,8 +128,15 @@ namespace Jellyfin.Server.Implementations.Migrations b.Property("ShowSidebar") .HasColumnType("INTEGER"); + b.Property("SkipBackwardLength") + .HasColumnType("INTEGER"); + + b.Property("SkipForwardLength") + .HasColumnType("INTEGER"); + b.Property("SortBy") - .HasColumnType("TEXT"); + .HasColumnType("TEXT") + .HasMaxLength(64); b.Property("SortOrder") .HasColumnType("INTEGER"); diff --git a/Jellyfin.Server.Implementations/Migrations/20200630170339_AddDisplayPreferences.cs b/Jellyfin.Server.Implementations/Migrations/20200717233541_AddDisplayPreferences.cs similarity index 88% rename from Jellyfin.Server.Implementations/Migrations/20200630170339_AddDisplayPreferences.cs rename to Jellyfin.Server.Implementations/Migrations/20200717233541_AddDisplayPreferences.cs index e9a493d9db..3cfd02e070 100644 --- a/Jellyfin.Server.Implementations/Migrations/20200630170339_AddDisplayPreferences.cs +++ b/Jellyfin.Server.Implementations/Migrations/20200717233541_AddDisplayPreferences.cs @@ -1,7 +1,4 @@ -#pragma warning disable CS1591 -#pragma warning disable SA1601 - -using System; +using System; using Microsoft.EntityFrameworkCore.Migrations; namespace Jellyfin.Server.Implementations.Migrations @@ -25,10 +22,14 @@ namespace Jellyfin.Server.Implementations.Migrations SortOrder = table.Column(nullable: false), ShowSidebar = table.Column(nullable: false), ShowBackdrop = table.Column(nullable: false), - SortBy = table.Column(nullable: true), + SortBy = table.Column(maxLength: 64, nullable: true), ViewType = table.Column(nullable: true), ScrollDirection = table.Column(nullable: false), - IndexBy = table.Column(nullable: true) + IndexBy = table.Column(nullable: true), + SkipForwardLength = table.Column(nullable: false), + SkipBackwardLength = table.Column(nullable: false), + ChromecastVersion = table.Column(nullable: false), + EnableNextVideoInfoOverlay = table.Column(nullable: false) }, constraints: table => { diff --git a/Jellyfin.Server.Implementations/Migrations/JellyfinDbModelSnapshot.cs b/Jellyfin.Server.Implementations/Migrations/JellyfinDbModelSnapshot.cs index 69b544e5ba..76de592ac7 100644 --- a/Jellyfin.Server.Implementations/Migrations/JellyfinDbModelSnapshot.cs +++ b/Jellyfin.Server.Implementations/Migrations/JellyfinDbModelSnapshot.cs @@ -94,11 +94,17 @@ namespace Jellyfin.Server.Implementations.Migrations .ValueGeneratedOnAdd() .HasColumnType("INTEGER"); + b.Property("ChromecastVersion") + .HasColumnType("INTEGER"); + b.Property("Client") .IsRequired() .HasColumnType("TEXT") .HasMaxLength(64); + b.Property("EnableNextVideoInfoOverlay") + .HasColumnType("INTEGER"); + b.Property("IndexBy") .HasColumnType("INTEGER"); @@ -120,8 +126,15 @@ namespace Jellyfin.Server.Implementations.Migrations b.Property("ShowSidebar") .HasColumnType("INTEGER"); + b.Property("SkipBackwardLength") + .HasColumnType("INTEGER"); + + b.Property("SkipForwardLength") + .HasColumnType("INTEGER"); + b.Property("SortBy") - .HasColumnType("TEXT"); + .HasColumnType("TEXT") + .HasMaxLength(64); b.Property("SortOrder") .HasColumnType("INTEGER"); diff --git a/MediaBrowser.Api/DisplayPreferencesService.cs b/MediaBrowser.Api/DisplayPreferencesService.cs index 5352bb36eb..877b124be5 100644 --- a/MediaBrowser.Api/DisplayPreferencesService.cs +++ b/MediaBrowser.Api/DisplayPreferencesService.cs @@ -134,6 +134,14 @@ namespace MediaBrowser.Api prefs.RememberIndexing = request.RememberIndexing; prefs.RememberSorting = request.RememberSorting; prefs.ScrollDirection = request.ScrollDirection; + prefs.ChromecastVersion = request.CustomPrefs.TryGetValue("chromecastVersion", out var chromecastVersion) + ? Enum.Parse(chromecastVersion, true) + : ChromecastVersion.Stable; + prefs.EnableNextVideoInfoOverlay = request.CustomPrefs.TryGetValue("enableNextVideoInfoOverlay", out var enableNextVideoInfoOverlay) + ? bool.Parse(enableNextVideoInfoOverlay) + : true; + prefs.SkipBackwardLength = request.CustomPrefs.TryGetValue("skipBackLength", out var skipBackLength) ? int.Parse(skipBackLength) : 10000; + prefs.SkipForwardLength = request.CustomPrefs.TryGetValue("skipForwardLength", out var skipForwardLength) ? int.Parse(skipForwardLength) : 30000; prefs.HomeSections.Clear(); foreach (var key in request.CustomPrefs.Keys.Where(key => key.StartsWith("homesection"))) From 1ac11863126edd570cd3f0baeebd9efb5925dde7 Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Fri, 17 Jul 2020 20:01:17 -0400 Subject: [PATCH 11/60] Add pragmas to DisplayPreferences migration files --- .../20200717233541_AddDisplayPreferences.Designer.cs | 4 +++- .../Migrations/20200717233541_AddDisplayPreferences.cs | 5 ++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/Jellyfin.Server.Implementations/Migrations/20200717233541_AddDisplayPreferences.Designer.cs b/Jellyfin.Server.Implementations/Migrations/20200717233541_AddDisplayPreferences.Designer.cs index cf6b166172..392e26daef 100644 --- a/Jellyfin.Server.Implementations/Migrations/20200717233541_AddDisplayPreferences.Designer.cs +++ b/Jellyfin.Server.Implementations/Migrations/20200717233541_AddDisplayPreferences.Designer.cs @@ -1,4 +1,6 @@ -// +#pragma warning disable CS1591 + +// using System; using Jellyfin.Server.Implementations; using Microsoft.EntityFrameworkCore; diff --git a/Jellyfin.Server.Implementations/Migrations/20200717233541_AddDisplayPreferences.cs b/Jellyfin.Server.Implementations/Migrations/20200717233541_AddDisplayPreferences.cs index 3cfd02e070..a5c344fac0 100644 --- a/Jellyfin.Server.Implementations/Migrations/20200717233541_AddDisplayPreferences.cs +++ b/Jellyfin.Server.Implementations/Migrations/20200717233541_AddDisplayPreferences.cs @@ -1,4 +1,7 @@ -using System; +#pragma warning disable CS1591 +#pragma warning disable SA1601 + +using System; using Microsoft.EntityFrameworkCore.Migrations; namespace Jellyfin.Server.Implementations.Migrations From 10f531bbe4699384439a45b8114037c42809e191 Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Fri, 17 Jul 2020 20:03:17 -0400 Subject: [PATCH 12/60] Manually specify enum order. --- Jellyfin.Data/Enums/ChromecastVersion.cs | 4 ++-- Jellyfin.Data/Enums/IndexingKind.cs | 6 +++--- Jellyfin.Data/Enums/ScrollDirection.cs | 4 ++-- Jellyfin.Data/Enums/SortOrder.cs | 4 ++-- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/Jellyfin.Data/Enums/ChromecastVersion.cs b/Jellyfin.Data/Enums/ChromecastVersion.cs index c549b6acc4..9211659cc3 100644 --- a/Jellyfin.Data/Enums/ChromecastVersion.cs +++ b/Jellyfin.Data/Enums/ChromecastVersion.cs @@ -8,11 +8,11 @@ /// /// Stable Chromecast version. /// - Stable, + Stable = 0, /// /// Nightly Chromecast version. /// - Nightly + Nightly = 2 } } diff --git a/Jellyfin.Data/Enums/IndexingKind.cs b/Jellyfin.Data/Enums/IndexingKind.cs index c4d8e70ca6..9badc6573b 100644 --- a/Jellyfin.Data/Enums/IndexingKind.cs +++ b/Jellyfin.Data/Enums/IndexingKind.cs @@ -5,16 +5,16 @@ /// /// Index by the premiere date. /// - PremiereDate, + PremiereDate = 0, /// /// Index by the production year. /// - ProductionYear, + ProductionYear = 1, /// /// Index by the community rating. /// - CommunityRating + CommunityRating = 2 } } diff --git a/Jellyfin.Data/Enums/ScrollDirection.cs b/Jellyfin.Data/Enums/ScrollDirection.cs index 382f585ba0..9595eb4904 100644 --- a/Jellyfin.Data/Enums/ScrollDirection.cs +++ b/Jellyfin.Data/Enums/ScrollDirection.cs @@ -8,11 +8,11 @@ /// /// Horizontal scrolling direction. /// - Horizontal, + Horizontal = 0, /// /// Vertical scrolling direction. /// - Vertical + Vertical = 1 } } diff --git a/Jellyfin.Data/Enums/SortOrder.cs b/Jellyfin.Data/Enums/SortOrder.cs index 309fa78775..760a857f51 100644 --- a/Jellyfin.Data/Enums/SortOrder.cs +++ b/Jellyfin.Data/Enums/SortOrder.cs @@ -8,11 +8,11 @@ /// /// Sort in increasing order. /// - Ascending, + Ascending = 0, /// /// Sort in decreasing order. /// - Descending + Descending = 1 } } From 5993a4ac2d2c54687f015755d69d495d796163d1 Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Fri, 17 Jul 2020 23:36:13 -0400 Subject: [PATCH 13/60] Fix ChromecastVersion numbering --- Jellyfin.Data/Enums/ChromecastVersion.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jellyfin.Data/Enums/ChromecastVersion.cs b/Jellyfin.Data/Enums/ChromecastVersion.cs index 9211659cc3..855c75ab45 100644 --- a/Jellyfin.Data/Enums/ChromecastVersion.cs +++ b/Jellyfin.Data/Enums/ChromecastVersion.cs @@ -13,6 +13,6 @@ /// /// Nightly Chromecast version. /// - Nightly = 2 + Nightly = 1 } } From 9f323e55791b6705f78b552d141e3362d967df08 Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Wed, 22 Jul 2020 14:37:17 -0400 Subject: [PATCH 14/60] Add missing chromecast version serialization/deserialization. --- .../Migrations/Routines/MigrateDisplayPreferencesDb.cs | 8 +++++++- MediaBrowser.Api/DisplayPreferencesService.cs | 2 ++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/Jellyfin.Server/Migrations/Routines/MigrateDisplayPreferencesDb.cs b/Jellyfin.Server/Migrations/Routines/MigrateDisplayPreferencesDb.cs index 1ed23fe8e4..447d74070f 100644 --- a/Jellyfin.Server/Migrations/Routines/MigrateDisplayPreferencesDb.cs +++ b/Jellyfin.Server/Migrations/Routines/MigrateDisplayPreferencesDb.cs @@ -68,6 +68,11 @@ namespace Jellyfin.Server.Migrations.Routines foreach (var result in results) { var dto = JsonSerializer.Deserialize(result[3].ToString(), _jsonOptions); + var chromecastVersion = dto.CustomPrefs.TryGetValue("chromecastVersion", out var version) + ? Enum.TryParse(version, true, out var parsed) + ? parsed + : ChromecastVersion.Stable + : ChromecastVersion.Stable; var displayPreferences = new DisplayPreferences(result[2].ToString(), new Guid(result[1].ToBlob())) { @@ -79,7 +84,8 @@ namespace Jellyfin.Server.Migrations.Routines SortOrder = dto.SortOrder, RememberIndexing = dto.RememberIndexing, RememberSorting = dto.RememberSorting, - ScrollDirection = dto.ScrollDirection + ScrollDirection = dto.ScrollDirection, + ChromecastVersion = chromecastVersion }; for (int i = 0; i < 7; i++) diff --git a/MediaBrowser.Api/DisplayPreferencesService.cs b/MediaBrowser.Api/DisplayPreferencesService.cs index 877b124be5..b95ab0dfde 100644 --- a/MediaBrowser.Api/DisplayPreferencesService.cs +++ b/MediaBrowser.Api/DisplayPreferencesService.cs @@ -103,6 +103,8 @@ namespace MediaBrowser.Api dto.CustomPrefs["homesection" + homeSection.Order] = homeSection.Type.ToString().ToLowerInvariant(); } + dto.CustomPrefs["chromecastVersion"] = result.ChromecastVersion.ToString().ToLowerInvariant(); + return ToOptimizedResult(dto); } From 52ebf6ae8f050fcbdf675529a97cfbbfcca4953a Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Wed, 22 Jul 2020 14:53:32 -0400 Subject: [PATCH 15/60] Move DisplayPreferencesManager.cs to Users namespace --- .../{ => Users}/DisplayPreferencesManager.cs | 2 +- MediaBrowser.Api/DisplayPreferencesService.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) rename Jellyfin.Server.Implementations/{ => Users}/DisplayPreferencesManager.cs (96%) diff --git a/Jellyfin.Server.Implementations/DisplayPreferencesManager.cs b/Jellyfin.Server.Implementations/Users/DisplayPreferencesManager.cs similarity index 96% rename from Jellyfin.Server.Implementations/DisplayPreferencesManager.cs rename to Jellyfin.Server.Implementations/Users/DisplayPreferencesManager.cs index 132e74c6aa..29ec6e706e 100644 --- a/Jellyfin.Server.Implementations/DisplayPreferencesManager.cs +++ b/Jellyfin.Server.Implementations/Users/DisplayPreferencesManager.cs @@ -3,7 +3,7 @@ using System.Linq; using Jellyfin.Data.Entities; using MediaBrowser.Controller; -namespace Jellyfin.Server.Implementations +namespace Jellyfin.Server.Implementations.Users { /// /// Manages the storage and retrieval of display preferences through Entity Framework. diff --git a/MediaBrowser.Api/DisplayPreferencesService.cs b/MediaBrowser.Api/DisplayPreferencesService.cs index b95ab0dfde..cca2092e97 100644 --- a/MediaBrowser.Api/DisplayPreferencesService.cs +++ b/MediaBrowser.Api/DisplayPreferencesService.cs @@ -148,7 +148,7 @@ namespace MediaBrowser.Api foreach (var key in request.CustomPrefs.Keys.Where(key => key.StartsWith("homesection"))) { - var order = int.Parse(key.Substring("homesection".Length)); + var order = int.Parse(key.AsSpan().Slice("homesection".Length)); if (!Enum.TryParse(request.CustomPrefs[key], true, out var type)) { type = order < 7 ? defaults[order] : HomeSectionType.None; From 8a9ec7809fab05a30ff8e5f13b38b9d34ed15050 Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Wed, 22 Jul 2020 15:19:18 -0400 Subject: [PATCH 16/60] Wrap context creation with using --- .../Users/DisplayPreferencesManager.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Jellyfin.Server.Implementations/Users/DisplayPreferencesManager.cs b/Jellyfin.Server.Implementations/Users/DisplayPreferencesManager.cs index 29ec6e706e..4ad9a12d42 100644 --- a/Jellyfin.Server.Implementations/Users/DisplayPreferencesManager.cs +++ b/Jellyfin.Server.Implementations/Users/DisplayPreferencesManager.cs @@ -2,6 +2,7 @@ using System.Linq; using Jellyfin.Data.Entities; using MediaBrowser.Controller; +using Microsoft.EntityFrameworkCore; namespace Jellyfin.Server.Implementations.Users { @@ -24,7 +25,7 @@ namespace Jellyfin.Server.Implementations.Users /// public DisplayPreferences GetDisplayPreferences(Guid userId, string client) { - var dbContext = _dbProvider.CreateContext(); + using var dbContext = _dbProvider.CreateContext(); var user = dbContext.Users.Find(userId); #pragma warning disable CA1307 var prefs = user.DisplayPreferences.FirstOrDefault(pref => string.Equals(pref.Client, client)); @@ -41,7 +42,7 @@ namespace Jellyfin.Server.Implementations.Users /// public void SaveChanges(DisplayPreferences preferences) { - var dbContext = _dbProvider.CreateContext(); + using var dbContext = _dbProvider.CreateContext(); dbContext.Update(preferences); dbContext.SaveChanges(); } From 5f67ba4d7087d7a0cad37fc9e2db212340076d12 Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Wed, 22 Jul 2020 15:21:50 -0400 Subject: [PATCH 17/60] Restructure query to avoid extra database access. --- .../Users/DisplayPreferencesManager.cs | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/Jellyfin.Server.Implementations/Users/DisplayPreferencesManager.cs b/Jellyfin.Server.Implementations/Users/DisplayPreferencesManager.cs index 4ad9a12d42..b7c65fc2cb 100644 --- a/Jellyfin.Server.Implementations/Users/DisplayPreferencesManager.cs +++ b/Jellyfin.Server.Implementations/Users/DisplayPreferencesManager.cs @@ -1,4 +1,6 @@ -using System; +#pragma warning disable CA1307 + +using System; using System.Linq; using Jellyfin.Data.Entities; using MediaBrowser.Controller; @@ -26,14 +28,15 @@ namespace Jellyfin.Server.Implementations.Users public DisplayPreferences GetDisplayPreferences(Guid userId, string client) { using var dbContext = _dbProvider.CreateContext(); - var user = dbContext.Users.Find(userId); -#pragma warning disable CA1307 - var prefs = user.DisplayPreferences.FirstOrDefault(pref => string.Equals(pref.Client, client)); + var prefs = dbContext.DisplayPreferences + .Include(pref => pref.HomeSections) + .FirstOrDefault(pref => + pref.UserId == userId && pref.ItemId == null && string.Equals(pref.Client, client)); if (prefs == null) { prefs = new DisplayPreferences(client, userId); - user.DisplayPreferences.Add(prefs); + dbContext.DisplayPreferences.Add(prefs); } return prefs; From 629ffe395feccbf512709bfaa54d5ef6d23c6852 Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Thu, 23 Jul 2020 20:36:36 -0400 Subject: [PATCH 18/60] Fixed build errors. --- .../Migrations/Routines/MigrateDisplayPreferencesDb.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Jellyfin.Server/Migrations/Routines/MigrateDisplayPreferencesDb.cs b/Jellyfin.Server/Migrations/Routines/MigrateDisplayPreferencesDb.cs index 447d74070f..588c0ecdde 100644 --- a/Jellyfin.Server/Migrations/Routines/MigrateDisplayPreferencesDb.cs +++ b/Jellyfin.Server/Migrations/Routines/MigrateDisplayPreferencesDb.cs @@ -45,6 +45,9 @@ namespace Jellyfin.Server.Migrations.Routines /// public string Name => "MigrateDisplayPreferencesDatabase"; + /// + public bool PerformOnNewInstall => false; + /// public void Perform() { From 1ee87901897290e971c2c65020f5576c08b0dc43 Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Thu, 23 Jul 2020 21:50:41 -0400 Subject: [PATCH 19/60] Use System.Text.Json in DefaultPasswordResetProvider --- .../Users/DefaultPasswordResetProvider.cs | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/Jellyfin.Server.Implementations/Users/DefaultPasswordResetProvider.cs b/Jellyfin.Server.Implementations/Users/DefaultPasswordResetProvider.cs index 007c296436..9b0201bfbf 100644 --- a/Jellyfin.Server.Implementations/Users/DefaultPasswordResetProvider.cs +++ b/Jellyfin.Server.Implementations/Users/DefaultPasswordResetProvider.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.IO; using System.Security.Cryptography; +using System.Text.Json; using System.Threading.Tasks; using Jellyfin.Data.Entities; using MediaBrowser.Common; @@ -11,7 +12,6 @@ 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 Jellyfin.Server.Implementations.Users @@ -23,7 +23,6 @@ namespace Jellyfin.Server.Implementations.Users { private const string BaseResetFileName = "passwordreset"; - private readonly IJsonSerializer _jsonSerializer; private readonly IApplicationHost _appHost; private readonly string _passwordResetFileBase; @@ -33,16 +32,11 @@ namespace Jellyfin.Server.Implementations.Users /// Initializes a new instance of the class. /// /// The configuration manager. - /// The JSON serializer. /// The application host. - public DefaultPasswordResetProvider( - IServerConfigurationManager configurationManager, - IJsonSerializer jsonSerializer, - IApplicationHost appHost) + public DefaultPasswordResetProvider(IServerConfigurationManager configurationManager, IApplicationHost appHost) { _passwordResetFileBaseDir = configurationManager.ApplicationPaths.ProgramDataPath; _passwordResetFileBase = Path.Combine(_passwordResetFileBaseDir, BaseResetFileName); - _jsonSerializer = jsonSerializer; _appHost = appHost; // TODO: Remove the circular dependency on UserManager } @@ -63,7 +57,7 @@ namespace Jellyfin.Server.Implementations.Users SerializablePasswordReset spr; await using (var str = File.OpenRead(resetFile)) { - spr = await _jsonSerializer.DeserializeFromStreamAsync(str).ConfigureAwait(false); + spr = await JsonSerializer.DeserializeAsync(str).ConfigureAwait(false); } if (spr.ExpirationDate < DateTime.UtcNow) @@ -119,7 +113,7 @@ namespace Jellyfin.Server.Implementations.Users await using (FileStream fileStream = File.OpenWrite(filePath)) { - _jsonSerializer.SerializeToStream(spr, fileStream); + fileStream.Write(JsonSerializer.SerializeToUtf8Bytes(spr)); await fileStream.FlushAsync().ConfigureAwait(false); } From 0d13d830bb3ae9fc0098aa802c16e428f6929341 Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Fri, 24 Jul 2020 16:30:54 -0400 Subject: [PATCH 20/60] Migrate skip lengths. --- .../Migrations/Routines/MigrateDisplayPreferencesDb.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/Jellyfin.Server/Migrations/Routines/MigrateDisplayPreferencesDb.cs b/Jellyfin.Server/Migrations/Routines/MigrateDisplayPreferencesDb.cs index 588c0ecdde..f46fb7e89c 100644 --- a/Jellyfin.Server/Migrations/Routines/MigrateDisplayPreferencesDb.cs +++ b/Jellyfin.Server/Migrations/Routines/MigrateDisplayPreferencesDb.cs @@ -1,4 +1,5 @@ using System; +using System.Globalization; using System.IO; using System.Text.Json; using System.Text.Json.Serialization; @@ -88,7 +89,13 @@ namespace Jellyfin.Server.Migrations.Routines RememberIndexing = dto.RememberIndexing, RememberSorting = dto.RememberSorting, ScrollDirection = dto.ScrollDirection, - ChromecastVersion = chromecastVersion + ChromecastVersion = chromecastVersion, + SkipForwardLength = dto.CustomPrefs.TryGetValue("skipForwardLength", out var length) + ? int.Parse(length, CultureInfo.InvariantCulture) + : 30000, + SkipBackwardLength = dto.CustomPrefs.TryGetValue("skipBackLength", out length) + ? int.Parse(length, CultureInfo.InvariantCulture) + : 30000 }; for (int i = 0; i < 7; i++) From ff7105982a291c6823aaae90adc43d5f0e82ed98 Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Fri, 24 Jul 2020 16:32:24 -0400 Subject: [PATCH 21/60] Read skip lengths from server. --- MediaBrowser.Api/DisplayPreferencesService.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/MediaBrowser.Api/DisplayPreferencesService.cs b/MediaBrowser.Api/DisplayPreferencesService.cs index cca2092e97..0323284228 100644 --- a/MediaBrowser.Api/DisplayPreferencesService.cs +++ b/MediaBrowser.Api/DisplayPreferencesService.cs @@ -104,6 +104,8 @@ namespace MediaBrowser.Api } dto.CustomPrefs["chromecastVersion"] = result.ChromecastVersion.ToString().ToLowerInvariant(); + dto.CustomPrefs["skipForwardLength"] = result.SkipForwardLength.ToString(); + dto.CustomPrefs["skipBackLength"] = result.SkipBackwardLength.ToString(); return ToOptimizedResult(dto); } From 9fcf23bd213c311b47dcc4d5c124040b6bdbbc52 Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Fri, 24 Jul 2020 16:34:19 -0400 Subject: [PATCH 22/60] Migrate EnableNextVideoInfoOverlay --- .../Migrations/Routines/MigrateDisplayPreferencesDb.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Jellyfin.Server/Migrations/Routines/MigrateDisplayPreferencesDb.cs b/Jellyfin.Server/Migrations/Routines/MigrateDisplayPreferencesDb.cs index f46fb7e89c..d8b081bb29 100644 --- a/Jellyfin.Server/Migrations/Routines/MigrateDisplayPreferencesDb.cs +++ b/Jellyfin.Server/Migrations/Routines/MigrateDisplayPreferencesDb.cs @@ -95,7 +95,10 @@ namespace Jellyfin.Server.Migrations.Routines : 30000, SkipBackwardLength = dto.CustomPrefs.TryGetValue("skipBackLength", out length) ? int.Parse(length, CultureInfo.InvariantCulture) - : 30000 + : 30000, + EnableNextVideoInfoOverlay = dto.CustomPrefs.TryGetValue("enableNextVideoInfoOverlay", out var enabled) + ? bool.Parse(enabled) + : true }; for (int i = 0; i < 7; i++) From 13d919f23649bf159ff44fb3f5b4e132549cbbf7 Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Fri, 24 Jul 2020 16:35:20 -0400 Subject: [PATCH 23/60] Read EnableNextVideoInfoOverlay from database. --- MediaBrowser.Api/DisplayPreferencesService.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/MediaBrowser.Api/DisplayPreferencesService.cs b/MediaBrowser.Api/DisplayPreferencesService.cs index 0323284228..0f3427e541 100644 --- a/MediaBrowser.Api/DisplayPreferencesService.cs +++ b/MediaBrowser.Api/DisplayPreferencesService.cs @@ -106,6 +106,7 @@ namespace MediaBrowser.Api dto.CustomPrefs["chromecastVersion"] = result.ChromecastVersion.ToString().ToLowerInvariant(); dto.CustomPrefs["skipForwardLength"] = result.SkipForwardLength.ToString(); dto.CustomPrefs["skipBackLength"] = result.SkipBackwardLength.ToString(); + dto.CustomPrefs["enableNextVideoInfoOverlay"] = result.EnableNextVideoInfoOverlay.ToString(); return ToOptimizedResult(dto); } From 1aa853067a5b6daf54bfd01b38e4a4c8256d07c3 Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Sat, 25 Jul 2020 13:07:12 -0400 Subject: [PATCH 24/60] Use async json serialization. --- .../Users/DefaultPasswordResetProvider.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jellyfin.Server.Implementations/Users/DefaultPasswordResetProvider.cs b/Jellyfin.Server.Implementations/Users/DefaultPasswordResetProvider.cs index 9b0201bfbf..6cb13cd23e 100644 --- a/Jellyfin.Server.Implementations/Users/DefaultPasswordResetProvider.cs +++ b/Jellyfin.Server.Implementations/Users/DefaultPasswordResetProvider.cs @@ -113,7 +113,7 @@ namespace Jellyfin.Server.Implementations.Users await using (FileStream fileStream = File.OpenWrite(filePath)) { - fileStream.Write(JsonSerializer.SerializeToUtf8Bytes(spr)); + await JsonSerializer.SerializeAsync(fileStream, spr).ConfigureAwait(false); await fileStream.FlushAsync().ConfigureAwait(false); } From 965c447c63b21a094bdca5506c1a0d30ec608f74 Mon Sep 17 00:00:00 2001 From: "Joshua M. Boniface" Date: Mon, 27 Jul 2020 19:01:33 -0400 Subject: [PATCH 25/60] Fix bump_version so it works properly --- bump_version | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/bump_version b/bump_version index 46b7f86e05..1c943f691d 100755 --- a/bump_version +++ b/bump_version @@ -4,6 +4,7 @@ set -o errexit set -o pipefail +set -o xtrace usage() { echo -e "bump_version - increase the shared version and generate changelogs" @@ -58,7 +59,7 @@ sed -i "s/${old_version_sed}/${new_version}/g" ${debian_equivs_file} debian_changelog_file="debian/changelog" debian_changelog_temp="$( mktemp )" # Create new temp file with our changelog -echo -e "jellyfin (${new_version_deb}) unstable; urgency=medium +echo -e "jellyfin-server (${new_version_deb}) unstable; urgency=medium * New upstream version ${new_version}; release changelog at https://github.com/jellyfin/jellyfin/releases/tag/v${new_version} From a511e0ac0b2cf600f96d18e159a0264fc519ace8 Mon Sep 17 00:00:00 2001 From: "Joshua M. Boniface" Date: Mon, 27 Jul 2020 19:10:40 -0400 Subject: [PATCH 26/60] Bump master version to 10.7.0 for next release --- SharedVersion.cs | 4 ++-- build.yaml | 2 +- debian/changelog | 6 ++++++ debian/metapackage/jellyfin | 2 +- fedora/jellyfin.spec | 4 +++- 5 files changed, 13 insertions(+), 5 deletions(-) diff --git a/SharedVersion.cs b/SharedVersion.cs index 6981c1ca9d..d17074fc0d 100644 --- a/SharedVersion.cs +++ b/SharedVersion.cs @@ -1,4 +1,4 @@ using System.Reflection; -[assembly: AssemblyVersion("10.6.0")] -[assembly: AssemblyFileVersion("10.6.0")] +[assembly: AssemblyVersion("10.7.0")] +[assembly: AssemblyFileVersion("10.7.0")] diff --git a/build.yaml b/build.yaml index 9e590e5a01..7c32d955c1 100644 --- a/build.yaml +++ b/build.yaml @@ -1,7 +1,7 @@ --- # We just wrap `build` so this is really it name: "jellyfin" -version: "10.6.0" +version: "10.7.0" packages: - debian.amd64 - debian.arm64 diff --git a/debian/changelog b/debian/changelog index d38d038051..184143fe9a 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,9 @@ +jellyfin-server (10.7.0-1) unstable; urgency=medium + + * Forthcoming stable release + + -- Jellyfin Packaging Team Mon, 27 Jul 2020 19:09:45 -0400 + jellyfin-server (10.6.0-2) unstable; urgency=medium * Fix upgrade bug diff --git a/debian/metapackage/jellyfin b/debian/metapackage/jellyfin index 9a41eafaed..026fcb8217 100644 --- a/debian/metapackage/jellyfin +++ b/debian/metapackage/jellyfin @@ -5,7 +5,7 @@ Homepage: https://jellyfin.org Standards-Version: 3.9.2 Package: jellyfin -Version: 10.6.0 +Version: 10.7.0 Maintainer: Jellyfin Packaging Team Depends: jellyfin-server, jellyfin-web Description: Provides the Jellyfin Free Software Media System diff --git a/fedora/jellyfin.spec b/fedora/jellyfin.spec index 9311864a63..b0a729a9e5 100644 --- a/fedora/jellyfin.spec +++ b/fedora/jellyfin.spec @@ -7,7 +7,7 @@ %endif Name: jellyfin -Version: 10.6.0 +Version: 10.7.0 Release: 1%{?dist} Summary: The Free Software Media System License: GPLv3 @@ -139,6 +139,8 @@ fi %systemd_postun_with_restart jellyfin.service %changelog +* Mon Jul 27 2020 Jellyfin Packaging Team +- Forthcoming stable release * Mon Mar 23 2020 Jellyfin Packaging Team - Forthcoming stable release * Fri Oct 11 2019 Jellyfin Packaging Team From 592d2480ca9c424c7b8b8f4be2bdfa81b4476f0c Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Mon, 27 Jul 2020 19:22:34 -0400 Subject: [PATCH 27/60] Make adjustments to display preference entities. --- Jellyfin.Data/Entities/DisplayPreferences.cs | 65 ++-------- .../Entities/LibraryDisplayPreferences.cs | 120 ++++++++++++++++++ Jellyfin.Data/Entities/User.cs | 12 +- 3 files changed, 143 insertions(+), 54 deletions(-) create mode 100644 Jellyfin.Data/Entities/LibraryDisplayPreferences.cs diff --git a/Jellyfin.Data/Entities/DisplayPreferences.cs b/Jellyfin.Data/Entities/DisplayPreferences.cs index bcb872db37..44b70d970c 100644 --- a/Jellyfin.Data/Entities/DisplayPreferences.cs +++ b/Jellyfin.Data/Entities/DisplayPreferences.cs @@ -14,14 +14,18 @@ namespace Jellyfin.Data.Entities /// /// Initializes a new instance of the class. /// - /// The client string. /// The user's id. - public DisplayPreferences(string client, Guid userId) + /// The client string. + public DisplayPreferences(Guid userId, string client) { - RememberIndexing = false; - ShowBackdrop = true; - Client = client; UserId = userId; + Client = client; + ShowSidebar = false; + ShowBackdrop = true; + SkipForwardLength = 30000; + SkipBackwardLength = 10000; + ScrollDirection = ScrollDirection.Horizontal; + ChromecastVersion = ChromecastVersion.Stable; HomeSections = new HashSet(); } @@ -50,50 +54,17 @@ namespace Jellyfin.Data.Entities /// public Guid UserId { get; set; } - /// - /// Gets or sets the id of the associated item. - /// - /// - /// This is currently unused. In the future, this will allow us to have users set - /// display preferences per item. - /// - public Guid? ItemId { get; set; } - /// /// Gets or sets the client string. /// /// - /// Required. Max Length = 64. + /// Required. Max Length = 32. /// [Required] - [MaxLength(64)] - [StringLength(64)] + [MaxLength(32)] + [StringLength(32)] public string Client { get; set; } - /// - /// Gets or sets a value indicating whether the indexing should be remembered. - /// - /// - /// Required. - /// - public bool RememberIndexing { get; set; } - - /// - /// Gets or sets a value indicating whether the sorting type should be remembered. - /// - /// - /// Required. - /// - public bool RememberSorting { get; set; } - - /// - /// Gets or sets the sort order. - /// - /// - /// Required. - /// - public SortOrder SortOrder { get; set; } - /// /// Gets or sets a value indicating whether to show the sidebar. /// @@ -110,18 +81,6 @@ namespace Jellyfin.Data.Entities /// public bool ShowBackdrop { get; set; } - /// - /// Gets or sets what the view should be sorted by. - /// - [MaxLength(64)] - [StringLength(64)] - public string SortBy { get; set; } - - /// - /// Gets or sets the view type. - /// - public ViewType? ViewType { get; set; } - /// /// Gets or sets the scroll direction. /// diff --git a/Jellyfin.Data/Entities/LibraryDisplayPreferences.cs b/Jellyfin.Data/Entities/LibraryDisplayPreferences.cs new file mode 100644 index 0000000000..87be1c6f7e --- /dev/null +++ b/Jellyfin.Data/Entities/LibraryDisplayPreferences.cs @@ -0,0 +1,120 @@ +using System; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using Jellyfin.Data.Enums; + +namespace Jellyfin.Data.Entities +{ + public class LibraryDisplayPreferences + { + /// + /// Initializes a new instance of the class. + /// + /// The user id. + /// The item id. + /// The client. + public LibraryDisplayPreferences(Guid userId, Guid itemId, string client) + { + UserId = userId; + ItemId = itemId; + Client = client; + + SortBy = "SortName"; + ViewType = ViewType.Poster; + SortOrder = SortOrder.Ascending; + RememberSorting = false; + RememberIndexing = false; + } + + /// + /// Initializes a new instance of the class. + /// + protected LibraryDisplayPreferences() + { + } + + /// + /// Gets or sets the Id. + /// + /// + /// Required. + /// + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public int Id { get; protected set; } + + /// + /// Gets or sets the user Id. + /// + /// + /// Required. + /// + public Guid UserId { get; set; } + + /// + /// Gets or sets the id of the associated item. + /// + /// + /// Required. + /// + public Guid ItemId { get; set; } + + /// + /// Gets or sets the client string. + /// + /// + /// Required. Max Length = 32. + /// + [Required] + [MaxLength(32)] + [StringLength(32)] + public string Client { get; set; } + + /// + /// Gets or sets the view type. + /// + /// + /// Required. + /// + public ViewType ViewType { get; set; } + + /// + /// Gets or sets a value indicating whether the indexing should be remembered. + /// + /// + /// Required. + /// + public bool RememberIndexing { get; set; } + + /// + /// Gets or sets what the view should be indexed by. + /// + public IndexingKind? IndexBy { get; set; } + + /// + /// Gets or sets a value indicating whether the sorting type should be remembered. + /// + /// + /// Required. + /// + public bool RememberSorting { get; set; } + + /// + /// Gets or sets what the view should be sorted by. + /// + /// + /// Required. + /// + [Required] + [MaxLength(64)] + [StringLength(64)] + public string SortBy { get; set; } + + /// + /// Gets or sets the sort order. + /// + /// + /// Required. + /// + public SortOrder SortOrder { get; set; } + } +} diff --git a/Jellyfin.Data/Entities/User.cs b/Jellyfin.Data/Entities/User.cs index d93144e3a5..dc4bd9979c 100644 --- a/Jellyfin.Data/Entities/User.cs +++ b/Jellyfin.Data/Entities/User.cs @@ -48,6 +48,7 @@ namespace Jellyfin.Data.Entities PasswordResetProviderId = passwordResetProviderId; AccessSchedules = new HashSet(); + LibraryDisplayPreferences = new HashSet(); // Groups = new HashSet(); Permissions = new HashSet(); Preferences = new HashSet(); @@ -327,6 +328,15 @@ namespace Jellyfin.Data.Entities // [ForeignKey("UserId")] public virtual ImageInfo ProfileImage { get; set; } + /// + /// Gets or sets the user's display preferences. + /// + /// + /// Required. + /// + [Required] + public virtual DisplayPreferences DisplayPreferences { get; set; } + [Required] public SyncPlayAccess SyncPlayAccess { get; set; } @@ -352,7 +362,7 @@ namespace Jellyfin.Data.Entities /// /// Gets or sets the list of item display preferences. /// - public virtual ICollection DisplayPreferences { get; protected set; } + public virtual ICollection LibraryDisplayPreferences { get; protected set; } /* /// From 68a185fd02844698ac5ecd5618d590ae254d95cf Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Mon, 27 Jul 2020 20:40:21 -0400 Subject: [PATCH 28/60] Serialize/deserialize new entities properly. --- ...eferences.cs => ItemDisplayPreferences.cs} | 10 +-- Jellyfin.Data/Entities/User.cs | 4 +- Jellyfin.Server.Implementations/JellyfinDb.cs | 2 + .../Users/DisplayPreferencesManager.cs | 39 ++++++++++- .../Routines/MigrateDisplayPreferencesDb.cs | 33 +++++++-- MediaBrowser.Api/DisplayPreferencesService.cs | 70 ++++++++++++------- .../IDisplayPreferencesManager.cs | 24 +++++++ 7 files changed, 139 insertions(+), 43 deletions(-) rename Jellyfin.Data/Entities/{LibraryDisplayPreferences.cs => ItemDisplayPreferences.cs} (89%) diff --git a/Jellyfin.Data/Entities/LibraryDisplayPreferences.cs b/Jellyfin.Data/Entities/ItemDisplayPreferences.cs similarity index 89% rename from Jellyfin.Data/Entities/LibraryDisplayPreferences.cs rename to Jellyfin.Data/Entities/ItemDisplayPreferences.cs index 87be1c6f7e..95c08e6c6c 100644 --- a/Jellyfin.Data/Entities/LibraryDisplayPreferences.cs +++ b/Jellyfin.Data/Entities/ItemDisplayPreferences.cs @@ -5,15 +5,15 @@ using Jellyfin.Data.Enums; namespace Jellyfin.Data.Entities { - public class LibraryDisplayPreferences + public class ItemDisplayPreferences { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The user id. /// The item id. /// The client. - public LibraryDisplayPreferences(Guid userId, Guid itemId, string client) + public ItemDisplayPreferences(Guid userId, Guid itemId, string client) { UserId = userId; ItemId = itemId; @@ -27,9 +27,9 @@ namespace Jellyfin.Data.Entities } /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// - protected LibraryDisplayPreferences() + protected ItemDisplayPreferences() { } diff --git a/Jellyfin.Data/Entities/User.cs b/Jellyfin.Data/Entities/User.cs index dc4bd9979c..50810561f5 100644 --- a/Jellyfin.Data/Entities/User.cs +++ b/Jellyfin.Data/Entities/User.cs @@ -48,7 +48,7 @@ namespace Jellyfin.Data.Entities PasswordResetProviderId = passwordResetProviderId; AccessSchedules = new HashSet(); - LibraryDisplayPreferences = new HashSet(); + ItemDisplayPreferences = new HashSet(); // Groups = new HashSet(); Permissions = new HashSet(); Preferences = new HashSet(); @@ -362,7 +362,7 @@ namespace Jellyfin.Data.Entities /// /// Gets or sets the list of item display preferences. /// - public virtual ICollection LibraryDisplayPreferences { get; protected set; } + public virtual ICollection ItemDisplayPreferences { get; protected set; } /* /// diff --git a/Jellyfin.Server.Implementations/JellyfinDb.cs b/Jellyfin.Server.Implementations/JellyfinDb.cs index 4ad6840639..7d864ebc69 100644 --- a/Jellyfin.Server.Implementations/JellyfinDb.cs +++ b/Jellyfin.Server.Implementations/JellyfinDb.cs @@ -32,6 +32,8 @@ namespace Jellyfin.Server.Implementations public virtual DbSet ImageInfos { get; set; } + public virtual DbSet ItemDisplayPreferences { get; set; } + public virtual DbSet Permissions { get; set; } public virtual DbSet Preferences { get; set; } diff --git a/Jellyfin.Server.Implementations/Users/DisplayPreferencesManager.cs b/Jellyfin.Server.Implementations/Users/DisplayPreferencesManager.cs index b7c65fc2cb..7c5c5a3ec5 100644 --- a/Jellyfin.Server.Implementations/Users/DisplayPreferencesManager.cs +++ b/Jellyfin.Server.Implementations/Users/DisplayPreferencesManager.cs @@ -1,6 +1,7 @@ #pragma warning disable CA1307 using System; +using System.Collections.Generic; using System.Linq; using Jellyfin.Data.Entities; using MediaBrowser.Controller; @@ -31,17 +32,43 @@ namespace Jellyfin.Server.Implementations.Users var prefs = dbContext.DisplayPreferences .Include(pref => pref.HomeSections) .FirstOrDefault(pref => - pref.UserId == userId && pref.ItemId == null && string.Equals(pref.Client, client)); + pref.UserId == userId && string.Equals(pref.Client, client)); if (prefs == null) { - prefs = new DisplayPreferences(client, userId); + prefs = new DisplayPreferences(userId, client); dbContext.DisplayPreferences.Add(prefs); } return prefs; } + /// + public ItemDisplayPreferences GetItemDisplayPreferences(Guid userId, Guid itemId, string client) + { + using var dbContext = _dbProvider.CreateContext(); + var prefs = dbContext.ItemDisplayPreferences + .FirstOrDefault(pref => pref.UserId == userId && pref.ItemId == itemId && string.Equals(pref.Client, client)); + + if (prefs == null) + { + prefs = new ItemDisplayPreferences(userId, Guid.Empty, client); + dbContext.ItemDisplayPreferences.Add(prefs); + } + + return prefs; + } + + /// + public IList ListItemDisplayPreferences(Guid userId, string client) + { + using var dbContext = _dbProvider.CreateContext(); + + return dbContext.ItemDisplayPreferences + .Where(prefs => prefs.UserId == userId && prefs.ItemId != Guid.Empty && string.Equals(prefs.Client, client)) + .ToList(); + } + /// public void SaveChanges(DisplayPreferences preferences) { @@ -49,5 +76,13 @@ namespace Jellyfin.Server.Implementations.Users dbContext.Update(preferences); dbContext.SaveChanges(); } + + /// + public void SaveChanges(ItemDisplayPreferences preferences) + { + using var dbContext = _dbProvider.CreateContext(); + dbContext.Update(preferences); + dbContext.SaveChanges(); + } } } diff --git a/Jellyfin.Server/Migrations/Routines/MigrateDisplayPreferencesDb.cs b/Jellyfin.Server/Migrations/Routines/MigrateDisplayPreferencesDb.cs index d8b081bb29..6a78bff4fa 100644 --- a/Jellyfin.Server/Migrations/Routines/MigrateDisplayPreferencesDb.cs +++ b/Jellyfin.Server/Migrations/Routines/MigrateDisplayPreferencesDb.cs @@ -1,6 +1,7 @@ using System; using System.Globalization; using System.IO; +using System.Linq; using System.Text.Json; using System.Text.Json.Serialization; using Jellyfin.Data.Entities; @@ -78,16 +79,11 @@ namespace Jellyfin.Server.Migrations.Routines : ChromecastVersion.Stable : ChromecastVersion.Stable; - var displayPreferences = new DisplayPreferences(result[2].ToString(), new Guid(result[1].ToBlob())) + var displayPreferences = new DisplayPreferences(new Guid(result[1].ToBlob()), result[2].ToString()) { - ViewType = Enum.TryParse(dto.ViewType, true, out var viewType) ? viewType : (ViewType?)null, IndexBy = Enum.TryParse(dto.IndexBy, true, out var indexBy) ? indexBy : (IndexingKind?)null, ShowBackdrop = dto.ShowBackdrop, ShowSidebar = dto.ShowSidebar, - SortBy = dto.SortBy, - SortOrder = dto.SortOrder, - RememberIndexing = dto.RememberIndexing, - RememberSorting = dto.RememberSorting, ScrollDirection = dto.ScrollDirection, ChromecastVersion = chromecastVersion, SkipForwardLength = dto.CustomPrefs.TryGetValue("skipForwardLength", out var length) @@ -95,7 +91,7 @@ namespace Jellyfin.Server.Migrations.Routines : 30000, SkipBackwardLength = dto.CustomPrefs.TryGetValue("skipBackLength", out length) ? int.Parse(length, CultureInfo.InvariantCulture) - : 30000, + : 10000, EnableNextVideoInfoOverlay = dto.CustomPrefs.TryGetValue("enableNextVideoInfoOverlay", out var enabled) ? bool.Parse(enabled) : true @@ -112,6 +108,29 @@ namespace Jellyfin.Server.Migrations.Routines }); } + foreach (var key in dto.CustomPrefs.Keys.Where(key => key.StartsWith("landing-", StringComparison.Ordinal))) + { + if (!Guid.TryParse(key.AsSpan().Slice("landing-".Length), out var itemId)) + { + continue; + } + + var libraryDisplayPreferences = new ItemDisplayPreferences(displayPreferences.UserId, itemId, displayPreferences.Client) + { + SortBy = dto.SortBy, + SortOrder = dto.SortOrder, + RememberIndexing = dto.RememberIndexing, + RememberSorting = dto.RememberSorting, + }; + + if (Enum.TryParse(dto.ViewType, true, out var viewType)) + { + libraryDisplayPreferences.ViewType = viewType; + } + + dbContext.ItemDisplayPreferences.Add(libraryDisplayPreferences); + } + dbContext.Add(displayPreferences); } diff --git a/MediaBrowser.Api/DisplayPreferencesService.cs b/MediaBrowser.Api/DisplayPreferencesService.cs index 0f3427e541..416d63100f 100644 --- a/MediaBrowser.Api/DisplayPreferencesService.cs +++ b/MediaBrowser.Api/DisplayPreferencesService.cs @@ -76,37 +76,38 @@ namespace MediaBrowser.Api /// The request. public object Get(GetDisplayPreferences request) { - var result = _displayPreferencesManager.GetDisplayPreferences(Guid.Parse(request.UserId), request.Client); - - if (result == null) - { - return null; - } + var displayPreferences = _displayPreferencesManager.GetDisplayPreferences(Guid.Parse(request.UserId), request.Client); + var itemPreferences = _displayPreferencesManager.GetItemDisplayPreferences(displayPreferences.UserId, Guid.Empty, displayPreferences.Client); var dto = new DisplayPreferencesDto { - Client = result.Client, - Id = result.UserId.ToString(), - ViewType = result.ViewType?.ToString(), - SortBy = result.SortBy, - SortOrder = result.SortOrder, - IndexBy = result.IndexBy?.ToString(), - RememberIndexing = result.RememberIndexing, - RememberSorting = result.RememberSorting, - ScrollDirection = result.ScrollDirection, - ShowBackdrop = result.ShowBackdrop, - ShowSidebar = result.ShowSidebar + Client = displayPreferences.Client, + Id = displayPreferences.UserId.ToString(), + ViewType = itemPreferences.ViewType.ToString(), + SortBy = itemPreferences.SortBy, + SortOrder = itemPreferences.SortOrder, + IndexBy = displayPreferences.IndexBy?.ToString(), + RememberIndexing = itemPreferences.RememberIndexing, + RememberSorting = itemPreferences.RememberSorting, + ScrollDirection = displayPreferences.ScrollDirection, + ShowBackdrop = displayPreferences.ShowBackdrop, + ShowSidebar = displayPreferences.ShowSidebar }; - foreach (var homeSection in result.HomeSections) + foreach (var homeSection in displayPreferences.HomeSections) { dto.CustomPrefs["homesection" + homeSection.Order] = homeSection.Type.ToString().ToLowerInvariant(); } - dto.CustomPrefs["chromecastVersion"] = result.ChromecastVersion.ToString().ToLowerInvariant(); - dto.CustomPrefs["skipForwardLength"] = result.SkipForwardLength.ToString(); - dto.CustomPrefs["skipBackLength"] = result.SkipBackwardLength.ToString(); - dto.CustomPrefs["enableNextVideoInfoOverlay"] = result.EnableNextVideoInfoOverlay.ToString(); + foreach (var itemDisplayPreferences in _displayPreferencesManager.ListItemDisplayPreferences(displayPreferences.UserId, displayPreferences.Client)) + { + dto.CustomPrefs["landing-" + itemDisplayPreferences.ItemId] = itemDisplayPreferences.ViewType.ToString().ToLowerInvariant(); + } + + dto.CustomPrefs["chromecastVersion"] = displayPreferences.ChromecastVersion.ToString().ToLowerInvariant(); + dto.CustomPrefs["skipForwardLength"] = displayPreferences.SkipForwardLength.ToString(); + dto.CustomPrefs["skipBackLength"] = displayPreferences.SkipBackwardLength.ToString(); + dto.CustomPrefs["enableNextVideoInfoOverlay"] = displayPreferences.EnableNextVideoInfoOverlay.ToString(); return ToOptimizedResult(dto); } @@ -130,14 +131,10 @@ namespace MediaBrowser.Api var prefs = _displayPreferencesManager.GetDisplayPreferences(Guid.Parse(request.UserId), request.Client); - prefs.ViewType = Enum.TryParse(request.ViewType, true, out var viewType) ? viewType : (ViewType?)null; prefs.IndexBy = Enum.TryParse(request.IndexBy, true, out var indexBy) ? indexBy : (IndexingKind?)null; prefs.ShowBackdrop = request.ShowBackdrop; prefs.ShowSidebar = request.ShowSidebar; - prefs.SortBy = request.SortBy; - prefs.SortOrder = request.SortOrder; - prefs.RememberIndexing = request.RememberIndexing; - prefs.RememberSorting = request.RememberSorting; + prefs.ScrollDirection = request.ScrollDirection; prefs.ChromecastVersion = request.CustomPrefs.TryGetValue("chromecastVersion", out var chromecastVersion) ? Enum.Parse(chromecastVersion, true) @@ -164,7 +161,26 @@ namespace MediaBrowser.Api }); } + foreach (var key in request.CustomPrefs.Keys.Where(key => key.StartsWith("landing-"))) + { + var itemPreferences = _displayPreferencesManager.GetItemDisplayPreferences(prefs.UserId, Guid.Parse(key.Substring("landing-".Length)), prefs.Client); + itemPreferences.ViewType = Enum.Parse(request.ViewType); + _displayPreferencesManager.SaveChanges(itemPreferences); + } + + var itemPrefs = _displayPreferencesManager.GetItemDisplayPreferences(prefs.UserId, Guid.Empty, prefs.Client); + itemPrefs.SortBy = request.SortBy; + itemPrefs.SortOrder = request.SortOrder; + itemPrefs.RememberIndexing = request.RememberIndexing; + itemPrefs.RememberSorting = request.RememberSorting; + + if (Enum.TryParse(request.ViewType, true, out var viewType)) + { + itemPrefs.ViewType = viewType; + } + _displayPreferencesManager.SaveChanges(prefs); + _displayPreferencesManager.SaveChanges(itemPrefs); } } } diff --git a/MediaBrowser.Controller/IDisplayPreferencesManager.cs b/MediaBrowser.Controller/IDisplayPreferencesManager.cs index e27b0ec7c3..b6bfed3e59 100644 --- a/MediaBrowser.Controller/IDisplayPreferencesManager.cs +++ b/MediaBrowser.Controller/IDisplayPreferencesManager.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using Jellyfin.Data.Entities; namespace MediaBrowser.Controller @@ -16,10 +17,33 @@ namespace MediaBrowser.Controller /// The associated display preferences. DisplayPreferences GetDisplayPreferences(Guid userId, string client); + /// + /// Gets the default item display preferences for the user and client. + /// + /// The user id. + /// The item id. + /// The client string. + /// The item display preferences. + ItemDisplayPreferences GetItemDisplayPreferences(Guid userId, Guid itemId, string client); + + /// + /// Gets all of the item display preferences for the user and client. + /// + /// The user id. + /// The client string. + /// A list of item display preferences. + IList ListItemDisplayPreferences(Guid userId, string client); + /// /// Saves changes to the provided display preferences. /// /// The display preferences to save. void SaveChanges(DisplayPreferences preferences); + + /// + /// Saves changes to the provided item display preferences. + /// + /// The item display preferences to save. + void SaveChanges(ItemDisplayPreferences preferences); } } From 7d6c1befe51ec20c4727426ae7bff8bb316e3495 Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Mon, 27 Jul 2020 20:44:46 -0400 Subject: [PATCH 29/60] Add Dashboard theme. --- Jellyfin.Data/Entities/DisplayPreferences.cs | 8 ++++++++ MediaBrowser.Api/DisplayPreferencesService.cs | 1 + 2 files changed, 9 insertions(+) diff --git a/Jellyfin.Data/Entities/DisplayPreferences.cs b/Jellyfin.Data/Entities/DisplayPreferences.cs index 44b70d970c..d2cf991958 100644 --- a/Jellyfin.Data/Entities/DisplayPreferences.cs +++ b/Jellyfin.Data/Entities/DisplayPreferences.cs @@ -26,6 +26,7 @@ namespace Jellyfin.Data.Entities SkipBackwardLength = 10000; ScrollDirection = ScrollDirection.Horizontal; ChromecastVersion = ChromecastVersion.Stable; + DashboardTheme = string.Empty; HomeSections = new HashSet(); } @@ -126,6 +127,13 @@ namespace Jellyfin.Data.Entities /// public bool EnableNextVideoInfoOverlay { get; set; } + /// + /// Gets or sets the dashboard theme. + /// + [MaxLength(32)] + [StringLength(32)] + public string DashboardTheme { get; set; } + /// /// Gets or sets the home sections. /// diff --git a/MediaBrowser.Api/DisplayPreferencesService.cs b/MediaBrowser.Api/DisplayPreferencesService.cs index 416d63100f..2cc7db6242 100644 --- a/MediaBrowser.Api/DisplayPreferencesService.cs +++ b/MediaBrowser.Api/DisplayPreferencesService.cs @@ -144,6 +144,7 @@ namespace MediaBrowser.Api : true; prefs.SkipBackwardLength = request.CustomPrefs.TryGetValue("skipBackLength", out var skipBackLength) ? int.Parse(skipBackLength) : 10000; prefs.SkipForwardLength = request.CustomPrefs.TryGetValue("skipForwardLength", out var skipForwardLength) ? int.Parse(skipForwardLength) : 30000; + prefs.DashboardTheme = request.CustomPrefs.TryGetValue("dashboardTheme", out var theme) ? theme : string.Empty; prefs.HomeSections.Clear(); foreach (var key in request.CustomPrefs.Keys.Where(key => key.StartsWith("homesection"))) From 754837f16fef1c56cc8ccb30b75a0e316a72906a Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Mon, 27 Jul 2020 20:50:58 -0400 Subject: [PATCH 30/60] Add tv home. --- Jellyfin.Data/Entities/DisplayPreferences.cs | 8 ++++++++ .../Migrations/Routines/MigrateDisplayPreferencesDb.cs | 4 +++- MediaBrowser.Api/DisplayPreferencesService.cs | 2 ++ 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/Jellyfin.Data/Entities/DisplayPreferences.cs b/Jellyfin.Data/Entities/DisplayPreferences.cs index d2cf991958..cda83f6bb7 100644 --- a/Jellyfin.Data/Entities/DisplayPreferences.cs +++ b/Jellyfin.Data/Entities/DisplayPreferences.cs @@ -27,6 +27,7 @@ namespace Jellyfin.Data.Entities ScrollDirection = ScrollDirection.Horizontal; ChromecastVersion = ChromecastVersion.Stable; DashboardTheme = string.Empty; + TvHome = string.Empty; HomeSections = new HashSet(); } @@ -134,6 +135,13 @@ namespace Jellyfin.Data.Entities [StringLength(32)] public string DashboardTheme { get; set; } + /// + /// Gets or sets the tv home screen. + /// + [MaxLength(32)] + [StringLength(32)] + public string TvHome { get; set; } + /// /// Gets or sets the home sections. /// diff --git a/Jellyfin.Server/Migrations/Routines/MigrateDisplayPreferencesDb.cs b/Jellyfin.Server/Migrations/Routines/MigrateDisplayPreferencesDb.cs index 6a78bff4fa..cef2c74351 100644 --- a/Jellyfin.Server/Migrations/Routines/MigrateDisplayPreferencesDb.cs +++ b/Jellyfin.Server/Migrations/Routines/MigrateDisplayPreferencesDb.cs @@ -94,7 +94,9 @@ namespace Jellyfin.Server.Migrations.Routines : 10000, EnableNextVideoInfoOverlay = dto.CustomPrefs.TryGetValue("enableNextVideoInfoOverlay", out var enabled) ? bool.Parse(enabled) - : true + : true, + DashboardTheme = dto.CustomPrefs.TryGetValue("dashboardtheme", out var theme) ? theme : string.Empty, + TvHome = dto.CustomPrefs.TryGetValue("tvhome", out var home) ? home : string.Empty }; for (int i = 0; i < 7; i++) diff --git a/MediaBrowser.Api/DisplayPreferencesService.cs b/MediaBrowser.Api/DisplayPreferencesService.cs index 2cc7db6242..4951dcc22c 100644 --- a/MediaBrowser.Api/DisplayPreferencesService.cs +++ b/MediaBrowser.Api/DisplayPreferencesService.cs @@ -108,6 +108,7 @@ namespace MediaBrowser.Api dto.CustomPrefs["skipForwardLength"] = displayPreferences.SkipForwardLength.ToString(); dto.CustomPrefs["skipBackLength"] = displayPreferences.SkipBackwardLength.ToString(); dto.CustomPrefs["enableNextVideoInfoOverlay"] = displayPreferences.EnableNextVideoInfoOverlay.ToString(); + dto.CustomPrefs["tvhome"] = displayPreferences.TvHome; return ToOptimizedResult(dto); } @@ -145,6 +146,7 @@ namespace MediaBrowser.Api prefs.SkipBackwardLength = request.CustomPrefs.TryGetValue("skipBackLength", out var skipBackLength) ? int.Parse(skipBackLength) : 10000; prefs.SkipForwardLength = request.CustomPrefs.TryGetValue("skipForwardLength", out var skipForwardLength) ? int.Parse(skipForwardLength) : 30000; prefs.DashboardTheme = request.CustomPrefs.TryGetValue("dashboardTheme", out var theme) ? theme : string.Empty; + prefs.TvHome = request.CustomPrefs.TryGetValue("tvhome", out var home) ? home : string.Empty; prefs.HomeSections.Clear(); foreach (var key in request.CustomPrefs.Keys.Where(key => key.StartsWith("homesection"))) From 4b8ab1a8030e13e4e0905f5f0b0dfcbdb97e7966 Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Mon, 27 Jul 2020 21:01:08 -0400 Subject: [PATCH 31/60] Set default value of SortBy during migrations. --- .../Migrations/Routines/MigrateDisplayPreferencesDb.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jellyfin.Server/Migrations/Routines/MigrateDisplayPreferencesDb.cs b/Jellyfin.Server/Migrations/Routines/MigrateDisplayPreferencesDb.cs index cef2c74351..e76d45da7e 100644 --- a/Jellyfin.Server/Migrations/Routines/MigrateDisplayPreferencesDb.cs +++ b/Jellyfin.Server/Migrations/Routines/MigrateDisplayPreferencesDb.cs @@ -119,7 +119,7 @@ namespace Jellyfin.Server.Migrations.Routines var libraryDisplayPreferences = new ItemDisplayPreferences(displayPreferences.UserId, itemId, displayPreferences.Client) { - SortBy = dto.SortBy, + SortBy = dto.SortBy ?? "SortName", SortOrder = dto.SortOrder, RememberIndexing = dto.RememberIndexing, RememberSorting = dto.RememberSorting, From 0798ab3945ad982d68b2e17a6467ce31f9a0e061 Mon Sep 17 00:00:00 2001 From: "Joshua M. Boniface" Date: Mon, 27 Jul 2020 21:35:57 -0400 Subject: [PATCH 32/60] Get and tag with the actual release version in CI --- .ci/azure-pipelines-package.yml | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/.ci/azure-pipelines-package.yml b/.ci/azure-pipelines-package.yml index 2721112991..64d40e7f5f 100644 --- a/.ci/azure-pipelines-package.yml +++ b/.ci/azure-pipelines-package.yml @@ -80,7 +80,15 @@ jobs: pool: vmImage: 'ubuntu-latest' + variables: + - name: JellyfinVersion + value: 0.0.0 + steps: + - script: echo '##vso[task.setvariable variable=JellyfinVersion]$( awk -F "/" "{ print $NF }" <<<"$(Build.SourceBranch)" | sed "s/^v//" )' + displayName: Set release version (stable) + condition: startsWith(variables['Build.SourceBranch'], 'refs/tags') + - task: Docker@2 displayName: 'Push Unstable Image' condition: startsWith(variables['Build.SourceBranch'], 'refs/heads/master') @@ -105,7 +113,7 @@ jobs: containerRegistry: Docker Hub tags: | stable-$(Build.BuildNumber)-$(BuildConfiguration) - stable-$(BuildConfiguration) + $(JellyfinVersion)-$(BuildConfiguration) - job: CollectArtifacts displayName: 'Collect Artifacts' From c3a36485b68c7eeaffd3b60af14a8ef051c25f96 Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Mon, 27 Jul 2020 23:41:16 -0400 Subject: [PATCH 33/60] Recreate display preferences migration. --- ...8005145_AddDisplayPreferences.Designer.cs} | 89 ++++++++++++++----- ...> 20200728005145_AddDisplayPreferences.cs} | 54 +++++++++-- .../Migrations/JellyfinDbModelSnapshot.cs | 87 +++++++++++++----- 3 files changed, 176 insertions(+), 54 deletions(-) rename Jellyfin.Server.Implementations/Migrations/{20200717233541_AddDisplayPreferences.Designer.cs => 20200728005145_AddDisplayPreferences.Designer.cs} (88%) rename Jellyfin.Server.Implementations/Migrations/{20200717233541_AddDisplayPreferences.cs => 20200728005145_AddDisplayPreferences.cs} (68%) diff --git a/Jellyfin.Server.Implementations/Migrations/20200717233541_AddDisplayPreferences.Designer.cs b/Jellyfin.Server.Implementations/Migrations/20200728005145_AddDisplayPreferences.Designer.cs similarity index 88% rename from Jellyfin.Server.Implementations/Migrations/20200717233541_AddDisplayPreferences.Designer.cs rename to Jellyfin.Server.Implementations/Migrations/20200728005145_AddDisplayPreferences.Designer.cs index 392e26daef..d44707d069 100644 --- a/Jellyfin.Server.Implementations/Migrations/20200717233541_AddDisplayPreferences.Designer.cs +++ b/Jellyfin.Server.Implementations/Migrations/20200728005145_AddDisplayPreferences.Designer.cs @@ -11,7 +11,7 @@ using Microsoft.EntityFrameworkCore.Storage.ValueConversion; namespace Jellyfin.Server.Implementations.Migrations { [DbContext(typeof(JellyfinDb))] - [Migration("20200717233541_AddDisplayPreferences")] + [Migration("20200728005145_AddDisplayPreferences")] partial class AddDisplayPreferences { protected override void BuildTargetModel(ModelBuilder modelBuilder) @@ -19,7 +19,7 @@ namespace Jellyfin.Server.Implementations.Migrations #pragma warning disable 612, 618 modelBuilder .HasDefaultSchema("jellyfin") - .HasAnnotation("ProductVersion", "3.1.5"); + .HasAnnotation("ProductVersion", "3.1.6"); modelBuilder.Entity("Jellyfin.Data.Entities.AccessSchedule", b => { @@ -104,7 +104,11 @@ namespace Jellyfin.Server.Implementations.Migrations b.Property("Client") .IsRequired() .HasColumnType("TEXT") - .HasMaxLength(64); + .HasMaxLength(32); + + b.Property("DashboardTheme") + .HasColumnType("TEXT") + .HasMaxLength(32); b.Property("EnableNextVideoInfoOverlay") .HasColumnType("INTEGER"); @@ -112,15 +116,6 @@ namespace Jellyfin.Server.Implementations.Migrations b.Property("IndexBy") .HasColumnType("INTEGER"); - b.Property("ItemId") - .HasColumnType("TEXT"); - - b.Property("RememberIndexing") - .HasColumnType("INTEGER"); - - b.Property("RememberSorting") - .HasColumnType("INTEGER"); - b.Property("ScrollDirection") .HasColumnType("INTEGER"); @@ -136,22 +131,17 @@ namespace Jellyfin.Server.Implementations.Migrations b.Property("SkipForwardLength") .HasColumnType("INTEGER"); - b.Property("SortBy") + b.Property("TvHome") .HasColumnType("TEXT") - .HasMaxLength(64); - - b.Property("SortOrder") - .HasColumnType("INTEGER"); + .HasMaxLength(32); b.Property("UserId") .HasColumnType("TEXT"); - b.Property("ViewType") - .HasColumnType("INTEGER"); - b.HasKey("Id"); - b.HasIndex("UserId"); + b.HasIndex("UserId") + .IsUnique(); b.ToTable("DisplayPreferences"); }); @@ -203,6 +193,50 @@ namespace Jellyfin.Server.Implementations.Migrations b.ToTable("ImageInfos"); }); + modelBuilder.Entity("Jellyfin.Data.Entities.ItemDisplayPreferences", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Client") + .IsRequired() + .HasColumnType("TEXT") + .HasMaxLength(32); + + b.Property("IndexBy") + .HasColumnType("INTEGER"); + + b.Property("ItemId") + .HasColumnType("TEXT"); + + b.Property("RememberIndexing") + .HasColumnType("INTEGER"); + + b.Property("RememberSorting") + .HasColumnType("INTEGER"); + + b.Property("SortBy") + .IsRequired() + .HasColumnType("TEXT") + .HasMaxLength(64); + + b.Property("SortOrder") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("ViewType") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("ItemDisplayPreferences"); + }); + modelBuilder.Entity("Jellyfin.Data.Entities.Permission", b => { b.Property("Id") @@ -375,8 +409,8 @@ namespace Jellyfin.Server.Implementations.Migrations modelBuilder.Entity("Jellyfin.Data.Entities.DisplayPreferences", b => { b.HasOne("Jellyfin.Data.Entities.User", null) - .WithMany("DisplayPreferences") - .HasForeignKey("UserId") + .WithOne("DisplayPreferences") + .HasForeignKey("Jellyfin.Data.Entities.DisplayPreferences", "UserId") .OnDelete(DeleteBehavior.Cascade) .IsRequired(); }); @@ -397,6 +431,15 @@ namespace Jellyfin.Server.Implementations.Migrations .HasForeignKey("Jellyfin.Data.Entities.ImageInfo", "UserId"); }); + modelBuilder.Entity("Jellyfin.Data.Entities.ItemDisplayPreferences", b => + { + b.HasOne("Jellyfin.Data.Entities.User", null) + .WithMany("ItemDisplayPreferences") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + modelBuilder.Entity("Jellyfin.Data.Entities.Permission", b => { b.HasOne("Jellyfin.Data.Entities.User", null) diff --git a/Jellyfin.Server.Implementations/Migrations/20200717233541_AddDisplayPreferences.cs b/Jellyfin.Server.Implementations/Migrations/20200728005145_AddDisplayPreferences.cs similarity index 68% rename from Jellyfin.Server.Implementations/Migrations/20200717233541_AddDisplayPreferences.cs rename to Jellyfin.Server.Implementations/Migrations/20200728005145_AddDisplayPreferences.cs index a5c344fac0..3009f0b61d 100644 --- a/Jellyfin.Server.Implementations/Migrations/20200717233541_AddDisplayPreferences.cs +++ b/Jellyfin.Server.Implementations/Migrations/20200728005145_AddDisplayPreferences.cs @@ -18,21 +18,17 @@ namespace Jellyfin.Server.Implementations.Migrations Id = table.Column(nullable: false) .Annotation("Sqlite:Autoincrement", true), UserId = table.Column(nullable: false), - ItemId = table.Column(nullable: true), - Client = table.Column(maxLength: 64, nullable: false), - RememberIndexing = table.Column(nullable: false), - RememberSorting = table.Column(nullable: false), - SortOrder = table.Column(nullable: false), + Client = table.Column(maxLength: 32, nullable: false), ShowSidebar = table.Column(nullable: false), ShowBackdrop = table.Column(nullable: false), - SortBy = table.Column(maxLength: 64, nullable: true), - ViewType = table.Column(nullable: true), ScrollDirection = table.Column(nullable: false), IndexBy = table.Column(nullable: true), SkipForwardLength = table.Column(nullable: false), SkipBackwardLength = table.Column(nullable: false), ChromecastVersion = table.Column(nullable: false), - EnableNextVideoInfoOverlay = table.Column(nullable: false) + EnableNextVideoInfoOverlay = table.Column(nullable: false), + DashboardTheme = table.Column(maxLength: 32, nullable: true), + TvHome = table.Column(maxLength: 32, nullable: true) }, constraints: table => { @@ -46,6 +42,35 @@ namespace Jellyfin.Server.Implementations.Migrations onDelete: ReferentialAction.Cascade); }); + migrationBuilder.CreateTable( + name: "ItemDisplayPreferences", + schema: "jellyfin", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("Sqlite:Autoincrement", true), + UserId = table.Column(nullable: false), + ItemId = table.Column(nullable: false), + Client = table.Column(maxLength: 32, nullable: false), + ViewType = table.Column(nullable: false), + RememberIndexing = table.Column(nullable: false), + IndexBy = table.Column(nullable: true), + RememberSorting = table.Column(nullable: false), + SortBy = table.Column(maxLength: 64, nullable: false), + SortOrder = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_ItemDisplayPreferences", x => x.Id); + table.ForeignKey( + name: "FK_ItemDisplayPreferences_Users_UserId", + column: x => x.UserId, + principalSchema: "jellyfin", + principalTable: "Users", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + migrationBuilder.CreateTable( name: "HomeSection", schema: "jellyfin", @@ -73,13 +98,20 @@ namespace Jellyfin.Server.Implementations.Migrations name: "IX_DisplayPreferences_UserId", schema: "jellyfin", table: "DisplayPreferences", - column: "UserId"); + column: "UserId", + unique: true); migrationBuilder.CreateIndex( name: "IX_HomeSection_DisplayPreferencesId", schema: "jellyfin", table: "HomeSection", column: "DisplayPreferencesId"); + + migrationBuilder.CreateIndex( + name: "IX_ItemDisplayPreferences_UserId", + schema: "jellyfin", + table: "ItemDisplayPreferences", + column: "UserId"); } protected override void Down(MigrationBuilder migrationBuilder) @@ -88,6 +120,10 @@ namespace Jellyfin.Server.Implementations.Migrations name: "HomeSection", schema: "jellyfin"); + migrationBuilder.DropTable( + name: "ItemDisplayPreferences", + schema: "jellyfin"); + migrationBuilder.DropTable( name: "DisplayPreferences", schema: "jellyfin"); diff --git a/Jellyfin.Server.Implementations/Migrations/JellyfinDbModelSnapshot.cs b/Jellyfin.Server.Implementations/Migrations/JellyfinDbModelSnapshot.cs index 76de592ac7..a6e6a23249 100644 --- a/Jellyfin.Server.Implementations/Migrations/JellyfinDbModelSnapshot.cs +++ b/Jellyfin.Server.Implementations/Migrations/JellyfinDbModelSnapshot.cs @@ -15,7 +15,7 @@ namespace Jellyfin.Server.Implementations.Migrations #pragma warning disable 612, 618 modelBuilder .HasDefaultSchema("jellyfin") - .HasAnnotation("ProductVersion", "3.1.5"); + .HasAnnotation("ProductVersion", "3.1.6"); modelBuilder.Entity("Jellyfin.Data.Entities.AccessSchedule", b => { @@ -100,7 +100,11 @@ namespace Jellyfin.Server.Implementations.Migrations b.Property("Client") .IsRequired() .HasColumnType("TEXT") - .HasMaxLength(64); + .HasMaxLength(32); + + b.Property("DashboardTheme") + .HasColumnType("TEXT") + .HasMaxLength(32); b.Property("EnableNextVideoInfoOverlay") .HasColumnType("INTEGER"); @@ -108,15 +112,6 @@ namespace Jellyfin.Server.Implementations.Migrations b.Property("IndexBy") .HasColumnType("INTEGER"); - b.Property("ItemId") - .HasColumnType("TEXT"); - - b.Property("RememberIndexing") - .HasColumnType("INTEGER"); - - b.Property("RememberSorting") - .HasColumnType("INTEGER"); - b.Property("ScrollDirection") .HasColumnType("INTEGER"); @@ -132,22 +127,17 @@ namespace Jellyfin.Server.Implementations.Migrations b.Property("SkipForwardLength") .HasColumnType("INTEGER"); - b.Property("SortBy") + b.Property("TvHome") .HasColumnType("TEXT") - .HasMaxLength(64); - - b.Property("SortOrder") - .HasColumnType("INTEGER"); + .HasMaxLength(32); b.Property("UserId") .HasColumnType("TEXT"); - b.Property("ViewType") - .HasColumnType("INTEGER"); - b.HasKey("Id"); - b.HasIndex("UserId"); + b.HasIndex("UserId") + .IsUnique(); b.ToTable("DisplayPreferences"); }); @@ -199,6 +189,50 @@ namespace Jellyfin.Server.Implementations.Migrations b.ToTable("ImageInfos"); }); + modelBuilder.Entity("Jellyfin.Data.Entities.ItemDisplayPreferences", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Client") + .IsRequired() + .HasColumnType("TEXT") + .HasMaxLength(32); + + b.Property("IndexBy") + .HasColumnType("INTEGER"); + + b.Property("ItemId") + .HasColumnType("TEXT"); + + b.Property("RememberIndexing") + .HasColumnType("INTEGER"); + + b.Property("RememberSorting") + .HasColumnType("INTEGER"); + + b.Property("SortBy") + .IsRequired() + .HasColumnType("TEXT") + .HasMaxLength(64); + + b.Property("SortOrder") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("ViewType") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("ItemDisplayPreferences"); + }); + modelBuilder.Entity("Jellyfin.Data.Entities.Permission", b => { b.Property("Id") @@ -371,8 +405,8 @@ namespace Jellyfin.Server.Implementations.Migrations modelBuilder.Entity("Jellyfin.Data.Entities.DisplayPreferences", b => { b.HasOne("Jellyfin.Data.Entities.User", null) - .WithMany("DisplayPreferences") - .HasForeignKey("UserId") + .WithOne("DisplayPreferences") + .HasForeignKey("Jellyfin.Data.Entities.DisplayPreferences", "UserId") .OnDelete(DeleteBehavior.Cascade) .IsRequired(); }); @@ -393,6 +427,15 @@ namespace Jellyfin.Server.Implementations.Migrations .HasForeignKey("Jellyfin.Data.Entities.ImageInfo", "UserId"); }); + modelBuilder.Entity("Jellyfin.Data.Entities.ItemDisplayPreferences", b => + { + b.HasOne("Jellyfin.Data.Entities.User", null) + .WithMany("ItemDisplayPreferences") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + modelBuilder.Entity("Jellyfin.Data.Entities.Permission", b => { b.HasOne("Jellyfin.Data.Entities.User", null) From 2d8d3ba826f62f368e0f6303165ff62db5856645 Mon Sep 17 00:00:00 2001 From: "Joshua M. Boniface" Date: Tue, 28 Jul 2020 00:21:30 -0400 Subject: [PATCH 34/60] Flip quoting in variable set command The quoting as-is was broken and would result in a junk output. This way, the proper value is obtained. --- .ci/azure-pipelines-package.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.ci/azure-pipelines-package.yml b/.ci/azure-pipelines-package.yml index 64d40e7f5f..1677f71c75 100644 --- a/.ci/azure-pipelines-package.yml +++ b/.ci/azure-pipelines-package.yml @@ -85,7 +85,7 @@ jobs: value: 0.0.0 steps: - - script: echo '##vso[task.setvariable variable=JellyfinVersion]$( awk -F "/" "{ print $NF }" <<<"$(Build.SourceBranch)" | sed "s/^v//" )' + - script: echo "##vso[task.setvariable variable=JellyfinVersion]$( awk -F '/' '{ print $NF }' <<<'$(Build.SourceBranch)' | sed 's/^v//' )" displayName: Set release version (stable) condition: startsWith(variables['Build.SourceBranch'], 'refs/tags') From 3ed9463d25776c3a371872183742703d470f871d Mon Sep 17 00:00:00 2001 From: K900 Date: Tue, 28 Jul 2020 12:03:08 +0300 Subject: [PATCH 35/60] Fix #3624 It doesn't really make sense to throw an error when creating the default user, because the error is completely non-actionable. Instead, if the autodetected username is not valid, just fall back to a sane default. --- Jellyfin.Server.Implementations/Users/UserManager.cs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/Jellyfin.Server.Implementations/Users/UserManager.cs b/Jellyfin.Server.Implementations/Users/UserManager.cs index eaa6a0a814..11402ee055 100644 --- a/Jellyfin.Server.Implementations/Users/UserManager.cs +++ b/Jellyfin.Server.Implementations/Users/UserManager.cs @@ -600,18 +600,13 @@ namespace Jellyfin.Server.Implementations.Users } var defaultName = Environment.UserName; - if (string.IsNullOrWhiteSpace(defaultName)) + if (string.IsNullOrWhiteSpace(defaultName) || !IsValidUsername(defaultName)) { defaultName = "MyJellyfinUser"; } _logger.LogWarning("No users, creating one with username {UserName}", defaultName); - if (!IsValidUsername(defaultName)) - { - throw new ArgumentException("Provided username is not valid!", defaultName); - } - var newUser = await CreateUserInternalAsync(defaultName, dbContext).ConfigureAwait(false); newUser.SetPermission(PermissionKind.IsAdministrator, true); newUser.SetPermission(PermissionKind.EnableContentDeletion, true); From 2139e9f8d11597a06839df7bb1b88787bf750305 Mon Sep 17 00:00:00 2001 From: Nyanmisaka Date: Tue, 28 Jul 2020 17:07:10 +0800 Subject: [PATCH 36/60] adjust priority in outputSizeParam cutter --- .../MediaEncoding/EncodingHelper.cs | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index 913e171f14..18d310a009 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -1606,42 +1606,48 @@ namespace MediaBrowser.Controller.MediaEncoding { outputSizeParam = GetOutputSizeParam(state, options, outputVideoCodec).TrimEnd('"'); - var index = outputSizeParam.IndexOf("hwdownload", StringComparison.OrdinalIgnoreCase); + // hwupload=extra_hw_frames=64,vpp_qsv (for overlay_qsv on linux) + var index = outputSizeParam.IndexOf("hwupload=extra_hw_frames", StringComparison.OrdinalIgnoreCase); if (index != -1) { outputSizeParam = outputSizeParam.Slice(index); } else { - index = outputSizeParam.IndexOf("hwupload=extra_hw_frames", StringComparison.OrdinalIgnoreCase); + // vpp_qsv + index = outputSizeParam.IndexOf("vpp", StringComparison.OrdinalIgnoreCase); if (index != -1) { outputSizeParam = outputSizeParam.Slice(index); } else { - index = outputSizeParam.IndexOf("format", StringComparison.OrdinalIgnoreCase); + // hwdownload,format=p010le (hardware decode + software encode for vaapi) + index = outputSizeParam.IndexOf("hwdownload", StringComparison.OrdinalIgnoreCase); if (index != -1) { outputSizeParam = outputSizeParam.Slice(index); } else { - index = outputSizeParam.IndexOf("yadif", StringComparison.OrdinalIgnoreCase); + // format=nv12|vaapi,hwupload,scale_vaapi + index = outputSizeParam.IndexOf("format", StringComparison.OrdinalIgnoreCase); if (index != -1) { outputSizeParam = outputSizeParam.Slice(index); } else { - index = outputSizeParam.IndexOf("scale", StringComparison.OrdinalIgnoreCase); + // yadif,scale=expr + index = outputSizeParam.IndexOf("yadif", StringComparison.OrdinalIgnoreCase); if (index != -1) { outputSizeParam = outputSizeParam.Slice(index); } else { - index = outputSizeParam.IndexOf("vpp", StringComparison.OrdinalIgnoreCase); + // scale=expr + index = outputSizeParam.IndexOf("scale", StringComparison.OrdinalIgnoreCase); if (index != -1) { outputSizeParam = outputSizeParam.Slice(index); From c094916df0e6202356602dc191516e3b0f87f429 Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Tue, 28 Jul 2020 09:19:25 -0400 Subject: [PATCH 37/60] Migrate default library display preferences. --- .../Migrations/Routines/MigrateDisplayPreferencesDb.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/Jellyfin.Server/Migrations/Routines/MigrateDisplayPreferencesDb.cs b/Jellyfin.Server/Migrations/Routines/MigrateDisplayPreferencesDb.cs index e76d45da7e..183b1aeabd 100644 --- a/Jellyfin.Server/Migrations/Routines/MigrateDisplayPreferencesDb.cs +++ b/Jellyfin.Server/Migrations/Routines/MigrateDisplayPreferencesDb.cs @@ -110,6 +110,16 @@ namespace Jellyfin.Server.Migrations.Routines }); } + var defaultLibraryPrefs = new ItemDisplayPreferences(displayPreferences.UserId, Guid.Empty, displayPreferences.Client) + { + SortBy = dto.SortBy ?? "SortName", + SortOrder = dto.SortOrder, + RememberIndexing = dto.RememberIndexing, + RememberSorting = dto.RememberSorting, + }; + + dbContext.Add(defaultLibraryPrefs); + foreach (var key in dto.CustomPrefs.Keys.Where(key => key.StartsWith("landing-", StringComparison.Ordinal))) { if (!Guid.TryParse(key.AsSpan().Slice("landing-".Length), out var itemId)) From 164eb3e9a2554d5a8ceed3cfb1e27cd4b429179b Mon Sep 17 00:00:00 2001 From: tmechen Date: Tue, 28 Jul 2020 19:06:02 +0000 Subject: [PATCH 38/60] Translated using Weblate (German) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/de/ --- Emby.Server.Implementations/Localization/Core/de.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Emby.Server.Implementations/Localization/Core/de.json b/Emby.Server.Implementations/Localization/Core/de.json index a6e9779c95..eec8802082 100644 --- a/Emby.Server.Implementations/Localization/Core/de.json +++ b/Emby.Server.Implementations/Localization/Core/de.json @@ -101,8 +101,8 @@ "TaskCleanTranscode": "Lösche Transkodier Pfad", "TaskUpdatePluginsDescription": "Lädt Updates für Plugins herunter, welche dazu eingestellt sind automatisch zu updaten und installiert sie.", "TaskUpdatePlugins": "Update Plugins", - "TaskRefreshPeopleDescription": "Erneuert Metadaten für Schausteller und Regisseure in deinen Bibliotheken.", - "TaskRefreshPeople": "Erneuere Schausteller", + "TaskRefreshPeopleDescription": "Erneuert Metadaten für Schauspieler und Regisseure in deinen Bibliotheken.", + "TaskRefreshPeople": "Erneuere Schauspieler", "TaskCleanLogsDescription": "Lösche Log Dateien die älter als {0} Tage sind.", "TaskCleanLogs": "Lösche Log Pfad", "TaskRefreshLibraryDescription": "Scanne alle Bibliotheken für hinzugefügte Datein und erneuere Metadaten.", From 995c8fadb7ce7821eaddc7ae9245cadc2e9ddc05 Mon Sep 17 00:00:00 2001 From: Patrick Barron <18354464+barronpm@users.noreply.github.com> Date: Tue, 28 Jul 2020 21:17:11 +0000 Subject: [PATCH 39/60] Update MediaBrowser.Api/DisplayPreferencesService.cs Co-authored-by: Cody Robibero --- MediaBrowser.Api/DisplayPreferencesService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MediaBrowser.Api/DisplayPreferencesService.cs b/MediaBrowser.Api/DisplayPreferencesService.cs index 4951dcc22c..559b71efc7 100644 --- a/MediaBrowser.Api/DisplayPreferencesService.cs +++ b/MediaBrowser.Api/DisplayPreferencesService.cs @@ -52,7 +52,7 @@ namespace MediaBrowser.Api public class DisplayPreferencesService : BaseApiService { /// - /// The user manager. + /// The display preferences manager. /// private readonly IDisplayPreferencesManager _displayPreferencesManager; From d4092846e4c1c6c545c37e2e31ecdaa892711789 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Wed, 29 Jul 2020 13:17:01 +0200 Subject: [PATCH 40/60] Optimize Substring and StringBuilder usage --- Emby.Dlna/DlnaManager.cs | 18 +++++++-------- .../Playback/Hls/BaseHlsService.cs | 3 ++- .../Playback/Hls/DynamicHlsService.cs | 14 +++++++----- MediaBrowser.Api/Subtitles/SubtitleService.cs | 15 ++++++++----- .../Probing/ProbeResultNormalizer.cs | 11 +++++++--- .../Subtitles/SubtitleEncoder.cs | 12 +++++----- .../MediaInfo/AudioImageProvider.cs | 6 ++--- .../Plugins/Omdb/OmdbItemProvider.cs | 2 +- .../Plugins/Omdb/OmdbProvider.cs | 4 ++-- .../Plugins/TheTvdb/TvdbEpisodeProvider.cs | 4 ++-- .../Plugins/Tmdb/People/TmdbPersonProvider.cs | 4 ++-- .../Parsers/BaseNfoParser.cs | 22 ++++++++++++++----- 12 files changed, 69 insertions(+), 46 deletions(-) diff --git a/Emby.Dlna/DlnaManager.cs b/Emby.Dlna/DlnaManager.cs index 1e20ff92b5..269f7ee43a 100644 --- a/Emby.Dlna/DlnaManager.cs +++ b/Emby.Dlna/DlnaManager.cs @@ -122,15 +122,15 @@ namespace Emby.Dlna var builder = new StringBuilder(); builder.AppendLine("No matching device profile found. The default will need to be used."); - builder.AppendLine(string.Format("DeviceDescription:{0}", profile.DeviceDescription ?? string.Empty)); - builder.AppendLine(string.Format("FriendlyName:{0}", profile.FriendlyName ?? string.Empty)); - builder.AppendLine(string.Format("Manufacturer:{0}", profile.Manufacturer ?? string.Empty)); - builder.AppendLine(string.Format("ManufacturerUrl:{0}", profile.ManufacturerUrl ?? string.Empty)); - builder.AppendLine(string.Format("ModelDescription:{0}", profile.ModelDescription ?? string.Empty)); - builder.AppendLine(string.Format("ModelName:{0}", profile.ModelName ?? string.Empty)); - builder.AppendLine(string.Format("ModelNumber:{0}", profile.ModelNumber ?? string.Empty)); - builder.AppendLine(string.Format("ModelUrl:{0}", profile.ModelUrl ?? string.Empty)); - builder.AppendLine(string.Format("SerialNumber:{0}", profile.SerialNumber ?? string.Empty)); + builder.AppendFormat(CultureInfo.InvariantCulture, "DeviceDescription:{0}", profile.DeviceDescription ?? string.Empty).AppendLine(); + builder.AppendFormat(CultureInfo.InvariantCulture, "FriendlyName:{0}", profile.FriendlyName ?? string.Empty).AppendLine(); + builder.AppendFormat(CultureInfo.InvariantCulture, "Manufacturer:{0}", profile.Manufacturer ?? string.Empty).AppendLine(); + builder.AppendFormat(CultureInfo.InvariantCulture, "ManufacturerUrl:{0}", profile.ManufacturerUrl ?? string.Empty).AppendLine(); + builder.AppendFormat(CultureInfo.InvariantCulture, "ModelDescription:{0}", profile.ModelDescription ?? string.Empty).AppendLine(); + builder.AppendFormat(CultureInfo.InvariantCulture, "ModelName:{0}", profile.ModelName ?? string.Empty).AppendLine(); + builder.AppendFormat(CultureInfo.InvariantCulture, "ModelNumber:{0}", profile.ModelNumber ?? string.Empty).AppendLine(); + builder.AppendFormat(CultureInfo.InvariantCulture, "ModelUrl:{0}", profile.ModelUrl ?? string.Empty).AppendLine(); + builder.AppendFormat(CultureInfo.InvariantCulture, "SerialNumber:{0}", profile.SerialNumber ?? string.Empty).AppendLine(); _logger.LogInformation(builder.ToString()); } diff --git a/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs b/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs index 418cd92b3d..c80e8e64f7 100644 --- a/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs +++ b/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs @@ -194,7 +194,8 @@ namespace MediaBrowser.Api.Playback.Hls var paddedBitrate = Convert.ToInt32(bitrate * 1.15); // Main stream - builder.AppendLine("#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=" + paddedBitrate.ToString(CultureInfo.InvariantCulture)); + builder.Append("#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=") + .AppendLine(paddedBitrate.ToString(CultureInfo.InvariantCulture)); var playlistUrl = "hls/" + Path.GetFileName(firstPlaylist).Replace(".m3u8", "/stream.m3u8"); builder.AppendLine(playlistUrl); diff --git a/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs b/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs index 6ff96ac565..661c1ba5f2 100644 --- a/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs +++ b/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs @@ -968,7 +968,8 @@ namespace MediaBrowser.Api.Playback.Hls builder.AppendLine("#EXTM3U"); builder.AppendLine("#EXT-X-PLAYLIST-TYPE:VOD"); builder.AppendLine("#EXT-X-VERSION:3"); - builder.AppendLine("#EXT-X-TARGETDURATION:" + Math.Ceiling(segmentLengths.Length > 0 ? segmentLengths.Max() : state.SegmentLength).ToString(CultureInfo.InvariantCulture)); + builder.Append("#EXT-X-TARGETDURATION:") + .AppendLine(Math.Ceiling(segmentLengths.Length > 0 ? segmentLengths.Max() : state.SegmentLength).ToString(CultureInfo.InvariantCulture)); builder.AppendLine("#EXT-X-MEDIA-SEQUENCE:0"); var queryStringIndex = Request.RawUrl.IndexOf('?'); @@ -983,14 +984,17 @@ namespace MediaBrowser.Api.Playback.Hls foreach (var length in segmentLengths) { - builder.AppendLine("#EXTINF:" + length.ToString("0.0000", CultureInfo.InvariantCulture) + ", nodesc"); - - builder.AppendLine(string.Format("hls1/{0}/{1}{2}{3}", + builder.Append("#EXTINF:") + .Append(length.ToString("0.0000", CultureInfo.InvariantCulture)) + .AppendLine(", nodesc"); + builder.AppendFormat( + CultureInfo.InvariantCulture, + "hls1/{0}/{1}{2}{3}", name, index.ToString(CultureInfo.InvariantCulture), GetSegmentFileExtension(request), - queryString)); + queryString).AppendLine(); index++; } diff --git a/MediaBrowser.Api/Subtitles/SubtitleService.cs b/MediaBrowser.Api/Subtitles/SubtitleService.cs index a70da8e56c..6a6196d8a3 100644 --- a/MediaBrowser.Api/Subtitles/SubtitleService.cs +++ b/MediaBrowser.Api/Subtitles/SubtitleService.cs @@ -175,11 +175,12 @@ namespace MediaBrowser.Api.Subtitles throw new ArgumentException("segmentLength was not given, or it was given incorrectly. (It should be bigger than 0)"); } - builder.AppendLine("#EXTM3U"); - builder.AppendLine("#EXT-X-TARGETDURATION:" + request.SegmentLength.ToString(CultureInfo.InvariantCulture)); - builder.AppendLine("#EXT-X-VERSION:3"); - builder.AppendLine("#EXT-X-MEDIA-SEQUENCE:0"); - builder.AppendLine("#EXT-X-PLAYLIST-TYPE:VOD"); + builder.AppendLine("#EXTM3U") + .Append("#EXT-X-TARGETDURATION:") + .AppendLine(request.SegmentLength.ToString(CultureInfo.InvariantCulture)) + .AppendLine("#EXT-X-VERSION:3") + .AppendLine("#EXT-X-MEDIA-SEQUENCE:0") + .AppendLine("#EXT-X-PLAYLIST-TYPE:VOD"); long positionTicks = 0; @@ -190,7 +191,9 @@ namespace MediaBrowser.Api.Subtitles var remaining = runtime - positionTicks; var lengthTicks = Math.Min(remaining, segmentLengthTicks); - builder.AppendLine("#EXTINF:" + TimeSpan.FromTicks(lengthTicks).TotalSeconds.ToString(CultureInfo.InvariantCulture) + ","); + builder.Append("#EXTINF:") + .Append(TimeSpan.FromTicks(lengthTicks).TotalSeconds.ToString(CultureInfo.InvariantCulture)) + .AppendLine(","); var endPositionTicks = Math.Min(runtime, positionTicks + segmentLengthTicks); diff --git a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs index c85d963ed1..0b447e3e64 100644 --- a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs +++ b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs @@ -1367,7 +1367,9 @@ namespace MediaBrowser.MediaEncoding.Probing // OR -> COMMENT. SUBTITLE: DESCRIPTION // e.g. -> 4/13. The Doctor's Wife: Science fiction drama. When he follows a Time Lord distress signal, the Doctor puts Amy, Rory and his beloved TARDIS in grave danger. Also in HD. [AD,S] // e.g. -> CBeebies Bedtime Hour. The Mystery: Animated adventures of two friends who live on an island in the middle of the big city. Some of Abney and Teal's favourite objects are missing. [S] - if (string.IsNullOrWhiteSpace(subTitle) && !string.IsNullOrWhiteSpace(description) && description.Substring(0, Math.Min(description.Length, MaxSubtitleDescriptionExtractionLength)).Contains(":")) // Check within the Subtitle size limit, otherwise from description it can get too long creating an invalid filename + if (string.IsNullOrWhiteSpace(subTitle) + && !string.IsNullOrWhiteSpace(description) + && description.AsSpan().Slice(0, Math.Min(description.Length, MaxSubtitleDescriptionExtractionLength)).IndexOf(':') != -1) // Check within the Subtitle size limit, otherwise from description it can get too long creating an invalid filename { string[] parts = description.Split(':'); if (parts.Length > 0) @@ -1375,7 +1377,7 @@ namespace MediaBrowser.MediaEncoding.Probing string subtitle = parts[0]; try { - if (subtitle.Contains("/")) // It contains a episode number and season number + if (subtitle.Contains('/', StringComparison.Ordinal)) // It contains a episode number and season number { string[] numbers = subtitle.Split(' '); video.IndexNumber = int.Parse(numbers[0].Replace(".", "").Split('/')[0]); @@ -1390,8 +1392,11 @@ namespace MediaBrowser.MediaEncoding.Probing } catch // Default parsing { - if (subtitle.Contains(".")) // skip the comment, keep the subtitle + if (subtitle.Contains('.', StringComparison.Ordinal)) + { + // skip the comment, keep the subtitle description = string.Join(".", subtitle.Split('.'), 1, subtitle.Split('.').Length - 1).Trim(); // skip the first + } else { description = subtitle.Trim(); // Clean up whitespaces and save it diff --git a/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs b/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs index f850f5c2e0..2afa89cdab 100644 --- a/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs +++ b/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs @@ -731,19 +731,19 @@ namespace MediaBrowser.MediaEncoding.Subtitles var date = _fileSystem.GetLastWriteTimeUtc(mediaPath); - var filename = (mediaPath + "_" + subtitleStreamIndex.ToString(CultureInfo.InvariantCulture) + "_" + date.Ticks.ToString(CultureInfo.InvariantCulture) + ticksParam).GetMD5() + outputSubtitleExtension; + ReadOnlySpan filename = (mediaPath + "_" + subtitleStreamIndex.ToString(CultureInfo.InvariantCulture) + "_" + date.Ticks.ToString(CultureInfo.InvariantCulture) + ticksParam).GetMD5() + outputSubtitleExtension; - var prefix = filename.Substring(0, 1); + var prefix = filename.Slice(0, 1); - return Path.Combine(SubtitleCachePath, prefix, filename); + return Path.Join(SubtitleCachePath, prefix, filename); } else { - var filename = (mediaPath + "_" + subtitleStreamIndex.ToString(CultureInfo.InvariantCulture)).GetMD5() + outputSubtitleExtension; + ReadOnlySpan filename = (mediaPath + "_" + subtitleStreamIndex.ToString(CultureInfo.InvariantCulture)).GetMD5() + outputSubtitleExtension; - var prefix = filename.Substring(0, 1); + var prefix = filename.Slice(0, 1); - return Path.Combine(SubtitleCachePath, prefix, filename); + return Path.Join(SubtitleCachePath, prefix, filename); } } diff --git a/MediaBrowser.Providers/MediaInfo/AudioImageProvider.cs b/MediaBrowser.Providers/MediaInfo/AudioImageProvider.cs index 80acb2c056..f69ec9744a 100644 --- a/MediaBrowser.Providers/MediaInfo/AudioImageProvider.cs +++ b/MediaBrowser.Providers/MediaInfo/AudioImageProvider.cs @@ -93,7 +93,7 @@ namespace MediaBrowser.Providers.MediaInfo private string GetAudioImagePath(Audio item) { - string filename = null; + string filename; if (item.GetType() == typeof(Audio)) { @@ -116,9 +116,9 @@ namespace MediaBrowser.Providers.MediaInfo filename = item.Id.ToString("N", CultureInfo.InvariantCulture) + ".jpg"; } - var prefix = filename.Substring(0, 1); + var prefix = filename.AsSpan().Slice(0, 1); - return Path.Combine(AudioImagesPath, prefix, filename); + return Path.Join(AudioImagesPath, prefix, filename); } public string AudioImagesPath => Path.Combine(_config.ApplicationPaths.CachePath, "extracted-audio-images"); diff --git a/MediaBrowser.Providers/Plugins/Omdb/OmdbItemProvider.cs b/MediaBrowser.Providers/Plugins/Omdb/OmdbItemProvider.cs index 944ba26afb..12aecba849 100644 --- a/MediaBrowser.Providers/Plugins/Omdb/OmdbItemProvider.cs +++ b/MediaBrowser.Providers/Plugins/Omdb/OmdbItemProvider.cs @@ -170,7 +170,7 @@ namespace MediaBrowser.Providers.Plugins.Omdb item.SetProviderId(MetadataProvider.Imdb, result.imdbID); if (result.Year.Length > 0 - && int.TryParse(result.Year.Substring(0, Math.Min(result.Year.Length, 4)), NumberStyles.Integer, CultureInfo.InvariantCulture, out var parsedYear)) + && int.TryParse(result.Year.AsSpan().Slice(0, Math.Min(result.Year.Length, 4)), NumberStyles.Integer, CultureInfo.InvariantCulture, out var parsedYear)) { item.ProductionYear = parsedYear; } diff --git a/MediaBrowser.Providers/Plugins/Omdb/OmdbProvider.cs b/MediaBrowser.Providers/Plugins/Omdb/OmdbProvider.cs index 9700f3b18f..13098d1405 100644 --- a/MediaBrowser.Providers/Plugins/Omdb/OmdbProvider.cs +++ b/MediaBrowser.Providers/Plugins/Omdb/OmdbProvider.cs @@ -62,7 +62,7 @@ namespace MediaBrowser.Providers.Plugins.Omdb } if (!string.IsNullOrEmpty(result.Year) && result.Year.Length >= 4 - && int.TryParse(result.Year.Substring(0, 4), NumberStyles.Number, _usCulture, out var year) + && int.TryParse(result.Year.AsSpan().Slice(0, 4), NumberStyles.Number, _usCulture, out var year) && year >= 0) { item.ProductionYear = year; @@ -163,7 +163,7 @@ namespace MediaBrowser.Providers.Plugins.Omdb } if (!string.IsNullOrEmpty(result.Year) && result.Year.Length >= 4 - && int.TryParse(result.Year.Substring(0, 4), NumberStyles.Number, _usCulture, out var year) + && int.TryParse(result.Year.AsSpan().Slice(0, 4), NumberStyles.Number, _usCulture, out var year) && year >= 0) { item.ProductionYear = year; diff --git a/MediaBrowser.Providers/Plugins/TheTvdb/TvdbEpisodeProvider.cs b/MediaBrowser.Providers/Plugins/TheTvdb/TvdbEpisodeProvider.cs index ced287d543..52fc538728 100644 --- a/MediaBrowser.Providers/Plugins/TheTvdb/TvdbEpisodeProvider.cs +++ b/MediaBrowser.Providers/Plugins/TheTvdb/TvdbEpisodeProvider.cs @@ -188,7 +188,7 @@ namespace MediaBrowser.Providers.Plugins.TheTvdb for (var i = 0; i < episode.GuestStars.Length; ++i) { var currentActor = episode.GuestStars[i]; - var roleStartIndex = currentActor.IndexOf('('); + var roleStartIndex = currentActor.IndexOf('(', StringComparison.Ordinal); if (roleStartIndex == -1) { @@ -207,7 +207,7 @@ namespace MediaBrowser.Providers.Plugins.TheTvdb for (var j = i + 1; j < episode.GuestStars.Length; ++j) { var currentRole = episode.GuestStars[j]; - var roleEndIndex = currentRole.IndexOf(')'); + var roleEndIndex = currentRole.IndexOf(')', StringComparison.Ordinal); if (roleEndIndex == -1) { diff --git a/MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonProvider.cs index 76d3f8224b..58cbf9eef6 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonProvider.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonProvider.cs @@ -251,9 +251,9 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.People private static string GetPersonDataPath(IApplicationPaths appPaths, string tmdbId) { - var letter = tmdbId.GetMD5().ToString().Substring(0, 1); + var letter = tmdbId.GetMD5().ToString().AsSpan().Slice(0, 1); - return Path.Combine(GetPersonsDataPath(appPaths), letter, tmdbId); + return Path.Join(GetPersonsDataPath(appPaths), letter, tmdbId); } internal static string GetPersonDataFilePath(IApplicationPaths appPaths, string tmdbId) diff --git a/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs b/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs index b2d99c1a1c..b064644090 100644 --- a/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs +++ b/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs @@ -222,8 +222,16 @@ namespace MediaBrowser.XbmcMetadata.Parsers if (index != -1) { - var tmdbId = xml.Substring(index + srch.Length).TrimEnd('/').Split('-')[0]; - if (!string.IsNullOrWhiteSpace(tmdbId) && int.TryParse(tmdbId, NumberStyles.Integer, CultureInfo.InvariantCulture, out var value)) + var tmdbId = xml.AsSpan().Slice(index + srch.Length).TrimEnd('/'); + index = tmdbId.IndexOf('-'); + if (index != -1) + { + tmdbId = tmdbId.Slice(0, index); + } + + if (!tmdbId.IsEmpty + && !tmdbId.IsWhiteSpace() + && int.TryParse(tmdbId, NumberStyles.Integer, CultureInfo.InvariantCulture, out var value)) { item.SetProviderId(MetadataProvider.Tmdb, value.ToString(UsCulture)); } @@ -237,8 +245,10 @@ namespace MediaBrowser.XbmcMetadata.Parsers if (index != -1) { - var tvdbId = xml.Substring(index + srch.Length).TrimEnd('/'); - if (!string.IsNullOrWhiteSpace(tvdbId) && int.TryParse(tvdbId, NumberStyles.Integer, CultureInfo.InvariantCulture, out var value)) + var tvdbId = xml.AsSpan().Slice(index + srch.Length).TrimEnd('/'); + if (!tvdbId.IsEmpty + && !tvdbId.IsWhiteSpace() + && int.TryParse(tvdbId, NumberStyles.Integer, CultureInfo.InvariantCulture, out var value)) { item.SetProviderId(MetadataProvider.Tvdb, value.ToString(UsCulture)); } @@ -442,8 +452,8 @@ namespace MediaBrowser.XbmcMetadata.Parsers { var val = reader.ReadElementContentAsString(); - var hasAspectRatio = item as IHasAspectRatio; - if (!string.IsNullOrWhiteSpace(val) && hasAspectRatio != null) + if (!string.IsNullOrWhiteSpace(val) + && item is IHasAspectRatio hasAspectRatio) { hasAspectRatio.AspectRatio = val; } From e5c6eec642952da1031c641cb83c868b6ef9d788 Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Wed, 29 Jul 2020 17:28:15 -0400 Subject: [PATCH 41/60] Use MemoryCache in LibraryManager --- .../Library/LibraryManager.cs | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs index 9165ede35d..f3c2f0e035 100644 --- a/Emby.Server.Implementations/Library/LibraryManager.cs +++ b/Emby.Server.Implementations/Library/LibraryManager.cs @@ -1,7 +1,6 @@ #pragma warning disable CS1591 using System; -using System.Collections.Concurrent; using System.Collections.Generic; using System.Globalization; using System.IO; @@ -46,6 +45,7 @@ using MediaBrowser.Model.Net; using MediaBrowser.Model.Querying; using MediaBrowser.Model.Tasks; using MediaBrowser.Providers.MediaInfo; +using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.Logging; using Episode = MediaBrowser.Controller.Entities.TV.Episode; using Genre = MediaBrowser.Controller.Entities.Genre; @@ -63,6 +63,7 @@ namespace Emby.Server.Implementations.Library private const string ShortcutFileExtension = ".mblink"; private readonly ILogger _logger; + private readonly IMemoryCache _memoryCache; private readonly ITaskManager _taskManager; private readonly IUserManager _userManager; private readonly IUserDataManager _userDataRepository; @@ -74,7 +75,6 @@ namespace Emby.Server.Implementations.Library private readonly IMediaEncoder _mediaEncoder; private readonly IFileSystem _fileSystem; private readonly IItemRepository _itemRepository; - private readonly ConcurrentDictionary _libraryItemsCache; private readonly IImageProcessor _imageProcessor; /// @@ -112,6 +112,7 @@ namespace Emby.Server.Implementations.Library /// The media encoder. /// The item repository. /// The image processor. + /// The memory cache. public LibraryManager( IServerApplicationHost appHost, ILogger logger, @@ -125,7 +126,8 @@ namespace Emby.Server.Implementations.Library Lazy userviewManagerFactory, IMediaEncoder mediaEncoder, IItemRepository itemRepository, - IImageProcessor imageProcessor) + IImageProcessor imageProcessor, + IMemoryCache memoryCache) { _appHost = appHost; _logger = logger; @@ -140,8 +142,7 @@ namespace Emby.Server.Implementations.Library _mediaEncoder = mediaEncoder; _itemRepository = itemRepository; _imageProcessor = imageProcessor; - - _libraryItemsCache = new ConcurrentDictionary(); + _memoryCache = memoryCache; _configurationManager.ConfigurationUpdated += ConfigurationUpdated; @@ -299,7 +300,7 @@ namespace Emby.Server.Implementations.Library } } - _libraryItemsCache.AddOrUpdate(item.Id, item, delegate { return item; }); + _memoryCache.CreateEntry(item.Id).SetValue(item); } public void DeleteItem(BaseItem item, DeleteOptions options) @@ -447,7 +448,7 @@ namespace Emby.Server.Implementations.Library _itemRepository.DeleteItem(child.Id); } - _libraryItemsCache.TryRemove(item.Id, out BaseItem removed); + _memoryCache.Remove(item.Id); ReportItemRemoved(item, parent); } @@ -1248,7 +1249,7 @@ namespace Emby.Server.Implementations.Library throw new ArgumentException("Guid can't be empty", nameof(id)); } - if (_libraryItemsCache.TryGetValue(id, out BaseItem item)) + if (_memoryCache.TryGetValue(id, out BaseItem item)) { return item; } From c77abca31a6dd5394b691b8283d3720b94409798 Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Wed, 29 Jul 2020 17:33:40 -0400 Subject: [PATCH 42/60] Use MemoryCache in ChannelManager --- .../Channels/ChannelManager.cs | 23 ++++++++----------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/Emby.Server.Implementations/Channels/ChannelManager.cs b/Emby.Server.Implementations/Channels/ChannelManager.cs index c803d9d825..4c3161ef4b 100644 --- a/Emby.Server.Implementations/Channels/ChannelManager.cs +++ b/Emby.Server.Implementations/Channels/ChannelManager.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Concurrent; using System.Collections.Generic; using System.Globalization; using System.IO; @@ -22,6 +21,7 @@ using MediaBrowser.Model.Entities; using MediaBrowser.Model.IO; using MediaBrowser.Model.Querying; using MediaBrowser.Model.Serialization; +using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.Logging; using Episode = MediaBrowser.Controller.Entities.TV.Episode; using Movie = MediaBrowser.Controller.Entities.Movies.Movie; @@ -45,10 +45,7 @@ namespace Emby.Server.Implementations.Channels private readonly IFileSystem _fileSystem; private readonly IJsonSerializer _jsonSerializer; private readonly IProviderManager _providerManager; - - private readonly ConcurrentDictionary>> _channelItemMediaInfo = - new ConcurrentDictionary>>(); - + private readonly IMemoryCache _memoryCache; private readonly SemaphoreSlim _resourcePool = new SemaphoreSlim(1, 1); /// @@ -63,6 +60,7 @@ namespace Emby.Server.Implementations.Channels /// The user data manager. /// The JSON serializer. /// The provider manager. + /// The memory cache. public ChannelManager( IUserManager userManager, IDtoService dtoService, @@ -72,7 +70,8 @@ namespace Emby.Server.Implementations.Channels IFileSystem fileSystem, IUserDataManager userDataManager, IJsonSerializer jsonSerializer, - IProviderManager providerManager) + IProviderManager providerManager, + IMemoryCache memoryCache) { _userManager = userManager; _dtoService = dtoService; @@ -83,6 +82,7 @@ namespace Emby.Server.Implementations.Channels _userDataManager = userDataManager; _jsonSerializer = jsonSerializer; _providerManager = providerManager; + _memoryCache = memoryCache; } internal IChannel[] Channels { get; private set; } @@ -417,20 +417,15 @@ namespace Emby.Server.Implementations.Channels private async Task> GetChannelItemMediaSourcesInternal(IRequiresMediaInfoCallback channel, string id, CancellationToken cancellationToken) { - if (_channelItemMediaInfo.TryGetValue(id, out Tuple> cachedInfo)) + if (_memoryCache.TryGetValue(id, out List cachedInfo)) { - if ((DateTime.UtcNow - cachedInfo.Item1).TotalMinutes < 5) - { - return cachedInfo.Item2; - } + return cachedInfo; } var mediaInfo = await channel.GetChannelItemMediaInfo(id, cancellationToken) .ConfigureAwait(false); var list = mediaInfo.ToList(); - - var item2 = new Tuple>(DateTime.UtcNow, list); - _channelItemMediaInfo.AddOrUpdate(id, item2, (key, oldValue) => item2); + _memoryCache.CreateEntry(id).SetValue(list).SetAbsoluteExpiration(DateTimeOffset.UtcNow.AddMinutes(5)); return list; } From a97f98fbd53490a976ecbc62b9d0bfd58313d570 Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Wed, 29 Jul 2020 19:25:47 -0400 Subject: [PATCH 43/60] Use MemoryCache in DeviceManager --- .../Devices/DeviceManager.cs | 23 ++++++++++--------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/Emby.Server.Implementations/Devices/DeviceManager.cs b/Emby.Server.Implementations/Devices/DeviceManager.cs index e75745cc6e..2921a7f0e0 100644 --- a/Emby.Server.Implementations/Devices/DeviceManager.cs +++ b/Emby.Server.Implementations/Devices/DeviceManager.cs @@ -5,8 +5,8 @@ using System.Collections.Generic; using System.Globalization; using System.IO; using System.Linq; -using Jellyfin.Data.Enums; using Jellyfin.Data.Entities; +using Jellyfin.Data.Enums; using MediaBrowser.Common.Extensions; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Devices; @@ -17,16 +17,17 @@ using MediaBrowser.Model.Events; using MediaBrowser.Model.Querying; using MediaBrowser.Model.Serialization; using MediaBrowser.Model.Session; +using Microsoft.Extensions.Caching.Memory; namespace Emby.Server.Implementations.Devices { public class DeviceManager : IDeviceManager { + private readonly IMemoryCache _memoryCache; private readonly IJsonSerializer _json; private readonly IUserManager _userManager; private readonly IServerConfigurationManager _config; private readonly IAuthenticationRepository _authRepo; - private readonly Dictionary _capabilitiesCache; private readonly object _capabilitiesSyncLock = new object(); public event EventHandler>> DeviceOptionsUpdated; @@ -35,13 +36,14 @@ namespace Emby.Server.Implementations.Devices IAuthenticationRepository authRepo, IJsonSerializer json, IUserManager userManager, - IServerConfigurationManager config) + IServerConfigurationManager config, + IMemoryCache memoryCache) { _json = json; _userManager = userManager; _config = config; + _memoryCache = memoryCache; _authRepo = authRepo; - _capabilitiesCache = new Dictionary(StringComparer.OrdinalIgnoreCase); } public void SaveCapabilities(string deviceId, ClientCapabilities capabilities) @@ -51,8 +53,7 @@ namespace Emby.Server.Implementations.Devices lock (_capabilitiesSyncLock) { - _capabilitiesCache[deviceId] = capabilities; - + _memoryCache.CreateEntry(deviceId).SetValue(capabilities); _json.SerializeToFile(capabilities, path); } } @@ -71,13 +72,13 @@ namespace Emby.Server.Implementations.Devices public ClientCapabilities GetCapabilities(string id) { + if (_memoryCache.TryGetValue(id, out ClientCapabilities result)) + { + return result; + } + lock (_capabilitiesSyncLock) { - if (_capabilitiesCache.TryGetValue(id, out var result)) - { - return result; - } - var path = Path.Combine(GetDevicePath(id), "capabilities.json"); try { From af274de77e95849da9b07ae659b5463bda24db46 Mon Sep 17 00:00:00 2001 From: cvium Date: Thu, 30 Jul 2020 22:50:13 +0200 Subject: [PATCH 44/60] Update BlurHashSharp and set max size to 128x128 --- Jellyfin.Drawing.Skia/Jellyfin.Drawing.Skia.csproj | 4 ++-- Jellyfin.Drawing.Skia/SkiaEncoder.cs | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/Jellyfin.Drawing.Skia/Jellyfin.Drawing.Skia.csproj b/Jellyfin.Drawing.Skia/Jellyfin.Drawing.Skia.csproj index 6db514b2e5..65a4394594 100644 --- a/Jellyfin.Drawing.Skia/Jellyfin.Drawing.Skia.csproj +++ b/Jellyfin.Drawing.Skia/Jellyfin.Drawing.Skia.csproj @@ -18,8 +18,8 @@ - - + + diff --git a/Jellyfin.Drawing.Skia/SkiaEncoder.cs b/Jellyfin.Drawing.Skia/SkiaEncoder.cs index 8f45ba6743..1f7c43de81 100644 --- a/Jellyfin.Drawing.Skia/SkiaEncoder.cs +++ b/Jellyfin.Drawing.Skia/SkiaEncoder.cs @@ -199,7 +199,8 @@ namespace Jellyfin.Drawing.Skia throw new ArgumentNullException(nameof(path)); } - return BlurHashEncoder.Encode(xComp, yComp, path); + // Any larger than 128x128 is too slow and there's no visually discernible difference + return BlurHashEncoder.Encode(xComp, yComp, path, 128, 128); } private static bool HasDiacritics(string text) From 2d3da0f1a6a9553e3151ac704697bfe55151d131 Mon Sep 17 00:00:00 2001 From: Alf Sebastian Houge Date: Thu, 30 Jul 2020 23:02:53 +0200 Subject: [PATCH 45/60] There is no 'nowebcontent' flag, the correct flag is 'nowebclient' --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 83090100d4..3f1b5e5140 100644 --- a/README.md +++ b/README.md @@ -162,6 +162,7 @@ The following sections describe some more advanced scenarios for running the ser It is not necessary to host the frontend web client as part of the backend server. Hosting these two components separately may be useful for frontend developers who would prefer to host the client in a separate webpack development server for a tighter development loop. See the [jellyfin-web](https://github.com/jellyfin/jellyfin-web#getting-started) repo for instructions on how to do this. -To instruct the server not to host the web content, there is a `nowebcontent` configuration flag that must be set. This can specified using the command line switch `--nowebcontent` or the environment variable `JELLYFIN_NOWEBCONTENT=true`. +To instruct the server not to host the web content, there is a `nowebclient` configuration flag that must be set. This can specified using the command line +switch `--nowebclient` or the environment variable `JELLYFIN_NOWEBCONTENT=true`. Since this is a common scenario, there is also a separate launch profile defined for Visual Studio called `Jellyfin.Server (nowebcontent)` that can be selected from the 'Start Debugging' dropdown in the main toolbar. From d8869419279695aa8adad6fb83ee03aecc7b3012 Mon Sep 17 00:00:00 2001 From: Bill Thornton Date: Thu, 30 Jul 2020 17:18:44 -0400 Subject: [PATCH 46/60] Fix inverted logic for LAN IP detection --- Emby.Server.Implementations/Networking/NetworkManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Networking/NetworkManager.cs b/Emby.Server.Implementations/Networking/NetworkManager.cs index ff95302eee..089ec30e6b 100644 --- a/Emby.Server.Implementations/Networking/NetworkManager.cs +++ b/Emby.Server.Implementations/Networking/NetworkManager.cs @@ -165,7 +165,7 @@ namespace Emby.Server.Implementations.Networking (octet[0] == 127) || // RFC1122 (octet[0] == 169 && octet[1] == 254)) // RFC3927 { - return false; + return true; } if (checkSubnets && IsInPrivateAddressSpaceAndLocalSubnet(endpoint)) From 5f03fb0ef7d25ea36f75feea7577d47a164950c5 Mon Sep 17 00:00:00 2001 From: cvium Date: Fri, 31 Jul 2020 10:13:54 +0200 Subject: [PATCH 47/60] Use factory pattern to instantiate jellyfindb context to avoid disposed contexts piling up in DI container --- .../JellyfinDbProvider.cs | 14 +++++++++++--- Jellyfin.Server/CoreAppHost.cs | 11 ++++++----- 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/Jellyfin.Server.Implementations/JellyfinDbProvider.cs b/Jellyfin.Server.Implementations/JellyfinDbProvider.cs index 8f5c199001..486be60537 100644 --- a/Jellyfin.Server.Implementations/JellyfinDbProvider.cs +++ b/Jellyfin.Server.Implementations/JellyfinDbProvider.cs @@ -1,4 +1,6 @@ using System; +using System.IO; +using MediaBrowser.Common.Configuration; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; @@ -10,15 +12,20 @@ namespace Jellyfin.Server.Implementations public class JellyfinDbProvider { private readonly IServiceProvider _serviceProvider; + private readonly IApplicationPaths _appPaths; /// /// Initializes a new instance of the class. /// /// The application's service provider. - public JellyfinDbProvider(IServiceProvider serviceProvider) + /// The application paths. + public JellyfinDbProvider(IServiceProvider serviceProvider, IApplicationPaths appPaths) { _serviceProvider = serviceProvider; - serviceProvider.GetRequiredService().Database.Migrate(); + _appPaths = appPaths; + + using var jellyfinDb = CreateContext(); + jellyfinDb.Database.Migrate(); } /// @@ -27,7 +34,8 @@ namespace Jellyfin.Server.Implementations /// The newly created context. public JellyfinDb CreateContext() { - return _serviceProvider.GetRequiredService(); + var contextOptions = new DbContextOptionsBuilder().UseSqlite($"Filename={Path.Combine(_appPaths.DataPath, "jellyfin.db")}"); + return ActivatorUtilities.CreateInstance(_serviceProvider, contextOptions.Options); } } } diff --git a/Jellyfin.Server/CoreAppHost.cs b/Jellyfin.Server/CoreAppHost.cs index 71b0fd8f35..7db3db36bd 100644 --- a/Jellyfin.Server/CoreAppHost.cs +++ b/Jellyfin.Server/CoreAppHost.cs @@ -64,11 +64,12 @@ namespace Jellyfin.Server Logger.LogWarning($"Skia not available. Will fallback to {nameof(NullImageEncoder)}."); } - // TODO: Set up scoping and use AddDbContextPool - serviceCollection.AddDbContext( - options => options - .UseSqlite($"Filename={Path.Combine(ApplicationPaths.DataPath, "jellyfin.db")}"), - ServiceLifetime.Transient); + // TODO: Set up scoping and use AddDbContextPool, + // can't register as Transient since tracking transient in GC is funky + // serviceCollection.AddDbContext( + // options => options + // .UseSqlite($"Filename={Path.Combine(ApplicationPaths.DataPath, "jellyfin.db")}"), + // ServiceLifetime.Transient); serviceCollection.AddSingleton(); From e0d2eb8eec1d48175c30309e1e4c0a771329ff4b Mon Sep 17 00:00:00 2001 From: dkanada Date: Sat, 1 Aug 2020 02:03:23 +0900 Subject: [PATCH 48/60] remove useless order step for intros --- Emby.Server.Implementations/Library/LibraryManager.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs index f3c2f0e035..169f50b646 100644 --- a/Emby.Server.Implementations/Library/LibraryManager.cs +++ b/Emby.Server.Implementations/Library/LibraryManager.cs @@ -1592,7 +1592,6 @@ namespace Emby.Server.Implementations.Library public async Task> GetIntros(BaseItem item, User user) { var tasks = IntroProviders - .OrderBy(i => i.GetType().Name.Contains("Default", StringComparison.OrdinalIgnoreCase) ? 1 : 0) .Take(1) .Select(i => GetIntros(i, item, user)); From 7ce99aaf785d3567633febfb60de1b26c43f7d4d Mon Sep 17 00:00:00 2001 From: Erwin de Haan Date: Fri, 31 Jul 2020 21:20:05 +0200 Subject: [PATCH 49/60] Update SkiaSharp to 2.80.1 and replace resize code. This fixed the blurry resized images in the Web UI. --- .../Jellyfin.Drawing.Skia.csproj | 5 +-- Jellyfin.Drawing.Skia/SkiaEncoder.cs | 43 +++++++++++++++++-- Jellyfin.Drawing.Skia/StripCollageBuilder.cs | 13 +++--- 3 files changed, 46 insertions(+), 15 deletions(-) diff --git a/Jellyfin.Drawing.Skia/Jellyfin.Drawing.Skia.csproj b/Jellyfin.Drawing.Skia/Jellyfin.Drawing.Skia.csproj index 65a4394594..c71c76f08f 100644 --- a/Jellyfin.Drawing.Skia/Jellyfin.Drawing.Skia.csproj +++ b/Jellyfin.Drawing.Skia/Jellyfin.Drawing.Skia.csproj @@ -20,9 +20,8 @@ - - - + + diff --git a/Jellyfin.Drawing.Skia/SkiaEncoder.cs b/Jellyfin.Drawing.Skia/SkiaEncoder.cs index 1f7c43de81..a66ecc0813 100644 --- a/Jellyfin.Drawing.Skia/SkiaEncoder.cs +++ b/Jellyfin.Drawing.Skia/SkiaEncoder.cs @@ -395,6 +395,42 @@ namespace Jellyfin.Drawing.Skia return rotated; } + /// + /// Resizes an image on the CPU, by utilizing a surface and canvas. + /// + /// The source bitmap. + /// This specifies the target size and other information required to create the surface. + /// This enables anti-aliasing on the SKPaint instance. + /// This enables dithering on the SKPaint instance. + /// The resized image. + internal static SKImage ResizeImage(SKBitmap source, SKImageInfo targetInfo, bool isAntialias = false, bool isDither = false) + { + using var surface = SKSurface.Create(targetInfo); + using var canvas = surface.Canvas; + using var paint = new SKPaint(); + + paint.FilterQuality = SKFilterQuality.High; + paint.IsAntialias = isAntialias; + paint.IsDither = isDither; + + var kernel = new float[9] + { + 0, -.1f, 0, + -.1f, 1.4f, -.1f, + 0, -.1f, 0, + }; + + var kernelSize = new SKSizeI(3, 3); + var kernelOffset = new SKPointI(1, 1); + + paint.ImageFilter = SKImageFilter.CreateMatrixConvolution( + kernelSize, kernel, 1f, 0f, kernelOffset, SKShaderTileMode.Clamp, false); + + canvas.DrawBitmap(source, SKRect.Create(0, 0, source.Width, source.Height), SKRect.Create(0, 0, targetInfo.Width, targetInfo.Height), paint); + + return surface.Snapshot(); + } + /// public string EncodeImage(string inputPath, DateTime dateModified, string outputPath, bool autoOrient, ImageOrientation? orientation, int quality, ImageProcessingOptions options, ImageFormat selectedOutputFormat) { @@ -436,9 +472,8 @@ namespace Jellyfin.Drawing.Skia var width = newImageSize.Width; var height = newImageSize.Height; - using var resizedBitmap = new SKBitmap(width, height, bitmap.ColorType, bitmap.AlphaType); - // scale image - bitmap.ScalePixels(resizedBitmap, SKFilterQuality.High); + // scale image (the FromImage creates a copy) + using var resizedBitmap = SKBitmap.FromImage(ResizeImage(bitmap, new SKImageInfo(width, height, bitmap.ColorType, bitmap.AlphaType, bitmap.ColorSpace))); // If all we're doing is resizing then we can stop now if (!hasBackgroundColor && !hasForegroundColor && blur == 0 && !hasIndicator) @@ -446,7 +481,7 @@ namespace Jellyfin.Drawing.Skia Directory.CreateDirectory(Path.GetDirectoryName(outputPath)); using var outputStream = new SKFileWStream(outputPath); using var pixmap = new SKPixmap(new SKImageInfo(width, height), resizedBitmap.GetPixels()); - pixmap.Encode(outputStream, skiaOutputFormat, quality); + resizedBitmap.Encode(outputStream, skiaOutputFormat, quality); return outputPath; } diff --git a/Jellyfin.Drawing.Skia/StripCollageBuilder.cs b/Jellyfin.Drawing.Skia/StripCollageBuilder.cs index e0ee4a342d..3b35594865 100644 --- a/Jellyfin.Drawing.Skia/StripCollageBuilder.cs +++ b/Jellyfin.Drawing.Skia/StripCollageBuilder.cs @@ -115,15 +115,13 @@ namespace Jellyfin.Drawing.Skia // resize to the same aspect as the original int iWidth = Math.Abs(iHeight * currentBitmap.Width / currentBitmap.Height); - using var resizeBitmap = new SKBitmap(iWidth, iHeight, currentBitmap.ColorType, currentBitmap.AlphaType); - currentBitmap.ScalePixels(resizeBitmap, SKFilterQuality.High); + using var resizedImage = SkiaEncoder.ResizeImage(bitmap, new SKImageInfo(iWidth, iHeight, currentBitmap.ColorType, currentBitmap.AlphaType, currentBitmap.ColorSpace)); // crop image int ix = Math.Abs((iWidth - iSlice) / 2); - using var image = SKImage.FromBitmap(resizeBitmap); - using var subset = image.Subset(SKRectI.Create(ix, 0, iSlice, iHeight)); + using var subset = resizedImage.Subset(SKRectI.Create(ix, 0, iSlice, iHeight)); // draw image onto canvas - canvas.DrawImage(subset ?? image, iSlice * i, 0); + canvas.DrawImage(subset ?? resizedImage, iSlice * i, 0); } return bitmap; @@ -177,9 +175,8 @@ namespace Jellyfin.Drawing.Skia continue; } - using var resizedBitmap = new SKBitmap(cellWidth, cellHeight, currentBitmap.ColorType, currentBitmap.AlphaType); - // scale image - currentBitmap.ScalePixels(resizedBitmap, SKFilterQuality.High); + // Scale image. The FromBitmap creates a copy + using var resizedBitmap = SKBitmap.FromImage(SkiaEncoder.ResizeImage(bitmap, new SKImageInfo(cellWidth, cellHeight, currentBitmap.ColorType, currentBitmap.AlphaType, currentBitmap.ColorSpace))); // draw this image into the strip at the next position var xPos = x * cellWidth; From af38e5469f30b1b08e341fa00a7a7eea53388b8a Mon Sep 17 00:00:00 2001 From: Erwin de Haan Date: Fri, 31 Jul 2020 21:33:25 +0200 Subject: [PATCH 50/60] Update Jellyfin.Drawing.Skia/SkiaEncoder.cs indentation. Co-authored-by: Cody Robibero --- Jellyfin.Drawing.Skia/SkiaEncoder.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Jellyfin.Drawing.Skia/SkiaEncoder.cs b/Jellyfin.Drawing.Skia/SkiaEncoder.cs index a66ecc0813..4e08758f62 100644 --- a/Jellyfin.Drawing.Skia/SkiaEncoder.cs +++ b/Jellyfin.Drawing.Skia/SkiaEncoder.cs @@ -415,9 +415,9 @@ namespace Jellyfin.Drawing.Skia var kernel = new float[9] { - 0, -.1f, 0, - -.1f, 1.4f, -.1f, - 0, -.1f, 0, + 0, -.1f, 0, + -.1f, 1.4f, -.1f, + 0, -.1f, 0, }; var kernelSize = new SKSizeI(3, 3); From 526eea41f0b74c5717575887839ecc41ca22067f Mon Sep 17 00:00:00 2001 From: Erwin de Haan Date: Fri, 31 Jul 2020 22:02:16 +0200 Subject: [PATCH 51/60] Add a note on the convolutional matrix filter. --- Jellyfin.Drawing.Skia/SkiaEncoder.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Jellyfin.Drawing.Skia/SkiaEncoder.cs b/Jellyfin.Drawing.Skia/SkiaEncoder.cs index 4e08758f62..58d3039557 100644 --- a/Jellyfin.Drawing.Skia/SkiaEncoder.cs +++ b/Jellyfin.Drawing.Skia/SkiaEncoder.cs @@ -397,6 +397,9 @@ namespace Jellyfin.Drawing.Skia /// /// Resizes an image on the CPU, by utilizing a surface and canvas. + /// + /// The convolutional matrix kernel used in this resize function gives a (light) sharpening effect. + /// This technique is similar to effect that can be created using for example the [Convolution matrix filter in GIMP](https://docs.gimp.org/2.10/en/gimp-filter-convolution-matrix.html). /// /// The source bitmap. /// This specifies the target size and other information required to create the surface. From 44aca4dc6ff478776ca56c95763b3db7177234f3 Mon Sep 17 00:00:00 2001 From: Erwin de Haan Date: Fri, 31 Jul 2020 22:12:20 +0200 Subject: [PATCH 52/60] Formatting in SkiaEncoder.cs --- Jellyfin.Drawing.Skia/SkiaEncoder.cs | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/Jellyfin.Drawing.Skia/SkiaEncoder.cs b/Jellyfin.Drawing.Skia/SkiaEncoder.cs index 58d3039557..62e1d6ed14 100644 --- a/Jellyfin.Drawing.Skia/SkiaEncoder.cs +++ b/Jellyfin.Drawing.Skia/SkiaEncoder.cs @@ -410,11 +410,12 @@ namespace Jellyfin.Drawing.Skia { using var surface = SKSurface.Create(targetInfo); using var canvas = surface.Canvas; - using var paint = new SKPaint(); - - paint.FilterQuality = SKFilterQuality.High; - paint.IsAntialias = isAntialias; - paint.IsDither = isDither; + using var paint = new SKPaint + { + FilterQuality = SKFilterQuality.High, + IsAntialias = isAntialias, + IsDither = isDither + }; var kernel = new float[9] { @@ -427,9 +428,19 @@ namespace Jellyfin.Drawing.Skia var kernelOffset = new SKPointI(1, 1); paint.ImageFilter = SKImageFilter.CreateMatrixConvolution( - kernelSize, kernel, 1f, 0f, kernelOffset, SKShaderTileMode.Clamp, false); + kernelSize, + kernel, + 1f, + 0f, + kernelOffset, + SKShaderTileMode.Clamp, + false); - canvas.DrawBitmap(source, SKRect.Create(0, 0, source.Width, source.Height), SKRect.Create(0, 0, targetInfo.Width, targetInfo.Height), paint); + canvas.DrawBitmap( + source, + SKRect.Create(0, 0, source.Width, source.Height), + SKRect.Create(0, 0, targetInfo.Width, targetInfo.Height), + paint); return surface.Snapshot(); } From a6bc4c688d707c86aabc608cd03941df9b85e132 Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Sat, 1 Aug 2020 14:52:45 -0400 Subject: [PATCH 53/60] Add using statement to DisplayPreferences migration --- .../Migrations/Routines/MigrateDisplayPreferencesDb.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jellyfin.Server/Migrations/Routines/MigrateDisplayPreferencesDb.cs b/Jellyfin.Server/Migrations/Routines/MigrateDisplayPreferencesDb.cs index 183b1aeabd..780c1488dd 100644 --- a/Jellyfin.Server/Migrations/Routines/MigrateDisplayPreferencesDb.cs +++ b/Jellyfin.Server/Migrations/Routines/MigrateDisplayPreferencesDb.cs @@ -67,7 +67,7 @@ namespace Jellyfin.Server.Migrations.Routines var dbFilePath = Path.Combine(_paths.DataPath, DbFilename); using (var connection = SQLite3.Open(dbFilePath, ConnectionFlags.ReadOnly, null)) { - var dbContext = _provider.CreateContext(); + using var dbContext = _provider.CreateContext(); var results = connection.Query("SELECT * FROM userdisplaypreferences"); foreach (var result in results) From ad32800504c08812212f7f39403924fdee635e5d Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Sat, 1 Aug 2020 14:56:32 -0400 Subject: [PATCH 54/60] Switch to unstable chromecast version. --- Jellyfin.Data/Enums/ChromecastVersion.cs | 4 ++-- .../Routines/MigrateDisplayPreferencesDb.cs | 12 +++++++++--- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/Jellyfin.Data/Enums/ChromecastVersion.cs b/Jellyfin.Data/Enums/ChromecastVersion.cs index 855c75ab45..2622e08efb 100644 --- a/Jellyfin.Data/Enums/ChromecastVersion.cs +++ b/Jellyfin.Data/Enums/ChromecastVersion.cs @@ -11,8 +11,8 @@ Stable = 0, /// - /// Nightly Chromecast version. + /// Unstable Chromecast version. /// - Nightly = 1 + Unstable = 1 } } diff --git a/Jellyfin.Server/Migrations/Routines/MigrateDisplayPreferencesDb.cs b/Jellyfin.Server/Migrations/Routines/MigrateDisplayPreferencesDb.cs index 780c1488dd..b15ccf01eb 100644 --- a/Jellyfin.Server/Migrations/Routines/MigrateDisplayPreferencesDb.cs +++ b/Jellyfin.Server/Migrations/Routines/MigrateDisplayPreferencesDb.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Globalization; using System.IO; using System.Linq; @@ -64,6 +65,13 @@ namespace Jellyfin.Server.Migrations.Routines HomeSectionType.None, }; + var chromecastDict = new Dictionary(StringComparer.OrdinalIgnoreCase) + { + { "stable", ChromecastVersion.Stable }, + { "nightly", ChromecastVersion.Unstable }, + { "unstable", ChromecastVersion.Unstable } + }; + var dbFilePath = Path.Combine(_paths.DataPath, DbFilename); using (var connection = SQLite3.Open(dbFilePath, ConnectionFlags.ReadOnly, null)) { @@ -74,9 +82,7 @@ namespace Jellyfin.Server.Migrations.Routines { var dto = JsonSerializer.Deserialize(result[3].ToString(), _jsonOptions); var chromecastVersion = dto.CustomPrefs.TryGetValue("chromecastVersion", out var version) - ? Enum.TryParse(version, true, out var parsed) - ? parsed - : ChromecastVersion.Stable + ? chromecastDict[version] : ChromecastVersion.Stable; var displayPreferences = new DisplayPreferences(new Guid(result[1].ToBlob()), result[2].ToString()) From 0f43780c8c50a6d933091056aaa523905f6e7b1a Mon Sep 17 00:00:00 2001 From: Erwin de Haan Date: Sun, 2 Aug 2020 12:43:25 +0200 Subject: [PATCH 55/60] Apply suggestions from code review Co-authored-by: Claus Vium --- Jellyfin.Drawing.Skia/SkiaEncoder.cs | 3 ++- Jellyfin.Drawing.Skia/StripCollageBuilder.cs | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/Jellyfin.Drawing.Skia/SkiaEncoder.cs b/Jellyfin.Drawing.Skia/SkiaEncoder.cs index 62e1d6ed14..a1caa751b1 100644 --- a/Jellyfin.Drawing.Skia/SkiaEncoder.cs +++ b/Jellyfin.Drawing.Skia/SkiaEncoder.cs @@ -487,7 +487,8 @@ namespace Jellyfin.Drawing.Skia var height = newImageSize.Height; // scale image (the FromImage creates a copy) - using var resizedBitmap = SKBitmap.FromImage(ResizeImage(bitmap, new SKImageInfo(width, height, bitmap.ColorType, bitmap.AlphaType, bitmap.ColorSpace))); + var imageInfo = new SKImageInfo(width, height, bitmap.ColorType, bitmap.AlphaType, bitmap.ColorSpace); + using var resizedBitmap = SKBitmap.FromImage(ResizeImage(bitmap, imageInfo)); // If all we're doing is resizing then we can stop now if (!hasBackgroundColor && !hasForegroundColor && blur == 0 && !hasIndicator) diff --git a/Jellyfin.Drawing.Skia/StripCollageBuilder.cs b/Jellyfin.Drawing.Skia/StripCollageBuilder.cs index 3b35594865..b08c3750d7 100644 --- a/Jellyfin.Drawing.Skia/StripCollageBuilder.cs +++ b/Jellyfin.Drawing.Skia/StripCollageBuilder.cs @@ -176,7 +176,8 @@ namespace Jellyfin.Drawing.Skia } // Scale image. The FromBitmap creates a copy - using var resizedBitmap = SKBitmap.FromImage(SkiaEncoder.ResizeImage(bitmap, new SKImageInfo(cellWidth, cellHeight, currentBitmap.ColorType, currentBitmap.AlphaType, currentBitmap.ColorSpace))); + var imageInfo = new SKImageInfo(cellWidth, cellHeight, currentBitmap.ColorType, currentBitmap.AlphaType, currentBitmap.ColorSpace); + using var resizedBitmap = SKBitmap.FromImage(SkiaEncoder.ResizeImage(bitmap, imageInfo)); // draw this image into the strip at the next position var xPos = x * cellWidth; From aa32aba0f847241ec8660b1819f5ec769c312d0a Mon Sep 17 00:00:00 2001 From: cvium Date: Sun, 2 Aug 2020 23:30:34 +0200 Subject: [PATCH 56/60] Remove some unnecessary string allocations. --- .../Data/SqliteItemRepository.cs | 133 +++--------------- 1 file changed, 20 insertions(+), 113 deletions(-) diff --git a/Emby.Server.Implementations/Data/SqliteItemRepository.cs b/Emby.Server.Implementations/Data/SqliteItemRepository.cs index 6188da35ac..b8901b99ee 100644 --- a/Emby.Server.Implementations/Data/SqliteItemRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteItemRepository.cs @@ -401,6 +401,8 @@ namespace Emby.Server.Implementations.Data "OwnerId" }; + private static readonly string _retriveItemColumnsSelectQuery = $"select {string.Join(',', _retriveItemColumns)} from TypedBaseItems where guid = @guid"; + private static readonly string[] _mediaStreamSaveColumns = { "ItemId", @@ -440,6 +442,12 @@ namespace Emby.Server.Implementations.Data "ColorTransfer" }; + private static readonly string _mediaStreamSaveColumnsInsertQuery = + $"insert into mediastreams ({string.Join(',', _mediaStreamSaveColumns)}) values "; + + private static readonly string _mediaStreamSaveColumnsSelectQuery = + $"select {string.Join(',', _mediaStreamSaveColumns)} from mediastreams where ItemId=@ItemId"; + private static readonly string[] _mediaAttachmentSaveColumns = { "ItemId", @@ -451,102 +459,15 @@ namespace Emby.Server.Implementations.Data "MIMEType" }; + private static readonly string _mediaAttachmentSaveColumnsSelectQuery = + $"select {string.Join(',', _mediaAttachmentSaveColumns)} from mediaattachments where ItemId=@ItemId"; + private static readonly string _mediaAttachmentInsertPrefix; - private static string GetSaveItemCommandText() - { - var saveColumns = new[] - { - "guid", - "type", - "data", - "Path", - "StartDate", - "EndDate", - "ChannelId", - "IsMovie", - "IsSeries", - "EpisodeTitle", - "IsRepeat", - "CommunityRating", - "CustomRating", - "IndexNumber", - "IsLocked", - "Name", - "OfficialRating", - "MediaType", - "Overview", - "ParentIndexNumber", - "PremiereDate", - "ProductionYear", - "ParentId", - "Genres", - "InheritedParentalRatingValue", - "SortName", - "ForcedSortName", - "RunTimeTicks", - "Size", - "DateCreated", - "DateModified", - "PreferredMetadataLanguage", - "PreferredMetadataCountryCode", - "Width", - "Height", - "DateLastRefreshed", - "DateLastSaved", - "IsInMixedFolder", - "LockedFields", - "Studios", - "Audio", - "ExternalServiceId", - "Tags", - "IsFolder", - "UnratedType", - "TopParentId", - "TrailerTypes", - "CriticRating", - "CleanName", - "PresentationUniqueKey", - "OriginalTitle", - "PrimaryVersionId", - "DateLastMediaAdded", - "Album", - "IsVirtualItem", - "SeriesName", - "UserDataKey", - "SeasonName", - "SeasonId", - "SeriesId", - "ExternalSeriesId", - "Tagline", - "ProviderIds", - "Images", - "ProductionLocations", - "ExtraIds", - "TotalBitrate", - "ExtraType", - "Artists", - "AlbumArtists", - "ExternalId", - "SeriesPresentationUniqueKey", - "ShowId", - "OwnerId" - }; - - var saveItemCommandCommandText = "replace into TypedBaseItems (" + string.Join(",", saveColumns) + ") values ("; - - for (var i = 0; i < saveColumns.Length; i++) - { - if (i != 0) - { - saveItemCommandCommandText += ","; - } - - saveItemCommandCommandText += "@" + saveColumns[i]; - } - - return saveItemCommandCommandText + ")"; - } + private const string GetSaveItemCommandText = + @"replace into TypedBaseItems + (guid,type,data,Path,StartDate,EndDate,ChannelId,IsMovie,IsSeries,EpisodeTitle,IsRepeat,CommunityRating,CustomRating,IndexNumber,IsLocked,Name,OfficialRating,MediaType,Overview,ParentIndexNumber,PremiereDate,ProductionYear,ParentId,Genres,InheritedParentalRatingValue,SortName,ForcedSortName,RunTimeTicks,Size,DateCreated,DateModified,PreferredMetadataLanguage,PreferredMetadataCountryCode,Width,Height,DateLastRefreshed,DateLastSaved,IsInMixedFolder,LockedFields,Studios,Audio,ExternalServiceId,Tags,IsFolder,UnratedType,TopParentId,TrailerTypes,CriticRating,CleanName,PresentationUniqueKey,OriginalTitle,PrimaryVersionId,DateLastMediaAdded,Album,IsVirtualItem,SeriesName,UserDataKey,SeasonName,SeasonId,SeriesId,ExternalSeriesId,Tagline,ProviderIds,Images,ProductionLocations,ExtraIds,TotalBitrate,ExtraType,Artists,AlbumArtists,ExternalId,SeriesPresentationUniqueKey,ShowId,OwnerId) + values (@guid,@type,@data,@Path,@StartDate,@EndDate,@ChannelId,@IsMovie,@IsSeries,@EpisodeTitle,@IsRepeat,@CommunityRating,@CustomRating,@IndexNumber,@IsLocked,@Name,@OfficialRating,@MediaType,@Overview,@ParentIndexNumber,@PremiereDate,@ProductionYear,@ParentId,@Genres,@InheritedParentalRatingValue,@SortName,@ForcedSortName,@RunTimeTicks,@Size,@DateCreated,@DateModified,@PreferredMetadataLanguage,@PreferredMetadataCountryCode,@Width,@Height,@DateLastRefreshed,@DateLastSaved,@IsInMixedFolder,@LockedFields,@Studios,@Audio,@ExternalServiceId,@Tags,@IsFolder,@UnratedType,@TopParentId,@TrailerTypes,@CriticRating,@CleanName,@PresentationUniqueKey,@OriginalTitle,@PrimaryVersionId,@DateLastMediaAdded,@Album,@IsVirtualItem,@SeriesName,@UserDataKey,@SeasonName,@SeasonId,@SeriesId,@ExternalSeriesId,@Tagline,@ProviderIds,@Images,@ProductionLocations,@ExtraIds,@TotalBitrate,@ExtraType,@Artists,@AlbumArtists,@ExternalId,@SeriesPresentationUniqueKey,@ShowId,@OwnerId)"; /// /// Save a standard item in the repo. @@ -637,7 +558,7 @@ namespace Emby.Server.Implementations.Data { var statements = PrepareAll(db, new string[] { - GetSaveItemCommandText(), + GetSaveItemCommandText, "delete from AncestorIds where ItemId=@ItemId" }).ToList(); @@ -1227,7 +1148,7 @@ namespace Emby.Server.Implementations.Data using (var connection = GetConnection(true)) { - using (var statement = PrepareStatement(connection, "select " + string.Join(",", _retriveItemColumns) + " from TypedBaseItems where guid = @guid")) + using (var statement = PrepareStatement(connection, _retriveItemColumnsSelectQuery)) { statement.TryBind("@guid", id); @@ -5895,10 +5816,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type throw new ArgumentNullException(nameof(query)); } - var cmdText = "select " - + string.Join(",", _mediaStreamSaveColumns) - + " from mediastreams where" - + " ItemId=@ItemId"; + var cmdText = _mediaStreamSaveColumnsSelectQuery; if (query.Type.HasValue) { @@ -5977,15 +5895,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type while (startIndex < streams.Count) { - var insertText = new StringBuilder("insert into mediastreams ("); - foreach (var column in _mediaStreamSaveColumns) - { - insertText.Append(column).Append(','); - } - - // Remove last comma - insertText.Length--; - insertText.Append(") values "); + var insertText = new StringBuilder(_mediaStreamSaveColumnsInsertQuery); var endIndex = Math.Min(streams.Count, startIndex + Limit); @@ -6252,10 +6162,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type throw new ArgumentNullException(nameof(query)); } - var cmdText = "select " - + string.Join(",", _mediaAttachmentSaveColumns) - + " from mediaattachments where" - + " ItemId=@ItemId"; + var cmdText = _mediaAttachmentSaveColumnsSelectQuery; if (query.Index.HasValue) { From 85e43d657f2983a78f247ce1246fed22bf0aa185 Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Sun, 2 Aug 2020 23:33:45 +0200 Subject: [PATCH 57/60] Update Emby.Server.Implementations/Data/SqliteItemRepository.cs Co-authored-by: Bond-009 --- Emby.Server.Implementations/Data/SqliteItemRepository.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Data/SqliteItemRepository.cs b/Emby.Server.Implementations/Data/SqliteItemRepository.cs index b8901b99ee..e4ebdb87e7 100644 --- a/Emby.Server.Implementations/Data/SqliteItemRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteItemRepository.cs @@ -464,7 +464,7 @@ namespace Emby.Server.Implementations.Data private static readonly string _mediaAttachmentInsertPrefix; - private const string GetSaveItemCommandText = + private const string SaveItemCommandText = @"replace into TypedBaseItems (guid,type,data,Path,StartDate,EndDate,ChannelId,IsMovie,IsSeries,EpisodeTitle,IsRepeat,CommunityRating,CustomRating,IndexNumber,IsLocked,Name,OfficialRating,MediaType,Overview,ParentIndexNumber,PremiereDate,ProductionYear,ParentId,Genres,InheritedParentalRatingValue,SortName,ForcedSortName,RunTimeTicks,Size,DateCreated,DateModified,PreferredMetadataLanguage,PreferredMetadataCountryCode,Width,Height,DateLastRefreshed,DateLastSaved,IsInMixedFolder,LockedFields,Studios,Audio,ExternalServiceId,Tags,IsFolder,UnratedType,TopParentId,TrailerTypes,CriticRating,CleanName,PresentationUniqueKey,OriginalTitle,PrimaryVersionId,DateLastMediaAdded,Album,IsVirtualItem,SeriesName,UserDataKey,SeasonName,SeasonId,SeriesId,ExternalSeriesId,Tagline,ProviderIds,Images,ProductionLocations,ExtraIds,TotalBitrate,ExtraType,Artists,AlbumArtists,ExternalId,SeriesPresentationUniqueKey,ShowId,OwnerId) values (@guid,@type,@data,@Path,@StartDate,@EndDate,@ChannelId,@IsMovie,@IsSeries,@EpisodeTitle,@IsRepeat,@CommunityRating,@CustomRating,@IndexNumber,@IsLocked,@Name,@OfficialRating,@MediaType,@Overview,@ParentIndexNumber,@PremiereDate,@ProductionYear,@ParentId,@Genres,@InheritedParentalRatingValue,@SortName,@ForcedSortName,@RunTimeTicks,@Size,@DateCreated,@DateModified,@PreferredMetadataLanguage,@PreferredMetadataCountryCode,@Width,@Height,@DateLastRefreshed,@DateLastSaved,@IsInMixedFolder,@LockedFields,@Studios,@Audio,@ExternalServiceId,@Tags,@IsFolder,@UnratedType,@TopParentId,@TrailerTypes,@CriticRating,@CleanName,@PresentationUniqueKey,@OriginalTitle,@PrimaryVersionId,@DateLastMediaAdded,@Album,@IsVirtualItem,@SeriesName,@UserDataKey,@SeasonName,@SeasonId,@SeriesId,@ExternalSeriesId,@Tagline,@ProviderIds,@Images,@ProductionLocations,@ExtraIds,@TotalBitrate,@ExtraType,@Artists,@AlbumArtists,@ExternalId,@SeriesPresentationUniqueKey,@ShowId,@OwnerId)"; From 996d0c07d02898774579e03ca9a32ef982707e19 Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Sun, 2 Aug 2020 23:34:28 +0200 Subject: [PATCH 58/60] Update Emby.Server.Implementations/Data/SqliteItemRepository.cs --- Emby.Server.Implementations/Data/SqliteItemRepository.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Data/SqliteItemRepository.cs b/Emby.Server.Implementations/Data/SqliteItemRepository.cs index e4ebdb87e7..1c6d3cb94e 100644 --- a/Emby.Server.Implementations/Data/SqliteItemRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteItemRepository.cs @@ -558,7 +558,7 @@ namespace Emby.Server.Implementations.Data { var statements = PrepareAll(db, new string[] { - GetSaveItemCommandText, + SaveItemCommandText, "delete from AncestorIds where ItemId=@ItemId" }).ToList(); From e7d9d8f7c5289855ca16ebb35446b59cfc04c41a Mon Sep 17 00:00:00 2001 From: cvium Date: Sun, 2 Aug 2020 23:54:58 +0200 Subject: [PATCH 59/60] Change Budget and Revenue to long to avoid overflow --- .../Plugins/Tmdb/Models/Movies/MovieResult.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Models/Movies/MovieResult.cs b/MediaBrowser.Providers/Plugins/Tmdb/Models/Movies/MovieResult.cs index 7566df8b61..704ebcd5a1 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/Models/Movies/MovieResult.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/Models/Movies/MovieResult.cs @@ -13,7 +13,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Models.Movies public BelongsToCollection Belongs_To_Collection { get; set; } - public int Budget { get; set; } + public long Budget { get; set; } public List Genres { get; set; } @@ -39,7 +39,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Models.Movies public string Release_Date { get; set; } - public int Revenue { get; set; } + public long Revenue { get; set; } public int Runtime { get; set; } From 705c0f93f671b54f19db4f446dd8bec0bc197fc0 Mon Sep 17 00:00:00 2001 From: Anthony Lavado Date: Sun, 2 Aug 2020 19:56:54 -0400 Subject: [PATCH 60/60] Update to newer Jellyfin.XMLTV --- Emby.Server.Implementations/Emby.Server.Implementations.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Emby.Server.Implementations.csproj b/Emby.Server.Implementations/Emby.Server.Implementations.csproj index efb2f81cc6..3b685c88b1 100644 --- a/Emby.Server.Implementations/Emby.Server.Implementations.csproj +++ b/Emby.Server.Implementations/Emby.Server.Implementations.csproj @@ -25,7 +25,7 @@ - +