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:
Joseph Milazzo 2021-10-03 15:40:22 -07:00 committed by GitHub
parent fcfc9e5159
commit 3907414ae4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 88 additions and 37 deletions

View File

@ -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));
}
}
}

View File

@ -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);

View File

@ -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);
}

View File

@ -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

View File

@ -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();
}
}

View File

@ -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">&times;</span>
</button>

View File

@ -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>

View File

@ -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">&nbsp;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>&nbsp;
@ -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>

View File

@ -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>();

View File

@ -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>

View File

@ -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}}

View File

@ -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">

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -11,3 +11,7 @@
background-color: #343c59;
box-shadow: 0 0 0 0.2rem rgb(68 79 117 / 50%);
}
input {
background-color: #fff !important;
}

View 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);
}
}

View File

@ -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,

View File

@ -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>

View File

@ -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;
}