aboutsummaryrefslogtreecommitdiff
path: root/Jellyfin.Server
diff options
context:
space:
mode:
Diffstat (limited to 'Jellyfin.Server')
-rw-r--r--Jellyfin.Server/CoreAppHost.cs14
-rw-r--r--Jellyfin.Server/Jellyfin.Server.csproj1
-rw-r--r--Jellyfin.Server/Migrations/MigrationRunner.cs4
-rw-r--r--Jellyfin.Server/Migrations/Routines/MigrateActivityLogDb.cs133
-rw-r--r--Jellyfin.Server/Migrations/Routines/RemoveDuplicateExtras.cs79
-rw-r--r--Jellyfin.Server/Program.cs7
-rw-r--r--Jellyfin.Server/Startup.cs1
7 files changed, 232 insertions, 7 deletions
diff --git a/Jellyfin.Server/CoreAppHost.cs b/Jellyfin.Server/CoreAppHost.cs
index f678e714c..331a32c73 100644
--- a/Jellyfin.Server/CoreAppHost.cs
+++ b/Jellyfin.Server/CoreAppHost.cs
@@ -1,12 +1,17 @@
using System;
using System.Collections.Generic;
+using System.IO;
using System.Reflection;
using Emby.Drawing;
using Emby.Server.Implementations;
using Jellyfin.Drawing.Skia;
+using Jellyfin.Server.Implementations;
+using Jellyfin.Server.Implementations.Activity;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Drawing;
+using MediaBrowser.Model.Activity;
using MediaBrowser.Model.IO;
+using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
@@ -56,6 +61,15 @@ namespace Jellyfin.Server
Logger.LogWarning($"Skia not available. Will fallback to {nameof(NullImageEncoder)}.");
}
+ // TODO: Set up scoping and use AddDbContextPool
+ serviceCollection.AddDbContext<JellyfinDb>(
+ options => options.UseSqlite($"Filename={Path.Combine(ApplicationPaths.DataPath, "jellyfin.db")}"),
+ ServiceLifetime.Transient);
+
+ serviceCollection.AddSingleton<JellyfinDbProvider>();
+
+ serviceCollection.AddSingleton<IActivityManager, ActivityManager>();
+
base.RegisterServices(serviceCollection);
}
diff --git a/Jellyfin.Server/Jellyfin.Server.csproj b/Jellyfin.Server/Jellyfin.Server.csproj
index 88114d999..9eec6ed4e 100644
--- a/Jellyfin.Server/Jellyfin.Server.csproj
+++ b/Jellyfin.Server/Jellyfin.Server.csproj
@@ -60,6 +60,7 @@
<ProjectReference Include="..\Emby.Drawing\Emby.Drawing.csproj" />
<ProjectReference Include="..\Emby.Server.Implementations\Emby.Server.Implementations.csproj" />
<ProjectReference Include="..\Jellyfin.Drawing.Skia\Jellyfin.Drawing.Skia.csproj" />
+ <ProjectReference Include="..\Jellyfin.Server.Implementations\Jellyfin.Server.Implementations.csproj" />
</ItemGroup>
</Project>
diff --git a/Jellyfin.Server/Migrations/MigrationRunner.cs b/Jellyfin.Server/Migrations/MigrationRunner.cs
index ca1748282..473f62737 100644
--- a/Jellyfin.Server/Migrations/MigrationRunner.cs
+++ b/Jellyfin.Server/Migrations/MigrationRunner.cs
@@ -17,7 +17,9 @@ namespace Jellyfin.Server.Migrations
private static readonly Type[] _migrationTypes =
{
typeof(Routines.DisableTranscodingThrottling),
- typeof(Routines.CreateUserLoggingConfigFile)
+ typeof(Routines.CreateUserLoggingConfigFile),
+ typeof(Routines.MigrateActivityLogDb),
+ typeof(Routines.RemoveDuplicateExtras)
};
/// <summary>
diff --git a/Jellyfin.Server/Migrations/Routines/MigrateActivityLogDb.cs b/Jellyfin.Server/Migrations/Routines/MigrateActivityLogDb.cs
new file mode 100644
index 000000000..b3cc29708
--- /dev/null
+++ b/Jellyfin.Server/Migrations/Routines/MigrateActivityLogDb.cs
@@ -0,0 +1,133 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using Emby.Server.Implementations.Data;
+using Jellyfin.Data.Entities;
+using Jellyfin.Server.Implementations;
+using MediaBrowser.Controller;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.Extensions.Logging;
+using SQLitePCL.pretty;
+
+namespace Jellyfin.Server.Migrations.Routines
+{
+ /// <summary>
+ /// The migration routine for migrating the activity log database to EF Core.
+ /// </summary>
+ public class MigrateActivityLogDb : IMigrationRoutine
+ {
+ private const string DbFilename = "activitylog.db";
+
+ private readonly ILogger<MigrateActivityLogDb> _logger;
+ private readonly JellyfinDbProvider _provider;
+ private readonly IServerApplicationPaths _paths;
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="MigrateActivityLogDb"/> class.
+ /// </summary>
+ /// <param name="logger">The logger.</param>
+ /// <param name="paths">The server application paths.</param>
+ /// <param name="provider">The database provider.</param>
+ public MigrateActivityLogDb(ILogger<MigrateActivityLogDb> logger, IServerApplicationPaths paths, JellyfinDbProvider provider)
+ {
+ _logger = logger;
+ _provider = provider;
+ _paths = paths;
+ }
+
+ /// <inheritdoc/>
+ public Guid Id => Guid.Parse("3793eb59-bc8c-456c-8b9f-bd5a62a42978");
+
+ /// <inheritdoc/>
+ public string Name => "MigrateActivityLogDatabase";
+
+ /// <inheritdoc/>
+ public void Perform()
+ {
+ var logLevelDictionary = new Dictionary<string, LogLevel>(StringComparer.OrdinalIgnoreCase)
+ {
+ { "None", LogLevel.None },
+ { "Trace", LogLevel.Trace },
+ { "Debug", LogLevel.Debug },
+ { "Information", LogLevel.Information },
+ { "Info", LogLevel.Information },
+ { "Warn", LogLevel.Warning },
+ { "Warning", LogLevel.Warning },
+ { "Error", LogLevel.Error },
+ { "Critical", LogLevel.Critical }
+ };
+
+ var dataPath = _paths.DataPath;
+ using (var connection = SQLite3.Open(
+ Path.Combine(dataPath, DbFilename),
+ ConnectionFlags.ReadOnly,
+ null))
+ {
+ _logger.LogWarning("Migrating the activity database may take a while, do not stop Jellyfin.");
+ using var dbContext = _provider.CreateContext();
+
+ var queryResult = connection.Query("SELECT * FROM ActivityLog ORDER BY Id ASC");
+
+ // Make sure that the database is empty in case of failed migration due to power outages, etc.
+ dbContext.ActivityLogs.RemoveRange(dbContext.ActivityLogs);
+ dbContext.SaveChanges();
+ // Reset the autoincrement counter
+ dbContext.Database.ExecuteSqlRaw("UPDATE sqlite_sequence SET seq = 0 WHERE name = 'ActivityLog';");
+ dbContext.SaveChanges();
+
+ var newEntries = queryResult.Select(entry =>
+ {
+ if (!logLevelDictionary.TryGetValue(entry[8].ToString(), out var severity))
+ {
+ severity = LogLevel.Trace;
+ }
+
+ var newEntry = new ActivityLog(
+ entry[1].ToString(),
+ entry[4].ToString(),
+ entry[6].SQLiteType == SQLiteType.Null ? Guid.Empty : Guid.Parse(entry[6].ToString()))
+ {
+ DateCreated = entry[7].ReadDateTime(),
+ LogSeverity = severity
+ };
+
+ if (entry[2].SQLiteType != SQLiteType.Null)
+ {
+ newEntry.Overview = entry[2].ToString();
+ }
+
+ if (entry[3].SQLiteType != SQLiteType.Null)
+ {
+ newEntry.ShortOverview = entry[3].ToString();
+ }
+
+ if (entry[5].SQLiteType != SQLiteType.Null)
+ {
+ newEntry.ItemId = entry[5].ToString();
+ }
+
+ return newEntry;
+ });
+
+ dbContext.ActivityLogs.AddRange(newEntries);
+ dbContext.SaveChanges();
+ }
+
+ try
+ {
+ File.Move(Path.Combine(dataPath, DbFilename), Path.Combine(dataPath, DbFilename + ".old"));
+
+ var journalPath = Path.Combine(dataPath, DbFilename + "-journal");
+ if (File.Exists(journalPath))
+ {
+ File.Move(journalPath, Path.Combine(dataPath, DbFilename + ".old-journal"));
+ }
+ }
+ catch (IOException e)
+ {
+ _logger.LogError(e, "Error renaming legacy activity log database to 'activitylog.db.old'");
+ }
+ }
+ }
+}
diff --git a/Jellyfin.Server/Migrations/Routines/RemoveDuplicateExtras.cs b/Jellyfin.Server/Migrations/Routines/RemoveDuplicateExtras.cs
new file mode 100644
index 000000000..e95536388
--- /dev/null
+++ b/Jellyfin.Server/Migrations/Routines/RemoveDuplicateExtras.cs
@@ -0,0 +1,79 @@
+using System;
+using System.Globalization;
+using System.IO;
+
+using MediaBrowser.Controller;
+using Microsoft.Extensions.Logging;
+using SQLitePCL.pretty;
+
+namespace Jellyfin.Server.Migrations.Routines
+{
+ /// <summary>
+ /// Remove duplicate entries which were caused by a bug where a file was considered to be an "Extra" to itself.
+ /// </summary>
+ internal class RemoveDuplicateExtras : IMigrationRoutine
+ {
+ private const string DbFilename = "library.db";
+ private readonly ILogger _logger;
+ private readonly IServerApplicationPaths _paths;
+
+ public RemoveDuplicateExtras(ILogger<RemoveDuplicateExtras> logger, IServerApplicationPaths paths)
+ {
+ _logger = logger;
+ _paths = paths;
+ }
+
+ /// <inheritdoc/>
+ public Guid Id => Guid.Parse("{ACBE17B7-8435-4A83-8B64-6FCF162CB9BD}");
+
+ /// <inheritdoc/>
+ public string Name => "RemoveDuplicateExtras";
+
+ /// <inheritdoc/>
+ public void Perform()
+ {
+ var dataPath = _paths.DataPath;
+ var dbPath = Path.Combine(dataPath, DbFilename);
+ using (var connection = SQLite3.Open(
+ dbPath,
+ ConnectionFlags.ReadWrite,
+ null))
+ {
+ // Query the database for the ids of duplicate extras
+ var queryResult = connection.Query("SELECT t1.Path FROM TypedBaseItems AS t1, TypedBaseItems AS t2 WHERE t1.Path=t2.Path AND t1.Type!=t2.Type AND t1.Type='MediaBrowser.Controller.Entities.Video'");
+ var bads = string.Join(", ", queryResult.SelectScalarString());
+
+ // Do nothing if no duplicate extras were detected
+ if (bads.Length == 0)
+ {
+ _logger.LogInformation("No duplicate extras detected, skipping migration.");
+ return;
+ }
+
+ // Back up the database before deleting any entries
+ for (int i = 1; ; i++)
+ {
+ var bakPath = string.Format(CultureInfo.InvariantCulture, "{0}.bak{1}", dbPath, i);
+ if (!File.Exists(bakPath))
+ {
+ try
+ {
+ File.Copy(dbPath, bakPath);
+ _logger.LogInformation("Library database backed up to {BackupPath}", bakPath);
+ break;
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Cannot make a backup of {Library} at path {BackupPath}", DbFilename, bakPath);
+ throw;
+ }
+ }
+ }
+
+ // Delete all duplicate extras
+ _logger.LogInformation("Removing found duplicated extras for the following items: {DuplicateExtras}", bads);
+ connection.Execute("DELETE FROM TypedBaseItems WHERE rowid IN (SELECT t1.rowid FROM TypedBaseItems AS t1, TypedBaseItems AS t2 WHERE t1.Path=t2.Path AND t1.Type!=t2.Type AND t1.Type='MediaBrowser.Controller.Entities.Video')");
+ }
+ }
+ }
+}
diff --git a/Jellyfin.Server/Program.cs b/Jellyfin.Server/Program.cs
index ae423532e..b9895386f 100644
--- a/Jellyfin.Server/Program.cs
+++ b/Jellyfin.Server/Program.cs
@@ -10,14 +10,11 @@ using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using CommandLine;
-using Emby.Drawing;
using Emby.Server.Implementations;
using Emby.Server.Implementations.HttpServer;
using Emby.Server.Implementations.IO;
using Emby.Server.Implementations.Networking;
-using Jellyfin.Drawing.Skia;
using MediaBrowser.Common.Configuration;
-using MediaBrowser.Controller.Drawing;
using MediaBrowser.Controller.Extensions;
using MediaBrowser.WebDashboard.Api;
using Microsoft.AspNetCore.Hosting;
@@ -297,7 +294,7 @@ namespace Jellyfin.Server
{
_logger.LogInformation("Kestrel listening on {IpAddress}", address);
options.Listen(address, appHost.HttpPort);
- if (appHost.EnableHttps && appHost.Certificate != null)
+ if (appHost.ListenWithHttps)
{
options.Listen(address, appHost.HttpsPort, listenOptions =>
{
@@ -327,7 +324,7 @@ namespace Jellyfin.Server
_logger.LogInformation("Kestrel listening on all interfaces");
options.ListenAnyIP(appHost.HttpPort);
- if (appHost.EnableHttps && appHost.Certificate != null)
+ if (appHost.ListenWithHttps)
{
options.ListenAnyIP(appHost.HttpsPort, listenOptions =>
{
diff --git a/Jellyfin.Server/Startup.cs b/Jellyfin.Server/Startup.cs
index 8bcfd1350..5f9a5c161 100644
--- a/Jellyfin.Server/Startup.cs
+++ b/Jellyfin.Server/Startup.cs
@@ -64,7 +64,6 @@ namespace Jellyfin.Server
app.UseResponseCompression();
// TODO app.UseMiddleware<WebSocketMiddleware>();
- app.Use(serverApplicationHost.ExecuteWebsocketHandlerAsync);
// TODO use when old API is removed: app.UseAuthentication();
app.UseJellyfinApiSwagger();