Hangfire now dies gracefully when using CTRL+C rather than Stop button in Rider. Implemented one stream method for testing. Regenerated a few migrations due to oversight in index not taking account of library.

This commit is contained in:
Joseph Milazzo 2021-03-15 08:43:43 -05:00
parent 126fb57f4d
commit 9035b6cc4e
17 changed files with 156 additions and 41 deletions

View File

@ -126,5 +126,11 @@ namespace API.Controllers
return BadRequest("There was an error with updating the series"); return BadRequest("There was an error with updating the series");
} }
[HttpGet("recently-added")]
public async Task<ActionResult<IEnumerable<SeriesDto>>> GetRecentlyAdded(int libraryId = 0)
{
return Ok(await _unitOfWork.SeriesRepository.GetRecentlyAdded(libraryId));
}
} }
} }

View File

@ -1,4 +1,5 @@
using System; using System;
using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.Linq; using System.Linq;
@ -112,5 +113,7 @@ namespace API.Data
.Include(l => l.Folders) .Include(l => l.Folders)
.ProjectTo<LibraryDto>(_mapper.ConfigurationProvider).ToListAsync(); .ProjectTo<LibraryDto>(_mapper.ConfigurationProvider).ToListAsync();
} }
} }
} }

View File

@ -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");
}
}
}

View File

@ -9,8 +9,8 @@ using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
namespace API.Data.Migrations namespace API.Data.Migrations
{ {
[DbContext(typeof(DataContext))] [DbContext(typeof(DataContext))]
[Migration("20210313001830_SearchIndex")] [Migration("20210315134028_SearchIndexAndProgressDates")]
partial class SearchIndex partial class SearchIndexAndProgressDates
{ {
protected override void BuildTargetModel(ModelBuilder modelBuilder) protected override void BuildTargetModel(ModelBuilder modelBuilder)
{ {
@ -161,6 +161,12 @@ namespace API.Data.Migrations
b.Property<int>("ChapterId") b.Property<int>("ChapterId")
.HasColumnType("INTEGER"); .HasColumnType("INTEGER");
b.Property<DateTime>("Created")
.HasColumnType("TEXT");
b.Property<DateTime>("LastModified")
.HasColumnType("TEXT");
b.Property<int>("PagesRead") b.Property<int>("PagesRead")
.HasColumnType("INTEGER"); .HasColumnType("INTEGER");
@ -367,7 +373,7 @@ namespace API.Data.Migrations
b.HasIndex("LibraryId"); b.HasIndex("LibraryId");
b.HasIndex("Name", "NormalizedName", "LocalizedName") b.HasIndex("Name", "NormalizedName", "LocalizedName", "LibraryId")
.IsUnique(); .IsUnique();
b.ToTable("Series"); b.ToTable("Series");

View File

@ -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<DateTime>(
name: "Created",
table: "AppUserProgresses",
type: "TEXT",
nullable: false,
defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified));
migrationBuilder.AddColumn<DateTime>(
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");
}
}
}

View File

@ -159,6 +159,12 @@ namespace API.Data.Migrations
b.Property<int>("ChapterId") b.Property<int>("ChapterId")
.HasColumnType("INTEGER"); .HasColumnType("INTEGER");
b.Property<DateTime>("Created")
.HasColumnType("TEXT");
b.Property<DateTime>("LastModified")
.HasColumnType("TEXT");
b.Property<int>("PagesRead") b.Property<int>("PagesRead")
.HasColumnType("INTEGER"); .HasColumnType("INTEGER");
@ -365,7 +371,7 @@ namespace API.Data.Migrations
b.HasIndex("LibraryId"); b.HasIndex("LibraryId");
b.HasIndex("Name", "NormalizedName", "LocalizedName") b.HasIndex("Name", "NormalizedName", "LocalizedName", "LibraryId")
.IsUnique(); .IsUnique();
b.ToTable("Series"); b.ToTable("Series");

View File

@ -1,4 +1,6 @@
using System.Collections.Generic; using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -275,5 +277,54 @@ namespace API.Data
v.PagesRead = userProgress.Where(p => p.VolumeId == v.Id).Sum(p => p.PagesRead); v.PagesRead = userProgress.Where(p => p.VolumeId == v.Id).Sum(p => p.PagesRead);
} }
} }
/// <summary>
/// Returns a list of Series that were added within 2 weeks.
/// </summary>
/// <param name="libraryId">Library to restrict to, if 0, will apply to all libraries</param>
/// <returns></returns>
public async Task<IEnumerable<SeriesDto>> 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<SeriesDto>(_mapper.ConfigurationProvider)
.ToListAsync();
}
public async Task<IEnumerable<SeriesDto>> 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[] {};
}
} }
} }

