Stats Fix & Library Bulk Actions (#3209)
Co-authored-by: Fesaa <77553571+Fesaa@users.noreply.github.com> Co-authored-by: Weblate (bot) <hosted@weblate.org> Co-authored-by: Gregory.Open <gregory.open@proton.me> Co-authored-by: Mateusz <mateuszvx8.96@gmail.com> Co-authored-by: majora2007 <kavitareader@gmail.com> Co-authored-by: 無情天 <kofzhanganguo@126.com>
This commit is contained in:
parent
894b49bb76
commit
857e419e4e
77 changed files with 72523 additions and 30914 deletions
|
@ -12,10 +12,10 @@
|
|||
<LangVersion>latestmajor</LangVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<Target Name="PostBuild" AfterTargets="Build" Condition=" '$(Configuration)' == 'Debug' ">
|
||||
<Delete Files="../openapi.json" />
|
||||
<Exec Command="swagger tofile --output ../openapi.json bin/$(Configuration)/$(TargetFramework)/$(AssemblyName).dll v1" />
|
||||
</Target>
|
||||
<!-- <Target Name="PostBuild" AfterTargets="Build" Condition=" '$(Configuration)' == 'Debug' ">-->
|
||||
<!-- <Delete Files="../openapi.json" />-->
|
||||
<!-- <Exec Command="swagger tofile --output ../openapi.json bin/$(Configuration)/$(TargetFramework)/$(AssemblyName).dll v1" />-->
|
||||
<!-- </Target>-->
|
||||
|
||||
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
|
||||
<DebugSymbols>false</DebugSymbols>
|
||||
|
@ -67,10 +67,10 @@
|
|||
<PackageReference Include="Flurl" Version="3.0.7" />
|
||||
<PackageReference Include="Flurl.Http" Version="3.2.4" />
|
||||
<PackageReference Include="Hangfire" Version="1.8.14" />
|
||||
<PackageReference Include="Hangfire.InMemory" Version="0.10.3" />
|
||||
<PackageReference Include="Hangfire.InMemory" Version="1.0.0" />
|
||||
<PackageReference Include="Hangfire.MaximumConcurrentExecutions" Version="1.1.0" />
|
||||
<PackageReference Include="Hangfire.Storage.SQLite" Version="0.4.2" />
|
||||
<PackageReference Include="HtmlAgilityPack" Version="1.11.64" />
|
||||
<PackageReference Include="HtmlAgilityPack" Version="1.11.66" />
|
||||
<PackageReference Include="MarkdownDeep.NET.Core" Version="1.5.0.4" />
|
||||
<PackageReference Include="Hangfire.AspNetCore" Version="1.8.14" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.SignalR" Version="1.1.0" />
|
||||
|
@ -94,7 +94,7 @@
|
|||
<PackageReference Include="Serilog.Sinks.Console" Version="6.0.0" />
|
||||
<PackageReference Include="Serilog.Sinks.File" Version="6.0.0" />
|
||||
<PackageReference Include="Serilog.Sinks.SignalR.Core" Version="0.1.2" />
|
||||
<PackageReference Include="SharpCompress" Version="0.37.2" />
|
||||
<PackageReference Include="SharpCompress" Version="0.38.0" />
|
||||
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.5" />
|
||||
<PackageReference Include="SonarAnalyzer.CSharp" Version="9.32.0.97167">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
|
|
|
@ -78,7 +78,6 @@ public class LibraryController : BaseApiController
|
|||
.WithFolders(dto.Folders.Select(x => new FolderPath {Path = x}).Distinct().ToList())
|
||||
.WithFolderWatching(dto.FolderWatching)
|
||||
.WithIncludeInDashboard(dto.IncludeInDashboard)
|
||||
.WithIncludeInRecommended(dto.IncludeInRecommended)
|
||||
.WithManageCollections(dto.ManageCollections)
|
||||
.WithManageReadingLists(dto.ManageReadingLists)
|
||||
.WIthAllowScrobbling(dto.AllowScrobbling)
|
||||
|
@ -302,6 +301,22 @@ public class LibraryController : BaseApiController
|
|||
return Ok();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enqueues a bunch of library scans
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[Authorize(Policy = "RequireAdminRole")]
|
||||
[HttpPost("scan-multiple")]
|
||||
public async Task<ActionResult> ScanMultiple(BulkActionDto dto)
|
||||
{
|
||||
foreach (var libraryId in dto.Ids)
|
||||
{
|
||||
await _taskScheduler.ScanLibrary(libraryId, dto.Force ?? false);
|
||||
}
|
||||
|
||||
return Ok();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Scans a given library for file changes. If another scan task is in progress, will reschedule the invocation for 3 hours in future.
|
||||
/// </summary>
|
||||
|
@ -323,6 +338,18 @@ public class LibraryController : BaseApiController
|
|||
return Ok();
|
||||
}
|
||||
|
||||
[Authorize(Policy = "RequireAdminRole")]
|
||||
[HttpPost("refresh-metadata-multiple")]
|
||||
public ActionResult RefreshMetadataMultiple(BulkActionDto dto, bool forceColorscape = true)
|
||||
{
|
||||
foreach (var libraryId in dto.Ids)
|
||||
{
|
||||
_taskScheduler.RefreshMetadata(libraryId, dto.Force ?? false, forceColorscape);
|
||||
}
|
||||
|
||||
return Ok();
|
||||
}
|
||||
|
||||
[Authorize(Policy = "RequireAdminRole")]
|
||||
[HttpPost("analyze")]
|
||||
public ActionResult Analyze(int libraryId)
|
||||
|
@ -331,6 +358,61 @@ public class LibraryController : BaseApiController
|
|||
return Ok();
|
||||
}
|
||||
|
||||
[Authorize(Policy = "RequireAdminRole")]
|
||||
[HttpPost("analyze-multiple")]
|
||||
public ActionResult AnalyzeMultiple(BulkActionDto dto)
|
||||
{
|
||||
foreach (var libraryId in dto.Ids)
|
||||
{
|
||||
_taskScheduler.AnalyzeFilesForLibrary(libraryId, dto.Force ?? false);
|
||||
}
|
||||
|
||||
return Ok();
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Copy the library settings (adv tab + optional type) to a set of other libraries.
|
||||
/// </summary>
|
||||
/// <param name="dto"></param>
|
||||
/// <returns></returns>
|
||||
[Authorize(Policy = "RequireAdminRole")]
|
||||
[HttpPost("copy-settings-from")]
|
||||
public async Task<ActionResult> CopySettingsFromLibraryToLibraries(CopySettingsFromLibraryDto dto)
|
||||
{
|
||||
var sourceLibrary = await _unitOfWork.LibraryRepository.GetLibraryForIdAsync(dto.SourceLibraryId, LibraryIncludes.ExcludePatterns | LibraryIncludes.FileTypes);
|
||||
if (sourceLibrary == null) return BadRequest("SourceLibraryId must exist");
|
||||
|
||||
var libraries = await _unitOfWork.LibraryRepository.GetLibraryForIdsAsync(dto.TargetLibraryIds, LibraryIncludes.ExcludePatterns | LibraryIncludes.FileTypes | LibraryIncludes.Folders);
|
||||
foreach (var targetLibrary in libraries)
|
||||
{
|
||||
UpdateLibrarySettings(new UpdateLibraryDto()
|
||||
{
|
||||
Folders = targetLibrary.Folders.Select(s => s.Path),
|
||||
Name = targetLibrary.Name,
|
||||
Id = targetLibrary.Id,
|
||||
Type = sourceLibrary.Type,
|
||||
AllowScrobbling = sourceLibrary.AllowScrobbling,
|
||||
ExcludePatterns = sourceLibrary.LibraryExcludePatterns.Select(p => p.Pattern).ToList(),
|
||||
FolderWatching = sourceLibrary.FolderWatching,
|
||||
ManageCollections = sourceLibrary.ManageCollections,
|
||||
FileGroupTypes = sourceLibrary.LibraryFileTypes.Select(t => t.FileTypeGroup).ToList(),
|
||||
IncludeInDashboard = sourceLibrary.IncludeInDashboard,
|
||||
IncludeInSearch = sourceLibrary.IncludeInSearch,
|
||||
ManageReadingLists = sourceLibrary.ManageReadingLists
|
||||
}, targetLibrary, dto.IncludeType);
|
||||
}
|
||||
|
||||
await _unitOfWork.CommitAsync();
|
||||
|
||||
if (sourceLibrary.FolderWatching)
|
||||
{
|
||||
BackgroundJob.Enqueue(() => _libraryWatcher.RestartWatching());
|
||||
}
|
||||
|
||||
return Ok();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Given a valid path, will invoke either a Scan Series or Scan Library. If the folder does not exist within Kavita, the request will be ignored
|
||||
/// </summary>
|
||||
|
@ -474,33 +556,7 @@ public class LibraryController : BaseApiController
|
|||
|
||||
var typeUpdate = library.Type != dto.Type;
|
||||
var folderWatchingUpdate = library.FolderWatching != dto.FolderWatching;
|
||||
library.Type = dto.Type;
|
||||
library.FolderWatching = dto.FolderWatching;
|
||||
library.IncludeInDashboard = dto.IncludeInDashboard;
|
||||
library.IncludeInRecommended = dto.IncludeInRecommended;
|
||||
library.IncludeInSearch = dto.IncludeInSearch;
|
||||
library.ManageCollections = dto.ManageCollections;
|
||||
library.ManageReadingLists = dto.ManageReadingLists;
|
||||
library.AllowScrobbling = dto.AllowScrobbling;
|
||||
library.LibraryFileTypes = dto.FileGroupTypes
|
||||
.Select(t => new LibraryFileTypeGroup() {FileTypeGroup = t, LibraryId = library.Id})
|
||||
.Distinct()
|
||||
.ToList();
|
||||
|
||||
library.LibraryExcludePatterns = dto.ExcludePatterns
|
||||
.Distinct()
|
||||
.Select(t => new LibraryExcludePattern() {Pattern = t, LibraryId = library.Id})
|
||||
.ToList();
|
||||
|
||||
// Override Scrobbling for Comic libraries since there are no providers to scrobble to
|
||||
if (library.Type == LibraryType.Comic)
|
||||
{
|
||||
_logger.LogInformation("Overrode Library {Name} to disable scrobbling since there are no providers for Comics", dto.Name.Replace(Environment.NewLine, string.Empty));
|
||||
library.AllowScrobbling = false;
|
||||
}
|
||||
|
||||
|
||||
_unitOfWork.LibraryRepository.Update(library);
|
||||
UpdateLibrarySettings(dto, library);
|
||||
|
||||
if (!await _unitOfWork.CommitAsync()) return BadRequest(await _localizationService.Translate(userId, "generic-library-update"));
|
||||
|
||||
|
@ -526,6 +582,39 @@ public class LibraryController : BaseApiController
|
|||
|
||||
}
|
||||
|
||||
private void UpdateLibrarySettings(UpdateLibraryDto dto, Library library, bool updateType = true)
|
||||
{
|
||||
if (updateType)
|
||||
{
|
||||
library.Type = dto.Type;
|
||||
}
|
||||
|
||||
library.FolderWatching = dto.FolderWatching;
|
||||
library.IncludeInDashboard = dto.IncludeInDashboard;
|
||||
library.IncludeInSearch = dto.IncludeInSearch;
|
||||
library.ManageCollections = dto.ManageCollections;
|
||||
library.ManageReadingLists = dto.ManageReadingLists;
|
||||
library.AllowScrobbling = dto.AllowScrobbling;
|
||||
library.LibraryFileTypes = dto.FileGroupTypes
|
||||
.Select(t => new LibraryFileTypeGroup() {FileTypeGroup = t, LibraryId = library.Id})
|
||||
.Distinct()
|
||||
.ToList();
|
||||
|
||||
library.LibraryExcludePatterns = dto.ExcludePatterns
|
||||
.Distinct()
|
||||
.Select(t => new LibraryExcludePattern() {Pattern = t, LibraryId = library.Id})
|
||||
.ToList();
|
||||
|
||||
// Override Scrobbling for Comic libraries since there are no providers to scrobble to
|
||||
if (library.Type is LibraryType.Comic or LibraryType.ComicVine)
|
||||
{
|
||||
_logger.LogInformation("Overrode Library {Name} to disable scrobbling since there are no providers for Comics", dto.Name.Replace(Environment.NewLine, string.Empty));
|
||||
library.AllowScrobbling = false;
|
||||
}
|
||||
|
||||
_unitOfWork.LibraryRepository.Update(library);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the type of the underlying library
|
||||
/// </summary>
|
||||
|
|
12
API/DTOs/BulkActionDto.cs
Normal file
12
API/DTOs/BulkActionDto.cs
Normal file
|
@ -0,0 +1,12 @@
|
|||
using System.Collections.Generic;
|
||||
|
||||
namespace API.DTOs;
|
||||
|
||||
public class BulkActionDto
|
||||
{
|
||||
public List<int> Ids { get; set; }
|
||||
/**
|
||||
* If this is a Scan action, will ignore optimizations
|
||||
*/
|
||||
public bool? Force { get; set; }
|
||||
}
|
|
@ -111,7 +111,7 @@ public class ChapterDto : IHasReadTimeEstimate, IHasCoverImage
|
|||
/// <inheritdoc cref="IHasReadTimeEstimate.MaxHoursToRead"/>
|
||||
public int MaxHoursToRead { get; set; }
|
||||
/// <inheritdoc cref="IHasReadTimeEstimate.AvgHoursToRead"/>
|
||||
public int AvgHoursToRead { get; set; }
|
||||
public float AvgHoursToRead { get; set; }
|
||||
/// <summary>
|
||||
/// Comma-separated link of urls to external services that have some relation to the Chapter
|
||||
/// </summary>
|
||||
|
|
14
API/DTOs/CopySettingsFromLibraryDto.cs
Normal file
14
API/DTOs/CopySettingsFromLibraryDto.cs
Normal file
|
@ -0,0 +1,14 @@
|
|||
using System.Collections.Generic;
|
||||
|
||||
namespace API.DTOs;
|
||||
|
||||
public class CopySettingsFromLibraryDto
|
||||
{
|
||||
public int SourceLibraryId { get; set; }
|
||||
public List<int> TargetLibraryIds { get; set; }
|
||||
/// <summary>
|
||||
/// Include copying over the type
|
||||
/// </summary>
|
||||
public bool IncludeType { get; set; }
|
||||
|
||||
}
|
|
@ -16,5 +16,5 @@ public record HourEstimateRangeDto
|
|||
/// <summary>
|
||||
/// Estimated average hours to read the selection
|
||||
/// </summary>
|
||||
public int AvgHours { get; init; } = 1;
|
||||
public float AvgHours { get; init; } = 1f;
|
||||
}
|
||||
|
|
|
@ -53,7 +53,7 @@ public class SeriesDto : IHasReadTimeEstimate, IHasCoverImage
|
|||
/// <inheritdoc cref="IHasReadTimeEstimate.MaxHoursToRead"/>
|
||||
public int MaxHoursToRead { get; set; }
|
||||
/// <inheritdoc cref="IHasReadTimeEstimate.AvgHoursToRead"/>
|
||||
public int AvgHoursToRead { get; set; }
|
||||
public float AvgHoursToRead { get; set; }
|
||||
/// <summary>
|
||||
/// The highest level folder for this Series
|
||||
/// </summary>
|
||||
|
|
|
@ -8,11 +8,11 @@ public class TopReadDto
|
|||
/// <summary>
|
||||
/// Amount of time read on Comic libraries
|
||||
/// </summary>
|
||||
public long ComicsTime { get; set; }
|
||||
public float ComicsTime { get; set; }
|
||||
/// <summary>
|
||||
/// Amount of time read on
|
||||
/// </summary>
|
||||
public long BooksTime { get; set; }
|
||||
public long MangaTime { get; set; }
|
||||
public float BooksTime { get; set; }
|
||||
public float MangaTime { get; set; }
|
||||
}
|
||||
|
||||
|
|
|
@ -19,8 +19,6 @@ public class UpdateLibraryDto
|
|||
[Required]
|
||||
public bool IncludeInDashboard { get; init; }
|
||||
[Required]
|
||||
public bool IncludeInRecommended { get; init; }
|
||||
[Required]
|
||||
public bool IncludeInSearch { get; init; }
|
||||
[Required]
|
||||
public bool ManageCollections { get; init; }
|
||||
|
|
|
@ -43,7 +43,7 @@ public class VolumeDto : IHasReadTimeEstimate, IHasCoverImage
|
|||
/// <inheritdoc cref="IHasReadTimeEstimate.MaxHoursToRead"/>
|
||||
public int MaxHoursToRead { get; set; }
|
||||
/// <inheritdoc cref="IHasReadTimeEstimate.AvgHoursToRead"/>
|
||||
public int AvgHoursToRead { get; set; }
|
||||
public float AvgHoursToRead { get; set; }
|
||||
public long WordCount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
|
|
3145
API/Data/Migrations/20240917180034_AvgReadingTimeFloat.Designer.cs
generated
Normal file
3145
API/Data/Migrations/20240917180034_AvgReadingTimeFloat.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load diff
66
API/Data/Migrations/20240917180034_AvgReadingTimeFloat.cs
Normal file
66
API/Data/Migrations/20240917180034_AvgReadingTimeFloat.cs
Normal file
|
@ -0,0 +1,66 @@
|
|||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace API.Data.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AvgReadingTimeFloat : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AlterColumn<float>(
|
||||
name: "AvgHoursToRead",
|
||||
table: "Volume",
|
||||
type: "REAL",
|
||||
nullable: false,
|
||||
oldClrType: typeof(int),
|
||||
oldType: "INTEGER");
|
||||
|
||||
migrationBuilder.AlterColumn<float>(
|
||||
name: "AvgHoursToRead",
|
||||
table: "Series",
|
||||
type: "REAL",
|
||||
nullable: false,
|
||||
oldClrType: typeof(int),
|
||||
oldType: "INTEGER");
|
||||
|
||||
migrationBuilder.AlterColumn<float>(
|
||||
name: "AvgHoursToRead",
|
||||
table: "Chapter",
|
||||
type: "REAL",
|
||||
nullable: false,
|
||||
oldClrType: typeof(int),
|
||||
oldType: "INTEGER");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AlterColumn<int>(
|
||||
name: "AvgHoursToRead",
|
||||
table: "Volume",
|
||||
type: "INTEGER",
|
||||
nullable: false,
|
||||
oldClrType: typeof(float),
|
||||
oldType: "REAL");
|
||||
|
||||
migrationBuilder.AlterColumn<int>(
|
||||
name: "AvgHoursToRead",
|
||||
table: "Series",
|
||||
type: "INTEGER",
|
||||
nullable: false,
|
||||
oldClrType: typeof(float),
|
||||
oldType: "REAL");
|
||||
|
||||
migrationBuilder.AlterColumn<int>(
|
||||
name: "AvgHoursToRead",
|
||||
table: "Chapter",
|
||||
type: "INTEGER",
|
||||
nullable: false,
|
||||
oldClrType: typeof(float),
|
||||
oldType: "REAL");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -15,7 +15,7 @@ namespace API.Data.Migrations
|
|||
protected override void BuildModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder.HasAnnotation("ProductVersion", "8.0.7");
|
||||
modelBuilder.HasAnnotation("ProductVersion", "8.0.8");
|
||||
|
||||
modelBuilder.Entity("API.Entities.AppRole", b =>
|
||||
{
|
||||
|
@ -731,8 +731,8 @@ namespace API.Data.Migrations
|
|||
b.Property<string>("AlternateSeries")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("AvgHoursToRead")
|
||||
.HasColumnType("INTEGER");
|
||||
b.Property<float>("AvgHoursToRead")
|
||||
.HasColumnType("REAL");
|
||||
|
||||
b.Property<bool>("CharacterLocked")
|
||||
.HasColumnType("INTEGER");
|
||||
|
@ -1809,8 +1809,8 @@ namespace API.Data.Migrations
|
|||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("AvgHoursToRead")
|
||||
.HasColumnType("INTEGER");
|
||||
b.Property<float>("AvgHoursToRead")
|
||||
.HasColumnType("REAL");
|
||||
|
||||
b.Property<string>("CoverImage")
|
||||
.HasColumnType("TEXT");
|
||||
|
@ -2040,8 +2040,8 @@ namespace API.Data.Migrations
|
|||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("AvgHoursToRead")
|
||||
.HasColumnType("INTEGER");
|
||||
b.Property<float>("AvgHoursToRead")
|
||||
.HasColumnType("REAL");
|
||||
|
||||
b.Property<string>("CoverImage")
|
||||
.HasColumnType("TEXT");
|
||||
|
|
|
@ -117,7 +117,7 @@ public class Chapter : IEntityDate, IHasReadTimeEstimate, IHasCoverImage
|
|||
/// <inheritdoc cref="IHasReadTimeEstimate"/>
|
||||
public int MaxHoursToRead { get; set; }
|
||||
/// <inheritdoc cref="IHasReadTimeEstimate"/>
|
||||
public int AvgHoursToRead { get; set; }
|
||||
public float AvgHoursToRead { get; set; }
|
||||
/// <summary>
|
||||
/// Comma-separated link of urls to external services that have some relation to the Chapter
|
||||
/// </summary>
|
||||
|
|
|
@ -21,5 +21,5 @@ public interface IHasReadTimeEstimate
|
|||
/// Average hours to read the chapter
|
||||
/// </summary>
|
||||
/// <remarks>Uses a fixed number to calculate from <see cref="ReaderService"/></remarks>
|
||||
public int AvgHoursToRead { get; set; }
|
||||
public float AvgHoursToRead { get; set; }
|
||||
}
|
||||
|
|
|
@ -38,7 +38,7 @@ public class Series : IEntityDate, IHasReadTimeEstimate, IHasCoverImage
|
|||
/// </summary>
|
||||
public DateTime Created { get; set; }
|
||||
/// <summary>
|
||||
/// Whenever a modification occurs. Ie) New volumes, removed volumes, title update, etc
|
||||
/// Whenever a modification occurs. ex: New volumes, removed volumes, title update, etc
|
||||
/// </summary>
|
||||
public DateTime LastModified { get; set; }
|
||||
|
||||
|
@ -101,7 +101,7 @@ public class Series : IEntityDate, IHasReadTimeEstimate, IHasCoverImage
|
|||
|
||||
public int MinHoursToRead { get; set; }
|
||||
public int MaxHoursToRead { get; set; }
|
||||
public int AvgHoursToRead { get; set; }
|
||||
public float AvgHoursToRead { get; set; }
|
||||
|
||||
public SeriesMetadata Metadata { get; set; } = null!;
|
||||
public ExternalSeriesMetadata ExternalSeriesMetadata { get; set; } = null!;
|
||||
|
|
|
@ -53,7 +53,7 @@ public class Volume : IEntityDate, IHasReadTimeEstimate, IHasCoverImage
|
|||
public long WordCount { get; set; }
|
||||
public int MinHoursToRead { get; set; }
|
||||
public int MaxHoursToRead { get; set; }
|
||||
public int AvgHoursToRead { get; set; }
|
||||
public float AvgHoursToRead { get; set; }
|
||||
|
||||
|
||||
// Relationships
|
||||
|
|
|
@ -698,21 +698,23 @@ public class ReaderService : IReaderService
|
|||
{
|
||||
var minHours = Math.Max((int) Math.Round((wordCount / MinWordsPerHour)), 0);
|
||||
var maxHours = Math.Max((int) Math.Round((wordCount / MaxWordsPerHour)), 0);
|
||||
|
||||
return new HourEstimateRangeDto
|
||||
{
|
||||
MinHours = Math.Min(minHours, maxHours),
|
||||
MaxHours = Math.Max(minHours, maxHours),
|
||||
AvgHours = (int) Math.Round((wordCount / AvgWordsPerHour))
|
||||
AvgHours = wordCount / AvgWordsPerHour
|
||||
};
|
||||
}
|
||||
|
||||
var minHoursPages = Math.Max((int) Math.Round((pageCount / MinPagesPerMinute / 60F)), 0);
|
||||
var maxHoursPages = Math.Max((int) Math.Round((pageCount / MaxPagesPerMinute / 60F)), 0);
|
||||
|
||||
return new HourEstimateRangeDto
|
||||
{
|
||||
MinHours = Math.Min(minHoursPages, maxHoursPages),
|
||||
MaxHours = Math.Max(minHoursPages, maxHoursPages),
|
||||
AvgHours = (int) Math.Round((pageCount / AvgPagesPerMinute / 60F))
|
||||
AvgHours = pageCount / AvgPagesPerMinute / 60F
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -808,6 +810,7 @@ public class ReaderService : IReaderService
|
|||
{
|
||||
switch(libraryType)
|
||||
{
|
||||
case LibraryType.Image:
|
||||
case LibraryType.Manga:
|
||||
return "Chapter" + (includeSpace ? " " : string.Empty);
|
||||
case LibraryType.Comic:
|
||||
|
|
|
@ -595,7 +595,6 @@ public class StatisticService : IStatisticService
|
|||
.Contains(c.Id))
|
||||
})
|
||||
.OrderByDescending(d => d.Chapters.Sum(c => c.AvgHoursToRead))
|
||||
.Take(5)
|
||||
.ToList();
|
||||
|
||||
|
||||
|
@ -615,16 +614,17 @@ public class StatisticService : IStatisticService
|
|||
chapterLibLookup.Add(cl.ChapterId, cl.LibraryId);
|
||||
}
|
||||
|
||||
var user = new Dictionary<int, Dictionary<LibraryType, long>>();
|
||||
var user = new Dictionary<int, Dictionary<LibraryType, float>>();
|
||||
foreach (var userChapter in topUsersAndReadChapters)
|
||||
{
|
||||
if (!user.ContainsKey(userChapter.User.Id)) user.Add(userChapter.User.Id, new Dictionary<LibraryType, long>());
|
||||
if (!user.ContainsKey(userChapter.User.Id)) user.Add(userChapter.User.Id, []);
|
||||
var libraryTimes = user[userChapter.User.Id];
|
||||
|
||||
foreach (var chapter in userChapter.Chapters)
|
||||
{
|
||||
var library = libraries.First(l => l.Id == chapterLibLookup[chapter.Id]);
|
||||
if (!libraryTimes.ContainsKey(library.Type)) libraryTimes.Add(library.Type, 0L);
|
||||
libraryTimes.TryAdd(library.Type, 0f);
|
||||
|
||||
var existingHours = libraryTimes[library.Type];
|
||||
libraryTimes[library.Type] = existingHours + chapter.AvgHoursToRead;
|
||||
}
|
||||
|
|
|
@ -421,6 +421,12 @@ public class TaskScheduler : ITaskScheduler
|
|||
BackgroundJob.Enqueue(() => _scannerService.ScanSeries(seriesId, forceUpdate));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculates TimeToRead and bytes
|
||||
/// </summary>
|
||||
/// <param name="libraryId"></param>
|
||||
/// <param name="seriesId"></param>
|
||||
/// <param name="forceUpdate"></param>
|
||||
public void AnalyzeFilesForSeries(int libraryId, int seriesId, bool forceUpdate = false)
|
||||
{
|
||||
if (HasAlreadyEnqueuedTask("WordCountAnalyzerService", "ScanSeries", [libraryId, seriesId, forceUpdate]))
|
||||
|
|
|
@ -217,6 +217,7 @@ public class WordCountAnalyzerService : IWordCountAnalyzerService
|
|||
chapter.MinHoursToRead = est.MinHours;
|
||||
chapter.MaxHoursToRead = est.MaxHours;
|
||||
chapter.AvgHoursToRead = est.AvgHours;
|
||||
|
||||
foreach (var file in chapter.Files)
|
||||
{
|
||||
UpdateFileAnalysis(file);
|
||||
|
|
|
@ -448,9 +448,7 @@ public class Startup
|
|||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if ((ex.Message.Contains("Permission denied")
|
||||
|| ex.Message.Contains("UnauthorizedAccessException"))
|
||||
&& baseUrl.Equals(Configuration.DefaultBaseUrl) && OsInfo.IsDocker)
|
||||
if (ex is UnauthorizedAccessException && baseUrl.Equals(Configuration.DefaultBaseUrl) && OsInfo.IsDocker)
|
||||
{
|
||||
// Swallow the exception as the install is non-root and Docker
|
||||
return;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue