Kavita+ Tweaks (#2595)
This commit is contained in:
parent
e21144bf6b
commit
3dcf7750f7
21 changed files with 501 additions and 414 deletions
|
@ -53,7 +53,7 @@
|
|||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.0">
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.1">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
|
@ -63,26 +63,26 @@
|
|||
<PackageReference Include="ExCSS" Version="4.2.4" />
|
||||
<PackageReference Include="Flurl" Version="3.0.7" />
|
||||
<PackageReference Include="Flurl.Http" Version="3.2.4" />
|
||||
<PackageReference Include="Hangfire" Version="1.8.6" />
|
||||
<PackageReference Include="Hangfire" Version="1.8.7" />
|
||||
<PackageReference Include="Hangfire.InMemory" Version="0.6.0" />
|
||||
<PackageReference Include="Hangfire.MaximumConcurrentExecutions" Version="1.1.0" />
|
||||
<PackageReference Include="Hangfire.MemoryStorage.Core" Version="1.4.0" />
|
||||
<PackageReference Include="Hangfire.Storage.SQLite" Version="0.3.4" />
|
||||
<PackageReference Include="HtmlAgilityPack" Version="1.11.54" />
|
||||
<PackageReference Include="Hangfire.Storage.SQLite" Version="0.4.0" />
|
||||
<PackageReference Include="HtmlAgilityPack" Version="1.11.57" />
|
||||
<PackageReference Include="MarkdownDeep.NET.Core" Version="1.5.0.4" />
|
||||
<PackageReference Include="Hangfire.AspNetCore" Version="1.8.6" />
|
||||
<PackageReference Include="Hangfire.AspNetCore" Version="1.8.7" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.SignalR" Version="1.1.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.OpenIdConnect" Version="8.0.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="8.0.0" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.1" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.OpenIdConnect" Version="8.0.1" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="8.0.1" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0" />
|
||||
<PackageReference Include="Microsoft.IO.RecyclableMemoryStream" Version="3.0.0" />
|
||||
<PackageReference Include="MimeTypeMapOfficial" Version="1.0.17" />
|
||||
<PackageReference Include="Nager.ArticleNumber" Version="1.0.7" />
|
||||
<PackageReference Include="NetVips" Version="2.4.0" />
|
||||
<PackageReference Include="NetVips.Native" Version="8.15.0" />
|
||||
<PackageReference Include="NReco.Logging.File" Version="1.1.7" />
|
||||
<PackageReference Include="NReco.Logging.File" Version="1.2.0" />
|
||||
<PackageReference Include="Serilog" Version="3.1.1" />
|
||||
<PackageReference Include="Serilog.AspNetCore" Version="8.0.0" />
|
||||
<PackageReference Include="Serilog.Enrichers.Thread" Version="3.2.0-dev-00752" />
|
||||
|
@ -92,17 +92,17 @@
|
|||
<PackageReference Include="Serilog.Sinks.Console" Version="5.0.1" />
|
||||
<PackageReference Include="Serilog.Sinks.File" Version="5.0.0" />
|
||||
<PackageReference Include="Serilog.Sinks.SignalR.Core" Version="0.1.2" />
|
||||
<PackageReference Include="SharpCompress" Version="0.34.2" />
|
||||
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.1" />
|
||||
<PackageReference Include="SharpCompress" Version="0.35.0" />
|
||||
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.2" />
|
||||
<PackageReference Include="SonarAnalyzer.CSharp" Version="9.15.0.81779">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.5.0" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore.Filters" Version="7.0.12" />
|
||||
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="7.0.3" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore.Filters" Version="8.0.0" />
|
||||
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="7.1.2" />
|
||||
<PackageReference Include="System.IO.Abstractions" Version="20.0.4" />
|
||||
<PackageReference Include="System.Drawing.Common" Version="8.0.0" />
|
||||
<PackageReference Include="System.Drawing.Common" Version="8.0.1" />
|
||||
<PackageReference Include="VersOne.Epub" Version="3.3.1" />
|
||||
</ItemGroup>
|
||||
|
||||
|
|
|
@ -39,22 +39,27 @@ public class ScrobblingController : BaseApiController
|
|||
_localizationService = localizationService;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the current user's AniList token
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[HttpGet("anilist-token")]
|
||||
public async Task<ActionResult> GetAniListToken()
|
||||
{
|
||||
// Validate the license
|
||||
|
||||
var user = await _unitOfWork.UserRepository.GetUserByUsernameAsync(User.GetUsername());
|
||||
if (user == null) return Unauthorized();
|
||||
|
||||
return Ok(user.AniListAccessToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update the current user's AniList token
|
||||
/// </summary>
|
||||
/// <param name="dto"></param>
|
||||
/// <returns></returns>
|
||||
[HttpPost("update-anilist-token")]
|
||||
public async Task<ActionResult> UpdateAniListToken(AniListUpdateDto dto)
|
||||
{
|
||||
// Validate the license
|
||||
|
||||
var user = await _unitOfWork.UserRepository.GetUserByUsernameAsync(User.GetUsername());
|
||||
if (user == null) return Unauthorized();
|
||||
|
||||
|
@ -71,6 +76,11 @@ public class ScrobblingController : BaseApiController
|
|||
return Ok();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the current Scrobbling token for the given Provider has expired for the current user
|
||||
/// </summary>
|
||||
/// <param name="provider"></param>
|
||||
/// <returns></returns>
|
||||
[HttpGet("token-expired")]
|
||||
public async Task<ActionResult<bool>> HasTokenExpired(ScrobbleProvider provider)
|
||||
{
|
||||
|
@ -159,15 +169,20 @@ public class ScrobblingController : BaseApiController
|
|||
var user = await _unitOfWork.UserRepository.GetUserByIdAsync(User.GetUserId(), AppUserIncludes.ScrobbleHolds);
|
||||
if (user == null) return Unauthorized();
|
||||
if (user.ScrobbleHolds.Any(s => s.SeriesId == seriesId))
|
||||
return Ok(await _localizationService.Translate(User.GetUserId(), "nothing-to-do"));
|
||||
return Ok(await _localizationService.Translate(user.Id, "nothing-to-do"));
|
||||
|
||||
var seriesHold = new ScrobbleHoldBuilder().WithSeriesId(seriesId).Build();
|
||||
var seriesHold = new ScrobbleHoldBuilder()
|
||||
.WithSeriesId(seriesId)
|
||||
.Build();
|
||||
user.ScrobbleHolds.Add(seriesHold);
|
||||
_unitOfWork.UserRepository.Update(user);
|
||||
try
|
||||
{
|
||||
_unitOfWork.UserRepository.Update(user);
|
||||
await _unitOfWork.CommitAsync();
|
||||
|
||||
// When a hold is placed on a series, clear any pre-existing Scrobble Events
|
||||
await _scrobblingService.ClearEventsForSeries(user.Id, seriesId);
|
||||
return Ok();
|
||||
}
|
||||
catch (DbUpdateConcurrencyException ex)
|
||||
|
|
|
@ -27,7 +27,7 @@ public interface IScrobbleRepository
|
|||
Task ClearScrobbleErrors();
|
||||
Task<bool> HasErrorForSeries(int seriesId);
|
||||
Task<ScrobbleEvent?> GetEvent(int userId, int seriesId, ScrobbleEventType eventType);
|
||||
Task<IEnumerable<ScrobbleEventDto>> GetUserEvents(int userId);
|
||||
Task<IEnumerable<ScrobbleEvent>> GetUserEventsForSeries(int userId, int seriesId);
|
||||
Task<PagedList<ScrobbleEventDto>> GetUserEvents(int userId, ScrobbleEventFilter filter, UserParams pagination);
|
||||
}
|
||||
|
||||
|
@ -127,16 +127,17 @@ public class ScrobbleRepository : IScrobbleRepository
|
|||
return await _context.ScrobbleEvent.FirstOrDefaultAsync(e =>
|
||||
e.AppUserId == userId && e.SeriesId == seriesId && e.ScrobbleEventType == eventType);
|
||||
}
|
||||
public async Task<IEnumerable<ScrobbleEventDto>> GetUserEvents(int userId)
|
||||
|
||||
public async Task<IEnumerable<ScrobbleEvent>> GetUserEventsForSeries(int userId, int seriesId)
|
||||
{
|
||||
return await _context.ScrobbleEvent
|
||||
.Where(e => e.AppUserId == userId)
|
||||
.Where(e => e.AppUserId == userId && !e.IsProcessed)
|
||||
.Include(e => e.Series)
|
||||
.OrderBy(e => e.LastModifiedUtc)
|
||||
.AsSplitQuery()
|
||||
.ProjectTo<ScrobbleEventDto>(_mapper.ConfigurationProvider)
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
public async Task<PagedList<ScrobbleEventDto>> GetUserEvents(int userId, ScrobbleEventFilter filter, UserParams pagination)
|
||||
{
|
||||
var query = _context.ScrobbleEvent
|
||||
|
@ -146,6 +147,7 @@ public class ScrobbleRepository : IScrobbleRepository
|
|||
.WhereIf(!string.IsNullOrEmpty(filter.Query), s =>
|
||||
EF.Functions.Like(s.Series.Name, $"%{filter.Query}%")
|
||||
)
|
||||
.WhereIf(!filter.IncludeReviews, e => e.ScrobbleEventType != ScrobbleEventType.Review)
|
||||
.AsSplitQuery()
|
||||
.ProjectTo<ScrobbleEventDto>(_mapper.ConfigurationProvider);
|
||||
|
||||
|
|
|
@ -15,4 +15,8 @@ public class ScrobbleEventFilter
|
|||
/// A query to search against
|
||||
/// </summary>
|
||||
public string Query { get; set; }
|
||||
/// <summary>
|
||||
/// Include reviews in the result - Note: Review Scrobbling is disabled
|
||||
/// </summary>
|
||||
public bool IncludeReviews { get; set; } = false;
|
||||
}
|
||||
|
|
|
@ -53,6 +53,7 @@ public interface IScrobblingService
|
|||
[AutomaticRetry(Attempts = 3, OnAttemptsExceeded = AttemptsExceededAction.Delete)]
|
||||
Task ProcessUpdatesSinceLastSync();
|
||||
Task CreateEventsFromExistingHistory(int userId = 0);
|
||||
Task ClearEventsForSeries(int userId, int seriesId);
|
||||
}
|
||||
|
||||
public class ScrobblingService : IScrobblingService
|
||||
|
@ -542,6 +543,26 @@ public class ScrobblingService : IScrobblingService
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes all events (active) that are tied to a now-on hold series
|
||||
/// </summary>
|
||||
/// <param name="userId"></param>
|
||||
/// <param name="seriesId"></param>
|
||||
public async Task ClearEventsForSeries(int userId, int seriesId)
|
||||
{
|
||||
_logger.LogInformation("Clearing Pre-existing Scrobble events for Series {SeriesId} by User {UserId} as Series is now on hold list", seriesId, userId);
|
||||
var events = await _unitOfWork.ScrobbleRepository.GetUserEventsForSeries(userId, seriesId);
|
||||
foreach (var scrobble in events)
|
||||
{
|
||||
_unitOfWork.ScrobbleRepository.Remove(scrobble);
|
||||
}
|
||||
|
||||
await _unitOfWork.CommitAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes all events that have been processed that are 7 days old
|
||||
/// </summary>
|
||||
[DisableConcurrentExecution(60 * 60 * 60)]
|
||||
[AutomaticRetry(Attempts = 3, OnAttemptsExceeded = AttemptsExceededAction.Delete)]
|
||||
public async Task ClearProcessedEvents()
|
||||
|
@ -594,10 +615,10 @@ public class ScrobblingService : IScrobblingService
|
|||
.Where(e => librariesWithScrobbling.Contains(e.LibraryId))
|
||||
.Where(e => !errors.Contains(e.SeriesId))
|
||||
.ToList();
|
||||
var reviewEvents = (await _unitOfWork.ScrobbleRepository.GetByEvent(ScrobbleEventType.Review))
|
||||
.Where(e => librariesWithScrobbling.Contains(e.LibraryId))
|
||||
.Where(e => !errors.Contains(e.SeriesId))
|
||||
.ToList();
|
||||
// var reviewEvents = (await _unitOfWork.ScrobbleRepository.GetByEvent(ScrobbleEventType.Review))
|
||||
// .Where(e => librariesWithScrobbling.Contains(e.LibraryId))
|
||||
// .Where(e => !errors.Contains(e.SeriesId))
|
||||
// .ToList();
|
||||
var decisions = addToWantToRead
|
||||
.GroupBy(item => new { item.SeriesId, item.AppUserId })
|
||||
.Select(group => new
|
||||
|
@ -624,7 +645,7 @@ public class ScrobblingService : IScrobblingService
|
|||
await SetAndCheckRateLimit(userRateLimits, user, license.Value);
|
||||
}
|
||||
|
||||
var totalProgress = readEvents.Count + decisions.Count + ratingEvents.Count + decisions.Count + reviewEvents.Count;
|
||||
var totalProgress = readEvents.Count + decisions.Count + ratingEvents.Count + decisions.Count;// + reviewEvents.Count;
|
||||
|
||||
_logger.LogInformation("Found {TotalEvents} Scrobble Events", totalProgress);
|
||||
try
|
||||
|
@ -671,21 +692,21 @@ public class ScrobblingService : IScrobblingService
|
|||
Year = evt.Series.Metadata.ReleaseYear
|
||||
}));
|
||||
|
||||
progressCounter = await ProcessEvents(reviewEvents, userRateLimits, usersToScrobble.Count, progressCounter,
|
||||
totalProgress, evt => Task.FromResult(new ScrobbleDto()
|
||||
{
|
||||
Format = evt.Format,
|
||||
AniListId = evt.AniListId,
|
||||
MALId = (int?) evt.MalId,
|
||||
ScrobbleEventType = evt.ScrobbleEventType,
|
||||
AniListToken = evt.AppUser.AniListAccessToken,
|
||||
SeriesName = evt.Series.Name,
|
||||
LocalizedSeriesName = evt.Series.LocalizedName,
|
||||
Rating = evt.Rating,
|
||||
Year = evt.Series.Metadata.ReleaseYear,
|
||||
ReviewBody = evt.ReviewBody,
|
||||
ReviewTitle = evt.ReviewTitle
|
||||
}));
|
||||
// progressCounter = await ProcessEvents(reviewEvents, userRateLimits, usersToScrobble.Count, progressCounter,
|
||||
// totalProgress, evt => Task.FromResult(new ScrobbleDto()
|
||||
// {
|
||||
// Format = evt.Format,
|
||||
// AniListId = evt.AniListId,
|
||||
// MALId = (int?) evt.MalId,
|
||||
// ScrobbleEventType = evt.ScrobbleEventType,
|
||||
// AniListToken = evt.AppUser.AniListAccessToken,
|
||||
// SeriesName = evt.Series.Name,
|
||||
// LocalizedSeriesName = evt.Series.LocalizedName,
|
||||
// Rating = evt.Rating,
|
||||
// Year = evt.Series.Metadata.ReleaseYear,
|
||||
// ReviewBody = evt.ReviewBody,
|
||||
// ReviewTitle = evt.ReviewTitle
|
||||
// }));
|
||||
|
||||
progressCounter = await ProcessEvents(decisions, userRateLimits, usersToScrobble.Count, progressCounter,
|
||||
totalProgress, evt => Task.FromResult(new ScrobbleDto()
|
||||
|
|
|
@ -49,6 +49,7 @@ public class DefaultParser : IDefaultParser
|
|||
// If library type is Image or this is not a cover image in a non-image library, then use dedicated parsing mechanism
|
||||
if (type == LibraryType.Image || Parser.IsImage(filePath))
|
||||
{
|
||||
// TODO: We can move this up one level
|
||||
return ParseImage(filePath, rootPath, ret);
|
||||
}
|
||||
|
||||
|
@ -78,7 +79,7 @@ public class DefaultParser : IDefaultParser
|
|||
var edition = Parser.ParseEdition(fileName);
|
||||
if (!string.IsNullOrEmpty(edition))
|
||||
{
|
||||
ret.Series = Parser.CleanTitle(ret.Series.Replace(edition, ""), type is LibraryType.Comic);
|
||||
ret.Series = Parser.CleanTitle(ret.Series.Replace(edition, string.Empty), type is LibraryType.Comic);
|
||||
ret.Edition = edition;
|
||||
}
|
||||
|
||||
|
|
|
@ -543,7 +543,7 @@ public static class Parser
|
|||
{
|
||||
// Historys Strongest Disciple Kenichi_v11_c90-98.zip, ...c90.5-100.5
|
||||
new Regex(
|
||||
@"(\b|_)(c|ch)(\.?\s?)(?<Chapter>(\d+(\.\d)?)-?(\d+(\.\d)?)?)",
|
||||
@"(\b|_)(c|ch)(\.?\s?)(?<Chapter>(\d+(\.\d)?)(-\d+(\.\d)?)?)",
|
||||
MatchOptions, RegexTimeout),
|
||||
// [Suihei Kiki]_Kasumi_Otoko_no_Ko_[Taruby]_v1.1.zip
|
||||
new Regex(
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue