Fixed: Improve synchronization logic for import list items

Closes #7511
import-list-items-id-lookup
Mark McDowall 4 months ago
parent c885fb81f9
commit 92856d9231
No known key found for this signature in database

@ -12,46 +12,256 @@ namespace NzbDrone.Core.Test.ImportListTests
{
public class ImportListItemServiceFixture : CoreTest<ImportListItemService>
{
[SetUp]
public void SetUp()
private void GivenExisting(List<ImportListItemInfo> existing)
{
var existing = Builder<ImportListItemInfo>.CreateListOfSize(3)
Mocker.GetMock<IImportListItemInfoRepository>()
.Setup(v => v.GetAllForLists(It.IsAny<List<int>>()))
.Returns(existing);
}
[Test]
public void should_insert_new_update_existing_and_delete_missing_based_on_tvdb_id()
{
var existing = Builder<ImportListItemInfo>.CreateListOfSize(2)
.All()
.With(s => s.TvdbId = 0)
.With(s => s.ImdbId = null)
.With(s => s.TmdbId = 0)
.With(s => s.MalId = 0)
.With(s => s.AniListId = 0)
.TheFirst(1)
.With(s => s.TvdbId = 6)
.With(s => s.ImdbId = "6")
.TheNext(1)
.With(s => s.TvdbId = 7)
.Build().ToList();
var newItem = Builder<ImportListItemInfo>.CreateNew()
.With(s => s.TvdbId = 5)
.With(s => s.ImdbId = null)
.With(s => s.TmdbId = 0)
.With(s => s.MalId = 0)
.With(s => s.AniListId = 0)
.Build();
var updatedItem = Builder<ImportListItemInfo>.CreateNew()
.With(s => s.TvdbId = 6)
.With(s => s.ImdbId = null)
.With(s => s.TmdbId = 0)
.With(s => s.MalId = 0)
.With(s => s.AniListId = 0)
.Build();
GivenExisting(existing);
var newItems = new List<ImportListItemInfo> { newItem, updatedItem };
var numDeleted = Subject.SyncSeriesForList(newItems, 1);
numDeleted.Should().Be(1);
Mocker.GetMock<IImportListItemInfoRepository>()
.Verify(v => v.InsertMany(It.Is<List<ImportListItemInfo>>(s => s.Count == 1 && s[0].TvdbId == newItem.TvdbId)), Times.Once());
Mocker.GetMock<IImportListItemInfoRepository>()
.Verify(v => v.UpdateMany(It.Is<List<ImportListItemInfo>>(s => s.Count == 1 && s[0].TvdbId == updatedItem.TvdbId)), Times.Once());
Mocker.GetMock<IImportListItemInfoRepository>()
.Verify(v => v.DeleteMany(It.Is<List<ImportListItemInfo>>(s => s.Count == 1 && s[0].TvdbId != newItem.TvdbId && s[0].TvdbId != updatedItem.TvdbId)), Times.Once());
}
[Test]
public void should_insert_new_update_existing_and_delete_missing_based_on_imdb_id()
{
var existing = Builder<ImportListItemInfo>.CreateListOfSize(2)
.All()
.With(s => s.TvdbId = 0)
.With(s => s.ImdbId = null)
.With(s => s.TmdbId = 0)
.With(s => s.MalId = 0)
.With(s => s.AniListId = 0)
.TheFirst(1)
.With(s => s.ImdbId = "6")
.TheNext(1)
.With(s => s.ImdbId = "7")
.Build().ToList();
var newItem = Builder<ImportListItemInfo>.CreateNew()
.With(s => s.TvdbId = 0)
.With(s => s.ImdbId = "5")
.With(s => s.TmdbId = 0)
.With(s => s.MalId = 0)
.With(s => s.AniListId = 0)
.Build();
var updatedItem = Builder<ImportListItemInfo>.CreateNew()
.With(s => s.TvdbId = 0)
.With(s => s.ImdbId = "6")
.With(s => s.TmdbId = 6)
.With(s => s.MalId = 0)
.With(s => s.AniListId = 0)
.Build();
GivenExisting(existing);
var newItems = new List<ImportListItemInfo> { newItem, updatedItem };
var numDeleted = Subject.SyncSeriesForList(newItems, 1);
numDeleted.Should().Be(1);
Mocker.GetMock<IImportListItemInfoRepository>()
.Verify(v => v.InsertMany(It.Is<List<ImportListItemInfo>>(s => s.Count == 1 && s[0].ImdbId == newItem.ImdbId)), Times.Once());
Mocker.GetMock<IImportListItemInfoRepository>()
.Verify(v => v.UpdateMany(It.Is<List<ImportListItemInfo>>(s => s.Count == 1 && s[0].ImdbId == updatedItem.ImdbId)), Times.Once());
Mocker.GetMock<IImportListItemInfoRepository>()
.Verify(v => v.DeleteMany(It.Is<List<ImportListItemInfo>>(s => s.Count == 1 && s[0].ImdbId != newItem.ImdbId && s[0].ImdbId != updatedItem.ImdbId)), Times.Once());
}
[Test]
public void should_insert_new_update_existing_and_delete_missing_based_on_tmdb_id()
{
var existing = Builder<ImportListItemInfo>.CreateListOfSize(2)
.All()
.With(s => s.TvdbId = 0)
.With(s => s.ImdbId = null)
.With(s => s.TmdbId = 0)
.With(s => s.MalId = 0)
.With(s => s.AniListId = 0)
.TheFirst(1)
.With(s => s.TmdbId = 6)
.TheNext(1)
.With(s => s.TvdbId = 8)
.With(s => s.ImdbId = "8")
.With(s => s.TmdbId = 7)
.Build().ToList();
var newItem = Builder<ImportListItemInfo>.CreateNew()
.With(s => s.TvdbId = 0)
.With(s => s.ImdbId = null)
.With(s => s.TmdbId = 5)
.With(s => s.MalId = 0)
.With(s => s.AniListId = 0)
.Build();
var updatedItem = Builder<ImportListItemInfo>.CreateNew()
.With(s => s.TvdbId = 0)
.With(s => s.ImdbId = null)
.With(s => s.TmdbId = 6)
.With(s => s.MalId = 0)
.With(s => s.AniListId = 0)
.Build();
GivenExisting(existing);
var newItems = new List<ImportListItemInfo> { newItem, updatedItem };
var numDeleted = Subject.SyncSeriesForList(newItems, 1);
numDeleted.Should().Be(1);
Mocker.GetMock<IImportListItemInfoRepository>()
.Setup(v => v.GetAllForLists(It.IsAny<List<int>>()))
.Returns(existing);
.Verify(v => v.InsertMany(It.Is<List<ImportListItemInfo>>(s => s.Count == 1 && s[0].TmdbId == newItem.TmdbId)), Times.Once());
Mocker.GetMock<IImportListItemInfoRepository>()
.Verify(v => v.UpdateMany(It.Is<List<ImportListItemInfo>>(s => s.Count == 1 && s[0].TmdbId == updatedItem.TmdbId)), Times.Once());
Mocker.GetMock<IImportListItemInfoRepository>()
.Verify(v => v.DeleteMany(It.Is<List<ImportListItemInfo>>(s => s.Count == 1 && s[0].TmdbId != newItem.TmdbId && s[0].TmdbId != updatedItem.TmdbId)), Times.Once());
}
[Test]
public void should_insert_new_update_existing_and_delete_missing()
public void should_insert_new_update_existing_and_delete_missing_based_on_mal_id()
{
var newItems = Builder<ImportListItemInfo>.CreateListOfSize(3)
var existing = Builder<ImportListItemInfo>.CreateListOfSize(2)
.All()
.With(s => s.TvdbId = 0)
.With(s => s.ImdbId = null)
.With(s => s.TmdbId = 0)
.With(s => s.MalId = 0)
.With(s => s.AniListId = 0)
.TheFirst(1)
.With(s => s.TvdbId = 5)
.With(s => s.MalId = 6)
.TheNext(1)
.With(s => s.TvdbId = 6)
.With(s => s.MalId = 7)
.Build().ToList();
var newItem = Builder<ImportListItemInfo>.CreateNew()
.With(s => s.TvdbId = 0)
.With(s => s.ImdbId = null)
.With(s => s.TmdbId = 0)
.With(s => s.MalId = 5)
.With(s => s.AniListId = 0)
.Build();
var updatedItem = Builder<ImportListItemInfo>.CreateNew()
.With(s => s.TvdbId = 0)
.With(s => s.ImdbId = null)
.With(s => s.TmdbId = 0)
.With(s => s.MalId = 6)
.With(s => s.AniListId = 0)
.Build();
GivenExisting(existing);
var newItems = new List<ImportListItemInfo> { newItem, updatedItem };
var numDeleted = Subject.SyncSeriesForList(newItems, 1);
numDeleted.Should().Be(1);
Mocker.GetMock<IImportListItemInfoRepository>()
.Verify(v => v.InsertMany(It.Is<List<ImportListItemInfo>>(s => s.Count == 1 && s[0].MalId == newItem.MalId)), Times.Once());
Mocker.GetMock<IImportListItemInfoRepository>()
.Verify(v => v.UpdateMany(It.Is<List<ImportListItemInfo>>(s => s.Count == 1 && s[0].MalId == updatedItem.MalId)), Times.Once());
Mocker.GetMock<IImportListItemInfoRepository>()
.Verify(v => v.DeleteMany(It.Is<List<ImportListItemInfo>>(s => s.Count == 1 && s[0].MalId != newItem.MalId && s[0].MalId != updatedItem.MalId)), Times.Once());
}
[Test]
public void should_insert_new_update_existing_and_delete_missing_based_on_anilist_id()
{
var existing = Builder<ImportListItemInfo>.CreateListOfSize(2)
.All()
.With(s => s.TvdbId = 0)
.With(s => s.ImdbId = null)
.With(s => s.TmdbId = 0)
.With(s => s.MalId = 0)
.With(s => s.AniListId = 0)
.TheFirst(1)
.With(s => s.AniListId = 6)
.TheNext(1)
.With(s => s.TvdbId = 7)
.With(s => s.AniListId = 7)
.Build().ToList();
var newItem = Builder<ImportListItemInfo>.CreateNew()
.With(s => s.TvdbId = 0)
.With(s => s.ImdbId = null)
.With(s => s.TmdbId = 0)
.With(s => s.MalId = 0)
.With(s => s.AniListId = 5)
.Build();
var updatedItem = Builder<ImportListItemInfo>.CreateNew()
.With(s => s.TvdbId = 0)
.With(s => s.ImdbId = null)
.With(s => s.TmdbId = 0)
.With(s => s.MalId = 0)
.With(s => s.AniListId = 6)
.Build();
GivenExisting(existing);
var newItems = new List<ImportListItemInfo> { newItem, updatedItem };
var numDeleted = Subject.SyncSeriesForList(newItems, 1);
numDeleted.Should().Be(1);
Mocker.GetMock<IImportListItemInfoRepository>()
.Verify(v => v.InsertMany(It.Is<List<ImportListItemInfo>>(s => s.Count == 1 && s[0].TvdbId == 5)), Times.Once());
.Verify(v => v.InsertMany(It.Is<List<ImportListItemInfo>>(s => s.Count == 1 && s[0].AniListId == newItem.AniListId)), Times.Once());
Mocker.GetMock<IImportListItemInfoRepository>()
.Verify(v => v.UpdateMany(It.Is<List<ImportListItemInfo>>(s => s.Count == 2 && s[0].TvdbId == 6 && s[1].TvdbId == 7)), Times.Once());
.Verify(v => v.UpdateMany(It.Is<List<ImportListItemInfo>>(s => s.Count == 1 && s[0].AniListId == updatedItem.AniListId)), Times.Once());
Mocker.GetMock<IImportListItemInfoRepository>()
.Verify(v => v.DeleteMany(It.Is<List<ImportListItemInfo>>(s => s.Count == 1 && s[0].TvdbId == 8)), Times.Once());
.Verify(v => v.DeleteMany(It.Is<List<ImportListItemInfo>>(s => s.Count == 1 && s[0].AniListId != newItem.AniListId && s[0].AniListId != updatedItem.AniListId)), Times.Once());
}
}
}

