2024-11-24 14:21:46 +00:00
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.
2024-11-24 15:30:42 +01:00
logger . LogWarning ( "Failed to migrate from the ConfusedPolarBear Config {Exception}" , ex ) ;
2024-11-24 14:21:46 +00:00
}
}
}
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 )
{
2024-11-24 16:13:50 +01:00
string pattern = @"<script src=""configurationpage\?name=skip-intro-button\.js.*<\/script>" ;
2024-11-24 14:21:46 +00:00
string indexPath = Path . Join ( webPath , "index.html" ) ;
2024-11-24 16:13:50 +01:00
// Check if we can actually access the file
bool canAccessFile = false ;
2024-11-24 15:30:42 +01:00
try
2024-11-24 14:21:46 +00:00
{
2024-11-24 15:30:42 +01:00
if ( File . Exists ( indexPath ) )
2024-11-24 14:21:46 +00:00
{
2024-11-24 16:13:50 +01:00
using var fs = File . Open ( indexPath , FileMode . Open , FileAccess . ReadWrite ) ;
canAccessFile = true ;
}
}
catch ( Exception )
{
// If skip button is disabled and we can't access the file, just return silently
if ( ! plugin . Configuration . SkipButtonEnabled )
{
logger . LogInformation ( "Skip button disabled and no permission to access index.html. Assuming its a fresh install." ) ;
return ;
}
2024-11-24 15:30:42 +01:00
2024-11-24 16:13:50 +01:00
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." ) ;
return ;
}
2024-11-24 15:30:42 +01:00
2024-11-24 16:13:50 +01:00
if ( ! canAccessFile )
{
logger . LogInformation ( "Jellyfin running as nowebclient" ) ;
return ;
}
2024-11-24 15:30:42 +01:00
2024-11-24 16:13:50 +01:00
try
{
logger . LogInformation ( "Reading index.html from {Path}" , indexPath ) ;
string contents = File . ReadAllText ( indexPath ) ;
2024-11-24 15:30:42 +01:00
2024-11-24 16:13:50 +01:00
if ( ! plugin . Configuration . SkipButtonEnabled )
{
if ( ! Regex . IsMatch ( contents , pattern , RegexOptions . IgnoreCase ) )
2024-11-24 14:21:46 +00:00
{
2024-11-24 16:13:50 +01:00
logger . LogInformation ( "Skip button not found. Assuming its a fresh install." ) ;
2024-11-24 14:21:46 +00:00
return ;
}
contents = Regex . Replace ( contents , pattern , string . Empty , RegexOptions . IgnoreCase ) ;
2024-11-24 15:30:42 +01:00
File . WriteAllText ( indexPath , contents ) ;
2024-11-24 16:13:50 +01:00
return ;
2024-11-24 15:30:42 +01:00
}
2024-11-24 16:13:50 +01:00
string scriptTag = "<script src=\"configurationpage?name=skip-intro-button.js&release=" + plugin . GetType ( ) . Assembly . GetName ( ) . Version + "\"></script>" ;
if ( contents . Contains ( scriptTag , StringComparison . OrdinalIgnoreCase ) )
2024-11-24 14:21:46 +00:00
{
2024-11-24 16:13:50 +01:00
logger . LogInformation ( "The skip button has already been injected." ) ;
return ;
2024-11-24 14:21:46 +00:00
}
2024-11-24 16:13:50 +01:00
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." ) ;
2024-11-24 14:21:46 +00:00
}
2024-11-24 16:13:50 +01:00
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 )
2024-11-24 14:21:46 +00:00
{
2024-11-24 15:30:42 +01:00
WarningManager . SetFlag ( PluginWarning . UnableToAddSkipButton ) ;
2024-11-24 16:13:50 +01:00
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." ) ;
2024-11-24 14:21:46 +00:00
}
}
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 ) ;
}
}