View File

@ -1,20 +1,25 @@
 
using System;
using API.Entities.Interfaces;
namespace API.Entities namespace API.Entities
{ {
/// <summary> /// <summary>
/// Represents the progress a single user has on a given Volume. Progress is realistically tracked against the Volume's chapters. /// Represents the progress a single user has on a given Volume. Progress is realistically tracked against the Volume's chapters.
/// </summary> /// </summary>
public class AppUserProgress public class AppUserProgress : IEntityDate
{ {
public int Id { get; set; } public int Id { get; set; }
public int PagesRead { get; set; } public int PagesRead { get; set; }
public int VolumeId { get; set; } public int VolumeId { get; set; }
public int SeriesId { get; set; } public int SeriesId { get; set; }
public int ChapterId { get; set; } public int ChapterId { get; set; }
// Relationships // Relationships
public AppUser AppUser { get; set; } public AppUser AppUser { get; set; }
public int AppUserId { get; set; } public int AppUserId { get; set; }
public DateTime Created { get; set; }
public DateTime LastModified { get; set; }
} }
} }

View File

@ -5,7 +5,7 @@ using Microsoft.EntityFrameworkCore;
namespace API.Entities 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 class Series : IEntityDate
{ {
public int Id { get; set; } public int Id { get; set; }
@ -36,7 +36,6 @@ namespace API.Entities
public DateTime Created { get; set; } public DateTime Created { get; set; }
public DateTime LastModified { get; set; } public DateTime LastModified { get; set; }
public byte[] CoverImage { get; set; } public byte[] CoverImage { get; set; }
// NOTE: Do I want to store a thumbImage for search results?
/// <summary> /// <summary>
/// Sum of all Volume page counts /// Sum of all Volume page counts
/// </summary> /// </summary>

View File

@ -55,5 +55,6 @@ namespace API.Interfaces
Task<byte[]> GetVolumeCoverImageAsync(int volumeId); Task<byte[]> GetVolumeCoverImageAsync(int volumeId);
Task<byte[]> GetSeriesCoverImageAsync(int seriesId); Task<byte[]> GetSeriesCoverImageAsync(int seriesId);
Task<IEnumerable<SeriesDto>> GetRecentlyAdded(int libraryId);
} }
} }

View File

@ -41,7 +41,6 @@ namespace API
logger.LogError(ex, "An error occurred during migration"); logger.LogError(ex, "An error occurred during migration");
} }
await host.RunAsync(); await host.RunAsync();
} }

View File

