Add support for custom item display preferences

pull/4652/head
crobibero 4 years ago
parent 4a3411cad1
commit b0c79edd2c

@ -81,6 +81,19 @@ namespace Jellyfin.Api.Controllers
dto.CustomPrefs["enableNextVideoInfoOverlay"] = displayPreferences.EnableNextVideoInfoOverlay.ToString(CultureInfo.InvariantCulture);
dto.CustomPrefs["tvhome"] = displayPreferences.TvHome;
// Load all custom display preferences
var customDisplayPreferences = _displayPreferencesManager.ListCustomItemDisplayPreferences(displayPreferences.UserId, displayPreferences.Client);
if (customDisplayPreferences != null)
{
foreach (var (key, value) in customDisplayPreferences)
{
if (!dto.CustomPrefs.ContainsKey(key))
{
dto.CustomPrefs[key] = value;
}
}
}
// This will essentially be a noop if no changes have been made, but new prefs must be saved at least.
_displayPreferencesManager.SaveChanges();
@ -124,21 +137,33 @@ namespace Jellyfin.Api.Controllers
existingDisplayPreferences.ChromecastVersion = displayPreferences.CustomPrefs.TryGetValue("chromecastVersion", out var chromecastVersion)
? Enum.Parse<ChromecastVersion>(chromecastVersion, true)
: ChromecastVersion.Stable;
displayPreferences.CustomPrefs.Remove("chromecastVersion");
existingDisplayPreferences.EnableNextVideoInfoOverlay = displayPreferences.CustomPrefs.TryGetValue("enableNextVideoInfoOverlay", out var enableNextVideoInfoOverlay)
? bool.Parse(enableNextVideoInfoOverlay)
: true;
displayPreferences.CustomPrefs.Remove("enableNextVideoInfoOverlay");
existingDisplayPreferences.SkipBackwardLength = displayPreferences.CustomPrefs.TryGetValue("skipBackLength", out var skipBackLength)
? int.Parse(skipBackLength, CultureInfo.InvariantCulture)
: 10000;
displayPreferences.CustomPrefs.Remove("skipBackLength");
existingDisplayPreferences.SkipForwardLength = displayPreferences.CustomPrefs.TryGetValue("skipForwardLength", out var skipForwardLength)
? int.Parse(skipForwardLength, CultureInfo.InvariantCulture)
: 30000;
displayPreferences.CustomPrefs.Remove("skipForwardLength");
existingDisplayPreferences.DashboardTheme = displayPreferences.CustomPrefs.TryGetValue("dashboardTheme", out var theme)
? theme
: string.Empty;
displayPreferences.CustomPrefs.Remove("dashboardTheme");
existingDisplayPreferences.TvHome = displayPreferences.CustomPrefs.TryGetValue("tvhome", out var home)
? home
: string.Empty;
displayPreferences.CustomPrefs.Remove("tvhome");
existingDisplayPreferences.HomeSections.Clear();
foreach (var key in displayPreferences.CustomPrefs.Keys.Where(key => key.StartsWith("homesection", StringComparison.OrdinalIgnoreCase)))
@ -149,13 +174,18 @@ namespace Jellyfin.Api.Controllers
type = order < 7 ? defaults[order] : HomeSectionType.None;
}
displayPreferences.CustomPrefs.Remove(key);
existingDisplayPreferences.HomeSections.Add(new HomeSection { Order = order, Type = type });
}
foreach (var key in displayPreferences.CustomPrefs.Keys.Where(key => key.StartsWith("landing-", StringComparison.OrdinalIgnoreCase)))
{
var itemPreferences = _displayPreferencesManager.GetItemDisplayPreferences(existingDisplayPreferences.UserId, Guid.Parse(key.Substring("landing-".Length)), existingDisplayPreferences.Client);
itemPreferences.ViewType = Enum.Parse<ViewType>(displayPreferences.ViewType);
if (Guid.TryParse(key.Substring("landing-".Length), out var preferenceId))
{
var itemPreferences = _displayPreferencesManager.GetItemDisplayPreferences(existingDisplayPreferences.UserId, preferenceId, existingDisplayPreferences.Client);
itemPreferences.ViewType = Enum.Parse<ViewType>(displayPreferences.ViewType);
displayPreferences.CustomPrefs.Remove(key);
}
}
var itemPrefs = _displayPreferencesManager.GetItemDisplayPreferences(existingDisplayPreferences.UserId, Guid.Empty, existingDisplayPreferences.Client);
@ -169,6 +199,8 @@ namespace Jellyfin.Api.Controllers
itemPrefs.ViewType = viewType;
}
// Set all remaining custom preferences.
_displayPreferencesManager.SetCustomItemDisplayPreferences(userId, existingDisplayPreferences.Client, displayPreferences.CustomPrefs);
_displayPreferencesManager.SaveChanges();
return NoContent();

