mirror of
https://github.com/Kareadita/Kavita.git
synced 2025-07-09 03:04:19 -04:00
Release Testing Day 3 (#1496)
* Tweaked log messaging for library scan when no files were scanned. * When a theme that is set gets removed due to a scan, inform the user to refresh. * Fixed a typo and make Darkness -> Brightness * Make download theme files allowed to be invoked by non-authenticated users, to allow new users to get the default theme. * Hide all series side nav item if there are no libraries exposed to the user * Fixed an API for Tachiyomi when syncing progress * Fixed dashboard not responding to Series Removed and Added events. Ensure we send SeriesRemoved events when they are deleted. * Reverted Hangfire SQLite due to aborted jobs being resumed, when they shouldnt. Fixed some scan loop issues where cover gen wouldn't be invoked always on new libraries.
This commit is contained in:
parent
ab21c1b49e
commit
f2249ea39b
@ -50,7 +50,7 @@ public class TachiyomiController : BaseApiController
|
||||
if (prevChapterId == -1)
|
||||
{
|
||||
var series = await _unitOfWork.SeriesRepository.GetSeriesDtoByIdAsync(seriesId, userId);
|
||||
var userHasProgress = series.PagesRead == 0 || series.PagesRead < series.Pages;
|
||||
var userHasProgress = series.PagesRead != 0 && series.PagesRead < series.Pages;
|
||||
|
||||
// If the user doesn't have progress, then return null, which the extension will catch as 204 (no content) and report nothing as read
|
||||
if (!userHasProgress) return null;
|
||||
|
@ -51,6 +51,7 @@ public class ThemeController : BaseApiController
|
||||
/// Returns css content to the UI. UI is expected to escape the content
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[AllowAnonymous]
|
||||
[HttpGet("download-content")]
|
||||
public async Task<ActionResult<string>> GetThemeContent(int themeId)
|
||||
{
|
||||
|
@ -122,7 +122,7 @@ public interface ISeriesRepository
|
||||
Task<Series> GetSeriesByFolderPath(string folder);
|
||||
Task<Series> GetFullSeriesByName(string series, int libraryId);
|
||||
Task<Series> GetFullSeriesByAnyName(string seriesName, string localizedName, int libraryId, MangaFormat format, bool withFullIncludes = true);
|
||||
Task RemoveSeriesNotInList(IList<ParsedSeries> seenSeries, int libraryId);
|
||||
Task<List<Series>> RemoveSeriesNotInList(IList<ParsedSeries> seenSeries, int libraryId);
|
||||
Task<IDictionary<string, IList<SeriesModified>>> GetFolderPathMap(int libraryId);
|
||||
}
|
||||
|
||||
@ -230,6 +230,7 @@ public class SeriesRepository : ISeriesRepository
|
||||
{
|
||||
return await _context.Series
|
||||
.Where(s => s.Id == seriesId)
|
||||
.Include(s => s.Relations)
|
||||
.Include(s => s.Metadata)
|
||||
.ThenInclude(m => m.People)
|
||||
.Include(s => s.Metadata)
|
||||
@ -1274,9 +1275,9 @@ public class SeriesRepository : ISeriesRepository
|
||||
/// </summary>
|
||||
/// <param name="seenSeries"></param>
|
||||
/// <param name="libraryId"></param>
|
||||
public async Task RemoveSeriesNotInList(IList<ParsedSeries> seenSeries, int libraryId)
|
||||
public async Task<List<Series>> RemoveSeriesNotInList(IList<ParsedSeries> seenSeries, int libraryId)
|
||||
{
|
||||
if (seenSeries.Count == 0) return;
|
||||
if (seenSeries.Count == 0) return new List<Series>();
|
||||
var ids = new List<int>();
|
||||
foreach (var parsedSeries in seenSeries)
|
||||
{
|
||||
@ -1289,7 +1290,6 @@ public class SeriesRepository : ISeriesRepository
|
||||
{
|
||||
ids.Add(series);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
var seriesToRemove = await _context.Series
|
||||
@ -1298,6 +1298,8 @@ public class SeriesRepository : ISeriesRepository
|
||||
.ToListAsync();
|
||||
|
||||
_context.Series.RemoveRange(seriesToRemove);
|
||||
|
||||
return seriesToRemove;
|
||||
}
|
||||
|
||||
public async Task<PagedList<SeriesDto>> GetHighlyRated(int userId, int libraryId, UserParams userParams)
|
||||
|
@ -36,8 +36,8 @@ public interface IMetadataService
|
||||
/// <param name="libraryId"></param>
|
||||
/// <param name="seriesId"></param>
|
||||
/// <param name="forceUpdate">Overrides any cache logic and forces execution</param>
|
||||
Task GenerateCoversForSeries(int libraryId, int seriesId, bool forceUpdate = true);
|
||||
|
||||
Task GenerateCoversForSeries(int libraryId, int seriesId, bool forceUpdate = true);
|
||||
Task GenerateCoversForSeries(Series series, bool forceUpdate = false);
|
||||
Task RemoveAbandonedMetadataKeys();
|
||||
}
|
||||
|
@ -171,7 +171,16 @@ public class ProcessSeries : IProcessSeries
|
||||
await _eventHub.SendMessageAsync(MessageFactory.Error,
|
||||
MessageFactory.ErrorEvent($"There was an issue writing to the DB for Series {series}",
|
||||
ex.Message));
|
||||
return;
|
||||
}
|
||||
|
||||
if (seriesAdded)
|
||||
{
|
||||
await _eventHub.SendMessageAsync(MessageFactory.SeriesAdded,
|
||||
MessageFactory.SeriesAddedEvent(series.Id, series.Name, series.LibraryId), false);
|
||||
}
|
||||
|
||||
_logger.LogInformation("[ScannerService] Finished series update on {SeriesName} in {Milliseconds} ms", seriesName, scanWatch.ElapsedMilliseconds);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
@ -179,13 +188,7 @@ public class ProcessSeries : IProcessSeries
|
||||
_logger.LogError(ex, "[ScannerService] There was an exception updating series for {SeriesName}", series.Name);
|
||||
}
|
||||
|
||||
if (seriesAdded)
|
||||
{
|
||||
await _eventHub.SendMessageAsync(MessageFactory.SeriesAdded,
|
||||
MessageFactory.SeriesAddedEvent(series.Id, series.Name, series.LibraryId));
|
||||
}
|
||||
|
||||
_logger.LogInformation("[ScannerService] Finished series update on {SeriesName} in {Milliseconds} ms", seriesName, scanWatch.ElapsedMilliseconds);
|
||||
await _metadataService.GenerateCoversForSeries(series, false);
|
||||
EnqueuePostSeriesProcessTasks(series.LibraryId, series.Id);
|
||||
}
|
||||
|
||||
@ -213,7 +216,7 @@ public class ProcessSeries : IProcessSeries
|
||||
|
||||
public void EnqueuePostSeriesProcessTasks(int libraryId, int seriesId, bool forceUpdate = false)
|
||||
{
|
||||
BackgroundJob.Enqueue(() => _metadataService.GenerateCoversForSeries(libraryId, seriesId, forceUpdate));
|
||||
//BackgroundJob.Enqueue(() => _metadataService.GenerateCoversForSeries(libraryId, seriesId, forceUpdate));
|
||||
BackgroundJob.Enqueue(() => _wordCountAnalyzerService.ScanSeries(libraryId, seriesId, forceUpdate));
|
||||
}
|
||||
|
||||
|
@ -205,7 +205,7 @@ public class ScannerService : IScannerService
|
||||
var scanElapsedTime = await ScanFiles(library, new []{folderPath}, false, TrackFiles, true);
|
||||
_logger.LogInformation("ScanFiles for {Series} took {Time}", series.Name, scanElapsedTime);
|
||||
|
||||
await Task.WhenAll(processTasks);
|
||||
//await Task.WhenAll(processTasks);
|
||||
|
||||
await _eventHub.SendMessageAsync(MessageFactory.NotificationProgress, MessageFactory.LibraryScanProgressEvent(library.Name, ProgressEventType.Ended, series.Name));
|
||||
|
||||
@ -475,14 +475,29 @@ public class ScannerService : IScannerService
|
||||
|
||||
// Could I delete anything in a Library's Series where the LastScan date is before scanStart?
|
||||
// NOTE: This implementation is expensive
|
||||
await _unitOfWork.SeriesRepository.RemoveSeriesNotInList(seenSeries, library.Id);
|
||||
var removedSeries = await _unitOfWork.SeriesRepository.RemoveSeriesNotInList(seenSeries, library.Id);
|
||||
|
||||
_unitOfWork.LibraryRepository.Update(library);
|
||||
if (await _unitOfWork.CommitAsync())
|
||||
{
|
||||
_logger.LogInformation(
|
||||
"[ScannerService] Finished scan of {TotalFiles} files and {ParsedSeriesCount} series in {ElapsedScanTime} milliseconds for {LibraryName}",
|
||||
totalFiles, seenSeries.Count, sw.ElapsedMilliseconds, library.Name);
|
||||
if (totalFiles == 0)
|
||||
{
|
||||
_logger.LogInformation(
|
||||
"[ScannerService] Finished library scan of {ParsedSeriesCount} series in {ElapsedScanTime} milliseconds for {LibraryName}. There were no changes",
|
||||
totalFiles, seenSeries.Count, sw.ElapsedMilliseconds, library.Name);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogInformation(
|
||||
"[ScannerService] Finished library scan of {TotalFiles} files and {ParsedSeriesCount} series in {ElapsedScanTime} milliseconds for {LibraryName}",
|
||||
totalFiles, seenSeries.Count, sw.ElapsedMilliseconds, library.Name);
|
||||
}
|
||||
|
||||
foreach (var s in removedSeries)
|
||||
{
|
||||
await _eventHub.SendMessageAsync(MessageFactory.SeriesRemoved,
|
||||
MessageFactory.SeriesRemovedEvent(s.Id, s.Name, s.LibraryId), false);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -156,7 +156,7 @@ namespace API
|
||||
services.AddHangfire(configuration => configuration
|
||||
.UseSimpleAssemblyNameTypeSerializer()
|
||||
.UseRecommendedSerializerSettings()
|
||||
.UseSQLiteStorage());
|
||||
.UseMemoryStorage()); // UseSQLiteStorage - SQLite has some issues around resuming jobs when aborted
|
||||
|
||||
// Add the processing server as IHostedService
|
||||
services.AddHangfireServer(options =>
|
||||
|
@ -2,7 +2,8 @@ import { DOCUMENT } from '@angular/common';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { Inject, Injectable, OnDestroy, Renderer2, RendererFactory2, SecurityContext } from '@angular/core';
|
||||
import { DomSanitizer } from '@angular/platform-browser';
|
||||
import { map, ReplaySubject, Subject, takeUntil } from 'rxjs';
|
||||
import { ToastrService } from 'ngx-toastr';
|
||||
import { map, ReplaySubject, Subject, takeUntil, take } from 'rxjs';
|
||||
import { environment } from 'src/environments/environment';
|
||||
import { ConfirmService } from '../shared/confirm.service';
|
||||
import { NotificationProgressEvent } from '../_models/events/notification-progress-event';
|
||||
@ -35,7 +36,7 @@ export class ThemeService implements OnDestroy {
|
||||
|
||||
|
||||
constructor(rendererFactory: RendererFactory2, @Inject(DOCUMENT) private document: Document, private httpClient: HttpClient,
|
||||
messageHub: MessageHubService, private domSantizer: DomSanitizer, private confirmService: ConfirmService) {
|
||||
messageHub: MessageHubService, private domSantizer: DomSanitizer, private confirmService: ConfirmService, private toastr: ToastrService) {
|
||||
this.renderer = rendererFactory.createRenderer(null, null);
|
||||
|
||||
this.getThemes();
|
||||
@ -47,7 +48,9 @@ export class ThemeService implements OnDestroy {
|
||||
if (notificationEvent.name !== EVENTS.SiteThemeProgress) return;
|
||||
|
||||
if (notificationEvent.eventType === 'ended') {
|
||||
if (notificationEvent.name === EVENTS.SiteThemeProgress) this.getThemes().subscribe(() => {});
|
||||
if (notificationEvent.name === EVENTS.SiteThemeProgress) this.getThemes().subscribe(() => {
|
||||
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -73,6 +76,12 @@ export class ThemeService implements OnDestroy {
|
||||
return this.httpClient.get<SiteTheme[]>(this.baseUrl + 'theme').pipe(map(themes => {
|
||||
this.themeCache = themes;
|
||||
this.themesSource.next(themes);
|
||||
this.currentTheme$.pipe(take(1)).subscribe(theme => {
|
||||
if (!themes.includes(theme)) {
|
||||
this.setTheme(this.defaultTheme);
|
||||
this.toastr.info('The active theme no longer exists. Please refresh the page.');
|
||||
}
|
||||
});
|
||||
return themes;
|
||||
}));
|
||||
}
|
||||
|
@ -57,7 +57,7 @@ export class DashboardComponent implements OnInit, OnDestroy {
|
||||
|
||||
this.seriesService.getSeries(seriesAddedEvent.seriesId).subscribe(series => {
|
||||
this.recentlyAddedSeries.unshift(series);
|
||||
this.cdRef.markForCheck();
|
||||
this.cdRef.detectChanges();
|
||||
});
|
||||
} else if (res.event === EVENTS.SeriesRemoved) {
|
||||
const seriesRemovedEvent = res.payload as SeriesRemovedEvent;
|
||||
|
@ -218,7 +218,7 @@
|
||||
</div>
|
||||
<div class="row mb-2">
|
||||
<div class="col-md-6 col-sm-12">
|
||||
<label for="darkness" class="form-label range-label">Darkess</label>
|
||||
<label for="darkness" class="form-label range-label">Brightness</label>
|
||||
<input type="range" class="form-range" id="darkness"
|
||||
min="10" max="100" step="1" formControlName="darkness">
|
||||
<span class="range-text">{{generalSettingsForm.get('darkness')?.value + '%'}}</span>
|
||||
|
@ -12,7 +12,7 @@
|
||||
<app-side-nav-item icon="fa-list" title="Collections" link="/collections/"></app-side-nav-item>
|
||||
<app-side-nav-item icon="fa-list-ol" title="Reading Lists" link="/lists/"></app-side-nav-item>
|
||||
<app-side-nav-item icon="fa-bookmark" title="Bookmarks" link="/bookmarks/"></app-side-nav-item>
|
||||
<app-side-nav-item icon="fa-regular fa-rectangle-list" title="All Series" link="/all-series/"></app-side-nav-item>
|
||||
<app-side-nav-item icon="fa-regular fa-rectangle-list" title="All Series" link="/all-series/" *ngIf="libraries.length > 0"></app-side-nav-item>
|
||||
<div class="mb-2 mt-3 ms-2 me-2" *ngIf="libraries.length > 10 && !(navService?.sideNavCollapsed$ | async)">
|
||||
<label for="filter" class="form-label visually-hidden">Filter</label>
|
||||
<div class="form-group">
|
||||
|
@ -20,7 +20,7 @@
|
||||
<h5 class="card-title">{{theme.name | sentenceCase}}</h5>
|
||||
<h6 class="card-subtitle mb-2 text-muted">{{theme.provider | siteThemeProvider}}</h6>
|
||||
<button class="btn btn-secondary me-2" [disabled]="theme.isDefault" *ngIf="isAdmin" (click)="updateDefault(theme)">Set Default</button>
|
||||
<button class="btn btn-primary" (click)="applyTheme(theme)" [disabled]="currentTheme.id === theme.id">{{currentTheme.id === theme.id ? 'Applied' : 'Apply'}}</button>
|
||||
<button class="btn btn-primary" (click)="applyTheme(theme)" [disabled]="currentTheme?.id === theme.id">{{currentTheme?.id === theme.id ? 'Applied' : 'Apply'}}</button>
|
||||
</div>
|
||||
</div>
|
||||
</ng-container>
|
||||
|
@ -14,7 +14,7 @@ import { AccountService } from 'src/app/_services/account.service';
|
||||
})
|
||||
export class ThemeManagerComponent implements OnInit, OnDestroy {
|
||||
|
||||
currentTheme!: SiteTheme;
|
||||
currentTheme: SiteTheme | undefined;
|
||||
isAdmin: boolean = false;
|
||||
user: User | undefined;
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user