Compare commits

..

1 Commits

Author SHA1 Message Date
rlauu
35cbf57f41 Add button to recreate db 2024-11-25 16:05:50 +01:00
9 changed files with 127 additions and 63 deletions

View File

@ -55,13 +55,13 @@ jobs:
run: dotnet restore run: dotnet restore
- name: Initialize CodeQL - name: Initialize CodeQL
uses: github/codeql-action/init@f09c1c0a94de965c15400f5634aa42fac8fb8f88 # v3.27.5 uses: github/codeql-action/init@ea9e4e37992a54ee68a9622e985e60c8e8f12d9f # v3.27.4
with: with:
languages: ${{ matrix.language }} languages: ${{ matrix.language }}
queries: +security-extended queries: +security-extended
- name: Autobuild - name: Autobuild
uses: github/codeql-action/autobuild@f09c1c0a94de965c15400f5634aa42fac8fb8f88 # v3.27.5 uses: github/codeql-action/autobuild@ea9e4e37992a54ee68a9622e985e60c8e8f12d9f # v3.27.4
- name: Perform CodeQL Analysis - name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@f09c1c0a94de965c15400f5634aa42fac8fb8f88 # v3.27.5 uses: github/codeql-action/analyze@ea9e4e37992a54ee68a9622e985e60c8e8f12d9f # v3.27.4

View File

@ -8,7 +8,7 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.12.0" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.11.1" />
<PackageReference Include="xunit" Version="2.9.2" /> <PackageReference Include="xunit" Version="2.9.2" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2"> <PackageReference Include="xunit.runner.visualstudio" Version="2.8.2">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>

View File