@ -0,0 +1,80 @@
using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace Jellyfin.Data.Entities
{
/// <summary>
/// An entity that represents a user's custom display preferences for a specific item.
/// </summary>
public class CustomItemDisplayPreferences
{
/// <summary>
/// Initializes a new instance of the <see cref="CustomItemDisplayPreferences"/> class.
/// </summary>
/// <param name="userId">The user id.</param>
/// <param name="client">The client.</param>
/// <param name="preferenceKey">The preference key.</param>
/// <param name="preferenceValue">The preference value.</param>
public CustomItemDisplayPreferences(Guid userId, string client, string preferenceKey, string preferenceValue)
{
UserId = userId;
Client = client;
Key = preferenceKey;
Value = preferenceValue;
}
/// <summary>
/// Initializes a new instance of the <see cref="CustomItemDisplayPreferences"/> class.
/// </summary>
protected CustomItemDisplayPreferences()
{
}
/// <summary>
/// Gets or sets the Id.
/// </summary>
/// <remarks>
/// Required.
/// </remarks>
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; protected set; }
/// <summary>
/// Gets or sets the user Id.
/// </summary>
/// <remarks>
/// Required.
/// </remarks>
public Guid UserId { get; set; }
/// <summary>
/// Gets or sets the client string.
/// </summary>
/// <remarks>
/// Required. Max Length = 32.
/// </remarks>
[Required]
[MaxLength(32)]
[StringLength(32)]
public string Client { get; set; }
/// <summary>
/// Gets or sets the preference key.
/// </summary>
/// <remarks>
/// Required.
/// </remarks>
[Required]
public string Key { get; set; }
/// <summary>
/// Gets or sets the preference value.
/// </summary>
/// <remarks>
/// Required.
/// </remarks>
[Required]
public string Value { get; set; }
}
}

@ -34,6 +34,8 @@ namespace Jellyfin.Server.Implementations
public virtual DbSet<ItemDisplayPreferences> ItemDisplayPreferences { get; set; }
public virtual DbSet<CustomItemDisplayPreferences> CustomItemDisplayPreferences { get; set; }
public virtual DbSet<Permission> Permissions { get; set; }
public virtual DbSet<Preference> Preferences { get; set; }
@ -153,6 +155,14 @@ namespace Jellyfin.Server.Implementations
modelBuilder.Entity<DisplayPreferences>()
.HasIndex(entity => new { entity.UserId, entity.Client })
.IsUnique();
modelBuilder.Entity<CustomItemDisplayPreferences>()
.HasIndex(entity => entity.UserId)
.IsUnique(false);
modelBuilder.Entity<CustomItemDisplayPreferences>()
.HasIndex(entity => new { entity.UserId, entity.Client, entity.Key })
.IsUnique();
}
}
}

