Time Estimation Cleanup (#1301)

* Moved the calculation for time to read to the backend. Tweaked some logic around showing est time to complete.

* Added debug logging to help pinpoint a duplicate issue in Kavita.

* More combination logic is error checked in a special way for Robbie to reproduce an issue.

* Migrated chapter detail card to use backend for time calculation. Ensure we take all chapters into account for volume time calcs

* Tweaked messaging for some critical logs to include file

* Ensure pages count uses comma separated number

* Moved Hangfire annotations to interface level. Adjusted word count service to always recalculate when user requests via analyze series files.
This commit is contained in:
Joseph Milazzo 2022-05-29 09:50:59 -05:00 committed by GitHub
parent 85b4ad0c58
commit 8e69b6cfc0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 161 additions and 62 deletions

View file

@ -133,6 +133,14 @@ export class ReaderService {
return this.httpClient.get<HourEstimateRange>(this.baseUrl + 'reader/time-left?seriesId=' + seriesId);
}
getTimeToRead(seriesId: number) {
return this.httpClient.get<HourEstimateRange>(this.baseUrl + 'reader/read-time?seriesId=' + seriesId);
}
getManualTimeToRead(wordCount: number, pageCount: number, isEpub: boolean) {
return this.httpClient.get<HourEstimateRange>(this.baseUrl + 'reader/manual-read-time?wordCount=' + wordCount + '&pageCount=' + pageCount + '&isEpub=' + isEpub);
}
/**
* Captures current body color and forces background color to be black. Call @see resetOverrideStyles() on destroy of component to revert changes
*/

View file

@ -52,7 +52,7 @@
<ng-container *ngIf="chapter.pages">
<div class="col-auto mb-2">
<app-icon-and-title [clickable]="false" fontClasses="fa-regular fa-file-lines" title="Pages">
{{chapter.pages}} Pages
{{chapter.pages | number:''}} Pages
</app-icon-and-title>
</div>
<div class="vr d-none d-lg-block m-2"></div>
@ -70,7 +70,7 @@
<ng-container *ngIf="chapter.files[0].format === MangaFormat.EPUB && chapterMetadata !== undefined && chapterMetadata.wordCount > 0 || chapter.files[0].format !== MangaFormat.EPUB">
<div class="col-auto mb-2">
<app-icon-and-title [clickable]="false" fontClasses="fa-regular fa-clock">
{{minHoursToRead}}{{maxHoursToRead !== minHoursToRead ? ('-' + maxHoursToRead) : ''}} Hour{{minHoursToRead > 1 ? 's' : ''}}
{{readingTime.minHours}}{{readingTime.maxHours !== readingTime.minHours ? ('-' + readingTime.maxHours) : ''}} Hour{{readingTime.minHours > 1 ? 's' : ''}}
</app-icon-and-title>
</div>
</ng-container>
@ -229,7 +229,7 @@
<span>{{file.filePath}}</span>
<div class="row g-0">
<div class="col">
Pages: {{file.pages}}
Pages: {{file.pages | number:''}}
</div>
<div class="col" *ngIf="data.hasOwnProperty('created')">
Added: {{(data.created | date: 'short') || '-'}}

View file

@ -6,6 +6,7 @@ import { Observable, of, take } from 'rxjs';
import { Breakpoint, UtilityService } from 'src/app/shared/_services/utility.service';
import { Chapter } from 'src/app/_models/chapter';
import { ChapterMetadata } from 'src/app/_models/chapter-metadata';
import { HourEstimateRange } from 'src/app/_models/hour-estimate-range';
import { LibraryType } from 'src/app/_models/library';
import { MangaFile } from 'src/app/_models/manga-file';
import { MangaFormat } from 'src/app/_models/manga-format';
@ -77,9 +78,11 @@ export class CardDetailDrawerComponent implements OnInit {
ageRating!: string;
summary$: Observable<string> = of('');
readingTime: HourEstimateRange = {maxHours: 1, minHours: 1, avgHours: 1, hasProgress: false};
minHoursToRead: number = 1;
maxHoursToRead: number = 1;
get MangaFormat() {
return MangaFormat;
@ -120,13 +123,13 @@ export class CardDetailDrawerComponent implements OnInit {
this.metadataService.getAgeRating(this.chapterMetadata.ageRating).subscribe(ageRating => this.ageRating = ageRating);
if (this.chapter.files[0].format === MangaFormat.EPUB && this.chapterMetadata.wordCount > 0) {
this.minHoursToRead = parseInt(Math.round(this.chapterMetadata.wordCount / MAX_WORDS_PER_HOUR) + '', 10) || 1;
this.maxHoursToRead = parseInt(Math.round(this.chapterMetadata.wordCount / MIN_WORDS_PER_HOUR) + '', 10) || 1;
} else if (this.chapter.files[0].format !== MangaFormat.EPUB) {
this.minHoursToRead = parseInt(Math.round((this.chapter.pages / MIN_PAGES_PER_MINUTE) / 60) + '', 10) || 1;
this.maxHoursToRead = parseInt(Math.round((this.chapter.pages / MAX_PAGES_PER_MINUTE) / 60) + '', 10) || 1;
let totalPages = this.chapter.pages;
if (!this.isChapter) {
// Need to account for multiple chapters if this is a volume
totalPages = this.utilityService.asVolume(this.data).chapters.map(c => c.pages).reduce((sum, d) => sum + d);
}
this.readerService.getManualTimeToRead(this.chapterMetadata.wordCount, totalPages, this.chapter.files[0].format === MangaFormat.EPUB).subscribe((time) => this.readingTime = time);
});

View file

@ -67,29 +67,28 @@
</app-icon-and-title>
</div>
</ng-container>
<div class="vr d-none d-lg-block m-2"></div>
</ng-container>
<ng-template #showPages>
<div class="d-none d-md-block col-lg-1 col-md-4 col-sm-4 col-4 mb-2">
<app-icon-and-title [clickable]="false" fontClasses="fa-regular fa-file-lines">
{{series.pages}} Pages
{{series.pages | number:''}} Pages
</app-icon-and-title>
</div>
</ng-template>
<div class="vr d-none d-lg-block m-2"></div>
<ng-container *ngIf="series.format === MangaFormat.EPUB && series.wordCount > 0 || series.format !== MangaFormat.EPUB">
<div class="col-lg-1 col-md-4 col-sm-4 col-4 mb-2">
<app-icon-and-title [clickable]="false" fontClasses="fa-regular fa-clock">
{{minHoursToRead}}{{maxHoursToRead !== minHoursToRead ? ('-' + maxHoursToRead) : ''}} Hour{{minHoursToRead > 1 ? 's' : ''}}
{{readingTime.minHours}}{{readingTime.maxHours !== readingTime.minHours ? ('-' + readingTime.maxHours) : ''}} Hour{{readingTime.minHours > 1 ? 's' : ''}}
</app-icon-and-title>
</div>
</ng-container>
<ng-container *ngIf="readingTimeLeft.hasProgress && readingTimeLeft.minHours !== 1 && readingTimeLeft.maxHours !== 1 && readingTimeLeft.avgHours !== 0">
<ng-container *ngIf="readingTimeLeft.hasProgress && readingTimeLeft.avgHours !== 0 ">
<div class="vr d-none d-lg-block m-2"></div>
<div class="col-lg-1 col-md-4 col-sm-4 col-4 mb-2">
<app-icon-and-title [clickable]="false" fontClasses="fa-solid fa-clock">

View file

@ -29,8 +29,7 @@ export class SeriesMetadataDetailComponent implements OnInit, OnChanges {
isCollapsed: boolean = true;
hasExtendedProperites: boolean = false;
minHoursToRead: number = 1;
maxHoursToRead: number = 1;
readingTime: HourEstimateRange = {maxHours: 1, minHours: 1, avgHours: 1, hasProgress: false};
readingTimeLeft: HourEstimateRange = {maxHours: 1, minHours: 1, avgHours: 1, hasProgress: false};
/**
@ -71,14 +70,7 @@ export class SeriesMetadataDetailComponent implements OnInit, OnChanges {
if (this.series !== null) {
this.readerService.getTimeLeft(this.series.id).subscribe((timeLeft) => this.readingTimeLeft = timeLeft);
if (this.series.format === MangaFormat.EPUB && this.series.wordCount > 0) {
this.minHoursToRead = parseInt(Math.round(this.series.wordCount / MAX_WORDS_PER_HOUR) + '', 10) || 1;
this.maxHoursToRead = parseInt(Math.round(this.series.wordCount / MIN_WORDS_PER_HOUR) + '', 10) || 1;
} else if (this.series.format !== MangaFormat.EPUB) {
this.minHoursToRead = parseInt(Math.round((this.series.pages / MIN_PAGES_PER_MINUTE) / 60) + '', 10) || 1;
this.maxHoursToRead = parseInt(Math.round((this.series.pages / MAX_PAGES_PER_MINUTE) / 60) + '', 10) || 1;
}
this.readerService.getTimeToRead(this.series.id).subscribe((time) => this.readingTime = time);
}
}