mirror of
https://github.com/Kareadita/Kavita.git
synced 2025-07-09 03:04:19 -04:00
Scanner & Parser Changes (#735)
* Fixed a bug where partial volume support got missed on the epub parser. * When a drive is unavailable during when a scan starts, abort so user doesn't loose half library if their networked drive goes down. * Moved format for card details to highest level (since all chapters/files have same format) and added date added to each file to help when new chapters/files are added and grouped into a volume. * Implemented handling on the UI when a series is deleted * Added case for series removal for series detail * Only redirect for this series
This commit is contained in:
parent
0bc63dda32
commit
78f3ccf889
@ -371,7 +371,7 @@ namespace API.Services
|
|||||||
FullFilePath = filePath,
|
FullFilePath = filePath,
|
||||||
IsSpecial = false,
|
IsSpecial = false,
|
||||||
Series = series.Trim(),
|
Series = series.Trim(),
|
||||||
Volumes = seriesIndex.Split(".")[0]
|
Volumes = seriesIndex
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -173,7 +173,15 @@ namespace API.Services
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Checks if the root path of a path exists or not.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="path"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static bool IsDriveMounted(string path)
|
||||||
|
{
|
||||||
|
return new DirectoryInfo(Path.GetPathRoot(path) ?? string.Empty).Exists;
|
||||||
|
}
|
||||||
|
|
||||||
public static string[] GetFilesWithExtension(string path, string searchPatternExpression = "")
|
public static string[] GetFilesWithExtension(string path, string searchPatternExpression = "")
|
||||||
{
|
{
|
||||||
|
@ -56,6 +56,14 @@ namespace API.Services.Tasks
|
|||||||
var chapterIds = await _unitOfWork.SeriesRepository.GetChapterIdsForSeriesAsync(new[] {seriesId});
|
var chapterIds = await _unitOfWork.SeriesRepository.GetChapterIdsForSeriesAsync(new[] {seriesId});
|
||||||
var library = await _unitOfWork.LibraryRepository.GetLibraryForIdAsync(libraryId, LibraryIncludes.Folders);
|
var library = await _unitOfWork.LibraryRepository.GetLibraryForIdAsync(libraryId, LibraryIncludes.Folders);
|
||||||
var folderPaths = library.Folders.Select(f => f.Path).ToList();
|
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());
|
var dirs = DirectoryService.FindHighestDirectoriesFromFiles(folderPaths, files.Select(f => f.FilePath).ToList());
|
||||||
|
|
||||||
_logger.LogInformation("Beginning file scan on {SeriesName}", series.Name);
|
_logger.LogInformation("Beginning file scan on {SeriesName}", series.Name);
|
||||||
@ -195,6 +203,14 @@ namespace API.Services.Tasks
|
|||||||
return;
|
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);
|
_logger.LogInformation("[ScannerService] Beginning file scan on {LibraryName}", library.Name);
|
||||||
await _messageHub.Clients.All.SendAsync(SignalREvents.ScanLibraryProgress,
|
await _messageHub.Clients.All.SendAsync(SignalREvents.ScanLibraryProgress,
|
||||||
MessageFactory.ScanLibraryProgressEvent(libraryId, 0));
|
MessageFactory.ScanLibraryProgressEvent(libraryId, 0));
|
||||||
|
5
UI/Web/src/app/_models/events/series-removed-event.ts
Normal file
5
UI/Web/src/app/_models/events/series-removed-event.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
export interface SeriesRemovedEvent {
|
||||||
|
libraryId: number;
|
||||||
|
seriesId: number;
|
||||||
|
seriesName: string;
|
||||||
|
}
|
@ -16,6 +16,7 @@ export enum EVENTS {
|
|||||||
ScanSeries = 'ScanSeries',
|
ScanSeries = 'ScanSeries',
|
||||||
RefreshMetadata = 'RefreshMetadata',
|
RefreshMetadata = 'RefreshMetadata',
|
||||||
SeriesAdded = 'SeriesAdded',
|
SeriesAdded = 'SeriesAdded',
|
||||||
|
SeriesRemoved = 'SeriesRemoved',
|
||||||
ScanLibraryProgress = 'ScanLibraryProgress',
|
ScanLibraryProgress = 'ScanLibraryProgress',
|
||||||
OnlineUsers = 'OnlineUsers',
|
OnlineUsers = 'OnlineUsers',
|
||||||
SeriesAddedToCollection = 'SeriesAddedToCollection',
|
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.hubConnection.on(EVENTS.RefreshMetadata, resp => {
|
||||||
this.messagesSource.next({
|
this.messagesSource.next({
|
||||||
event: EVENTS.RefreshMetadata,
|
event: EVENTS.RefreshMetadata,
|
||||||
|
@ -17,7 +17,8 @@
|
|||||||
<div class="col">
|
<div class="col">
|
||||||
Id: {{data.id}}
|
Id: {{data.id}}
|
||||||
</div>
|
</div>
|
||||||
<div class="col">
|
<div class="col" *ngIf="series !== undefined">
|
||||||
|
Format: <span class="badge badge-secondary">{{utilityService.mangaFormat(series.format) | sentenceCase}}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row no-gutters">
|
<div class="row no-gutters">
|
||||||
@ -58,8 +59,8 @@
|
|||||||
<div class="col">
|
<div class="col">
|
||||||
Pages: {{file.pages}}
|
Pages: {{file.pages}}
|
||||||
</div>
|
</div>
|
||||||
<div class="col">
|
<div class="col" *ngIf="data.hasOwnProperty('created')">
|
||||||
Format: <span class="badge badge-secondary">{{utilityService.mangaFormatToText(file.format)}}</span>
|
Added: {{(data.created | date: 'short') || '-'}}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
|
@ -15,6 +15,8 @@ import { UploadService } from 'src/app/_services/upload.service';
|
|||||||
import { ChangeCoverImageModalComponent } from '../change-cover-image/change-cover-image-modal.component';
|
import { ChangeCoverImageModalComponent } from '../change-cover-image/change-cover-image-modal.component';
|
||||||
import { LibraryType } from '../../../_models/library';
|
import { LibraryType } from '../../../_models/library';
|
||||||
import { LibraryService } from '../../../_services/library.service';
|
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<any>[] = [];
|
actions: ActionItem<any>[] = [];
|
||||||
chapterActions: ActionItem<Chapter>[] = [];
|
chapterActions: ActionItem<Chapter>[] = [];
|
||||||
libraryType: LibraryType = LibraryType.Manga;
|
libraryType: LibraryType = LibraryType.Manga;
|
||||||
|
series: Series | undefined = undefined;
|
||||||
|
|
||||||
get LibraryType(): typeof LibraryType {
|
get LibraryType(): typeof LibraryType {
|
||||||
return LibraryType;
|
return LibraryType;
|
||||||
@ -50,7 +53,8 @@ export class CardDetailsModalComponent implements OnInit {
|
|||||||
constructor(private modalService: NgbModal, public modal: NgbActiveModal, public utilityService: UtilityService,
|
constructor(private modalService: NgbModal, public modal: NgbActiveModal, public utilityService: UtilityService,
|
||||||
public imageService: ImageService, private uploadService: UploadService, private toastr: ToastrService,
|
public imageService: ImageService, private uploadService: UploadService, private toastr: ToastrService,
|
||||||
private accountService: AccountService, private actionFactoryService: ActionFactoryService,
|
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 {
|
ngOnInit(): void {
|
||||||
this.isChapter = this.utilityService.isChapter(this.data);
|
this.isChapter = this.utilityService.isChapter(this.data);
|
||||||
@ -79,6 +83,10 @@ export class CardDetailsModalComponent implements OnInit {
|
|||||||
this.chapters.forEach((c: Chapter) => {
|
this.chapters.forEach((c: Chapter) => {
|
||||||
c.files.sort((a: MangaFile, b: MangaFile) => collator.compare(a.filePath, b.filePath));
|
c.files.sort((a: MangaFile, b: MangaFile) => collator.compare(a.filePath, b.filePath));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.seriesService.getSeries(this.seriesId).subscribe(series => {
|
||||||
|
this.series = series;
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
close() {
|
close() {
|
||||||
|
@ -11,6 +11,7 @@ import { EditCollectionTagsComponent } from 'src/app/cards/_modals/edit-collecti
|
|||||||
import { KEY_CODES } from 'src/app/shared/_services/utility.service';
|
import { KEY_CODES } from 'src/app/shared/_services/utility.service';
|
||||||
import { CollectionTag } from 'src/app/_models/collection-tag';
|
import { CollectionTag } from 'src/app/_models/collection-tag';
|
||||||
import { SeriesAddedToCollectionEvent } from 'src/app/_models/events/series-added-to-collection-event';
|
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 { Pagination } from 'src/app/_models/pagination';
|
||||||
import { Series } from 'src/app/_models/series';
|
import { Series } from 'src/app/_models/series';
|
||||||
import { FilterItem, mangaFormatFilters, SeriesFilter } from 'src/app/_models/series-filter';
|
import { FilterItem, mangaFormatFilters, SeriesFilter } from 'src/app/_models/series-filter';
|
||||||
@ -106,10 +107,14 @@ export class CollectionDetailComponent implements OnInit, OnDestroy {
|
|||||||
this.collectionTagActions = this.actionFactoryService.getCollectionTagActions(this.handleCollectionActionCallback.bind(this));
|
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 => {
|
this.messageHub.messages$.pipe(takeWhile(event => event.event === EVENTS.SeriesAddedToCollection), takeUntil(this.onDestory), debounceTime(2000)).subscribe(event => {
|
||||||
|
if (event.event == EVENTS.SeriesAddedToCollection) {
|
||||||
const collectionEvent = event.payload as SeriesAddedToCollectionEvent;
|
const collectionEvent = event.payload as SeriesAddedToCollectionEvent;
|
||||||
if (collectionEvent.tagId === this.collectionTag.id) {
|
if (collectionEvent.tagId === this.collectionTag.id) {
|
||||||
this.loadPage();
|
this.loadPage();
|
||||||
}
|
}
|
||||||
|
} else if (event.event === EVENTS.SeriesRemoved) {
|
||||||
|
this.loadPage();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,19 +1,15 @@
|
|||||||
import { Component, OnDestroy, OnInit } from '@angular/core';
|
import { Component, OnDestroy, OnInit } from '@angular/core';
|
||||||
import { Title } from '@angular/platform-browser';
|
import { Title } from '@angular/platform-browser';
|
||||||
import { Router } from '@angular/router';
|
import { Router } from '@angular/router';
|
||||||
import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
|
|
||||||
import { Subject } from 'rxjs';
|
import { Subject } from 'rxjs';
|
||||||
import { take, takeUntil } from 'rxjs/operators';
|
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 { SeriesAddedEvent } from '../_models/events/series-added-event';
|
||||||
|
import { SeriesRemovedEvent } from '../_models/events/series-removed-event';
|
||||||
import { InProgressChapter } from '../_models/in-progress-chapter';
|
import { InProgressChapter } from '../_models/in-progress-chapter';
|
||||||
import { Library } from '../_models/library';
|
import { Library } from '../_models/library';
|
||||||
import { Series } from '../_models/series';
|
import { Series } from '../_models/series';
|
||||||
import { User } from '../_models/user';
|
import { User } from '../_models/user';
|
||||||
import { AccountService } from '../_services/account.service';
|
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 { ImageService } from '../_services/image.service';
|
||||||
import { LibraryService } from '../_services/library.service';
|
import { LibraryService } from '../_services/library.service';
|
||||||
import { EVENTS, MessageHubService } from '../_services/message-hub.service';
|
import { EVENTS, MessageHubService } from '../_services/message-hub.service';
|
||||||
@ -44,11 +40,15 @@ export class LibraryComponent implements OnInit, OnDestroy {
|
|||||||
private titleService: Title, public imageService: ImageService,
|
private titleService: Title, public imageService: ImageService,
|
||||||
private messageHub: MessageHubService) {
|
private messageHub: MessageHubService) {
|
||||||
this.messageHub.messages$.pipe(takeUntil(this.onDestroy)).subscribe(res => {
|
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;
|
const seriesAddedEvent = res.payload as SeriesAddedEvent;
|
||||||
this.seriesService.getSeries(seriesAddedEvent.seriesId).subscribe(series => {
|
this.seriesService.getSeries(seriesAddedEvent.seriesId).subscribe(series => {
|
||||||
this.recentlyAdded.unshift(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);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -112,4 +112,11 @@ export class LibraryComponent implements OnInit, OnDestroy {
|
|||||||
this.router.navigate(['in-progress']);
|
this.router.navigate(['in-progress']);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
removeFromArray(arr: Array<any>, element: any) {
|
||||||
|
const index = arr.indexOf(element);
|
||||||
|
if (index >= 0) {
|
||||||
|
arr.splice(index);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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 { ReviewSeriesModalComponent } from '../_modals/review-series-modal/review-series-modal.component';
|
||||||
import { Chapter } from '../_models/chapter';
|
import { Chapter } from '../_models/chapter';
|
||||||
import { ScanSeriesEvent } from '../_models/events/scan-series-event';
|
import { ScanSeriesEvent } from '../_models/events/scan-series-event';
|
||||||
|
import { SeriesRemovedEvent } from '../_models/events/series-removed-event';
|
||||||
import { LibraryType } from '../_models/library';
|
import { LibraryType } from '../_models/library';
|
||||||
import { MangaFormat } from '../_models/manga-format';
|
import { MangaFormat } from '../_models/manga-format';
|
||||||
import { Series } from '../_models/series';
|
import { Series } from '../_models/series';
|
||||||
@ -26,7 +27,7 @@ import { ActionItem, ActionFactoryService, Action } from '../_services/action-fa
|
|||||||
import { ActionService } from '../_services/action.service';
|
import { ActionService } from '../_services/action.service';
|
||||||
import { ImageService } from '../_services/image.service';
|
import { ImageService } from '../_services/image.service';
|
||||||
import { LibraryService } from '../_services/library.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 { ReaderService } from '../_services/reader.service';
|
||||||
import { SeriesService } from '../_services/series.service';
|
import { SeriesService } from '../_services/series.service';
|
||||||
|
|
||||||
@ -180,6 +181,16 @@ export class SeriesDetailComponent implements OnInit, OnDestroy {
|
|||||||
this.toastr.success('Scan series completed');
|
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);
|
const seriesId = parseInt(routeId, 10);
|
||||||
this.libraryId = parseInt(libraryId, 10);
|
this.libraryId = parseInt(libraryId, 10);
|
||||||
this.seriesImage = this.imageService.getSeriesCoverImage(seriesId);
|
this.seriesImage = this.imageService.getSeriesCoverImage(seriesId);
|
||||||
|
@ -158,5 +158,4 @@ export class UtilityService {
|
|||||||
rect.right <= (window.innerWidth || document.documentElement.clientWidth)
|
rect.right <= (window.innerWidth || document.documentElement.clientWidth)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user