@ -0,0 +1,516 @@
#pragma warning disable CS1591
// <auto-generated />
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("20201203203707_AddCustomDisplayPreferences")]
partial class AddCustomDisplayPreferences
{
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasDefaultSchema("jellyfin")
.HasAnnotation("ProductVersion", "5.0.0");
modelBuilder.Entity("Jellyfin.Data.Entities.AccessSchedule", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<int>("DayOfWeek")
.HasColumnType("INTEGER");
b.Property<double>("EndHour")
.HasColumnType("REAL");
b.Property<double>("StartHour")
.HasColumnType("REAL");
b.Property<Guid>("UserId")
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("UserId");
b.ToTable("AccessSchedules");
});
modelBuilder.Entity("Jellyfin.Data.Entities.ActivityLog", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<DateTime>("DateCreated")
.HasColumnType("TEXT");
b.Property<string>("ItemId")
.HasMaxLength(256)
.HasColumnType("TEXT");
b.Property<int>("LogSeverity")
.HasColumnType("INTEGER");
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(512)
.HasColumnType("TEXT");
b.Property<string>("Overview")
.HasMaxLength(512)
.HasColumnType("TEXT");
b.Property<uint>("RowVersion")
.IsConcurrencyToken()
.HasColumnType("INTEGER");
b.Property<string>("ShortOverview")
.HasMaxLength(512)
.HasColumnType("TEXT");
b.Property<string>("Type")
.IsRequired()
.HasMaxLength(256)
.HasColumnType("TEXT");
b.Property<Guid>("UserId")
.HasColumnType("TEXT");
b.HasKey("Id");
b.ToTable("ActivityLogs");
});
modelBuilder.Entity("Jellyfin.Data.Entities.CustomItemDisplayPreferences", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("Client")
.IsRequired()
.HasMaxLength(32)
.HasColumnType("TEXT");
b.Property<string>("Key")
.IsRequired()
.HasColumnType("TEXT");
b.Property<Guid>("UserId")
.HasColumnType("TEXT");
b.Property<string>("Value")
.IsRequired()
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("UserId");
b.HasIndex("UserId", "Client", "Key")
.IsUnique();
b.ToTable("CustomItemDisplayPreferences");
});
modelBuilder.Entity("Jellyfin.Data.Entities.DisplayPreferences", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<int>("ChromecastVersion")
.HasColumnType("INTEGER");
b.Property<string>("Client")
.IsRequired()
.HasMaxLength(32)
.HasColumnType("TEXT");
b.Property<string>("DashboardTheme")
.HasMaxLength(32)
.HasColumnType("TEXT");
b.Property<bool>("EnableNextVideoInfoOverlay")
.HasColumnType("INTEGER");
b.Property<int?>("IndexBy")
.HasColumnType("INTEGER");
b.Property<int>("ScrollDirection")
.HasColumnType("INTEGER");
b.Property<bool>("ShowBackdrop")
.HasColumnType("INTEGER");
b.Property<bool>("ShowSidebar")
.HasColumnType("INTEGER");
b.Property<int>("SkipBackwardLength")
.HasColumnType("INTEGER");
b.Property<int>("SkipForwardLength")
.HasColumnType("INTEGER");
b.Property<string>("TvHome")
.HasMaxLength(32)
.HasColumnType("TEXT");
b.Property<Guid>("UserId")
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("UserId");
b.HasIndex("UserId", "Client")
.IsUnique();
b.ToTable("DisplayPreferences");
});
modelBuilder.Entity("Jellyfin.Data.Entities.HomeSection", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<int>("DisplayPreferencesId")
.HasColumnType("INTEGER");
b.Property<int>("Order")
.HasColumnType("INTEGER");
b.Property<int>("Type")
.HasColumnType("INTEGER");
b.HasKey("Id");
b.HasIndex("DisplayPreferencesId");
b.ToTable("HomeSection");
});
modelBuilder.Entity("Jellyfin.Data.Entities.ImageInfo", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<DateTime>("LastModified")
.HasColumnType("TEXT");
b.Property<string>("Path")
.IsRequired()
.HasMaxLength(512)
.HasColumnType("TEXT");
b.Property<Guid?>("UserId")
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("UserId")
.IsUnique();
b.ToTable("ImageInfos");
});
modelBuilder.Entity("Jellyfin.Data.Entities.ItemDisplayPreferences", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("Client")
.IsRequired()
.HasMaxLength(32)
.HasColumnType("TEXT");
b.Property<int?>("IndexBy")
.HasColumnType("INTEGER");
b.Property<Guid>("ItemId")
.HasColumnType("TEXT");
b.Property<bool>("RememberIndexing")
.HasColumnType("INTEGER");
b.Property<bool>("RememberSorting")
.HasColumnType("INTEGER");
b.Property<string>("SortBy")
.IsRequired()
.HasMaxLength(64)
.HasColumnType("TEXT");
b.Property<int>("SortOrder")
.HasColumnType("INTEGER");
b.Property<Guid>("UserId")
.HasColumnType("TEXT");
b.Property<int>("ViewType")
.HasColumnType("INTEGER");
b.HasKey("Id");
b.HasIndex("UserId");
b.ToTable("ItemDisplayPreferences");
});
modelBuilder.Entity("Jellyfin.Data.Entities.Permission", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<int>("Kind")
.HasColumnType("INTEGER");
b.Property<Guid?>("Permission_Permissions_Guid")
.HasColumnType("TEXT");
b.Property<uint>("RowVersion")
.IsConcurrencyToken()
.HasColumnType("INTEGER");
b.Property<bool>("Value")
.HasColumnType("INTEGER");
b.HasKey("Id");
b.HasIndex("Permission_Permissions_Guid");
b.ToTable("Permissions");
});
modelBuilder.Entity("Jellyfin.Data.Entities.Preference", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<int>("Kind")
.HasColumnType("INTEGER");
b.Property<Guid?>("Preference_Preferences_Guid")
.HasColumnType("TEXT");
b.Property<uint>("RowVersion")
.IsConcurrencyToken()
.HasColumnType("INTEGER");
b.Property<string>("Value")
.IsRequired()
.HasMaxLength(65535)
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("Preference_Preferences_Guid");
b.ToTable("Preferences");
});
modelBuilder.Entity("Jellyfin.Data.Entities.User", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT");
b.Property<string>("AudioLanguagePreference")
.HasMaxLength(255)
.HasColumnType("TEXT");
b.Property<string>("AuthenticationProviderId")
.IsRequired()
.HasMaxLength(255)
.HasColumnType("TEXT");
b.Property<bool>("DisplayCollectionsView")
.HasColumnType("INTEGER");
b.Property<bool>("DisplayMissingEpisodes")
.HasColumnType("INTEGER");
b.Property<string>("EasyPassword")
.HasMaxLength(65535)
.HasColumnType("TEXT");
b.Property<bool>("EnableAutoLogin")
.HasColumnType("INTEGER");
b.Property<bool>("EnableLocalPassword")
.HasColumnType("INTEGER");
b.Property<bool>("EnableNextEpisodeAutoPlay")
.HasColumnType("INTEGER");
b.Property<bool>("EnableUserPreferenceAccess")
.HasColumnType("INTEGER");
b.Property<bool>("HidePlayedInLatest")
.HasColumnType("INTEGER");
b.Property<long>("InternalId")
.HasColumnType("INTEGER");
b.Property<int>("InvalidLoginAttemptCount")
.HasColumnType("INTEGER");
b.Property<DateTime?>("LastActivityDate")
.HasColumnType("TEXT");
b.Property<DateTime?>("LastLoginDate")
.HasColumnType("TEXT");
b.Property<int?>("LoginAttemptsBeforeLockout")
.HasColumnType("INTEGER");
b.Property<int>("MaxActiveSessions")
.HasColumnType("INTEGER");
b.Property<int?>("MaxParentalAgeRating")
.HasColumnType("INTEGER");
b.Property<bool>("MustUpdatePassword")
.HasColumnType("INTEGER");
b.Property<string>("Password")
.HasMaxLength(65535)
.HasColumnType("TEXT");
b.Property<string>("PasswordResetProviderId")
.IsRequired()
.HasMaxLength(255)
.HasColumnType("TEXT");
b.Property<bool>("PlayDefaultAudioTrack")
.HasColumnType("INTEGER");
b.Property<bool>("RememberAudioSelections")
.HasColumnType("INTEGER");
b.Property<bool>("RememberSubtitleSelections")
.HasColumnType("INTEGER");
b.Property<int?>("RemoteClientBitrateLimit")
.HasColumnType("INTEGER");
b.Property<uint>("RowVersion")
.IsConcurrencyToken()
.HasColumnType("INTEGER");
b.Property<string>("SubtitleLanguagePreference")
.HasMaxLength(255)
.HasColumnType("TEXT");
b.Property<int>("SubtitleMode")
.HasColumnType("INTEGER");
b.Property<int>("SyncPlayAccess")
.HasColumnType("INTEGER");
b.Property<string>("Username")
.IsRequired()
.HasMaxLength(255)
.HasColumnType("TEXT");
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)
.WithOne("DisplayPreferences")
.HasForeignKey("Jellyfin.Data.Entities.DisplayPreferences", "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.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)
.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");
});
modelBuilder.Entity("Jellyfin.Data.Entities.DisplayPreferences", b =>
{
b.Navigation("HomeSections");
});
modelBuilder.Entity("Jellyfin.Data.Entities.User", b =>
{
b.Navigation("AccessSchedules");
b.Navigation("DisplayPreferences")
.IsRequired();
b.Navigation("ItemDisplayPreferences");
b.Navigation("Permissions");
b.Navigation("Preferences");
b.Navigation("ProfileImage");
});
#pragma warning restore 612, 618
}
}
}

