parent
965ed041ae
commit
10322a1867
@ -0,0 +1,47 @@
|
||||
using FizzWare.NBuilder;
|
||||
using FluentAssertions;
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Core.Housekeeping.Housekeepers;
|
||||
using NzbDrone.Core.Languages;
|
||||
using NzbDrone.Core.Movies;
|
||||
using NzbDrone.Core.Movies.Translations;
|
||||
using NzbDrone.Core.Test.Framework;
|
||||
|
||||
namespace NzbDrone.Core.Test.Housekeeping.Housekeepers
|
||||
{
|
||||
[TestFixture]
|
||||
public class CleanupOrphanedMovieTranslationsFixture : DbTest<CleanupOrphanedMovieTranslations, MovieTranslation>
|
||||
{
|
||||
[Test]
|
||||
public void should_delete_orphaned_movie_translation_items()
|
||||
{
|
||||
var translation = Builder<MovieTranslation>.CreateNew()
|
||||
.With(h => h.MovieId = default)
|
||||
.With(h => h.Language = Language.English)
|
||||
.BuildNew();
|
||||
|
||||
Db.Insert(translation);
|
||||
Subject.Clean();
|
||||
AllStoredModels.Should().BeEmpty();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_not_delete_unorphaned_movie_translation_items()
|
||||
{
|
||||
var movie = Builder<Movie>.CreateNew().BuildNew();
|
||||
|
||||
Db.Insert(movie);
|
||||
|
||||
var translation = Builder<MovieTranslation>.CreateNew()
|
||||
.With(h => h.MovieId = default)
|
||||
.With(h => h.Language = Language.English)
|
||||
.With(b => b.MovieId = movie.Id)
|
||||
.BuildNew();
|
||||
|
||||
Db.Insert(translation);
|
||||
|
||||
Subject.Clean();
|
||||
AllStoredModels.Should().HaveCount(1);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,115 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Data;
|
||||
using System.Linq;
|
||||
using System.Text.Json;
|
||||
using Dapper;
|
||||
using FluentMigrator;
|
||||
using NzbDrone.Core.Datastore.Migration.Framework;
|
||||
using NzbDrone.Core.Languages;
|
||||
|
||||
namespace NzbDrone.Core.Datastore.Migration
|
||||
{
|
||||
[Migration(177)]
|
||||
public class language_improvements : NzbDroneMigrationBase
|
||||
{
|
||||
private readonly JsonSerializerOptions _serializerSettings;
|
||||
|
||||
public language_improvements()
|
||||
{
|
||||
_serializerSettings = new JsonSerializerOptions
|
||||
{
|
||||
AllowTrailingCommas = true,
|
||||
IgnoreNullValues = false,
|
||||
PropertyNameCaseInsensitive = true,
|
||||
DictionaryKeyPolicy = JsonNamingPolicy.CamelCase,
|
||||
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
|
||||
WriteIndented = true
|
||||
};
|
||||
}
|
||||
|
||||
protected override void MainDbUpgrade()
|
||||
{
|
||||
// Use original language to set default language fallback for releases
|
||||
// Set all to English (1) on migration to ensure default behavior persists until refresh
|
||||
Alter.Table("Movies").AddColumn("OriginalLanguage").AsInt32().WithDefaultValue((int)Language.English);
|
||||
Alter.Table("Movies").AddColumn("OriginalTitle").AsString().Nullable();
|
||||
|
||||
Alter.Table("Movies").AddColumn("DigitalRelease").AsDateTime().Nullable();
|
||||
|
||||
// Column not used
|
||||
Delete.Column("PhysicalReleaseNote").FromTable("Movies");
|
||||
Delete.Column("SecondaryYearSourceId").FromTable("Movies");
|
||||
|
||||
Alter.Table("NamingConfig").AddColumn("RenameMovies").AsBoolean().WithDefaultValue(0);
|
||||
Execute.Sql("UPDATE NamingConfig SET RenameMovies=RenameEpisodes");
|
||||
Delete.Column("RenameEpisodes").FromTable("NamingConfig");
|
||||
|
||||
//Manual SQL, Fluent Migrator doesn't support multi-column unique contraint on table creation, SQLite doesn't support adding it after creation
|
||||
Execute.Sql("CREATE TABLE MovieTranslations(" +
|
||||
"Id INTEGER PRIMARY KEY, " +
|
||||
"MovieId INTEGER NOT NULL, " +
|
||||
"Title TEXT, " +
|
||||
"CleanTitle TEXT, " +
|
||||
"Overview TEXT, " +
|
||||
"Language INTEGER NOT NULL, " +
|
||||
"Unique(\"MovieId\", \"Language\"));");
|
||||
|
||||
// Prevent failure if two movies have same alt titles
|
||||
Execute.Sql("DROP INDEX IF EXISTS \"IX_AlternativeTitles_CleanTitle\"");
|
||||
|
||||
Execute.WithConnection(FixLanguagesMoveFile);
|
||||
Execute.WithConnection(FixLanguagesHistory);
|
||||
}
|
||||
|
||||
private void FixLanguagesMoveFile(IDbConnection conn, IDbTransaction tran)
|
||||
{
|
||||
var rows = conn.Query<LanguageEntity177>($"SELECT Id, Languages FROM MovieFiles");
|
||||
|
||||
var corrected = new List<LanguageEntity177>();
|
||||
|
||||
foreach (var row in rows)
|
||||
{
|
||||
var languages = JsonSerializer.Deserialize<List<int>>(row.Languages, _serializerSettings);
|
||||
|
||||
var newLanguages = languages.Distinct().ToList();
|
||||
|
||||
corrected.Add(new LanguageEntity177
|
||||
{
|
||||
Id = row.Id,
|
||||
Languages = JsonSerializer.Serialize(newLanguages, _serializerSettings)
|
||||
});
|
||||
}
|
||||
|
||||
var updateSql = "UPDATE MovieFiles SET Languages = @Languages WHERE Id = @Id";
|
||||
conn.Execute(updateSql, corrected, transaction: tran);
|
||||
}
|
||||
|
||||
private void FixLanguagesHistory(IDbConnection conn, IDbTransaction tran)
|
||||
{
|
||||
var rows = conn.Query<LanguageEntity177>($"SELECT Id, Languages FROM History");
|
||||
|
||||
var corrected = new List<LanguageEntity177>();
|
||||
|
||||
foreach (var row in rows)
|
||||
{
|
||||
var languages = JsonSerializer.Deserialize<List<int>>(row.Languages, _serializerSettings);
|
||||
|
||||
var newLanguages = languages.Distinct().ToList();
|
||||
|
||||
corrected.Add(new LanguageEntity177
|
||||
{
|
||||
Id = row.Id,
|
||||
Languages = JsonSerializer.Serialize(newLanguages, _serializerSettings)
|
||||
});
|
||||
}
|
||||
|
||||
var updateSql = "UPDATE History SET Languages = @Languages WHERE Id = @Id";
|
||||
conn.Execute(updateSql, corrected, transaction: tran);
|
||||
}
|
||||
|
||||
private class LanguageEntity177 : ModelBase
|
||||
{
|
||||
public string Languages { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
using Dapper;
|
||||
using NzbDrone.Core.Datastore;
|
||||
|
||||
namespace NzbDrone.Core.Housekeeping.Housekeepers
|
||||
{
|
||||
public class CleanupOrphanedMovieTranslations : IHousekeepingTask
|
||||
{
|
||||
private readonly IMainDatabase _database;
|
||||
|
||||
public CleanupOrphanedMovieTranslations(IMainDatabase database)
|
||||
{
|
||||
_database = database;
|
||||
}
|
||||
|
||||
public void Clean()
|
||||
{
|
||||
using (var mapper = _database.OpenConnection())
|
||||
{
|
||||
mapper.Execute(@"DELETE FROM MovieTranslations
|
||||
WHERE Id IN (
|
||||
SELECT MovieTranslations.Id FROM MovieTranslations
|
||||
LEFT OUTER JOIN Movies
|
||||
ON MovieTranslations.MovieId = Movies.Id
|
||||
WHERE Movies.Id IS NULL)");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,14 @@
|
||||
using NzbDrone.Core.Datastore;
|
||||
using NzbDrone.Core.Languages;
|
||||
|
||||
namespace NzbDrone.Core.Movies.Translations
|
||||
{
|
||||
public class MovieTranslation : ModelBase
|
||||
{
|
||||
public int MovieId { get; set; }
|
||||
public string Title { get; set; }
|
||||
public string CleanTitle { get; set; }
|
||||
public string Overview { get; set; }
|
||||
public Language Language { get; set; }
|
||||
}
|
||||
}
|
@ -0,0 +1,37 @@
|
||||
using System.Collections.Generic;
|
||||
using NzbDrone.Core.Datastore;
|
||||
using NzbDrone.Core.Languages;
|
||||
using NzbDrone.Core.Messaging.Events;
|
||||
|
||||
namespace NzbDrone.Core.Movies.Translations
|
||||
{
|
||||
public interface IMovieTranslationRepository : IBasicRepository<MovieTranslation>
|
||||
{
|
||||
List<MovieTranslation> FindByMovieId(int movieId);
|
||||
List<MovieTranslation> FindByLanguage(Language language);
|
||||
void DeleteForMovies(List<int> movieIds);
|
||||
}
|
||||
|
||||
public class MovieTranslationRepository : BasicRepository<MovieTranslation>, IMovieTranslationRepository
|
||||
{
|
||||
public MovieTranslationRepository(IMainDatabase database, IEventAggregator eventAggregator)
|
||||
: base(database, eventAggregator)
|
||||
{
|
||||
}
|
||||
|
||||
public List<MovieTranslation> FindByMovieId(int movieId)
|
||||
{
|
||||
return Query(x => x.MovieId == movieId);
|
||||
}
|
||||
|
||||
public List<MovieTranslation> FindByLanguage(Language language)
|
||||
{
|
||||
return Query(x => x.Language == language);
|
||||
}
|
||||
|
||||
public void DeleteForMovies(List<int> movieIds)
|
||||
{
|
||||
Delete(x => movieIds.Contains(x.MovieId));
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,79 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Core.Languages;
|
||||
using NzbDrone.Core.Messaging.Events;
|
||||
using NzbDrone.Core.Movies.Events;
|
||||
|
||||
namespace NzbDrone.Core.Movies.Translations
|
||||
{
|
||||
public interface IMovieTranslationService
|
||||
{
|
||||
List<MovieTranslation> GetAllTranslationsForMovie(int movieId);
|
||||
List<MovieTranslation> GetAllTranslationsForLanguage(Language language);
|
||||
List<MovieTranslation> UpdateTranslations(List<MovieTranslation> titles, Movie movie);
|
||||
}
|
||||
|
||||
public class MovieTranslationService : IMovieTranslationService, IHandleAsync<MoviesDeletedEvent>
|
||||
{
|
||||
private readonly IMovieTranslationRepository _translationRepo;
|
||||
private readonly Logger _logger;
|
||||
|
||||
public MovieTranslationService(IMovieTranslationRepository translationRepo,
|
||||
Logger logger)
|
||||
{
|
||||
_translationRepo = translationRepo;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public List<MovieTranslation> GetAllTranslationsForMovie(int movieId)
|
||||
{
|
||||
return _translationRepo.FindByMovieId(movieId).ToList();
|
||||
}
|
||||
|
||||
public List<MovieTranslation> GetAllTranslationsForLanguage(Language language)
|
||||
{
|
||||
return _translationRepo.FindByLanguage(language).ToList();
|
||||
}
|
||||
|
||||
public void RemoveTitle(MovieTranslation title)
|
||||
{
|
||||
_translationRepo.Delete(title);
|
||||
}
|
||||
|
||||
public List<MovieTranslation> UpdateTranslations(List<MovieTranslation> translations, Movie movie)
|
||||
{
|
||||
int movieId = movie.Id;
|
||||
|
||||
// First update the movie ids so we can correlate them later
|
||||
translations.ForEach(t => t.MovieId = movieId);
|
||||
|
||||
// Then throw out any we don't have languages for
|
||||
translations = translations.Where(t => t.Language != null).ToList();
|
||||
|
||||
// Then make sure they are all distinct languages
|
||||
translations = translations.DistinctBy(t => t.Language).ToList();
|
||||
|
||||
// Now find translations to delete, update and insert
|
||||
var existingTranslations = _translationRepo.FindByMovieId(movieId);
|
||||
|
||||
translations.ForEach(c => c.Id = existingTranslations.FirstOrDefault(t => t.Language == c.Language)?.Id ?? 0);
|
||||
|
||||
var insert = translations.Where(t => t.Id == 0).ToList();
|
||||
var update = translations.Where(t => t.Id > 0).ToList();
|
||||
var delete = existingTranslations.Where(t => !translations.Any(c => c.Language == t.Language)).ToList();
|
||||
|
||||
_translationRepo.DeleteMany(delete.ToList());
|
||||
_translationRepo.UpdateMany(update.ToList());
|
||||
_translationRepo.InsertMany(insert.ToList());
|
||||
|
||||
return translations;
|
||||
}
|
||||
|
||||
public void HandleAsync(MoviesDeletedEvent message)
|
||||
{
|
||||
_translationRepo.DeleteForMovies(message.Movies.Select(m => m.Id).ToList());
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in new issue