.NET 8 Update (#2496)

This commit is contained in:
Joe Milazzo 2023-12-13 19:16:54 -06:00 committed by GitHub
parent 6d4d2d4a7f
commit b838fd53e5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
75 changed files with 590 additions and 405 deletions

View File

@ -17,7 +17,7 @@ jobs:
- name: Setup .NET Core
uses: actions/setup-dotnet@v3
with:
dotnet-version: 7.0.x
dotnet-version: 8.0.x
- name: Install Swashbuckle CLI
shell: powershell

View File

@ -33,7 +33,7 @@ jobs:
- name: Setup .NET Core
uses: actions/setup-dotnet@v3
with:
dotnet-version: 7.0.x
dotnet-version: 8.0.x
- name: Bump versions
uses: SiqiLu/dotnet-bump-version@2.0.0
@ -98,7 +98,7 @@ jobs:
- name: Compile dotnet app
uses: actions/setup-dotnet@v3
with:
dotnet-version: 7.0.x
dotnet-version: 8.0.x
- name: Install Swashbuckle CLI
run: dotnet tool install -g --version 6.5.0 Swashbuckle.AspNetCore.Cli

View File

@ -46,7 +46,7 @@ jobs:
- name: Setup .NET Core
uses: actions/setup-dotnet@v3
with:
dotnet-version: 7.0.x
dotnet-version: 8.0.x
- name: Bump versions
uses: majora2007/dotnet-bump-version@v0.0.10
@ -131,7 +131,7 @@ jobs:
- name: Compile dotnet app
uses: actions/setup-dotnet@v3
with:
dotnet-version: 7.0.x
dotnet-version: 8.0.x
- name: Install Swashbuckle CLI
run: dotnet tool install -g --version 6.5.0 Swashbuckle.AspNetCore.Cli

View File

@ -119,7 +119,7 @@ jobs:
- name: Compile dotnet app
uses: actions/setup-dotnet@v3
with:
dotnet-version: 7.0.x
dotnet-version: 8.0.x
- name: Install Swashbuckle CLI
run: dotnet tool install -g --version 6.5.0 Swashbuckle.AspNetCore.Cli

View File

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<TargetFramework>net8.0</TargetFramework>
<OutputType>Exe</OutputType>
</PropertyGroup>
@ -10,8 +10,8 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="BenchmarkDotNet" Version="0.13.10" />
<PackageReference Include="BenchmarkDotNet.Annotations" Version="0.13.10" />
<PackageReference Include="BenchmarkDotNet" Version="0.13.11" />
<PackageReference Include="BenchmarkDotNet.Annotations" Version="0.13.11" />
<PackageReference Include="NSubstitute" Version="5.1.0" />
</ItemGroup>

View File

@ -1,18 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<TargetFramework>net8.0</TargetFramework>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="7.0.13" />
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="8.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
<PackageReference Include="NSubstitute" Version="5.1.0" />
<PackageReference Include="System.IO.Abstractions.TestingHelpers" Version="19.2.69" />
<PackageReference Include="TestableIO.System.IO.Abstractions.Wrappers" Version="19.2.69" />
<PackageReference Include="xunit" Version="2.6.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.3">
<PackageReference Include="System.IO.Abstractions.TestingHelpers" Version="20.0.4" />
<PackageReference Include="TestableIO.System.IO.Abstractions.Wrappers" Version="20.0.4" />
<PackageReference Include="xunit" Version="2.6.3" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.5">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>

View File

@ -2,7 +2,7 @@
<PropertyGroup>
<AnalysisMode>Default</AnalysisMode>
<TargetFramework>net7.0</TargetFramework>
<TargetFramework>net8.0</TargetFramework>
<EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild>
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
<TieredPGO>true</TieredPGO>
@ -53,7 +53,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="7.0.13">
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
@ -72,38 +72,37 @@
<PackageReference Include="MarkdownDeep.NET.Core" Version="1.5.0.4" />
<PackageReference Include="Hangfire.AspNetCore" Version="1.8.6" />
<PackageReference Include="Microsoft.AspNetCore.SignalR" Version="1.1.0" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="7.0.13" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.OpenIdConnect" Version="7.0.13" />
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="7.0.13" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="7.0.13" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="7.0.13" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="7.0.0" />
<PackageReference Include="Microsoft.IO.RecyclableMemoryStream" Version="2.3.2" />
<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.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.3.1" />
<PackageReference Include="NetVips.Native" Version="8.14.5" />
<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="Serilog" Version="3.1.0" />
<PackageReference Include="Serilog.AspNetCore" Version="7.0.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" />
<PackageReference Include="Serilog.Extensions.Hosting" Version="7.0.0" />
<PackageReference Include="Serilog.Settings.Configuration" Version="7.0.1" />
<PackageReference Include="Serilog.Extensions.Hosting" Version="8.0.0" />
<PackageReference Include="Serilog.Settings.Configuration" Version="8.0.0" />
<PackageReference Include="Serilog.Sinks.AspNetCore.SignalR" Version="0.4.0" />
<PackageReference Include="Serilog.Sinks.Console" Version="5.0.0" />
<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.1" />
<PackageReference Include="SixLabors.ImageSharp" Version="3.0.2" />
<PackageReference Include="SonarAnalyzer.CSharp" Version="9.12.0.78982">
<PackageReference Include="SharpCompress" Version="0.34.2" />
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.1" />
<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="System.IO.Abstractions" Version="19.2.69" />
<PackageReference Include="System.Drawing.Common" Version="7.0.0" />
<PackageReference Include="System.IO.Abstractions" Version="20.0.4" />
<PackageReference Include="System.Drawing.Common" Version="8.0.0" />
<PackageReference Include="VersOne.Epub" Version="3.3.1" />
</ItemGroup>

View File

@ -40,15 +40,13 @@ public class LibraryController : BaseApiController
private readonly IEventHub _eventHub;
private readonly ILibraryWatcher _libraryWatcher;
private readonly ILocalizationService _localizationService;
private readonly IStreamService _streamService;
private readonly IEasyCachingProvider _libraryCacheProvider;
private const string CacheKey = "library_";
public LibraryController(IDirectoryService directoryService,
ILogger<LibraryController> logger, IMapper mapper, ITaskScheduler taskScheduler,
IUnitOfWork unitOfWork, IEventHub eventHub, ILibraryWatcher libraryWatcher,
IEasyCachingProviderFactory cachingProviderFactory, ILocalizationService localizationService,
IStreamService streamService)
IEasyCachingProviderFactory cachingProviderFactory, ILocalizationService localizationService)
{
_directoryService = directoryService;
_logger = logger;
@ -58,7 +56,6 @@ public class LibraryController : BaseApiController
_eventHub = eventHub;
_libraryWatcher = libraryWatcher;
_localizationService = localizationService;
_streamService = streamService;
_libraryCacheProvider = cachingProviderFactory.GetCachingProvider(EasyCacheProfiles.Library);
}
@ -157,7 +154,7 @@ public class LibraryController : BaseApiController
}));
}
if (!Directory.Exists(path)) return Ok(_directoryService.ListDirectory(Path.GetDirectoryName(path)));
if (!Directory.Exists(path)) return Ok(_directoryService.ListDirectory(Path.GetDirectoryName(path)!));
return Ok(_directoryService.ListDirectory(path));
}

