Small set of Fixes (#3776)

This commit is contained in:
Joe Milazzo 2025-04-26 12:32:56 -06:00 committed by GitHub
parent 5c06e14a73
commit 7b3198ed9c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 37 additions and 16 deletions

View File

@ -80,7 +80,6 @@
<PackageReference Include="Nager.ArticleNumber" Version="1.0.7" /> <PackageReference Include="Nager.ArticleNumber" Version="1.0.7" />
<PackageReference Include="NetVips" Version="3.0.0" /> <PackageReference Include="NetVips" Version="3.0.0" />
<PackageReference Include="NetVips.Native" Version="8.16.1" /> <PackageReference Include="NetVips.Native" Version="8.16.1" />
<PackageReference Include="NReco.Logging.File" Version="1.2.2" />
<PackageReference Include="Serilog" Version="4.2.0" /> <PackageReference Include="Serilog" Version="4.2.0" />
<PackageReference Include="Serilog.AspNetCore" Version="9.0.0" /> <PackageReference Include="Serilog.AspNetCore" Version="9.0.0" />
<PackageReference Include="Serilog.Enrichers.Thread" Version="4.0.0" /> <PackageReference Include="Serilog.Enrichers.Thread" Version="4.0.0" />

View File

@ -80,7 +80,6 @@ public static class PersonHelper
// If not, create a new Person entity using the real name // If not, create a new Person entity using the real name
dbPerson = new PersonBuilder(personName).Build(); dbPerson = new PersonBuilder(personName).Build();
peopleToAttach.Add(dbPerson); // Add new person to the list to be attached peopleToAttach.Add(dbPerson); // Add new person to the list to be attached
modification = true;
} }
// Add the person to the SeriesMetadataPeople collection // Add the person to the SeriesMetadataPeople collection

View File

@ -399,11 +399,14 @@ public class BookService : IBookService
{ {
// Check if any classes on the html node (some r2l books do this) and move them to body tag for scoping // Check if any classes on the html node (some r2l books do this) and move them to body tag for scoping
var htmlNode = doc.DocumentNode.SelectSingleNode("//html"); var htmlNode = doc.DocumentNode.SelectSingleNode("//html");
if (htmlNode == null || !htmlNode.Attributes.Contains("class")) return body.InnerHtml; if (htmlNode == null) return body.InnerHtml;
var bodyClasses = body.Attributes.Contains("class") ? body.Attributes["class"].Value : string.Empty; var bodyClasses = body.Attributes.Contains("class") ? body.Attributes["class"].Value : string.Empty;
var classes = htmlNode.Attributes["class"].Value + " " + bodyClasses; var htmlClasses = htmlNode.Attributes.Contains("class") ? htmlNode.Attributes["class"].Value : string.Empty;
body.Attributes.Add("class", $"{classes}");
body.Attributes.Add("class", $"{htmlClasses} {bodyClasses}");
// I actually need the body tag itself for the classes, so i will create a div and put the body stuff there. // I actually need the body tag itself for the classes, so i will create a div and put the body stuff there.
return $"<div class=\"{body.Attributes["class"].Value}\">{body.InnerHtml}</div>"; return $"<div class=\"{body.Attributes["class"].Value}\">{body.InnerHtml}</div>";
} }

View File

