diff --git a/API/Services/BookService.cs b/API/Services/BookService.cs index 71f633e4c..cb31f2cc8 100644 --- a/API/Services/BookService.cs +++ b/API/Services/BookService.cs @@ -371,7 +371,7 @@ namespace API.Services FullFilePath = filePath, IsSpecial = false, Series = series.Trim(), - Volumes = seriesIndex.Split(".")[0] + Volumes = seriesIndex }; } } diff --git a/API/Services/DirectoryService.cs b/API/Services/DirectoryService.cs index 1a067a706..12011123d 100644 --- a/API/Services/DirectoryService.cs +++ b/API/Services/DirectoryService.cs @@ -173,7 +173,15 @@ namespace API.Services return true; } - + /// + /// Checks if the root path of a path exists or not. + /// + /// + /// + public static bool IsDriveMounted(string path) + { + return new DirectoryInfo(Path.GetPathRoot(path) ?? string.Empty).Exists; + } public static string[] GetFilesWithExtension(string path, string searchPatternExpression = "") { diff --git a/API/Services/Tasks/ScannerService.cs b/API/Services/Tasks/ScannerService.cs index d82c9579c..38497aac2 100644 --- a/API/Services/Tasks/ScannerService.cs +++ b/API/Services/Tasks/ScannerService.cs @@ -56,6 +56,14 @@ namespace API.Services.Tasks var chapterIds = await _unitOfWork.SeriesRepository.GetChapterIdsForSeriesAsync(new[] {seriesId}); var library = await _unitOfWork.LibraryRepository.GetLibraryForIdAsync(libraryId, LibraryIncludes.Folders); var folderPaths = library.Folders.Select(f => f.Path).ToList(); + + // Check if any of the folder roots are not available (ie disconnected from network, etc) and fail if any of them are + if (folderPaths.Any(f => !DirectoryService.IsDriveMounted(f))) + { + _logger.LogError("Some of the root folders for library are not accessible. Please check that drives are connected and rescan. Scan will be aborted"); + return; + } + var dirs = DirectoryService.FindHighestDirectoriesFromFiles(folderPaths, files.Select(f => f.FilePath).ToList()); _logger.LogInformation("Beginning file scan on {SeriesName}", series.Name); @@ -195,6 +203,14 @@ namespace API.Services.Tasks return; } + // Check if any of the folder roots are not available (ie disconnected from network, etc) and fail if any of them are + if (library.Folders.Any(f => !DirectoryService.IsDriveMounted(f.Path))) + { + _logger.LogError("Some of the root folders for library are not accessible. Please check that drives are connected and rescan. Scan will be aborted"); + return; + } + + _logger.LogInformation("[ScannerService] Beginning file scan on {LibraryName}", library.Name); await _messageHub.Clients.All.SendAsync(SignalREvents.ScanLibraryProgress, MessageFactory.ScanLibraryProgressEvent(libraryId, 0)); diff --git a/UI/Web/src/app/_models/events/series-removed-event.ts b/UI/Web/src/app/_models/events/series-removed-event.ts new file mode 100644 index 000000000..9901fc7e5 --- /dev/null +++ b/UI/Web/src/app/_models/events/series-removed-event.ts @@ -0,0 +1,5 @@ +export interface SeriesRemovedEvent { + libraryId: number; + seriesId: number; + seriesName: string; +} \ No newline at end of file diff --git a/UI/Web/src/app/_services/message-hub.service.ts b/UI/Web/src/app/_services/message-hub.service.ts index c058e1596..ba62adb49 100644 --- a/UI/Web/src/app/_services/message-hub.service.ts +++ b/UI/Web/src/app/_services/message-hub.service.ts @@ -16,6 +16,7 @@ export enum EVENTS { ScanSeries = 'ScanSeries', RefreshMetadata = 'RefreshMetadata', SeriesAdded = 'SeriesAdded', + SeriesRemoved = 'SeriesRemoved', ScanLibraryProgress = 'ScanLibraryProgress', OnlineUsers = 'OnlineUsers', SeriesAddedToCollection = 'SeriesAddedToCollection', @@ -115,6 +116,13 @@ export class MessageHubService { } }); + this.hubConnection.on(EVENTS.SeriesRemoved, resp => { + this.messagesSource.next({ + event: EVENTS.SeriesRemoved, + payload: resp.body + }); + }); + this.hubConnection.on(EVENTS.RefreshMetadata, resp => { this.messagesSource.next({ event: EVENTS.RefreshMetadata, diff --git a/UI/Web/src/app/cards/_modals/card-details-modal/card-details-modal.component.html b/UI/Web/src/app/cards/_modals/card-details-modal/card-details-modal.component.html index b5938118c..62a40f9e0 100644 --- a/UI/Web/src/app/cards/_modals/card-details-modal/card-details-modal.component.html +++ b/UI/Web/src/app/cards/_modals/card-details-modal/card-details-modal.component.html @@ -17,7 +17,8 @@
Id: {{data.id}}
-
+
+ Format: {{utilityService.mangaFormat(series.format) | sentenceCase}}
@@ -58,8 +59,8 @@
Pages: {{file.pages}}
-
- Format: {{utilityService.mangaFormatToText(file.format)}} +
+ Added: {{(data.created | date: 'short') || '-'}}
diff --git a/UI/Web/src/app/cards/_modals/card-details-modal/card-details-modal.component.ts b/UI/Web/src/app/cards/_modals/card-details-modal/card-details-modal.component.ts index 3f15edd65..e1501cd9b 100644 --- a/UI/Web/src/app/cards/_modals/card-details-modal/card-details-modal.component.ts +++ b/UI/Web/src/app/cards/_modals/card-details-modal/card-details-modal.component.ts @@ -15,6 +15,8 @@ import { UploadService } from 'src/app/_services/upload.service'; import { ChangeCoverImageModalComponent } from '../change-cover-image/change-cover-image-modal.component'; import { LibraryType } from '../../../_models/library'; import { LibraryService } from '../../../_services/library.service'; +import { SeriesService } from 'src/app/_services/series.service'; +import { Series } from 'src/app/_models/series'; @@ -42,6 +44,7 @@ export class CardDetailsModalComponent implements OnInit { actions: ActionItem[] = []; chapterActions: ActionItem[] = []; libraryType: LibraryType = LibraryType.Manga; + series: Series | undefined = undefined; get LibraryType(): typeof LibraryType { return LibraryType; @@ -50,7 +53,8 @@ export class CardDetailsModalComponent implements OnInit { constructor(private modalService: NgbModal, public modal: NgbActiveModal, public utilityService: UtilityService, public imageService: ImageService, private uploadService: UploadService, private toastr: ToastrService, private accountService: AccountService, private actionFactoryService: ActionFactoryService, - private actionService: ActionService, private router: Router, private libraryService: LibraryService) { } + private actionService: ActionService, private router: Router, private libraryService: LibraryService, + private seriesService: SeriesService) { } ngOnInit(): void { this.isChapter = this.utilityService.isChapter(this.data); @@ -79,6 +83,10 @@ export class CardDetailsModalComponent implements OnInit { this.chapters.forEach((c: Chapter) => { c.files.sort((a: MangaFile, b: MangaFile) => collator.compare(a.filePath, b.filePath)); }); + + this.seriesService.getSeries(this.seriesId).subscribe(series => { + this.series = series; + }) } close() { diff --git a/UI/Web/src/app/collections/collection-detail/collection-detail.component.ts b/UI/Web/src/app/collections/collection-detail/collection-detail.component.ts index 2593aaba8..2b4f6c4a4 100644 --- a/UI/Web/src/app/collections/collection-detail/collection-detail.component.ts +++ b/UI/Web/src/app/collections/collection-detail/collection-detail.component.ts @@ -11,6 +11,7 @@ import { EditCollectionTagsComponent } from 'src/app/cards/_modals/edit-collecti import { KEY_CODES } from 'src/app/shared/_services/utility.service'; import { CollectionTag } from 'src/app/_models/collection-tag'; import { SeriesAddedToCollectionEvent } from 'src/app/_models/events/series-added-to-collection-event'; +import { SeriesRemovedEvent } from 'src/app/_models/events/series-removed-event'; import { Pagination } from 'src/app/_models/pagination'; import { Series } from 'src/app/_models/series'; import { FilterItem, mangaFormatFilters, SeriesFilter } from 'src/app/_models/series-filter'; @@ -106,8 +107,12 @@ export class CollectionDetailComponent implements OnInit, OnDestroy { this.collectionTagActions = this.actionFactoryService.getCollectionTagActions(this.handleCollectionActionCallback.bind(this)); this.messageHub.messages$.pipe(takeWhile(event => event.event === EVENTS.SeriesAddedToCollection), takeUntil(this.onDestory), debounceTime(2000)).subscribe(event => { - const collectionEvent = event.payload as SeriesAddedToCollectionEvent; - if (collectionEvent.tagId === this.collectionTag.id) { + if (event.event == EVENTS.SeriesAddedToCollection) { + const collectionEvent = event.payload as SeriesAddedToCollectionEvent; + if (collectionEvent.tagId === this.collectionTag.id) { + this.loadPage(); + } + } else if (event.event === EVENTS.SeriesRemoved) { this.loadPage(); } }); diff --git a/UI/Web/src/app/library/library.component.ts b/UI/Web/src/app/library/library.component.ts index 94b1e61bc..fab0d4222 100644 --- a/UI/Web/src/app/library/library.component.ts +++ b/UI/Web/src/app/library/library.component.ts @@ -1,19 +1,15 @@ import { Component, OnDestroy, OnInit } from '@angular/core'; import { Title } from '@angular/platform-browser'; import { Router } from '@angular/router'; -import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; import { Subject } from 'rxjs'; import { take, takeUntil } from 'rxjs/operators'; -import { EditCollectionTagsComponent } from '../cards/_modals/edit-collection-tags/edit-collection-tags.component'; -import { CollectionTag } from '../_models/collection-tag'; import { SeriesAddedEvent } from '../_models/events/series-added-event'; +import { SeriesRemovedEvent } from '../_models/events/series-removed-event'; import { InProgressChapter } from '../_models/in-progress-chapter'; import { Library } from '../_models/library'; import { Series } from '../_models/series'; import { User } from '../_models/user'; import { AccountService } from '../_services/account.service'; -import { Action, ActionFactoryService, ActionItem } from '../_services/action-factory.service'; -import { CollectionTagService } from '../_services/collection-tag.service'; import { ImageService } from '../_services/image.service'; import { LibraryService } from '../_services/library.service'; import { EVENTS, MessageHubService } from '../_services/message-hub.service'; @@ -44,14 +40,18 @@ export class LibraryComponent implements OnInit, OnDestroy { private titleService: Title, public imageService: ImageService, private messageHub: MessageHubService) { this.messageHub.messages$.pipe(takeUntil(this.onDestroy)).subscribe(res => { - if (res.event == EVENTS.SeriesAdded) { + if (res.event === EVENTS.SeriesAdded) { const seriesAddedEvent = res.payload as SeriesAddedEvent; this.seriesService.getSeries(seriesAddedEvent.seriesId).subscribe(series => { this.recentlyAdded.unshift(series); }); + } else if (res.event === EVENTS.SeriesRemoved) { + const seriesRemovedEvent = res.payload as SeriesRemovedEvent; + this.recentlyAdded = this.recentlyAdded.filter(item => item.id != seriesRemovedEvent.seriesId); + this.inProgress = this.inProgress.filter(item => item.id != seriesRemovedEvent.seriesId); } }); - } + } ngOnInit(): void { this.titleService.setTitle('Kavita - Dashboard'); @@ -112,4 +112,11 @@ export class LibraryComponent implements OnInit, OnDestroy { this.router.navigate(['in-progress']); } } + + removeFromArray(arr: Array, element: any) { + const index = arr.indexOf(element); + if (index >= 0) { + arr.splice(index); + } + } } diff --git a/UI/Web/src/app/series-detail/series-detail.component.ts b/UI/Web/src/app/series-detail/series-detail.component.ts index 65e99ebc0..356d99657 100644 --- a/UI/Web/src/app/series-detail/series-detail.component.ts +++ b/UI/Web/src/app/series-detail/series-detail.component.ts @@ -16,6 +16,7 @@ import { KEY_CODES, UtilityService } from '../shared/_services/utility.service'; import { ReviewSeriesModalComponent } from '../_modals/review-series-modal/review-series-modal.component'; import { Chapter } from '../_models/chapter'; import { ScanSeriesEvent } from '../_models/events/scan-series-event'; +import { SeriesRemovedEvent } from '../_models/events/series-removed-event'; import { LibraryType } from '../_models/library'; import { MangaFormat } from '../_models/manga-format'; import { Series } from '../_models/series'; @@ -26,7 +27,7 @@ import { ActionItem, ActionFactoryService, Action } from '../_services/action-fa import { ActionService } from '../_services/action.service'; import { ImageService } from '../_services/image.service'; import { LibraryService } from '../_services/library.service'; -import { MessageHubService } from '../_services/message-hub.service'; +import { EVENTS, MessageHubService } from '../_services/message-hub.service'; import { ReaderService } from '../_services/reader.service'; import { SeriesService } from '../_services/series.service'; @@ -180,6 +181,16 @@ export class SeriesDetailComponent implements OnInit, OnDestroy { this.toastr.success('Scan series completed'); }); + this.messageHub.messages$.pipe(takeUntil(this.onDestroy)).subscribe(event => { + if (event.event === EVENTS.SeriesRemoved) { + const seriesRemovedEvent = event.payload as SeriesRemovedEvent; + if (seriesRemovedEvent.seriesId === this.series.id) { + this.toastr.info('This series no longer exists'); + this.router.navigateByUrl('/libraries'); + } + } + }); + const seriesId = parseInt(routeId, 10); this.libraryId = parseInt(libraryId, 10); this.seriesImage = this.imageService.getSeriesCoverImage(seriesId); diff --git a/UI/Web/src/app/shared/_services/utility.service.ts b/UI/Web/src/app/shared/_services/utility.service.ts index 95751885e..18a7098cc 100644 --- a/UI/Web/src/app/shared/_services/utility.service.ts +++ b/UI/Web/src/app/shared/_services/utility.service.ts @@ -158,5 +158,4 @@ export class UtilityService { rect.right <= (window.innerWidth || document.documentElement.clientWidth) ); } - }