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:
parent
d48ea90190
commit
62683ede87
183
IntroSkipper/Helper/LegacyMigrations.cs
Normal file
183
IntroSkipper/Helper/LegacyMigrations.cs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
@ -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.");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user