@ -80,7 +80,6 @@ public class CacheService : ICacheService
/// <returns></returns> /// <returns></returns>
public IEnumerable<FileDimensionDto> GetCachedFileDimensions(string cachePath) public IEnumerable<FileDimensionDto> GetCachedFileDimensions(string cachePath)
{ {
var sw = Stopwatch.StartNew();
var files = _directoryService.GetFilesWithExtension(cachePath, Tasks.Scanner.Parser.Parser.ImageFileExtensions) var files = _directoryService.GetFilesWithExtension(cachePath, Tasks.Scanner.Parser.Parser.ImageFileExtensions)
.OrderByNatural(Path.GetFileNameWithoutExtension) .OrderByNatural(Path.GetFileNameWithoutExtension)
.ToArray(); .ToArray();
@ -186,9 +185,16 @@ public class CacheService : ICacheService
} }
else else
{ {
// Potential BUG: If the folder is left here and there are no files within, this could theoretically return without proper cache // Do an explicit check for files since rarely a "permission denied" error on deleting
// the file can occur, thus leaving an empty folder and we would never re-cache the files.
if (_directoryService.GetFiles(extractPath).Any())
{
return chapter; return chapter;
} }
// Delete the extractPath as ExtractArchive will return if the directory already exists
_directoryService.ClearAndDeleteDirectory(extractPath);
}
} }
var files = chapter?.Files.ToList(); var files = chapter?.Files.ToList();
@ -210,13 +216,13 @@ public class CacheService : ICacheService
/// <returns></returns> /// <returns></returns>
public void ExtractChapterFiles(string extractPath, IReadOnlyList<MangaFile>? files, bool extractPdfImages = false) public void ExtractChapterFiles(string extractPath, IReadOnlyList<MangaFile>? files, bool extractPdfImages = false)
{ {
if (files == null) return; if (files == null || files.Count == 0) return;
var removeNonImages = true; var removeNonImages = true;
var fileCount = files.Count; var fileCount = files.Count;
var extraPath = string.Empty; var extraPath = string.Empty;
var extractDi = _directoryService.FileSystem.DirectoryInfo.New(extractPath); var extractDi = _directoryService.FileSystem.DirectoryInfo.New(extractPath);
if (files.Count > 0 && files[0].Format == MangaFormat.Image) if (files[0].Format == MangaFormat.Image)
{ {
// Check if all the files are Images. If so, do a directory copy, else do the normal copy // Check if all the files are Images. If so, do a directory copy, else do the normal copy
if (files.All(f => f.Format == MangaFormat.Image)) if (files.All(f => f.Format == MangaFormat.Image))

View File

@ -113,7 +113,7 @@ public class ExternalMetadataService : IExternalMetadataService
public async Task FetchExternalDataTask() public async Task FetchExternalDataTask()
{ {
// Find all Series that are eligible and limit // Find all Series that are eligible and limit
var ids = await _unitOfWork.ExternalSeriesMetadataRepository.GetSeriesThatNeedExternalMetadata(25, false); var ids = await _unitOfWork.ExternalSeriesMetadataRepository.GetSeriesThatNeedExternalMetadata(25);
if (ids.Count == 0) return; if (ids.Count == 0) return;
ids = await _unitOfWork.ExternalSeriesMetadataRepository.GetSeriesThatNeedExternalMetadata(25, true); ids = await _unitOfWork.ExternalSeriesMetadataRepository.GetSeriesThatNeedExternalMetadata(25, true);
@ -444,7 +444,7 @@ public class ExternalMetadataService : IExternalMetadataService
{ {
if (errorMessage.Contains("Too many Requests")) if (errorMessage.Contains("Too many Requests"))
{ {
_logger.LogInformation("Hit rate limit, will retry in 3 seconds"); _logger.LogDebug("Hit rate limit, will retry in 3 seconds");
await Task.Delay(3000); await Task.Delay(3000);
result = await (Configuration.KavitaPlusApiUrl + "/api/metadata/v2/series-detail") result = await (Configuration.KavitaPlusApiUrl + "/api/metadata/v2/series-detail")
@ -673,7 +673,7 @@ public class ExternalMetadataService : IExternalMetadataService
foreach (var relation in externalMetadataRelations.Where(r => r.Relation != RelationKind.Parent)) foreach (var relation in externalMetadataRelations.Where(r => r.Relation != RelationKind.Parent))
{ {
var names = new [] {relation.SeriesName.PreferredTitle, relation.SeriesName.RomajiTitle, relation.SeriesName.EnglishTitle, relation.SeriesName.NativeTitle}; List<string> names = new [] {relation.SeriesName.PreferredTitle, relation.SeriesName.RomajiTitle, relation.SeriesName.EnglishTitle, relation.SeriesName.NativeTitle}.Where(s => !string.IsNullOrEmpty(s)).ToList()!;
var relatedSeries = await _unitOfWork.SeriesRepository.GetSeriesByAnyName( var relatedSeries = await _unitOfWork.SeriesRepository.GetSeriesByAnyName(
names, names,
relation.PlusMediaFormat.GetMangaFormats(), relation.PlusMediaFormat.GetMangaFormats(),
@ -1171,6 +1171,14 @@ public class ExternalMetadataService : IExternalMetadataService
return false; return false;
} }
// Some publishers (CBR) can be represented as Boom! Studios/Boom! Town imprint, so let's handle that appropriately
if (publisher.Contains('/') || publisher.Contains("imprint", StringComparison.InvariantCultureIgnoreCase))
{
var imprint = publisher.Split('/')[1].Replace("imprint", string.Empty);
return await UpdateChapterPeople(chapter, settings, PersonRole.Publisher, [publisher]) ||
await UpdateChapterPeople(chapter, settings, PersonRole.Imprint, [imprint]);
}
return await UpdateChapterPeople(chapter, settings, PersonRole.Publisher, [publisher]); return await UpdateChapterPeople(chapter, settings, PersonRole.Publisher, [publisher]);
} }
@ -1224,7 +1232,7 @@ public class ExternalMetadataService : IExternalMetadataService
.DistinctBy(p => Parser.Normalize(p.Name)) .DistinctBy(p => Parser.Normalize(p.Name))
.ToList(); .ToList();
await PersonHelper.UpdateChapterPeopleAsync(chapter, staff, role, _unitOfWork); await PersonHelper.UpdateChapterPeopleAsync(chapter, staff ?? [], role, _unitOfWork);
foreach (var person in chapter.People.Where(p => p.Role == role)) foreach (var person in chapter.People.Where(p => p.Role == role))
{ {

View File

@ -7,7 +7,12 @@
<app-card-actionables [actions]="actionables" (actionHandler)="performAction($event)"></app-card-actionables> <app-card-actionables [actions]="actionables" (actionHandler)="performAction($event)"></app-card-actionables>
} }
<h4 class="header" (click)="sectionClicked($event)" [ngClass]="{'non-selectable': !clickableTitle}"> <h4 class="header" (click)="sectionClicked($event)" [ngClass]="{'non-selectable': !clickableTitle}">
<a [href]="titleLink !== '' ? titleLink : 'javascript:void(0)'" class="section-title">{{title}}</a> @if (titleLink !== '') {
<a [href]="titleLink | safeUrl" class="section-title">{{title}}</a>
} @else {
<a href="javascript:void(0)" class="section-title">{{title}}</a>
}
@if (iconClasses !== '') { @if (iconClasses !== '') {
<i class="{{iconClasses}} title-icon ms-1" aria-hidden="true"></i> <i class="{{iconClasses}} title-icon ms-1" aria-hidden="true"></i>
} }

View File

@ -15,13 +15,14 @@ import {NgClass, NgTemplateOutlet} from '@angular/common';
import {TranslocoDirective} from "@jsverse/transloco"; import {TranslocoDirective} from "@jsverse/transloco";
import {CardActionablesComponent} from "../../../_single-module/card-actionables/card-actionables.component"; import {CardActionablesComponent} from "../../../_single-module/card-actionables/card-actionables.component";
import {ActionItem} from "../../../_services/action-factory.service"; import {ActionItem} from "../../../_services/action-factory.service";
import {SafeUrlPipe} from "../../../_pipes/safe-url.pipe";
@Component({ @Component({
selector: 'app-carousel-reel', selector: 'app-carousel-reel',
templateUrl: './carousel-reel.component.html', templateUrl: './carousel-reel.component.html',
styleUrls: ['./carousel-reel.component.scss'], styleUrls: ['./carousel-reel.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush,
imports: [NgClass, SwiperModule, NgTemplateOutlet, TranslocoDirective, CardActionablesComponent] imports: [NgClass, SwiperModule, NgTemplateOutlet, TranslocoDirective, CardActionablesComponent, SafeUrlPipe]
}) })
export class CarouselReelComponent { export class CarouselReelComponent {