@ -52,6 +52,7 @@ namespace API.Services
return chapter; return chapter;
} }
public void Cleanup() public void Cleanup()
{ {
_logger.LogInformation("Performing cleanup of Cache directory"); _logger.LogInformation("Performing cleanup of Cache directory");

View File

@ -21,7 +21,7 @@ namespace API.Services
private readonly ICleanupService _cleanupService; private readonly ICleanupService _cleanupService;
private readonly IDirectoryService _directoryService; private readonly IDirectoryService _directoryService;
public BackgroundJobServer Client => new BackgroundJobServer(); public static BackgroundJobServer Client => new BackgroundJobServer();
// new BackgroundJobServerOptions() // new BackgroundJobServerOptions()
// { // {
// WorkerCount = 1 // WorkerCount = 1
@ -40,15 +40,13 @@ namespace API.Services
_cleanupService = cleanupService; _cleanupService = cleanupService;
_directoryService = directoryService; _directoryService = directoryService;
//Hangfire.RecurringJob.RemoveIfExists();
ScheduleTasks(); ScheduleTasks();
//JobStorage.Current.GetMonitoringApi().EnqueuedJobs()
} }
public void ScheduleTasks() public void ScheduleTasks()
{ {
_logger.LogInformation("Scheduling reoccurring tasks"); _logger.LogInformation("Scheduling reoccurring tasks");
string setting = null; string setting = null;
setting = Task.Run(() => _unitOfWork.SettingsRepository.GetSettingAsync(ServerSettingKey.TaskScan)).Result.Value; setting = Task.Run(() => _unitOfWork.SettingsRepository.GetSettingAsync(ServerSettingKey.TaskScan)).Result.Value;
if (setting != null) if (setting != null)

View File

@ -3,11 +3,13 @@ using System.Collections.Generic;
using System.IO; using System.IO;
using System.IO.Compression; using System.IO.Compression;
using System.Linq; using System.Linq;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using API.Entities.Enums; using API.Entities.Enums;
using API.Extensions; using API.Extensions;
using API.Interfaces; using API.Interfaces;
using API.Interfaces.Services; using API.Interfaces.Services;
using Hangfire;
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
@ -57,6 +59,7 @@ namespace API.Services.Tasks
return files; return files;
} }
[AutomaticRetry(Attempts = 3, LogEvents = false, OnAttemptsExceeded = AttemptsExceededAction.Fail)]
public void BackupDatabase() public void BackupDatabase()
{ {
_logger.LogInformation("Beginning backup of Database at {BackupTime}", DateTime.Now); _logger.LogInformation("Beginning backup of Database at {BackupTime}", DateTime.Now);

View File

@ -1,5 +1,6 @@
using System.IO; using System.IO;
using API.Interfaces.Services; using API.Interfaces.Services;
using Hangfire;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
namespace API.Services.Tasks namespace API.Services.Tasks
@ -20,6 +21,7 @@ namespace API.Services.Tasks
_logger = logger; _logger = logger;
} }
[AutomaticRetry(Attempts = 3, LogEvents = false, OnAttemptsExceeded = AttemptsExceededAction.Fail)]
public void Cleanup() public void Cleanup()
{ {
_logger.LogInformation("Cleaning temp directory"); _logger.LogInformation("Cleaning temp directory");

View File

@ -33,7 +33,8 @@ namespace API.Services.Tasks
_metadataService = metadataService; _metadataService = metadataService;
} }
[DisableConcurrentExecution(timeoutInSeconds: 120)] [DisableConcurrentExecution(timeoutInSeconds: 5)]
[AutomaticRetry(Attempts = 0, LogEvents = false, OnAttemptsExceeded = AttemptsExceededAction.Delete)]
public void ScanLibraries() public void ScanLibraries()
{ {
var libraries = Task.Run(() => _unitOfWork.LibraryRepository.GetLibrariesAsync()).Result.ToList(); var libraries = Task.Run(() => _unitOfWork.LibraryRepository.GetLibrariesAsync()).Result.ToList();

View File

@ -18,6 +18,7 @@ using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Microsoft.OpenApi.Models; using Microsoft.OpenApi.Models;
using IApplicationLifetime = Microsoft.AspNetCore.Hosting.IApplicationLifetime;
namespace API namespace API
{ {
@ -70,7 +71,7 @@ namespace API
} }
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline. // 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<ExceptionMiddleware>(); app.UseMiddleware<ExceptionMiddleware>();
@ -125,6 +126,16 @@ namespace API
endpoints.MapHangfireDashboard(); endpoints.MapHangfireDashboard();
endpoints.MapFallbackToController("Index", "Fallback"); 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);
} }
} }
} }