diff --git a/API/Controllers/SeriesController.cs b/API/Controllers/SeriesController.cs index 04e33a07a..ba0571ec3 100644 --- a/API/Controllers/SeriesController.cs +++ b/API/Controllers/SeriesController.cs @@ -180,7 +180,7 @@ namespace API.Controllers if (series == null) return BadRequest("Series does not exist"); - if (series.Name != updateSeries.Name && await _unitOfWork.SeriesRepository.DoesSeriesNameExistInLibrary(updateSeries.Name)) + if (series.Name != updateSeries.Name && await _unitOfWork.SeriesRepository.DoesSeriesNameExistInLibrary(updateSeries.Name, series.Format)) { return BadRequest("A series already exists in this library with this name. Series Names must be unique to a library."); } diff --git a/API/Data/Repositories/SeriesRepository.cs b/API/Data/Repositories/SeriesRepository.cs index 8e94ff9b6..baa55330f 100644 --- a/API/Data/Repositories/SeriesRepository.cs +++ b/API/Data/Repositories/SeriesRepository.cs @@ -7,6 +7,7 @@ using API.DTOs; using API.DTOs.CollectionTags; using API.DTOs.Filtering; using API.Entities; +using API.Entities.Enums; using API.Extensions; using API.Helpers; using API.Interfaces.Repositories; @@ -47,16 +48,22 @@ namespace API.Data.Repositories _context.Series.RemoveRange(series); } - public async Task DoesSeriesNameExistInLibrary(string name) + /// + /// Returns if a series name and format exists already in a library + /// + /// Name of series + /// Format of series + /// + public async Task DoesSeriesNameExistInLibrary(string name, MangaFormat format) { var libraries = _context.Series .AsNoTracking() - .Where(x => x.Name == name) + .Where(x => x.Name.Equals(name) && x.Format == format) .Select(s => s.LibraryId); return await _context.Series .AsNoTracking() - .Where(s => libraries.Contains(s.LibraryId) && s.Name == name) + .Where(s => libraries.Contains(s.LibraryId) && s.Name.Equals(name) && s.Format == format) .CountAsync() > 1; } diff --git a/API/Data/Seed.cs b/API/Data/Seed.cs index 11f1afe86..9cfbaeaa4 100644 --- a/API/Data/Seed.cs +++ b/API/Data/Seed.cs @@ -41,7 +41,7 @@ namespace API.Data IList defaultSettings = new List() { - new() {Key = ServerSettingKey.CacheDirectory, Value = DirectoryService.CacheDirectory}, + new () {Key = ServerSettingKey.CacheDirectory, Value = DirectoryService.CacheDirectory}, new () {Key = ServerSettingKey.TaskScan, Value = "daily"}, new () {Key = ServerSettingKey.LoggingLevel, Value = "Information"}, // Not used from DB, but DB is sync with appSettings.json new () {Key = ServerSettingKey.TaskBackup, Value = "weekly"}, @@ -51,6 +51,7 @@ namespace API.Data new () {Key = ServerSettingKey.EnableOpds, Value = "false"}, new () {Key = ServerSettingKey.EnableAuthentication, Value = "true"}, new () {Key = ServerSettingKey.BaseUrl, Value = "/"}, + new () {Key = ServerSettingKey.InstallId, Value = HashUtil.AnonymousToken()}, }; foreach (var defaultSetting in defaultSettings) diff --git a/API/Entities/Enums/ServerSettingKey.cs b/API/Entities/Enums/ServerSettingKey.cs index 997d0a33e..3f097c675 100644 --- a/API/Entities/Enums/ServerSettingKey.cs +++ b/API/Entities/Enums/ServerSettingKey.cs @@ -4,26 +4,61 @@ namespace API.Entities.Enums { public enum ServerSettingKey { + /// + /// Cron format for how often full library scans are performed. + /// [Description("TaskScan")] TaskScan = 0, + /// + /// Where files are cached. Not currently used. + /// [Description("CacheDirectory")] CacheDirectory = 1, + /// + /// Cron format for how often backups are taken. + /// [Description("TaskBackup")] TaskBackup = 2, + /// + /// Logging level for Server. Not managed in DB. Managed in appsettings.json and synced to DB. + /// [Description("LoggingLevel")] LoggingLevel = 3, + /// + /// Port server listens on. Not managed in DB. Managed in appsettings.json and synced to DB. + /// [Description("Port")] Port = 4, + /// + /// Where the backups are stored. + /// [Description("BackupDirectory")] BackupDirectory = 5, + /// + /// Allow anonymous data to be reported to KavitaStats + /// [Description("AllowStatCollection")] AllowStatCollection = 6, + /// + /// Is OPDS enabled for the server + /// [Description("EnableOpds")] EnableOpds = 7, + /// + /// Is Authentication needed for non-admin accounts + /// [Description("EnableAuthentication")] EnableAuthentication = 8, + /// + /// Base Url for the server. Not Implemented. + /// [Description("BaseUrl")] - BaseUrl = 9 + BaseUrl = 9, + /// + /// Represents this installation of Kavita. Is tied to Stat reporting but has no information about user or files. + /// + [Description("InstallId")] + InstallId = 10 } } diff --git a/API/Entities/Series.cs b/API/Entities/Series.cs index 5b7bc86bd..de02ad427 100644 --- a/API/Entities/Series.cs +++ b/API/Entities/Series.cs @@ -23,7 +23,7 @@ namespace API.Entities /// public string SortName { get; set; } /// - /// Name in Japanese. By default, will be same as Name. + /// Name in original language (Japanese for Manga). By default, will be same as Name. /// public string LocalizedName { get; set; } /// diff --git a/API/Interfaces/Repositories/ISeriesRepository.cs b/API/Interfaces/Repositories/ISeriesRepository.cs index 70b5aa672..4c8b2e74e 100644 --- a/API/Interfaces/Repositories/ISeriesRepository.cs +++ b/API/Interfaces/Repositories/ISeriesRepository.cs @@ -4,6 +4,7 @@ using API.Data.Scanner; using API.DTOs; using API.DTOs.Filtering; using API.Entities; +using API.Entities.Enums; using API.Helpers; namespace API.Interfaces.Repositories @@ -14,7 +15,7 @@ namespace API.Interfaces.Repositories void Update(Series series); void Remove(Series series); void Remove(IEnumerable series); - Task DoesSeriesNameExistInLibrary(string name); + Task DoesSeriesNameExistInLibrary(string name, MangaFormat format); /// /// Adds user information like progress, ratings, etc /// diff --git a/API/Services/TaskScheduler.cs b/API/Services/TaskScheduler.cs index ee68df106..3f97bc77a 100644 --- a/API/Services/TaskScheduler.cs +++ b/API/Services/TaskScheduler.cs @@ -26,6 +26,7 @@ namespace API.Services private const string SendDataTask = "finalize-stats"; public static BackgroundJobServer Client => new BackgroundJobServer(); + private static readonly Random Rnd = new Random(); public TaskScheduler(ICacheService cacheService, ILogger logger, IScannerService scannerService, @@ -73,7 +74,8 @@ namespace API.Services RecurringJob.AddOrUpdate("cleanup", () => _cleanupService.Cleanup(), Cron.Daily, TimeZoneInfo.Local); - RecurringJob.AddOrUpdate("check-for-updates", () => _scannerService.ScanLibraries(), Cron.Daily, TimeZoneInfo.Local); + // Schedule update check between noon and 6pm local time + RecurringJob.AddOrUpdate("check-for-updates", () => _scannerService.ScanLibraries(), Cron.Daily(Rnd.Next(12, 18)), TimeZoneInfo.Local); } #region StatsTasks diff --git a/UI/Web/src/app/library/library.component.ts b/UI/Web/src/app/library/library.component.ts index f8a7649d3..dc5ff3228 100644 --- a/UI/Web/src/app/library/library.component.ts +++ b/UI/Web/src/app/library/library.component.ts @@ -75,7 +75,7 @@ export class LibraryComponent implements OnInit, OnDestroy { reloadSeries() { this.loadRecentlyAdded(); - this.loadInProgress(); + this.loadOnDeck(); } reloadInProgress(series: Series | boolean) { @@ -88,10 +88,10 @@ export class LibraryComponent implements OnInit, OnDestroy { return; } - this.loadInProgress(); + this.loadOnDeck(); } - loadInProgress() { + loadOnDeck() { this.seriesService.getOnDeck().pipe(takeUntil(this.onDestroy)).subscribe((updatedSeries) => { this.inProgress = updatedSeries.result; }); diff --git a/UI/Web/src/app/manga-reader/infinite-scroller/infinite-scroller.component.ts b/UI/Web/src/app/manga-reader/infinite-scroller/infinite-scroller.component.ts index 2fd52520f..b97f5f014 100644 --- a/UI/Web/src/app/manga-reader/infinite-scroller/infinite-scroller.component.ts +++ b/UI/Web/src/app/manga-reader/infinite-scroller/infinite-scroller.component.ts @@ -251,6 +251,7 @@ export class InfiniteScrollerComponent implements OnInit, OnChanges, OnDestroy { if (totalScroll === totalHeight && !this.atBottom) { this.atBottom = true; this.setPageNum(this.totalPages); + this.debugLog('At last page, saving last page ', this.totalPages); // Scroll user back to original location this.previousScrollHeightMinusTop = this.getScrollTop(); requestAnimationFrame(() => document.documentElement.scrollTop = this.previousScrollHeightMinusTop + (SPACER_SCROLL_INTO_PX / 2)); diff --git a/UI/Web/src/app/register-member/register-member.component.scss b/UI/Web/src/app/register-member/register-member.component.scss index 485525296..5fc352dbb 100644 --- a/UI/Web/src/app/register-member/register-member.component.scss +++ b/UI/Web/src/app/register-member/register-member.component.scss @@ -14,4 +14,5 @@ input { background-color: #fff !important; + color: black !important; } \ No newline at end of file