View File

@ -99,7 +99,7 @@ public class OpdsController : BaseApiController
if (!(await _unitOfWork.SettingsRepository.GetSettingsDtoAsync()).EnableOpds)
return BadRequest(await _localizationService.Translate(userId, "opds-disabled"));
var (baseUrl, prefix) = await GetPrefix();
var (_, prefix) = await GetPrefix();
var feed = CreateFeed("Kavita", string.Empty, apiKey, prefix);
SetFeedId(feed, "root");
@ -141,10 +141,37 @@ public class OpdsController : BaseApiController
});
break;
case DashboardStreamType.RecentlyUpdated:
// TODO: See if we can implement this and use (count) on series name for number of updates
feed.Entries.Add(new FeedEntry()
{
Id = "recentlyUpdated",
Title = await _localizationService.Translate(userId, "recently-updated"),
Content = new FeedEntryContent()
{
Text = await _localizationService.Translate(userId, "browse-recently-updated")
},
Links = new List<FeedLink>()
{
CreateLink(FeedLinkRelation.SubSection, FeedLinkType.AtomNavigation, $"{prefix}{apiKey}/recently-updated"),
}
});
break;
case DashboardStreamType.MoreInGenre:
// TODO: See if we can implement this
var randomGenre = await _unitOfWork.GenreRepository.GetRandomGenre();
if (randomGenre == null) break;
feed.Entries.Add(new FeedEntry()
{
Id = "moreInGenre",
Title = await _localizationService.Translate(userId, "more-in-genre", randomGenre.Title),
Content = new FeedEntryContent()
{
Text = await _localizationService.Translate(userId, "browse-more-in-genre", randomGenre.Title)
},
Links = new List<FeedLink>()
{
CreateLink(FeedLinkRelation.SubSection, FeedLinkType.AtomNavigation, $"{prefix}{apiKey}/more-in-genre?genreId={randomGenre.Id}"),
}
});
break;
case DashboardStreamType.SmartFilter:
@ -632,6 +659,61 @@ public class OpdsController : BaseApiController
return CreateXmlResult(SerializeXml(feed));
}
[HttpGet("{apiKey}/more-in-genre")]
[Produces("application/xml")]
public async Task<IActionResult> GetMoreInGenre(string apiKey, [FromQuery] int genreId, [FromQuery] int pageNumber = 1)
{
var userId = await GetUser(apiKey);
if (!(await _unitOfWork.SettingsRepository.GetSettingsDtoAsync()).EnableOpds)
return BadRequest(await _localizationService.Translate(userId, "opds-disabled"));
var (baseUrl, prefix) = await GetPrefix();
var genre = await _unitOfWork.GenreRepository.GetGenreById(genreId);
var seriesDtos = await _unitOfWork.SeriesRepository.GetMoreIn(userId, 0, genreId, GetUserParams(pageNumber));
var seriesMetadatas = await _unitOfWork.SeriesRepository.GetSeriesMetadataForIds(seriesDtos.Select(s => s.Id));
var feed = CreateFeed(await _localizationService.Translate(userId, "more-in-genre", genre.Title), $"{prefix}{apiKey}/more-in-genre", apiKey, prefix);
SetFeedId(feed, "more-in-genre");
AddPagination(feed, seriesDtos, $"{prefix}{apiKey}/more-in-genre");
foreach (var seriesDto in seriesDtos)
{
feed.Entries.Add(CreateSeries(seriesDto, seriesMetadatas.First(s => s.SeriesId == seriesDto.Id), apiKey, prefix, baseUrl));
}
return CreateXmlResult(SerializeXml(feed));
}
[HttpGet("{apiKey}/recently-updated")]
[Produces("application/xml")]
public async Task<IActionResult> GetRecentlyUpdated(string apiKey, [FromQuery] int pageNumber = 1)
{
var userId = await GetUser(apiKey);
if (!(await _unitOfWork.SettingsRepository.GetSettingsDtoAsync()).EnableOpds)
return BadRequest(await _localizationService.Translate(userId, "opds-disabled"));
var (baseUrl, prefix) = await GetPrefix();
var seriesDtos = (await _unitOfWork.SeriesRepository.GetRecentlyUpdatedSeries(userId, PageSize)).ToList();
var seriesMetadatas = await _unitOfWork.SeriesRepository.GetSeriesMetadataForIds(seriesDtos.Select(s => s.SeriesId));
var feed = CreateFeed(await _localizationService.Translate(userId, "recently-updated"), $"{prefix}{apiKey}/recently-updated", apiKey, prefix);
SetFeedId(feed, "recently-updated");
//AddPagination(feed, seriesDtos, $"{prefix}{apiKey}/recently-updated");
foreach (var groupedSeries in seriesDtos)
{
var seriesDto = new SeriesDto()
{
Name = $"{groupedSeries.SeriesName} ({groupedSeries.Count})",
Id = groupedSeries.SeriesId,
Format = groupedSeries.Format,
LibraryId = groupedSeries.LibraryId,
};
var metadata = seriesMetadatas.First(s => s.SeriesId == seriesDto.Id);
feed.Entries.Add(CreateSeries(seriesDto, metadata, apiKey, prefix, baseUrl));
}
return CreateXmlResult(SerializeXml(feed));
}
[HttpGet("{apiKey}/on-deck")]
[Produces("application/xml")]
public async Task<IActionResult> GetOnDeck(string apiKey, [FromQuery] int pageNumber = 1)
@ -1161,7 +1243,7 @@ public class OpdsController : BaseApiController
var userId = await GetUser(apiKey);
var progress = await _unitOfWork.AppUserProgressRepository.GetUserProgressDtoAsync(chapterId, userId);
// TODO: Type could be wrong
// NOTE: Type could be wrong, there is nothing I can do in the spec
var link = CreateLink(FeedLinkRelation.Stream, "image/jpeg",
$"{prefix}{apiKey}/image?libraryId={libraryId}&seriesId={seriesId}&volumeId={volumeId}&chapterId={chapterId}&pageNumber=" + "{pageNumber}");
link.TotalPages = mangaFile.Pages;

