mirror of
https://github.com/Kareadita/Kavita.git
synced 2025-07-09 03:04:19 -04:00
Warn on Refresh Metadata (#610)
* Warn the user about the dangers of refresh metadata and promote them to use a scan instead. * Removed presence hub and moved it over to message hub. When a library scan is in progress, now a spinner will show on manage libraries page. * Code cleanup
This commit is contained in:
parent
9b536ce700
commit
0ac54e682f
@ -185,6 +185,9 @@ namespace API.Services.Tasks
|
|||||||
}
|
}
|
||||||
|
|
||||||
_logger.LogInformation("[ScannerService] Beginning file scan on {LibraryName}", library.Name);
|
_logger.LogInformation("[ScannerService] Beginning file scan on {LibraryName}", library.Name);
|
||||||
|
await _messageHub.Clients.All.SendAsync(SignalREvents.ScanLibraryProgress,
|
||||||
|
MessageFactory.ScanLibraryProgressEvent(libraryId, 0, string.Empty));
|
||||||
|
|
||||||
var scanner = new ParseScannedFiles(_bookService, _logger);
|
var scanner = new ParseScannedFiles(_bookService, _logger);
|
||||||
var series = scanner.ScanLibrariesForSeries(library.Type, library.Folders.Select(fp => fp.Path), out var totalFiles, out var scanElapsedTime);
|
var series = scanner.ScanLibrariesForSeries(library.Type, library.Folders.Select(fp => fp.Path), out var totalFiles, out var scanElapsedTime);
|
||||||
|
|
||||||
@ -212,7 +215,8 @@ namespace API.Services.Tasks
|
|||||||
await CleanupAbandonedChapters();
|
await CleanupAbandonedChapters();
|
||||||
|
|
||||||
BackgroundJob.Enqueue(() => _metadataService.RefreshMetadata(libraryId, false));
|
BackgroundJob.Enqueue(() => _metadataService.RefreshMetadata(libraryId, false));
|
||||||
await _messageHub.Clients.All.SendAsync(SignalREvents.ScanLibrary, MessageFactory.ScanLibraryEvent(libraryId, "complete"));
|
await _messageHub.Clients.All.SendAsync(SignalREvents.ScanLibraryProgress,
|
||||||
|
MessageFactory.ScanLibraryProgressEvent(libraryId, 100, string.Empty));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -277,7 +281,10 @@ namespace API.Services.Tasks
|
|||||||
|
|
||||||
// Now, we only have to deal with series that exist on disk. Let's recalculate the volumes for each series
|
// Now, we only have to deal with series that exist on disk. Let's recalculate the volumes for each series
|
||||||
var librarySeries = cleanedSeries.ToList();
|
var librarySeries = cleanedSeries.ToList();
|
||||||
Parallel.ForEach(librarySeries, (series) => { UpdateSeries(series, parsedSeries); });
|
Parallel.ForEach(librarySeries, (series) =>
|
||||||
|
{
|
||||||
|
UpdateSeries(series, parsedSeries);
|
||||||
|
});
|
||||||
|
|
||||||
await _unitOfWork.CommitAsync();
|
await _unitOfWork.CommitAsync();
|
||||||
_logger.LogInformation(
|
_logger.LogInformation(
|
||||||
|
@ -59,6 +59,22 @@ namespace API.SignalR
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static SignalRMessage ScanLibraryProgressEvent(int libraryId, int progress, string seriesName)
|
||||||
|
{
|
||||||
|
return new SignalRMessage()
|
||||||
|
{
|
||||||
|
Name = SignalREvents.ScanLibrary,
|
||||||
|
Body = new
|
||||||
|
{
|
||||||
|
LibraryId = libraryId,
|
||||||
|
Progress = progress,
|
||||||
|
SeriesName = seriesName
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public static SignalRMessage RefreshMetadataEvent(int libraryId, int seriesId)
|
public static SignalRMessage RefreshMetadataEvent(int libraryId, int seriesId)
|
||||||
{
|
{
|
||||||
return new SignalRMessage()
|
return new SignalRMessage()
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using API.Extensions;
|
||||||
|
using API.SignalR.Presence;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.SignalR;
|
using Microsoft.AspNetCore.SignalR;
|
||||||
|
|
||||||
@ -13,8 +15,14 @@ namespace API.SignalR
|
|||||||
[Authorize]
|
[Authorize]
|
||||||
public class MessageHub : Hub
|
public class MessageHub : Hub
|
||||||
{
|
{
|
||||||
|
private readonly IPresenceTracker _tracker;
|
||||||
private static readonly HashSet<string> Connections = new HashSet<string>();
|
private static readonly HashSet<string> Connections = new HashSet<string>();
|
||||||
|
|
||||||
|
public MessageHub(IPresenceTracker tracker)
|
||||||
|
{
|
||||||
|
_tracker = tracker;
|
||||||
|
}
|
||||||
|
|
||||||
public static bool IsConnected
|
public static bool IsConnected
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
@ -33,6 +41,12 @@ namespace API.SignalR
|
|||||||
Connections.Add(Context.ConnectionId);
|
Connections.Add(Context.ConnectionId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await _tracker.UserConnected(Context.User.GetUsername(), Context.ConnectionId);
|
||||||
|
|
||||||
|
var currentUsers = await PresenceTracker.GetOnlineUsers();
|
||||||
|
await Clients.All.SendAsync(SignalREvents.OnlineUsers, currentUsers);
|
||||||
|
|
||||||
|
|
||||||
await base.OnConnectedAsync();
|
await base.OnConnectedAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -43,6 +57,12 @@ namespace API.SignalR
|
|||||||
Connections.Remove(Context.ConnectionId);
|
Connections.Remove(Context.ConnectionId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await _tracker.UserDisconnected(Context.User.GetUsername(), Context.ConnectionId);
|
||||||
|
|
||||||
|
var currentUsers = await PresenceTracker.GetOnlineUsers();
|
||||||
|
await Clients.All.SendAsync(SignalREvents.OnlineUsers, currentUsers);
|
||||||
|
|
||||||
|
|
||||||
await base.OnDisconnectedAsync(exception);
|
await base.OnDisconnectedAsync(exception);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,6 +8,7 @@
|
|||||||
public const string ScanLibrary = "ScanLibrary";
|
public const string ScanLibrary = "ScanLibrary";
|
||||||
public const string SeriesAdded = "SeriesAdded";
|
public const string SeriesAdded = "SeriesAdded";
|
||||||
public const string SeriesRemoved = "SeriesRemoved";
|
public const string SeriesRemoved = "SeriesRemoved";
|
||||||
|
public const string ScanLibraryProgress = "ScanLibraryProgress";
|
||||||
|
public const string OnlineUsers = "OnlineUsers";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -147,7 +147,6 @@ namespace API
|
|||||||
{
|
{
|
||||||
endpoints.MapControllers();
|
endpoints.MapControllers();
|
||||||
endpoints.MapHub<MessageHub>("hubs/messages");
|
endpoints.MapHub<MessageHub>("hubs/messages");
|
||||||
endpoints.MapHub<PresenceHub>("hubs/presence");
|
|
||||||
endpoints.MapHangfireDashboard();
|
endpoints.MapHangfireDashboard();
|
||||||
endpoints.MapFallbackToController("Index", "Fallback");
|
endpoints.MapFallbackToController("Index", "Fallback");
|
||||||
});
|
});
|
||||||
|
@ -1,4 +0,0 @@
|
|||||||
export interface ScanLibraryEvent {
|
|
||||||
libraryId: number;
|
|
||||||
stage: 'complete';
|
|
||||||
}
|
|
@ -0,0 +1,4 @@
|
|||||||
|
export interface ScanLibraryProgressEvent {
|
||||||
|
libraryId: number;
|
||||||
|
progress: number;
|
||||||
|
}
|
@ -8,7 +8,6 @@ import { User } from '../_models/user';
|
|||||||
import * as Sentry from "@sentry/angular";
|
import * as Sentry from "@sentry/angular";
|
||||||
import { Router } from '@angular/router';
|
import { Router } from '@angular/router';
|
||||||
import { MessageHubService } from './message-hub.service';
|
import { MessageHubService } from './message-hub.service';
|
||||||
import { PresenceHubService } from './presence-hub.service';
|
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root'
|
providedIn: 'root'
|
||||||
@ -26,7 +25,7 @@ export class AccountService implements OnDestroy {
|
|||||||
private readonly onDestroy = new Subject<void>();
|
private readonly onDestroy = new Subject<void>();
|
||||||
|
|
||||||
constructor(private httpClient: HttpClient, private router: Router,
|
constructor(private httpClient: HttpClient, private router: Router,
|
||||||
private messageHub: MessageHubService, private presenceHub: PresenceHubService) {}
|
private messageHub: MessageHubService) {}
|
||||||
|
|
||||||
ngOnDestroy(): void {
|
ngOnDestroy(): void {
|
||||||
this.onDestroy.next();
|
this.onDestroy.next();
|
||||||
@ -52,7 +51,6 @@ export class AccountService implements OnDestroy {
|
|||||||
if (user) {
|
if (user) {
|
||||||
this.setCurrentUser(user);
|
this.setCurrentUser(user);
|
||||||
this.messageHub.createHubConnection(user, this.hasAdminRole(user));
|
this.messageHub.createHubConnection(user, this.hasAdminRole(user));
|
||||||
this.presenceHub.createHubConnection(user);
|
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
takeUntil(this.onDestroy)
|
takeUntil(this.onDestroy)
|
||||||
@ -85,7 +83,6 @@ export class AccountService implements OnDestroy {
|
|||||||
// Upon logout, perform redirection
|
// Upon logout, perform redirection
|
||||||
this.router.navigateByUrl('/login');
|
this.router.navigateByUrl('/login');
|
||||||
this.messageHub.stopHubConnection();
|
this.messageHub.stopHubConnection();
|
||||||
this.presenceHub.stopHubConnection();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
register(model: {username: string, password: string, isAdmin?: boolean}) {
|
register(model: {username: string, password: string, isAdmin?: boolean}) {
|
||||||
|
@ -6,6 +6,7 @@ import { take } from 'rxjs/operators';
|
|||||||
import { BookmarksModalComponent } from '../cards/_modals/bookmarks-modal/bookmarks-modal.component';
|
import { BookmarksModalComponent } from '../cards/_modals/bookmarks-modal/bookmarks-modal.component';
|
||||||
import { AddToListModalComponent, ADD_FLOW } from '../reading-list/_modals/add-to-list-modal/add-to-list-modal.component';
|
import { AddToListModalComponent, ADD_FLOW } from '../reading-list/_modals/add-to-list-modal/add-to-list-modal.component';
|
||||||
import { EditReadingListModalComponent } from '../reading-list/_modals/edit-reading-list-modal/edit-reading-list-modal.component';
|
import { EditReadingListModalComponent } from '../reading-list/_modals/edit-reading-list-modal/edit-reading-list-modal.component';
|
||||||
|
import { ConfirmService } from '../shared/confirm.service';
|
||||||
import { Chapter } from '../_models/chapter';
|
import { Chapter } from '../_models/chapter';
|
||||||
import { Library } from '../_models/library';
|
import { Library } from '../_models/library';
|
||||||
import { ReadingList } from '../_models/reading-list';
|
import { ReadingList } from '../_models/reading-list';
|
||||||
@ -35,7 +36,8 @@ export class ActionService implements OnDestroy {
|
|||||||
private readingListModalRef: NgbModalRef | null = null;
|
private readingListModalRef: NgbModalRef | null = null;
|
||||||
|
|
||||||
constructor(private libraryService: LibraryService, private seriesService: SeriesService,
|
constructor(private libraryService: LibraryService, private seriesService: SeriesService,
|
||||||
private readerService: ReaderService, private toastr: ToastrService, private modalService: NgbModal) { }
|
private readerService: ReaderService, private toastr: ToastrService, private modalService: NgbModal,
|
||||||
|
private confirmService: ConfirmService) { }
|
||||||
|
|
||||||
ngOnDestroy() {
|
ngOnDestroy() {
|
||||||
this.onDestroy.next();
|
this.onDestroy.next();
|
||||||
@ -66,11 +68,15 @@ export class ActionService implements OnDestroy {
|
|||||||
* @param callback Optional callback to perform actions after API completes
|
* @param callback Optional callback to perform actions after API completes
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
refreshMetadata(library: Partial<Library>, callback?: LibraryActionCallback) {
|
async refreshMetadata(library: Partial<Library>, callback?: LibraryActionCallback) {
|
||||||
if (!library.hasOwnProperty('id') || library.id === undefined) {
|
if (!library.hasOwnProperty('id') || library.id === undefined) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!await this.confirmService.confirm('Refresh metadata will force all cover images and metadata to be recalculated. This is a heavy operation. Are you sure you don\'t want to perform a Scan instead?')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
this.libraryService.refreshMetadata(library?.id).pipe(take(1)).subscribe((res: any) => {
|
this.libraryService.refreshMetadata(library?.id).pipe(take(1)).subscribe((res: any) => {
|
||||||
this.toastr.success('Scan started for ' + library.name);
|
this.toastr.success('Scan started for ' + library.name);
|
||||||
if (callback) {
|
if (callback) {
|
||||||
@ -128,7 +134,11 @@ export class ActionService implements OnDestroy {
|
|||||||
* @param series Series, must have libraryId, id and name populated
|
* @param series Series, must have libraryId, id and name populated
|
||||||
* @param callback Optional callback to perform actions after API completes
|
* @param callback Optional callback to perform actions after API completes
|
||||||
*/
|
*/
|
||||||
refreshMetdata(series: Series, callback?: SeriesActionCallback) {
|
async refreshMetdata(series: Series, callback?: SeriesActionCallback) {
|
||||||
|
if (!await this.confirmService.confirm('Refresh metadata will force all cover images and metadata to be recalculated. This is a heavy operation. Are you sure you don\'t want to perform a Scan instead?')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
this.seriesService.refreshMetadata(series).pipe(take(1)).subscribe((res: any) => {
|
this.seriesService.refreshMetadata(series).pipe(take(1)).subscribe((res: any) => {
|
||||||
this.toastr.success('Refresh started for ' + series.name);
|
this.toastr.success('Refresh started for ' + series.name);
|
||||||
if (callback) {
|
if (callback) {
|
||||||
|
@ -3,20 +3,21 @@ import { HubConnection, HubConnectionBuilder } from '@microsoft/signalr';
|
|||||||
import { NgbModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap';
|
import { NgbModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap';
|
||||||
import { User } from '@sentry/angular';
|
import { User } from '@sentry/angular';
|
||||||
import { ToastrService } from 'ngx-toastr';
|
import { ToastrService } from 'ngx-toastr';
|
||||||
import { ReplaySubject } from 'rxjs';
|
import { BehaviorSubject, ReplaySubject } from 'rxjs';
|
||||||
import { environment } from 'src/environments/environment';
|
import { environment } from 'src/environments/environment';
|
||||||
import { UpdateNotificationModalComponent } from '../shared/update-notification/update-notification-modal.component';
|
import { UpdateNotificationModalComponent } from '../shared/update-notification/update-notification-modal.component';
|
||||||
import { RefreshMetadataEvent } from '../_models/events/refresh-metadata-event';
|
import { RefreshMetadataEvent } from '../_models/events/refresh-metadata-event';
|
||||||
import { ScanLibraryEvent } from '../_models/events/scan-library-event';
|
import { ScanLibraryProgressEvent } from '../_models/events/scan-library-progress-event';
|
||||||
import { ScanSeriesEvent } from '../_models/events/scan-series-event';
|
import { ScanSeriesEvent } from '../_models/events/scan-series-event';
|
||||||
import { SeriesAddedEvent } from '../_models/events/series-added-event';
|
import { SeriesAddedEvent } from '../_models/events/series-added-event';
|
||||||
|
|
||||||
export enum EVENTS {
|
export enum EVENTS {
|
||||||
UpdateAvailable = 'UpdateAvailable',
|
UpdateAvailable = 'UpdateAvailable',
|
||||||
ScanSeries = 'ScanSeries',
|
ScanSeries = 'ScanSeries',
|
||||||
ScanLibrary = 'ScanLibrary',
|
|
||||||
RefreshMetadata = 'RefreshMetadata',
|
RefreshMetadata = 'RefreshMetadata',
|
||||||
SeriesAdded = 'SeriesAdded'
|
SeriesAdded = 'SeriesAdded',
|
||||||
|
ScanLibraryProgress = 'ScanLibraryProgress',
|
||||||
|
OnlineUsers = 'OnlineUsers'
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Message<T> {
|
export interface Message<T> {
|
||||||
@ -35,8 +36,11 @@ export class MessageHubService {
|
|||||||
private messagesSource = new ReplaySubject<Message<any>>(1);
|
private messagesSource = new ReplaySubject<Message<any>>(1);
|
||||||
public messages$ = this.messagesSource.asObservable();
|
public messages$ = this.messagesSource.asObservable();
|
||||||
|
|
||||||
|
private onlineUsersSource = new BehaviorSubject<string[]>([]);
|
||||||
|
onlineUsers$ = this.onlineUsersSource.asObservable();
|
||||||
|
|
||||||
public scanSeries: EventEmitter<ScanSeriesEvent> = new EventEmitter<ScanSeriesEvent>();
|
public scanSeries: EventEmitter<ScanSeriesEvent> = new EventEmitter<ScanSeriesEvent>();
|
||||||
public scanLibrary: EventEmitter<ScanLibraryEvent> = new EventEmitter<ScanLibraryEvent>();
|
public scanLibrary: EventEmitter<ScanLibraryProgressEvent> = new EventEmitter<ScanLibraryProgressEvent>();
|
||||||
public seriesAdded: EventEmitter<SeriesAddedEvent> = new EventEmitter<SeriesAddedEvent>();
|
public seriesAdded: EventEmitter<SeriesAddedEvent> = new EventEmitter<SeriesAddedEvent>();
|
||||||
public refreshMetadata: EventEmitter<RefreshMetadataEvent> = new EventEmitter<RefreshMetadataEvent>();
|
public refreshMetadata: EventEmitter<RefreshMetadataEvent> = new EventEmitter<RefreshMetadataEvent>();
|
||||||
|
|
||||||
@ -60,10 +64,11 @@ export class MessageHubService {
|
|||||||
.start()
|
.start()
|
||||||
.catch(err => console.error(err));
|
.catch(err => console.error(err));
|
||||||
|
|
||||||
this.hubConnection.on('receiveMessage', body => {
|
this.hubConnection.on(EVENTS.OnlineUsers, (usernames: string[]) => {
|
||||||
//console.log('[Hub] Body: ', body);
|
this.onlineUsersSource.next(usernames);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
this.hubConnection.on(EVENTS.ScanSeries, resp => {
|
this.hubConnection.on(EVENTS.ScanSeries, resp => {
|
||||||
this.messagesSource.next({
|
this.messagesSource.next({
|
||||||
event: EVENTS.ScanSeries,
|
event: EVENTS.ScanSeries,
|
||||||
@ -72,9 +77,9 @@ export class MessageHubService {
|
|||||||
this.scanSeries.emit(resp.body);
|
this.scanSeries.emit(resp.body);
|
||||||
});
|
});
|
||||||
|
|
||||||
this.hubConnection.on(EVENTS.ScanLibrary, resp => {
|
this.hubConnection.on(EVENTS.ScanLibraryProgress, resp => {
|
||||||
this.messagesSource.next({
|
this.messagesSource.next({
|
||||||
event: EVENTS.ScanLibrary,
|
event: EVENTS.ScanLibraryProgress,
|
||||||
payload: resp.body
|
payload: resp.body
|
||||||
});
|
});
|
||||||
this.scanLibrary.emit(resp.body);
|
this.scanLibrary.emit(resp.body);
|
||||||
|
@ -1,40 +0,0 @@
|
|||||||
import { Injectable } from '@angular/core';
|
|
||||||
import { HubConnection, HubConnectionBuilder } from '@microsoft/signalr';
|
|
||||||
import { User } from '@sentry/angular';
|
|
||||||
import { ToastrService } from 'ngx-toastr';
|
|
||||||
import { BehaviorSubject } from 'rxjs';
|
|
||||||
import { environment } from 'src/environments/environment';
|
|
||||||
|
|
||||||
@Injectable({
|
|
||||||
providedIn: 'root'
|
|
||||||
})
|
|
||||||
export class PresenceHubService {
|
|
||||||
|
|
||||||
hubUrl = environment.hubUrl;
|
|
||||||
private hubConnection!: HubConnection;
|
|
||||||
private onlineUsersSource = new BehaviorSubject<string[]>([]);
|
|
||||||
onlineUsers$ = this.onlineUsersSource.asObservable();
|
|
||||||
|
|
||||||
constructor(private toatsr: ToastrService) { }
|
|
||||||
|
|
||||||
createHubConnection(user: User) {
|
|
||||||
this.hubConnection = new HubConnectionBuilder()
|
|
||||||
.withUrl(this.hubUrl + 'presence', {
|
|
||||||
accessTokenFactory: () => user.token
|
|
||||||
})
|
|
||||||
.withAutomaticReconnect()
|
|
||||||
.build();
|
|
||||||
|
|
||||||
this.hubConnection
|
|
||||||
.start()
|
|
||||||
.catch(err => console.error(err));
|
|
||||||
|
|
||||||
this.hubConnection.on('GetOnlineUsers', (usernames: string[]) => {
|
|
||||||
this.onlineUsersSource.next(usernames);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
stopHubConnection() {
|
|
||||||
this.hubConnection.stop().catch(err => console.error(err));
|
|
||||||
}
|
|
||||||
}
|
|
@ -7,7 +7,10 @@
|
|||||||
<li *ngFor="let library of libraries; let idx = index;" class="list-group-item">
|
<li *ngFor="let library of libraries; let idx = index;" class="list-group-item">
|
||||||
<div>
|
<div>
|
||||||
<h4>
|
<h4>
|
||||||
<span id="library-name--{{idx}}">{{library.name | titlecase}}</span>
|
<span id="library-name--{{idx}}">{{library.name | titlecase}}</span>
|
||||||
|
<div class="spinner-border text-primary" style="width: 1.5rem; height: 1.5rem;" role="status" *ngIf="scanInProgress.hasOwnProperty(library.id) && scanInProgress[library.id]" title="Scan in progress">
|
||||||
|
<span class="sr-only">Scan for {{library.name}} in progress</span>
|
||||||
|
</div>
|
||||||
<div class="float-right">
|
<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-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-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>
|
||||||
|
@ -4,8 +4,10 @@ import { ToastrService } from 'ngx-toastr';
|
|||||||
import { Subject } from 'rxjs';
|
import { Subject } from 'rxjs';
|
||||||
import { take, takeUntil } from 'rxjs/operators';
|
import { take, takeUntil } from 'rxjs/operators';
|
||||||
import { ConfirmService } from 'src/app/shared/confirm.service';
|
import { ConfirmService } from 'src/app/shared/confirm.service';
|
||||||
|
import { ScanLibraryProgressEvent } from 'src/app/_models/events/scan-library-progress-event';
|
||||||
import { Library, LibraryType } from 'src/app/_models/library';
|
import { Library, LibraryType } from 'src/app/_models/library';
|
||||||
import { LibraryService } from 'src/app/_services/library.service';
|
import { LibraryService } from 'src/app/_services/library.service';
|
||||||
|
import { MessageHubService } from 'src/app/_services/message-hub.service';
|
||||||
import { LibraryEditorModalComponent } from '../_modals/library-editor-modal/library-editor-modal.component';
|
import { LibraryEditorModalComponent } from '../_modals/library-editor-modal/library-editor-modal.component';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
@ -22,13 +24,21 @@ export class ManageLibraryComponent implements OnInit, OnDestroy {
|
|||||||
* If a deletion is in progress for a library
|
* If a deletion is in progress for a library
|
||||||
*/
|
*/
|
||||||
deletionInProgress: boolean = false;
|
deletionInProgress: boolean = false;
|
||||||
|
scanInProgress: {[key: number]: boolean} = {};
|
||||||
|
|
||||||
private readonly onDestroy = new Subject<void>();
|
private readonly onDestroy = new Subject<void>();
|
||||||
|
|
||||||
constructor(private modalService: NgbModal, private libraryService: LibraryService, private toastr: ToastrService, private confirmService: ConfirmService) { }
|
constructor(private modalService: NgbModal, private libraryService: LibraryService,
|
||||||
|
private toastr: ToastrService, private confirmService: ConfirmService,
|
||||||
|
private hubService: MessageHubService) { }
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.getLibraries();
|
this.getLibraries();
|
||||||
|
|
||||||
|
this.hubService.scanLibrary.subscribe((event: ScanLibraryProgressEvent) => {
|
||||||
|
|
||||||
|
this.scanInProgress[event.libraryId] = event.progress !== 100;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnDestroy() {
|
ngOnDestroy() {
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
<li *ngFor="let member of members; let idx = index;" class="list-group-item">
|
<li *ngFor="let member of members; let idx = index;" class="list-group-item">
|
||||||
<div>
|
<div>
|
||||||
<h4>
|
<h4>
|
||||||
<i class="presence fa fa-circle" title="Active" aria-hidden="true" *ngIf="false && (presence.onlineUsers$ | async)?.includes(member.username)"></i><span id="member-name--{{idx}}">{{member.username | titlecase}} </span><span *ngIf="member.username === loggedInUsername">(You)</span>
|
<i class="presence fa fa-circle" title="Active" aria-hidden="true" *ngIf="false && (messageHub.onlineUsers$ | async)?.includes(member.username)"></i><span id="member-name--{{idx}}">{{member.username | titlecase}} </span><span *ngIf="member.username === loggedInUsername">(You)</span>
|
||||||
<div class="float-right" *ngIf="canEditMember(member)">
|
<div class="float-right" *ngIf="canEditMember(member)">
|
||||||
<button class="btn btn-danger mr-2" (click)="deleteUser(member)" placement="top" ngbTooltip="Delete User" attr.aria-label="Delete User {{member.username | titlecase}}"><i class="fa fa-trash" aria-hidden="true"></i></button>
|
<button class="btn btn-danger mr-2" (click)="deleteUser(member)" placement="top" ngbTooltip="Delete User" attr.aria-label="Delete User {{member.username | titlecase}}"><i class="fa fa-trash" aria-hidden="true"></i></button>
|
||||||
<button class="btn btn-secondary mr-2" (click)="updatePassword(member)" placement="top" ngbTooltip="Change Password" attr.aria-label="Change Password for {{member.username | titlecase}}"><i class="fa fa-key" aria-hidden="true"></i></button>
|
<button class="btn btn-secondary mr-2" (click)="updatePassword(member)" placement="top" ngbTooltip="Change Password" attr.aria-label="Change Password for {{member.username | titlecase}}"><i class="fa fa-key" aria-hidden="true"></i></button>
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { Component, OnDestroy, OnInit } from '@angular/core';
|
import { Component, OnDestroy, OnInit } from '@angular/core';
|
||||||
import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
|
import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
|
||||||
import { take, takeUntil } from 'rxjs/operators';
|
import { take } from 'rxjs/operators';
|
||||||
import { MemberService } from 'src/app/_services/member.service';
|
import { MemberService } from 'src/app/_services/member.service';
|
||||||
import { Member } from 'src/app/_models/member';
|
import { Member } from 'src/app/_models/member';
|
||||||
import { User } from 'src/app/_models/user';
|
import { User } from 'src/app/_models/user';
|
||||||
@ -10,8 +10,8 @@ import { ToastrService } from 'ngx-toastr';
|
|||||||
import { ResetPasswordModalComponent } from '../_modals/reset-password-modal/reset-password-modal.component';
|
import { ResetPasswordModalComponent } from '../_modals/reset-password-modal/reset-password-modal.component';
|
||||||
import { ConfirmService } from 'src/app/shared/confirm.service';
|
import { ConfirmService } from 'src/app/shared/confirm.service';
|
||||||
import { EditRbsModalComponent } from '../_modals/edit-rbs-modal/edit-rbs-modal.component';
|
import { EditRbsModalComponent } from '../_modals/edit-rbs-modal/edit-rbs-modal.component';
|
||||||
import { PresenceHubService } from 'src/app/_services/presence-hub.service';
|
|
||||||
import { Subject } from 'rxjs';
|
import { Subject } from 'rxjs';
|
||||||
|
import { MessageHubService } from 'src/app/_services/message-hub.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-manage-users',
|
selector: 'app-manage-users',
|
||||||
@ -34,7 +34,7 @@ export class ManageUsersComponent implements OnInit, OnDestroy {
|
|||||||
private modalService: NgbModal,
|
private modalService: NgbModal,
|
||||||
private toastr: ToastrService,
|
private toastr: ToastrService,
|
||||||
private confirmService: ConfirmService,
|
private confirmService: ConfirmService,
|
||||||
public presence: PresenceHubService) {
|
public messageHub: MessageHubService) {
|
||||||
this.accountService.currentUser$.pipe(take(1)).subscribe((user: User) => {
|
this.accountService.currentUser$.pipe(take(1)).subscribe((user: User) => {
|
||||||
this.loggedInUsername = user.username;
|
this.loggedInUsername = user.username;
|
||||||
});
|
});
|
||||||
|
@ -5,7 +5,6 @@ import { AccountService } from './_services/account.service';
|
|||||||
import { LibraryService } from './_services/library.service';
|
import { LibraryService } from './_services/library.service';
|
||||||
import { MessageHubService } from './_services/message-hub.service';
|
import { MessageHubService } from './_services/message-hub.service';
|
||||||
import { NavService } from './_services/nav.service';
|
import { NavService } from './_services/nav.service';
|
||||||
import { PresenceHubService } from './_services/presence-hub.service';
|
|
||||||
import { StatsService } from './_services/stats.service';
|
import { StatsService } from './_services/stats.service';
|
||||||
import { filter } from 'rxjs/operators';
|
import { filter } from 'rxjs/operators';
|
||||||
import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
|
import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
|
||||||
@ -19,7 +18,7 @@ export class AppComponent implements OnInit {
|
|||||||
|
|
||||||
constructor(private accountService: AccountService, public navService: NavService,
|
constructor(private accountService: AccountService, public navService: NavService,
|
||||||
private statsService: StatsService, private messageHub: MessageHubService,
|
private statsService: StatsService, private messageHub: MessageHubService,
|
||||||
private presenceHub: PresenceHubService, private libraryService: LibraryService, private router: Router, private ngbModal: NgbModal) {
|
private libraryService: LibraryService, private router: Router, private ngbModal: NgbModal) {
|
||||||
|
|
||||||
// Close any open modals when a route change occurs
|
// Close any open modals when a route change occurs
|
||||||
router.events
|
router.events
|
||||||
@ -48,7 +47,6 @@ export class AppComponent implements OnInit {
|
|||||||
if (user) {
|
if (user) {
|
||||||
this.navService.setDarkMode(user.preferences.siteDarkMode);
|
this.navService.setDarkMode(user.preferences.siteDarkMode);
|
||||||
this.messageHub.createHubConnection(user, this.accountService.hasAdminRole(user));
|
this.messageHub.createHubConnection(user, this.accountService.hasAdminRole(user));
|
||||||
this.presenceHub.createHubConnection(user);
|
|
||||||
this.libraryService.getLibraryNames().pipe(take(1)).subscribe(() => {/* No Operation */});
|
this.libraryService.getLibraryNames().pipe(take(1)).subscribe(() => {/* No Operation */});
|
||||||
} else {
|
} else {
|
||||||
this.navService.setDarkMode(true);
|
this.navService.setDarkMode(true);
|
||||||
|
@ -1,11 +1,10 @@
|
|||||||
import { Component, HostListener, OnInit } from '@angular/core';
|
import { Component, HostListener, OnInit } from '@angular/core';
|
||||||
import { Title } from '@angular/platform-browser';
|
import { Title } from '@angular/platform-browser';
|
||||||
import { ActivatedRoute, Router } from '@angular/router';
|
import { ActivatedRoute, Router } from '@angular/router';
|
||||||
import { take, takeWhile } from 'rxjs/operators';
|
import { debounceTime, take, takeWhile } from 'rxjs/operators';
|
||||||
import { BulkSelectionService } from '../cards/bulk-selection.service';
|
import { BulkSelectionService } from '../cards/bulk-selection.service';
|
||||||
import { UpdateFilterEvent } from '../cards/card-detail-layout/card-detail-layout.component';
|
import { UpdateFilterEvent } from '../cards/card-detail-layout/card-detail-layout.component';
|
||||||
import { KEY_CODES } from '../shared/_services/utility.service';
|
import { KEY_CODES } from '../shared/_services/utility.service';
|
||||||
import { RefreshMetadataEvent } from '../_models/events/refresh-metadata-event';
|
|
||||||
import { SeriesAddedEvent } from '../_models/events/series-added-event';
|
import { SeriesAddedEvent } from '../_models/events/series-added-event';
|
||||||
import { Library } from '../_models/library';
|
import { Library } from '../_models/library';
|
||||||
import { Pagination } from '../_models/pagination';
|
import { Pagination } from '../_models/pagination';
|
||||||
@ -81,8 +80,7 @@ export class LibraryDetailComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
|
this.hubService.seriesAdded.pipe(takeWhile(event => event.libraryId === this.libraryId), debounceTime(6000)).subscribe((event: SeriesAddedEvent) => {
|
||||||
this.hubService.seriesAdded.pipe(takeWhile(event => event.libraryId === this.libraryId)).subscribe((event: SeriesAddedEvent) => {
|
|
||||||
this.loadPage();
|
this.loadPage();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user