@ -0,0 +1,70 @@
#pragma warning disable CS1591
// <auto-generated />
using System;
using Microsoft.EntityFrameworkCore.Migrations;
namespace Jellyfin.Server.Implementations.Migrations
{
public partial class AddCustomDisplayPreferences : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AlterColumn<int>(
name: "MaxActiveSessions",
schema: "jellyfin",
table: "Users",
type: "INTEGER",
nullable: false,
defaultValue: 0,
oldClrType: typeof(int),
oldType: "INTEGER",
oldNullable: true);
migrationBuilder.CreateTable(
name: "CustomItemDisplayPreferences",
schema: "jellyfin",
columns: table => new
{
Id = table.Column<int>(type: "INTEGER", nullable: false)
.Annotation("Sqlite:Autoincrement", true),
UserId = table.Column<Guid>(type: "TEXT", nullable: false),
Client = table.Column<string>(type: "TEXT", maxLength: 32, nullable: false),
Key = table.Column<string>(type: "TEXT", nullable: false),
Value = table.Column<string>(type: "TEXT", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_CustomItemDisplayPreferences", x => x.Id);
});
migrationBuilder.CreateIndex(
name: "IX_CustomItemDisplayPreferences_UserId",
schema: "jellyfin",
table: "CustomItemDisplayPreferences",
column: "UserId");
migrationBuilder.CreateIndex(
name: "IX_CustomItemDisplayPreferences_UserId_Client_Key",
schema: "jellyfin",
table: "CustomItemDisplayPreferences",
columns: new[] { "UserId", "Client", "Key" },
unique: true);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "CustomItemDisplayPreferences",
schema: "jellyfin");
migrationBuilder.AlterColumn<int>(
name: "MaxActiveSessions",
schema: "jellyfin",
table: "Users",
type: "INTEGER",
nullable: true,
oldClrType: typeof(int),
oldType: "INTEGER");
}
}
}

