From ab396225eaf486932fdb2f23eefa1cbfecbb27f4 Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Tue, 30 Jun 2020 21:44:41 -0400 Subject: [PATCH 01/32] 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/32] 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/32] 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/32] 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/32] 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/32] 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/32] 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/32] 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/32] 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/32] 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/32] 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/32] 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/32] 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/32] 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/32] 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/32] 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/32] 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/32] 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 0d13d830bb3ae9fc0098aa802c16e428f6929341 Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Fri, 24 Jul 2020 16:30:54 -0400 Subject: [PATCH 19/32] 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 20/32] 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 21/32] 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 22/32] 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 592d2480ca9c424c7b8b8f4be2bdfa81b4476f0c Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Mon, 27 Jul 2020 19:22:34 -0400 Subject: [PATCH 23/32] 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 24/32] 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 25/32] 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 26/32] 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 27/32] 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 c3a36485b68c7eeaffd3b60af14a8ef051c25f96 Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Mon, 27 Jul 2020 23:41:16 -0400 Subject: [PATCH 28/32] 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 c094916df0e6202356602dc191516e3b0f87f429 Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Tue, 28 Jul 2020 09:19:25 -0400 Subject: [PATCH 29/32] 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 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 30/32] 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 a6bc4c688d707c86aabc608cd03941df9b85e132 Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Sat, 1 Aug 2020 14:52:45 -0400 Subject: [PATCH 31/32] 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 32/32] 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())