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");
}
[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.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
@ -112,5 +113,7 @@ namespace API.Data
.Include(l => l.Folders)
.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
{
[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<int>("ChapterId")
.HasColumnType("INTEGER");
b.Property<DateTime>("Created")
.HasColumnType("TEXT");
b.Property<DateTime>("LastModified")
.HasColumnType("TEXT");
b.Property<int>("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");

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")
.HasColumnType("INTEGER");
b.Property<DateTime>("Created")
.HasColumnType("TEXT");
b.Property<DateTime>("LastModified")
.HasColumnType("TEXT");
b.Property<int>("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");

View File

@ -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);
}
}
/// <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
{
/// <summary>
/// Represents the progress a single user has on a given Volume. Progress is realistically tracked against the Volume's chapters.
/// </summary>
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; }
}
}

View File

@ -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?
/// <summary>
/// Sum of all Volume page counts
/// </summary>

View File

@ -55,5 +55,6 @@ namespace API.Interfaces
Task<byte[]> GetVolumeCoverImageAsync(int volumeId);
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");
}
await host.RunAsync();
}

View File

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

View File

@ -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)

View File

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

View File

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

View File

@ -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();

View File

@ -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<WarmupServicesStartupTask>()
.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<ExceptionMiddleware>();
@ -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);
}
}
}