mirror of
https://github.com/Kareadita/Kavita.git
synced 2025-06-03 05:34:21 -04:00
Hotfix Prep (#2484)
This commit is contained in:
parent
8c16b87ff0
commit
bd4cbeb393
@ -44,6 +44,17 @@ public class SmartFilterHelperTests
|
|||||||
AssertStatementSame(list[0], FilterField.Genres, FilterComparison.Equal, "95");
|
AssertStatementSame(list[0], FilterField.Genres, FilterComparison.Equal, "95");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Test_Decode2()
|
||||||
|
{
|
||||||
|
const string encoded = """
|
||||||
|
name=Test%202&stmts=comparison%253D10%25C2%25A6field%253D1%25C2%25A6value%253DA%EF%BF%BDcomparison%253D0%25C2%25A6field%253D19%25C2%25A6value%253D11&sortOptions=sortField%3D1%C2%A6isAscending%3DTrue&limitTo=0&combination=1
|
||||||
|
""";
|
||||||
|
|
||||||
|
var filter = SmartFilterHelper.Decode(encoded);
|
||||||
|
Assert.True(filter.SortOptions.IsAscending);
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void Test_EncodeDecode()
|
public void Test_EncodeDecode()
|
||||||
{
|
{
|
||||||
|
@ -83,6 +83,7 @@ public class MangaParserTests
|
|||||||
[InlineData("시즌34삽화2", "34")]
|
[InlineData("시즌34삽화2", "34")]
|
||||||
[InlineData("Accel World Chapter 001 Volume 002", "2")]
|
[InlineData("Accel World Chapter 001 Volume 002", "2")]
|
||||||
[InlineData("Accel World Volume 2", "2")]
|
[InlineData("Accel World Volume 2", "2")]
|
||||||
|
[InlineData("Nagasarete Airantou - Vol. 30 Ch. 187.5 - Vol.31 Omake", "30")]
|
||||||
public void ParseVolumeTest(string filename, string expected)
|
public void ParseVolumeTest(string filename, string expected)
|
||||||
{
|
{
|
||||||
Assert.Equal(expected, API.Services.Tasks.Scanner.Parser.Parser.ParseVolume(filename));
|
Assert.Equal(expected, API.Services.Tasks.Scanner.Parser.Parser.ParseVolume(filename));
|
||||||
@ -204,6 +205,7 @@ public class MangaParserTests
|
|||||||
[InlineData("죠시라쿠! 2년 후 1권", "죠시라쿠! 2년 후")]
|
[InlineData("죠시라쿠! 2년 후 1권", "죠시라쿠! 2년 후")]
|
||||||
[InlineData("test 2 years 1권", "test 2 years")]
|
[InlineData("test 2 years 1권", "test 2 years")]
|
||||||
[InlineData("test 2 years 1화", "test 2 years")]
|
[InlineData("test 2 years 1화", "test 2 years")]
|
||||||
|
[InlineData("Nagasarete Airantou - Vol. 30 Ch. 187.5 - Vol.30 Omake", "Nagasarete Airantou")]
|
||||||
public void ParseSeriesTest(string filename, string expected)
|
public void ParseSeriesTest(string filename, string expected)
|
||||||
{
|
{
|
||||||
Assert.Equal(expected, API.Services.Tasks.Scanner.Parser.Parser.ParseSeries(filename));
|
Assert.Equal(expected, API.Services.Tasks.Scanner.Parser.Parser.ParseSeries(filename));
|
||||||
|
@ -2,13 +2,23 @@
|
|||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace API.Tests.Services;
|
namespace API.Tests.Services;
|
||||||
|
#nullable enable
|
||||||
|
|
||||||
public class ScrobblingServiceTests
|
public class ScrobblingServiceTests
|
||||||
{
|
{
|
||||||
[Theory]
|
[Theory]
|
||||||
[InlineData("https://anilist.co/manga/35851/Byeontaega-Doeja/", 35851)]
|
[InlineData("https://anilist.co/manga/35851/Byeontaega-Doeja/", 35851)]
|
||||||
public void CanParseWeblink(string link, long expectedId)
|
[InlineData("https://anilist.co/manga/30105", 30105)]
|
||||||
|
[InlineData("https://anilist.co/manga/30105/Kekkaishi/", 30105)]
|
||||||
|
public void CanParseWeblink_AniList(string link, int? expectedId)
|
||||||
{
|
{
|
||||||
Assert.Equal(ScrobblingService.ExtractId<long>(link, ScrobblingService.AniListWeblinkWebsite), expectedId);
|
Assert.Equal(ScrobblingService.ExtractId<int?>(link, ScrobblingService.AniListWeblinkWebsite), expectedId);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("https://mangadex.org/title/316d3d09-bb83-49da-9d90-11dc7ce40967/honzuki-no-gekokujou-shisho-ni-naru-tame-ni-wa-shudan-wo-erandeiraremasen-dai-3-bu-ryouchi-ni-hon-o", "316d3d09-bb83-49da-9d90-11dc7ce40967")]
|
||||||
|
public void CanParseWeblink_MangaDex(string link, string expectedId)
|
||||||
|
{
|
||||||
|
Assert.Equal(ScrobblingService.ExtractId<string?>(link, ScrobblingService.MangaDexWeblinkWebsite), expectedId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -26,6 +26,7 @@ using Microsoft.AspNetCore.Mvc;
|
|||||||
using Microsoft.AspNetCore.RateLimiting;
|
using Microsoft.AspNetCore.RateLimiting;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
using SharpCompress;
|
||||||
|
|
||||||
namespace API.Controllers;
|
namespace API.Controllers;
|
||||||
|
|
||||||
@ -137,8 +138,7 @@ public class AccountController : BaseApiController
|
|||||||
if (!result.Succeeded) return BadRequest(result.Errors);
|
if (!result.Succeeded) return BadRequest(result.Errors);
|
||||||
|
|
||||||
// Assign default streams
|
// Assign default streams
|
||||||
user.DashboardStreams = Seed.DefaultStreams.ToList();
|
AddDefaultStreamsToUser(user);
|
||||||
user.SideNavStreams = Seed.DefaultSideNavStreams.ToList();
|
|
||||||
|
|
||||||
var token = await _userManager.GenerateEmailConfirmationTokenAsync(user);
|
var token = await _userManager.GenerateEmailConfirmationTokenAsync(user);
|
||||||
if (string.IsNullOrEmpty(token)) return BadRequest(await _localizationService.Get("en", "confirm-token-gen"));
|
if (string.IsNullOrEmpty(token)) return BadRequest(await _localizationService.Get("en", "confirm-token-gen"));
|
||||||
@ -608,7 +608,8 @@ public class AccountController : BaseApiController
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Create a new user
|
// Create a new user
|
||||||
var user = new AppUserBuilder(dto.Email, dto.Email, await _unitOfWork.SiteThemeRepository.GetDefaultTheme()).Build();
|
var user = new AppUserBuilder(dto.Email, dto.Email,
|
||||||
|
await _unitOfWork.SiteThemeRepository.GetDefaultTheme()).Build();
|
||||||
_unitOfWork.UserRepository.Add(user);
|
_unitOfWork.UserRepository.Add(user);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@ -616,9 +617,7 @@ public class AccountController : BaseApiController
|
|||||||
if (!result.Succeeded) return BadRequest(result.Errors);
|
if (!result.Succeeded) return BadRequest(result.Errors);
|
||||||
|
|
||||||
// Assign default streams
|
// Assign default streams
|
||||||
user.DashboardStreams = Seed.DefaultStreams.ToList();
|
AddDefaultStreamsToUser(user);
|
||||||
user.SideNavStreams = Seed.DefaultSideNavStreams.ToList();
|
|
||||||
|
|
||||||
|
|
||||||
// Assign Roles
|
// Assign Roles
|
||||||
var roles = dto.Roles;
|
var roles = dto.Roles;
|
||||||
@ -657,7 +656,6 @@ public class AccountController : BaseApiController
|
|||||||
user.CreateSideNavFromLibrary(lib);
|
user.CreateSideNavFromLibrary(lib);
|
||||||
}
|
}
|
||||||
|
|
||||||
_unitOfWork.UserRepository.Update(user);
|
|
||||||
user.AgeRestriction = hasAdminRole ? AgeRating.NotApplicable : dto.AgeRestriction.AgeRating;
|
user.AgeRestriction = hasAdminRole ? AgeRating.NotApplicable : dto.AgeRestriction.AgeRating;
|
||||||
user.AgeRestrictionIncludeUnknowns = hasAdminRole || dto.AgeRestriction.IncludeUnknowns;
|
user.AgeRestrictionIncludeUnknowns = hasAdminRole || dto.AgeRestriction.IncludeUnknowns;
|
||||||
|
|
||||||
@ -669,6 +667,7 @@ public class AccountController : BaseApiController
|
|||||||
}
|
}
|
||||||
|
|
||||||
user.ConfirmationToken = token;
|
user.ConfirmationToken = token;
|
||||||
|
_unitOfWork.UserRepository.Update(user);
|
||||||
await _unitOfWork.CommitAsync();
|
await _unitOfWork.CommitAsync();
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@ -702,7 +701,7 @@ public class AccountController : BaseApiController
|
|||||||
BackgroundJob.Enqueue(() => _emailService.SendConfirmationEmail(new ConfirmationEmailDto()
|
BackgroundJob.Enqueue(() => _emailService.SendConfirmationEmail(new ConfirmationEmailDto()
|
||||||
{
|
{
|
||||||
EmailAddress = dto.Email,
|
EmailAddress = dto.Email,
|
||||||
InvitingUser = adminUser.UserName!,
|
InvitingUser = adminUser.UserName,
|
||||||
ServerConfirmationLink = emailLink
|
ServerConfirmationLink = emailLink
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
@ -721,6 +720,19 @@ public class AccountController : BaseApiController
|
|||||||
return BadRequest(await _localizationService.Translate(User.GetUserId(), "generic-invite-user"));
|
return BadRequest(await _localizationService.Translate(User.GetUserId(), "generic-invite-user"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void AddDefaultStreamsToUser(AppUser user)
|
||||||
|
{
|
||||||
|
foreach (var newStream in Seed.DefaultStreams.Select(stream => _mapper.Map<AppUserDashboardStream, AppUserDashboardStream>(stream)))
|
||||||
|
{
|
||||||
|
user.DashboardStreams.Add(newStream);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var stream in Seed.DefaultSideNavStreams.Select(stream => _mapper.Map<AppUserSideNavStream, AppUserSideNavStream>(stream)))
|
||||||
|
{
|
||||||
|
user.SideNavStreams.Add(stream);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Last step in authentication flow, confirms the email token for email
|
/// Last step in authentication flow, confirms the email token for email
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -115,10 +115,6 @@ public class ReaderController : BaseApiController
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (new Random().Next(1, 10) > 5)
|
|
||||||
{
|
|
||||||
await Task.Delay(1000);
|
|
||||||
}
|
|
||||||
var chapter = await _cacheService.Ensure(chapterId, extractPdf);
|
var chapter = await _cacheService.Ensure(chapterId, extractPdf);
|
||||||
if (chapter == null) return NoContent();
|
if (chapter == null) return NoContent();
|
||||||
_logger.LogInformation("Fetching Page {PageNum} on Chapter {ChapterId}", page, chapterId);
|
_logger.LogInformation("Fetching Page {PageNum} on Chapter {ChapterId}", page, chapterId);
|
||||||
|
@ -76,48 +76,42 @@ public static class Seed
|
|||||||
},
|
},
|
||||||
}.ToArray());
|
}.ToArray());
|
||||||
|
|
||||||
public static readonly ImmutableArray<AppUserSideNavStream> DefaultSideNavStreams = ImmutableArray.Create(new[]
|
public static readonly ImmutableArray<AppUserSideNavStream> DefaultSideNavStreams = ImmutableArray.Create(
|
||||||
|
new AppUserSideNavStream()
|
||||||
{
|
{
|
||||||
new AppUserSideNavStream()
|
Name = "want-to-read",
|
||||||
{
|
StreamType = SideNavStreamType.WantToRead,
|
||||||
Name = "want-to-read",
|
Order = 1,
|
||||||
StreamType = SideNavStreamType.WantToRead,
|
IsProvided = true,
|
||||||
Order = 1,
|
Visible = true
|
||||||
IsProvided = true,
|
}, new AppUserSideNavStream()
|
||||||
Visible = true
|
{
|
||||||
},
|
Name = "collections",
|
||||||
new AppUserSideNavStream()
|
StreamType = SideNavStreamType.Collections,
|
||||||
{
|
Order = 2,
|
||||||
Name = "collections",
|
IsProvided = true,
|
||||||
StreamType = SideNavStreamType.Collections,
|
Visible = true
|
||||||
Order = 2,
|
}, new AppUserSideNavStream()
|
||||||
IsProvided = true,
|
{
|
||||||
Visible = true
|
Name = "reading-lists",
|
||||||
},
|
StreamType = SideNavStreamType.ReadingLists,
|
||||||
new AppUserSideNavStream()
|
Order = 3,
|
||||||
{
|
IsProvided = true,
|
||||||
Name = "reading-lists",
|
Visible = true
|
||||||
StreamType = SideNavStreamType.ReadingLists,
|
}, new AppUserSideNavStream()
|
||||||
Order = 3,
|
{
|
||||||
IsProvided = true,
|
Name = "bookmarks",
|
||||||
Visible = true
|
StreamType = SideNavStreamType.Bookmarks,
|
||||||
},
|
Order = 4,
|
||||||
new AppUserSideNavStream()
|
IsProvided = true,
|
||||||
{
|
Visible = true
|
||||||
Name = "bookmarks",
|
}, new AppUserSideNavStream()
|
||||||
StreamType = SideNavStreamType.Bookmarks,
|
{
|
||||||
Order = 4,
|
Name = "all-series",
|
||||||
IsProvided = true,
|
StreamType = SideNavStreamType.AllSeries,
|
||||||
Visible = true
|
Order = 5,
|
||||||
},
|
IsProvided = true,
|
||||||
new AppUserSideNavStream()
|
Visible = true
|
||||||
{
|
|
||||||
Name = "all-series",
|
|
||||||
StreamType = SideNavStreamType.AllSeries,
|
|
||||||
Order = 5,
|
|
||||||
IsProvided = true,
|
|
||||||
Visible = true
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
@ -240,9 +240,10 @@ public class AutoMapperProfiles : Profile
|
|||||||
|
|
||||||
CreateMap<AppUserSmartFilter, SmartFilterDto>();
|
CreateMap<AppUserSmartFilter, SmartFilterDto>();
|
||||||
CreateMap<AppUserDashboardStream, DashboardStreamDto>();
|
CreateMap<AppUserDashboardStream, DashboardStreamDto>();
|
||||||
// CreateMap<AppUserDashboardStream, DashboardStreamDto>()
|
|
||||||
// .ForMember(dest => dest.SmartFilterEncoded,
|
// This is for cloning to ensure the records don't get overwritten when setting from SeedData
|
||||||
// opt => opt.MapFrom(src => src.SmartFilter));
|
CreateMap<AppUserDashboardStream, AppUserDashboardStream>();
|
||||||
|
CreateMap<AppUserSideNavStream, AppUserSideNavStream>();
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -27,6 +27,8 @@ public class PlusSeriesDtoBuilder : IEntityBuilder<PlusSeriesDto>
|
|||||||
ScrobblingService.MalWeblinkWebsite),
|
ScrobblingService.MalWeblinkWebsite),
|
||||||
GoogleBooksId = ScrobblingService.ExtractId<string?>(series.Metadata.WebLinks,
|
GoogleBooksId = ScrobblingService.ExtractId<string?>(series.Metadata.WebLinks,
|
||||||
ScrobblingService.GoogleBooksWeblinkWebsite),
|
ScrobblingService.GoogleBooksWeblinkWebsite),
|
||||||
|
MangaDexId = ScrobblingService.ExtractId<string?>(series.Metadata.WebLinks,
|
||||||
|
ScrobblingService.MangaDexWeblinkWebsite),
|
||||||
VolumeCount = series.Volumes.Count,
|
VolumeCount = series.Volumes.Count,
|
||||||
ChapterCount = series.Volumes.SelectMany(v => v.Chapters).Count(c => !c.IsSpecial),
|
ChapterCount = series.Volumes.SelectMany(v => v.Chapters).Count(c => !c.IsSpecial),
|
||||||
Year = series.Metadata.ReleaseYear
|
Year = series.Metadata.ReleaseYear
|
||||||
|
@ -133,7 +133,7 @@ public static class SmartFilterHelper
|
|||||||
var sortFieldPart = parts.FirstOrDefault(part => part.StartsWith(SortFieldKey));
|
var sortFieldPart = parts.FirstOrDefault(part => part.StartsWith(SortFieldKey));
|
||||||
var isAscendingPart = parts.FirstOrDefault(part => part.StartsWith(IsAscendingKey));
|
var isAscendingPart = parts.FirstOrDefault(part => part.StartsWith(IsAscendingKey));
|
||||||
|
|
||||||
var isAscending = isAscendingPart?.Substring(11).Equals("true", StringComparison.OrdinalIgnoreCase) ?? false;
|
var isAscending = isAscendingPart?.Trim().Replace(IsAscendingKey, string.Empty).Equals("true", StringComparison.OrdinalIgnoreCase) ?? false;
|
||||||
if (sortFieldPart == null)
|
if (sortFieldPart == null)
|
||||||
{
|
{
|
||||||
return new SortOptions();
|
return new SortOptions();
|
||||||
|
@ -25,6 +25,7 @@ public record PlusSeriesDto
|
|||||||
public int? AniListId { get; set; }
|
public int? AniListId { get; set; }
|
||||||
public long? MalId { get; set; }
|
public long? MalId { get; set; }
|
||||||
public string? GoogleBooksId { get; set; }
|
public string? GoogleBooksId { get; set; }
|
||||||
|
public string? MangaDexId { get; set; }
|
||||||
public string SeriesName { get; set; }
|
public string SeriesName { get; set; }
|
||||||
public string? AltSeriesName { get; set; }
|
public string? AltSeriesName { get; set; }
|
||||||
public MediaFormat MediaFormat { get; set; }
|
public MediaFormat MediaFormat { get; set; }
|
||||||
|
@ -67,13 +67,14 @@ public class ScrobblingService : IScrobblingService
|
|||||||
public const string AniListWeblinkWebsite = "https://anilist.co/manga/";
|
public const string AniListWeblinkWebsite = "https://anilist.co/manga/";
|
||||||
public const string MalWeblinkWebsite = "https://myanimelist.net/manga/";
|
public const string MalWeblinkWebsite = "https://myanimelist.net/manga/";
|
||||||
public const string GoogleBooksWeblinkWebsite = "https://books.google.com/books?id=";
|
public const string GoogleBooksWeblinkWebsite = "https://books.google.com/books?id=";
|
||||||
|
public const string MangaDexWeblinkWebsite = "https://mangadex.org/title/";
|
||||||
|
|
||||||
private static readonly IDictionary<string, int> WeblinkExtractionMap = new Dictionary<string, int>()
|
private static readonly IDictionary<string, int> WeblinkExtractionMap = new Dictionary<string, int>()
|
||||||
{
|
{
|
||||||
{AniListWeblinkWebsite, 0},
|
{AniListWeblinkWebsite, 0},
|
||||||
{MalWeblinkWebsite, 0},
|
{MalWeblinkWebsite, 0},
|
||||||
{GoogleBooksWeblinkWebsite, 0},
|
{GoogleBooksWeblinkWebsite, 0},
|
||||||
|
{MangaDexWeblinkWebsite, 0},
|
||||||
};
|
};
|
||||||
|
|
||||||
private const int ScrobbleSleepTime = 700; // We can likely tie this to AniList's 90 rate / min ((60 * 1000) / 90)
|
private const int ScrobbleSleepTime = 700; // We can likely tie this to AniList's 90 rate / min ((60 * 1000) / 90)
|
||||||
@ -829,12 +830,12 @@ public class ScrobblingService : IScrobblingService
|
|||||||
if (!webLink.StartsWith(website)) continue;
|
if (!webLink.StartsWith(website)) continue;
|
||||||
var tokens = webLink.Split(website)[1].Split('/');
|
var tokens = webLink.Split(website)[1].Split('/');
|
||||||
var value = tokens[index];
|
var value = tokens[index];
|
||||||
if (typeof(T) == typeof(int))
|
if (typeof(T) == typeof(int?))
|
||||||
{
|
{
|
||||||
if (int.TryParse(value, out var intValue))
|
if (int.TryParse(value, out var intValue))
|
||||||
return (T)(object)intValue;
|
return (T)(object)intValue;
|
||||||
}
|
}
|
||||||
else if (typeof(T) == typeof(long))
|
else if (typeof(T) == typeof(long?))
|
||||||
{
|
{
|
||||||
if (long.TryParse(value, out var longValue))
|
if (long.TryParse(value, out var longValue))
|
||||||
return (T)(object)longValue;
|
return (T)(object)longValue;
|
||||||
|
@ -34,7 +34,6 @@ public interface ITaskScheduler
|
|||||||
void ScanSiteThemes();
|
void ScanSiteThemes();
|
||||||
void CovertAllCoversToEncoding();
|
void CovertAllCoversToEncoding();
|
||||||
Task CleanupDbEntries();
|
Task CleanupDbEntries();
|
||||||
Task ScrobbleUpdates(int userId);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
public class TaskScheduler : ITaskScheduler
|
public class TaskScheduler : ITaskScheduler
|
||||||
@ -141,7 +140,6 @@ public class TaskScheduler : ITaskScheduler
|
|||||||
}
|
}
|
||||||
|
|
||||||
RecurringJob.AddOrUpdate(CleanupTaskId, () => _cleanupService.Cleanup(), Cron.Daily, RecurringJobOptions);
|
RecurringJob.AddOrUpdate(CleanupTaskId, () => _cleanupService.Cleanup(), Cron.Daily, RecurringJobOptions);
|
||||||
RecurringJob.AddOrUpdate(CleanupDbTaskId, () => _cleanupService.CleanupDbEntries(), Cron.Daily, RecurringJobOptions);
|
|
||||||
RecurringJob.AddOrUpdate(RemoveFromWantToReadTaskId, () => _cleanupService.CleanupWantToRead(), Cron.Daily, RecurringJobOptions);
|
RecurringJob.AddOrUpdate(RemoveFromWantToReadTaskId, () => _cleanupService.CleanupWantToRead(), Cron.Daily, RecurringJobOptions);
|
||||||
RecurringJob.AddOrUpdate(UpdateYearlyStatsTaskId, () => _statisticService.UpdateServerStatistics(), Cron.Monthly, RecurringJobOptions);
|
RecurringJob.AddOrUpdate(UpdateYearlyStatsTaskId, () => _statisticService.UpdateServerStatistics(), Cron.Monthly, RecurringJobOptions);
|
||||||
|
|
||||||
@ -272,16 +270,6 @@ public class TaskScheduler : ITaskScheduler
|
|||||||
await _cleanupService.CleanupDbEntries();
|
await _cleanupService.CleanupDbEntries();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// TODO: Remove this for Release
|
|
||||||
/// </summary>
|
|
||||||
/// <returns></returns>
|
|
||||||
public async Task ScrobbleUpdates(int userId)
|
|
||||||
{
|
|
||||||
if (!await _licenseService.HasActiveLicense()) return;
|
|
||||||
BackgroundJob.Enqueue(() => _scrobblingService.ProcessUpdatesSinceLastSync());
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Attempts to call ScanLibraries on ScannerService, but if another scan task is in progress, will reschedule the invocation for 3 hours in future.
|
/// Attempts to call ScanLibraries on ScannerService, but if another scan task is in progress, will reschedule the invocation for 3 hours in future.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -9,6 +9,7 @@ using API.Entities.Enums;
|
|||||||
using API.Logging;
|
using API.Logging;
|
||||||
using API.SignalR;
|
using API.SignalR;
|
||||||
using Hangfire;
|
using Hangfire;
|
||||||
|
using Kavita.Common.EnvironmentInfo;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
namespace API.Services.Tasks;
|
namespace API.Services.Tasks;
|
||||||
@ -91,7 +92,7 @@ public class BackupService : IBackupService
|
|||||||
await SendProgress(0.1F, "Copying core files");
|
await SendProgress(0.1F, "Copying core files");
|
||||||
|
|
||||||
var dateString = $"{DateTime.UtcNow.ToShortDateString()}_{DateTime.UtcNow.ToLongTimeString()}".Replace("/", "_").Replace(":", "_");
|
var dateString = $"{DateTime.UtcNow.ToShortDateString()}_{DateTime.UtcNow.ToLongTimeString()}".Replace("/", "_").Replace(":", "_");
|
||||||
var zipPath = _directoryService.FileSystem.Path.Join(backupDirectory, $"kavita_backup_{dateString}.zip");
|
var zipPath = _directoryService.FileSystem.Path.Join(backupDirectory, $"kavita_backup_{dateString}_v{BuildInfo.Version}.zip");
|
||||||
|
|
||||||
if (File.Exists(zipPath))
|
if (File.Exists(zipPath))
|
||||||
{
|
{
|
||||||
|
@ -92,6 +92,8 @@ public class CleanupService : ICleanupService
|
|||||||
await CleanupLogs();
|
await CleanupLogs();
|
||||||
await SendProgress(0.9F, "Cleaning progress events that exceed 100%");
|
await SendProgress(0.9F, "Cleaning progress events that exceed 100%");
|
||||||
await EnsureChapterProgressIsCapped();
|
await EnsureChapterProgressIsCapped();
|
||||||
|
await SendProgress(0.95F, "Cleaning abandoned database rows");
|
||||||
|
await CleanupDbEntries();
|
||||||
await SendProgress(1F, "Cleanup finished");
|
await SendProgress(1F, "Cleanup finished");
|
||||||
_logger.LogInformation("Cleanup finished");
|
_logger.LogInformation("Cleanup finished");
|
||||||
}
|
}
|
||||||
|
@ -109,9 +109,9 @@ public static class Parser
|
|||||||
new Regex(
|
new Regex(
|
||||||
@"(?<Series>.*)(\b|_)v(?<Volume>\d+-?\d+)( |_)",
|
@"(?<Series>.*)(\b|_)v(?<Volume>\d+-?\d+)( |_)",
|
||||||
MatchOptions, RegexTimeout),
|
MatchOptions, RegexTimeout),
|
||||||
// NEEDLESS_Vol.4_-Simeon_6_v2[SugoiSugoi].rar
|
// Nagasarete Airantou - Vol. 30 Ch. 187.5 - Vol.31 Omake
|
||||||
new Regex(
|
new Regex(
|
||||||
@"(?<Series>.*)(\b|_)(?!\[)(vol\.?)(?<Volume>\d+(-\d+)?)(?!\])",
|
@"^(?<Series>.+?)(\s*Chapter\s*\d+)?(\s|_|\-\s)+(Vol(ume)?\.?(\s|_)?)(?<Volume>\d+(\.\d+)?)(.+?|$)",
|
||||||
MatchOptions, RegexTimeout),
|
MatchOptions, RegexTimeout),
|
||||||
// Historys Strongest Disciple Kenichi_v11_c90-98.zip or Dance in the Vampire Bund v16-17
|
// Historys Strongest Disciple Kenichi_v11_c90-98.zip or Dance in the Vampire Bund v16-17
|
||||||
new Regex(
|
new Regex(
|
||||||
@ -137,6 +137,7 @@ public static class Parser
|
|||||||
new Regex(
|
new Regex(
|
||||||
@"(vol_)(?<Volume>\d+(\.\d)?)",
|
@"(vol_)(?<Volume>\d+(\.\d)?)",
|
||||||
MatchOptions, RegexTimeout),
|
MatchOptions, RegexTimeout),
|
||||||
|
|
||||||
// Chinese Volume: 第n卷 -> Volume n, 第n册 -> Volume n, 幽游白书完全版 第03卷 天下 or 阿衰online 第1册
|
// Chinese Volume: 第n卷 -> Volume n, 第n册 -> Volume n, 幽游白书完全版 第03卷 天下 or 阿衰online 第1册
|
||||||
new Regex(
|
new Regex(
|
||||||
@"第(?<Volume>\d+)(卷|册)",
|
@"第(?<Volume>\d+)(卷|册)",
|
||||||
@ -197,16 +198,17 @@ public static class Parser
|
|||||||
new Regex(
|
new Regex(
|
||||||
@"(?<Series>.*)(\b|_|-|\s)(?:sp)\d",
|
@"(?<Series>.*)(\b|_|-|\s)(?:sp)\d",
|
||||||
MatchOptions, RegexTimeout),
|
MatchOptions, RegexTimeout),
|
||||||
// [SugoiSugoi]_NEEDLESS_Vol.2_-_Disk_The_Informant_5_[ENG].rar, Yuusha Ga Shinda! - Vol.tbd Chapter 27.001 V2 Infection ①.cbz
|
|
||||||
new Regex(
|
|
||||||
@"^(?<Series>.*)( |_)Vol\.?(\d+|tbd)",
|
|
||||||
MatchOptions, RegexTimeout),
|
|
||||||
// Mad Chimera World - Volume 005 - Chapter 026.cbz (couldn't figure out how to get Volume negative lookaround working on below regex),
|
// Mad Chimera World - Volume 005 - Chapter 026.cbz (couldn't figure out how to get Volume negative lookaround working on below regex),
|
||||||
// The Duke of Death and His Black Maid - Vol. 04 Ch. 054.5 - V4 Omake
|
// The Duke of Death and His Black Maid - Vol. 04 Ch. 054.5 - V4 Omake
|
||||||
new Regex(
|
new Regex(
|
||||||
@"(?<Series>.+?)(\s|_|-)+(?:Vol(ume|\.)?(\s|_|-)+\d+)(\s|_|-)+(?:(Ch|Chapter|Ch)\.?)(\s|_|-)+(?<Chapter>\d+)",
|
@"(?<Series>.+?)(\s|_|-)+(?:Vol(ume|\.)?(\s|_|-)+\d+)(\s|_|-)+(?:(Ch|Chapter|Ch)\.?)(\s|_|-)+(?<Chapter>\d+)",
|
||||||
MatchOptions,
|
MatchOptions,
|
||||||
RegexTimeout),
|
RegexTimeout),
|
||||||
|
// [SugoiSugoi]_NEEDLESS_Vol.2_-_Disk_The_Informant_5_[ENG].rar, Yuusha Ga Shinda! - Vol.tbd Chapter 27.001 V2 Infection ①.cbz,
|
||||||
|
// Nagasarete Airantou - Vol. 30 Ch. 187.5 - Vol.30 Omake
|
||||||
|
new Regex(
|
||||||
|
@"^(?<Series>.+?)(\s*Chapter\s*\d+)?(\s|_|\-\s)+Vol(ume)?\.?(\d+|tbd|\s\d).+?",
|
||||||
|
MatchOptions, RegexTimeout),
|
||||||
// Ichiban_Ushiro_no_Daimaou_v04_ch34_[VISCANS].zip, VanDread-v01-c01.zip
|
// Ichiban_Ushiro_no_Daimaou_v04_ch34_[VISCANS].zip, VanDread-v01-c01.zip
|
||||||
new Regex(
|
new Regex(
|
||||||
@"(?<Series>.*)(\b|_)v(?<Volume>\d+-?\d*)(\s|_|-)",
|
@"(?<Series>.*)(\b|_)v(?<Volume>\d+-?\d*)(\s|_|-)",
|
||||||
@ -233,6 +235,7 @@ public static class Parser
|
|||||||
@"(?<Series>.+?):?(\s|\b|_|-)Chapter(\s|\b|_|-)\d+(\s|\b|_|-)(vol)(ume)",
|
@"(?<Series>.+?):?(\s|\b|_|-)Chapter(\s|\b|_|-)\d+(\s|\b|_|-)(vol)(ume)",
|
||||||
MatchOptions,
|
MatchOptions,
|
||||||
RegexTimeout),
|
RegexTimeout),
|
||||||
|
|
||||||
// [xPearse] Kyochuu Rettou Volume 1 [English] [Manga] [Volume Scans]
|
// [xPearse] Kyochuu Rettou Volume 1 [English] [Manga] [Volume Scans]
|
||||||
new Regex(
|
new Regex(
|
||||||
@"(?<Series>.+?):? (\b|_|-)(vol)(ume)",
|
@"(?<Series>.+?):? (\b|_|-)(vol)(ume)",
|
||||||
|
Binary file not shown.
@ -55,7 +55,7 @@ install methods and platforms.
|
|||||||
**Note: Kavita is under heavy development and is being updated all the time, so the tag for bleeding edge builds is `:nightly`. The `:latest` tag will be the latest stable release.**
|
**Note: Kavita is under heavy development and is being updated all the time, so the tag for bleeding edge builds is `:nightly`. The `:latest` tag will be the latest stable release.**
|
||||||
|
|
||||||
## Feature Requests
|
## Feature Requests
|
||||||
Got a great idea? Throw it up on our [Feature Request site](https://feats.kavitareader.com/) or vote on another idea. Please check the [Project Board](https://github.com/Kareadita/Kavita/projects) first for a list of planned features before you submit an idea.
|
Got a great idea? Throw it up on our [Feature Request site](https://feats.kavitareader.com/) or vote on another idea. Please check the [Project Board](https://github.com/Kareadita/Kavita/projects?type=classic) first for a list of planned features before you submit an idea.
|
||||||
|
|
||||||
## Notice
|
## Notice
|
||||||
Kavita is being actively developed and should be considered beta software until the 1.0 release.
|
Kavita is being actively developed and should be considered beta software until the 1.0 release.
|
||||||
|
@ -15,7 +15,8 @@ type UtcToLocalTimeFormat = 'full' | 'short' | 'shortDate' | 'shortTime';
|
|||||||
})
|
})
|
||||||
export class UtcToLocalTimePipe implements PipeTransform {
|
export class UtcToLocalTimePipe implements PipeTransform {
|
||||||
|
|
||||||
transform(utcDate: string, format: UtcToLocalTimeFormat = 'short'): string {
|
transform(utcDate: string | undefined | null, format: UtcToLocalTimeFormat = 'short'): string {
|
||||||
|
if (utcDate === undefined || utcDate === null) return '';
|
||||||
const browserLanguage = navigator.language;
|
const browserLanguage = navigator.language;
|
||||||
const dateTime = DateTime.fromISO(utcDate, { zone: 'utc' }).toLocal().setLocale(browserLanguage);
|
const dateTime = DateTime.fromISO(utcDate, { zone: 'utc' }).toLocal().setLocale(browserLanguage);
|
||||||
|
|
||||||
|
@ -58,7 +58,9 @@
|
|||||||
<td>
|
<td>
|
||||||
{{task.title | titlecase}}
|
{{task.title | titlecase}}
|
||||||
</td>
|
</td>
|
||||||
<td>{{task.lastExecutionUtc | utcToLocalTime | defaultValue }}</td>
|
<td>
|
||||||
|
{{task.lastExecutionUtc | utcToLocalTime | defaultValue }}
|
||||||
|
</td>
|
||||||
<td>{{task.cron}}</td>
|
<td>{{task.cron}}</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
|
@ -36,7 +36,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<app-loading [loading]="isLoading || !(currentImage$ | async)?.complete" [absolute]="true"></app-loading>
|
<app-loading [loading]="isLoading || (!(currentImage$ | async)?.complete && this.readerMode !== ReaderMode.Webtoon)" [absolute]="true"></app-loading>
|
||||||
<div class="reading-area"
|
<div class="reading-area"
|
||||||
ngSwipe (swipeEnd)="onSwipeEnd($event)" (swipeMove)="onSwipeMove($event)"
|
ngSwipe (swipeEnd)="onSwipeEnd($event)" (swipeMove)="onSwipeMove($event)"
|
||||||
[ngStyle]="{'background-color': backgroundColor, 'height': readerMode === ReaderMode.Webtoon ? 'inherit' : 'calc(var(--vh)*100)'}" #readingArea>
|
[ngStyle]="{'background-color': backgroundColor, 'height': readerMode === ReaderMode.Webtoon ? 'inherit' : 'calc(var(--vh)*100)'}" #readingArea>
|
||||||
|
@ -51,7 +51,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<app-image height="100%" maxHeight="400px" objectFit="contain" background="none" [imageUrl]="seriesImage"></app-image>
|
<app-image height="100%" maxHeight="400px" objectFit="contain" background="none" [imageUrl]="seriesImage"></app-image>
|
||||||
<ng-container *ngIf="series.pagesRead < series.pages && hasReadingProgress && currentlyReadingChapter && !currentlyReadingChapter.isSpecial">
|
<ng-container *ngIf="series.pagesRead < series.pages && hasReadingProgress && currentlyReadingChapter && !currentlyReadingChapter.isSpecial">
|
||||||
<div class="progress-banner" ngbTooltip="{{(series.pagesRead / series.pages) | number:'1.0-1'}}% Read">
|
<div class="progress-banner" ngbTooltip="{{(series.pagesRead / series.pages) * 100 | number:'1.0-1'}}% Read">
|
||||||
<ngb-progressbar type="primary" height="5px" [value]="series.pagesRead" [max]="series.pages"></ngb-progressbar>
|
<ngb-progressbar type="primary" height="5px" [value]="series.pagesRead" [max]="series.pages"></ngb-progressbar>
|
||||||
</div>
|
</div>
|
||||||
<div class="under-image">
|
<div class="under-image">
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
"name": "GPL-3.0",
|
"name": "GPL-3.0",
|
||||||
"url": "https://github.com/Kareadita/Kavita/blob/develop/LICENSE"
|
"url": "https://github.com/Kareadita/Kavita/blob/develop/LICENSE"
|
||||||
},
|
},
|
||||||
"version": "0.7.11.0"
|
"version": "0.7.11.1"
|
||||||
},
|
},
|
||||||
"servers": [
|
"servers": [
|
||||||
{
|
{
|
||||||
|
Loading…
x
Reference in New Issue
Block a user