@ -15,7 +15,7 @@ namespace Jellyfin.Server.Implementations.Migrations
#pragma warning disable 612, 618
modelBuilder
.HasDefaultSchema("jellyfin")
.HasAnnotation("ProductVersion", "3.1.8");
.HasAnnotation("ProductVersion", "5.0.0");
modelBuilder.Entity("Jellyfin.Data.Entities.AccessSchedule", b =>
{
@ -52,33 +52,33 @@ namespace Jellyfin.Server.Implementations.Migrations
.HasColumnType("TEXT");
b.Property<string>("ItemId")
.HasColumnType("TEXT")
.HasMaxLength(256);
.HasMaxLength(256)
.HasColumnType("TEXT");
b.Property<int>("LogSeverity")
.HasColumnType("INTEGER");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("TEXT")
.HasMaxLength(512);
.HasMaxLength(512)
.HasColumnType("TEXT");
b.Property<string>("Overview")
.HasColumnType("TEXT")
.HasMaxLength(512);
.HasMaxLength(512)
.HasColumnType("TEXT");
b.Property<uint>("RowVersion")
.IsConcurrencyToken()
.HasColumnType("INTEGER");
b.Property<string>("ShortOverview")
.HasColumnType("TEXT")
.HasMaxLength(512);
.HasMaxLength(512)
.HasColumnType("TEXT");
b.Property<string>("Type")
.IsRequired()
.HasColumnType("TEXT")
.HasMaxLength(256);
.HasMaxLength(256)
.HasColumnType("TEXT");
b.Property<Guid>("UserId")
.HasColumnType("TEXT");
@ -88,6 +88,38 @@ namespace Jellyfin.Server.Implementations.Migrations
b.ToTable("ActivityLogs");
});
modelBuilder.Entity("Jellyfin.Data.Entities.CustomItemDisplayPreferences", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("Client")
.IsRequired()
.HasMaxLength(32)
.HasColumnType("TEXT");
b.Property<string>("Key")
.IsRequired()
.HasColumnType("TEXT");
b.Property<Guid>("UserId")
.HasColumnType("TEXT");
b.Property<string>("Value")
.IsRequired()
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("UserId");
b.HasIndex("UserId", "Client", "Key")
.IsUnique();
b.ToTable("CustomItemDisplayPreferences");
});
modelBuilder.Entity("Jellyfin.Data.Entities.DisplayPreferences", b =>
{
b.Property<int>("Id")
@ -99,12 +131,12 @@ namespace Jellyfin.Server.Implementations.Migrations
b.Property<string>("Client")
.IsRequired()
.HasColumnType("TEXT")
.HasMaxLength(32);
.HasMaxLength(32)
.HasColumnType("TEXT");
b.Property<string>("DashboardTheme")
.HasColumnType("TEXT")
.HasMaxLength(32);
.HasMaxLength(32)
.HasColumnType("TEXT");
b.Property<bool>("EnableNextVideoInfoOverlay")
.HasColumnType("INTEGER");
@ -128,8 +160,8 @@ namespace Jellyfin.Server.Implementations.Migrations
.HasColumnType("INTEGER");
b.Property<string>("TvHome")
.HasColumnType("TEXT")
.HasMaxLength(32);
.HasMaxLength(32)
.HasColumnType("TEXT");
b.Property<Guid>("UserId")
.HasColumnType("TEXT");
@ -177,8 +209,8 @@ namespace Jellyfin.Server.Implementations.Migrations
b.Property<string>("Path")
.IsRequired()
.HasColumnType("TEXT")
.HasMaxLength(512);
.HasMaxLength(512)
.HasColumnType("TEXT");
b.Property<Guid?>("UserId")
.HasColumnType("TEXT");
@ -199,8 +231,8 @@ namespace Jellyfin.Server.Implementations.Migrations
b.Property<string>("Client")
.IsRequired()
.HasColumnType("TEXT")
.HasMaxLength(32);
.HasMaxLength(32)
.HasColumnType("TEXT");
b.Property<int?>("IndexBy")
.HasColumnType("INTEGER");
@ -216,8 +248,8 @@ namespace Jellyfin.Server.Implementations.Migrations
b.Property<string>("SortBy")
.IsRequired()
.HasColumnType("TEXT")
.HasMaxLength(64);
.HasMaxLength(64)
.HasColumnType("TEXT");
b.Property<int>("SortOrder")
.HasColumnType("INTEGER");
@ -279,8 +311,8 @@ namespace Jellyfin.Server.Implementations.Migrations
b.Property<string>("Value")
.IsRequired()
.HasColumnType("TEXT")
.HasMaxLength(65535);
.HasMaxLength(65535)
.HasColumnType("TEXT");
b.HasKey("Id");
@ -296,13 +328,13 @@ namespace Jellyfin.Server.Implementations.Migrations
.HasColumnType("TEXT");
b.Property<string>("AudioLanguagePreference")
.HasColumnType("TEXT")
.HasMaxLength(255);
.HasMaxLength(255)
.HasColumnType("TEXT");
b.Property<string>("AuthenticationProviderId")
.IsRequired()
.HasColumnType("TEXT")
.HasMaxLength(255);
.HasMaxLength(255)
.HasColumnType("TEXT");
b.Property<bool>("DisplayCollectionsView")
.HasColumnType("INTEGER");
@ -311,8 +343,8 @@ namespace Jellyfin.Server.Implementations.Migrations
.HasColumnType("INTEGER");
b.Property<string>("EasyPassword")
.HasColumnType("TEXT")
.HasMaxLength(65535);
.HasMaxLength(65535)
.HasColumnType("TEXT");
b.Property<bool>("EnableAutoLogin")
.HasColumnType("INTEGER");
@ -354,13 +386,13 @@ namespace Jellyfin.Server.Implementations.Migrations
.HasColumnType("INTEGER");
b.Property<string>("Password")
.HasColumnType("TEXT")
.HasMaxLength(65535);
.HasMaxLength(65535)
.HasColumnType("TEXT");
b.Property<string>("PasswordResetProviderId")
.IsRequired()
.HasColumnType("TEXT")
.HasMaxLength(255);
.HasMaxLength(255)
.HasColumnType("TEXT");
b.Property<bool>("PlayDefaultAudioTrack")
.HasColumnType("INTEGER");
@ -379,8 +411,8 @@ namespace Jellyfin.Server.Implementations.Migrations
.HasColumnType("INTEGER");
b.Property<string>("SubtitleLanguagePreference")
.HasColumnType("TEXT")
.HasMaxLength(255);
.HasMaxLength(255)
.HasColumnType("TEXT");
b.Property<int>("SubtitleMode")
.HasColumnType("INTEGER");
@ -390,8 +422,8 @@ namespace Jellyfin.Server.Implementations.Migrations
b.Property<string>("Username")
.IsRequired()
.HasColumnType("TEXT")
.HasMaxLength(255);
.HasMaxLength(255)
.HasColumnType("TEXT");
b.HasKey("Id");
@ -454,6 +486,27 @@ namespace Jellyfin.Server.Implementations.Migrations
.WithMany("Preferences")
.HasForeignKey("Preference_Preferences_Guid");
});
modelBuilder.Entity("Jellyfin.Data.Entities.DisplayPreferences", b =>
{
b.Navigation("HomeSections");
});
modelBuilder.Entity("Jellyfin.Data.Entities.User", b =>
{
b.Navigation("AccessSchedules");
b.Navigation("DisplayPreferences")
.IsRequired();
b.Navigation("ItemDisplayPreferences");
b.Navigation("Permissions");
b.Navigation("Preferences");
b.Navigation("ProfileImage");
});
#pragma warning restore 612, 618
}
}

