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.Controller.Library; using MediaBrowser.Model.Entities; 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, ILibraryManager libraryManager) { 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); MigrateSettingsToJellyfin(plugin, logger, libraryManager); 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("Failed to migrate from the ConfusedPolarBear Config {Exception}", ex); } } } private static void MigrateRepoUrl(Plugin plugin, IServerConfigurationManager serverConfiguration, ILogger logger) { try { List 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 = @""; if (contents.Contains(scriptTag, StringComparison.OrdinalIgnoreCase)) { logger.LogDebug("The skip button has already been injected."); return; } contents = Regex.Replace(contents, pattern, string.Empty, RegexOptions.IgnoreCase); Regex headEnd = new Regex(@"", RegexOptions.IgnoreCase, TimeSpan.FromSeconds(1)); contents = headEnd.Replace(contents, scriptTag + "", 1); File.WriteAllText(indexPath, contents); logger.LogInformation("Skip button added successfully."); } catch (UnauthorizedAccessException) { 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."); } catch (IOException) { 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."); } } 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(introPath); foreach (var intro in introList) { db.DbSegment.Add(new DbSegment(intro, AnalysisMode.Introduction)); } } // Import credits if (File.Exists(creditsPath)) { var creditList = XmlSerializationHelper.DeserializeFromXml(creditsPath); foreach (var credit in creditList) { db.DbSegment.Add(new DbSegment(credit, AnalysisMode.Credits)); } } db.SaveChanges(); File.Delete(introPath); File.Delete(creditsPath); } private static void MigrateSettingsToJellyfin(Plugin plugin, ILogger logger, ILibraryManager libraryManager) { try { if (!plugin.Configuration.SelectAllLibraries) { logger.LogInformation("Migration of your old library settings to Jellyfin"); List selectedLibraries = [.. plugin.Configuration.SelectedLibraries.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)]; foreach (var folder in libraryManager.GetVirtualFolders()) { if (!selectedLibraries.Contains(folder.Name) && folder.CollectionType is CollectionTypeOptions.tvshows or CollectionTypeOptions.movies or CollectionTypeOptions.mixed) { // only add if not already disabled if (!folder.LibraryOptions.DisabledMediaSegmentProviders.Contains(plugin.Name)) { // append in case there other disabled media segment providers folder.LibraryOptions.DisabledMediaSegmentProviders = [.. folder.LibraryOptions.DisabledMediaSegmentProviders, plugin.Name]; logger.LogInformation("Disable Media Segment Provider <{Name}> for Library <{Name}>", plugin.Name, folder.Name); } } } // reset to default plugin.Configuration.SelectAllLibraries = true; plugin.Configuration.SelectedLibraries = string.Empty; plugin.SaveConfiguration(); } if (!plugin.Configuration.AnalyzeMovies) { logger.LogInformation("Migration of your old Movie settings to Jellyfin"); foreach (var folder in libraryManager.GetVirtualFolders()) { if (folder.CollectionType is CollectionTypeOptions.movies or CollectionTypeOptions.mixed) { // only add if not already disabled if (!folder.LibraryOptions.DisabledMediaSegmentProviders.Contains(plugin.Name)) { // append in case there other disabled media segment providers folder.LibraryOptions.DisabledMediaSegmentProviders = [.. folder.LibraryOptions.DisabledMediaSegmentProviders, plugin.Name]; logger.LogInformation("Disable Media Segment Provider <{Name}> for Library <{Name}>", plugin.Name, folder.Name); } } } // reset to default plugin.Configuration.AnalyzeMovies = true; plugin.SaveConfiguration(); } } catch (Exception ex) { logger.LogWarning("The migration of your old library settings to Jellyfin has failed: {Exception}", ex); } finally { // reset to default plugin.Configuration.SelectAllLibraries = true; plugin.Configuration.SelectedLibraries = string.Empty; plugin.SaveConfiguration(); } } }