Merge branch 'master' into tonemap

This commit is contained in:
Nyanmisaka 2020-08-03 14:58:44 +08:00 committed by GitHub
commit c23d991c95
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
78 changed files with 1927 additions and 608 deletions

View File

@ -80,7 +80,15 @@ jobs:
pool: pool:
vmImage: 'ubuntu-latest' vmImage: 'ubuntu-latest'
variables:
- name: JellyfinVersion
value: 0.0.0
steps: steps:
- script: echo "##vso[task.setvariable variable=JellyfinVersion]$( awk -F '/' '{ print $NF }' <<<'$(Build.SourceBranch)' | sed 's/^v//' )"
displayName: Set release version (stable)
condition: startsWith(variables['Build.SourceBranch'], 'refs/tags')
- task: Docker@2 - task: Docker@2
displayName: 'Push Unstable Image' displayName: 'Push Unstable Image'
condition: startsWith(variables['Build.SourceBranch'], 'refs/heads/master') condition: startsWith(variables['Build.SourceBranch'], 'refs/heads/master')
@ -105,7 +113,7 @@ jobs:
containerRegistry: Docker Hub containerRegistry: Docker Hub
tags: | tags: |
stable-$(Build.BuildNumber)-$(BuildConfiguration) stable-$(Build.BuildNumber)-$(BuildConfiguration)
stable-$(BuildConfiguration) $(JellyfinVersion)-$(BuildConfiguration)
- job: CollectArtifacts - job: CollectArtifacts
displayName: 'Collect Artifacts' displayName: 'Collect Artifacts'

View File

@ -11,6 +11,7 @@ using System.Xml;
using Emby.Dlna.Didl; using Emby.Dlna.Didl;
using Emby.Dlna.Service; using Emby.Dlna.Service;
using Jellyfin.Data.Entities; using Jellyfin.Data.Entities;
using Jellyfin.Data.Enums;
using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Drawing; using MediaBrowser.Controller.Drawing;

View File

@ -122,15 +122,15 @@ namespace Emby.Dlna
var builder = new StringBuilder(); var builder = new StringBuilder();
builder.AppendLine("No matching device profile found. The default will need to be used."); builder.AppendLine("No matching device profile found. The default will need to be used.");
builder.AppendLine(string.Format("DeviceDescription:{0}", profile.DeviceDescription ?? string.Empty)); builder.AppendFormat(CultureInfo.InvariantCulture, "DeviceDescription:{0}", profile.DeviceDescription ?? string.Empty).AppendLine();
builder.AppendLine(string.Format("FriendlyName:{0}", profile.FriendlyName ?? string.Empty)); builder.AppendFormat(CultureInfo.InvariantCulture, "FriendlyName:{0}", profile.FriendlyName ?? string.Empty).AppendLine();
builder.AppendLine(string.Format("Manufacturer:{0}", profile.Manufacturer ?? string.Empty)); builder.AppendFormat(CultureInfo.InvariantCulture, "Manufacturer:{0}", profile.Manufacturer ?? string.Empty).AppendLine();
builder.AppendLine(string.Format("ManufacturerUrl:{0}", profile.ManufacturerUrl ?? string.Empty)); builder.AppendFormat(CultureInfo.InvariantCulture, "ManufacturerUrl:{0}", profile.ManufacturerUrl ?? string.Empty).AppendLine();
builder.AppendLine(string.Format("ModelDescription:{0}", profile.ModelDescription ?? string.Empty)); builder.AppendFormat(CultureInfo.InvariantCulture, "ModelDescription:{0}", profile.ModelDescription ?? string.Empty).AppendLine();
builder.AppendLine(string.Format("ModelName:{0}", profile.ModelName ?? string.Empty)); builder.AppendFormat(CultureInfo.InvariantCulture, "ModelName:{0}", profile.ModelName ?? string.Empty).AppendLine();
builder.AppendLine(string.Format("ModelNumber:{0}", profile.ModelNumber ?? string.Empty)); builder.AppendFormat(CultureInfo.InvariantCulture, "ModelNumber:{0}", profile.ModelNumber ?? string.Empty).AppendLine();
builder.AppendLine(string.Format("ModelUrl:{0}", profile.ModelUrl ?? string.Empty)); builder.AppendFormat(CultureInfo.InvariantCulture, "ModelUrl:{0}", profile.ModelUrl ?? string.Empty).AppendLine();
builder.AppendLine(string.Format("SerialNumber:{0}", profile.SerialNumber ?? string.Empty)); builder.AppendFormat(CultureInfo.InvariantCulture, "SerialNumber:{0}", profile.SerialNumber ?? string.Empty).AppendLine();
_logger.LogInformation(builder.ToString()); _logger.LogInformation(builder.ToString());
} }

View File

@ -554,8 +554,6 @@ namespace Emby.Server.Implementations
serviceCollection.AddSingleton<IUserDataRepository, SqliteUserDataRepository>(); serviceCollection.AddSingleton<IUserDataRepository, SqliteUserDataRepository>();
serviceCollection.AddSingleton<IUserDataManager, UserDataManager>(); serviceCollection.AddSingleton<IUserDataManager, UserDataManager>();
serviceCollection.AddSingleton<IDisplayPreferencesRepository, SqliteDisplayPreferencesRepository>();
serviceCollection.AddSingleton<IItemRepository, SqliteItemRepository>(); serviceCollection.AddSingleton<IItemRepository, SqliteItemRepository>();
serviceCollection.AddSingleton<IAuthenticationRepository, AuthenticationRepository>(); serviceCollection.AddSingleton<IAuthenticationRepository, AuthenticationRepository>();
@ -650,7 +648,6 @@ namespace Emby.Server.Implementations
_httpServer = Resolve<IHttpServer>(); _httpServer = Resolve<IHttpServer>();
_httpClient = Resolve<IHttpClient>(); _httpClient = Resolve<IHttpClient>();
((SqliteDisplayPreferencesRepository)Resolve<IDisplayPreferencesRepository>()).Initialize();
((AuthenticationRepository)Resolve<IAuthenticationRepository>()).Initialize(); ((AuthenticationRepository)Resolve<IAuthenticationRepository>()).Initialize();
SetStaticProperties(); SetStaticProperties();

View File

@ -1,5 +1,4 @@
using System; using System;
using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.IO; using System.IO;
@ -7,6 +6,7 @@ using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Jellyfin.Data.Entities; using Jellyfin.Data.Entities;
using Jellyfin.Data.Enums;
using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Extensions;
using MediaBrowser.Common.Progress; using MediaBrowser.Common.Progress;
using MediaBrowser.Controller.Channels; using MediaBrowser.Controller.Channels;
@ -22,6 +22,7 @@ using MediaBrowser.Model.Entities;
using MediaBrowser.Model.IO; using MediaBrowser.Model.IO;
using MediaBrowser.Model.Querying; using MediaBrowser.Model.Querying;
using MediaBrowser.Model.Serialization; using MediaBrowser.Model.Serialization;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Episode = MediaBrowser.Controller.Entities.TV.Episode; using Episode = MediaBrowser.Controller.Entities.TV.Episode;
using Movie = MediaBrowser.Controller.Entities.Movies.Movie; using Movie = MediaBrowser.Controller.Entities.Movies.Movie;
@ -45,10 +46,7 @@ namespace Emby.Server.Implementations.Channels
private readonly IFileSystem _fileSystem; private readonly IFileSystem _fileSystem;
private readonly IJsonSerializer _jsonSerializer; private readonly IJsonSerializer _jsonSerializer;
private readonly IProviderManager _providerManager; private readonly IProviderManager _providerManager;
private readonly IMemoryCache _memoryCache;
private readonly ConcurrentDictionary<string, Tuple<DateTime, List<MediaSourceInfo>>> _channelItemMediaInfo =
new ConcurrentDictionary<string, Tuple<DateTime, List<MediaSourceInfo>>>();
private readonly SemaphoreSlim _resourcePool = new SemaphoreSlim(1, 1); private readonly SemaphoreSlim _resourcePool = new SemaphoreSlim(1, 1);
/// <summary> /// <summary>
@ -63,6 +61,7 @@ namespace Emby.Server.Implementations.Channels
/// <param name="userDataManager">The user data manager.</param> /// <param name="userDataManager">The user data manager.</param>
/// <param name="jsonSerializer">The JSON serializer.</param> /// <param name="jsonSerializer">The JSON serializer.</param>
/// <param name="providerManager">The provider manager.</param> /// <param name="providerManager">The provider manager.</param>
/// <param name="memoryCache">The memory cache.</param>
public ChannelManager( public ChannelManager(
IUserManager userManager, IUserManager userManager,
IDtoService dtoService, IDtoService dtoService,
@ -72,7 +71,8 @@ namespace Emby.Server.Implementations.Channels
IFileSystem fileSystem, IFileSystem fileSystem,
IUserDataManager userDataManager, IUserDataManager userDataManager,
IJsonSerializer jsonSerializer, IJsonSerializer jsonSerializer,
IProviderManager providerManager) IProviderManager providerManager,
IMemoryCache memoryCache)
{ {
_userManager = userManager; _userManager = userManager;
_dtoService = dtoService; _dtoService = dtoService;
@ -83,6 +83,7 @@ namespace Emby.Server.Implementations.Channels
_userDataManager = userDataManager; _userDataManager = userDataManager;
_jsonSerializer = jsonSerializer; _jsonSerializer = jsonSerializer;
_providerManager = providerManager; _providerManager = providerManager;
_memoryCache = memoryCache;
} }
internal IChannel[] Channels { get; private set; } internal IChannel[] Channels { get; private set; }
@ -417,20 +418,15 @@ namespace Emby.Server.Implementations.Channels
private async Task<IEnumerable<MediaSourceInfo>> GetChannelItemMediaSourcesInternal(IRequiresMediaInfoCallback channel, string id, CancellationToken cancellationToken) private async Task<IEnumerable<MediaSourceInfo>> GetChannelItemMediaSourcesInternal(IRequiresMediaInfoCallback channel, string id, CancellationToken cancellationToken)
{ {
if (_channelItemMediaInfo.TryGetValue(id, out Tuple<DateTime, List<MediaSourceInfo>> cachedInfo)) if (_memoryCache.TryGetValue(id, out List<MediaSourceInfo> cachedInfo))
{ {
if ((DateTime.UtcNow - cachedInfo.Item1).TotalMinutes < 5) return cachedInfo;
{
return cachedInfo.Item2;
}
} }
var mediaInfo = await channel.GetChannelItemMediaInfo(id, cancellationToken) var mediaInfo = await channel.GetChannelItemMediaInfo(id, cancellationToken)
.ConfigureAwait(false); .ConfigureAwait(false);
var list = mediaInfo.ToList(); var list = mediaInfo.ToList();
_memoryCache.CreateEntry(id).SetValue(list).SetAbsoluteExpiration(DateTimeOffset.UtcNow.AddMinutes(5));
var item2 = new Tuple<DateTime, List<MediaSourceInfo>>(DateTime.UtcNow, list);
_channelItemMediaInfo.AddOrUpdate(id, item2, (key, oldValue) => item2);
return list; return list;
} }

View File

@ -1,225 +0,0 @@
#pragma warning disable CS1591
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Text.Json;
using System.Threading;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Common.Json;
using MediaBrowser.Controller.Persistence;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.IO;
using Microsoft.Extensions.Logging;
using SQLitePCL.pretty;
namespace Emby.Server.Implementations.Data
{
/// <summary>
/// Class SQLiteDisplayPreferencesRepository.
/// </summary>
public class SqliteDisplayPreferencesRepository : BaseSqliteRepository, IDisplayPreferencesRepository
{
private readonly IFileSystem _fileSystem;
private readonly JsonSerializerOptions _jsonOptions;
public SqliteDisplayPreferencesRepository(ILogger<SqliteDisplayPreferencesRepository> logger, IApplicationPaths appPaths, IFileSystem fileSystem)
: base(logger)
{
_fileSystem = fileSystem;
_jsonOptions = JsonDefaults.GetOptions();
DbFilePath = Path.Combine(appPaths.DataPath, "displaypreferences.db");
}
/// <summary>
/// Gets the name of the repository.
/// </summary>
/// <value>The name.</value>
public string Name => "SQLite";
public void Initialize()
{
try
{
InitializeInternal();
}
catch (Exception ex)
{
Logger.LogError(ex, "Error loading database file. Will reset and retry.");
_fileSystem.DeleteFile(DbFilePath);
InitializeInternal();
}
}
/// <summary>
/// Opens the connection to the database.
/// </summary>
/// <returns>Task.</returns>
private void InitializeInternal()
{
string[] queries =
{
"create table if not exists userdisplaypreferences (id GUID NOT NULL, userId GUID NOT NULL, client text NOT NULL, data BLOB NOT NULL)",
"create unique index if not exists userdisplaypreferencesindex on userdisplaypreferences (id, userId, client)"
};
using (var connection = GetConnection())
{
connection.RunQueries(queries);
}
}
/// <summary>
/// Save the display preferences associated with an item in the repo.
/// </summary>
/// <param name="displayPreferences">The display preferences.</param>
/// <param name="userId">The user id.</param>
/// <param name="client">The client.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <exception cref="ArgumentNullException">item</exception>
public void SaveDisplayPreferences(DisplayPreferences displayPreferences, Guid userId, string client, CancellationToken cancellationToken)
{
if (displayPreferences == null)
{
throw new ArgumentNullException(nameof(displayPreferences));
}
if (string.IsNullOrEmpty(displayPreferences.Id))
{
throw new ArgumentException("Display preferences has an invalid Id", nameof(displayPreferences));
}
cancellationToken.ThrowIfCancellationRequested();
using (var connection = GetConnection())
{
connection.RunInTransaction(
db => SaveDisplayPreferences(displayPreferences, userId, client, db),
TransactionMode);
}
}
private void SaveDisplayPreferences(DisplayPreferences displayPreferences, Guid userId, string client, IDatabaseConnection connection)
{
var serialized = JsonSerializer.SerializeToUtf8Bytes(displayPreferences, _jsonOptions);
using (var statement = connection.PrepareStatement("replace into userdisplaypreferences (id, userid, client, data) values (@id, @userId, @client, @data)"))
{
statement.TryBind("@id", new Guid(displayPreferences.Id).ToByteArray());
statement.TryBind("@userId", userId.ToByteArray());
statement.TryBind("@client", client);
statement.TryBind("@data", serialized);
statement.MoveNext();
}
}
/// <summary>
/// Save all display preferences associated with a user in the repo.
/// </summary>
/// <param name="displayPreferences">The display preferences.</param>
/// <param name="userId">The user id.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <exception cref="ArgumentNullException">item</exception>
public void SaveAllDisplayPreferences(IEnumerable<DisplayPreferences> displayPreferences, Guid userId, CancellationToken cancellationToken)
{
if (displayPreferences == null)
{
throw new ArgumentNullException(nameof(displayPreferences));
}
cancellationToken.ThrowIfCancellationRequested();
using (var connection = GetConnection())
{
connection.RunInTransaction(
db =>
{
foreach (var displayPreference in displayPreferences)
{
SaveDisplayPreferences(displayPreference, userId, displayPreference.Client, db);
}
},
TransactionMode);
}
}
/// <summary>
/// Gets the display preferences.
/// </summary>
/// <param name="displayPreferencesId">The display preferences id.</param>
/// <param name="userId">The user id.</param>
/// <param name="client">The client.</param>
/// <returns>Task{DisplayPreferences}.</returns>
/// <exception cref="ArgumentNullException">item</exception>
public DisplayPreferences GetDisplayPreferences(string displayPreferencesId, Guid userId, string client)
{
if (string.IsNullOrEmpty(displayPreferencesId))
{
throw new ArgumentNullException(nameof(displayPreferencesId));
}
var guidId = displayPreferencesId.GetMD5();
using (var connection = GetConnection(true))
{
using (var statement = connection.PrepareStatement("select data from userdisplaypreferences where id = @id and userId=@userId and client=@client"))
{
statement.TryBind("@id", guidId.ToByteArray());
statement.TryBind("@userId", userId.ToByteArray());
statement.TryBind("@client", client);
foreach (var row in statement.ExecuteQuery())
{
return Get(row);
}
}
}
return new DisplayPreferences
{
Id = guidId.ToString("N", CultureInfo.InvariantCulture)
};
}
/// <summary>
/// Gets all display preferences for the given user.
/// </summary>
/// <param name="userId">The user id.</param>
/// <returns>Task{DisplayPreferences}.</returns>
/// <exception cref="ArgumentNullException">item</exception>
public IEnumerable<DisplayPreferences> GetAllDisplayPreferences(Guid userId)
{
var list = new List<DisplayPreferences>();
using (var connection = GetConnection(true))
using (var statement = connection.PrepareStatement("select data from userdisplaypreferences where userId=@userId"))
{
statement.TryBind("@userId", userId.ToByteArray());
foreach (var row in statement.ExecuteQuery())
{
list.Add(Get(row));
}
}
return list;
}
private DisplayPreferences Get(IReadOnlyList<IResultSetValue> row)
=> JsonSerializer.Deserialize<DisplayPreferences>(row[0].ToBlob(), _jsonOptions);
public void SaveDisplayPreferences(DisplayPreferences displayPreferences, string userId, string client, CancellationToken cancellationToken)
=> SaveDisplayPreferences(displayPreferences, new Guid(userId), client, cancellationToken);
public DisplayPreferences GetDisplayPreferences(string displayPreferencesId, string userId, string client)
=> GetDisplayPreferences(displayPreferencesId, new Guid(userId), client);
}
}

