mirror of https://github.com/ErsatzTV/ErsatzTV.git
Browse Source
* add plex show, season sync * sync plex episodes * sync plex episode statistics * update plex artwork as needed * code cleanup * add note about testspull/74/head
31 changed files with 2840 additions and 97 deletions
@ -0,0 +1,7 @@
@@ -0,0 +1,7 @@
|
||||
namespace ErsatzTV.Core.Domain |
||||
{ |
||||
public class PlexEpisode : Episode |
||||
{ |
||||
public string Key { get; set; } |
||||
} |
||||
} |
@ -0,0 +1,7 @@
@@ -0,0 +1,7 @@
|
||||
namespace ErsatzTV.Core.Domain |
||||
{ |
||||
public class PlexSeason : Season |
||||
{ |
||||
public string Key { get; set; } |
||||
} |
||||
} |
@ -0,0 +1,7 @@
@@ -0,0 +1,7 @@
|
||||
namespace ErsatzTV.Core.Domain |
||||
{ |
||||
public class PlexShow : Show |
||||
{ |
||||
public string Key { get; set; } |
||||
} |
||||
} |
@ -0,0 +1,15 @@
@@ -0,0 +1,15 @@
|
||||
using System.Threading.Tasks; |
||||
using ErsatzTV.Core.Domain; |
||||
using ErsatzTV.Core.Plex; |
||||
using LanguageExt; |
||||
|
||||
namespace ErsatzTV.Core.Interfaces.Plex |
||||
{ |
||||
public interface IPlexTelevisionLibraryScanner |
||||
{ |
||||
Task<Either<BaseError, Unit>> ScanLibrary( |
||||
PlexConnection connection, |
||||
PlexServerAuthToken token, |
||||
PlexLibrary plexMediaSourceLibrary); |
||||
} |
||||
} |
@ -0,0 +1,15 @@
@@ -0,0 +1,15 @@
|
||||
using System.Threading.Tasks; |
||||
using ErsatzTV.Core.Domain; |
||||
using LanguageExt; |
||||
|
||||
namespace ErsatzTV.Core.Interfaces.Repositories |
||||
{ |
||||
public interface IMetadataRepository |
||||
{ |
||||
Task<Unit> RemoveGenre(Genre genre); |
||||
Task<Unit> UpdateStatistics(MediaVersion mediaVersion); |
||||
Task<Unit> UpdateArtworkPath(Artwork artwork); |
||||
Task<Unit> AddArtwork(Domain.Metadata metadata, Artwork artwork); |
||||
Task<Unit> RemoveArtwork(Domain.Metadata metadata, ArtworkKind artworkKind); |
||||
} |
||||
} |
@ -0,0 +1,58 @@
@@ -0,0 +1,58 @@
|
||||
using System.Collections.Generic; |
||||
using System.Threading.Tasks; |
||||
using ErsatzTV.Core.Domain; |
||||
using ErsatzTV.Core.Interfaces.Repositories; |
||||
using LanguageExt; |
||||
using static LanguageExt.Prelude; |
||||
|
||||
namespace ErsatzTV.Core.Plex |
||||
{ |
||||
public abstract class PlexLibraryScanner |
||||
{ |
||||
private readonly IMetadataRepository _metadataRepository; |
||||
|
||||
protected PlexLibraryScanner(IMetadataRepository metadataRepository) => |
||||
_metadataRepository = metadataRepository; |
||||
|
||||
protected async Task<Unit> UpdateArtworkIfNeeded( |
||||
Domain.Metadata existingMetadata, |
||||
Domain.Metadata incomingMetadata, |
||||
ArtworkKind artworkKind) |
||||
{ |
||||
if (incomingMetadata.DateUpdated > existingMetadata.DateUpdated) |
||||
{ |
||||
Option<Artwork> maybeIncomingArtwork = Optional(incomingMetadata.Artwork).Flatten() |
||||
.Find(a => a.ArtworkKind == artworkKind); |
||||
|
||||
await maybeIncomingArtwork.Match( |
||||
async incomingArtwork => |
||||
{ |
||||
Option<Artwork> maybeExistingArtwork = Optional(existingMetadata.Artwork).Flatten() |
||||
.Find(a => a.ArtworkKind == artworkKind); |
||||
|
||||
await maybeExistingArtwork.Match( |
||||
async existingArtwork => |
||||
{ |
||||
existingArtwork.Path = incomingArtwork.Path; |
||||
existingArtwork.DateUpdated = incomingArtwork.DateUpdated; |
||||
await _metadataRepository.UpdateArtworkPath(existingArtwork); |
||||
}, |
||||
async () => |
||||
{ |
||||
existingMetadata.Artwork ??= new List<Artwork>(); |
||||
existingMetadata.Artwork.Add(incomingArtwork); |
||||
await _metadataRepository.AddArtwork(existingMetadata, incomingArtwork); |
||||
}); |
||||
}, |
||||
async () => |
||||
{ |
||||
existingMetadata.Artwork ??= new List<Artwork>(); |
||||
existingMetadata.Artwork.RemoveAll(a => a.ArtworkKind == artworkKind); |
||||
await _metadataRepository.RemoveArtwork(existingMetadata, artworkKind); |
||||
}); |
||||
} |
||||
|
||||
return Unit.Default; |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,281 @@
@@ -0,0 +1,281 @@
|
||||
using System.Collections.Generic; |
||||
using System.Linq; |
||||
using System.Threading.Tasks; |
||||
using ErsatzTV.Core.Domain; |
||||
using ErsatzTV.Core.Interfaces.Plex; |
||||
using ErsatzTV.Core.Interfaces.Repositories; |
||||
using LanguageExt; |
||||
using Microsoft.Extensions.Logging; |
||||
using static LanguageExt.Prelude; |
||||
|
||||
namespace ErsatzTV.Core.Plex |
||||
{ |
||||
public class PlexTelevisionLibraryScanner : PlexLibraryScanner, IPlexTelevisionLibraryScanner |
||||
{ |
||||
private readonly ILogger<PlexTelevisionLibraryScanner> _logger; |
||||
private readonly IMetadataRepository _metadataRepository; |
||||
private readonly IPlexServerApiClient _plexServerApiClient; |
||||
private readonly ITelevisionRepository _televisionRepository; |
||||
|
||||
public PlexTelevisionLibraryScanner( |
||||
IPlexServerApiClient plexServerApiClient, |
||||
ITelevisionRepository televisionRepository, |
||||
IMetadataRepository metadataRepository, |
||||
ILogger<PlexTelevisionLibraryScanner> logger) |
||||
: base(metadataRepository) |
||||
{ |
||||
_plexServerApiClient = plexServerApiClient; |
||||
_televisionRepository = televisionRepository; |
||||
_metadataRepository = metadataRepository; |
||||
_logger = logger; |
||||
} |
||||
|
||||
public async Task<Either<BaseError, Unit>> ScanLibrary( |
||||
PlexConnection connection, |
||||
PlexServerAuthToken token, |
||||
PlexLibrary plexMediaSourceLibrary) |
||||
{ |
||||
Either<BaseError, List<PlexShow>> entries = await _plexServerApiClient.GetShowLibraryContents( |
||||
plexMediaSourceLibrary, |
||||
connection, |
||||
token); |
||||
|
||||
return await entries.Match<Task<Either<BaseError, Unit>>>( |
||||
async showEntries => |
||||
{ |
||||
foreach (PlexShow incoming in showEntries) |
||||
{ |
||||
// TODO: optimize dbcontext use here, do we need tracking? can we make partial updates with dapper?
|
||||
// TODO: figure out how to rebuild playlists
|
||||
Either<BaseError, PlexShow> maybeShow = await _televisionRepository |
||||
.GetOrAddPlexShow(plexMediaSourceLibrary, incoming) |
||||
.BindT(existing => UpdateMetadata(existing, incoming)) |
||||
.BindT(existing => UpdateArtwork(existing, incoming)); |
||||
|
||||
await maybeShow.Match( |
||||
async show => await ScanSeasons(plexMediaSourceLibrary, show, connection, token), |
||||
error => |
||||
{ |
||||
_logger.LogWarning( |
||||
"Error processing plex show at {Key}: {Error}", |
||||
incoming.Key, |
||||
error.Value); |
||||
return Task.CompletedTask; |
||||
}); |
||||
} |
||||
|
||||
// TODO: delete removed shows
|
||||
|
||||
return Unit.Default; |
||||
}, |
||||
error => |
||||
{ |
||||
_logger.LogWarning( |
||||
"Error synchronizing plex library {Path}: {Error}", |
||||
plexMediaSourceLibrary.Name, |
||||
error.Value); |
||||
|
||||
return Left<BaseError, Unit>(error).AsTask(); |
||||
}); |
||||
} |
||||
|
||||
private Task<Either<BaseError, PlexShow>> UpdateMetadata(PlexShow existing, PlexShow incoming) |
||||
{ |
||||
ShowMetadata existingMetadata = existing.ShowMetadata.Head(); |
||||
ShowMetadata incomingMetadata = incoming.ShowMetadata.Head(); |
||||
|
||||
// TODO: this probably doesn't work
|
||||
// plex doesn't seem to update genres returned by the main library call
|
||||
if (incomingMetadata.DateUpdated > existingMetadata.DateUpdated) |
||||
{ |
||||
foreach (Genre genre in existingMetadata.Genres |
||||
.Filter(g => incomingMetadata.Genres.All(g2 => g2.Name != g.Name)) |
||||
.ToList()) |
||||
{ |
||||
existingMetadata.Genres.Remove(genre); |
||||
_metadataRepository.RemoveGenre(genre); |
||||
} |
||||
|
||||
foreach (Genre genre in incomingMetadata.Genres |
||||
.Filter(g => existingMetadata.Genres.All(g2 => g2.Name != g.Name)) |
||||
.ToList()) |
||||
{ |
||||
existingMetadata.Genres.Add(genre); |
||||
_televisionRepository.AddGenre(existingMetadata, genre); |
||||
} |
||||
} |
||||
|
||||
return Right<BaseError, PlexShow>(existing).AsTask(); |
||||
} |
||||
|
||||
private async Task<Either<BaseError, PlexShow>> UpdateArtwork(PlexShow existing, PlexShow incoming) |
||||
{ |
||||
ShowMetadata existingMetadata = existing.ShowMetadata.Head(); |
||||
ShowMetadata incomingMetadata = incoming.ShowMetadata.Head(); |
||||
|
||||
if (incomingMetadata.DateUpdated > existingMetadata.DateUpdated) |
||||
{ |
||||
await UpdateArtworkIfNeeded(existingMetadata, incomingMetadata, ArtworkKind.Poster); |
||||
await UpdateArtworkIfNeeded(existingMetadata, incomingMetadata, ArtworkKind.FanArt); |
||||
} |
||||
|
||||
return existing; |
||||
} |
||||
|
||||
private async Task<Either<BaseError, Unit>> ScanSeasons( |
||||
PlexLibrary plexMediaSourceLibrary, |
||||
PlexShow show, |
||||
PlexConnection connection, |
||||
PlexServerAuthToken token) |
||||
{ |
||||
Either<BaseError, List<PlexSeason>> entries = await _plexServerApiClient.GetShowSeasons( |
||||
plexMediaSourceLibrary, |
||||
show, |
||||
connection, |
||||
token); |
||||
|
||||
return await entries.Match<Task<Either<BaseError, Unit>>>( |
||||
async seasonEntries => |
||||
{ |
||||
foreach (PlexSeason incoming in seasonEntries) |
||||
{ |
||||
incoming.ShowId = show.Id; |
||||
|
||||
// TODO: optimize dbcontext use here, do we need tracking? can we make partial updates with dapper?
|
||||
// TODO: figure out how to rebuild playlists
|
||||
Either<BaseError, PlexSeason> maybeSeason = await _televisionRepository |
||||
.GetOrAddPlexSeason(plexMediaSourceLibrary, incoming) |
||||
.BindT(existing => UpdateArtwork(existing, incoming)); |
||||
|
||||
await maybeSeason.Match( |
||||
async season => await ScanEpisodes(plexMediaSourceLibrary, season, connection, token), |
||||
error => |
||||
{ |
||||
_logger.LogWarning( |
||||
"Error processing plex show at {Key}: {Error}", |
||||
incoming.Key, |
||||
error.Value); |
||||
return Task.CompletedTask; |
||||
}); |
||||
} |
||||
|
||||
// TODO: delete removed seasons
|
||||
|
||||
return Unit.Default; |
||||
}, |
||||
error => |
||||
{ |
||||
_logger.LogWarning( |
||||
"Error synchronizing plex library {Path}: {Error}", |
||||
plexMediaSourceLibrary.Name, |
||||
error.Value); |
||||
|
||||
return Left<BaseError, Unit>(error).AsTask(); |
||||
}); |
||||
} |
||||
|
||||
private async Task<Either<BaseError, PlexSeason>> UpdateArtwork(PlexSeason existing, PlexSeason incoming) |
||||
{ |
||||
SeasonMetadata existingMetadata = existing.SeasonMetadata.Head(); |
||||
SeasonMetadata incomingMetadata = incoming.SeasonMetadata.Head(); |
||||
|
||||
if (incomingMetadata.DateUpdated > existingMetadata.DateUpdated) |
||||
{ |
||||
await UpdateArtworkIfNeeded(existingMetadata, incomingMetadata, ArtworkKind.Poster); |
||||
} |
||||
|
||||
return existing; |
||||
} |
||||
|
||||
private async Task<Either<BaseError, Unit>> ScanEpisodes( |
||||
PlexLibrary plexMediaSourceLibrary, |
||||
PlexSeason season, |
||||
PlexConnection connection, |
||||
PlexServerAuthToken token) |
||||
{ |
||||
Either<BaseError, List<PlexEpisode>> entries = await _plexServerApiClient.GetSeasonEpisodes( |
||||
plexMediaSourceLibrary, |
||||
season, |
||||
connection, |
||||
token); |
||||
|
||||
return await entries.Match<Task<Either<BaseError, Unit>>>( |
||||
async episodeEntries => |
||||
{ |
||||
foreach (PlexEpisode incoming in episodeEntries) |
||||
{ |
||||
incoming.SeasonId = season.Id; |
||||
|
||||
// TODO: optimize dbcontext use here, do we need tracking? can we make partial updates with dapper?
|
||||
// TODO: figure out how to rebuild playlists
|
||||
Either<BaseError, PlexEpisode> maybeEpisode = await _televisionRepository |
||||
.GetOrAddPlexEpisode(plexMediaSourceLibrary, incoming) |
||||
.BindT(existing => UpdateStatistics(existing, incoming, connection, token)) |
||||
.BindT(existing => UpdateArtwork(existing, incoming)); |
||||
|
||||
maybeEpisode.IfLeft( |
||||
error => _logger.LogWarning( |
||||
"Error processing plex episode at {Key}: {Error}", |
||||
incoming.Key, |
||||
error.Value)); |
||||
} |
||||
|
||||
// TODO: delete removed episodes
|
||||
|
||||
return Unit.Default; |
||||
}, |
||||
error => |
||||
{ |
||||
_logger.LogWarning( |
||||
"Error synchronizing plex library {Path}: {Error}", |
||||
plexMediaSourceLibrary.Name, |
||||
error.Value); |
||||
|
||||
return Left<BaseError, Unit>(error).AsTask(); |
||||
}); |
||||
} |
||||
|
||||
private async Task<Either<BaseError, PlexEpisode>> UpdateStatistics( |
||||
PlexEpisode existing, |
||||
PlexEpisode incoming, |
||||
PlexConnection connection, |
||||
PlexServerAuthToken token) |
||||
{ |
||||
MediaVersion existingVersion = existing.MediaVersions.Head(); |
||||
MediaVersion incomingVersion = incoming.MediaVersions.Head(); |
||||
|
||||
if (incomingVersion.DateUpdated > existingVersion.DateUpdated || |
||||
string.IsNullOrWhiteSpace(existingVersion.SampleAspectRatio)) |
||||
{ |
||||
Either<BaseError, MediaVersion> maybeStatistics = |
||||
await _plexServerApiClient.GetStatistics(incoming.Key.Split("/").Last(), connection, token); |
||||
|
||||
await maybeStatistics.Match( |
||||
async mediaVersion => |
||||
{ |
||||
existingVersion.SampleAspectRatio = mediaVersion.SampleAspectRatio ?? "1:1"; |
||||
existingVersion.VideoScanKind = mediaVersion.VideoScanKind; |
||||
existingVersion.DateUpdated = incomingVersion.DateUpdated; |
||||
|
||||
await _metadataRepository.UpdateStatistics(existingVersion); |
||||
}, |
||||
_ => Task.CompletedTask); |
||||
} |
||||
|
||||
return Right<BaseError, PlexEpisode>(existing); |
||||
} |
||||
|
||||
private async Task<Either<BaseError, PlexEpisode>> UpdateArtwork(PlexEpisode existing, PlexEpisode incoming) |
||||
{ |
||||
EpisodeMetadata existingMetadata = existing.EpisodeMetadata.Head(); |
||||
EpisodeMetadata incomingMetadata = incoming.EpisodeMetadata.Head(); |
||||
|
||||
if (incomingMetadata.DateUpdated > existingMetadata.DateUpdated) |
||||
{ |
||||
await UpdateArtworkIfNeeded(existingMetadata, incomingMetadata, ArtworkKind.Thumbnail); |
||||
} |
||||
|
||||
return existing; |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,11 @@
@@ -0,0 +1,11 @@
|
||||
using ErsatzTV.Core.Domain; |
||||
using Microsoft.EntityFrameworkCore; |
||||
using Microsoft.EntityFrameworkCore.Metadata.Builders; |
||||
|
||||
namespace ErsatzTV.Infrastructure.Data.Configurations |
||||
{ |
||||
public class PlexEpisodeConfiguration : IEntityTypeConfiguration<PlexEpisode> |
||||
{ |
||||
public void Configure(EntityTypeBuilder<PlexEpisode> builder) => builder.ToTable("PlexEpisode"); |
||||
} |
||||
} |
@ -0,0 +1,11 @@
@@ -0,0 +1,11 @@
|
||||
using ErsatzTV.Core.Domain; |
||||
using Microsoft.EntityFrameworkCore; |
||||
using Microsoft.EntityFrameworkCore.Metadata.Builders; |
||||
|
||||
namespace ErsatzTV.Infrastructure.Data.Configurations |
||||
{ |
||||
public class PlexSeasonConfiguration : IEntityTypeConfiguration<PlexSeason> |
||||
{ |
||||
public void Configure(EntityTypeBuilder<PlexSeason> builder) => builder.ToTable("PlexSeason"); |
||||
} |
||||
} |
@ -0,0 +1,11 @@
@@ -0,0 +1,11 @@
|
||||
using ErsatzTV.Core.Domain; |
||||
using Microsoft.EntityFrameworkCore; |
||||
using Microsoft.EntityFrameworkCore.Metadata.Builders; |
||||
|
||||
namespace ErsatzTV.Infrastructure.Data.Configurations |
||||
{ |
||||
public class PlexShowConfiguration : IEntityTypeConfiguration<PlexShow> |
||||
{ |
||||
public void Configure(EntityTypeBuilder<PlexShow> builder) => builder.ToTable("PlexShow"); |
||||
} |
||||
} |
@ -0,0 +1,78 @@
@@ -0,0 +1,78 @@
|
||||
using System.Data; |
||||
using System.Threading.Tasks; |
||||
using Dapper; |
||||
using ErsatzTV.Core.Domain; |
||||
using ErsatzTV.Core.Interfaces.Repositories; |
||||
using LanguageExt; |
||||
|
||||
namespace ErsatzTV.Infrastructure.Data.Repositories |
||||
{ |
||||
public class MetadataRepository : IMetadataRepository |
||||
{ |
||||
private readonly IDbConnection _dbConnection; |
||||
|
||||
public MetadataRepository(IDbConnection dbConnection) => _dbConnection = dbConnection; |
||||
|
||||
public Task<Unit> RemoveGenre(Genre genre) => |
||||
_dbConnection.ExecuteAsync("DELETE FROM Genre WHERE Id = @GenreId", new { GenreId = genre.Id }).ToUnit(); |
||||
|
||||
public Task<Unit> UpdateStatistics(MediaVersion mediaVersion) => |
||||
_dbConnection.ExecuteAsync( |
||||
@"UPDATE MediaVersion SET
|
||||
SampleAspectRatio = @SampleAspectRatio, |
||||
VideoScanKind = @VideoScanKind, |
||||
DateUpdated = @DateUpdated |
||||
WHERE Id = @MediaVersionId",
|
||||
new |
||||
{ |
||||
mediaVersion.SampleAspectRatio, |
||||
mediaVersion.VideoScanKind, |
||||
mediaVersion.DateUpdated, |
||||
MediaVersionId = mediaVersion.Id |
||||
}).ToUnit(); |
||||
|
||||
public Task<Unit> UpdateArtworkPath(Artwork artwork) => |
||||
_dbConnection.ExecuteAsync( |
||||
"UPDATE Artwork SET Path = @Path, DateUpdated = @DateUpdated WHERE Id = @Id", |
||||
new { artwork.Path, artwork.DateUpdated, artwork.Id }).ToUnit(); |
||||
|
||||
public Task<Unit> AddArtwork(Metadata metadata, Artwork artwork) |
||||
{ |
||||
var parameters = new |
||||
{ |
||||
artwork.ArtworkKind, metadata.Id, artwork.DateAdded, artwork.DateUpdated, artwork.Path |
||||
}; |
||||
|
||||
return metadata switch |
||||
{ |
||||
MovieMetadata => _dbConnection.ExecuteAsync( |
||||
@"INSERT INTO Artwork (ArtworkKind, MovieMetadataId, DateAdded, DateUpdated, Path)
|
||||
VALUES (@ArtworkKind, @Id, @DateAdded, @DateUpdated, @Path)",
|
||||
parameters) |
||||
.ToUnit(), |
||||
ShowMetadata => _dbConnection.ExecuteAsync( |
||||
@"INSERT INTO Artwork (ArtworkKind, ShowMetadataId, DateAdded, DateUpdated, Path)
|
||||
VALUES (@ArtworkKind, @Id, @DateAdded, @DateUpdated, @Path)",
|
||||
parameters) |
||||
.ToUnit(), |
||||
SeasonMetadata => _dbConnection.ExecuteAsync( |
||||
@"INSERT INTO Artwork (ArtworkKind, SeasonMetadataId, DateAdded, DateUpdated, Path)
|
||||
VALUES (@ArtworkKind, @Id, @DateAdded, @DateUpdated, @Path)",
|
||||
parameters) |
||||
.ToUnit(), |
||||
EpisodeMetadata => _dbConnection.ExecuteAsync( |
||||
@"INSERT INTO Artwork (ArtworkKind, EpisodeMetadataId, DateAdded, DateUpdated, Path)
|
||||
VALUES (@ArtworkKind, @Id, @DateAdded, @DateUpdated, @Path)",
|
||||
parameters) |
||||
.ToUnit(), |
||||
_ => Task.FromResult(Unit.Default) |
||||
}; |
||||
} |
||||
|
||||
public Task<Unit> RemoveArtwork(Metadata metadata, ArtworkKind artworkKind) => |
||||
_dbConnection.ExecuteAsync( |
||||
@"DELETE FROM Artwork WHERE ArtworkKind = @ArtworkKind AND (MovieMetadataId = @Id
|
||||
OR ShowMetadataId = @Id OR SeasonMetadataId = @Id OR EpisodeMetadataId = @Id)",
|
||||
new { ArtworkKind = artworkKind, metadata.Id }).ToUnit(); |
||||
} |
||||
} |
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,79 @@
@@ -0,0 +1,79 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations; |
||||
|
||||
namespace ErsatzTV.Infrastructure.Migrations |
||||
{ |
||||
public partial class Add_PlexTelevision : Migration |
||||
{ |
||||
protected override void Up(MigrationBuilder migrationBuilder) |
||||
{ |
||||
migrationBuilder.CreateTable( |
||||
"PlexEpisode", |
||||
table => new |
||||
{ |
||||
Id = table.Column<int>("INTEGER", nullable: false) |
||||
.Annotation("Sqlite:Autoincrement", true), |
||||
Key = table.Column<string>("TEXT", nullable: true) |
||||
}, |
||||
constraints: table => |
||||
{ |
||||
table.PrimaryKey("PK_PlexEpisode", x => x.Id); |
||||
table.ForeignKey( |
||||
"FK_PlexEpisode_Episode_Id", |
||||
x => x.Id, |
||||
"Episode", |
||||
"Id", |
||||
onDelete: ReferentialAction.Cascade); |
||||
}); |
||||
|
||||
migrationBuilder.CreateTable( |
||||
"PlexSeason", |
||||
table => new |
||||
{ |
||||
Id = table.Column<int>("INTEGER", nullable: false) |
||||
.Annotation("Sqlite:Autoincrement", true), |
||||
Key = table.Column<string>("TEXT", nullable: true) |
||||
}, |
||||
constraints: table => |
||||
{ |
||||
table.PrimaryKey("PK_PlexSeason", x => x.Id); |
||||
table.ForeignKey( |
||||
"FK_PlexSeason_Season_Id", |
||||
x => x.Id, |
||||
"Season", |
||||
"Id", |
||||
onDelete: ReferentialAction.Cascade); |
||||
}); |
||||
|
||||
migrationBuilder.CreateTable( |
||||
"PlexShow", |
||||
table => new |
||||
{ |
||||
Id = table.Column<int>("INTEGER", nullable: false) |
||||
.Annotation("Sqlite:Autoincrement", true), |
||||
Key = table.Column<string>("TEXT", nullable: true) |
||||
}, |
||||
constraints: table => |
||||
{ |
||||
table.PrimaryKey("PK_PlexShow", x => x.Id); |
||||
table.ForeignKey( |
||||
"FK_PlexShow_Show_Id", |
||||
x => x.Id, |
||||
"Show", |
||||
"Id", |
||||
onDelete: ReferentialAction.Cascade); |
||||
}); |
||||
} |
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder) |
||||
{ |
||||
migrationBuilder.DropTable( |
||||
"PlexEpisode"); |
||||
|
||||
migrationBuilder.DropTable( |
||||
"PlexSeason"); |
||||
|
||||
migrationBuilder.DropTable( |
||||
"PlexShow"); |
||||
} |
||||
} |
||||
} |
Loading…
Reference in new issue