mirror of
https://github.com/Kareadita/Kavita.git
synced 2025-07-09 03:04:19 -04:00
Shakeout Part 2 (#630)
* When flattening directories, ensure the order or directories being enumerated follows a natural sort. Some users are discovering directories in a different order than other machines. * Added a case for volume parsing and fixed a poorly designed negative lookahead. Added a sentence case pipe for formatting things. Added time for all dates. * Some more sentence case * Register user now has a white input * Fixed an issue with Manga up/down reading mode where top of the page was going forwards, when it should have gone backwards * Reworked some code to ensure that scanseries doesn't show errors where in fact there was just nothing to update. * Last updated should be working as intended for new library flow. * Code smell
This commit is contained in:
parent
fcfc9e5159
commit
3907414ae4
@ -10,5 +10,12 @@ namespace API.Tests.Parser
|
||||
{
|
||||
Assert.Equal(expected, API.Parser.Parser.ParseSeries(filename));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("Harrison, Kim - Dates from Hell - Hollows Vol 2.5.epub", "2.5")]
|
||||
public void ParseVolumeTest(string filename, string expected)
|
||||
{
|
||||
Assert.Equal(expected, API.Parser.Parser.ParseVolume(filename));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -239,6 +239,7 @@ namespace API.Tests.Parser
|
||||
[InlineData("Kimi no Koto ga Daidaidaidaidaisuki na 100-nin no Kanojo Chapter 1-10", "1-10")]
|
||||
[InlineData("Deku_&_Bakugo_-_Rising_v1_c1.1.cbz", "1.1")]
|
||||
[InlineData("Chapter 63 - The Promise Made for 520 Cenz.cbr", "63")]
|
||||
[InlineData("Harrison, Kim - The Good, The Bad, and the Undead - Hollows Vol 2.5.epub", "0")]
|
||||
public void ParseChaptersTest(string filename, string expected)
|
||||
{
|
||||
Assert.Equal(expected, API.Parser.Parser.ParseChapter(filename));
|
||||
@ -425,6 +426,14 @@ namespace API.Tests.Parser
|
||||
FullFilePath = filepath, IsSpecial = false
|
||||
});
|
||||
|
||||
filepath = @"E:\Manga\Harrison, Kim - The Good, The Bad, and the Undead - Hollows Vol 2.5.epub";
|
||||
expected.Add(filepath, new ParserInfo
|
||||
{
|
||||
Series = "Harrison, Kim - The Good, The Bad, and the Undead - Hollows", Volumes = "2.5", Edition = "",
|
||||
Chapters = "0", Filename = "Harrison, Kim - The Good, The Bad, and the Undead - Hollows Vol 2.5.epub", Format = MangaFormat.Epub,
|
||||
FullFilePath = filepath, IsSpecial = false
|
||||
});
|
||||
|
||||
// If an image is cover exclusively, ignore it
|
||||
filepath = @"E:\Manga\Seraph of the End\cover.png";
|
||||
expected.Add(filepath, null);
|
||||
|
@ -76,7 +76,8 @@ namespace API.Extensions
|
||||
directoryIndex++;
|
||||
}
|
||||
|
||||
foreach (var subDirectory in directory.EnumerateDirectories())
|
||||
var sort = new NaturalSortComparer();
|
||||
foreach (var subDirectory in directory.EnumerateDirectories().OrderBy(d => d.FullName, sort))
|
||||
{
|
||||
FlattenDirectory(root, subDirectory, ref directoryIndex);
|
||||
}
|
||||
|
@ -470,7 +470,7 @@ namespace API.Parser
|
||||
RegexTimeout),
|
||||
// Hinowa ga CRUSH! 018 (2019) (Digital) (LuCaZ).cbz, Hinowa ga CRUSH! 018.5 (2019) (Digital) (LuCaZ).cbz
|
||||
new Regex(
|
||||
@"^(?!Vol)(?<Series>.+?)\s(?<!vol\. )(?<Chapter>\d+(?:.\d+|-\d+)?)(?:\s\(\d{4}\))?(\b|_|-)",
|
||||
@"^(?!Vol)(?<Series>.+?)(?<!Vol)\.?\s(?<Chapter>\d+(?:.\d+|-\d+)?)(?:\s\(\d{4}\))?(\b|_|-)",
|
||||
MatchOptions,
|
||||
RegexTimeout),
|
||||
// Tower Of God S01 014 (CBT) (digital).cbz
|
||||
|
@ -73,8 +73,17 @@ namespace API.Services.Tasks
|
||||
|
||||
if (!anyFilesExist)
|
||||
{
|
||||
_unitOfWork.SeriesRepository.Remove(series);
|
||||
await CommitAndSend(libraryId, seriesId, totalFiles, parsedSeries, sw, scanElapsedTime, series, chapterIds, token);
|
||||
try
|
||||
{
|
||||
_unitOfWork.SeriesRepository.Remove(series);
|
||||
await CommitAndSend(libraryId, seriesId, totalFiles, parsedSeries, sw, scanElapsedTime, series, chapterIds);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogCritical(ex, "There was an error during ScanSeries to delete the series");
|
||||
await _unitOfWork.RollbackAsync();
|
||||
}
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -97,7 +106,7 @@ namespace API.Services.Tasks
|
||||
}
|
||||
}
|
||||
|
||||
_logger.LogInformation("{SeriesName} has bad naming convention, forcing rescan at a higher directory.", series.OriginalName);
|
||||
_logger.LogInformation("{SeriesName} has bad naming convention, forcing rescan at a higher directory", series.OriginalName);
|
||||
scanner = new ParseScannedFiles(_bookService, _logger);
|
||||
parsedSeries = scanner.ScanLibrariesForSeries(library.Type, dirs.Keys, out var totalFiles2, out var scanElapsedTime2);
|
||||
totalFiles += totalFiles2;
|
||||
@ -109,8 +118,19 @@ namespace API.Services.Tasks
|
||||
// At this point, parsedSeries will have at least one key and we can perform the update. If it still doesn't, just return and don't do anything
|
||||
if (parsedSeries.Count == 0) return;
|
||||
|
||||
UpdateSeries(series, parsedSeries);
|
||||
await CommitAndSend(libraryId, seriesId, totalFiles, parsedSeries, sw, scanElapsedTime, series, chapterIds, token);
|
||||
try
|
||||
{
|
||||
UpdateSeries(series, parsedSeries);
|
||||
await CommitAndSend(libraryId, seriesId, totalFiles, parsedSeries, sw, scanElapsedTime, series, chapterIds);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogCritical(ex, "There was an error during ScanSeries to update the series");
|
||||
await _unitOfWork.RollbackAsync();
|
||||
}
|
||||
// Tell UI that this series is done
|
||||
await _messageHub.Clients.All.SendAsync(SignalREvents.ScanSeries, MessageFactory.ScanSeriesEvent(seriesId, series.Name),
|
||||
cancellationToken: token);
|
||||
}
|
||||
|
||||
private static void RemoveParsedInfosNotForSeries(Dictionary<ParsedSeries, List<ParserInfo>> parsedSeries, Series series)
|
||||
@ -124,10 +144,11 @@ namespace API.Services.Tasks
|
||||
}
|
||||
|
||||
private async Task CommitAndSend(int libraryId, int seriesId, int totalFiles,
|
||||
Dictionary<ParsedSeries, List<ParserInfo>> parsedSeries, Stopwatch sw, long scanElapsedTime, Series series, int[] chapterIds, CancellationToken token)
|
||||
Dictionary<ParsedSeries, List<ParserInfo>> parsedSeries, Stopwatch sw, long scanElapsedTime, Series series, int[] chapterIds)
|
||||
{
|
||||
if (await _unitOfWork.CommitAsync())
|
||||
if (_unitOfWork.HasChanges())
|
||||
{
|
||||
await _unitOfWork.CommitAsync();
|
||||
_logger.LogInformation(
|
||||
"Processed {TotalFiles} files and {ParsedSeriesCount} series in {ElapsedScanTime} milliseconds for {SeriesName}",
|
||||
totalFiles, parsedSeries.Keys.Count, sw.ElapsedMilliseconds + scanElapsedTime, series.Name);
|
||||
@ -135,15 +156,6 @@ namespace API.Services.Tasks
|
||||
await CleanupDbEntities();
|
||||
BackgroundJob.Enqueue(() => _metadataService.RefreshMetadataForSeries(libraryId, seriesId, false));
|
||||
BackgroundJob.Enqueue(() => _cacheService.CleanupChapters(chapterIds));
|
||||
// Tell UI that this series is done
|
||||
await _messageHub.Clients.All.SendAsync(SignalREvents.ScanSeries, MessageFactory.ScanSeriesEvent(seriesId, series.Name),
|
||||
cancellationToken: token);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogCritical(
|
||||
"There was a critical error that resulted in a failed scan. Please check logs and rescan");
|
||||
await _unitOfWork.RollbackAsync();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
<form [formGroup]="resetPasswordForm">
|
||||
<div class="modal-header">
|
||||
<h4 class="modal-title" id="modal-basic-title">Reset {{member.username | titlecase}}'s Password</h4>
|
||||
<h4 class="modal-title" id="modal-basic-title">Reset {{member.username | sentenceCase}}'s Password</h4>
|
||||
<button type="button" class="close" aria-label="Close" (click)="close()">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
|
@ -3,7 +3,7 @@
|
||||
|
||||
<ul ngbNav #nav="ngbNav" [(activeId)]="active" class="nav-tabs nav-pills">
|
||||
<li *ngFor="let tab of tabs" [ngbNavItem]="tab">
|
||||
<a ngbNavLink routerLink="." [fragment]="tab.fragment">{{ tab.title | titlecase }}</a>
|
||||
<a ngbNavLink routerLink="." [fragment]="tab.fragment">{{ tab.title | sentenceCase }}</a>
|
||||
<ng-template ngbNavContent>
|
||||
<ng-container *ngIf="tab.fragment === ''">
|
||||
<app-manage-settings></app-manage-settings>
|
||||
|
@ -4,7 +4,7 @@
|
||||
<div class="col-4"><button class="btn btn-primary float-right" (click)="addLibrary()"><i class="fa fa-plus" aria-hidden="true"></i><span class="phone-hidden"> Add Library</span></button></div>
|
||||
</div>
|
||||
<ul class="list-group" *ngIf="!createLibraryToggle; else createLibrary">
|
||||
<li *ngFor="let library of libraries; let idx = index;" class="list-group-item">
|
||||
<li *ngFor="let library of libraries; let idx = index; trackby: trackbyLibrary" class="list-group-item">
|
||||
<div>
|
||||
<h4>
|
||||
<span id="library-name--{{idx}}">{{library.name}}</span>
|
||||
@ -13,8 +13,8 @@
|
||||
</div>
|
||||
<div class="float-right">
|
||||
<button class="btn btn-secondary mr-2 btn-sm" (click)="scanLibrary(library)" placement="top" ngbTooltip="Scan Library" attr.aria-label="Scan Library"><i class="fa fa-sync-alt" title="Scan"></i></button>
|
||||
<button class="btn btn-danger mr-2 btn-sm" [disabled]="deletionInProgress" (click)="deleteLibrary(library)"><i class="fa fa-trash" placement="top" ngbTooltip="Delete Library" attr.aria-label="Delete {{library.name | titlecase}}"></i></button>
|
||||
<button class="btn btn-primary btn-sm" (click)="editLibrary(library)"><i class="fa fa-pen" placement="top" ngbTooltip="Edit" attr.aria-label="Edit {{library.name | titlecase}}"></i></button>
|
||||
<button class="btn btn-danger mr-2 btn-sm" [disabled]="deletionInProgress" (click)="deleteLibrary(library)"><i class="fa fa-trash" placement="top" ngbTooltip="Delete Library" attr.aria-label="Delete {{library.name | sentenceCase}}"></i></button>
|
||||
<button class="btn btn-primary btn-sm" (click)="editLibrary(library)"><i class="fa fa-pen" placement="top" ngbTooltip="Edit" attr.aria-label="Edit {{library.name | sentenceCase}}"></i></button>
|
||||
</div>
|
||||
</h4>
|
||||
</div>
|
||||
|
@ -25,6 +25,7 @@ export class ManageLibraryComponent implements OnInit, OnDestroy {
|
||||
*/
|
||||
deletionInProgress: boolean = false;
|
||||
scanInProgress: {[key: number]: boolean} = {};
|
||||
libraryTrackBy = (index: number, item: Library) => `${item.name}_${item.lastScanned}_${item.type}_${item.folders.length}`;
|
||||
|
||||
private readonly onDestroy = new Subject<void>();
|
||||
|
||||
|
@ -19,7 +19,7 @@
|
||||
<div>Last Active:
|
||||
<span *ngIf="member.lastActive == '0001-01-01T00:00:00'; else activeDate">Never</span>
|
||||
<ng-template #activeDate>
|
||||
{{member.lastActive | date: 'MM/dd/yyyy'}}
|
||||
{{member.lastActive | date: 'short'}}
|
||||
</ng-template>
|
||||
</div>
|
||||
<div *ngIf="!member.isAdmin">Sharing: {{formatLibraries(member)}}</div>
|
||||
|
@ -19,7 +19,7 @@
|
||||
</div>
|
||||
<div class="row no-gutters">
|
||||
<div class="col" *ngIf="utilityService.isVolume(data)">
|
||||
Added: {{(data.created | date: 'MM/dd/yyyy') || '-'}}
|
||||
Added: {{(data.created | date: 'short') || '-'}}
|
||||
</div>
|
||||
<div class="col">
|
||||
Pages: {{data.pages}}
|
||||
|
@ -95,7 +95,7 @@
|
||||
<ng-template ngbNavContent>
|
||||
<h4>Information</h4>
|
||||
<div class="row no-gutters mb-2">
|
||||
<div class="col-md-6" *ngIf="libraryName">Library: {{libraryName | titlecase}}</div>
|
||||
<div class="col-md-6" *ngIf="libraryName">Library: {{libraryName | sentenceCase}}</div>
|
||||
<div class="col-md-6">Format: <app-tag-badge>{{utilityService.mangaFormat(series.format)}}</app-tag-badge></div>
|
||||
</div>
|
||||
<h4>Volumes</h4>
|
||||
@ -110,10 +110,10 @@
|
||||
<div>
|
||||
<div class="row no-gutters">
|
||||
<div class="col">
|
||||
Created: {{volume.created | date: 'MM/dd/yyyy'}}
|
||||
Created: {{volume.created | date: 'short'}}
|
||||
</div>
|
||||
<div class="col">
|
||||
Last Modified: {{volume.lastModified | date: 'MM/dd/yyyy'}}
|
||||
Last Modified: {{volume.lastModified | date: 'short'}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="row no-gutters">
|
||||
|
@ -38,6 +38,6 @@
|
||||
<app-card-actionables (actionHandler)="performAction($event)" [actions]="actions" [labelBy]="title"></app-card-actionables>
|
||||
</span>
|
||||
</div>
|
||||
<a class="card-title library" [routerLink]="['/library', libraryId]" routerLinkActive="router-link-active" *ngIf="!supressLibraryLink && libraryName">{{libraryName}}</a>
|
||||
<a class="card-title library" [routerLink]="['/library', libraryId]" routerLinkActive="router-link-active" *ngIf="!supressLibraryLink && libraryName">{{libraryName | sentenceCase}}</a>
|
||||
</div>
|
||||
</div>
|
@ -31,8 +31,8 @@
|
||||
<app-infinite-scroller [pageNum]="pageNum" [bufferPages]="5" [goToPage]="goToPageEvent" (pageNumberChange)="handleWebtoonPageChange($event)" [totalPages]="maxPages - 1" [urlProvider]="getPageUrl" (loadNextChapter)="loadNextChapter()" (loadPrevChapter)="loadPrevChapter()"></app-infinite-scroller>
|
||||
</div>
|
||||
<ng-container *ngIf="readerMode === READER_MODE.MANGA_LR || readerMode === READER_MODE.MANGA_UD">
|
||||
<div class="{{readerMode === READER_MODE.MANGA_LR ? 'right' : 'top'}} {{clickOverlayClass('right')}}" (click)="handlePageChange($event, 'right')"></div>
|
||||
<div class="{{readerMode === READER_MODE.MANGA_LR ? 'left' : 'bottom'}} {{clickOverlayClass('left')}}" (click)="handlePageChange($event, 'left')"></div>
|
||||
<div class="{{readerMode === READER_MODE.MANGA_LR ? 'right' : 'bottom'}} {{clickOverlayClass('right')}}" (click)="handlePageChange($event, 'right')"></div>
|
||||
<div class="{{readerMode === READER_MODE.MANGA_LR ? 'left' : 'top'}} {{clickOverlayClass('left')}}" (click)="handlePageChange($event, 'left')"></div>
|
||||
</ng-container>
|
||||
</div>
|
||||
|
||||
|
@ -63,7 +63,7 @@
|
||||
|
||||
<div ngbDropdown class="nav-item dropdown" display="dynamic" placement="bottom-right" *ngIf="(accountService.currentUser$ | async) as user" dropdown>
|
||||
<button class="btn btn-outline-secondary primary-text" ngbDropdownToggle>
|
||||
{{user.username | titlecase}}
|
||||
{{user.username | sentenceCase}}
|
||||
</button>
|
||||
<div ngbDropdownMenu>
|
||||
<a ngbDropdownItem routerLink="/preferences/">User Settings</a>
|
||||
|
@ -11,3 +11,7 @@
|
||||
background-color: #343c59;
|
||||
box-shadow: 0 0 0 0.2rem rgb(68 79 117 / 50%);
|
||||
}
|
||||
|
||||
input {
|
||||
background-color: #fff !important;
|
||||
}
|
14
UI/Web/src/app/shared/sentence-case.pipe.ts
Normal file
14
UI/Web/src/app/shared/sentence-case.pipe.ts
Normal file
@ -0,0 +1,14 @@
|
||||
import { Pipe, PipeTransform } from '@angular/core';
|
||||
|
||||
@Pipe({
|
||||
name: 'sentenceCase'
|
||||
})
|
||||
export class SentenceCasePipe implements PipeTransform {
|
||||
|
||||
transform(value: string | null): string {
|
||||
if (value === null || value === undefined) return '';
|
||||
|
||||
return value.charAt(0).toUpperCase() + value.substr(1);
|
||||
}
|
||||
|
||||
}
|
@ -15,6 +15,7 @@ import { SeriesFormatComponent } from './series-format/series-format.component';
|
||||
import { UpdateNotificationModalComponent } from './update-notification/update-notification-modal.component';
|
||||
import { CircularLoaderComponent } from './circular-loader/circular-loader.component';
|
||||
import { NgCircleProgressModule } from 'ng-circle-progress';
|
||||
import { SentenceCasePipe } from './sentence-case.pipe';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
@ -29,6 +30,7 @@ import { NgCircleProgressModule } from 'ng-circle-progress';
|
||||
SeriesFormatComponent,
|
||||
UpdateNotificationModalComponent,
|
||||
CircularLoaderComponent,
|
||||
SentenceCasePipe,
|
||||
],
|
||||
imports: [
|
||||
CommonModule,
|
||||
@ -40,6 +42,7 @@ import { NgCircleProgressModule } from 'ng-circle-progress';
|
||||
exports: [
|
||||
RegisterMemberComponent,
|
||||
SafeHtmlPipe,
|
||||
SentenceCasePipe,
|
||||
ReadMoreComponent,
|
||||
DrawerComponent,
|
||||
TagBadgeComponent,
|
||||
|
@ -15,7 +15,7 @@
|
||||
<div class="col align-self-center card p-3 m-3" style="width: 12rem;">
|
||||
<span tabindex="0" (click)="select(member)" a11y-click="13,32">
|
||||
<div class="logo-container">
|
||||
<h3 class="card-title text-center">{{member | titlecase}}</h3>
|
||||
<h3 class="card-title text-center">{{member | sentenceCase}}</h3>
|
||||
</div>
|
||||
</span>
|
||||
|
||||
|
@ -55,10 +55,6 @@
|
||||
|
||||
.card-text {
|
||||
font-family: "EBGaramond", "Helvetica Neue", sans-serif;
|
||||
|
||||
input {
|
||||
background-color: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
.alt {
|
||||
@ -81,3 +77,7 @@
|
||||
display: inline-block;
|
||||
color: #343c59;
|
||||
}
|
||||
|
||||
input {
|
||||
background-color: #fff !important;
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user