View File

@ -9,6 +9,7 @@ using System.Text;
using System.Text.Json; using System.Text.Json;
using System.Threading; using System.Threading;
using Emby.Server.Implementations.Playlists; using Emby.Server.Implementations.Playlists;
using Jellyfin.Data.Enums;
using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Extensions;
using MediaBrowser.Common.Json; using MediaBrowser.Common.Json;
using MediaBrowser.Controller; using MediaBrowser.Controller;
@ -400,6 +401,8 @@ namespace Emby.Server.Implementations.Data
"OwnerId" "OwnerId"
}; };
private static readonly string _retriveItemColumnsSelectQuery = $"select {string.Join(',', _retriveItemColumns)} from TypedBaseItems where guid = @guid";
private static readonly string[] _mediaStreamSaveColumns = private static readonly string[] _mediaStreamSaveColumns =
{ {
"ItemId", "ItemId",
@ -439,6 +442,12 @@ namespace Emby.Server.Implementations.Data
"ColorTransfer" "ColorTransfer"
}; };
private static readonly string _mediaStreamSaveColumnsInsertQuery =
$"insert into mediastreams ({string.Join(',', _mediaStreamSaveColumns)}) values ";
private static readonly string _mediaStreamSaveColumnsSelectQuery =
$"select {string.Join(',', _mediaStreamSaveColumns)} from mediastreams where ItemId=@ItemId";
private static readonly string[] _mediaAttachmentSaveColumns = private static readonly string[] _mediaAttachmentSaveColumns =
{ {
"ItemId", "ItemId",
@ -450,102 +459,15 @@ namespace Emby.Server.Implementations.Data
"MIMEType" "MIMEType"
}; };
private static readonly string _mediaAttachmentSaveColumnsSelectQuery =
$"select {string.Join(',', _mediaAttachmentSaveColumns)} from mediaattachments where ItemId=@ItemId";
private static readonly string _mediaAttachmentInsertPrefix; private static readonly string _mediaAttachmentInsertPrefix;
private static string GetSaveItemCommandText() private const string SaveItemCommandText =
{ @"replace into TypedBaseItems
var saveColumns = new[] (guid,type,data,Path,StartDate,EndDate,ChannelId,IsMovie,IsSeries,EpisodeTitle,IsRepeat,CommunityRating,CustomRating,IndexNumber,IsLocked,Name,OfficialRating,MediaType,Overview,ParentIndexNumber,PremiereDate,ProductionYear,ParentId,Genres,InheritedParentalRatingValue,SortName,ForcedSortName,RunTimeTicks,Size,DateCreated,DateModified,PreferredMetadataLanguage,PreferredMetadataCountryCode,Width,Height,DateLastRefreshed,DateLastSaved,IsInMixedFolder,LockedFields,Studios,Audio,ExternalServiceId,Tags,IsFolder,UnratedType,TopParentId,TrailerTypes,CriticRating,CleanName,PresentationUniqueKey,OriginalTitle,PrimaryVersionId,DateLastMediaAdded,Album,IsVirtualItem,SeriesName,UserDataKey,SeasonName,SeasonId,SeriesId,ExternalSeriesId,Tagline,ProviderIds,Images,ProductionLocations,ExtraIds,TotalBitrate,ExtraType,Artists,AlbumArtists,ExternalId,SeriesPresentationUniqueKey,ShowId,OwnerId)
{ values (@guid,@type,@data,@Path,@StartDate,@EndDate,@ChannelId,@IsMovie,@IsSeries,@EpisodeTitle,@IsRepeat,@CommunityRating,@CustomRating,@IndexNumber,@IsLocked,@Name,@OfficialRating,@MediaType,@Overview,@ParentIndexNumber,@PremiereDate,@ProductionYear,@ParentId,@Genres,@InheritedParentalRatingValue,@SortName,@ForcedSortName,@RunTimeTicks,@Size,@DateCreated,@DateModified,@PreferredMetadataLanguage,@PreferredMetadataCountryCode,@Width,@Height,@DateLastRefreshed,@DateLastSaved,@IsInMixedFolder,@LockedFields,@Studios,@Audio,@ExternalServiceId,@Tags,@IsFolder,@UnratedType,@TopParentId,@TrailerTypes,@CriticRating,@CleanName,@PresentationUniqueKey,@OriginalTitle,@PrimaryVersionId,@DateLastMediaAdded,@Album,@IsVirtualItem,@SeriesName,@UserDataKey,@SeasonName,@SeasonId,@SeriesId,@ExternalSeriesId,@Tagline,@ProviderIds,@Images,@ProductionLocations,@ExtraIds,@TotalBitrate,@ExtraType,@Artists,@AlbumArtists,@ExternalId,@SeriesPresentationUniqueKey,@ShowId,@OwnerId)";
"guid",
"type",
"data",
"Path",
"StartDate",
"EndDate",
"ChannelId",
"IsMovie",
"IsSeries",
"EpisodeTitle",
"IsRepeat",
"CommunityRating",
"CustomRating",
"IndexNumber",
"IsLocked",
"Name",
"OfficialRating",
"MediaType",
"Overview",
"ParentIndexNumber",
"PremiereDate",
"ProductionYear",
"ParentId",
"Genres",
"InheritedParentalRatingValue",
"SortName",
"ForcedSortName",
"RunTimeTicks",
"Size",
"DateCreated",
"DateModified",
"PreferredMetadataLanguage",
"PreferredMetadataCountryCode",
"Width",
"Height",
"DateLastRefreshed",
"DateLastSaved",
"IsInMixedFolder",
"LockedFields",
"Studios",
"Audio",
"ExternalServiceId",
"Tags",
"IsFolder",
"UnratedType",
"TopParentId",
"TrailerTypes",
"CriticRating",
"CleanName",
"PresentationUniqueKey",
"OriginalTitle",
"PrimaryVersionId",
"DateLastMediaAdded",
"Album",
"IsVirtualItem",
"SeriesName",
"UserDataKey",
"SeasonName",
"SeasonId",
"SeriesId",
"ExternalSeriesId",
"Tagline",
"ProviderIds",
"Images",
"ProductionLocations",
"ExtraIds",
"TotalBitrate",
"ExtraType",
"Artists",
"AlbumArtists",
"ExternalId",
"SeriesPresentationUniqueKey",
"ShowId",
"OwnerId"
};
var saveItemCommandCommandText = "replace into TypedBaseItems (" + string.Join(",", saveColumns) + ") values (";
for (var i = 0; i < saveColumns.Length; i++)
{
if (i != 0)
{
saveItemCommandCommandText += ",";
}
saveItemCommandCommandText += "@" + saveColumns[i];
}
return saveItemCommandCommandText + ")";
}
/// <summary> /// <summary>
/// Save a standard item in the repo. /// Save a standard item in the repo.
@ -636,7 +558,7 @@ namespace Emby.Server.Implementations.Data
{ {
var statements = PrepareAll(db, new string[] var statements = PrepareAll(db, new string[]
{ {
GetSaveItemCommandText(), SaveItemCommandText,
"delete from AncestorIds where ItemId=@ItemId" "delete from AncestorIds where ItemId=@ItemId"
}).ToList(); }).ToList();
@ -1226,7 +1148,7 @@ namespace Emby.Server.Implementations.Data
using (var connection = GetConnection(true)) using (var connection = GetConnection(true))
{ {
using (var statement = PrepareStatement(connection, "select " + string.Join(",", _retriveItemColumns) + " from TypedBaseItems where guid = @guid")) using (var statement = PrepareStatement(connection, _retriveItemColumnsSelectQuery))
{ {
statement.TryBind("@guid", id); statement.TryBind("@guid", id);
@ -5894,10 +5816,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
throw new ArgumentNullException(nameof(query)); throw new ArgumentNullException(nameof(query));
} }
var cmdText = "select " var cmdText = _mediaStreamSaveColumnsSelectQuery;
+ string.Join(",", _mediaStreamSaveColumns)
+ " from mediastreams where"
+ " ItemId=@ItemId";
if (query.Type.HasValue) if (query.Type.HasValue)
{ {
@ -5976,15 +5895,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
while (startIndex < streams.Count) while (startIndex < streams.Count)
{ {
var insertText = new StringBuilder("insert into mediastreams ("); var insertText = new StringBuilder(_mediaStreamSaveColumnsInsertQuery);
foreach (var column in _mediaStreamSaveColumns)
{
insertText.Append(column).Append(',');
}
// Remove last comma
insertText.Length--;
insertText.Append(") values ");
var endIndex = Math.Min(streams.Count, startIndex + Limit); var endIndex = Math.Min(streams.Count, startIndex + Limit);
@ -6251,10 +6162,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
throw new ArgumentNullException(nameof(query)); throw new ArgumentNullException(nameof(query));
} }
var cmdText = "select " var cmdText = _mediaAttachmentSaveColumnsSelectQuery;
+ string.Join(",", _mediaAttachmentSaveColumns)
+ " from mediaattachments where"
+ " ItemId=@ItemId";
if (query.Index.HasValue) if (query.Index.HasValue)
{ {

View File

@ -5,8 +5,8 @@ using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using Jellyfin.Data.Enums;
using Jellyfin.Data.Entities; using Jellyfin.Data.Entities;
using Jellyfin.Data.Enums;
using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Devices; using MediaBrowser.Controller.Devices;
@ -17,16 +17,17 @@ using MediaBrowser.Model.Events;
using MediaBrowser.Model.Querying; using MediaBrowser.Model.Querying;
using MediaBrowser.Model.Serialization; using MediaBrowser.Model.Serialization;
using MediaBrowser.Model.Session; using MediaBrowser.Model.Session;
using Microsoft.Extensions.Caching.Memory;
namespace Emby.Server.Implementations.Devices namespace Emby.Server.Implementations.Devices
{ {
public class DeviceManager : IDeviceManager public class DeviceManager : IDeviceManager
{ {
private readonly IMemoryCache _memoryCache;
private readonly IJsonSerializer _json; private readonly IJsonSerializer _json;
private readonly IUserManager _userManager; private readonly IUserManager _userManager;
private readonly IServerConfigurationManager _config; private readonly IServerConfigurationManager _config;
private readonly IAuthenticationRepository _authRepo; private readonly IAuthenticationRepository _authRepo;
private readonly Dictionary<string, ClientCapabilities> _capabilitiesCache;
private readonly object _capabilitiesSyncLock = new object(); private readonly object _capabilitiesSyncLock = new object();
public event EventHandler<GenericEventArgs<Tuple<string, DeviceOptions>>> DeviceOptionsUpdated; public event EventHandler<GenericEventArgs<Tuple<string, DeviceOptions>>> DeviceOptionsUpdated;
@ -35,13 +36,14 @@ namespace Emby.Server.Implementations.Devices
IAuthenticationRepository authRepo, IAuthenticationRepository authRepo,
IJsonSerializer json, IJsonSerializer json,
IUserManager userManager, IUserManager userManager,
IServerConfigurationManager config) IServerConfigurationManager config,
IMemoryCache memoryCache)
{ {
_json = json; _json = json;
_userManager = userManager; _userManager = userManager;
_config = config; _config = config;
_memoryCache = memoryCache;
_authRepo = authRepo; _authRepo = authRepo;
_capabilitiesCache = new Dictionary<string, ClientCapabilities>(StringComparer.OrdinalIgnoreCase);
} }
public void SaveCapabilities(string deviceId, ClientCapabilities capabilities) public void SaveCapabilities(string deviceId, ClientCapabilities capabilities)
@ -51,8 +53,7 @@ namespace Emby.Server.Implementations.Devices
lock (_capabilitiesSyncLock) lock (_capabilitiesSyncLock)
{ {
_capabilitiesCache[deviceId] = capabilities; _memoryCache.CreateEntry(deviceId).SetValue(capabilities);
_json.SerializeToFile(capabilities, path); _json.SerializeToFile(capabilities, path);
} }
} }
@ -71,13 +72,13 @@ namespace Emby.Server.Implementations.Devices
public ClientCapabilities GetCapabilities(string id) public ClientCapabilities GetCapabilities(string id)
{ {
if (_memoryCache.TryGetValue(id, out ClientCapabilities result))
{
return result;
}
lock (_capabilitiesSyncLock) lock (_capabilitiesSyncLock)
{ {
if (_capabilitiesCache.TryGetValue(id, out var result))
{
return result;
}
var path = Path.Combine(GetDevicePath(id), "capabilities.json"); var path = Path.Combine(GetDevicePath(id), "capabilities.json");
try try
{ {

View File

@ -25,7 +25,7 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="IPNetwork2" Version="2.5.211" /> <PackageReference Include="IPNetwork2" Version="2.5.211" />
<PackageReference Include="Jellyfin.XmlTv" Version="10.6.0-pre1" /> <PackageReference Include="Jellyfin.XmlTv" Version="10.6.2" />
<PackageReference Include="Microsoft.AspNetCore.Hosting" Version="2.2.7" /> <PackageReference Include="Microsoft.AspNetCore.Hosting" Version="2.2.7" />
<PackageReference Include="Microsoft.AspNetCore.Hosting.Abstractions" Version="2.2.0" /> <PackageReference Include="Microsoft.AspNetCore.Hosting.Abstractions" Version="2.2.0" />
<PackageReference Include="Microsoft.AspNetCore.Hosting.Server.Abstractions" Version="2.2.0" /> <PackageReference Include="Microsoft.AspNetCore.Hosting.Server.Abstractions" Version="2.2.0" />
@ -54,7 +54,7 @@
<TargetFramework>netstandard2.1</TargetFramework> <TargetFramework>netstandard2.1</TargetFramework>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo> <GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<GenerateDocumentationFile>true</GenerateDocumentationFile> <GenerateDocumentationFile>true</GenerateDocumentationFile>
<TreatWarningsAsErrors Condition=" '$(Configuration)' == 'Release'" >true</TreatWarningsAsErrors> <TreatWarningsAsErrors Condition=" '$(Configuration)' == 'Release'">true</TreatWarningsAsErrors>
</PropertyGroup> </PropertyGroup>
<!-- Code Analyzers--> <!-- Code Analyzers-->

View File

@ -3,7 +3,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using Emby.Server.Implementations.Images; using Jellyfin.Data.Enums;
using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Configuration;
using MediaBrowser.Controller.Drawing; using MediaBrowser.Controller.Drawing;
using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Dto;

View File

@ -1,7 +1,7 @@
#pragma warning disable CS1591 #pragma warning disable CS1591
using System.Collections.Generic; using System.Collections.Generic;
using Emby.Server.Implementations.Images; using Jellyfin.Data.Enums;
using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Configuration;
using MediaBrowser.Controller.Drawing; using MediaBrowser.Controller.Drawing;
using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Dto;

View File

@ -1,6 +1,7 @@
#pragma warning disable CS1591 #pragma warning disable CS1591
using System.Collections.Generic; using System.Collections.Generic;
using Jellyfin.Data.Enums;
using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Configuration;
using MediaBrowser.Controller.Drawing; using MediaBrowser.Controller.Drawing;
using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Dto;

View File

@ -1,7 +1,6 @@
#pragma warning disable CS1591 #pragma warning disable CS1591
using System; using System;
using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.IO; using System.IO;
@ -46,11 +45,11 @@ using MediaBrowser.Model.Net;
using MediaBrowser.Model.Querying; using MediaBrowser.Model.Querying;
using MediaBrowser.Model.Tasks; using MediaBrowser.Model.Tasks;
using MediaBrowser.Providers.MediaInfo; using MediaBrowser.Providers.MediaInfo;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Episode = MediaBrowser.Controller.Entities.TV.Episode; using Episode = MediaBrowser.Controller.Entities.TV.Episode;
using Genre = MediaBrowser.Controller.Entities.Genre; using Genre = MediaBrowser.Controller.Entities.Genre;
using Person = MediaBrowser.Controller.Entities.Person; using Person = MediaBrowser.Controller.Entities.Person;
using SortOrder = MediaBrowser.Model.Entities.SortOrder;
using VideoResolver = Emby.Naming.Video.VideoResolver; using VideoResolver = Emby.Naming.Video.VideoResolver;
namespace Emby.Server.Implementations.Library namespace Emby.Server.Implementations.Library
@ -63,6 +62,7 @@ namespace Emby.Server.Implementations.Library
private const string ShortcutFileExtension = ".mblink"; private const string ShortcutFileExtension = ".mblink";
private readonly ILogger<LibraryManager> _logger; private readonly ILogger<LibraryManager> _logger;
private readonly IMemoryCache _memoryCache;
private readonly ITaskManager _taskManager; private readonly ITaskManager _taskManager;
private readonly IUserManager _userManager; private readonly IUserManager _userManager;
private readonly IUserDataManager _userDataRepository; private readonly IUserDataManager _userDataRepository;
@ -74,7 +74,6 @@ namespace Emby.Server.Implementations.Library
private readonly IMediaEncoder _mediaEncoder; private readonly IMediaEncoder _mediaEncoder;
private readonly IFileSystem _fileSystem; private readonly IFileSystem _fileSystem;
private readonly IItemRepository _itemRepository; private readonly IItemRepository _itemRepository;
private readonly ConcurrentDictionary<Guid, BaseItem> _libraryItemsCache;
private readonly IImageProcessor _imageProcessor; private readonly IImageProcessor _imageProcessor;
/// <summary> /// <summary>
@ -112,6 +111,7 @@ namespace Emby.Server.Implementations.Library
/// <param name="mediaEncoder">The media encoder.</param> /// <param name="mediaEncoder">The media encoder.</param>
/// <param name="itemRepository">The item repository.</param> /// <param name="itemRepository">The item repository.</param>
/// <param name="imageProcessor">The image processor.</param> /// <param name="imageProcessor">The image processor.</param>
/// <param name="memoryCache">The memory cache.</param>
public LibraryManager( public LibraryManager(
IServerApplicationHost appHost, IServerApplicationHost appHost,
ILogger<LibraryManager> logger, ILogger<LibraryManager> logger,
@ -125,7 +125,8 @@ namespace Emby.Server.Implementations.Library
Lazy<IUserViewManager> userviewManagerFactory, Lazy<IUserViewManager> userviewManagerFactory,
IMediaEncoder mediaEncoder, IMediaEncoder mediaEncoder,
IItemRepository itemRepository, IItemRepository itemRepository,
IImageProcessor imageProcessor) IImageProcessor imageProcessor,
IMemoryCache memoryCache)
{ {
_appHost = appHost; _appHost = appHost;
_logger = logger; _logger = logger;
@ -140,8 +141,7 @@ namespace Emby.Server.Implementations.Library
_mediaEncoder = mediaEncoder; _mediaEncoder = mediaEncoder;
_itemRepository = itemRepository; _itemRepository = itemRepository;
_imageProcessor = imageProcessor; _imageProcessor = imageProcessor;
_memoryCache = memoryCache;
_libraryItemsCache = new ConcurrentDictionary<Guid, BaseItem>();
_configurationManager.ConfigurationUpdated += ConfigurationUpdated; _configurationManager.ConfigurationUpdated += ConfigurationUpdated;
@ -299,7 +299,7 @@ namespace Emby.Server.Implementations.Library
} }
} }
_libraryItemsCache.AddOrUpdate(item.Id, item, delegate { return item; }); _memoryCache.CreateEntry(item.Id).SetValue(item);
} }
public void DeleteItem(BaseItem item, DeleteOptions options) public void DeleteItem(BaseItem item, DeleteOptions options)
@ -447,7 +447,7 @@ namespace Emby.Server.Implementations.Library
_itemRepository.DeleteItem(child.Id); _itemRepository.DeleteItem(child.Id);
} }
_libraryItemsCache.TryRemove(item.Id, out BaseItem removed); _memoryCache.Remove(item.Id);
ReportItemRemoved(item, parent); ReportItemRemoved(item, parent);
} }
@ -1248,7 +1248,7 @@ namespace Emby.Server.Implementations.Library
throw new ArgumentException("Guid can't be empty", nameof(id)); throw new ArgumentException("Guid can't be empty", nameof(id));
} }
if (_libraryItemsCache.TryGetValue(id, out BaseItem item)) if (_memoryCache.TryGetValue(id, out BaseItem item))
{ {
return item; return item;
} }
@ -1591,7 +1591,6 @@ namespace Emby.Server.Implementations.Library
public async Task<IEnumerable<Video>> GetIntros(BaseItem item, User user) public async Task<IEnumerable<Video>> GetIntros(BaseItem item, User user)
{ {
var tasks = IntroProviders var tasks = IntroProviders
.OrderBy(i => i.GetType().Name.Contains("Default", StringComparison.OrdinalIgnoreCase) ? 1 : 0)
.Take(1) .Take(1)
.Select(i => GetIntros(i, item, user)); .Select(i => GetIntros(i, item, user));

View File

@ -4,12 +4,12 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Jellyfin.Data.Entities; using Jellyfin.Data.Entities;
using Jellyfin.Data.Enums;
using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Playlists; using MediaBrowser.Controller.Playlists;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Querying; using MediaBrowser.Model.Querying;
using MusicAlbum = MediaBrowser.Controller.Entities.Audio.MusicAlbum; using MusicAlbum = MediaBrowser.Controller.Entities.Audio.MusicAlbum;

View File

@ -4,12 +4,12 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Jellyfin.Data.Entities; using Jellyfin.Data.Entities;
using Jellyfin.Data.Enums;
using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Extensions; using MediaBrowser.Controller.Extensions;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Querying; using MediaBrowser.Model.Querying;
using MediaBrowser.Model.Search; using MediaBrowser.Model.Search;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;

View File

@ -12,6 +12,7 @@ using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Xml; using System.Xml;
using Emby.Server.Implementations.Library; using Emby.Server.Implementations.Library;
using Jellyfin.Data.Enums;
using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Extensions;
using MediaBrowser.Common.Net; using MediaBrowser.Common.Net;

View File

@ -101,8 +101,8 @@
"TaskCleanTranscode": "Lösche Transkodier Pfad", "TaskCleanTranscode": "Lösche Transkodier Pfad",
"TaskUpdatePluginsDescription": "Lädt Updates für Plugins herunter, welche dazu eingestellt sind automatisch zu updaten und installiert sie.", "TaskUpdatePluginsDescription": "Lädt Updates für Plugins herunter, welche dazu eingestellt sind automatisch zu updaten und installiert sie.",
"TaskUpdatePlugins": "Update Plugins", "TaskUpdatePlugins": "Update Plugins",
"TaskRefreshPeopleDescription": "Erneuert Metadaten für Schausteller und Regisseure in deinen Bibliotheken.", "TaskRefreshPeopleDescription": "Erneuert Metadaten für Schauspieler und Regisseure in deinen Bibliotheken.",
"TaskRefreshPeople": "Erneuere Schausteller", "TaskRefreshPeople": "Erneuere Schauspieler",
"TaskCleanLogsDescription": "Lösche Log Dateien die älter als {0} Tage sind.", "TaskCleanLogsDescription": "Lösche Log Dateien die älter als {0} Tage sind.",
"TaskCleanLogs": "Lösche Log Pfad", "TaskCleanLogs": "Lösche Log Pfad",
"TaskRefreshLibraryDescription": "Scanne alle Bibliotheken für hinzugefügte Datein und erneuere Metadaten.", "TaskRefreshLibraryDescription": "Scanne alle Bibliotheken für hinzugefügte Datein und erneuere Metadaten.",

View File

@ -165,7 +165,7 @@ namespace Emby.Server.Implementations.Networking
(octet[0] == 127) || // RFC1122 (octet[0] == 127) || // RFC1122
(octet[0] == 169 && octet[1] == 254)) // RFC3927 (octet[0] == 169 && octet[1] == 254)) // RFC3927
{ {
return false; return true;
} }
if (checkSubnets && IsInPrivateAddressSpaceAndLocalSubnet(endpoint)) if (checkSubnets && IsInPrivateAddressSpaceAndLocalSubnet(endpoint))

View File

@ -0,0 +1,150 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using Jellyfin.Data.Enums;
namespace Jellyfin.Data.Entities
{
/// <summary>
/// An entity representing a user's display preferences.
/// </summary>
public class DisplayPreferences
{
/// <summary>
/// Initializes a new instance of the <see cref="DisplayPreferences"/> class.
/// </summary>
/// <param name="userId">The user's id.</param>
/// <param name="client">The client string.</param>
public DisplayPreferences(Guid userId, string client)
{
UserId = userId;
Client = client;
ShowSidebar = false;
ShowBackdrop = true;
SkipForwardLength = 30000;
SkipBackwardLength = 10000;
ScrollDirection = ScrollDirection.Horizontal;
ChromecastVersion = ChromecastVersion.Stable;
DashboardTheme = string.Empty;
TvHome = string.Empty;
HomeSections = new HashSet<HomeSection>();
}
/// <summary>
/// Initializes a new instance of the <see cref="DisplayPreferences"/> class.
/// </summary>
protected DisplayPreferences()
{
}
/// <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 a value indicating whether to show the sidebar.
/// </summary>
/// <remarks>
/// Required.
/// </remarks>
public bool ShowSidebar { get; set; }
/// <summary>
/// Gets or sets a value indicating whether to show the backdrop.
/// </summary>
/// <remarks>
/// Required.
/// </remarks>
public bool ShowBackdrop { get; set; }
/// <summary>
/// Gets or sets the scroll direction.
/// </summary>
/// <remarks>
/// Required.
/// </remarks>
public ScrollDirection ScrollDirection { get; set; }
/// <summary>
/// Gets or sets what the view should be indexed by.
/// </summary>
public IndexingKind? IndexBy { get; set; }
/// <summary>
/// Gets or sets the length of time to skip forwards, in milliseconds.
/// </summary>
/// <remarks>
/// Required.
/// </remarks>
public int SkipForwardLength { get; set; }
/// <summary>
/// Gets or sets the length of time to skip backwards, in milliseconds.
/// </summary>
/// <remarks>
/// Required.
/// </remarks>
public int SkipBackwardLength { get; set; }
/// <summary>
/// Gets or sets the Chromecast Version.
/// </summary>
/// <remarks>
/// Required.
/// </remarks>
public ChromecastVersion ChromecastVersion { get; set; }
/// <summary>
/// Gets or sets a value indicating whether the next video info overlay should be shown.
/// </summary>
/// <remarks>
/// Required.
/// </remarks>
public bool EnableNextVideoInfoOverlay { get; set; }
/// <summary>
/// Gets or sets the dashboard theme.
/// </summary>
[MaxLength(32)]
[StringLength(32)]
public string DashboardTheme { get; set; }
/// <summary>
/// Gets or sets the tv home screen.
/// </summary>
[MaxLength(32)]
[StringLength(32)]
public string TvHome { get; set; }
/// <summary>
/// Gets or sets the home sections.
/// </summary>
public virtual ICollection<HomeSection> HomeSections { get; protected set; }
}
}

View File

@ -0,0 +1,46 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using Jellyfin.Data.Enums;
namespace Jellyfin.Data.Entities
{
/// <summary>
/// An entity representing a section on the user's home page.
/// </summary>
public class HomeSection
{
/// <summary>
/// Gets or sets the id.
/// </summary>
/// <remarks>
/// Identity. Required.
/// </remarks>
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; protected set; }
/// <summary>
/// Gets or sets the Id of the associated display preferences.
/// </summary>
/// <remarks>
/// Required.
/// </remarks>
public int DisplayPreferencesId { get; set; }
/// <summary>
/// Gets or sets the order.
/// </summary>
/// <remarks>
/// Required.
/// </remarks>
public int Order { get; set; }
/// <summary>
/// Gets or sets the type.
/// </summary>
/// <remarks>
/// Required.
/// </remarks>
public HomeSectionType Type { get; set; }
}
}

View File

@ -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 ItemDisplayPreferences
{
/// <summary>
/// Initializes a new instance of the <see cref="ItemDisplayPreferences"/> class.
/// </summary>
/// <param name="userId">The user id.</param>
/// <param name="itemId">The item id.</param>
/// <param name="client">The client.</param>
public ItemDisplayPreferences(Guid userId, Guid itemId, string client)
{
UserId = userId;
ItemId = itemId;
Client = client;
SortBy = "SortName";
ViewType = ViewType.Poster;
SortOrder = SortOrder.Ascending;
RememberSorting = false;
RememberIndexing = false;
}
/// <summary>
/// Initializes a new instance of the <see cref="ItemDisplayPreferences"/> class.
/// </summary>
protected ItemDisplayPreferences()
{
}
/// <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 id of the associated item.
/// </summary>
/// <remarks>
/// Required.
/// </remarks>
public Guid ItemId { 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 view type.
/// </summary>
/// <remarks>
/// Required.
/// </remarks>
public ViewType ViewType { get; set; }
/// <summary>
/// Gets or sets a value indicating whether the indexing should be remembered.
/// </summary>
/// <remarks>
/// Required.
/// </remarks>
public bool RememberIndexing { get; set; }
/// <summary>
/// Gets or sets what the view should be indexed by.
/// </summary>
public IndexingKind? IndexBy { get; set; }
/// <summary>
/// Gets or sets a value indicating whether the sorting type should be remembered.
/// </summary>
/// <remarks>
/// Required.
/// </remarks>
public bool RememberSorting { get; set; }
/// <summary>
/// Gets or sets what the view should be sorted by.
/// </summary>
/// <remarks>
/// Required.
/// </remarks>
[Required]
[MaxLength(64)]
[StringLength(64)]
public string SortBy { get; set; }
/// <summary>
/// Gets or sets the sort order.
/// </summary>
/// <remarks>
/// Required.
/// </remarks>
public SortOrder SortOrder { get; set; }
}
}

View File

@ -48,6 +48,7 @@ namespace Jellyfin.Data.Entities
PasswordResetProviderId = passwordResetProviderId; PasswordResetProviderId = passwordResetProviderId;
AccessSchedules = new HashSet<AccessSchedule>(); AccessSchedules = new HashSet<AccessSchedule>();
ItemDisplayPreferences = new HashSet<ItemDisplayPreferences>();
// Groups = new HashSet<Group>(); // Groups = new HashSet<Group>();
Permissions = new HashSet<Permission>(); Permissions = new HashSet<Permission>();
Preferences = new HashSet<Preference>(); Preferences = new HashSet<Preference>();
@ -327,6 +328,15 @@ namespace Jellyfin.Data.Entities
// [ForeignKey("UserId")] // [ForeignKey("UserId")]
public virtual ImageInfo ProfileImage { get; set; } public virtual ImageInfo ProfileImage { get; set; }
/// <summary>
/// Gets or sets the user's display preferences.
/// </summary>
/// <remarks>
/// Required.
/// </remarks>
[Required]
public virtual DisplayPreferences DisplayPreferences { get; set; }
[Required] [Required]
public SyncPlayAccess SyncPlayAccess { get; set; } public SyncPlayAccess SyncPlayAccess { get; set; }
@ -349,6 +359,11 @@ namespace Jellyfin.Data.Entities
/// </summary> /// </summary>
public virtual ICollection<AccessSchedule> AccessSchedules { get; protected set; } public virtual ICollection<AccessSchedule> AccessSchedules { get; protected set; }
/// <summary>
/// Gets or sets the list of item display preferences.
/// </summary>
public virtual ICollection<ItemDisplayPreferences> ItemDisplayPreferences { get; protected set; }
/* /*
/// <summary> /// <summary>
/// Gets or sets the list of groups this user is a member of. /// Gets or sets the list of groups this user is a member of.

View File

@ -0,0 +1,18 @@
namespace Jellyfin.Data.Enums
{
/// <summary>
/// An enum representing the version of Chromecast to be used by clients.
/// </summary>
public enum ChromecastVersion
{
/// <summary>
/// Stable Chromecast version.
/// </summary>
Stable = 0,
/// <summary>
/// Unstable Chromecast version.
/// </summary>
Unstable = 1
}
}

View File

@ -0,0 +1,53 @@
namespace Jellyfin.Data.Enums
{
/// <summary>
/// An enum representing the different options for the home screen sections.
/// </summary>
public enum HomeSectionType
{
/// <summary>
/// None.
/// </summary>
None = 0,
/// <summary>
/// My Media.
/// </summary>
SmallLibraryTiles = 1,
/// <summary>
/// My Media Small.
/// </summary>
LibraryButtons = 2,
/// <summary>
/// Active Recordings.
/// </summary>
ActiveRecordings = 3,
/// <summary>
/// Continue Watching.
/// </summary>
Resume = 4,
/// <summary>
/// Continue Listening.
/// </summary>
ResumeAudio = 5,
/// <summary>
/// Latest Media.
/// </summary>
LatestMedia = 6,
/// <summary>
/// Next Up.
/// </summary>
NextUp = 7,
/// <summary>
/// Live TV.
/// </summary>
LiveTv = 8
}
}

View File

@ -0,0 +1,20 @@
namespace Jellyfin.Data.Enums
{
public enum IndexingKind
{
/// <summary>
/// Index by the premiere date.
/// </summary>
PremiereDate = 0,
/// <summary>
/// Index by the production year.
/// </summary>
ProductionYear = 1,
/// <summary>
/// Index by the community rating.
/// </summary>
CommunityRating = 2
}
}

View File

@ -0,0 +1,18 @@
namespace Jellyfin.Data.Enums
{
/// <summary>
/// An enum representing the axis that should be scrolled.
/// </summary>
public enum ScrollDirection
{
/// <summary>
/// Horizontal scrolling direction.
/// </summary>
Horizontal = 0,
/// <summary>
/// Vertical scrolling direction.
/// </summary>
Vertical = 1
}
}

View File

@ -0,0 +1,18 @@
namespace Jellyfin.Data.Enums
{
/// <summary>
/// An enum representing the sorting order.
/// </summary>
public enum SortOrder
{
/// <summary>
/// Sort in increasing order.
/// </summary>
Ascending = 0,
/// <summary>
/// Sort in decreasing order.
/// </summary>
Descending = 1
}
}

View File

@ -0,0 +1,38 @@
namespace Jellyfin.Data.Enums
{
/// <summary>
/// An enum representing the type of view for a library or collection.
/// </summary>
public enum ViewType
{
/// <summary>
/// Shows banners.
/// </summary>
Banner = 0,
/// <summary>
/// Shows a list of content.
/// </summary>
List = 1,
/// <summary>
/// Shows poster artwork.
/// </summary>
Poster = 2,
/// <summary>
/// Shows poster artwork with a card containing the name and year.
/// </summary>
PosterCard = 3,
/// <summary>
/// Shows a thumbnail.
/// </summary>
Thumb = 4,
/// <summary>
/// Shows a thumbnail with a card containing the name and year.
/// </summary>
ThumbCard = 5
}
}

View File

@ -18,11 +18,10 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="BlurHashSharp" Version="1.0.1" /> <PackageReference Include="BlurHashSharp" Version="1.1.0" />
<PackageReference Include="BlurHashSharp.SkiaSharp" Version="1.0.0" /> <PackageReference Include="BlurHashSharp.SkiaSharp" Version="1.1.0" />
<PackageReference Include="SkiaSharp" Version="1.68.3" /> <PackageReference Include="SkiaSharp" Version="2.80.1" />
<PackageReference Include="SkiaSharp.NativeAssets.Linux" Version="1.68.3" /> <PackageReference Include="SkiaSharp.NativeAssets.Linux" Version="2.80.1" />
<PackageReference Include="Jellyfin.SkiaSharp.NativeAssets.LinuxArm" Version="1.68.1" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View File

@ -199,7 +199,8 @@ namespace Jellyfin.Drawing.Skia
throw new ArgumentNullException(nameof(path)); throw new ArgumentNullException(nameof(path));
} }
return BlurHashEncoder.Encode(xComp, yComp, path); // Any larger than 128x128 is too slow and there's no visually discernible difference
return BlurHashEncoder.Encode(xComp, yComp, path, 128, 128);
} }
private static bool HasDiacritics(string text) private static bool HasDiacritics(string text)
@ -394,6 +395,56 @@ namespace Jellyfin.Drawing.Skia
return rotated; return rotated;
} }
/// <summary>
/// Resizes an image on the CPU, by utilizing a surface and canvas.
///
/// The convolutional matrix kernel used in this resize function gives a (light) sharpening effect.
/// This technique is similar to effect that can be created using for example the [Convolution matrix filter in GIMP](https://docs.gimp.org/2.10/en/gimp-filter-convolution-matrix.html).
/// </summary>
/// <param name="source">The source bitmap.</param>
/// <param name="targetInfo">This specifies the target size and other information required to create the surface.</param>
/// <param name="isAntialias">This enables anti-aliasing on the SKPaint instance.</param>
/// <param name="isDither">This enables dithering on the SKPaint instance.</param>
/// <returns>The resized image.</returns>
internal static SKImage ResizeImage(SKBitmap source, SKImageInfo targetInfo, bool isAntialias = false, bool isDither = false)
{
using var surface = SKSurface.Create(targetInfo);
using var canvas = surface.Canvas;
using var paint = new SKPaint
{
FilterQuality = SKFilterQuality.High,
IsAntialias = isAntialias,
IsDither = isDither
};
var kernel = new float[9]
{
0, -.1f, 0,
-.1f, 1.4f, -.1f,
0, -.1f, 0,
};
var kernelSize = new SKSizeI(3, 3);
var kernelOffset = new SKPointI(1, 1);
paint.ImageFilter = SKImageFilter.CreateMatrixConvolution(
kernelSize,
kernel,
1f,
0f,
kernelOffset,
SKShaderTileMode.Clamp,
false);
canvas.DrawBitmap(
source,
SKRect.Create(0, 0, source.Width, source.Height),
SKRect.Create(0, 0, targetInfo.Width, targetInfo.Height),
paint);
return surface.Snapshot();
}
/// <inheritdoc/> /// <inheritdoc/>
public string EncodeImage(string inputPath, DateTime dateModified, string outputPath, bool autoOrient, ImageOrientation? orientation, int quality, ImageProcessingOptions options, ImageFormat selectedOutputFormat) public string EncodeImage(string inputPath, DateTime dateModified, string outputPath, bool autoOrient, ImageOrientation? orientation, int quality, ImageProcessingOptions options, ImageFormat selectedOutputFormat)
{ {
@ -435,9 +486,9 @@ namespace Jellyfin.Drawing.Skia
var width = newImageSize.Width; var width = newImageSize.Width;
var height = newImageSize.Height; var height = newImageSize.Height;
using var resizedBitmap = new SKBitmap(width, height, bitmap.ColorType, bitmap.AlphaType); // scale image (the FromImage creates a copy)
// scale image var imageInfo = new SKImageInfo(width, height, bitmap.ColorType, bitmap.AlphaType, bitmap.ColorSpace);
bitmap.ScalePixels(resizedBitmap, SKFilterQuality.High); using var resizedBitmap = SKBitmap.FromImage(ResizeImage(bitmap, imageInfo));
// If all we're doing is resizing then we can stop now // If all we're doing is resizing then we can stop now
if (!hasBackgroundColor && !hasForegroundColor && blur == 0 && !hasIndicator) if (!hasBackgroundColor && !hasForegroundColor && blur == 0 && !hasIndicator)
@ -445,7 +496,7 @@ namespace Jellyfin.Drawing.Skia
Directory.CreateDirectory(Path.GetDirectoryName(outputPath)); Directory.CreateDirectory(Path.GetDirectoryName(outputPath));
using var outputStream = new SKFileWStream(outputPath); using var outputStream = new SKFileWStream(outputPath);
using var pixmap = new SKPixmap(new SKImageInfo(width, height), resizedBitmap.GetPixels()); using var pixmap = new SKPixmap(new SKImageInfo(width, height), resizedBitmap.GetPixels());
pixmap.Encode(outputStream, skiaOutputFormat, quality); resizedBitmap.Encode(outputStream, skiaOutputFormat, quality);
return outputPath; return outputPath;
} }

View File

@ -115,15 +115,13 @@ namespace Jellyfin.Drawing.Skia
// resize to the same aspect as the original // resize to the same aspect as the original
int iWidth = Math.Abs(iHeight * currentBitmap.Width / currentBitmap.Height); int iWidth = Math.Abs(iHeight * currentBitmap.Width / currentBitmap.Height);
using var resizeBitmap = new SKBitmap(iWidth, iHeight, currentBitmap.ColorType, currentBitmap.AlphaType); using var resizedImage = SkiaEncoder.ResizeImage(bitmap, new SKImageInfo(iWidth, iHeight, currentBitmap.ColorType, currentBitmap.AlphaType, currentBitmap.ColorSpace));
currentBitmap.ScalePixels(resizeBitmap, SKFilterQuality.High);
// crop image // crop image
int ix = Math.Abs((iWidth - iSlice) / 2); int ix = Math.Abs((iWidth - iSlice) / 2);
using var image = SKImage.FromBitmap(resizeBitmap); using var subset = resizedImage.Subset(SKRectI.Create(ix, 0, iSlice, iHeight));
using var subset = image.Subset(SKRectI.Create(ix, 0, iSlice, iHeight));
// draw image onto canvas // draw image onto canvas
canvas.DrawImage(subset ?? image, iSlice * i, 0); canvas.DrawImage(subset ?? resizedImage, iSlice * i, 0);
} }
return bitmap; return bitmap;
@ -177,9 +175,9 @@ namespace Jellyfin.Drawing.Skia
continue; continue;
} }
using var resizedBitmap = new SKBitmap(cellWidth, cellHeight, currentBitmap.ColorType, currentBitmap.AlphaType); // Scale image. The FromBitmap creates a copy
// scale image var imageInfo = new SKImageInfo(cellWidth, cellHeight, currentBitmap.ColorType, currentBitmap.AlphaType, currentBitmap.ColorSpace);
currentBitmap.ScalePixels(resizedBitmap, SKFilterQuality.High); using var resizedBitmap = SKBitmap.FromImage(SkiaEncoder.ResizeImage(bitmap, imageInfo));
// draw this image into the strip at the next position // draw this image into the strip at the next position
var xPos = x * cellWidth; var xPos = x * cellWidth;

View File

@ -28,8 +28,12 @@ namespace Jellyfin.Server.Implementations
public virtual DbSet<ActivityLog> ActivityLogs { get; set; } public virtual DbSet<ActivityLog> ActivityLogs { get; set; }
public virtual DbSet<DisplayPreferences> DisplayPreferences { get; set; }
public virtual DbSet<ImageInfo> ImageInfos { get; set; } public virtual DbSet<ImageInfo> ImageInfos { get; set; }
public virtual DbSet<ItemDisplayPreferences> ItemDisplayPreferences { get; set; }
public virtual DbSet<Permission> Permissions { get; set; } public virtual DbSet<Permission> Permissions { get; set; }
public virtual DbSet<Preference> Preferences { get; set; } public virtual DbSet<Preference> Preferences { get; set; }

View File

@ -1,4 +1,6 @@
using System; using System;
using System.IO;
using MediaBrowser.Common.Configuration;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
@ -10,15 +12,20 @@ namespace Jellyfin.Server.Implementations
public class JellyfinDbProvider public class JellyfinDbProvider
{ {
private readonly IServiceProvider _serviceProvider; private readonly IServiceProvider _serviceProvider;
private readonly IApplicationPaths _appPaths;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="JellyfinDbProvider"/> class. /// Initializes a new instance of the <see cref="JellyfinDbProvider"/> class.
/// </summary> /// </summary>
/// <param name="serviceProvider">The application's service provider.</param> /// <param name="serviceProvider">The application's service provider.</param>
public JellyfinDbProvider(IServiceProvider serviceProvider) /// <param name="appPaths">The application paths.</param>
public JellyfinDbProvider(IServiceProvider serviceProvider, IApplicationPaths appPaths)
{ {
_serviceProvider = serviceProvider; _serviceProvider = serviceProvider;
serviceProvider.GetRequiredService<JellyfinDb>().Database.Migrate(); _appPaths = appPaths;
using var jellyfinDb = CreateContext();
jellyfinDb.Database.Migrate();
} }
/// <summary> /// <summary>
@ -27,7 +34,8 @@ namespace Jellyfin.Server.Implementations
/// <returns>The newly created context.</returns> /// <returns>The newly created context.</returns>
public JellyfinDb CreateContext() public JellyfinDb CreateContext()
{ {
return _serviceProvider.GetRequiredService<JellyfinDb>(); var contextOptions = new DbContextOptionsBuilder<JellyfinDb>().UseSqlite($"Filename={Path.Combine(_appPaths.DataPath, "jellyfin.db")}");
return ActivatorUtilities.CreateInstance<JellyfinDb>(_serviceProvider, contextOptions.Options);
} }
} }
} }

View File

@ -0,0 +1,459 @@
#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("20200728005145_AddDisplayPreferences")]
partial class AddDisplayPreferences
{
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasDefaultSchema("jellyfin")
.HasAnnotation("ProductVersion", "3.1.6");
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")
.HasColumnType("TEXT")
.HasMaxLength(256);
b.Property<int>("LogSeverity")
.HasColumnType("INTEGER");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("TEXT")
.HasMaxLength(512);
b.Property<string>("Overview")
.HasColumnType("TEXT")
.HasMaxLength(512);
b.Property<uint>("RowVersion")
.IsConcurrencyToken()
.HasColumnType("INTEGER");
b.Property<string>("ShortOverview")
.HasColumnType("TEXT")
.HasMaxLength(512);
b.Property<string>("Type")
.IsRequired()
.HasColumnType("TEXT")
.HasMaxLength(256);
b.Property<Guid>("UserId")
.HasColumnType("TEXT");
b.HasKey("Id");
b.ToTable("ActivityLogs");
});
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()
.HasColumnType("TEXT")
.HasMaxLength(32);
b.Property<string>("DashboardTheme")
.HasColumnType("TEXT")
.HasMaxLength(32);
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")
.HasColumnType("TEXT")
.HasMaxLength(32);
b.Property<Guid>("UserId")
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("UserId")
.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()
.HasColumnType("TEXT")
.HasMaxLength(512);
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()
.HasColumnType("TEXT")
.HasMaxLength(32);
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()
.HasColumnType("TEXT")
.HasMaxLength(64);
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()
.HasColumnType("TEXT")
.HasMaxLength(65535);
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")
.HasColumnType("TEXT")
.HasMaxLength(255);
b.Property<string>("AuthenticationProviderId")
.IsRequired()
.HasColumnType("TEXT")
.HasMaxLength(255);
b.Property<bool>("DisplayCollectionsView")
.HasColumnType("INTEGER");
b.Property<bool>("DisplayMissingEpisodes")
.HasColumnType("INTEGER");
b.Property<string>("EasyPassword")
.HasColumnType("TEXT")
.HasMaxLength(65535);
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?>("MaxParentalAgeRating")
.HasColumnType("INTEGER");
b.Property<bool>("MustUpdatePassword")
.HasColumnType("INTEGER");
b.Property<string>("Password")
.HasColumnType("TEXT")
.HasMaxLength(65535);
b.Property<string>("PasswordResetProviderId")
.IsRequired()
.HasColumnType("TEXT")
.HasMaxLength(255);
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")
.HasColumnType("TEXT")
.HasMaxLength(255);
b.Property<int>("SubtitleMode")
.HasColumnType("INTEGER");
b.Property<int>("SyncPlayAccess")
.HasColumnType("INTEGER");
b.Property<string>("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)
.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");
});
#pragma warning restore 612, 618
}
}
}

View File

@ -0,0 +1,132 @@
#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<int>(nullable: false)
.Annotation("Sqlite:Autoincrement", true),
UserId = table.Column<Guid>(nullable: false),
Client = table.Column<string>(maxLength: 32, nullable: false),
ShowSidebar = table.Column<bool>(nullable: false),
ShowBackdrop = table.Column<bool>(nullable: false),
ScrollDirection = table.Column<int>(nullable: false),
IndexBy = table.Column<int>(nullable: true),
SkipForwardLength = table.Column<int>(nullable: false),
SkipBackwardLength = table.Column<int>(nullable: false),
ChromecastVersion = table.Column<int>(nullable: false),
EnableNextVideoInfoOverlay = table.Column<bool>(nullable: false),
DashboardTheme = table.Column<string>(maxLength: 32, nullable: true),
TvHome = table.Column<string>(maxLength: 32, 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: "ItemDisplayPreferences",
schema: "jellyfin",
columns: table => new
{
Id = table.Column<int>(nullable: false)
.Annotation("Sqlite:Autoincrement", true),
UserId = table.Column<Guid>(nullable: false),
ItemId = table.Column<Guid>(nullable: false),
Client = table.Column<string>(maxLength: 32, nullable: false),
ViewType = table.Column<int>(nullable: false),
RememberIndexing = table.Column<bool>(nullable: false),
IndexBy = table.Column<int>(nullable: true),
RememberSorting = table.Column<bool>(nullable: false),
SortBy = table.Column<string>(maxLength: 64, nullable: false),
SortOrder = table.Column<int>(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",
columns: table => new
{
Id = table.Column<int>(nullable: false)
.Annotation("Sqlite:Autoincrement", true),
DisplayPreferencesId = table.Column<int>(nullable: false),
Order = table.Column<int>(nullable: false),
Type = table.Column<int>(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",
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)
{
migrationBuilder.DropTable(
name: "HomeSection",
schema: "jellyfin");
migrationBuilder.DropTable(
name: "ItemDisplayPreferences",
schema: "jellyfin");
migrationBuilder.DropTable(
name: "DisplayPreferences",
schema: "jellyfin");
}
}
}

View File

@ -15,7 +15,7 @@ namespace Jellyfin.Server.Implementations.Migrations
#pragma warning disable 612, 618 #pragma warning disable 612, 618
modelBuilder modelBuilder
.HasDefaultSchema("jellyfin") .HasDefaultSchema("jellyfin")
.HasAnnotation("ProductVersion", "3.1.4"); .HasAnnotation("ProductVersion", "3.1.6");
modelBuilder.Entity("Jellyfin.Data.Entities.AccessSchedule", b => modelBuilder.Entity("Jellyfin.Data.Entities.AccessSchedule", b =>
{ {
@ -88,6 +88,82 @@ namespace Jellyfin.Server.Implementations.Migrations
b.ToTable("ActivityLogs"); b.ToTable("ActivityLogs");
}); });
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()
.HasColumnType("TEXT")
.HasMaxLength(32);
b.Property<string>("DashboardTheme")
.HasColumnType("TEXT")
.HasMaxLength(32);
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")
.HasColumnType("TEXT")
.HasMaxLength(32);
b.Property<Guid>("UserId")
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("UserId")
.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 => modelBuilder.Entity("Jellyfin.Data.Entities.ImageInfo", b =>
{ {
b.Property<int>("Id") b.Property<int>("Id")
@ -113,6 +189,50 @@ namespace Jellyfin.Server.Implementations.Migrations
b.ToTable("ImageInfos"); b.ToTable("ImageInfos");
}); });
modelBuilder.Entity("Jellyfin.Data.Entities.ItemDisplayPreferences", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("Client")
.IsRequired()
.HasColumnType("TEXT")
.HasMaxLength(32);
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()
.HasColumnType("TEXT")
.HasMaxLength(64);
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 => modelBuilder.Entity("Jellyfin.Data.Entities.Permission", b =>
{ {
b.Property<int>("Id") b.Property<int>("Id")
@ -282,6 +402,24 @@ namespace Jellyfin.Server.Implementations.Migrations
.IsRequired(); .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 => modelBuilder.Entity("Jellyfin.Data.Entities.ImageInfo", b =>
{ {
b.HasOne("Jellyfin.Data.Entities.User", null) b.HasOne("Jellyfin.Data.Entities.User", null)
@ -289,6 +427,15 @@ namespace Jellyfin.Server.Implementations.Migrations
.HasForeignKey("Jellyfin.Data.Entities.ImageInfo", "UserId"); .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 => modelBuilder.Entity("Jellyfin.Data.Entities.Permission", b =>
{ {
b.HasOne("Jellyfin.Data.Entities.User", null) b.HasOne("Jellyfin.Data.Entities.User", null)

View File

@ -4,6 +4,7 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Security.Cryptography; using System.Security.Cryptography;
using System.Text.Json;
using System.Threading.Tasks; using System.Threading.Tasks;
using Jellyfin.Data.Entities; using Jellyfin.Data.Entities;
using MediaBrowser.Common; using MediaBrowser.Common;
@ -11,7 +12,6 @@ using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller.Authentication; using MediaBrowser.Controller.Authentication;
using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
using MediaBrowser.Model.Serialization;
using MediaBrowser.Model.Users; using MediaBrowser.Model.Users;
namespace Jellyfin.Server.Implementations.Users namespace Jellyfin.Server.Implementations.Users
@ -23,7 +23,6 @@ namespace Jellyfin.Server.Implementations.Users
{ {
private const string BaseResetFileName = "passwordreset"; private const string BaseResetFileName = "passwordreset";
private readonly IJsonSerializer _jsonSerializer;
private readonly IApplicationHost _appHost; private readonly IApplicationHost _appHost;
private readonly string _passwordResetFileBase; private readonly string _passwordResetFileBase;
@ -33,16 +32,11 @@ namespace Jellyfin.Server.Implementations.Users
/// Initializes a new instance of the <see cref="DefaultPasswordResetProvider"/> class. /// Initializes a new instance of the <see cref="DefaultPasswordResetProvider"/> class.
/// </summary> /// </summary>
/// <param name="configurationManager">The configuration manager.</param> /// <param name="configurationManager">The configuration manager.</param>
/// <param name="jsonSerializer">The JSON serializer.</param>
/// <param name="appHost">The application host.</param> /// <param name="appHost">The application host.</param>
public DefaultPasswordResetProvider( public DefaultPasswordResetProvider(IServerConfigurationManager configurationManager, IApplicationHost appHost)
IServerConfigurationManager configurationManager,
IJsonSerializer jsonSerializer,
IApplicationHost appHost)
{ {
_passwordResetFileBaseDir = configurationManager.ApplicationPaths.ProgramDataPath; _passwordResetFileBaseDir = configurationManager.ApplicationPaths.ProgramDataPath;
_passwordResetFileBase = Path.Combine(_passwordResetFileBaseDir, BaseResetFileName); _passwordResetFileBase = Path.Combine(_passwordResetFileBaseDir, BaseResetFileName);
_jsonSerializer = jsonSerializer;
_appHost = appHost; _appHost = appHost;
// TODO: Remove the circular dependency on UserManager // TODO: Remove the circular dependency on UserManager
} }
@ -63,7 +57,7 @@ namespace Jellyfin.Server.Implementations.Users
SerializablePasswordReset spr; SerializablePasswordReset spr;
await using (var str = File.OpenRead(resetFile)) await using (var str = File.OpenRead(resetFile))
{ {
spr = await _jsonSerializer.DeserializeFromStreamAsync<SerializablePasswordReset>(str).ConfigureAwait(false); spr = await JsonSerializer.DeserializeAsync<SerializablePasswordReset>(str).ConfigureAwait(false);
} }
if (spr.ExpirationDate < DateTime.UtcNow) if (spr.ExpirationDate < DateTime.UtcNow)
@ -119,7 +113,7 @@ namespace Jellyfin.Server.Implementations.Users
await using (FileStream fileStream = File.OpenWrite(filePath)) await using (FileStream fileStream = File.OpenWrite(filePath))
{ {
_jsonSerializer.SerializeToStream(spr, fileStream); await JsonSerializer.SerializeAsync(fileStream, spr).ConfigureAwait(false);
await fileStream.FlushAsync().ConfigureAwait(false); await fileStream.FlushAsync().ConfigureAwait(false);
} }

View File

@ -0,0 +1,88 @@
#pragma warning disable CA1307
using System;
using System.Collections.Generic;
using System.Linq;
using Jellyfin.Data.Entities;
using MediaBrowser.Controller;
using Microsoft.EntityFrameworkCore;
namespace Jellyfin.Server.Implementations.Users
{
/// <summary>
/// Manages the storage and retrieval of display preferences through Entity Framework.
/// </summary>
public class DisplayPreferencesManager : IDisplayPreferencesManager
{
private readonly JellyfinDbProvider _dbProvider;
/// <summary>
/// Initializes a new instance of the <see cref="DisplayPreferencesManager"/> class.
/// </summary>
/// <param name="dbProvider">The Jellyfin db provider.</param>
public DisplayPreferencesManager(JellyfinDbProvider dbProvider)
{
_dbProvider = dbProvider;
}
/// <inheritdoc />
public DisplayPreferences GetDisplayPreferences(Guid userId, string client)
{
using var dbContext = _dbProvider.CreateContext();
var prefs = dbContext.DisplayPreferences
.Include(pref => pref.HomeSections)
.FirstOrDefault(pref =>
pref.UserId == userId && string.Equals(pref.Client, client));
if (prefs == null)
{
prefs = new DisplayPreferences(userId, client);
dbContext.DisplayPreferences.Add(prefs);
}
return prefs;
}
/// <inheritdoc />
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;
}
/// <inheritdoc />
public IList<ItemDisplayPreferences> 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();
}
/// <inheritdoc />
public void SaveChanges(DisplayPreferences preferences)
{
using var dbContext = _dbProvider.CreateContext();
dbContext.Update(preferences);
dbContext.SaveChanges();
}
/// <inheritdoc />
public void SaveChanges(ItemDisplayPreferences preferences)
{
using var dbContext = _dbProvider.CreateContext();
dbContext.Update(preferences);
dbContext.SaveChanges();
}
}
}

View File

@ -600,18 +600,13 @@ namespace Jellyfin.Server.Implementations.Users
} }
var defaultName = Environment.UserName; var defaultName = Environment.UserName;
if (string.IsNullOrWhiteSpace(defaultName)) if (string.IsNullOrWhiteSpace(defaultName) || !IsValidUsername(defaultName))
{ {
defaultName = "MyJellyfinUser"; defaultName = "MyJellyfinUser";
} }
_logger.LogWarning("No users, creating one with username {UserName}", defaultName); _logger.LogWarning("No users, creating one with username {UserName}", defaultName);
if (!IsValidUsername(defaultName))
{
throw new ArgumentException("Provided username is not valid!", defaultName);
}
var newUser = await CreateUserInternalAsync(defaultName, dbContext).ConfigureAwait(false); var newUser = await CreateUserInternalAsync(defaultName, dbContext).ConfigureAwait(false);
newUser.SetPermission(PermissionKind.IsAdministrator, true); newUser.SetPermission(PermissionKind.IsAdministrator, true);
newUser.SetPermission(PermissionKind.EnableContentDeletion, true); newUser.SetPermission(PermissionKind.EnableContentDeletion, true);

View File

@ -64,16 +64,18 @@ namespace Jellyfin.Server
Logger.LogWarning($"Skia not available. Will fallback to {nameof(NullImageEncoder)}."); Logger.LogWarning($"Skia not available. Will fallback to {nameof(NullImageEncoder)}.");
} }
// TODO: Set up scoping and use AddDbContextPool // TODO: Set up scoping and use AddDbContextPool,
serviceCollection.AddDbContext<JellyfinDb>( // can't register as Transient since tracking transient in GC is funky
options => options // serviceCollection.AddDbContext<JellyfinDb>(
.UseSqlite($"Filename={Path.Combine(ApplicationPaths.DataPath, "jellyfin.db")}"), // options => options
ServiceLifetime.Transient); // .UseSqlite($"Filename={Path.Combine(ApplicationPaths.DataPath, "jellyfin.db")}"),
// ServiceLifetime.Transient);
serviceCollection.AddSingleton<JellyfinDbProvider>(); serviceCollection.AddSingleton<JellyfinDbProvider>();
serviceCollection.AddSingleton<IActivityManager, ActivityManager>(); serviceCollection.AddSingleton<IActivityManager, ActivityManager>();
serviceCollection.AddSingleton<IUserManager, UserManager>(); serviceCollection.AddSingleton<IUserManager, UserManager>();
serviceCollection.AddSingleton<IDisplayPreferencesManager, DisplayPreferencesManager>();
base.RegisterServices(serviceCollection); base.RegisterServices(serviceCollection);
} }

View File

@ -22,7 +22,8 @@ namespace Jellyfin.Server.Migrations
typeof(Routines.RemoveDuplicateExtras), typeof(Routines.RemoveDuplicateExtras),
typeof(Routines.AddDefaultPluginRepository), typeof(Routines.AddDefaultPluginRepository),
typeof(Routines.MigrateUserDb), typeof(Routines.MigrateUserDb),
typeof(Routines.ReaddDefaultPluginRepository) typeof(Routines.ReaddDefaultPluginRepository),
typeof(Routines.MigrateDisplayPreferencesDb)
}; };
/// <summary> /// <summary>

View File

@ -0,0 +1,174 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
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
{
/// <summary>
/// The migration routine for migrating the display preferences database to EF Core.
/// </summary>
public class MigrateDisplayPreferencesDb : IMigrationRoutine
{
private const string DbFilename = "displaypreferences.db";
private readonly ILogger<MigrateDisplayPreferencesDb> _logger;
private readonly IServerApplicationPaths _paths;
private readonly JellyfinDbProvider _provider;
private readonly JsonSerializerOptions _jsonOptions;
/// <summary>
/// Initializes a new instance of the <see cref="MigrateDisplayPreferencesDb"/> class.
/// </summary>
/// <param name="logger">The logger.</param>
/// <param name="paths">The server application paths.</param>
/// <param name="provider">The database provider.</param>
public MigrateDisplayPreferencesDb(ILogger<MigrateDisplayPreferencesDb> logger, IServerApplicationPaths paths, JellyfinDbProvider provider)
{
_logger = logger;
_paths = paths;
_provider = provider;
_jsonOptions = new JsonSerializerOptions();
_jsonOptions.Converters.Add(new JsonStringEnumConverter());
}
/// <inheritdoc />
public Guid Id => Guid.Parse("06387815-C3CC-421F-A888-FB5F9992BEA8");
/// <inheritdoc />
public string Name => "MigrateDisplayPreferencesDatabase";
/// <inheritdoc />
public bool PerformOnNewInstall => false;
/// <inheritdoc />
public void Perform()
{
HomeSectionType[] defaults =
{
HomeSectionType.SmallLibraryTiles,
HomeSectionType.Resume,
HomeSectionType.ResumeAudio,
HomeSectionType.LiveTv,
HomeSectionType.NextUp,
HomeSectionType.LatestMedia,
HomeSectionType.None,
};
var chromecastDict = new Dictionary<string, ChromecastVersion>(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))
{
using var dbContext = _provider.CreateContext();
var results = connection.Query("SELECT * FROM userdisplaypreferences");
foreach (var result in results)
{
var dto = JsonSerializer.Deserialize<DisplayPreferencesDto>(result[3].ToString(), _jsonOptions);
var chromecastVersion = dto.CustomPrefs.TryGetValue("chromecastVersion", out var version)
? chromecastDict[version]
: ChromecastVersion.Stable;
var displayPreferences = new DisplayPreferences(new Guid(result[1].ToBlob()), result[2].ToString())
{
IndexBy = Enum.TryParse<IndexingKind>(dto.IndexBy, true, out var indexBy) ? indexBy : (IndexingKind?)null,
ShowBackdrop = dto.ShowBackdrop,
ShowSidebar = dto.ShowSidebar,
ScrollDirection = dto.ScrollDirection,
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)
: 10000,
EnableNextVideoInfoOverlay = dto.CustomPrefs.TryGetValue("enableNextVideoInfoOverlay", out var enabled)
? bool.Parse(enabled)
: 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++)
{
dto.CustomPrefs.TryGetValue("homesection" + i, out var homeSection);
displayPreferences.HomeSections.Add(new HomeSection
{
Order = i,
Type = Enum.TryParse<HomeSectionType>(homeSection, true, out var type) ? type : defaults[i]
});
}
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))
{
continue;
}
var libraryDisplayPreferences = new ItemDisplayPreferences(displayPreferences.UserId, itemId, displayPreferences.Client)
{
SortBy = dto.SortBy ?? "SortName",
SortOrder = dto.SortOrder,
RememberIndexing = dto.RememberIndexing,
RememberSorting = dto.RememberSorting,
};
if (Enum.TryParse<ViewType>(dto.ViewType, true, out var viewType))
{
libraryDisplayPreferences.ViewType = viewType;
}
dbContext.ItemDisplayPreferences.Add(libraryDisplayPreferences);
}
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'");
}
}
}
}

View File

@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Jellyfin.Data.Enums;
using MediaBrowser.Api.UserLibrary; using MediaBrowser.Api.UserLibrary;
using MediaBrowser.Controller.Channels; using MediaBrowser.Controller.Channels;
using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Configuration;
@ -11,7 +12,6 @@ using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Net;
using MediaBrowser.Model.Channels; using MediaBrowser.Model.Channels;
using MediaBrowser.Model.Dto; using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Querying; using MediaBrowser.Model.Querying;
using MediaBrowser.Model.Services; using MediaBrowser.Model.Services;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;

View File

@ -1,9 +1,11 @@
using System.Threading; using System;
using System.Linq;
using Jellyfin.Data.Entities;
using Jellyfin.Data.Enums;
using MediaBrowser.Controller;
using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Net;
using MediaBrowser.Controller.Persistence;
using MediaBrowser.Model.Entities; using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Serialization;
using MediaBrowser.Model.Services; using MediaBrowser.Model.Services;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
@ -13,7 +15,7 @@ namespace MediaBrowser.Api
/// Class UpdateDisplayPreferences. /// Class UpdateDisplayPreferences.
/// </summary> /// </summary>
[Route("/DisplayPreferences/{DisplayPreferencesId}", "POST", Summary = "Updates a user's display preferences for an item")] [Route("/DisplayPreferences/{DisplayPreferencesId}", "POST", Summary = "Updates a user's display preferences for an item")]
public class UpdateDisplayPreferences : DisplayPreferences, IReturnVoid public class UpdateDisplayPreferences : DisplayPreferencesDto, IReturnVoid
{ {
/// <summary> /// <summary>
/// Gets or sets the id. /// Gets or sets the id.
@ -27,7 +29,7 @@ namespace MediaBrowser.Api
} }
[Route("/DisplayPreferences/{Id}", "GET", Summary = "Gets a user's display preferences for an item")] [Route("/DisplayPreferences/{Id}", "GET", Summary = "Gets a user's display preferences for an item")]
public class GetDisplayPreferences : IReturn<DisplayPreferences> public class GetDisplayPreferences : IReturn<DisplayPreferencesDto>
{ {
/// <summary> /// <summary>
/// Gets or sets the id. /// Gets or sets the id.
@ -50,28 +52,21 @@ namespace MediaBrowser.Api
public class DisplayPreferencesService : BaseApiService public class DisplayPreferencesService : BaseApiService
{ {
/// <summary> /// <summary>
/// The _display preferences manager. /// The display preferences manager.
/// </summary> /// </summary>
private readonly IDisplayPreferencesRepository _displayPreferencesManager; private readonly IDisplayPreferencesManager _displayPreferencesManager;
/// <summary>
/// The _json serializer.
/// </summary>
private readonly IJsonSerializer _jsonSerializer;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="DisplayPreferencesService" /> class. /// Initializes a new instance of the <see cref="DisplayPreferencesService" /> class.
/// </summary> /// </summary>
/// <param name="jsonSerializer">The json serializer.</param>
/// <param name="displayPreferencesManager">The display preferences manager.</param> /// <param name="displayPreferencesManager">The display preferences manager.</param>
public DisplayPreferencesService( public DisplayPreferencesService(
ILogger<DisplayPreferencesService> logger, ILogger<DisplayPreferencesService> logger,
IServerConfigurationManager serverConfigurationManager, IServerConfigurationManager serverConfigurationManager,
IHttpResultFactory httpResultFactory, IHttpResultFactory httpResultFactory,
IJsonSerializer jsonSerializer, IDisplayPreferencesManager displayPreferencesManager)
IDisplayPreferencesRepository displayPreferencesManager)
: base(logger, serverConfigurationManager, httpResultFactory) : base(logger, serverConfigurationManager, httpResultFactory)
{ {
_jsonSerializer = jsonSerializer;
_displayPreferencesManager = displayPreferencesManager; _displayPreferencesManager = displayPreferencesManager;
} }
@ -81,9 +76,41 @@ namespace MediaBrowser.Api
/// <param name="request">The request.</param> /// <param name="request">The request.</param>
public object Get(GetDisplayPreferences request) public object Get(GetDisplayPreferences request)
{ {
var result = _displayPreferencesManager.GetDisplayPreferences(request.Id, request.UserId, request.Client); var displayPreferences = _displayPreferencesManager.GetDisplayPreferences(Guid.Parse(request.UserId), request.Client);
var itemPreferences = _displayPreferencesManager.GetItemDisplayPreferences(displayPreferences.UserId, Guid.Empty, displayPreferences.Client);
return ToOptimizedResult(result); var dto = new DisplayPreferencesDto
{
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 displayPreferences.HomeSections)
{
dto.CustomPrefs["homesection" + homeSection.Order] = homeSection.Type.ToString().ToLowerInvariant();
}
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();
dto.CustomPrefs["tvhome"] = displayPreferences.TvHome;
return ToOptimizedResult(dto);
} }
/// <summary> /// <summary>
@ -92,10 +119,71 @@ namespace MediaBrowser.Api
/// <param name="request">The request.</param> /// <param name="request">The request.</param>
public void Post(UpdateDisplayPreferences request) public void Post(UpdateDisplayPreferences request)
{ {
// Serialize to json and then back so that the core doesn't see the request dto type HomeSectionType[] defaults =
var displayPreferences = _jsonSerializer.DeserializeFromString<DisplayPreferences>(_jsonSerializer.SerializeToString(request)); {
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.IndexBy = Enum.TryParse<IndexingKind>(request.IndexBy, true, out var indexBy) ? indexBy : (IndexingKind?)null;
prefs.ShowBackdrop = request.ShowBackdrop;
prefs.ShowSidebar = request.ShowSidebar;
prefs.ScrollDirection = request.ScrollDirection;
prefs.ChromecastVersion = request.CustomPrefs.TryGetValue("chromecastVersion", out var chromecastVersion)
? Enum.Parse<ChromecastVersion>(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.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")))
{
var order = int.Parse(key.AsSpan().Slice("homesection".Length));
if (!Enum.TryParse<HomeSectionType>(request.CustomPrefs[key], true, out var type))
{
type = order < 7 ? defaults[order] : HomeSectionType.None;
}
prefs.HomeSections.Add(new HomeSection
{
Order = order,
Type = type
});
}
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<ViewType>(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<ViewType>(request.ViewType, true, out var viewType))
{
itemPrefs.ViewType = viewType;
}
_displayPreferencesManager.SaveChanges(prefs);
_displayPreferencesManager.SaveChanges(itemPrefs);
} }
} }
} }

View File

@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.Linq; using System.Linq;
using Jellyfin.Data.Entities; using Jellyfin.Data.Entities;
using Jellyfin.Data.Enums;
using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Dto;

View File

@ -194,7 +194,8 @@ namespace MediaBrowser.Api.Playback.Hls
var paddedBitrate = Convert.ToInt32(bitrate * 1.15); var paddedBitrate = Convert.ToInt32(bitrate * 1.15);
// Main stream // Main stream
builder.AppendLine("#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=" + paddedBitrate.ToString(CultureInfo.InvariantCulture)); builder.Append("#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=")
.AppendLine(paddedBitrate.ToString(CultureInfo.InvariantCulture));
var playlistUrl = "hls/" + Path.GetFileName(firstPlaylist).Replace(".m3u8", "/stream.m3u8"); var playlistUrl = "hls/" + Path.GetFileName(firstPlaylist).Replace(".m3u8", "/stream.m3u8");
builder.AppendLine(playlistUrl); builder.AppendLine(playlistUrl);

View File

@ -968,7 +968,8 @@ namespace MediaBrowser.Api.Playback.Hls
builder.AppendLine("#EXTM3U"); builder.AppendLine("#EXTM3U");
builder.AppendLine("#EXT-X-PLAYLIST-TYPE:VOD"); builder.AppendLine("#EXT-X-PLAYLIST-TYPE:VOD");
builder.AppendLine("#EXT-X-VERSION:3"); builder.AppendLine("#EXT-X-VERSION:3");
builder.AppendLine("#EXT-X-TARGETDURATION:" + Math.Ceiling(segmentLengths.Length > 0 ? segmentLengths.Max() : state.SegmentLength).ToString(CultureInfo.InvariantCulture)); builder.Append("#EXT-X-TARGETDURATION:")
.AppendLine(Math.Ceiling(segmentLengths.Length > 0 ? segmentLengths.Max() : state.SegmentLength).ToString(CultureInfo.InvariantCulture));
builder.AppendLine("#EXT-X-MEDIA-SEQUENCE:0"); builder.AppendLine("#EXT-X-MEDIA-SEQUENCE:0");
var queryStringIndex = Request.RawUrl.IndexOf('?'); var queryStringIndex = Request.RawUrl.IndexOf('?');
@ -983,14 +984,17 @@ namespace MediaBrowser.Api.Playback.Hls
foreach (var length in segmentLengths) foreach (var length in segmentLengths)
{ {
builder.AppendLine("#EXTINF:" + length.ToString("0.0000", CultureInfo.InvariantCulture) + ", nodesc"); builder.Append("#EXTINF:")
.Append(length.ToString("0.0000", CultureInfo.InvariantCulture))
builder.AppendLine(string.Format("hls1/{0}/{1}{2}{3}", .AppendLine(", nodesc");
builder.AppendFormat(
CultureInfo.InvariantCulture,
"hls1/{0}/{1}{2}{3}",
name, name,
index.ToString(CultureInfo.InvariantCulture), index.ToString(CultureInfo.InvariantCulture),
GetSegmentFileExtension(request), GetSegmentFileExtension(request),
queryString)); queryString).AppendLine();
index++; index++;
} }

View File

@ -175,11 +175,12 @@ namespace MediaBrowser.Api.Subtitles
throw new ArgumentException("segmentLength was not given, or it was given incorrectly. (It should be bigger than 0)"); throw new ArgumentException("segmentLength was not given, or it was given incorrectly. (It should be bigger than 0)");
} }
builder.AppendLine("#EXTM3U"); builder.AppendLine("#EXTM3U")
builder.AppendLine("#EXT-X-TARGETDURATION:" + request.SegmentLength.ToString(CultureInfo.InvariantCulture)); .Append("#EXT-X-TARGETDURATION:")
builder.AppendLine("#EXT-X-VERSION:3"); .AppendLine(request.SegmentLength.ToString(CultureInfo.InvariantCulture))
builder.AppendLine("#EXT-X-MEDIA-SEQUENCE:0"); .AppendLine("#EXT-X-VERSION:3")
builder.AppendLine("#EXT-X-PLAYLIST-TYPE:VOD"); .AppendLine("#EXT-X-MEDIA-SEQUENCE:0")
.AppendLine("#EXT-X-PLAYLIST-TYPE:VOD");
long positionTicks = 0; long positionTicks = 0;
@ -190,7 +191,9 @@ namespace MediaBrowser.Api.Subtitles
var remaining = runtime - positionTicks; var remaining = runtime - positionTicks;
var lengthTicks = Math.Min(remaining, segmentLengthTicks); var lengthTicks = Math.Min(remaining, segmentLengthTicks);
builder.AppendLine("#EXTINF:" + TimeSpan.FromTicks(lengthTicks).TotalSeconds.ToString(CultureInfo.InvariantCulture) + ","); builder.Append("#EXTINF:")
.Append(TimeSpan.FromTicks(lengthTicks).TotalSeconds.ToString(CultureInfo.InvariantCulture))
.AppendLine(",");
var endPositionTicks = Math.Min(runtime, positionTicks + segmentLengthTicks); var endPositionTicks = Math.Min(runtime, positionTicks + segmentLengthTicks);

View File

@ -1,6 +1,7 @@
using System; using System;
using System.Linq; using System.Linq;
using Jellyfin.Data.Entities; using Jellyfin.Data.Entities;
using Jellyfin.Data.Enums;
using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities;

View File

@ -2,6 +2,7 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.Linq; using System.Linq;
using Jellyfin.Data.Enums;
using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Dto;

View File

@ -1,5 +1,6 @@
using System; using System;
using System.Linq; using System.Linq;
using Jellyfin.Data.Enums;
using MediaBrowser.Model.Entities; using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Querying; using MediaBrowser.Model.Querying;
using MediaBrowser.Model.Services; using MediaBrowser.Model.Services;
@ -466,8 +467,8 @@ namespace MediaBrowser.Api.UserLibrary
var sortOrderValue = sortOrders.Length > sortOrderIndex ? sortOrders[sortOrderIndex] : null; var sortOrderValue = sortOrders.Length > sortOrderIndex ? sortOrders[sortOrderIndex] : null;
var sortOrder = string.Equals(sortOrderValue, "Descending", StringComparison.OrdinalIgnoreCase) var sortOrder = string.Equals(sortOrderValue, "Descending", StringComparison.OrdinalIgnoreCase)
? MediaBrowser.Model.Entities.SortOrder.Descending ? Jellyfin.Data.Enums.SortOrder.Descending
: MediaBrowser.Model.Entities.SortOrder.Ascending; : Jellyfin.Data.Enums.SortOrder.Ascending;
result[i] = new ValueTuple<string, SortOrder>(vals[i], sortOrder); result[i] = new ValueTuple<string, SortOrder>(vals[i], sortOrder);
} }

View File

@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.Linq; using System.Linq;
using Jellyfin.Data.Entities; using Jellyfin.Data.Entities;
using Jellyfin.Data.Enums;
using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities.Movies; using MediaBrowser.Controller.Entities.Movies;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;

View File

@ -0,0 +1,49 @@
using System;
using System.Collections.Generic;
using Jellyfin.Data.Entities;
namespace MediaBrowser.Controller
{
/// <summary>
/// Manages the storage and retrieval of display preferences.
/// </summary>
public interface IDisplayPreferencesManager
{
/// <summary>
/// Gets the display preferences for the user and client.
/// </summary>
/// <param name="userId">The user's id.</param>
/// <param name="client">The client string.</param>
/// <returns>The associated display preferences.</returns>
DisplayPreferences GetDisplayPreferences(Guid userId, string client);
/// <summary>
/// Gets the default item display preferences for the user and client.
/// </summary>
/// <param name="userId">The user id.</param>
/// <param name="itemId">The item id.</param>
/// <param name="client">The client string.</param>
/// <returns>The item display preferences.</returns>
ItemDisplayPreferences GetItemDisplayPreferences(Guid userId, Guid itemId, string client);
/// <summary>
/// Gets all of the item display preferences for the user and client.
/// </summary>
/// <param name="userId">The user id.</param>
/// <param name="client">The client string.</param>
/// <returns>A list of item display preferences.</returns>
IList<ItemDisplayPreferences> ListItemDisplayPreferences(Guid userId, string client);
/// <summary>
/// Saves changes to the provided display preferences.
/// </summary>
/// <param name="preferences">The display preferences to save.</param>
void SaveChanges(DisplayPreferences preferences);
/// <summary>
/// Saves changes to the provided item display preferences.
/// </summary>
/// <param name="preferences">The item display preferences to save.</param>
void SaveChanges(ItemDisplayPreferences preferences);
}
}

View File

@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Jellyfin.Data.Entities; using Jellyfin.Data.Entities;
using Jellyfin.Data.Enums;
using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Entities.Audio;

View File

@ -1,53 +0,0 @@
using System;
using System.Collections.Generic;
using System.Threading;
using MediaBrowser.Model.Entities;
namespace MediaBrowser.Controller.Persistence
{
/// <summary>
/// Interface IDisplayPreferencesRepository.
/// </summary>
public interface IDisplayPreferencesRepository : IRepository
{
/// <summary>
/// Saves display preferences for an item.
/// </summary>
/// <param name="displayPreferences">The display preferences.</param>
/// <param name="userId">The user id.</param>
/// <param name="client">The client.</param>
/// <param name="cancellationToken">The cancellation token.</param>
void SaveDisplayPreferences(
DisplayPreferences displayPreferences,
string userId,
string client,
CancellationToken cancellationToken);
/// <summary>
/// Saves all display preferences for a user.
/// </summary>
/// <param name="displayPreferences">The display preferences.</param>
/// <param name="userId">The user id.</param>
/// <param name="cancellationToken">The cancellation token.</param>
void SaveAllDisplayPreferences(
IEnumerable<DisplayPreferences> displayPreferences,
Guid userId,
CancellationToken cancellationToken);
/// <summary>
/// Gets the display preferences.
/// </summary>
/// <param name="displayPreferencesId">The display preferences id.</param>
/// <param name="userId">The user id.</param>
/// <param name="client">The client.</param>
/// <returns>Task{DisplayPreferences}.</returns>
DisplayPreferences GetDisplayPreferences(string displayPreferencesId, string userId, string client);
/// <summary>
/// Gets all display preferences for the given user.
/// </summary>
/// <param name="userId">The user id.</param>
/// <returns>Task{DisplayPreferences}.</returns>
IEnumerable<DisplayPreferences> GetAllDisplayPreferences(Guid userId);
}
}

View File

@ -6,6 +6,7 @@ using System.Text.Json.Serialization;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Jellyfin.Data.Entities; using Jellyfin.Data.Entities;
using Jellyfin.Data.Enums;
using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Entities.Audio;

View File

@ -1377,7 +1377,9 @@ namespace MediaBrowser.MediaEncoding.Probing
// OR -> COMMENT. SUBTITLE: DESCRIPTION // OR -> COMMENT. SUBTITLE: DESCRIPTION
// e.g. -> 4/13. The Doctor's Wife: Science fiction drama. When he follows a Time Lord distress signal, the Doctor puts Amy, Rory and his beloved TARDIS in grave danger. Also in HD. [AD,S] // e.g. -> 4/13. The Doctor's Wife: Science fiction drama. When he follows a Time Lord distress signal, the Doctor puts Amy, Rory and his beloved TARDIS in grave danger. Also in HD. [AD,S]
// e.g. -> CBeebies Bedtime Hour. The Mystery: Animated adventures of two friends who live on an island in the middle of the big city. Some of Abney and Teal's favourite objects are missing. [S] // e.g. -> CBeebies Bedtime Hour. The Mystery: Animated adventures of two friends who live on an island in the middle of the big city. Some of Abney and Teal's favourite objects are missing. [S]
if (string.IsNullOrWhiteSpace(subTitle) && !string.IsNullOrWhiteSpace(description) && description.Substring(0, Math.Min(description.Length, MaxSubtitleDescriptionExtractionLength)).Contains(":")) // Check within the Subtitle size limit, otherwise from description it can get too long creating an invalid filename if (string.IsNullOrWhiteSpace(subTitle)
&& !string.IsNullOrWhiteSpace(description)
&& description.AsSpan().Slice(0, Math.Min(description.Length, MaxSubtitleDescriptionExtractionLength)).IndexOf(':') != -1) // Check within the Subtitle size limit, otherwise from description it can get too long creating an invalid filename
{ {
string[] parts = description.Split(':'); string[] parts = description.Split(':');
if (parts.Length > 0) if (parts.Length > 0)
@ -1385,7 +1387,7 @@ namespace MediaBrowser.MediaEncoding.Probing
string subtitle = parts[0]; string subtitle = parts[0];
try try
{ {
if (subtitle.Contains("/")) // It contains a episode number and season number if (subtitle.Contains('/', StringComparison.Ordinal)) // It contains a episode number and season number
{ {
string[] numbers = subtitle.Split(' '); string[] numbers = subtitle.Split(' ');
video.IndexNumber = int.Parse(numbers[0].Replace(".", "").Split('/')[0]); video.IndexNumber = int.Parse(numbers[0].Replace(".", "").Split('/')[0]);
@ -1400,8 +1402,11 @@ namespace MediaBrowser.MediaEncoding.Probing
} }
catch // Default parsing catch // Default parsing
{ {
if (subtitle.Contains(".")) // skip the comment, keep the subtitle if (subtitle.Contains('.', StringComparison.Ordinal))
{
// skip the comment, keep the subtitle
description = string.Join(".", subtitle.Split('.'), 1, subtitle.Split('.').Length - 1).Trim(); // skip the first description = string.Join(".", subtitle.Split('.'), 1, subtitle.Split('.').Length - 1).Trim(); // skip the first
}
else else
{ {
description = subtitle.Trim(); // Clean up whitespaces and save it description = subtitle.Trim(); // Clean up whitespaces and save it

View File

@ -731,19 +731,19 @@ namespace MediaBrowser.MediaEncoding.Subtitles
var date = _fileSystem.GetLastWriteTimeUtc(mediaPath); var date = _fileSystem.GetLastWriteTimeUtc(mediaPath);
var filename = (mediaPath + "_" + subtitleStreamIndex.ToString(CultureInfo.InvariantCulture) + "_" + date.Ticks.ToString(CultureInfo.InvariantCulture) + ticksParam).GetMD5() + outputSubtitleExtension; ReadOnlySpan<char> filename = (mediaPath + "_" + subtitleStreamIndex.ToString(CultureInfo.InvariantCulture) + "_" + date.Ticks.ToString(CultureInfo.InvariantCulture) + ticksParam).GetMD5() + outputSubtitleExtension;
var prefix = filename.Substring(0, 1); var prefix = filename.Slice(0, 1);
return Path.Combine(SubtitleCachePath, prefix, filename); return Path.Join(SubtitleCachePath, prefix, filename);
} }
else else
{ {
var filename = (mediaPath + "_" + subtitleStreamIndex.ToString(CultureInfo.InvariantCulture)).GetMD5() + outputSubtitleExtension; ReadOnlySpan<char> filename = (mediaPath + "_" + subtitleStreamIndex.ToString(CultureInfo.InvariantCulture)).GetMD5() + outputSubtitleExtension;
var prefix = filename.Substring(0, 1); var prefix = filename.Slice(0, 1);
return Path.Combine(SubtitleCachePath, prefix, filename); return Path.Join(SubtitleCachePath, prefix, filename);
} }
} }

View File

@ -1,6 +1,6 @@
#pragma warning disable CS1591 #pragma warning disable CS1591
using MediaBrowser.Model.Entities; using Jellyfin.Data.Enums;
namespace MediaBrowser.Model.Dlna namespace MediaBrowser.Model.Dlna
{ {

View File

@ -1,22 +1,18 @@
#nullable disable #nullable disable
using System.Collections.Generic; using System.Collections.Generic;
using Jellyfin.Data.Enums;
namespace MediaBrowser.Model.Entities namespace MediaBrowser.Model.Entities
{ {
/// <summary> /// <summary>
/// Defines the display preferences for any item that supports them (usually Folders). /// Defines the display preferences for any item that supports them (usually Folders).
/// </summary> /// </summary>
public class DisplayPreferences public class DisplayPreferencesDto
{ {
/// <summary> /// <summary>
/// The image scale. /// Initializes a new instance of the <see cref="DisplayPreferencesDto" /> class.
/// </summary> /// </summary>
private const double ImageScale = .9; public DisplayPreferencesDto()
/// <summary>
/// Initializes a new instance of the <see cref="DisplayPreferences" /> class.
/// </summary>
public DisplayPreferences()
{ {
RememberIndexing = false; RememberIndexing = false;
PrimaryImageHeight = 250; PrimaryImageHeight = 250;

View File

@ -1,18 +0,0 @@
namespace MediaBrowser.Model.Entities
{
/// <summary>
/// Enum ScrollDirection.
/// </summary>
public enum ScrollDirection
{
/// <summary>
/// The horizontal.
/// </summary>
Horizontal,
/// <summary>
/// The vertical.
/// </summary>
Vertical
}
}

View File

@ -1,18 +0,0 @@
namespace MediaBrowser.Model.Entities
{
/// <summary>
/// Enum SortOrder.
/// </summary>
public enum SortOrder
{
/// <summary>
/// The ascending.
/// </summary>
Ascending,
/// <summary>
/// The descending.
/// </summary>
Descending
}
}

View File

@ -2,7 +2,7 @@
#pragma warning disable CS1591 #pragma warning disable CS1591
using System; using System;
using MediaBrowser.Model.Entities; using Jellyfin.Data.Enums;
namespace MediaBrowser.Model.LiveTv namespace MediaBrowser.Model.LiveTv
{ {

View File

@ -1,6 +1,6 @@
#pragma warning disable CS1591 #pragma warning disable CS1591
using MediaBrowser.Model.Entities; using Jellyfin.Data.Enums;
namespace MediaBrowser.Model.LiveTv namespace MediaBrowser.Model.LiveTv
{ {

View File

@ -93,7 +93,7 @@ namespace MediaBrowser.Providers.MediaInfo
private string GetAudioImagePath(Audio item) private string GetAudioImagePath(Audio item)
{ {
string filename = null; string filename;
if (item.GetType() == typeof(Audio)) if (item.GetType() == typeof(Audio))
{ {
@ -116,9 +116,9 @@ namespace MediaBrowser.Providers.MediaInfo
filename = item.Id.ToString("N", CultureInfo.InvariantCulture) + ".jpg"; filename = item.Id.ToString("N", CultureInfo.InvariantCulture) + ".jpg";
} }
var prefix = filename.Substring(0, 1); var prefix = filename.AsSpan().Slice(0, 1);
return Path.Combine(AudioImagesPath, prefix, filename); return Path.Join(AudioImagesPath, prefix, filename);
} }
public string AudioImagesPath => Path.Combine(_config.ApplicationPaths.CachePath, "extracted-audio-images"); public string AudioImagesPath => Path.Combine(_config.ApplicationPaths.CachePath, "extracted-audio-images");

View File

@ -170,7 +170,7 @@ namespace MediaBrowser.Providers.Plugins.Omdb
item.SetProviderId(MetadataProvider.Imdb, result.imdbID); item.SetProviderId(MetadataProvider.Imdb, result.imdbID);
if (result.Year.Length > 0 if (result.Year.Length > 0
&& int.TryParse(result.Year.Substring(0, Math.Min(result.Year.Length, 4)), NumberStyles.Integer, CultureInfo.InvariantCulture, out var parsedYear)) && int.TryParse(result.Year.AsSpan().Slice(0, Math.Min(result.Year.Length, 4)), NumberStyles.Integer, CultureInfo.InvariantCulture, out var parsedYear))
{ {
item.ProductionYear = parsedYear; item.ProductionYear = parsedYear;
} }

View File

@ -62,7 +62,7 @@ namespace MediaBrowser.Providers.Plugins.Omdb
} }
if (!string.IsNullOrEmpty(result.Year) && result.Year.Length >= 4 if (!string.IsNullOrEmpty(result.Year) && result.Year.Length >= 4
&& int.TryParse(result.Year.Substring(0, 4), NumberStyles.Number, _usCulture, out var year) && int.TryParse(result.Year.AsSpan().Slice(0, 4), NumberStyles.Number, _usCulture, out var year)
&& year >= 0) && year >= 0)
{ {
item.ProductionYear = year; item.ProductionYear = year;
@ -163,7 +163,7 @@ namespace MediaBrowser.Providers.Plugins.Omdb
} }
if (!string.IsNullOrEmpty(result.Year) && result.Year.Length >= 4 if (!string.IsNullOrEmpty(result.Year) && result.Year.Length >= 4
&& int.TryParse(result.Year.Substring(0, 4), NumberStyles.Number, _usCulture, out var year) && int.TryParse(result.Year.AsSpan().Slice(0, 4), NumberStyles.Number, _usCulture, out var year)
&& year >= 0) && year >= 0)
{ {
item.ProductionYear = year; item.ProductionYear = year;

View File

@ -188,7 +188,7 @@ namespace MediaBrowser.Providers.Plugins.TheTvdb
for (var i = 0; i < episode.GuestStars.Length; ++i) for (var i = 0; i < episode.GuestStars.Length; ++i)
{ {
var currentActor = episode.GuestStars[i]; var currentActor = episode.GuestStars[i];
var roleStartIndex = currentActor.IndexOf('('); var roleStartIndex = currentActor.IndexOf('(', StringComparison.Ordinal);
if (roleStartIndex == -1) if (roleStartIndex == -1)
{ {
@ -207,7 +207,7 @@ namespace MediaBrowser.Providers.Plugins.TheTvdb
for (var j = i + 1; j < episode.GuestStars.Length; ++j) for (var j = i + 1; j < episode.GuestStars.Length; ++j)
{ {
var currentRole = episode.GuestStars[j]; var currentRole = episode.GuestStars[j];
var roleEndIndex = currentRole.IndexOf(')'); var roleEndIndex = currentRole.IndexOf(')', StringComparison.Ordinal);
if (roleEndIndex == -1) if (roleEndIndex == -1)
{ {

View File

@ -13,7 +13,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Models.Movies
public BelongsToCollection Belongs_To_Collection { get; set; } public BelongsToCollection Belongs_To_Collection { get; set; }
public int Budget { get; set; } public long Budget { get; set; }
public List<Genre> Genres { get; set; } public List<Genre> Genres { get; set; }
@ -39,7 +39,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Models.Movies
public string Release_Date { get; set; } public string Release_Date { get; set; }
public int Revenue { get; set; } public long Revenue { get; set; }
public int Runtime { get; set; } public int Runtime { get; set; }

View File

@ -251,9 +251,9 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.People
private static string GetPersonDataPath(IApplicationPaths appPaths, string tmdbId) private static string GetPersonDataPath(IApplicationPaths appPaths, string tmdbId)
{ {
var letter = tmdbId.GetMD5().ToString().Substring(0, 1); var letter = tmdbId.GetMD5().ToString().AsSpan().Slice(0, 1);
return Path.Combine(GetPersonsDataPath(appPaths), letter, tmdbId); return Path.Join(GetPersonsDataPath(appPaths), letter, tmdbId);
} }
internal static string GetPersonDataFilePath(IApplicationPaths appPaths, string tmdbId) internal static string GetPersonDataFilePath(IApplicationPaths appPaths, string tmdbId)

View File

@ -222,8 +222,16 @@ namespace MediaBrowser.XbmcMetadata.Parsers
if (index != -1) if (index != -1)
{ {
var tmdbId = xml.Substring(index + srch.Length).TrimEnd('/').Split('-')[0]; var tmdbId = xml.AsSpan().Slice(index + srch.Length).TrimEnd('/');
if (!string.IsNullOrWhiteSpace(tmdbId) && int.TryParse(tmdbId, NumberStyles.Integer, CultureInfo.InvariantCulture, out var value)) index = tmdbId.IndexOf('-');
if (index != -1)
{
tmdbId = tmdbId.Slice(0, index);
}
if (!tmdbId.IsEmpty
&& !tmdbId.IsWhiteSpace()
&& int.TryParse(tmdbId, NumberStyles.Integer, CultureInfo.InvariantCulture, out var value))
{ {
item.SetProviderId(MetadataProvider.Tmdb, value.ToString(UsCulture)); item.SetProviderId(MetadataProvider.Tmdb, value.ToString(UsCulture));
} }
@ -237,8 +245,10 @@ namespace MediaBrowser.XbmcMetadata.Parsers
if (index != -1) if (index != -1)
{ {
var tvdbId = xml.Substring(index + srch.Length).TrimEnd('/'); var tvdbId = xml.AsSpan().Slice(index + srch.Length).TrimEnd('/');
if (!string.IsNullOrWhiteSpace(tvdbId) && int.TryParse(tvdbId, NumberStyles.Integer, CultureInfo.InvariantCulture, out var value)) if (!tvdbId.IsEmpty
&& !tvdbId.IsWhiteSpace()
&& int.TryParse(tvdbId, NumberStyles.Integer, CultureInfo.InvariantCulture, out var value))
{ {
item.SetProviderId(MetadataProvider.Tvdb, value.ToString(UsCulture)); item.SetProviderId(MetadataProvider.Tvdb, value.ToString(UsCulture));
} }
@ -442,8 +452,8 @@ namespace MediaBrowser.XbmcMetadata.Parsers
{ {
var val = reader.ReadElementContentAsString(); var val = reader.ReadElementContentAsString();
var hasAspectRatio = item as IHasAspectRatio; if (!string.IsNullOrWhiteSpace(val)
if (!string.IsNullOrWhiteSpace(val) && hasAspectRatio != null) && item is IHasAspectRatio hasAspectRatio)
{ {
hasAspectRatio.AspectRatio = val; hasAspectRatio.AspectRatio = val;
} }

View File

@ -162,6 +162,7 @@ The following sections describe some more advanced scenarios for running the ser
It is not necessary to host the frontend web client as part of the backend server. Hosting these two components separately may be useful for frontend developers who would prefer to host the client in a separate webpack development server for a tighter development loop. See the [jellyfin-web](https://github.com/jellyfin/jellyfin-web#getting-started) repo for instructions on how to do this. It is not necessary to host the frontend web client as part of the backend server. Hosting these two components separately may be useful for frontend developers who would prefer to host the client in a separate webpack development server for a tighter development loop. See the [jellyfin-web](https://github.com/jellyfin/jellyfin-web#getting-started) repo for instructions on how to do this.
To instruct the server not to host the web content, there is a `nowebcontent` configuration flag that must be set. This can specified using the command line switch `--nowebcontent` or the environment variable `JELLYFIN_NOWEBCONTENT=true`. To instruct the server not to host the web content, there is a `nowebclient` configuration flag that must be set. This can specified using the command line
switch `--nowebclient` or the environment variable `JELLYFIN_NOWEBCONTENT=true`.
Since this is a common scenario, there is also a separate launch profile defined for Visual Studio called `Jellyfin.Server (nowebcontent)` that can be selected from the 'Start Debugging' dropdown in the main toolbar. Since this is a common scenario, there is also a separate launch profile defined for Visual Studio called `Jellyfin.Server (nowebcontent)` that can be selected from the 'Start Debugging' dropdown in the main toolbar.

View File

@ -1,4 +1,4 @@
using System.Reflection; using System.Reflection;
[assembly: AssemblyVersion("10.6.0")] [assembly: AssemblyVersion("10.7.0")]
[assembly: AssemblyFileVersion("10.6.0")] [assembly: AssemblyFileVersion("10.7.0")]

View File

@ -1,7 +1,7 @@
--- ---
# We just wrap `build` so this is really it # We just wrap `build` so this is really it
name: "jellyfin" name: "jellyfin"
version: "10.6.0" version: "10.7.0"
packages: packages:
- debian.amd64 - debian.amd64
- debian.arm64 - debian.arm64

View File

@ -4,6 +4,7 @@
set -o errexit set -o errexit
set -o pipefail set -o pipefail
set -o xtrace
usage() { usage() {
echo -e "bump_version - increase the shared version and generate changelogs" echo -e "bump_version - increase the shared version and generate changelogs"
@ -58,7 +59,7 @@ sed -i "s/${old_version_sed}/${new_version}/g" ${debian_equivs_file}
debian_changelog_file="debian/changelog" debian_changelog_file="debian/changelog"
debian_changelog_temp="$( mktemp )" debian_changelog_temp="$( mktemp )"
# Create new temp file with our changelog # Create new temp file with our changelog
echo -e "jellyfin (${new_version_deb}) unstable; urgency=medium echo -e "jellyfin-server (${new_version_deb}) unstable; urgency=medium
* New upstream version ${new_version}; release changelog at https://github.com/jellyfin/jellyfin/releases/tag/v${new_version} * New upstream version ${new_version}; release changelog at https://github.com/jellyfin/jellyfin/releases/tag/v${new_version}

6
debian/changelog vendored
View File

@ -1,3 +1,9 @@
jellyfin-server (10.7.0-1) unstable; urgency=medium
* Forthcoming stable release
-- Jellyfin Packaging Team <packaging@jellyfin.org> Mon, 27 Jul 2020 19:09:45 -0400
jellyfin-server (10.6.0-2) unstable; urgency=medium jellyfin-server (10.6.0-2) unstable; urgency=medium
* Fix upgrade bug * Fix upgrade bug

View File

@ -5,7 +5,7 @@ Homepage: https://jellyfin.org
Standards-Version: 3.9.2 Standards-Version: 3.9.2
Package: jellyfin Package: jellyfin
Version: 10.6.0 Version: 10.7.0
Maintainer: Jellyfin Packaging Team <packaging@jellyfin.org> Maintainer: Jellyfin Packaging Team <packaging@jellyfin.org>
Depends: jellyfin-server, jellyfin-web Depends: jellyfin-server, jellyfin-web
Description: Provides the Jellyfin Free Software Media System Description: Provides the Jellyfin Free Software Media System

View File

@ -7,7 +7,7 @@
%endif %endif
Name: jellyfin Name: jellyfin
Version: 10.6.0 Version: 10.7.0
Release: 1%{?dist} Release: 1%{?dist}
Summary: The Free Software Media System Summary: The Free Software Media System
License: GPLv3 License: GPLv3
@ -139,6 +139,8 @@ fi
%systemd_postun_with_restart jellyfin.service %systemd_postun_with_restart jellyfin.service
%changelog %changelog
* Mon Jul 27 2020 Jellyfin Packaging Team <packaging@jellyfin.org>
- Forthcoming stable release
* Mon Mar 23 2020 Jellyfin Packaging Team <packaging@jellyfin.org> * Mon Mar 23 2020 Jellyfin Packaging Team <packaging@jellyfin.org>
- Forthcoming stable release - Forthcoming stable release
* Fri Oct 11 2019 Jellyfin Packaging Team <packaging@jellyfin.org> * Fri Oct 11 2019 Jellyfin Packaging Team <packaging@jellyfin.org>