@ -66,6 +66,30 @@ namespace Jellyfin.Server.Implementations.Users
.ToList();
}
/// <inheritdoc />
public IDictionary<string, string> ListCustomItemDisplayPreferences(Guid userId, string client)
{
return _dbContext.CustomItemDisplayPreferences
.AsQueryable()
.Where(prefs => prefs.UserId == userId && string.Equals(prefs.Client, client))
.ToDictionary(prefs => prefs.Key, prefs => prefs.Value);
}
/// <inheritdoc />
public void SetCustomItemDisplayPreferences(Guid userId, string client, Dictionary<string, string> customPreferences)
{
var existingPrefs = _dbContext.CustomItemDisplayPreferences
.AsQueryable()
.Where(prefs => prefs.UserId == userId && string.Equals(prefs.Client, client));
_dbContext.CustomItemDisplayPreferences.RemoveRange(existingPrefs);
foreach (var (key, value) in customPreferences)
{
_dbContext.CustomItemDisplayPreferences
.Add(new CustomItemDisplayPreferences(userId, client, key, value));
}
}
/// <inheritdoc />
public void SaveChanges()
{

@ -40,6 +40,22 @@ namespace MediaBrowser.Controller
/// <returns>A list of item display preferences.</returns>
IList<ItemDisplayPreferences> ListItemDisplayPreferences(Guid userId, string client);
/// <summary>
/// Gets all of the custom item display preferences for the user and client.
/// </summary>
/// <param name="userId">The user id.</param>
/// <param name="client">The client string.</param>
/// <returns>The dictionary of custom item display preferences.</returns>
IDictionary<string, string> ListCustomItemDisplayPreferences(Guid userId, string client);
/// <summary>
/// Sets the custom item display preference for the user and client.
/// </summary>
/// <param name="userId">The user id.</param>
/// <param name="client">The client id.</param>
/// <param name="customPreferences">A dictionary of custom item display preferences.</param>
void SetCustomItemDisplayPreferences(Guid userId, string client, Dictionary<string, string> customPreferences);
/// <summary>
/// Saves changes made to the database.
/// </summary>

Loading…
Cancel
Save