View File

@ -19,13 +19,11 @@ public class StreamController : BaseApiController
{
private readonly IStreamService _streamService;
private readonly IUnitOfWork _unitOfWork;
private readonly ILogger<StreamController> _logger;
public StreamController(IStreamService streamService, IUnitOfWork unitOfWork, ILogger<StreamController> logger)
public StreamController(IStreamService streamService, IUnitOfWork unitOfWork)
{
_streamService = streamService;
_unitOfWork = unitOfWork;
_logger = logger;
}
/// <summary>

View File

@ -53,7 +53,7 @@ public class ThemeController : BaseApiController
{
await _themeService.UpdateDefault(dto.ThemeId);
}
catch (KavitaException ex)
catch (KavitaException)
{
return BadRequest(await _localizationService.Translate(User.GetUserId(), "theme-doesnt-exist"));
}

View File

@ -1,4 +1,5 @@
namespace API.DTOs.Account;
#nullable enable
public class LoginDto
{

View File

@ -3,6 +3,7 @@ using API.Entities;
using API.Entities.Enums;
namespace API.DTOs.Filtering;
#nullable enable
public class FilterDto
{

View File

@ -1,4 +1,6 @@
namespace API.DTOs.Filtering;
#nullable enable
/// <summary>
/// Represents a range between two int/float/double
/// </summary>

View File

@ -1,9 +1,7 @@
using System.Collections.Generic;
namespace API.DTOs.Filtering.v2;
#nullable enable
/// <summary>
/// Metadata filtering for v2 API only

View File

@ -4,6 +4,7 @@ using System.Collections.Generic;
using API.Entities.Enums;
namespace API.DTOs;
#nullable enable
public class LibraryDto
{

View File

@ -3,6 +3,7 @@ using System.Collections.Generic;
using API.DTOs.Account;
namespace API.DTOs;
#nullable enable
/// <summary>
/// Represents a member of a Kavita server.

View File

@ -2,6 +2,7 @@
using API.Entities.Enums;
namespace API.DTOs.Metadata;
#nullable enable
/// <summary>
/// Exclusively metadata about a given chapter

View File

@ -1,6 +1,7 @@
using API.Services.Plus;
namespace API.DTOs;
#nullable enable
public class RatingDto
{

View File

@ -1,6 +1,7 @@
using System.ComponentModel.DataAnnotations;
namespace API.DTOs.Reader;
#nullable enable
public class BookmarkDto
{

View File

@ -2,6 +2,7 @@
using API.Entities.Enums;
namespace API.DTOs.Reader;
#nullable enable
public class BookmarkInfoDto
{

View File

@ -2,6 +2,7 @@
using API.Entities.Enums;
namespace API.DTOs.Reader;
#nullable enable
/// <summary>
/// Information about the Chapter for the Reader to render

View File

@ -1,5 +1,7 @@
namespace API.DTOs.Reader;
#nullable enable
public class PersonalToCDto
{
public required int ChapterId { get; set; }

View File

@ -2,6 +2,7 @@
using API.DTOs.Scrobbling;
namespace API.DTOs.Recommendation;
#nullable enable
public class ExternalSeriesDetailDto
{

View File

@ -1,4 +1,5 @@
namespace API.DTOs.Recommendation;
#nullable enable
public class SeriesStaffDto
{

View File

@ -1,4 +1,5 @@
namespace API.DTOs.Scrobbling;
#nullable enable
/// <summary>
/// Response from Kavita+ Scrobble API

View File

@ -1,6 +1,7 @@
using System.ComponentModel.DataAnnotations;
namespace API.DTOs.SeriesDetail;
#nullable enable
public class UpdateUserReviewDto
{

View File

@ -1,6 +1,7 @@
using API.Services.Plus;
namespace API.DTOs.SeriesDetail;
#nullable enable
/// <summary>
/// Represents a User Review for a given Series

View File

@ -2,6 +2,7 @@
using API.Entities.Enums;
namespace API.DTOs.Statistics;
#nullable enable
public class FileExtensionDto
{

View File

@ -1,4 +1,5 @@
namespace API.DTOs.Statistics;
#nullable enable
public class TopReadDto
{

View File

@ -3,6 +3,7 @@ using System.Collections.Generic;
using API.Entities.Enums;
namespace API.DTOs.Stats;
#nullable enable
/// <summary>
/// Represents information about a Kavita Installation

View File

@ -1,6 +1,7 @@
using System.Collections.Generic;
namespace API.DTOs;
#nullable enable
public class UpdateRbsDto
{

View File

@ -2,6 +2,7 @@
using API.DTOs.Account;
namespace API.DTOs;
#nullable enable
public class UserDto
{

View File

@ -9,6 +9,7 @@ using Kavita.Common.Extensions;
using Nager.ArticleNumber;
namespace API.Data.Metadata;
#nullable enable
/// <summary>
/// A representation of a ComicInfo.xml file

View File

@ -1,4 +1,5 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using API.DTOs.Metadata;
@ -22,6 +23,8 @@ public interface IGenreRepository
Task RemoveAllGenreNoLongerAssociated(bool removeExternal = false);
Task<IList<GenreTagDto>> GetAllGenreDtosForLibrariesAsync(IList<int> libraryIds, int userId);
Task<int> GetCountAsync();
Task<GenreTagDto> GetRandomGenre();
Task<GenreTagDto> GetGenreById(int id);
}
public class GenreRepository : IGenreRepository
@ -92,6 +95,27 @@ public class GenreRepository : IGenreRepository
return await _context.Genre.CountAsync();
}
public async Task<GenreTagDto> GetRandomGenre()
{
var genreCount = await GetCountAsync();
if (genreCount == 0) return null;
var randomIndex = new Random().Next(0, genreCount);
return await _context.Genre
.Skip(randomIndex)
.Take(1)
.ProjectTo<GenreTagDto>(_mapper.ConfigurationProvider)
.FirstOrDefaultAsync();
}
public async Task<GenreTagDto> GetGenreById(int id)
{
return await _context.Genre
.Where(g => g.Id == id)
.ProjectTo<GenreTagDto>(_mapper.ConfigurationProvider)
.FirstOrDefaultAsync();
}
public async Task<IList<Genre>> GetAllGenresAsync()
{
return await _context.Genre.ToListAsync();

View File

@ -17,7 +17,7 @@ public static class FileTypeGroupExtensions
case FileTypeGroup.Pdf:
return Parser.PdfFileExtension;
case FileTypeGroup.Images:
return Parser.ImageFileExtensions;;
return Parser.ImageFileExtensions;
default:
throw new ArgumentOutOfRangeException(nameof(fileTypeGroup), fileTypeGroup, null);
}

View File

@ -21,8 +21,8 @@ public static class HttpExtensions
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
};
response.Headers.Add("Pagination", JsonSerializer.Serialize(paginationHeader, options));
response.Headers.Add("Access-Control-Expose-Headers", "Pagination");
response.Headers.Append("Pagination", JsonSerializer.Serialize(paginationHeader, options));
response.Headers.Append("Access-Control-Expose-Headers", "Pagination");
}
/// <summary>
@ -33,7 +33,7 @@ public static class HttpExtensions
public static void AddCacheHeader(this HttpResponse response, byte[] content)
{
if (content is not {Length: > 0}) return;
response.Headers.Add(HeaderNames.ETag, string.Concat(SHA256.HashData(content).Select(x => x.ToString("X2"))));
response.Headers.Append(HeaderNames.ETag, string.Concat(SHA256.HashData(content).Select(x => x.ToString("X2"))));
response.Headers.CacheControl = $"private,max-age=100";
}
@ -47,7 +47,7 @@ public static class HttpExtensions
{
if (filename is not {Length: > 0}) return;
var hashContent = filename + File.GetLastWriteTimeUtc(filename);
response.Headers.Add("ETag", string.Concat(SHA256.HashData(Encoding.UTF8.GetBytes(hashContent)).Select(x => x.ToString("X2"))));
response.Headers.Append("ETag", string.Concat(SHA256.HashData(Encoding.UTF8.GetBytes(hashContent)).Select(x => x.ToString("X2"))));
if (maxAge != 10)
{
response.Headers.CacheControl = $"max-age={maxAge}";

View File

@ -22,7 +22,7 @@ public static class SeriesFilter
switch (comparison)
{
case FilterComparison.Equal:
return queryable.Where(s => s.Metadata.Language.Equals(languages.First()));
return queryable.Where(s => s.Metadata.Language.Equals(languages[0]));
case FilterComparison.Contains:
return queryable.Where(s => languages.Contains(s.Metadata.Language));
case FilterComparison.MustContains:
@ -30,9 +30,9 @@ public static class SeriesFilter
case FilterComparison.NotContains:
return queryable.Where(s => !languages.Contains(s.Metadata.Language));
case FilterComparison.NotEqual:
return queryable.Where(s => !s.Metadata.Language.Equals(languages.First()));
return queryable.Where(s => !s.Metadata.Language.Equals(languages[0]));
case FilterComparison.Matches:
return queryable.Where(s => EF.Functions.Like(s.Metadata.Language, $"{languages.First()}%"));
return queryable.Where(s => EF.Functions.Like(s.Metadata.Language, $"{languages[0]}%"));
case FilterComparison.GreaterThan:
case FilterComparison.GreaterThanEqual:
case FilterComparison.LessThan:

View File

@ -6,8 +6,9 @@ namespace API.Extensions;
public static class StringExtensions
{
private static readonly Regex SentenceCaseRegex = new Regex(@"(^[a-z])|\.\s+(.)",
RegexOptions.ExplicitCapture | RegexOptions.Compiled, Services.Tasks.Scanner.Parser.Parser.RegexTimeout);
private static readonly Regex SentenceCaseRegex = new(@"(^[a-z])|\.\s+(.)",
RegexOptions.ExplicitCapture | RegexOptions.Compiled,
Services.Tasks.Scanner.Parser.Parser.RegexTimeout);
public static string SentenceCase(this string value)
{
@ -21,17 +22,16 @@ public static class StringExtensions
/// <returns></returns>
public static string ToNormalized(this string? value)
{
if (string.IsNullOrEmpty(value)) return string.Empty;
return Services.Tasks.Scanner.Parser.Parser.Normalize(value);
return string.IsNullOrEmpty(value) ? string.Empty : Services.Tasks.Scanner.Parser.Parser.Normalize(value);
}
public static float AsFloat(this string value)
public static float AsFloat(this string? value, float defaultValue = 0.0f)
{
return float.Parse(value, CultureInfo.InvariantCulture);
return string.IsNullOrEmpty(value) ? defaultValue : float.Parse(value, CultureInfo.InvariantCulture);
}
public static double AsDouble(this string value)
public static double AsDouble(this string? value, double defaultValue = 0.0f)
{
return double.Parse(value, CultureInfo.InvariantCulture);
return string.IsNullOrEmpty(value) ? defaultValue : double.Parse(value, CultureInfo.InvariantCulture);
}
}

View File

@ -5,6 +5,7 @@ using API.Entities;
using Kavita.Common;
namespace API.Helpers.Builders;
#nullable enable
public class AppUserBuilder : IEntityBuilder<AppUser>
{

View File

@ -6,6 +6,7 @@ using API.Entities.Enums;
using API.Services.Tasks.Scanner.Parser;
namespace API.Helpers.Builders;
#nullable enable
public class ChapterBuilder : IEntityBuilder<Chapter>
{

View File

@ -1,6 +1,7 @@
using API.Entities.Scrobble;
namespace API.Helpers.Builders;
#nullable enable
public class ScrobbleHoldBuilder : IEntityBuilder<ScrobbleHold>
{

View File

@ -40,7 +40,7 @@ public static class ParserInfoHelpers
}
}
if (series.Format == MangaFormat.Unknown && format != MangaFormat.Unknown)
if (series.Format == MangaFormat.Unknown)
{
return true;
}

View File

@ -130,8 +130,9 @@ public static class SmartFilterHelper
private static SortOptions DecodeSortOptions(string encodedSortOptions)
{
var parts = Uri.UnescapeDataString(encodedSortOptions).Split(InnerStatementSeparator);
var sortFieldPart = parts.FirstOrDefault(part => part.StartsWith(SortFieldKey));
var isAscendingPart = parts.FirstOrDefault(part => part.StartsWith(IsAscendingKey));
var sortFieldPart = Array.Find(parts, part => part.StartsWith(SortFieldKey));
var isAscendingPart = Array.Find(parts, part => part.StartsWith(IsAscendingKey));
var isAscending = isAscendingPart?.Trim().Replace(IsAscendingKey, string.Empty).Equals("true", StringComparison.OrdinalIgnoreCase) ?? false;
if (sortFieldPart == null)

View File

@ -151,6 +151,10 @@
"browse-libraries": "Browse by Libraries",
"collections": "All Collections",
"browse-collections": "Browse by Collections",
"more-in-genre": "More in Genre {0}",
"browse-more-in-genre": "Browse more in {0}",
"recently-updated": "Recently Updated",
"browse-recently-updated": "Browse Recently Updated",
"smart-filters": "Smart Filters",
"external-sources": "External Sources",
"browse-external-sources": "Browse External Sources",

View File

@ -7,6 +7,7 @@ using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.RateLimiting;
namespace API.Middleware.RateLimit;
#nullable enable
public class AuthenticationRateLimiterPolicy : IRateLimiterPolicy<string>
{

View File

@ -25,6 +25,7 @@ using Serilog.Events;
using Serilog.Sinks.AspNetCore.SignalR.Extensions;
namespace API;
#nullable enable
public class Program
{

View File

@ -199,7 +199,6 @@ public class BookService : IBookService
}
if (!book.Content.AllFiles.TryGetLocalFileRefByKey(key, out var bookFile)) continue;
//var bookFile = book.Content.AllFiles.Local[key];
var content = await bookFile.ReadContentAsBytesAsync();
importBuilder.Append(Encoding.UTF8.GetString(content));
}
@ -555,7 +554,6 @@ public class BookService : IBookService
// If this is a single book and not a collection, set publication status to Completed
if (string.IsNullOrEmpty(info.Volume) && Parser.ParseVolume(filePath).Equals(Parser.DefaultVolume))
{
//info.Number = "1";
info.Count = 1;
}
@ -938,8 +936,9 @@ public class BookService : IBookService
/// <param name="mappings"></param>
/// <param name="key"></param>
/// <returns></returns>
private static string CoalesceKey(EpubBookRef book, IReadOnlyDictionary<string, int> mappings, string key)
private static string? CoalesceKey(EpubBookRef book, IReadOnlyDictionary<string, int> mappings, string? key)
{
if (string.IsNullOrEmpty(key)) return key;
if (mappings.ContainsKey(CleanContentKeys(key))) return key;
// Fallback to searching for key (bad epub metadata)
@ -949,7 +948,7 @@ public class BookService : IBookService
key = correctedKey;
}
var stepsBack = CountParentDirectory(book.Content.NavigationHtmlFile?.FilePath); // FileName -> FilePath
var stepsBack = CountParentDirectory(book.Content.NavigationHtmlFile?.FilePath);
if (mappings.TryGetValue(key, out _))
{
return key;

View File

@ -644,10 +644,10 @@ public class DirectoryService : IDirectoryService
/// Scans a directory by utilizing a recursive folder search. If a .kavitaignore file is found, will ignore matching patterns
/// </summary>
/// <param name="folderPath"></param>
/// <param name="supportedExtensions"></param>
/// <param name="fileTypes"></param>
/// <param name="matcher"></param>
/// <returns></returns>
public IList<string> ScanFiles(string folderPath, string supportedExtensions, GlobMatcher? matcher = null)
public IList<string> ScanFiles(string folderPath, string fileTypes, GlobMatcher? matcher = null)
{
_logger.LogDebug("[ScanFiles] called on {Path}", folderPath);
var files = new List<string>();
@ -668,19 +668,19 @@ public class DirectoryService : IDirectoryService
foreach (var directory in directories)
{
files.AddRange(ScanFiles(directory, supportedExtensions, matcher));
files.AddRange(ScanFiles(directory, fileTypes, matcher));
}
// Get the matcher from either ignore or global (default setup)
if (matcher == null)
{
files.AddRange(GetFilesWithCertainExtensions(folderPath, supportedExtensions));
files.AddRange(GetFilesWithCertainExtensions(folderPath, fileTypes));
}
else
{
var foundFiles = GetFilesWithCertainExtensions(folderPath,
supportedExtensions)
fileTypes)
.Where(file => !matcher.ExcludeMatches(FileSystem.FileInfo.New(file).Name));
files.AddRange(foundFiles);
}

View File

@ -16,6 +16,7 @@ using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
namespace API.Services;
#nullable enable
public interface IEmailService
{

View File

@ -14,6 +14,7 @@ using Hangfire;
using Microsoft.Extensions.Logging;
namespace API.Services;
#nullable enable
public interface IMetadataService
{

View File

@ -31,7 +31,7 @@ internal class ExternalMetadataIdsDto
public interface IExternalMetadataService
{
Task<ExternalSeriesDetailDto> GetExternalSeriesDetail(int? aniListId, long? malId, int? seriesId);
Task<ExternalSeriesDetailDto?> GetExternalSeriesDetail(int? aniListId, long? malId, int? seriesId);
}
public class ExternalMetadataService : IExternalMetadataService

View File

@ -22,6 +22,7 @@ using Kavita.Common;
using Microsoft.Extensions.Logging;
namespace API.Services;
#nullable enable
public interface IReaderService
{

View File

@ -21,6 +21,7 @@ using Kavita.Common;
using Microsoft.Extensions.Logging;
namespace API.Services;
#nullable enable
public interface IReadingListService
{

View File

@ -399,7 +399,7 @@ public class ParseScannedFiles
/// World of Acceleration v02.cbz having Series "Accel World" and Localized Series of "World of Acceleration"
/// </example>
/// <param name="infos">A collection of ParserInfos</param>
private void MergeLocalizedSeriesWithSeries(IReadOnlyCollection<ParserInfo> infos)
private void MergeLocalizedSeriesWithSeries(IReadOnlyCollection<ParserInfo?> infos)
{
var hasLocalizedSeries = infos.Any(i => !string.IsNullOrEmpty(i.LocalizedSeries));
if (!hasLocalizedSeries) return;

View File

@ -34,7 +34,7 @@ public class DefaultParser : IDefaultParser
public ParserInfo? Parse(string filePath, string rootPath, LibraryType type = LibraryType.Manga)
{
var fileName = _directoryService.FileSystem.Path.GetFileNameWithoutExtension(filePath);
// TODO: Potential Bug: This will return null, but on Image libraries, if all images, we would want to include this. (we can probably remove this and have users use kavitaignore)
// TODO: Potential Bug: This will return null, but on Image libraries, if all images, we would want to include this.
if (type != LibraryType.Image && Parser.IsCoverImage(_directoryService.FileSystem.Path.GetFileName(filePath))) return null;
var ret = new ParserInfo()

View File

@ -501,7 +501,6 @@ public class ScannerService : IScannerService
// {
// await task();
// }
// TODO: We might be able to do Task.WhenAll
await _eventHub.SendMessageAsync(MessageFactory.NotificationProgress,
MessageFactory.FileScanProgressEvent(string.Empty, library.Name, ProgressEventType.Ended));

View File

@ -37,7 +37,7 @@ public class StatsService : IStatsService
private readonly IUnitOfWork _unitOfWork;
private readonly DataContext _context;
private readonly IStatisticService _statisticService;
private const string ApiUrl = "https://stats.kavitareader.com"; // ""
private const string ApiUrl = "https://stats.kavitareader.com";
public StatsService(ILogger<StatsService> logger, IUnitOfWork unitOfWork, DataContext context, IStatisticService statisticService)
{

View File

@ -13,7 +13,6 @@ using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
namespace API.Services.Tasks;
#nullable enable
internal class GithubReleaseMetadata
@ -76,7 +75,7 @@ public class VersionUpdaterService : IVersionUpdaterService
/// Fetches the latest release from Github
/// </summary>
/// <returns>Latest update</returns>
public async Task<UpdateNotificationDto> CheckForUpdate()
public async Task<UpdateNotificationDto?> CheckForUpdate()
{
var update = await GetGithubRelease();
return CreateDto(update);

View File

@ -17,6 +17,7 @@ using JwtRegisteredClaimNames = Microsoft.IdentityModel.JsonWebTokens.JwtRegiste
namespace API.Services;
#nullable enable
public interface ITokenService
{

View File

@ -21,13 +21,11 @@ public class EventHub : IEventHub
{
private readonly IHubContext<MessageHub> _messageHub;
private readonly IPresenceTracker _presenceTracker;
private readonly IUnitOfWork _unitOfWork;
public EventHub(IHubContext<MessageHub> messageHub, IPresenceTracker presenceTracker, IUnitOfWork unitOfWork)
public EventHub(IHubContext<MessageHub> messageHub, IPresenceTracker presenceTracker)
{
_messageHub = messageHub;
_presenceTracker = presenceTracker;
_unitOfWork = unitOfWork;
// TODO: When sending a message, queue the message up and on re-connect, reply the queued messages. Queue messages expire on a rolling basis (rolling array)
}

View File

@ -6,6 +6,7 @@ using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.SignalR;
namespace API.SignalR;
#nullable enable
public interface ILogHub : Serilog.Sinks.AspNetCore.SignalR.Interfaces.IHub
{

View File

@ -6,6 +6,7 @@ using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.SignalR;
namespace API.SignalR;
#nullable enable
/// <summary>
/// Generic hub for sending messages to UI

View File

@ -5,6 +5,7 @@ using System.Threading.Tasks;
using API.Data;
namespace API.SignalR.Presence;
#nullable enable
public interface IPresenceTracker
{
@ -22,7 +23,6 @@ internal class ConnectionDetail
public bool IsAdmin { get; set; }
}
// TODO: This can respond to UserRoleUpdate events to handle online users
/// <summary>
/// This is a singleton service for tracking what users have a SignalR connection and their difference connectionIds
/// </summary>

View File

@ -1,6 +1,7 @@
using System;
namespace API.SignalR;
#nullable enable
/// <summary>
/// Payload for SignalR messages to Frontend

View File

@ -232,7 +232,6 @@ public class Startup
Task.Run(async () =>
{
// Apply all migrations on startup
var userManager = serviceProvider.GetRequiredService<UserManager<AppUser>>();
var dataContext = serviceProvider.GetRequiredService<DataContext>();

View File

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<TargetFramework>net8.0</TargetFramework>
<Company>kavitareader.com</Company>
<Product>Kavita</Product>
<AssemblyVersion>0.7.11.2</AssemblyVersion>
@ -12,9 +12,9 @@
<ItemGroup>
<PackageReference Include="DotNet.Glob" Version="3.1.3" />
<PackageReference Include="Flurl.Http" Version="3.2.4" />
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="7.0.0" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="7.0.1" />
<PackageReference Include="SonarAnalyzer.CSharp" Version="9.12.0.78982">
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="8.0.0" />
<PackageReference Include="SonarAnalyzer.CSharp" Version="9.15.0.81779">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>

View File

@ -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.**
## 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?type=classic) 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/), [Feature Discord Channel](https://discord.com/channels/821879810934439936/1164375153493422122) 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
Kavita is being actively developed and should be considered beta software until the 1.0 release.
@ -107,7 +107,7 @@ Thank you to [<img src="/Logo/jetbrains.svg" alt="" width="32"> JetBrains](http:
Thank you to [Weblate](https://hosted.weblate.org/engage/kavita/) who hosts our localization infrastructure pro-bono. If you want to see Kavita in your language, please help us localize.
<a href="https://hosted.weblate.org/engage/kavita/">
<img src="https://hosted.weblate.org/widgets/kavita/-/horizontal-blue.svg" alt="Translation status" />
<img src="https://hosted.weblate.org/widget/kavita/horizontal-auto.svg" alt="Translation status" />
</a>
## PikaPods

540
UI/Web/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -13,26 +13,26 @@
},
"private": true,
"dependencies": {
"@angular/animations": "^17.0.4",
"@angular/cdk": "^17.0.1",
"@angular/common": "^17.0.4",
"@angular/compiler": "^17.0.4",
"@angular/core": "^17.0.4",
"@angular/forms": "^17.0.4",
"@angular/localize": "^17.0.4",
"@angular/platform-browser": "^17.0.4",
"@angular/platform-browser-dynamic": "^17.0.4",
"@angular/router": "^17.0.4",
"@angular/animations": "^17.0.6",
"@angular/cdk": "^17.0.4",
"@angular/common": "^17.0.6",
"@angular/compiler": "^17.0.6",
"@angular/core": "^17.0.6",
"@angular/forms": "^17.0.6",
"@angular/localize": "^17.0.6",
"@angular/platform-browser": "^17.0.6",
"@angular/platform-browser-dynamic": "^17.0.6",
"@angular/router": "^17.0.6",
"@fortawesome/fontawesome-free": "^6.4.2",
"@iharbeck/ngx-virtual-scroller": "^17.0.0",
"@iplab/ngx-file-upload": "^17.0.0",
"@microsoft/signalr": "^7.0.12",
"@microsoft/signalr": "^8.0.0",
"@ng-bootstrap/ng-bootstrap": "^16.0.0",
"@ngneat/transloco": "^6.0.0",
"@ngneat/transloco": "^6.0.4",
"@ngneat/transloco-locale": "^5.1.1",
"@ngneat/transloco-persist-lang": "^5.0.0",
"@ngneat/transloco-persist-translations": "^5.0.0",
"@ngneat/transloco-preload-langs": "^5.0.0",
"@ngneat/transloco-preload-langs": "^5.0.1",
"@popperjs/core": "^2.11.7",
"@swimlane/ngx-charts": "^20.5.0",
"@tweenjs/tween.js": "^21.0.0",
@ -58,17 +58,17 @@
"zone.js": "^0.14.2"
},
"devDependencies": {
"@angular-devkit/build-angular": "^17.0.3",
"@angular-eslint/builder": "^17.1.0",
"@angular-eslint/eslint-plugin": "^17.1.0",
"@angular-eslint/eslint-plugin-template": "^17.1.0",
"@angular-eslint/schematics": "^17.1.0",
"@angular-eslint/template-parser": "^17.1.0",
"@angular/cli": "^17.0.3",
"@angular/compiler-cli": "^17.0.4",
"@angular-devkit/build-angular": "^17.0.7",
"@angular-eslint/builder": "^17.1.1",
"@angular-eslint/eslint-plugin": "^17.1.1",
"@angular-eslint/eslint-plugin-template": "^17.1.1",
"@angular-eslint/schematics": "^17.1.1",
"@angular-eslint/template-parser": "^17.1.1",
"@angular/cli": "^17.0.7",
"@angular/compiler-cli": "^17.0.6",
"@types/d3": "^7.4.3",
"@types/file-saver": "^2.0.7",
"@types/luxon": "^3.3.5",
"@types/luxon": "^3.3.7",
"@types/node": "^20.10.0",
"@typescript-eslint/eslint-plugin": "^6.13.0",
"@typescript-eslint/parser": "^6.13.0",

View File

@ -141,6 +141,7 @@ export class MessageHubService {
accessTokenFactory: () => user.token
})
.withAutomaticReconnect()
.withStatefulReconnect()
.build();
this.hubConnection

View File

@ -21,7 +21,6 @@ Build()
slnFile=Kavita.sln
dotnet clean $slnFile -c Debug
dotnet clean $slnFile -c Release
dotnet msbuild -restore $slnFile -p:Configuration=Release -p:Platform="Any CPU" -p:RuntimeIdentifiers=$RID

View File

@ -1,6 +1,6 @@
{
"sdk": {
"version": "7.0.0",
"version": "8.0.0",
"rollForward": "latestMajor",
"allowPrerelease": false
}

View File

@ -53,9 +53,6 @@ Package()
echo "Copying LICENSE"
cp ../LICENSE "$lOutputFolder"/LICENSE.txt
echo "Show API structure"
find
echo "Copying appsettings.json"
cp ./config/appsettings.Development.json $lOutputFolder/config/appsettings.json

View File

@ -7,7 +7,7 @@
"name": "GPL-3.0",
"url": "https://github.com/Kareadita/Kavita/blob/develop/LICENSE"
},
"version": "0.7.11.1"
"version": "0.7.11.2"
},
"servers": [
{
@ -3917,6 +3917,76 @@
}
}
},
"/api/Opds/{apiKey}/more-in-genre": {
"get": {
"tags": [
"Opds"
],
"parameters": [
{
"name": "apiKey",
"in": "path",
"required": true,
"schema": {
"type": "string"
}
},
{
"name": "genreId",
"in": "query",
"schema": {
"type": "integer",
"format": "int32"
}
},
{
"name": "pageNumber",
"in": "query",
"schema": {
"type": "integer",
"format": "int32",
"default": 1
}
}
],
"responses": {
"200": {
"description": "Success"
}
}
}
},
"/api/Opds/{apiKey}/recently-updated": {
"get": {
"tags": [
"Opds"
],
"parameters": [
{
"name": "apiKey",
"in": "path",
"required": true,
"schema": {
"type": "string"
}
},
{
"name": "pageNumber",
"in": "query",
"schema": {
"type": "integer",
"format": "int32",
"default": 1
}
}
],
"responses": {
"200": {
"description": "Success"
}
}
}
},
"/api/Opds/{apiKey}/on-deck": {
"get": {
"tags": [