move migration functions to a new file (#395)

* move migration functions to a new file

* check if index.html exits
This commit is contained in:
Kilian von Pflugk 2024-11-24 14:21:46 +00:00 committed by GitHub
parent d48ea90190
commit 62683ede87
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 191 additions and 190 deletions

View File

@ -0,0 +1,183 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using System.Xml;
using System.Xml.Serialization;
using IntroSkipper.Configuration;
using IntroSkipper.Data;
using IntroSkipper.Db;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Model.Updates;
using Microsoft.Extensions.Logging;
namespace IntroSkipper.Helper;
internal static class LegacyMigrations
{
public static void MigrateAll(
Plugin plugin,
IServerConfigurationManager serverConfiguration,
ILogger logger,
IApplicationPaths applicationPaths)
{
var pluginDirName = "introskipper";
var introPath = Path.Join(applicationPaths.DataPath, pluginDirName, "intros.xml");
var creditsPath = Path.Join(applicationPaths.DataPath, pluginDirName, "credits.xml");
// Migrate XML files from XMLSchema to DataContract
XmlSerializationHelper.MigrateXML(introPath);
XmlSerializationHelper.MigrateXML(creditsPath);
MigrateConfig(plugin, applicationPaths.PluginConfigurationsPath, logger);
MigrateRepoUrl(plugin, serverConfiguration, logger);
InjectSkipButton(plugin, applicationPaths.WebPath, logger);
RestoreTimestamps(plugin.DbPath, introPath, creditsPath);
}
private static void MigrateConfig(Plugin plugin, string pluginConfigurationsPath, ILogger logger)
{
var oldConfigFile = Path.Join(pluginConfigurationsPath, "ConfusedPolarBear.Plugin.IntroSkipper.xml");
if (File.Exists(oldConfigFile))
{
try
{
XmlSerializer serializer = new XmlSerializer(typeof(PluginConfiguration));
using FileStream fileStream = new FileStream(oldConfigFile, FileMode.Open);
var settings = new XmlReaderSettings
{
DtdProcessing = DtdProcessing.Prohibit, // Disable DTD processing
XmlResolver = null // Disable the XmlResolver
};
using var reader = XmlReader.Create(fileStream, settings);
if (serializer.Deserialize(reader) is PluginConfiguration oldConfig)
{
plugin.UpdateConfiguration(oldConfig);
fileStream.Close();
File.Delete(oldConfigFile);
}
}
catch (Exception ex)
{
// Handle exceptions, such as file not found, deserialization errors, etc.
logger.LogWarning("Something stupid happened: {Exception}", ex);
}
}
}
private static void MigrateRepoUrl(Plugin plugin, IServerConfigurationManager serverConfiguration, ILogger logger)
{
try
{
List<string> oldRepos =
[
"https://raw.githubusercontent.com/intro-skipper/intro-skipper/master/manifest.json",
"https://raw.githubusercontent.com/jumoog/intro-skipper/master/manifest.json",
"https://manifest.intro-skipper.workers.dev/manifest.json"
];
var config = serverConfiguration.Configuration;
var pluginRepositories = config.PluginRepositories.ToList();
if (pluginRepositories.Exists(repo => repo.Url != null && oldRepos.Contains(repo.Url)))
{
pluginRepositories.RemoveAll(repo => repo.Url != null && oldRepos.Contains(repo.Url));
if (!pluginRepositories.Exists(repo => repo.Url == "https://manifest.intro-skipper.org/manifest.json") && plugin.Configuration.OverrideManifestUrl)
{
pluginRepositories.Add(new RepositoryInfo
{
Name = "intro skipper (automatically migrated by plugin)",
Url = "https://manifest.intro-skipper.org/manifest.json",
Enabled = true,
});
}
config.PluginRepositories = [.. pluginRepositories];
serverConfiguration.SaveConfiguration();
}
}
catch (Exception ex)
{
logger.LogError(ex, "Error occurred while migrating repo URL");
}
}
private static void InjectSkipButton(Plugin plugin, string webPath, ILogger logger)
{
string pattern;
string indexPath = Path.Join(webPath, "index.html");
if (File.Exists(indexPath))
{
logger.LogDebug("Reading index.html from {Path}", indexPath);
string contents = File.ReadAllText(indexPath);
if (!plugin.Configuration.SkipButtonEnabled)
{
pattern = @"<script src=""configurationpage\?name=skip-intro-button\.js.*<\/script>";
if (!Regex.IsMatch(contents, pattern, RegexOptions.IgnoreCase))
{
return;
}
contents = Regex.Replace(contents, pattern, string.Empty, RegexOptions.IgnoreCase);
File.WriteAllText(indexPath, contents);
return;
}
string scriptTag = "<script src=\"configurationpage?name=skip-intro-button.js&release=" + plugin.GetType().Assembly.GetName().Version + "\"></script>";
if (contents.Contains(scriptTag, StringComparison.OrdinalIgnoreCase))
{
logger.LogInformation("The skip button has already been injected.");
return;
}
pattern = @"<script src=""configurationpage\?name=skip-intro-button\.js.*<\/script>";
contents = Regex.Replace(contents, pattern, string.Empty, RegexOptions.IgnoreCase);
Regex headEnd = new Regex(@"</head>", RegexOptions.IgnoreCase, TimeSpan.FromSeconds(1));
contents = headEnd.Replace(contents, scriptTag + "</head>", 1);
File.WriteAllText(indexPath, contents);
logger.LogInformation("Skip button added successfully.");
}
else
{
logger.LogInformation("Jellyfin running as nowebclient");
}
}
private static void RestoreTimestamps(string dbPath, string introPath, string creditsPath)
{
using var db = new IntroSkipperDbContext(dbPath);
// Import intros
if (File.Exists(introPath))
{
var introList = XmlSerializationHelper.DeserializeFromXml<Segment>(introPath);
foreach (var intro in introList)
{
db.DbSegment.Add(new DbSegment(intro, AnalysisMode.Introduction));
}
}
// Import credits
if (File.Exists(creditsPath))
{
var creditList = XmlSerializationHelper.DeserializeFromXml<Segment>(creditsPath);
foreach (var credit in creditList)
{
db.DbSegment.Add(new DbSegment(credit, AnalysisMode.Credits));
}
}
db.SaveChanges();
File.Delete(introPath);
File.Delete(creditsPath);
}
}

View File

@ -6,10 +6,7 @@ using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Text.RegularExpressions;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Xml;
using System.Xml.Serialization;
using IntroSkipper.Configuration; using IntroSkipper.Configuration;
using IntroSkipper.Data; using IntroSkipper.Data;
using IntroSkipper.Db; using IntroSkipper.Db;
@ -23,7 +20,6 @@ using MediaBrowser.Controller.Persistence;
using MediaBrowser.Model.Entities; using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Plugins; using MediaBrowser.Model.Plugins;
using MediaBrowser.Model.Serialization; using MediaBrowser.Model.Serialization;
using MediaBrowser.Model.Updates;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
@ -37,8 +33,6 @@ public class Plugin : BasePlugin<PluginConfiguration>, IHasWebPages
private readonly ILibraryManager _libraryManager; private readonly ILibraryManager _libraryManager;
private readonly IItemRepository _itemRepository; private readonly IItemRepository _itemRepository;
private readonly ILogger<Plugin> _logger; private readonly ILogger<Plugin> _logger;
private readonly string _introPath;
private readonly string _creditsPath;
private readonly string _dbPath; private readonly string _dbPath;
/// <summary> /// <summary>
@ -74,8 +68,7 @@ public class Plugin : BasePlugin<PluginConfiguration>, IHasWebPages
var introsDirectory = Path.Join(applicationPaths.DataPath, pluginDirName); var introsDirectory = Path.Join(applicationPaths.DataPath, pluginDirName);
FingerprintCachePath = Path.Join(introsDirectory, pluginCachePath); FingerprintCachePath = Path.Join(introsDirectory, pluginCachePath);
_introPath = Path.Join(applicationPaths.DataPath, pluginDirName, "intros.xml");
_creditsPath = Path.Join(applicationPaths.DataPath, pluginDirName, "credits.xml");
_dbPath = Path.Join(applicationPaths.DataPath, pluginDirName, "introskipper.db"); _dbPath = Path.Join(applicationPaths.DataPath, pluginDirName, "introskipper.db");
// Create the base & cache directories (if needed). // Create the base & cache directories (if needed).
@ -84,66 +77,24 @@ public class Plugin : BasePlugin<PluginConfiguration>, IHasWebPages
Directory.CreateDirectory(FingerprintCachePath); Directory.CreateDirectory(FingerprintCachePath);
} }
// migrate from XMLSchema to DataContract try
XmlSerializationHelper.MigrateXML(_introPath);
XmlSerializationHelper.MigrateXML(_creditsPath);
var oldConfigFile = Path.Join(applicationPaths.PluginConfigurationsPath, "ConfusedPolarBear.Plugin.IntroSkipper.xml");
if (File.Exists(oldConfigFile))
{ {
try LegacyMigrations.MigrateAll(this, serverConfiguration, logger, applicationPaths);
{ }
XmlSerializer serializer = new XmlSerializer(typeof(PluginConfiguration)); catch (Exception ex)
using FileStream fileStream = new FileStream(oldConfigFile, FileMode.Open); {
var settings = new XmlReaderSettings logger.LogError("Failed to perform migrations. Error: {Error}", ex);
{
DtdProcessing = DtdProcessing.Prohibit, // Disable DTD processing
XmlResolver = null // Disable the XmlResolver
};
using var reader = XmlReader.Create(fileStream, settings);
if (serializer.Deserialize(reader) is PluginConfiguration oldConfig)
{
Instance.UpdateConfiguration(oldConfig);
fileStream.Close();
File.Delete(oldConfigFile);
}
}
catch (Exception ex)
{
// Handle exceptions, such as file not found, deserialization errors, etc.
_logger.LogWarning("Something stupid happened: {Exception}", ex);
}
} }
MigrateRepoUrl(serverConfiguration);
// Initialize database, restore timestamps if available. // Initialize database, restore timestamps if available.
try try
{ {
using var db = new IntroSkipperDbContext(_dbPath); using var db = new IntroSkipperDbContext(_dbPath);
db.ApplyMigrations(); db.ApplyMigrations();
if (File.Exists(_introPath) || File.Exists(_creditsPath))
{
RestoreTimestamps();
}
} }
catch (Exception ex) catch (Exception ex)
{ {
_logger.LogWarning("Error initializing database: {Exception}", ex); logger.LogWarning("Error initializing database: {Exception}", ex);
}
// Inject the skip intro button code into the web interface.
try
{
InjectSkipButton(applicationPaths.WebPath);
}
catch (Exception ex)
{
WarningManager.SetFlag(PluginWarning.UnableToAddSkipButton);
_logger.LogError("Failed to add skip button to web interface. See https://github.com/intro-skipper/intro-skipper/wiki/Troubleshooting#skip-button-is-not-visible for the most common issues. Error: {Error}", ex);
} }
FFmpegWrapper.CheckFFmpegVersion(); FFmpegWrapper.CheckFFmpegVersion();
@ -195,38 +146,6 @@ public class Plugin : BasePlugin<PluginConfiguration>, IHasWebPages
/// </summary> /// </summary>
public static Plugin? Instance { get; private set; } public static Plugin? Instance { get; private set; }
/// <summary>
/// Restore previous analysis results from disk.
/// </summary>
public void RestoreTimestamps()
{
using var db = new IntroSkipperDbContext(_dbPath);
// Import intros
if (File.Exists(_introPath))
{
var introList = XmlSerializationHelper.DeserializeFromXml<Segment>(_introPath);
foreach (var intro in introList)
{
db.DbSegment.Add(new DbSegment(intro, AnalysisMode.Introduction));
}
}
// Import credits
if (File.Exists(_creditsPath))
{
var creditList = XmlSerializationHelper.DeserializeFromXml<Segment>(_creditsPath);
foreach (var credit in creditList)
{
db.DbSegment.Add(new DbSegment(credit, AnalysisMode.Credits));
}
}
db.SaveChanges();
File.Delete(_introPath);
File.Delete(_creditsPath);
}
/// <inheritdoc /> /// <inheritdoc />
public IEnumerable<PluginPageInfo> GetPages() public IEnumerable<PluginPageInfo> GetPages()
{ {
@ -404,105 +323,4 @@ public class Plugin : BasePlugin<PluginConfiguration>, IHasWebPages
db.DbSeasonInfo.RemoveRange(obsoleteSeasons); db.DbSeasonInfo.RemoveRange(obsoleteSeasons);
await db.SaveChangesAsync().ConfigureAwait(false); await db.SaveChangesAsync().ConfigureAwait(false);
} }
private void MigrateRepoUrl(IServerConfigurationManager serverConfiguration)
{
try
{
List<string> oldRepos =
[
"https://raw.githubusercontent.com/intro-skipper/intro-skipper/master/manifest.json",
"https://raw.githubusercontent.com/jumoog/intro-skipper/master/manifest.json",
"https://manifest.intro-skipper.workers.dev/manifest.json"
];
// Access the current server configuration
var config = serverConfiguration.Configuration;
// Get the list of current plugin repositories
var pluginRepositories = config.PluginRepositories.ToList();
// check if old plugins exits
if (pluginRepositories.Exists(repo => repo.Url != null && oldRepos.Contains(repo.Url)))
{
// remove all old plugins
pluginRepositories.RemoveAll(repo => repo.Url != null && oldRepos.Contains(repo.Url));
// Add repository only if it does not exit and the OverideManifestUrl Option is activated
if (!pluginRepositories.Exists(repo => repo.Url == "https://manifest.intro-skipper.org/manifest.json") && Instance!.Configuration.OverrideManifestUrl)
{
// Add the new repository to the list
pluginRepositories.Add(new RepositoryInfo
{
Name = "intro skipper (automatically migrated by plugin)",
Url = "https://manifest.intro-skipper.org/manifest.json",
Enabled = true,
});
}
// Update the configuration with the new repository list
config.PluginRepositories = [.. pluginRepositories];
// Save the updated configuration
serverConfiguration.SaveConfiguration();
}
}
catch (Exception ex)
{
_logger.LogError(ex, "Error occurred while migrating repo URL");
}
}
/// <summary>
/// Inject the skip button script into the web interface.
/// </summary>
/// <param name="webPath">Full path to index.html.</param>
private void InjectSkipButton(string webPath)
{
string pattern;
// Inject the skip intro button code into the web interface.
string indexPath = Path.Join(webPath, "index.html");
// Parts of this code are based off of JellyScrub's script injection code.
// https://github.com/nicknsy/jellyscrub/blob/main/Nick.Plugin.Jellyscrub/JellyscrubPlugin.cs#L38
_logger.LogDebug("Reading index.html from {Path}", indexPath);
string contents = File.ReadAllText(indexPath);
if (!Instance!.Configuration.SkipButtonEnabled)
{
pattern = @"<script src=""configurationpage\?name=skip-intro-button\.js.*<\/script>";
if (!Regex.IsMatch(contents, pattern, RegexOptions.IgnoreCase))
{
return;
}
contents = Regex.Replace(contents, pattern, string.Empty, RegexOptions.IgnoreCase);
File.WriteAllText(indexPath, contents);
return; // Button is disabled, so remove and abort
}
// change URL with every release to prevent the Browsers from caching
string scriptTag = "<script src=\"configurationpage?name=skip-intro-button.js&release=" + GetType().Assembly.GetName().Version + "\"></script>";
// Only inject the script tag once
if (contents.Contains(scriptTag, StringComparison.OrdinalIgnoreCase))
{
_logger.LogInformation("The skip button has already been injected.");
return;
}
// remove old version if necessary
pattern = @"<script src=""configurationpage\?name=skip-intro-button\.js.*<\/script>";
contents = Regex.Replace(contents, pattern, string.Empty, RegexOptions.IgnoreCase);
// Inject a link to the script at the end of the <head> section.
// A regex is used here to ensure the replacement is only done once.
Regex headEnd = new Regex(@"</head>", RegexOptions.IgnoreCase, TimeSpan.FromSeconds(1));
contents = headEnd.Replace(contents, scriptTag + "</head>", 1);
// Write the modified file contents
File.WriteAllText(indexPath, contents);
_logger.LogInformation("Skip button added successfully.");
}
} }