pull/2601/head
commit
99bf6edba2
@ -0,0 +1,96 @@
|
||||
{
|
||||
"ServerNameNeedsToBeRestarted": "{0} ir vajadzīgs restarts",
|
||||
"NotificationOptionTaskFailed": "Plānota uzdevuma kļūme",
|
||||
"HeaderRecordingGroups": "Ierakstu Grupas",
|
||||
"UserPolicyUpdatedWithName": "Lietotāju politika atjaunota priekš {0}",
|
||||
"SubtitleDownloadFailureFromForItem": "Subtitru lejupielāde no {0} priekš {1} neizdevās",
|
||||
"NotificationOptionVideoPlaybackStopped": "Video atskaņošana apturēta",
|
||||
"NotificationOptionVideoPlayback": "Video atskaņošana sākta",
|
||||
"NotificationOptionInstallationFailed": "Instalācija neizdevās",
|
||||
"AuthenticationSucceededWithUserName": "{0} veiksmīgi autentificējies",
|
||||
"ValueSpecialEpisodeName": "Speciālais - {0}",
|
||||
"ScheduledTaskStartedWithName": "{0} iesākts",
|
||||
"ScheduledTaskFailedWithName": "{0} neizdevās",
|
||||
"Photos": "Attēli",
|
||||
"NotificationOptionUserLockedOut": "Lietotājs bloķēts",
|
||||
"LabelRunningTimeValue": "Garums: {0}",
|
||||
"Inherit": "Mantot",
|
||||
"AppDeviceValues": "Lietotne:{0}, Ierīce:{1}",
|
||||
"VersionNumber": "Versija {0}",
|
||||
"ValueHasBeenAddedToLibrary": "{0} ir ticis pievienots tavai multvides bibliotēkai",
|
||||
"UserStoppedPlayingItemWithValues": "{0} ir beidzis atskaņot {1} uz {2}",
|
||||
"UserStartedPlayingItemWithValues": "{0} atskaņo {1} uz {2}",
|
||||
"UserPasswordChangedWithName": "Parole nomainīta lietotājam {0}",
|
||||
"UserOnlineFromDevice": "{0} ir tiešsaistē no {1}",
|
||||
"UserOfflineFromDevice": "{0} ir atvienojies no {1}",
|
||||
"UserLockedOutWithName": "Lietotājs {0} ir ticis bloķēts",
|
||||
"UserDownloadingItemWithValues": "{0} lejupielādē {1}",
|
||||
"UserDeletedWithName": "Lietotājs {0} ir izdzēsts",
|
||||
"UserCreatedWithName": "Lietotājs {0} ir ticis izveidots",
|
||||
"User": "Lietotājs",
|
||||
"TvShows": "TV Raidījumi",
|
||||
"Sync": "Sinhronizācija",
|
||||
"System": "Sistēma",
|
||||
"SubtitlesDownloadedForItem": "Subtitri lejupielādēti priekš {0}",
|
||||
"StartupEmbyServerIsLoading": "Jellyfin Serveris lādējas. Lūdzu mēģiniet vēlreiz pēc brīža.",
|
||||
"Songs": "Dziesmas",
|
||||
"Shows": "Raidījumi",
|
||||
"PluginUpdatedWithName": "{0} tika atjaunots",
|
||||
"PluginUninstalledWithName": "{0} tika noņemts",
|
||||
"PluginInstalledWithName": "{0} tika uzstādīts",
|
||||
"Plugin": "Paplašinājums",
|
||||
"Playlists": "Atskaņošanas Saraksti",
|
||||
"MixedContent": "Jaukts saturs",
|
||||
"HomeVideos": "Mājas Video",
|
||||
"HeaderNextUp": "Nākamais",
|
||||
"ChapterNameValue": "Nodaļa {0}",
|
||||
"Application": "Lietotne",
|
||||
"NotificationOptionServerRestartRequired": "Vajadzīgs servera restarts",
|
||||
"NotificationOptionPluginUpdateInstalled": "Paplašinājuma atjauninājums uzstādīts",
|
||||
"NotificationOptionPluginUninstalled": "Paplašinājums noņemts",
|
||||
"NotificationOptionPluginInstalled": "Paplašinājums uzstādīts",
|
||||
"NotificationOptionPluginError": "Paplašinājuma kļūda",
|
||||
"NotificationOptionNewLibraryContent": "Jauns saturs pievienots",
|
||||
"NotificationOptionCameraImageUploaded": "Kameras attēls augšupielādēts",
|
||||
"NotificationOptionAudioPlaybackStopped": "Audio atskaņošana apturēta",
|
||||
"NotificationOptionAudioPlayback": "Audio atskaņošana sākta",
|
||||
"NotificationOptionApplicationUpdateInstalled": "Lietotnes atjauninājums uzstādīts",
|
||||
"NotificationOptionApplicationUpdateAvailable": "Lietotnes atjauninājums pieejams",
|
||||
"NewVersionIsAvailable": "Lejupielādei ir pieejama jauna Jellyfin Server versija.",
|
||||
"NameSeasonUnknown": "Nezināma Sezona",
|
||||
"NameSeasonNumber": "Sezona {0}",
|
||||
"NameInstallFailed": "{0} instalācija neizdevās",
|
||||
"MusicVideos": "Mūzikas video",
|
||||
"Music": "Mūzika",
|
||||
"Movies": "Filmas",
|
||||
"MessageServerConfigurationUpdated": "Servera konfigurācija ir tikusi atjaunota",
|
||||
"MessageNamedServerConfigurationUpdatedWithValue": "Servera konfigurācijas sadaļa {0} ir tikusi atjaunota",
|
||||
"MessageApplicationUpdatedTo": "Jellyfin Server ir ticis atjaunots uz {0}",
|
||||
"MessageApplicationUpdated": "Jellyfin Server ir ticis atjaunots",
|
||||
"Latest": "Jaunākais",
|
||||
"LabelIpAddressValue": "IP adrese: {0}",
|
||||
"ItemRemovedWithName": "{0} tika noņemts no bibliotēkas",
|
||||
"ItemAddedWithName": "{0} tika pievienots bibliotēkai",
|
||||
"HeaderLiveTV": "Tiešraides TV",
|
||||
"HeaderContinueWatching": "Turpināt Skatīšanos",
|
||||
"HeaderCameraUploads": "Kameras augšupielādes",
|
||||
"HeaderAlbumArtists": "Albumu Izpildītāji",
|
||||
"Genres": "Žanri",
|
||||
"Folders": "Mapes",
|
||||
"Favorites": "Favorīti",
|
||||
"FailedLoginAttemptWithUserName": "Neizdevies pieslēgšanās mēģinājums no {0}",
|
||||
"DeviceOnlineWithName": "{0} ir pievienojies",
|
||||
"DeviceOfflineWithName": "{0} ir atvienojies",
|
||||
"Collections": "Kolekcijas",
|
||||
"Channels": "Kanāli",
|
||||
"CameraImageUploadedFrom": "Jauns kameras attēls ir ticis augšupielādēts no {0}",
|
||||
"Books": "Grāmatas",
|
||||
"Artists": "Izpildītāji",
|
||||
"Albums": "Albumi",
|
||||
"ProviderValue": "Provider: {0}",
|
||||
"HeaderFavoriteSongs": "Dziesmu Favorīti",
|
||||
"HeaderFavoriteShows": "Raidījumu Favorīti",
|
||||
"HeaderFavoriteEpisodes": "Episožu Favorīti",
|
||||
"HeaderFavoriteArtists": "Izpildītāju Favorīti",
|
||||
"HeaderFavoriteAlbums": "Albumu Favorīti"
|
||||
}
|
@ -1,99 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using MediaBrowser.Controller.Sorting;
|
||||
|
||||
namespace Emby.Server.Implementations.Sorting
|
||||
{
|
||||
public class AlphanumComparator : IComparer<string>
|
||||
{
|
||||
public static int CompareValues(string s1, string s2)
|
||||
{
|
||||
if (s1 == null || s2 == null)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
int thisMarker = 0, thisNumericChunk = 0;
|
||||
int thatMarker = 0, thatNumericChunk = 0;
|
||||
|
||||
while ((thisMarker < s1.Length) || (thatMarker < s2.Length))
|
||||
{
|
||||
if (thisMarker >= s1.Length)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
else if (thatMarker >= s2.Length)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
char thisCh = s1[thisMarker];
|
||||
char thatCh = s2[thatMarker];
|
||||
|
||||
var thisChunk = new StringBuilder();
|
||||
var thatChunk = new StringBuilder();
|
||||
|
||||
while ((thisMarker < s1.Length) && (thisChunk.Length == 0 || SortHelper.InChunk(thisCh, thisChunk[0])))
|
||||
{
|
||||
thisChunk.Append(thisCh);
|
||||
thisMarker++;
|
||||
|
||||
if (thisMarker < s1.Length)
|
||||
{
|
||||
thisCh = s1[thisMarker];
|
||||
}
|
||||
}
|
||||
|
||||
while ((thatMarker < s2.Length) && (thatChunk.Length == 0 || SortHelper.InChunk(thatCh, thatChunk[0])))
|
||||
{
|
||||
thatChunk.Append(thatCh);
|
||||
thatMarker++;
|
||||
|
||||
if (thatMarker < s2.Length)
|
||||
{
|
||||
thatCh = s2[thatMarker];
|
||||
}
|
||||
}
|
||||
|
||||
int result = 0;
|
||||
// If both chunks contain numeric characters, sort them numerically
|
||||
if (char.IsDigit(thisChunk[0]) && char.IsDigit(thatChunk[0]))
|
||||
{
|
||||
if (!int.TryParse(thisChunk.ToString(), out thisNumericChunk))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
if (!int.TryParse(thatChunk.ToString(), out thatNumericChunk))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (thisNumericChunk < thatNumericChunk)
|
||||
{
|
||||
result = -1;
|
||||
}
|
||||
|
||||
if (thisNumericChunk > thatNumericChunk)
|
||||
{
|
||||
result = 1;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
result = thisChunk.ToString().CompareTo(thatChunk.ToString());
|
||||
}
|
||||
|
||||
if (result != 0)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
public int Compare(string x, string y)
|
||||
{
|
||||
return CompareValues(x, y);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
using System;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Jellyfin.Server.Migrations
|
||||
{
|
||||
/// <summary>
|
||||
/// Interface that describes a migration routine.
|
||||
/// </summary>
|
||||
internal interface IMigrationRoutine
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the unique id for this migration. This should never be modified after the migration has been created.
|
||||
/// </summary>
|
||||
public Guid Id { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the display name of the migration.
|
||||
/// </summary>
|
||||
public string Name { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Execute the migration routine.
|
||||
/// </summary>
|
||||
/// <param name="host">Host that hosts current version.</param>
|
||||
/// <param name="logger">Host logger.</param>
|
||||
public void Perform(CoreAppHost host, ILogger logger);
|
||||
}
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Jellyfin.Server.Migrations
|
||||
{
|
||||
/// <summary>
|
||||
/// Configuration part that holds all migrations that were applied.
|
||||
/// </summary>
|
||||
public class MigrationOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="MigrationOptions"/> class.
|
||||
/// </summary>
|
||||
public MigrationOptions()
|
||||
{
|
||||
Applied = new List<(Guid Id, string Name)>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the list of applied migration routine names.
|
||||
/// </summary>
|
||||
public List<(Guid Id, string Name)> Applied { get; }
|
||||
}
|
||||
}
|
@ -0,0 +1,73 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using MediaBrowser.Common.Configuration;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Jellyfin.Server.Migrations
|
||||
{
|
||||
/// <summary>
|
||||
/// The class that knows which migrations to apply and how to apply them.
|
||||
/// </summary>
|
||||
public sealed class MigrationRunner
|
||||
{
|
||||
/// <summary>
|
||||
/// The list of known migrations, in order of applicability.
|
||||
/// </summary>
|
||||
internal static readonly IMigrationRoutine[] Migrations =
|
||||
{
|
||||
new Routines.DisableTranscodingThrottling(),
|
||||
new Routines.CreateUserLoggingConfigFile()
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Run all needed migrations.
|
||||
/// </summary>
|
||||
/// <param name="host">CoreAppHost that hosts current version.</param>
|
||||
/// <param name="loggerFactory">Factory for making the logger.</param>
|
||||
public static void Run(CoreAppHost host, ILoggerFactory loggerFactory)
|
||||
{
|
||||
var logger = loggerFactory.CreateLogger<MigrationRunner>();
|
||||
var migrationOptions = ((IConfigurationManager)host.ServerConfigurationManager).GetConfiguration<MigrationOptions>(MigrationsListStore.StoreKey);
|
||||
|
||||
if (!host.ServerConfigurationManager.Configuration.IsStartupWizardCompleted && migrationOptions.Applied.Count == 0)
|
||||
{
|
||||
// If startup wizard is not finished, this is a fresh install.
|
||||
// Don't run any migrations, just mark all of them as applied.
|
||||
logger.LogInformation("Marking all known migrations as applied because this is a fresh install");
|
||||
migrationOptions.Applied.AddRange(Migrations.Select(m => (m.Id, m.Name)));
|
||||
host.ServerConfigurationManager.SaveConfiguration(MigrationsListStore.StoreKey, migrationOptions);
|
||||
return;
|
||||
}
|
||||
|
||||
var appliedMigrationIds = migrationOptions.Applied.Select(m => m.Id).ToHashSet();
|
||||
|
||||
for (var i = 0; i < Migrations.Length; i++)
|
||||
{
|
||||
var migrationRoutine = Migrations[i];
|
||||
if (appliedMigrationIds.Contains(migrationRoutine.Id))
|
||||
{
|
||||
logger.LogDebug("Skipping migration '{Name}' since it is already applied", migrationRoutine.Name);
|
||||
continue;
|
||||
}
|
||||
|
||||
logger.LogInformation("Applying migration '{Name}'", migrationRoutine.Name);
|
||||
|
||||
try
|
||||
{
|
||||
migrationRoutine.Perform(host, logger);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogError(ex, "Could not apply migration '{Name}'", migrationRoutine.Name);
|
||||
throw;
|
||||
}
|
||||
|
||||
// Mark the migration as completed
|
||||
logger.LogInformation("Migration '{Name}' applied successfully", migrationRoutine.Name);
|
||||
migrationOptions.Applied.Add((migrationRoutine.Id, migrationRoutine.Name));
|
||||
host.ServerConfigurationManager.SaveConfiguration(MigrationsListStore.StoreKey, migrationOptions);
|
||||
logger.LogDebug("Migration '{Name}' marked as applied in configuration.", migrationRoutine.Name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
using System.Collections.Generic;
|
||||
using MediaBrowser.Common.Configuration;
|
||||
|
||||
namespace Jellyfin.Server.Migrations
|
||||
{
|
||||
/// <summary>
|
||||
/// A factory that can find a persistent file of the migration configuration, which lists all applied migrations.
|
||||
/// </summary>
|
||||
public class MigrationsFactory : IConfigurationFactory
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public IEnumerable<ConfigurationStore> GetConfigurations()
|
||||
{
|
||||
return new[]
|
||||
{
|
||||
new MigrationsListStore()
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
using MediaBrowser.Common.Configuration;
|
||||
|
||||
namespace Jellyfin.Server.Migrations
|
||||
{
|
||||
/// <summary>
|
||||
/// A configuration that lists all the migration routines that were applied.
|
||||
/// </summary>
|
||||
public class MigrationsListStore : ConfigurationStore
|
||||
{
|
||||
/// <summary>
|
||||
/// The name of the configuration in the storage.
|
||||
/// </summary>
|
||||
public static readonly string StoreKey = "migrations";
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="MigrationsListStore"/> class.
|
||||
/// </summary>
|
||||
public MigrationsListStore()
|
||||
{
|
||||
ConfigurationType = typeof(MigrationOptions);
|
||||
Key = StoreKey;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,73 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using MediaBrowser.Common.Configuration;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace Jellyfin.Server.Migrations.Routines
|
||||
{
|
||||
/// <summary>
|
||||
/// Migration to initialize the user logging configuration file "logging.user.json".
|
||||
/// If the deprecated logging.json file exists and has a custom config, it will be used as logging.user.json,
|
||||
/// otherwise a blank file will be created.
|
||||
/// </summary>
|
||||
internal class CreateUserLoggingConfigFile : IMigrationRoutine
|
||||
{
|
||||
/// <summary>
|
||||
/// File history for logging.json as existed during this migration creation. The contents for each has been minified.
|
||||
/// </summary>
|
||||
private readonly List<string> _defaultConfigHistory = new List<string>
|
||||
{
|
||||
// 9a6c27947353585391e211aa88b925f81e8cd7b9
|
||||
@"{""Serilog"":{""MinimumLevel"":{""Default"":""Information"",""Override"":{""Microsoft"":""Warning"",""System"":""Warning""}},""WriteTo"":[{""Name"":""Console"",""Args"":{""outputTemplate"":""[{Timestamp:HH:mm:ss}] [{Level:u3}] [{ThreadId}] {SourceContext}: {Message:lj}{NewLine}{Exception}""}},{""Name"":""Async"",""Args"":{""configure"":[{""Name"":""File"",""Args"":{""path"":""%JELLYFIN_LOG_DIR%//log_.log"",""rollingInterval"":""Day"",""retainedFileCountLimit"":3,""rollOnFileSizeLimit"":true,""fileSizeLimitBytes"":100000000,""outputTemplate"":""[{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz}] [{Level:u3}] [{ThreadId}] {SourceContext}: {Message}{NewLine}{Exception}""}}]}}],""Enrich"":[""FromLogContext"",""WithThreadId""]}}",
|
||||
// 71bdcd730705a714ee208eaad7290b7c68df3885
|
||||
@"{""Serilog"":{""MinimumLevel"":""Information"",""WriteTo"":[{""Name"":""Console"",""Args"":{""outputTemplate"":""[{Timestamp:HH:mm:ss}] [{Level:u3}] [{ThreadId}] {SourceContext}: {Message:lj}{NewLine}{Exception}""}},{""Name"":""Async"",""Args"":{""configure"":[{""Name"":""File"",""Args"":{""path"":""%JELLYFIN_LOG_DIR%//log_.log"",""rollingInterval"":""Day"",""retainedFileCountLimit"":3,""rollOnFileSizeLimit"":true,""fileSizeLimitBytes"":100000000,""outputTemplate"":""[{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz}] [{Level:u3}] [{ThreadId}] {SourceContext}: {Message}{NewLine}{Exception}""}}]}}],""Enrich"":[""FromLogContext"",""WithThreadId""]}}",
|
||||
// a44936f97f8afc2817d3491615a7cfe1e31c251c
|
||||
@"{""Serilog"":{""MinimumLevel"":""Information"",""WriteTo"":[{""Name"":""Console"",""Args"":{""outputTemplate"":""[{Timestamp:HH:mm:ss}] [{Level:u3}] {Message:lj}{NewLine}{Exception}""}},{""Name"":""File"",""Args"":{""path"":""%JELLYFIN_LOG_DIR%//log_.log"",""rollingInterval"":""Day"",""outputTemplate"":""[{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz}] [{Level:u3}] {Message}{NewLine}{Exception}""}}]}}",
|
||||
// 7af3754a11ad5a4284f107997fb5419a010ce6f3
|
||||
@"{""Serilog"":{""MinimumLevel"":""Information"",""WriteTo"":[{""Name"":""Console"",""Args"":{""outputTemplate"":""[{Timestamp:HH:mm:ss}] [{Level:u3}] {Message:lj}{NewLine}{Exception}""}},{""Name"":""Async"",""Args"":{""configure"":[{""Name"":""File"",""Args"":{""path"":""%JELLYFIN_LOG_DIR%//log_.log"",""rollingInterval"":""Day"",""outputTemplate"":""[{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz}] [{Level:u3}] {Message}{NewLine}{Exception}""}}]}}]}}",
|
||||
// 60691349a11f541958e0b2247c9abc13cb40c9fb
|
||||
@"{""Serilog"":{""MinimumLevel"":""Information"",""WriteTo"":[{""Name"":""Console"",""Args"":{""outputTemplate"":""[{Timestamp:HH:mm:ss}] [{Level:u3}] {Message:lj}{NewLine}{Exception}""}},{""Name"":""Async"",""Args"":{""configure"":[{""Name"":""File"",""Args"":{""path"":""%JELLYFIN_LOG_DIR%//log_.log"",""rollingInterval"":""Day"",""retainedFileCountLimit"":3,""rollOnFileSizeLimit"":true,""fileSizeLimitBytes"":100000000,""outputTemplate"":""[{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz}] [{Level:u3}] {Message}{NewLine}{Exception}""}}]}}]}}",
|
||||
// 65fe243afbcc4b596cf8726708c1965cd34b5f68
|
||||
@"{""Serilog"":{""MinimumLevel"":""Information"",""WriteTo"":[{""Name"":""Console"",""Args"":{""outputTemplate"":""[{Timestamp:HH:mm:ss}] [{Level:u3}] {ThreadId} {SourceContext}: {Message:lj} {NewLine}{Exception}""}},{""Name"":""Async"",""Args"":{""configure"":[{""Name"":""File"",""Args"":{""path"":""%JELLYFIN_LOG_DIR%//log_.log"",""rollingInterval"":""Day"",""retainedFileCountLimit"":3,""rollOnFileSizeLimit"":true,""fileSizeLimitBytes"":100000000,""outputTemplate"":""[{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz}] [{Level:u3}] {ThreadId} {SourceContext}:{Message} {NewLine}{Exception}""}}]}}],""Enrich"":[""FromLogContext"",""WithThreadId""]}}",
|
||||
// 96c9af590494aa8137d5a061aaf1e68feee60b67
|
||||
@"{""Serilog"":{""MinimumLevel"":""Information"",""WriteTo"":[{""Name"":""Console"",""Args"":{""outputTemplate"":""[{Timestamp:HH:mm:ss}] [{Level:u3}] [{ThreadId}] {SourceContext}: {Message:lj}{NewLine}{Exception}""}},{""Name"":""Async"",""Args"":{""configure"":[{""Name"":""File"",""Args"":{""path"":""%JELLYFIN_LOG_DIR%//log_.log"",""rollingInterval"":""Day"",""retainedFileCountLimit"":3,""rollOnFileSizeLimit"":true,""fileSizeLimitBytes"":100000000,""outputTemplate"":""[{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz}] [{Level:u3}] [{ThreadId}] {SourceContext}:{Message}{NewLine}{Exception}""}}]}}],""Enrich"":[""FromLogContext"",""WithThreadId""]}}",
|
||||
};
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Guid Id => Guid.Parse("{EF103419-8451-40D8-9F34-D1A8E93A1679}");
|
||||
|
||||
/// <inheritdoc/>
|
||||
public string Name => "CreateLoggingConfigHeirarchy";
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Perform(CoreAppHost host, ILogger logger)
|
||||
{
|
||||
var logDirectory = host.Resolve<IApplicationPaths>().ConfigurationDirectoryPath;
|
||||
var existingConfigPath = Path.Combine(logDirectory, "logging.json");
|
||||
|
||||
// If the existing logging.json config file is unmodified, then 'reset' it by moving it to 'logging.old.json'
|
||||
// NOTE: This config file has 'reloadOnChange: true', so this change will take effect immediately even though it has already been loaded
|
||||
if (File.Exists(existingConfigPath) && ExistingConfigUnmodified(existingConfigPath))
|
||||
{
|
||||
File.Move(existingConfigPath, Path.Combine(logDirectory, "logging.old.json"));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if the existing logging.json file has not been modified by the user by comparing it to all the
|
||||
/// versions in our git history. Until now, the file has never been migrated after first creation so users
|
||||
/// could have any version from the git history.
|
||||
/// </summary>
|
||||
/// <exception cref="IOException"><paramref name="oldConfigPath"/> does not exist or could not be read.</exception>
|
||||
private bool ExistingConfigUnmodified(string oldConfigPath)
|
||||
{
|
||||
var existingConfigJson = JToken.Parse(File.ReadAllText(oldConfigPath));
|
||||
return _defaultConfigHistory
|
||||
.Select(historicalConfigText => JToken.Parse(historicalConfigText))
|
||||
.Any(historicalConfigJson => JToken.DeepEquals(existingConfigJson, historicalConfigJson));
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using MediaBrowser.Common.Configuration;
|
||||
using MediaBrowser.Model.Configuration;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Jellyfin.Server.Migrations.Routines
|
||||
{
|
||||
/// <summary>
|
||||
/// Disable transcode throttling for all installations since it is currently broken for certain video formats.
|
||||
/// </summary>
|
||||
internal class DisableTranscodingThrottling : IMigrationRoutine
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public Guid Id => Guid.Parse("{4124C2CD-E939-4FFB-9BE9-9B311C413638}");
|
||||
|
||||
/// <inheritdoc/>
|
||||
public string Name => "DisableTranscodingThrottling";
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Perform(CoreAppHost host, ILogger logger)
|
||||
{
|
||||
// Set EnableThrottling to false since it wasn't used before and may introduce issues
|
||||
var encoding = ((IConfigurationManager)host.ServerConfigurationManager).GetConfiguration<EncodingOptions>("encoding");
|
||||
if (encoding.EnableThrottling)
|
||||
{
|
||||
logger.LogInformation("Disabling transcoding throttling during migration");
|
||||
encoding.EnableThrottling = false;
|
||||
|
||||
host.ServerConfigurationManager.SaveConfiguration("encoding", encoding);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,135 @@
|
||||
#nullable enable
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace MediaBrowser.Controller.Sorting
|
||||
{
|
||||
public class AlphanumComparator : IComparer<string?>
|
||||
{
|
||||
public static int CompareValues(string? s1, string? s2)
|
||||
{
|
||||
if (s1 == null && s2 == null)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
else if (s1 == null)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
else if (s2 == null)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
int len1 = s1.Length;
|
||||
int len2 = s2.Length;
|
||||
|
||||
// Early return for empty strings
|
||||
if (len1 == 0 && len2 == 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
else if (len1 == 0)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
else if (len2 == 0)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
int pos1 = 0;
|
||||
int pos2 = 0;
|
||||
|
||||
do
|
||||
{
|
||||
int start1 = pos1;
|
||||
int start2 = pos2;
|
||||
|
||||
bool isNum1 = char.IsDigit(s1[pos1++]);
|
||||
bool isNum2 = char.IsDigit(s2[pos2++]);
|
||||
|
||||
while (pos1 < len1 && char.IsDigit(s1[pos1]) == isNum1)
|
||||
{
|
||||
pos1++;
|
||||
}
|
||||
|
||||
while (pos2 < len2 && char.IsDigit(s2[pos2]) == isNum2)
|
||||
{
|
||||
pos2++;
|
||||
}
|
||||
|
||||
var span1 = s1.AsSpan(start1, pos1 - start1);
|
||||
var span2 = s2.AsSpan(start2, pos2 - start2);
|
||||
|
||||
if (isNum1 && isNum2)
|
||||
{
|
||||
// Trim leading zeros so we can compare the length
|
||||
// of the strings to find the largest number
|
||||
span1 = span1.TrimStart('0');
|
||||
span2 = span2.TrimStart('0');
|
||||
var span1Len = span1.Length;
|
||||
var span2Len = span2.Length;
|
||||
if (span1Len < span2Len)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
else if (span1Len > span2Len)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
else if (span1Len >= 20) // Number is probably too big for a ulong
|
||||
{
|
||||
// Trim all the first digits that are the same
|
||||
int i = 0;
|
||||
while (i < span1Len && span1[i] == span2[i])
|
||||
{
|
||||
i++;
|
||||
}
|
||||
|
||||
// If there are no more digits it's the same number
|
||||
if (i == span1Len)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Only need to compare the most significant digit
|
||||
span1 = span1.Slice(i, 1);
|
||||
span2 = span2.Slice(i, 1);
|
||||
}
|
||||
|
||||
if (!ulong.TryParse(span1, out var num1)
|
||||
|| !ulong.TryParse(span2, out var num2))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
else if (num1 < num2)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
else if (num1 > num2)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
int result = span1.CompareTo(span2, StringComparison.InvariantCulture);
|
||||
if (result != 0)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
}
|
||||
} while (pos1 < len1 && pos2 < len2);
|
||||
|
||||
return len1 - len2;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public int Compare(string x, string y)
|
||||
{
|
||||
return CompareValues(x, y);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,25 +0,0 @@
|
||||
namespace MediaBrowser.Controller.Sorting
|
||||
{
|
||||
public static class SortHelper
|
||||
{
|
||||
private enum ChunkType { Alphanumeric, Numeric };
|
||||
|
||||
public static bool InChunk(char ch, char otherCh)
|
||||
{
|
||||
var type = ChunkType.Alphanumeric;
|
||||
|
||||
if (char.IsDigit(otherCh))
|
||||
{
|
||||
type = ChunkType.Numeric;
|
||||
}
|
||||
|
||||
if ((type == ChunkType.Alphanumeric && char.IsDigit(ch))
|
||||
|| (type == ChunkType.Numeric && !char.IsDigit(ch)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue