diff --git a/API/Controllers/SeriesController.cs b/API/Controllers/SeriesController.cs index 8be412c07..d4bd4c5a9 100644 --- a/API/Controllers/SeriesController.cs +++ b/API/Controllers/SeriesController.cs @@ -126,5 +126,11 @@ namespace API.Controllers return BadRequest("There was an error with updating the series"); } + + [HttpGet("recently-added")] + public async Task>> GetRecentlyAdded(int libraryId = 0) + { + return Ok(await _unitOfWork.SeriesRepository.GetRecentlyAdded(libraryId)); + } } } \ No newline at end of file diff --git a/API/Data/LibraryRepository.cs b/API/Data/LibraryRepository.cs index 80dbbb553..c33c42281 100644 --- a/API/Data/LibraryRepository.cs +++ b/API/Data/LibraryRepository.cs @@ -1,4 +1,5 @@ using System; +using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Linq; @@ -112,5 +113,7 @@ namespace API.Data .Include(l => l.Folders) .ProjectTo(_mapper.ConfigurationProvider).ToListAsync(); } + + } } \ No newline at end of file diff --git a/API/Data/Migrations/20210313001830_SearchIndex.cs b/API/Data/Migrations/20210313001830_SearchIndex.cs deleted file mode 100644 index 2272f73bf..000000000 --- a/API/Data/Migrations/20210313001830_SearchIndex.cs +++ /dev/null @@ -1,23 +0,0 @@ -using Microsoft.EntityFrameworkCore.Migrations; - -namespace API.Data.Migrations -{ - public partial class SearchIndex : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.CreateIndex( - name: "IX_Series_Name_NormalizedName_LocalizedName", - table: "Series", - columns: new[] { "Name", "NormalizedName", "LocalizedName" }, - unique: true); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropIndex( - name: "IX_Series_Name_NormalizedName_LocalizedName", - table: "Series"); - } - } -} diff --git a/API/Data/Migrations/20210313001830_SearchIndex.Designer.cs b/API/Data/Migrations/20210315134028_SearchIndexAndProgressDates.Designer.cs similarity index 98% rename from API/Data/Migrations/20210313001830_SearchIndex.Designer.cs rename to API/Data/Migrations/20210315134028_SearchIndexAndProgressDates.Designer.cs index 5aff37c5d..a407ccc28 100644 --- a/API/Data/Migrations/20210313001830_SearchIndex.Designer.cs +++ b/API/Data/Migrations/20210315134028_SearchIndexAndProgressDates.Designer.cs @@ -9,8 +9,8 @@ using Microsoft.EntityFrameworkCore.Storage.ValueConversion; namespace API.Data.Migrations { [DbContext(typeof(DataContext))] - [Migration("20210313001830_SearchIndex")] - partial class SearchIndex + [Migration("20210315134028_SearchIndexAndProgressDates")] + partial class SearchIndexAndProgressDates { protected override void BuildTargetModel(ModelBuilder modelBuilder) { @@ -161,6 +161,12 @@ namespace API.Data.Migrations b.Property("ChapterId") .HasColumnType("INTEGER"); + b.Property("Created") + .HasColumnType("TEXT"); + + b.Property("LastModified") + .HasColumnType("TEXT"); + b.Property("PagesRead") .HasColumnType("INTEGER"); @@ -367,7 +373,7 @@ namespace API.Data.Migrations b.HasIndex("LibraryId"); - b.HasIndex("Name", "NormalizedName", "LocalizedName") + b.HasIndex("Name", "NormalizedName", "LocalizedName", "LibraryId") .IsUnique(); b.ToTable("Series"); diff --git a/API/Data/Migrations/20210315134028_SearchIndexAndProgressDates.cs b/API/Data/Migrations/20210315134028_SearchIndexAndProgressDates.cs new file mode 100644 index 000000000..02dc1db2c --- /dev/null +++ b/API/Data/Migrations/20210315134028_SearchIndexAndProgressDates.cs @@ -0,0 +1,46 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace API.Data.Migrations +{ + public partial class SearchIndexAndProgressDates : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "Created", + table: "AppUserProgresses", + type: "TEXT", + nullable: false, + defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified)); + + migrationBuilder.AddColumn( + name: "LastModified", + table: "AppUserProgresses", + type: "TEXT", + nullable: false, + defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified)); + + migrationBuilder.CreateIndex( + name: "IX_Series_Name_NormalizedName_LocalizedName_LibraryId", + table: "Series", + columns: new[] { "Name", "NormalizedName", "LocalizedName", "LibraryId" }, + unique: true); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropIndex( + name: "IX_Series_Name_NormalizedName_LocalizedName_LibraryId", + table: "Series"); + + migrationBuilder.DropColumn( + name: "Created", + table: "AppUserProgresses"); + + migrationBuilder.DropColumn( + name: "LastModified", + table: "AppUserProgresses"); + } + } +} diff --git a/API/Data/Migrations/DataContextModelSnapshot.cs b/API/Data/Migrations/DataContextModelSnapshot.cs index 922966162..c0fcd6f87 100644 --- a/API/Data/Migrations/DataContextModelSnapshot.cs +++ b/API/Data/Migrations/DataContextModelSnapshot.cs @@ -159,6 +159,12 @@ namespace API.Data.Migrations b.Property("ChapterId") .HasColumnType("INTEGER"); + b.Property("Created") + .HasColumnType("TEXT"); + + b.Property("LastModified") + .HasColumnType("TEXT"); + b.Property("PagesRead") .HasColumnType("INTEGER"); @@ -365,7 +371,7 @@ namespace API.Data.Migrations b.HasIndex("LibraryId"); - b.HasIndex("Name", "NormalizedName", "LocalizedName") + b.HasIndex("Name", "NormalizedName", "LocalizedName", "LibraryId") .IsUnique(); b.ToTable("Series"); diff --git a/API/Data/SeriesRepository.cs b/API/Data/SeriesRepository.cs index d1b5e3eb5..e4bda9997 100644 --- a/API/Data/SeriesRepository.cs +++ b/API/Data/SeriesRepository.cs @@ -1,4 +1,6 @@ -using System.Collections.Generic; +using System; +using System.Collections; +using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Threading.Tasks; @@ -275,5 +277,54 @@ namespace API.Data v.PagesRead = userProgress.Where(p => p.VolumeId == v.Id).Sum(p => p.PagesRead); } } + + /// + /// Returns a list of Series that were added within 2 weeks. + /// + /// Library to restrict to, if 0, will apply to all libraries + /// + public async Task> GetRecentlyAdded(int libraryId) + { + // && (libraryId <= 0 || s.LibraryId == libraryId) + var twoWeeksAgo = DateTime.Today.Subtract(TimeSpan.FromDays(14)); + _logger.LogDebug("2 weeks from today is: {Date}", twoWeeksAgo); + return await _context.Series + .Where(s => s.Created > twoWeeksAgo) + .Take(20) + .AsNoTracking() + .ProjectTo(_mapper.ConfigurationProvider) + .ToListAsync(); + + } + + + public async Task> GetSeriesStream(int userId) + { + // Testing out In Progress to figure out how to design generalized solution + var userProgress = await _context.AppUserProgresses + .Where(p => p.AppUserId == userId && p.PagesRead > 0) + .AsNoTracking() + .ToListAsync(); + if (!userProgress.Any()) return new SeriesDto[] {}; + + var seriesIds = userProgress.Select(p => p.SeriesId).ToList(); + /* + *select P.*, S.Name, S.Pages from AppUserProgresses AS P + LEFT join Series as "S" on s.Id = P.SeriesId + where AppUserId = 1 AND P.PagesRead > 0 AND P.PagesRead < S.Pages + * + */ + + + + + // var series = await _context.Series + // .Where(s => seriesIds.Contains(s.Id) && s.Pages) // I need a join + + + + + return new SeriesDto[] {}; + } } } \ No newline at end of file diff --git a/API/Entities/AppUserProgress.cs b/API/Entities/AppUserProgress.cs index be3953246..170a249ac 100644 --- a/API/Entities/AppUserProgress.cs +++ b/API/Entities/AppUserProgress.cs @@ -1,20 +1,25 @@  +using System; +using API.Entities.Interfaces; + namespace API.Entities { /// /// Represents the progress a single user has on a given Volume. Progress is realistically tracked against the Volume's chapters. /// - public class AppUserProgress + public class AppUserProgress : IEntityDate { public int Id { get; set; } public int PagesRead { get; set; } public int VolumeId { get; set; } public int SeriesId { get; set; } - public int ChapterId { get; set; } // Relationships public AppUser AppUser { get; set; } public int AppUserId { get; set; } + + public DateTime Created { get; set; } + public DateTime LastModified { get; set; } } } \ No newline at end of file diff --git a/API/Entities/Series.cs b/API/Entities/Series.cs index c3d5ba68e..6406e118f 100644 --- a/API/Entities/Series.cs +++ b/API/Entities/Series.cs @@ -5,7 +5,7 @@ using Microsoft.EntityFrameworkCore; namespace API.Entities { - [Index(nameof(Name), nameof(NormalizedName), nameof(LocalizedName), IsUnique = true)] + [Index(nameof(Name), nameof(NormalizedName), nameof(LocalizedName), nameof(LibraryId), IsUnique = true)] public class Series : IEntityDate { public int Id { get; set; } @@ -36,7 +36,6 @@ namespace API.Entities public DateTime Created { get; set; } public DateTime LastModified { get; set; } public byte[] CoverImage { get; set; } - // NOTE: Do I want to store a thumbImage for search results? /// /// Sum of all Volume page counts /// diff --git a/API/Interfaces/ISeriesRepository.cs b/API/Interfaces/ISeriesRepository.cs index e9d950937..03cc758b1 100644 --- a/API/Interfaces/ISeriesRepository.cs +++ b/API/Interfaces/ISeriesRepository.cs @@ -55,5 +55,6 @@ namespace API.Interfaces Task GetVolumeCoverImageAsync(int volumeId); Task GetSeriesCoverImageAsync(int seriesId); + Task> GetRecentlyAdded(int libraryId); } } \ No newline at end of file diff --git a/API/Program.cs b/API/Program.cs index 7d7628252..7bd6ef289 100644 --- a/API/Program.cs +++ b/API/Program.cs @@ -41,7 +41,6 @@ namespace API logger.LogError(ex, "An error occurred during migration"); } - await host.RunAsync(); } diff --git a/API/Services/CacheService.cs b/API/Services/CacheService.cs index a5bdb4220..8e6ede340 100644 --- a/API/Services/CacheService.cs +++ b/API/Services/CacheService.cs @@ -52,6 +52,7 @@ namespace API.Services return chapter; } + public void Cleanup() { _logger.LogInformation("Performing cleanup of Cache directory"); diff --git a/API/Services/TaskScheduler.cs b/API/Services/TaskScheduler.cs index 492ff6357..758d653a2 100644 --- a/API/Services/TaskScheduler.cs +++ b/API/Services/TaskScheduler.cs @@ -21,7 +21,7 @@ namespace API.Services private readonly ICleanupService _cleanupService; private readonly IDirectoryService _directoryService; - public BackgroundJobServer Client => new BackgroundJobServer(); + public static BackgroundJobServer Client => new BackgroundJobServer(); // new BackgroundJobServerOptions() // { // WorkerCount = 1 @@ -39,16 +39,14 @@ namespace API.Services _backupService = backupService; _cleanupService = cleanupService; _directoryService = directoryService; - - //Hangfire.RecurringJob.RemoveIfExists(); + ScheduleTasks(); - //JobStorage.Current.GetMonitoringApi().EnqueuedJobs() - } public void ScheduleTasks() { _logger.LogInformation("Scheduling reoccurring tasks"); + string setting = null; setting = Task.Run(() => _unitOfWork.SettingsRepository.GetSettingAsync(ServerSettingKey.TaskScan)).Result.Value; if (setting != null) diff --git a/API/Services/Tasks/BackupService.cs b/API/Services/Tasks/BackupService.cs index 3a5685035..9642a0faa 100644 --- a/API/Services/Tasks/BackupService.cs +++ b/API/Services/Tasks/BackupService.cs @@ -3,11 +3,13 @@ using System.Collections.Generic; using System.IO; using System.IO.Compression; using System.Linq; +using System.Threading; using System.Threading.Tasks; using API.Entities.Enums; using API.Extensions; using API.Interfaces; using API.Interfaces.Services; +using Hangfire; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; @@ -57,6 +59,7 @@ namespace API.Services.Tasks return files; } + [AutomaticRetry(Attempts = 3, LogEvents = false, OnAttemptsExceeded = AttemptsExceededAction.Fail)] public void BackupDatabase() { _logger.LogInformation("Beginning backup of Database at {BackupTime}", DateTime.Now); diff --git a/API/Services/Tasks/CleanupService.cs b/API/Services/Tasks/CleanupService.cs index d7079b4a7..a33cf746f 100644 --- a/API/Services/Tasks/CleanupService.cs +++ b/API/Services/Tasks/CleanupService.cs @@ -1,5 +1,6 @@ using System.IO; using API.Interfaces.Services; +using Hangfire; using Microsoft.Extensions.Logging; namespace API.Services.Tasks @@ -20,6 +21,7 @@ namespace API.Services.Tasks _logger = logger; } + [AutomaticRetry(Attempts = 3, LogEvents = false, OnAttemptsExceeded = AttemptsExceededAction.Fail)] public void Cleanup() { _logger.LogInformation("Cleaning temp directory"); diff --git a/API/Services/Tasks/ScannerService.cs b/API/Services/Tasks/ScannerService.cs index 5587ad0c8..af7b6eca8 100644 --- a/API/Services/Tasks/ScannerService.cs +++ b/API/Services/Tasks/ScannerService.cs @@ -33,7 +33,8 @@ namespace API.Services.Tasks _metadataService = metadataService; } - [DisableConcurrentExecution(timeoutInSeconds: 120)] + [DisableConcurrentExecution(timeoutInSeconds: 5)] + [AutomaticRetry(Attempts = 0, LogEvents = false, OnAttemptsExceeded = AttemptsExceededAction.Delete)] public void ScanLibraries() { var libraries = Task.Run(() => _unitOfWork.LibraryRepository.GetLibrariesAsync()).Result.ToList(); diff --git a/API/Startup.cs b/API/Startup.cs index 4e9319b37..d53e346d6 100644 --- a/API/Startup.cs +++ b/API/Startup.cs @@ -18,6 +18,7 @@ using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Microsoft.OpenApi.Models; +using IApplicationLifetime = Microsoft.AspNetCore.Hosting.IApplicationLifetime; namespace API { @@ -66,11 +67,11 @@ namespace API services .AddStartupTask() .TryAddSingleton(services); - + } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. - public void Configure(IApplicationBuilder app, IBackgroundJobClient backgroundJobs, IWebHostEnvironment env) + public void Configure(IApplicationBuilder app, IBackgroundJobClient backgroundJobs, IWebHostEnvironment env, IHostApplicationLifetime applicationLifetime) { app.UseMiddleware(); @@ -125,6 +126,16 @@ namespace API endpoints.MapHangfireDashboard(); endpoints.MapFallbackToController("Index", "Fallback"); }); + + applicationLifetime.ApplicationStopping.Register(OnShutdown); + } + + private void OnShutdown() + { + Console.WriteLine("Server is shutting down. Going to dispose Hangfire"); + //this code is called when the application stops + //TaskScheduler.Client.Dispose(); + System.Threading.Thread.Sleep(1000); } } }