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