intro-skipper/IntroSkipper/Db/IntroSkipperDbContext.cs
rlauuzo 6ccf002e51
Recaps and Previews Support (#357)
* Recaps and Previews Support

* Add draft UI of preview / recap edit

* remove intro/credit tasks

* Update configPage.html

* rename task

* Reorganize settings by relation

* More standardized formatting

* Some additional formatting

* fix a typo

* Update configPage.html

* Allow missing recap / prview data

* More risk to corrupt than benefit

* Update TimeStamps.cs

* Update PluginConfiguration.cs

* Update configPage.html

* Update PluginConfiguration.cs

* Add chapter regex to settings

* Move all UI into UI section

* Move ending seconds with similar

* Add default

* fixes

* Update SkipIntroController.cs

* Autoskip all segments

* Check if adjacent segment

* Update AutoSkip.cs

* Update AutoSkip.cs

* Settings apply to all segment types

* Update SegmentProvider

* Update configPage.html

Whoops

* Update Plugin.cs

* Update AutoSkip.cs

* Let’s call it missing instead

* Update BaseItemAnalyzerTask.cs

* Update BaseItemAnalyzerTask.cs

* Update BaseItemAnalyzerTask.cs

* Move "select" all below list

* Clarify button wording

* Update configPage.html

* Nope, long client list will hide it

* Simplify wording

* Update QueuedEpisode.cs

* fix unit test for ffmpeg7

* Add migration

* Restore DataContract

* update

* Update configPage.html

* remove analyzed status

* Update AutoSkip.cs

* Update configPage.html typo

* Store analyzed items in seasoninfo

* Update VisualizationController.cs

* update

* Update IntroSkipperDbContext.cs

* Add preview / recap delete

* This keeps changing itself

* Update SkipIntroController.cs

* Rather add it to be removed

---------

Co-authored-by: rlauu <46294892+rlauu@users.noreply.github.com>
Co-authored-by: TwistedUmbrellaX <1173913+AbandonedCart@users.noreply.github.com>
Co-authored-by: Kilian von Pflugk <github@jumoog.io>
2024-11-21 09:42:55 -05:00

148 lines
4.7 KiB
C#

// Copyright (C) 2024 Intro-Skipper contributors <intro-skipper.org>
// SPDX-License-Identifier: GPL-3.0-only.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.Json;
using IntroSkipper.Data;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.ChangeTracking;
namespace IntroSkipper.Db;
/// <summary>
/// Plugin database.
/// </summary>
/// <remarks>
/// Initializes a new instance of the <see cref="IntroSkipperDbContext"/> class.
/// </remarks>
public class IntroSkipperDbContext : DbContext
{
private readonly string _dbPath;
/// <summary>
/// Initializes a new instance of the <see cref="IntroSkipperDbContext"/> class.
/// </summary>
/// <param name="dbPath">The path to the SQLite database file.</param>
public IntroSkipperDbContext(string dbPath)
{
_dbPath = dbPath;
DbSegment = Set<DbSegment>();
DbSeasonInfo = Set<DbSeasonInfo>();
}
/// <summary>
/// Initializes a new instance of the <see cref="IntroSkipperDbContext"/> class.
/// </summary>
/// <param name="options">The options.</param>
public IntroSkipperDbContext(DbContextOptions<IntroSkipperDbContext> options) : base(options)
{
var folder = Environment.SpecialFolder.LocalApplicationData;
var path = Environment.GetFolderPath(folder);
_dbPath = System.IO.Path.Join(path, "introskipper.db");
DbSegment = Set<DbSegment>();
DbSeasonInfo = Set<DbSeasonInfo>();
}
/// <summary>
/// Gets or sets the <see cref="DbSet{TEntity}"/> containing the segments.
/// </summary>
public DbSet<DbSegment> DbSegment { get; set; }
/// <summary>
/// Gets or sets the <see cref="DbSet{TEntity}"/> containing the season information.
/// </summary>
public DbSet<DbSeasonInfo> DbSeasonInfo { get; set; }
/// <inheritdoc/>
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlite($"Data Source={_dbPath}");
}
/// <inheritdoc/>
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<DbSegment>(entity =>
{
entity.ToTable("DbSegment");
entity.HasKey(s => new { s.ItemId, s.Type });
entity.HasIndex(e => e.ItemId);
entity.Property(e => e.Start)
.HasDefaultValue(0.0)
.IsRequired();
entity.Property(e => e.End)
.HasDefaultValue(0.0)
.IsRequired();
});
modelBuilder.Entity<DbSeasonInfo>(entity =>
{
entity.ToTable("DbSeasonInfo");
entity.HasKey(s => new { s.SeasonId, s.Type });
entity.HasIndex(e => e.SeasonId);
entity.Property(e => e.Action)
.HasDefaultValue(AnalyzerAction.Default)
.IsRequired();
entity.Property(e => e.EpisodeIds)
.HasConversion(
v => JsonSerializer.Serialize(v, (JsonSerializerOptions?)null),
v => JsonSerializer.Deserialize<IEnumerable<Guid>>(v, (JsonSerializerOptions?)null) ?? new List<Guid>(),
new ValueComparer<IEnumerable<Guid>>(
(c1, c2) => (c1 ?? new List<Guid>()).SequenceEqual(c2 ?? new List<Guid>()),
c => c.Aggregate(0, (a, v) => HashCode.Combine(a, v.GetHashCode())),
c => c.ToList()));
});
base.OnModelCreating(modelBuilder);
}
/// <summary>
/// Applies any pending migrations to the database.
/// </summary>
public void ApplyMigrations()
{
// If migrations table exists, just apply pending migrations normally
if (Database.GetAppliedMigrations().Any() || !Database.CanConnect())
{
Database.Migrate();
return;
}
// For databases without migration history
try
{
// Backup existing data
List<DbSegment> segments;
using (var db = new IntroSkipperDbContext(_dbPath))
{
segments = [.. db.DbSegment.AsEnumerable().Where(s => s.ToSegment().Valid)];
}
// Delete old database
Database.EnsureDeleted();
// Create new database with proper migration history
Database.Migrate();
// Restore the data
using (var db = new IntroSkipperDbContext(_dbPath))
{
db.DbSegment.AddRange(segments);
db.SaveChanges();
}
}
catch (Exception ex)
{
throw new InvalidOperationException("Failed to apply migrations", ex);
}
}
}