@ -1,6 +1,7 @@
using System.Collections.Generic;
using System.Linq;
using NLog;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.ThingiProvider.Events;
@ -30,14 +31,38 @@ namespace NzbDrone.Core.ImportLists.ImportListItems
{
var existingListSeries = GetAllForLists(new List<int> { listId });
listSeries.ForEach(l => l.Id = existingListSeries.FirstOrDefault(e => e.TvdbId == l.TvdbId)?.Id ?? 0);
var toAdd = new List<ImportListItemInfo>();
var toUpdate = new List<ImportListItemInfo>();
_importListSeriesRepository.InsertMany(listSeries.Where(l => l.Id == 0).ToList());
_importListSeriesRepository.UpdateMany(listSeries.Where(l => l.Id > 0).ToList());
var toDelete = existingListSeries.Where(l => !listSeries.Any(x => x.TvdbId == l.TvdbId)).ToList();
_importListSeriesRepository.DeleteMany(toDelete);
listSeries.ForEach(item =>
{
var existingItem = FindItem(existingListSeries, item);
return toDelete.Count;
if (existingItem == null)
{
toAdd.Add(item);
return;
}
// Remove so we'll only be left with items to remove at the end
existingListSeries.Remove(existingItem);
toUpdate.Add(existingItem);
existingItem.Title = item.Title;
existingItem.Year = item.Year;
existingItem.TvdbId = item.TvdbId;
existingItem.ImdbId = item.ImdbId;
existingItem.TmdbId = item.TmdbId;
existingItem.MalId = item.MalId;
existingItem.AniListId = item.AniListId;
existingItem.ReleaseDate = item.ReleaseDate;
});
_importListSeriesRepository.InsertMany(toAdd);
_importListSeriesRepository.UpdateMany(toUpdate);
_importListSeriesRepository.DeleteMany(existingListSeries);
return existingListSeries.Count;
}
public List<ImportListItemInfo> GetAllForLists(List<int> listIds)
@ -55,5 +80,38 @@ namespace NzbDrone.Core.ImportLists.ImportListItems
{
return _importListSeriesRepository.Exists(tvdbId, imdbId);
}
private ImportListItemInfo FindItem(List<ImportListItemInfo> existingItems, ImportListItemInfo item)
{
return existingItems.FirstOrDefault(e =>
{
if (e.TvdbId > 0 && item.TvdbId > 0 && e.TvdbId == item.TvdbId)
{
return true;
}
if (e.ImdbId.IsNotNullOrWhiteSpace() && item.ImdbId.IsNotNullOrWhiteSpace() && e.ImdbId == item.ImdbId)
{
return true;
}
if (e.TmdbId > 0 && item.TmdbId > 0 && e.TmdbId == item.TmdbId)
{
return true;
}
if (e.MalId > 0 && item.MalId > 0 && e.MalId == item.MalId)
{
return true;
}
if (e.AniListId > 0 && item.AniListId > 0 && e.AniListId == item.AniListId)
{
return true;
}
return false;
});
}
}
}

Loading…
Cancel
Save