[FA-6] Need to test Novelpia import
This commit is contained in:
@@ -190,6 +190,48 @@ public class NovelUpdateService
|
||||
return existingChapters.Concat(newChapters).ToList();
|
||||
}
|
||||
|
||||
private static List<Volume> SynchronizeVolumes(
|
||||
List<VolumeMetadata> metadataVolumes,
|
||||
Language rawLanguage,
|
||||
List<Volume>? existingVolumes)
|
||||
{
|
||||
existingVolumes ??= new List<Volume>();
|
||||
var result = new List<Volume>();
|
||||
|
||||
foreach (var metaVolume in metadataVolumes)
|
||||
{
|
||||
// Match volumes by Order (unique per novel)
|
||||
var existingVolume = existingVolumes.FirstOrDefault(v => v.Order == metaVolume.Order);
|
||||
|
||||
if (existingVolume != null)
|
||||
{
|
||||
// Volume exists - sync its chapters
|
||||
existingVolume.Chapters = SynchronizeChapters(
|
||||
metaVolume.Chapters,
|
||||
rawLanguage,
|
||||
existingVolume.Chapters);
|
||||
result.Add(existingVolume);
|
||||
}
|
||||
else
|
||||
{
|
||||
// New volume - create it with synced chapters
|
||||
var newVolume = new Volume
|
||||
{
|
||||
Order = metaVolume.Order,
|
||||
Name = LocalizationKey.CreateFromText(metaVolume.Name, rawLanguage),
|
||||
Chapters = SynchronizeChapters(metaVolume.Chapters, rawLanguage, null)
|
||||
};
|
||||
result.Add(newVolume);
|
||||
}
|
||||
}
|
||||
|
||||
// Keep existing volumes not in metadata (user-created volumes)
|
||||
var metaOrders = metadataVolumes.Select(v => v.Order).ToHashSet();
|
||||
result.AddRange(existingVolumes.Where(v => !metaOrders.Contains(v.Order)));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static (Image? image, bool shouldPublishEvent) HandleCoverImage(
|
||||
ImageData? newCoverData,
|
||||
Image? existingCoverImage)
|
||||
@@ -232,7 +274,7 @@ public class NovelUpdateService
|
||||
metadata.SystemTags,
|
||||
metadata.RawLanguage);
|
||||
|
||||
var chapters = SynchronizeChapters(metadata.Chapters, metadata.RawLanguage, null);
|
||||
var volumes = SynchronizeVolumes(metadata.Volumes, metadata.RawLanguage, null);
|
||||
|
||||
var novel = new Novel
|
||||
{
|
||||
@@ -243,7 +285,7 @@ public class NovelUpdateService
|
||||
CoverImage = metadata.CoverImage != null
|
||||
? new Image { OriginalPath = metadata.CoverImage.Url }
|
||||
: null,
|
||||
Chapters = chapters,
|
||||
Volumes = volumes,
|
||||
Description = LocalizationKey.CreateFromText(metadata.Description, metadata.RawLanguage),
|
||||
Name = LocalizationKey.CreateFromText(metadata.Name, metadata.RawLanguage),
|
||||
RawStatus = metadata.RawStatus,
|
||||
@@ -289,7 +331,9 @@ public class NovelUpdateService
|
||||
.Include(n => n.Description)
|
||||
.ThenInclude(lk => lk.Texts)
|
||||
.Include(n => n.Tags)
|
||||
.Include(n => n.Chapters).ThenInclude(chapter => chapter.Body)
|
||||
.Include(n => n.Volumes)
|
||||
.ThenInclude(volume => volume.Chapters)
|
||||
.ThenInclude(chapter => chapter.Body)
|
||||
.ThenInclude(localizationKey => localizationKey.Texts)
|
||||
.Include(n => n.CoverImage)
|
||||
.FirstOrDefaultAsync(n =>
|
||||
@@ -326,11 +370,11 @@ public class NovelUpdateService
|
||||
metadata.SystemTags,
|
||||
metadata.RawLanguage);
|
||||
|
||||
// Synchronize chapters (add only)
|
||||
novel.Chapters = SynchronizeChapters(
|
||||
metadata.Chapters,
|
||||
// Synchronize volumes (and their chapters)
|
||||
novel.Volumes = SynchronizeVolumes(
|
||||
metadata.Volumes,
|
||||
metadata.RawLanguage,
|
||||
existingNovel.Chapters);
|
||||
existingNovel.Volumes);
|
||||
|
||||
// Handle cover image
|
||||
(novel.CoverImage, shouldPublishCoverEvent) = HandleCoverImage(
|
||||
@@ -352,31 +396,40 @@ public class NovelUpdateService
|
||||
}
|
||||
|
||||
// Publish chapter pull events for chapters without body content
|
||||
var chaptersNeedingPull = novel.Chapters
|
||||
.Where(c => c.Body?.Texts == null || !c.Body.Texts.Any())
|
||||
.ToList();
|
||||
|
||||
foreach (var chapter in chaptersNeedingPull)
|
||||
foreach (var volume in novel.Volumes)
|
||||
{
|
||||
await _eventBus.Publish(new ChapterPullRequestedEvent
|
||||
var chaptersNeedingPull = volume.Chapters
|
||||
.Where(c => c.Body?.Texts == null || !c.Body.Texts.Any())
|
||||
.ToList();
|
||||
|
||||
foreach (var chapter in chaptersNeedingPull)
|
||||
{
|
||||
NovelId = novel.Id,
|
||||
ChapterNumber = chapter.Order
|
||||
});
|
||||
await _eventBus.Publish(new ChapterPullRequestedEvent
|
||||
{
|
||||
NovelId = novel.Id,
|
||||
VolumeId = volume.Id,
|
||||
ChapterOrder = chapter.Order
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return novel;
|
||||
}
|
||||
|
||||
public async Task<Chapter> PullChapterContents(uint novelId, uint chapterNumber)
|
||||
public async Task<Chapter> PullChapterContents(uint novelId, uint volumeId, uint chapterOrder)
|
||||
{
|
||||
var novel = await _dbContext.Novels.Where(novel => novel.Id == novelId)
|
||||
.Include(novel => novel.Chapters)
|
||||
.Include(novel => novel.Volumes)
|
||||
.ThenInclude(volume => volume.Chapters)
|
||||
.ThenInclude(chapter => chapter.Body)
|
||||
.ThenInclude(body => body.Texts)
|
||||
.Include(novel => novel.Source).Include(novel => novel.Chapters).ThenInclude(chapter => chapter.Images)
|
||||
.Include(novel => novel.Source)
|
||||
.Include(novel => novel.Volumes)
|
||||
.ThenInclude(volume => volume.Chapters)
|
||||
.ThenInclude(chapter => chapter.Images)
|
||||
.FirstOrDefaultAsync();
|
||||
var chapter = novel.Chapters.Where(chapter => chapter.Order == chapterNumber).FirstOrDefault();
|
||||
var volume = novel.Volumes.FirstOrDefault(v => v.Id == volumeId);
|
||||
var chapter = volume.Chapters.FirstOrDefault(c => c.Order == chapterOrder);
|
||||
var adapter = _sourceAdapters.FirstOrDefault(adapter => adapter.SourceDescriptor.Key == novel.Source.Key);
|
||||
var rawChapter = await adapter.GetRawChapter(chapter.Url);
|
||||
|
||||
@@ -478,12 +531,13 @@ public class NovelUpdateService
|
||||
return importNovelRequestEvent;
|
||||
}
|
||||
|
||||
public async Task<ChapterPullRequestedEvent> QueueChapterPull(uint novelId, uint chapterNumber)
|
||||
public async Task<ChapterPullRequestedEvent> QueueChapterPull(uint novelId, uint volumeId, uint chapterOrder)
|
||||
{
|
||||
var chapterPullEvent = new ChapterPullRequestedEvent()
|
||||
{
|
||||
NovelId = novelId,
|
||||
ChapterNumber = chapterNumber
|
||||
VolumeId = volumeId,
|
||||
ChapterOrder = chapterOrder
|
||||
};
|
||||
await _eventBus.Publish(chapterPullEvent);
|
||||
return chapterPullEvent;
|
||||
@@ -495,9 +549,10 @@ public class NovelUpdateService
|
||||
.Include(n => n.CoverImage)
|
||||
.Include(n => n.Name).ThenInclude(k => k.Texts)
|
||||
.Include(n => n.Description).ThenInclude(k => k.Texts)
|
||||
.Include(n => n.Chapters).ThenInclude(c => c.Images)
|
||||
.Include(n => n.Chapters).ThenInclude(c => c.Name).ThenInclude(k => k.Texts)
|
||||
.Include(n => n.Chapters).ThenInclude(c => c.Body).ThenInclude(k => k.Texts)
|
||||
.Include(n => n.Volumes).ThenInclude(v => v.Name).ThenInclude(k => k.Texts)
|
||||
.Include(n => n.Volumes).ThenInclude(v => v.Chapters).ThenInclude(c => c.Images)
|
||||
.Include(n => n.Volumes).ThenInclude(v => v.Chapters).ThenInclude(c => c.Name).ThenInclude(k => k.Texts)
|
||||
.Include(n => n.Volumes).ThenInclude(v => v.Chapters).ThenInclude(c => c.Body).ThenInclude(k => k.Texts)
|
||||
.FirstOrDefaultAsync(n => n.Id == novelId);
|
||||
|
||||
if (novel == null)
|
||||
@@ -505,8 +560,12 @@ public class NovelUpdateService
|
||||
|
||||
// Collect all LocalizationKey IDs for cleanup
|
||||
var locKeyIds = new List<Guid> { novel.Name.Id, novel.Description.Id };
|
||||
locKeyIds.AddRange(novel.Chapters.Select(c => c.Name.Id));
|
||||
locKeyIds.AddRange(novel.Chapters.Select(c => c.Body.Id));
|
||||
foreach (var volume in novel.Volumes)
|
||||
{
|
||||
locKeyIds.Add(volume.Name.Id);
|
||||
locKeyIds.AddRange(volume.Chapters.Select(c => c.Name.Id));
|
||||
locKeyIds.AddRange(volume.Chapters.Select(c => c.Body.Id));
|
||||
}
|
||||
|
||||
// 1. Remove LocalizationRequests referencing these keys
|
||||
var locRequests = await _dbContext.LocalizationRequests
|
||||
@@ -517,19 +576,26 @@ public class NovelUpdateService
|
||||
// 2. Remove LocalizationTexts (NO ACTION FK - won't cascade)
|
||||
_dbContext.RemoveRange(novel.Name.Texts);
|
||||
_dbContext.RemoveRange(novel.Description.Texts);
|
||||
foreach (var chapter in novel.Chapters)
|
||||
foreach (var volume in novel.Volumes)
|
||||
{
|
||||
_dbContext.RemoveRange(chapter.Name.Texts);
|
||||
_dbContext.RemoveRange(chapter.Body.Texts);
|
||||
_dbContext.RemoveRange(volume.Name.Texts);
|
||||
foreach (var chapter in volume.Chapters)
|
||||
{
|
||||
_dbContext.RemoveRange(chapter.Name.Texts);
|
||||
_dbContext.RemoveRange(chapter.Body.Texts);
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Remove Images (NO ACTION FK - won't cascade)
|
||||
if (novel.CoverImage != null)
|
||||
_dbContext.Images.Remove(novel.CoverImage);
|
||||
foreach (var chapter in novel.Chapters)
|
||||
_dbContext.Images.RemoveRange(chapter.Images);
|
||||
foreach (var volume in novel.Volumes)
|
||||
{
|
||||
foreach (var chapter in volume.Chapters)
|
||||
_dbContext.Images.RemoveRange(chapter.Images);
|
||||
}
|
||||
|
||||
// 4. Remove novel - cascades: chapters, localization keys, tag mappings
|
||||
// 4. Remove novel - cascades: volumes, chapters, localization keys, tag mappings
|
||||
_dbContext.Novels.Remove(novel);
|
||||
await _dbContext.SaveChangesAsync();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user