@ -455,10 +455,8 @@
<h3 style="margin: 0">Analyzer actions</h3> <h3 style="margin: 0">Analyzer actions</h3>
<p style="margin: 0"> <p style="margin: 0">
Choose how segments should be analyzed for this season.<br /> Choose how segments should be analyzed for this season.<br />
<i>
Default uses all available detection methods (Chromaprint, Chapter, and BlackFrame for credits).<br /> Default uses all available detection methods (Chromaprint, Chapter, and BlackFrame for credits).<br />
Select specific methods to limit analysis, or None to skip detection entirely. Select specific methods to limit analysis, or None to skip detection entirely.
</i>
</p> </p>
<br /> <br />
@ -625,9 +623,10 @@
</div> </div>
<div id="timestampErrorDiv" style="display: none"> <div id="timestampErrorDiv" style="display: none">
<p> <br />
<textarea id="timestampError" rows="2" cols="75" readonly></textarea> <textarea id="timestampError" rows="2" cols="75" readonly></textarea>
</p> <br />
<br />
</div> </div>
<div id="fingerprintVisualizer" style="display: none"> <div id="fingerprintVisualizer" style="display: none">
@ -666,11 +665,12 @@
<span>Shift amount:</span> <span>Shift amount:</span>
<input type="number" min="-3000" max="3000" value="0" id="offset" /> <input type="number" min="-3000" max="3000" value="0" id="offset" />
<p> <br />
<span id="suggestedShifts"> <span id="suggestedShifts">
<span>Suggested shifts: </span> <span>Suggested shifts: </span>
</span> </span>
</p> <br />
<br />
<canvas id="troubleshooter" style="display: none"></canvas> <canvas id="troubleshooter" style="display: none"></canvas>
<span id="timestampContainer"> <span id="timestampContainer">
<span id="timestamps"></span> <span id="timestamps"></span>
@ -679,27 +679,26 @@
</span> </span>
<br /> <br />
</div> </div>
<br />
<div id="eraseSeasonContainer" style="display: none"> <div id="eraseSeasonContainer" style="display: none">
<p>
<button is="emby-button" id="btnEraseSeasonTimestamps" class="button-submit emby-button" type="button">Erase all timestamps for this season</button> <button is="emby-button" id="btnEraseSeasonTimestamps" class="button-submit emby-button" type="button">Erase all timestamps for this season</button>
<input type="checkbox" id="eraseSeasonCacheCheckbox" style="margin-left: 10px" /> <input type="checkbox" id="eraseSeasonCacheCheckbox" style="margin-left: 10px" />
<label for="eraseSeasonCacheCheckbox" style="margin-left: 5px">Erase cached fingerprint files</label> <label for="eraseSeasonCacheCheckbox" style="margin-left: 5px">Erase cached fingerprint files</label>
</p> <br />
</div> </div>
<div id="eraseMovieContainer" style="display: none"> <div id="eraseMovieContainer" style="display: none">
<p>
<button is="emby-button" id="btnEraseMovieTimestamps" class="button-submit emby-button" type="button">Erase all timestamps for this movie</button> <button is="emby-button" id="btnEraseMovieTimestamps" class="button-submit emby-button" type="button">Erase all timestamps for this movie</button>
<input type="checkbox" id="eraseMovieCacheCheckbox" style="margin-left: 10px" /> <input type="checkbox" id="eraseMovieCacheCheckbox" style="margin-left: 10px" />
<label for="eraseMovieCacheCheckbox" style="margin-left: 5px">Erase cached fingerprint files</label> <label for="eraseMovieCacheCheckbox" style="margin-left: 5px">Erase cached fingerprint files</label>
</p> <br />
</div> </div>
<div style="display: flex; align-items: center;"> <div style="display: flex; align-items: center;">
<p> <div>
<button is="emby-button" class="button-submit emby-button" id="btnEraseIntroTimestamps">Erase all introduction timestamps (globally)</button> <button is="emby-button" class="button-submit emby-button" id="btnEraseIntroTimestamps">Erase all introduction timestamps (globally)</button>
<br /> <br />
@ -710,16 +709,21 @@
<br /> <br />
<button is="emby-button" class="button-submit emby-button" id="btnErasePreviewTimestamps">Erase all preview timestamps (globally)</button> <button is="emby-button" class="button-submit emby-button" id="btnErasePreviewTimestamps">Erase all preview timestamps (globally)</button>
</p> </div>
<div> <div>
<input type="checkbox" id="eraseModeCacheCheckbox" style="margin-left: 10px" /> <input type="checkbox" id="eraseModeCacheCheckbox" style="margin-left: 10px" />
<label for="eraseModeCacheCheckbox" style="margin-left: 5px">Erase global cached fingerprint files</label> <label for="eraseModeCacheCheckbox" style="margin-left: 5px">Erase global cached fingerprint files</label>
</div> </div>
<div>
<button is="emby-button" class="button-submit emby-button" id="btnRebuildDatabase">Rebuild database</button>
</div> </div>
</div>
<br />
<br />
</details> </details>
<details id="support"> <details id="support">
<summary>Intro Skipper Support Log</summary> <summary>Support Bundle Info</summary>
<textarea id="supportBundle" rows="20" cols="75" readonly></textarea> <textarea id="supportBundle" rows="20" cols="75" readonly></textarea>
</details> </details>
@ -757,6 +761,7 @@
var btnEraseRecapTimestamps = document.querySelector("button#btnEraseRecapTimestamps"); var btnEraseRecapTimestamps = document.querySelector("button#btnEraseRecapTimestamps");
var btnEraseCreditTimestamps = document.querySelector("button#btnEraseCreditTimestamps"); var btnEraseCreditTimestamps = document.querySelector("button#btnEraseCreditTimestamps");
var btnErasePreviewTimestamps = document.querySelector("button#btnErasePreviewTimestamps"); var btnErasePreviewTimestamps = document.querySelector("button#btnErasePreviewTimestamps");
var btnRebuildDatabase = document.querySelector("button#btnRebuildDatabase");
// all plugin configuration fields that can be get or set with .value (i.e. strings or numbers). // all plugin configuration fields that can be get or set with .value (i.e. strings or numbers).
var configurationFields = [ var configurationFields = [
@ -1503,6 +1508,9 @@
eraseTimestamps("Preview"); eraseTimestamps("Preview");
e.preventDefault(); e.preventDefault();
}); });
btnRebuildDatabase.addEventListener("click", () => {
fetchWithAuth("Intros/RebuildDatabase", "POST", null);
});
btnSeasonEraseTimestamps.addEventListener("click", () => { btnSeasonEraseTimestamps.addEventListener("click", () => {
Dashboard.confirm("Are you sure you want to erase all timestamps for this season?", "Confirm timestamp erasure", (result) => { Dashboard.confirm("Are you sure you want to erase all timestamps for this season?", "Confirm timestamp erasure", (result) => {
if (!result) { if (!result) {

View File

@ -251,6 +251,20 @@ public class SkipIntroController(MediaSegmentUpdateManager mediaSegmentUpdateMan
return NoContent(); return NoContent();
} }
/// <summary>
/// Rebuilds the database.
/// </summary>
/// <response code="204">Database rebuilt.</response>
/// <returns>No content.</returns>
[Authorize(Policy = Policies.RequiresElevation)]
[HttpPost("Intros/RebuildDatabase")]
public ActionResult RebuildDatabase()
{
using var db = new IntroSkipperDbContext(Plugin.Instance!.DbPath);
db.RebuildDatabase();
return NoContent();
}
/// <summary> /// <summary>
/// Gets the user interface configuration. /// Gets the user interface configuration.
/// </summary> /// </summary>

View File

@ -109,22 +109,52 @@ public class IntroSkipperDbContext : DbContext
/// </summary> /// </summary>
public void ApplyMigrations() public void ApplyMigrations()
{ {
// If migrations table exists, just apply pending migrations normally // If database doesn't exist or can't connect, create it with migrations
if (Database.GetAppliedMigrations().Any() || !Database.CanConnect()) if (!Database.CanConnect())
{
Database.Migrate();
return;
}
// If migrations table exists, apply pending migrations normally
if (Database.GetAppliedMigrations().Any())
{ {
Database.Migrate(); Database.Migrate();
return; return;
} }
// For databases without migration history // For databases without migration history
try RebuildDatabase();
}
/// <summary>
/// Rebuilds the database while preserving valid segments and season information.
/// </summary>
public void RebuildDatabase()
{ {
// Backup existing data // Backup existing data
List<DbSegment> segments; List<DbSegment> segments = [];
List<DbSeasonInfo> seasonInfos = [];
using (var db = new IntroSkipperDbContext(_dbPath)) using (var db = new IntroSkipperDbContext(_dbPath))
{
try
{ {
segments = [.. db.DbSegment.AsEnumerable().Where(s => s.ToSegment().Valid)]; segments = [.. db.DbSegment.AsEnumerable().Where(s => s.ToSegment().Valid)];
} }
catch (Exception ex)
{
throw new InvalidOperationException("Failed to read DbSegment data", ex);
}
try
{
seasonInfos = [.. db.DbSeasonInfo];
}
catch (Exception ex)
{
throw new InvalidOperationException("Failed to read DbSeasonInfo data", ex);
}
}
// Delete old database // Delete old database
Database.EnsureDeleted(); Database.EnsureDeleted();
@ -134,14 +164,18 @@ public class IntroSkipperDbContext : DbContext
// Restore the data // Restore the data
using (var db = new IntroSkipperDbContext(_dbPath)) using (var db = new IntroSkipperDbContext(_dbPath))
{
if (segments.Count > 0)
{ {
db.DbSegment.AddRange(segments); db.DbSegment.AddRange(segments);
}
if (seasonInfos.Count > 0)
{
db.DbSeasonInfo.AddRange(seasonInfos);
}
db.SaveChanges(); db.SaveChanges();
} }
} }
catch (Exception ex)
{
throw new InvalidOperationException("Failed to apply migrations", ex);
}
}
} }

View File

@ -2,8 +2,8 @@
<PropertyGroup> <PropertyGroup>
<TargetFramework>net8.0</TargetFramework> <TargetFramework>net8.0</TargetFramework>
<RootNamespace>IntroSkipper</RootNamespace> <RootNamespace>IntroSkipper</RootNamespace>
<AssemblyVersion>1.10.10.11</AssemblyVersion> <AssemblyVersion>1.10.10.10</AssemblyVersion>
<FileVersion>1.10.10.11</FileVersion> <FileVersion>1.10.10.10</FileVersion>
<GenerateDocumentationFile>true</GenerateDocumentationFile> <GenerateDocumentationFile>true</GenerateDocumentationFile>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors> <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>

View File

@ -77,6 +77,15 @@ public class Plugin : BasePlugin<PluginConfiguration>, IHasWebPages
Directory.CreateDirectory(FingerprintCachePath); Directory.CreateDirectory(FingerprintCachePath);
} }
try
{
LegacyMigrations.MigrateAll(this, serverConfiguration, logger, applicationPaths);
}
catch (Exception ex)
{
logger.LogError("Failed to perform migrations. Error: {Error}", ex);
}
// Initialize database, restore timestamps if available. // Initialize database, restore timestamps if available.
try try
{ {
@ -88,15 +97,6 @@ public class Plugin : BasePlugin<PluginConfiguration>, IHasWebPages
logger.LogWarning("Error initializing database: {Exception}", ex); logger.LogWarning("Error initializing database: {Exception}", ex);
} }
try
{
LegacyMigrations.MigrateAll(this, serverConfiguration, logger, applicationPaths);
}
catch (Exception ex)
{
logger.LogError("Failed to perform migrations. Error: {Error}", ex);
}
FFmpegWrapper.CheckFFmpegVersion(); FFmpegWrapper.CheckFFmpegVersion();
} }

View File

@ -48,7 +48,7 @@ https://manifest.intro-skipper.org/manifest.json
- #### [Scheduled tasks fail instantly](https://github.com/intro-skipper/intro-skipper/wiki/Troubleshooting#scheduled-tasks-fail-instantly) - #### [Scheduled tasks fail instantly](https://github.com/intro-skipper/intro-skipper/wiki/Troubleshooting#scheduled-tasks-fail-instantly)
- #### [Plugin settings not saved](https://github.com/intro-skipper/intro-skipper/wiki/Troubleshooting#plugin-settings-not-saved) - #### [Plugin settings not saved](https://github.com/intro-skipper/intro-skipper/wiki/Troubleshooting#plugin-settings-not-saved)
- #### [Skip button is not visible](https://github.com/intro-skipper/intro-skipper/wiki/Troubleshooting#skip-button-is-not-visible) - #### [Skip button is not visible](https://github.com/intro-skipper/intro-skipper/wiki/Troubleshooting#skip-button-is-not-visible)
- #### [Auto skip is not working](https://github.com/intro-skipper/intro-skipper/wiki/Troubleshooting#auto-skip-is-not-working) - #### [Autoskip is not working](https://github.com/intro-skipper/intro-skipper/wiki/Troubleshooting#autoskip-is-not-working)
## [API Documentation](https://github.com/intro-skipper/intro-skipper/blob/master/docs/api.md) ## [API Documentation](https://github.com/intro-skipper/intro-skipper/blob/master/docs/api.md)

View File

@ -9,12 +9,20 @@
"imageUrl": "https://raw.githubusercontent.com/intro-skipper/intro-skipper/master/images/logo.png", "imageUrl": "https://raw.githubusercontent.com/intro-skipper/intro-skipper/master/images/logo.png",
"versions": [ "versions": [
{ {
"version": "1.10.10.11", "version": "1.10.10.10",
"changelog": "- See the full changelog at [GitHub](https://github.com/intro-skipper/intro-skipper/releases/tag/10.10/v1.10.10.11)\n", "changelog": "- See the full changelog at [GitHub](https://github.com/intro-skipper/intro-skipper/releases/tag/10.10/v1.10.10.10)\n",
"targetAbi": "10.10.3.0", "targetAbi": "10.10.3.0",
"sourceUrl": "https://github.com/intro-skipper/intro-skipper/releases/download/10.10/v1.10.10.11/intro-skipper-v1.10.10.11.zip", "sourceUrl": "https://github.com/intro-skipper/intro-skipper/releases/download/10.10/v1.10.10.10/intro-skipper-v1.10.10.10.zip",
"checksum": "30a71fd3996e0fbe9076371539b1ca56", "checksum": "96ec29871e726ece01fbf7ccb24ae392",
"timestamp": "2024-11-25T17:07:33Z" "timestamp": "2024-11-24T18:33:26Z"
},
{
"version": "1.10.10.9",
"changelog": "- See the full changelog at [GitHub](https://github.com/intro-skipper/intro-skipper/releases/tag/10.10/v1.10.10.9)\n",
"targetAbi": "10.10.2.0",
"sourceUrl": "https://github.com/intro-skipper/intro-skipper/releases/download/10.10/v1.10.10.9/intro-skipper-v1.10.10.9.zip",
"checksum": "a39436ee4476a4e74acbafbaf1008dfb",
"timestamp": "2024-11-17T21:14:18Z"
} }
] ]
} }