diff --git a/API.Tests/Parser/ParserTest.cs b/API.Tests/Parser/ParserTest.cs index ac1e506bc..6660ba2af 100644 --- a/API.Tests/Parser/ParserTest.cs +++ b/API.Tests/Parser/ParserTest.cs @@ -142,6 +142,8 @@ namespace API.Tests.Parser [InlineData("Darker Than Black", "darkerthanblack")] [InlineData("Darker Than Black - Something", "darkerthanblacksomething")] [InlineData("Darker Than_Black", "darkerthanblack")] + [InlineData("Citrus", "citrus")] + [InlineData("Citrus+", "citrus+")] [InlineData("", "")] public void NormalizeTest(string input, string expected) { diff --git a/API/Controllers/OPDSController.cs b/API/Controllers/OPDSController.cs index 256dc362d..a58ed30e0 100644 --- a/API/Controllers/OPDSController.cs +++ b/API/Controllers/OPDSController.cs @@ -170,14 +170,14 @@ namespace API.Controllers var user = await _unitOfWork.UserRepository.GetUserByIdAsync(userId); var isAdmin = await _unitOfWork.UserRepository.IsUserAdmin(user); - IEnumerable tags; + IList tags; if (isAdmin) { - tags = await _unitOfWork.CollectionTagRepository.GetAllTagDtosAsync(); + tags = (await _unitOfWork.CollectionTagRepository.GetAllTagDtosAsync()).ToList(); } else { - tags = await _unitOfWork.CollectionTagRepository.GetAllPromotedTagDtosAsync(); + tags = (await _unitOfWork.CollectionTagRepository.GetAllPromotedTagDtosAsync()).ToList(); } @@ -199,6 +199,14 @@ namespace API.Controllers }); } + if (tags.Count == 0) + { + feed.Entries.Add(new FeedEntry() + { + Title = "Nothing here", + }); + } + return CreateXmlResult(SerializeXml(feed)); } @@ -298,13 +306,13 @@ namespace API.Controllers var feed = CreateFeed(readingList.Title + " Reading List", $"{apiKey}/reading-list/{readingListId}", apiKey); - var items = await _unitOfWork.ReadingListRepository.GetReadingListItemDtosByIdAsync(readingListId, userId); + var items = (await _unitOfWork.ReadingListRepository.GetReadingListItemDtosByIdAsync(readingListId, userId)).ToList(); foreach (var item in items) { feed.Entries.Add(new FeedEntry() { Id = item.ChapterId.ToString(), - Title = "Chapter " + item.ChapterNumber, + Title = $"{item.SeriesName} Chapter {item.ChapterNumber}", Links = new List() { CreateLink(FeedLinkRelation.SubSection, FeedLinkType.AtomNavigation, Prefix + $"{apiKey}/series/{item.SeriesId}/volume/{item.VolumeId}/chapter/{item.ChapterId}"), @@ -313,6 +321,14 @@ namespace API.Controllers }); } + if (items.Count == 0) + { + feed.Entries.Add(new FeedEntry() + { + Title = "Nothing here", + }); + } + return CreateXmlResult(SerializeXml(feed)); @@ -371,6 +387,14 @@ namespace API.Controllers feed.Entries.Add(CreateSeries(seriesDto, apiKey)); } + if (recentlyAdded.Count == 0) + { + feed.Entries.Add(new FeedEntry() + { + Title = "Nothing here", + }); + } + return CreateXmlResult(SerializeXml(feed)); } @@ -402,6 +426,14 @@ namespace API.Controllers feed.Entries.Add(CreateSeries(seriesDto, apiKey)); } + if (pagedList.Count == 0) + { + feed.Entries.Add(new FeedEntry() + { + Title = "Nothing here", + }); + } + return CreateXmlResult(SerializeXml(feed)); } diff --git a/API/Extensions/HttpExtensions.cs b/API/Extensions/HttpExtensions.cs index 80b52f18f..975cbde5f 100644 --- a/API/Extensions/HttpExtensions.cs +++ b/API/Extensions/HttpExtensions.cs @@ -1,4 +1,5 @@ -using System.Linq; +using System.IO; +using System.Linq; using System.Text; using System.Text.Json; using API.Helpers; @@ -41,8 +42,9 @@ namespace API.Extensions public static void AddCacheHeader(this HttpResponse response, string filename) { if (filename == null || filename.Length <= 0) return; + var hashContent = filename + File.GetLastWriteTimeUtc(filename); using var sha1 = new System.Security.Cryptography.SHA256CryptoServiceProvider(); - response.Headers.Add("ETag", string.Concat(sha1.ComputeHash(Encoding.UTF8.GetBytes(filename)).Select(x => x.ToString("X2")))); + response.Headers.Add("ETag", string.Concat(sha1.ComputeHash(Encoding.UTF8.GetBytes(hashContent)).Select(x => x.ToString("X2")))); } } diff --git a/API/Parser/Parser.cs b/API/Parser/Parser.cs index ef2d9ef5d..d1169d71c 100644 --- a/API/Parser/Parser.cs +++ b/API/Parser/Parser.cs @@ -48,7 +48,7 @@ namespace API.Parser MatchOptions, RegexTimeout); - private static readonly Regex NormalizeRegex = new Regex(@"[^a-zA-Z0-9]", + private static readonly Regex NormalizeRegex = new Regex(@"[^a-zA-Z0-9\+]", MatchOptions, RegexTimeout); diff --git a/API/Services/MetadataService.cs b/API/Services/MetadataService.cs index 88220df7d..14be8ce17 100644 --- a/API/Services/MetadataService.cs +++ b/API/Services/MetadataService.cs @@ -139,6 +139,8 @@ namespace API.Services { var madeUpdate = false; if (series == null) return false; + + // NOTE: This will fail if we replace the cover of the first volume on a first scan. Because the series will already have a cover image if (ShouldUpdateCoverImage(series.CoverImage, null, forceUpdate, series.CoverImageLocked)) { series.Volumes ??= new List(); diff --git a/API/SignalR/MessageFactory.cs b/API/SignalR/MessageFactory.cs index a8ed729f4..19fd66f55 100644 --- a/API/SignalR/MessageFactory.cs +++ b/API/SignalR/MessageFactory.cs @@ -1,4 +1,4 @@ -using System.Threading; +using System; using API.DTOs.Update; namespace API.SignalR @@ -46,19 +46,6 @@ namespace API.SignalR }; } - public static SignalRMessage ScanLibraryEvent(int libraryId, string stage) - { - return new SignalRMessage() - { - Name = SignalREvents.ScanLibrary, - Body = new - { - LibraryId = libraryId, - Stage = stage - } - }; - } - public static SignalRMessage ScanLibraryProgressEvent(int libraryId, float progress) { return new SignalRMessage() @@ -68,6 +55,7 @@ namespace API.SignalR { LibraryId = libraryId, Progress = progress, + EventTime = DateTime.Now } }; } diff --git a/UI/Web/src/app/_models/events/scan-library-progress-event.ts b/UI/Web/src/app/_models/events/scan-library-progress-event.ts index 41a5afbbe..e8460fde4 100644 --- a/UI/Web/src/app/_models/events/scan-library-progress-event.ts +++ b/UI/Web/src/app/_models/events/scan-library-progress-event.ts @@ -1,4 +1,5 @@ export interface ScanLibraryProgressEvent { libraryId: number; progress: number; + eventTime: string; } \ No newline at end of file diff --git a/UI/Web/src/app/admin/manage-library/manage-library.component.html b/UI/Web/src/app/admin/manage-library/manage-library.component.html index a13c1286d..fbd9809e5 100644 --- a/UI/Web/src/app/admin/manage-library/manage-library.component.html +++ b/UI/Web/src/app/admin/manage-library/manage-library.component.html @@ -8,7 +8,7 @@

{{library.name}}  -
+
Scan for {{library.name}} in progress
diff --git a/UI/Web/src/app/admin/manage-library/manage-library.component.ts b/UI/Web/src/app/admin/manage-library/manage-library.component.ts index 7ba0c87b8..7160bd758 100644 --- a/UI/Web/src/app/admin/manage-library/manage-library.component.ts +++ b/UI/Web/src/app/admin/manage-library/manage-library.component.ts @@ -24,7 +24,7 @@ export class ManageLibraryComponent implements OnInit, OnDestroy { * If a deletion is in progress for a library */ deletionInProgress: boolean = false; - scanInProgress: {[key: number]: boolean} = {}; + scanInProgress: {[key: number]: {progress: boolean, timestamp?: string}} = {}; libraryTrackBy = (index: number, item: Library) => `${item.name}_${item.lastScanned}_${item.type}_${item.folders.length}`; private readonly onDestroy = new Subject(); @@ -41,9 +41,12 @@ export class ManageLibraryComponent implements OnInit, OnDestroy { if (event.event != EVENTS.ScanLibraryProgress) return; const scanEvent = event.payload as ScanLibraryProgressEvent; - this.scanInProgress[scanEvent.libraryId] = scanEvent.progress !== 100; + this.scanInProgress[scanEvent.libraryId] = {progress: scanEvent.progress !== 100}; + if (scanEvent.progress === 0) { + this.scanInProgress[scanEvent.libraryId].timestamp = scanEvent.eventTime; + } - if (this.scanInProgress[scanEvent.libraryId] === false && scanEvent.progress === 100) { + if (this.scanInProgress[scanEvent.libraryId].progress === false && scanEvent.progress === 100) { this.libraryService.getLibraries().pipe(take(1)).subscribe(libraries => { const newLibrary = libraries.find(lib => lib.id === scanEvent.libraryId); const existingLibrary = this.libraries.find(lib => lib.id === scanEvent.libraryId); diff --git a/UI/Web/src/app/book-reader/book-reader/book-reader.component.ts b/UI/Web/src/app/book-reader/book-reader/book-reader.component.ts index b5a4702d4..494bf6907 100644 --- a/UI/Web/src/app/book-reader/book-reader/book-reader.component.ts +++ b/UI/Web/src/app/book-reader/book-reader/book-reader.component.ts @@ -558,9 +558,12 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy { margin = this.user.preferences.bookReaderMargin + '%'; } this.pageStyles = {'font-family': this.user.preferences.bookReaderFontFamily, 'font-size': this.user.preferences.bookReaderFontSize + '%', 'margin-left': margin, 'margin-right': margin, 'line-height': this.user.preferences.bookReaderLineSpacing + '%'}; - if (this.user.preferences.siteDarkMode && !this.user.preferences.bookReaderDarkMode) { - this.user.preferences.bookReaderDarkMode = true; + if (!afterSave) { + if (this.user.preferences.siteDarkMode && !this.user.preferences.bookReaderDarkMode) { + this.user.preferences.bookReaderDarkMode = true; + } } + this.toggleDarkMode(this.user.preferences.bookReaderDarkMode); } else { this.pageStyles = {'font-family': 'default', 'font-size': '100%', 'margin-left': margin, 'margin-right': margin, 'line-height': '100%'};