Lots of Bugfixes (#2356)
This commit is contained in:
parent
86e931dd9a
commit
226d6831df
47 changed files with 359 additions and 225 deletions
|
|
@ -10,6 +10,7 @@ using API.Data.Metadata;
|
|||
using API.DTOs.Reader;
|
||||
using API.Entities;
|
||||
using API.Entities.Enums;
|
||||
using API.Extensions;
|
||||
using API.Services.Tasks.Scanner.Parser;
|
||||
using Docnet.Core;
|
||||
using Docnet.Core.Converters;
|
||||
|
|
@ -490,7 +491,7 @@ public class BookService : IBookService
|
|||
switch (metadataItem.Name)
|
||||
{
|
||||
case "calibre:rating":
|
||||
info.UserRating = float.Parse(metadataItem.Content);
|
||||
info.UserRating = metadataItem.Content.AsFloat();
|
||||
break;
|
||||
case "calibre:title_sort":
|
||||
info.TitleSort = metadataItem.Content;
|
||||
|
|
@ -649,7 +650,7 @@ public class BookService : IBookService
|
|||
return;
|
||||
case "ill":
|
||||
case "illustrator":
|
||||
info.Letterer += AppendAuthor(person);
|
||||
info.Inker += AppendAuthor(person);
|
||||
return;
|
||||
case "clr":
|
||||
case "colorist":
|
||||
|
|
|
|||
|
|
@ -972,9 +972,9 @@ public class DirectoryService : IDirectoryService
|
|||
foreach (var file in directory.EnumerateFiles().OrderByNatural(file => file.FullName))
|
||||
{
|
||||
if (file.Directory == null) continue;
|
||||
var paddedIndex = Tasks.Scanner.Parser.Parser.PadZeros(directoryIndex + "");
|
||||
var paddedIndex = Tasks.Scanner.Parser.Parser.PadZeros(directoryIndex + string.Empty);
|
||||
// We need to rename the files so that after flattening, they are in the order we found them
|
||||
var newName = $"{paddedIndex}_{Tasks.Scanner.Parser.Parser.PadZeros(fileIndex + "")}{file.Extension}";
|
||||
var newName = $"{paddedIndex}_{Tasks.Scanner.Parser.Parser.PadZeros(fileIndex + string.Empty)}{file.Extension}";
|
||||
var newPath = Path.Join(root.FullName, newName);
|
||||
if (!File.Exists(newPath)) file.MoveTo(newPath);
|
||||
fileIndex++;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Threading.Tasks;
|
||||
|
|
@ -27,6 +28,7 @@ public interface IEmailService
|
|||
Task<bool> IsDefaultEmailService();
|
||||
Task SendEmailChangeEmail(ConfirmationEmailDto data);
|
||||
Task<string?> GetVersion(string emailUrl);
|
||||
bool IsValidEmail(string email);
|
||||
}
|
||||
|
||||
public class EmailService : IEmailService
|
||||
|
|
@ -123,6 +125,11 @@ public class EmailService : IEmailService
|
|||
return null;
|
||||
}
|
||||
|
||||
public bool IsValidEmail(string email)
|
||||
{
|
||||
return new EmailAddressAttribute().IsValid(email);
|
||||
}
|
||||
|
||||
public async Task SendConfirmationEmail(ConfirmationEmailDto data)
|
||||
{
|
||||
var emailLink = (await _unitOfWork.SettingsRepository.GetSettingAsync(ServerSettingKey.EmailServiceUrl)).Value;
|
||||
|
|
@ -138,8 +145,15 @@ public class EmailService : IEmailService
|
|||
// This is the only exception for using the default because we need an external service to check if the server is accessible for emails
|
||||
try
|
||||
{
|
||||
if (IsLocalIpAddress(host)) return false;
|
||||
return await SendEmailWithGet(DefaultApiUrl + "/api/reachable?host=" + host);
|
||||
if (IsLocalIpAddress(host))
|
||||
{
|
||||
_logger.LogDebug("[EmailService] Server is not accessible, using local ip");
|
||||
return false;
|
||||
}
|
||||
|
||||
var url = DefaultApiUrl + "/api/reachable?host=" + host;
|
||||
_logger.LogDebug("[EmailService] Checking if this server is accessible for sending an email to: {Url}", url);
|
||||
return await SendEmailWithGet(url);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -288,7 +288,7 @@ public class ImageService : IImageService
|
|||
|
||||
// Create the destination file path
|
||||
using var image = Image.PngloadStream(faviconStream);
|
||||
var filename = $"{domain}{encodeFormat.GetExtension()}";
|
||||
var filename = ImageService.GetWebLinkFormat(baseUrl, encodeFormat);
|
||||
switch (encodeFormat)
|
||||
{
|
||||
case EncodeFormat.PNG:
|
||||
|
|
|
|||
|
|
@ -197,7 +197,7 @@ public class MediaConversionService : IMediaConversionService
|
|||
foreach (var volume in nonCustomOrConvertedVolumeCovers)
|
||||
{
|
||||
if (string.IsNullOrEmpty(volume.CoverImage)) continue;
|
||||
volume.CoverImage = volume.Chapters.MinBy(x => double.Parse(x.Number), ChapterSortComparerZeroFirst.Default)?.CoverImage;
|
||||
volume.CoverImage = volume.Chapters.MinBy(x => x.Number.AsDouble(), ChapterSortComparerZeroFirst.Default)?.CoverImage;
|
||||
_unitOfWork.VolumeRepository.Update(volume);
|
||||
await _unitOfWork.CommitAsync();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -107,7 +107,7 @@ public class MetadataService : IMetadataService
|
|||
|
||||
|
||||
volume.Chapters ??= new List<Chapter>();
|
||||
var firstChapter = volume.Chapters.MinBy(x => double.Parse(x.Number), ChapterSortComparerZeroFirst.Default);
|
||||
var firstChapter = volume.Chapters.MinBy(x => x.Number.AsDouble(), ChapterSortComparerZeroFirst.Default);
|
||||
if (firstChapter == null) return Task.FromResult(false);
|
||||
|
||||
volume.CoverImage = firstChapter.CoverImage;
|
||||
|
|
|
|||
|
|
@ -526,6 +526,7 @@ public class ScrobblingService : IScrobblingService
|
|||
foreach (var series in seriesWithProgress)
|
||||
{
|
||||
if (!libAllowsScrobbling[series.LibraryId]) continue;
|
||||
if (series.PagesRead <= 0) continue; // Since we only scrobble when things are higher, we can
|
||||
await ScrobbleReadingUpdate(uId, series.Id);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
|
@ -245,6 +246,9 @@ public class ReaderService : IReaderService
|
|||
var userProgress =
|
||||
await _unitOfWork.AppUserProgressRepository.GetUserProgressAsync(progressDto.ChapterId, userId);
|
||||
|
||||
// Don't create an empty progress record if there isn't any progress. This prevents Last Read date from being updated when
|
||||
// opening a chapter
|
||||
if (userProgress == null && progressDto.PageNum == 0) return true;
|
||||
|
||||
if (userProgress == null)
|
||||
{
|
||||
|
|
@ -367,16 +371,16 @@ public class ReaderService : IReaderService
|
|||
if (chapterId > 0) return chapterId;
|
||||
}
|
||||
|
||||
var currentVolumeNumber = float.Parse(currentVolume.Name);
|
||||
var currentVolumeNumber = currentVolume.Name.AsFloat();
|
||||
var next = false;
|
||||
foreach (var volume in volumes)
|
||||
{
|
||||
var volumeNumbersMatch = Math.Abs(float.Parse(volume.Name) - currentVolumeNumber) < 0.00001f;
|
||||
var volumeNumbersMatch = Math.Abs(volume.Name.AsFloat() - currentVolumeNumber) < 0.00001f;
|
||||
if (volumeNumbersMatch && volume.Chapters.Count > 1)
|
||||
{
|
||||
// Handle Chapters within current Volume
|
||||
// In this case, i need 0 first because 0 represents a full volume file.
|
||||
var chapterId = GetNextChapterId(currentVolume.Chapters.OrderBy(x => double.Parse(x.Number), _chapterSortComparer),
|
||||
var chapterId = GetNextChapterId(currentVolume.Chapters.OrderBy(x => x.Number.AsFloat(), _chapterSortComparer),
|
||||
currentChapter.Range, dto => dto.Range);
|
||||
if (chapterId > 0) return chapterId;
|
||||
next = true;
|
||||
|
|
@ -393,7 +397,7 @@ public class ReaderService : IReaderService
|
|||
|
||||
// Handle Chapters within next Volume
|
||||
// ! When selecting the chapter for the next volume, we need to make sure a c0 comes before a c1+
|
||||
var chapters = volume.Chapters.OrderBy(x => double.Parse(x.Number), _chapterSortComparer).ToList();
|
||||
var chapters = volume.Chapters.OrderBy(x => x.Number.AsDouble(), _chapterSortComparer).ToList();
|
||||
if (currentChapter.Number.Equals(Parser.DefaultChapter) && chapters.Last().Number.Equals(Parser.DefaultChapter))
|
||||
{
|
||||
// We need to handle an extra check if the current chapter is the last special, as we should return -1
|
||||
|
|
@ -410,9 +414,9 @@ public class ReaderService : IReaderService
|
|||
var chapterId = GetNextChapterId(volume.Chapters.OrderByNatural(x => x.Number),
|
||||
currentChapter.Range, dto => dto.Range);
|
||||
if (chapterId > 0) return chapterId;
|
||||
} else if (double.Parse(firstChapter.Number) >= double.Parse(currentChapter.Number)) return firstChapter.Id;
|
||||
} else if (firstChapter.Number.AsDouble() >= currentChapter.Number.AsDouble()) return firstChapter.Id;
|
||||
// If we are the last chapter and next volume is there, we should try to use it (unless it's volume 0)
|
||||
else if (double.Parse(firstChapter.Number) == 0) return firstChapter.Id;
|
||||
else if (firstChapter.Number.AsDouble() == 0) return firstChapter.Id;
|
||||
|
||||
// If on last volume AND there are no specials left, then let's return -1
|
||||
var anySpecials = volumes.Where(v => $"{v.Number}" == Parser.DefaultVolume)
|
||||
|
|
@ -439,16 +443,16 @@ public class ReaderService : IReaderService
|
|||
// if (currentVolume.Number == orderedVolumes.FirstOrDefault().Number)
|
||||
// {
|
||||
// // We can move into loose leaf chapters
|
||||
// //var firstLooseLeaf = volumes.LastOrDefault().Chapters.MinBy(x => double.Parse(x.Number), _chapterSortComparer);
|
||||
// //var firstLooseLeaf = volumes.LastOrDefault().Chapters.MinBy(x => x.Number.AsDouble(), _chapterSortComparer);
|
||||
// var nextChapterId = GetNextChapterId(
|
||||
// volumes.LastOrDefault().Chapters.OrderBy(x => double.Parse(x.Number), _chapterSortComparer),
|
||||
// volumes.LastOrDefault().Chapters.OrderBy(x => x.Number.AsDouble(), _chapterSortComparer),
|
||||
// "0", dto => dto.Range);
|
||||
// // CHECK if we need a IsSpecial check
|
||||
// if (nextChapterId > 0) return nextChapterId;
|
||||
// }
|
||||
|
||||
|
||||
var firstChapter = chapterVolume.Chapters.MinBy(x => double.Parse(x.Number), _chapterSortComparer);
|
||||
var firstChapter = chapterVolume.Chapters.MinBy(x => x.Number.AsDouble(), _chapterSortComparer);
|
||||
if (firstChapter == null) return -1;
|
||||
|
||||
|
||||
|
|
@ -486,7 +490,7 @@ public class ReaderService : IReaderService
|
|||
{
|
||||
if (volume.Number == currentVolume.Number)
|
||||
{
|
||||
var chapterId = GetNextChapterId(currentVolume.Chapters.OrderBy(x => double.Parse(x.Number), _chapterSortComparerForInChapterSorting).Reverse(),
|
||||
var chapterId = GetNextChapterId(currentVolume.Chapters.OrderBy(x => x.Number.AsDouble(), _chapterSortComparerForInChapterSorting).Reverse(),
|
||||
currentChapter.Range, dto => dto.Range);
|
||||
if (chapterId > 0) return chapterId;
|
||||
next = true; // When the diff between volumes is more than 1, we need to explicitly tell that next volume is our use case
|
||||
|
|
@ -495,7 +499,7 @@ public class ReaderService : IReaderService
|
|||
if (next)
|
||||
{
|
||||
if (currentVolume.Number - 1 == 0) break; // If we have walked all the way to chapter volume, then we should break so logic outside can work
|
||||
var lastChapter = volume.Chapters.MaxBy(x => double.Parse(x.Number), _chapterSortComparerForInChapterSorting);
|
||||
var lastChapter = volume.Chapters.MaxBy(x => x.Number.AsDouble(), _chapterSortComparerForInChapterSorting);
|
||||
if (lastChapter == null) return -1;
|
||||
return lastChapter.Id;
|
||||
}
|
||||
|
|
@ -504,7 +508,7 @@ public class ReaderService : IReaderService
|
|||
var lastVolume = volumes.MaxBy(v => v.Number);
|
||||
if (currentVolume.Number == 0 && currentVolume.Number != lastVolume?.Number && lastVolume?.Chapters.Count > 1)
|
||||
{
|
||||
var lastChapter = lastVolume.Chapters.MaxBy(x => double.Parse(x.Number), _chapterSortComparerForInChapterSorting);
|
||||
var lastChapter = lastVolume.Chapters.MaxBy(x => x.Number.AsDouble(), _chapterSortComparerForInChapterSorting);
|
||||
if (lastChapter == null) return -1;
|
||||
return lastChapter.Id;
|
||||
}
|
||||
|
|
@ -527,8 +531,8 @@ public class ReaderService : IReaderService
|
|||
if (!await _unitOfWork.AppUserProgressRepository.AnyUserProgressForSeriesAsync(seriesId, userId))
|
||||
{
|
||||
// I think i need a way to sort volumes last
|
||||
return volumes.OrderBy(v => double.Parse(v.Number + string.Empty), _chapterSortComparer).First().Chapters
|
||||
.OrderBy(c => float.Parse(c.Number)).First();
|
||||
return volumes.OrderBy(v => v.Number.ToString(CultureInfo.InvariantCulture).AsDouble(), _chapterSortComparer).First().Chapters
|
||||
.OrderBy(c => c.Number.AsFloat()).First();
|
||||
}
|
||||
|
||||
// Loop through all chapters that are not in volume 0
|
||||
|
|
@ -540,13 +544,14 @@ public class ReaderService : IReaderService
|
|||
// NOTE: If volume 1 has chapter 1 and volume 2 is just chapter 0 due to being a full volume file, then this fails
|
||||
// If there are any volumes that have progress, return those. If not, move on.
|
||||
var currentlyReadingChapter = volumeChapters
|
||||
.OrderBy(c => double.Parse(c.Number), _chapterSortComparer)
|
||||
.OrderBy(c => c.Number.AsDouble(), _chapterSortComparer)
|
||||
.FirstOrDefault(chapter => chapter.PagesRead < chapter.Pages && chapter.PagesRead > 0);
|
||||
if (currentlyReadingChapter != null) return currentlyReadingChapter;
|
||||
|
||||
// Order with volume 0 last so we prefer the natural order
|
||||
return FindNextReadingChapter(volumes.OrderBy(v => v.Number, SortComparerZeroLast.Default)
|
||||
.SelectMany(v => v.Chapters.OrderBy(c => double.Parse(c.Number))).ToList());
|
||||
.SelectMany(v => v.Chapters.OrderBy(c => c.Number.AsDouble()))
|
||||
.ToList());
|
||||
}
|
||||
|
||||
private static ChapterDto FindNextReadingChapter(IList<ChapterDto> volumeChapters)
|
||||
|
|
@ -616,8 +621,8 @@ public class ReaderService : IReaderService
|
|||
foreach (var volume in volumes.OrderBy(v => v.Number))
|
||||
{
|
||||
var chapters = volume.Chapters
|
||||
.Where(c => !c.IsSpecial && Tasks.Scanner.Parser.Parser.MaxNumberFromRange(c.Range) <= chapterNumber)
|
||||
.OrderBy(c => float.Parse(c.Number));
|
||||
.Where(c => !c.IsSpecial && Parser.MaxNumberFromRange(c.Range) <= chapterNumber)
|
||||
.OrderBy(c => c.Number.AsFloat());
|
||||
await MarkChaptersAsRead(user, volume.SeriesId, chapters.ToList());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ using API.DTOs.ReadingLists;
|
|||
using API.DTOs.ReadingLists.CBL;
|
||||
using API.Entities;
|
||||
using API.Entities.Enums;
|
||||
using API.Extensions;
|
||||
using API.Helpers;
|
||||
using API.Helpers.Builders;
|
||||
using API.Services.Tasks.Scanner.Parser;
|
||||
|
|
@ -390,7 +391,7 @@ public class ReadingListService : IReadingListService
|
|||
var existingChapterExists = readingList.Items.Select(rli => rli.ChapterId).ToHashSet();
|
||||
var chaptersForSeries = (await _unitOfWork.ChapterRepository.GetChaptersByIdsAsync(chapterIds, ChapterIncludes.Volumes))
|
||||
.OrderBy(c => Parser.MinNumberFromRange(c.Volume.Name))
|
||||
.ThenBy(x => double.Parse(x.Number), _chapterSortComparerForInChapterSorting)
|
||||
.ThenBy(x => x.Number.AsDouble(), _chapterSortComparerForInChapterSorting)
|
||||
.ToList();
|
||||
|
||||
var index = readingList.Items.Count == 0 ? 0 : lastOrder + 1;
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ using API.DTOs.SeriesDetail;
|
|||
using API.Entities;
|
||||
using API.Entities.Enums;
|
||||
using API.Entities.Metadata;
|
||||
using API.Extensions;
|
||||
using API.Helpers;
|
||||
using API.Helpers.Builders;
|
||||
using API.Services.Plus;
|
||||
|
|
@ -77,20 +78,22 @@ public class SeriesService : ISeriesService
|
|||
public static Chapter? GetFirstChapterForMetadata(Series series)
|
||||
{
|
||||
var sortedVolumes = series.Volumes
|
||||
.Where(v => float.TryParse(v.Name, out var parsedValue) && parsedValue != 0.0f)
|
||||
.OrderBy(v => float.TryParse(v.Name, out var parsedValue) ? parsedValue : float.MaxValue);
|
||||
.Where(v => float.TryParse(v.Name, CultureInfo.InvariantCulture, out var parsedValue) && parsedValue != 0.0f)
|
||||
.OrderBy(v => float.TryParse(v.Name, CultureInfo.InvariantCulture, out var parsedValue) ? parsedValue : float.MaxValue);
|
||||
var minVolumeNumber = sortedVolumes
|
||||
.MinBy(v => float.Parse(v.Name));
|
||||
.MinBy(v => v.Name.AsFloat());
|
||||
|
||||
|
||||
var allChapters = series.Volumes
|
||||
.SelectMany(v => v.Chapters.OrderBy(c => float.Parse(c.Number), ChapterSortComparer.Default)).ToList();
|
||||
var minChapter = allChapters
|
||||
.SelectMany(v => v.Chapters.OrderBy(c => c.Number.AsFloat(), ChapterSortComparer.Default))
|
||||
.ToList();
|
||||
var minChapter = allChapters
|
||||
.FirstOrDefault();
|
||||
|
||||
if (minVolumeNumber != null && minChapter != null && float.TryParse(minChapter.Number, out var chapNum) && chapNum >= minVolumeNumber.Number)
|
||||
if (minVolumeNumber != null && minChapter != null && float.TryParse(minChapter.Number, CultureInfo.InvariantCulture, out var chapNum) &&
|
||||
(chapNum >= minVolumeNumber.Number || chapNum == 0))
|
||||
{
|
||||
return minVolumeNumber.Chapters.MinBy(c => float.Parse(c.Number), ChapterSortComparer.Default);
|
||||
return minVolumeNumber.Chapters.MinBy(c => c.Number.AsFloat(), ChapterSortComparer.Default);
|
||||
}
|
||||
|
||||
return minChapter;
|
||||
|
|
@ -436,7 +439,9 @@ public class SeriesService : ISeriesService
|
|||
var volumeLabel = await _localizationService.Translate(userId, "volume-num", string.Empty);
|
||||
foreach (var volume in volumes)
|
||||
{
|
||||
volume.Chapters = volume.Chapters.OrderBy(d => double.Parse(d.Number), ChapterSortComparer.Default).ToList();
|
||||
volume.Chapters = volume.Chapters
|
||||
.OrderBy(d => d.Number.AsDouble(), ChapterSortComparer.Default)
|
||||
.ToList();
|
||||
var firstChapter = volume.Chapters.First();
|
||||
// On Books, skip volumes that are specials, since these will be shown
|
||||
if (firstChapter.IsSpecial) continue;
|
||||
|
|
@ -450,7 +455,7 @@ public class SeriesService : ISeriesService
|
|||
processedVolumes.ForEach(v =>
|
||||
{
|
||||
v.Name = $"Volume {v.Name}";
|
||||
v.Chapters = v.Chapters.OrderBy(d => double.Parse(d.Number), ChapterSortComparer.Default).ToList();
|
||||
v.Chapters = v.Chapters.OrderBy(d => d.Number.AsDouble(), ChapterSortComparer.Default).ToList();
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -460,7 +465,7 @@ public class SeriesService : ISeriesService
|
|||
if (v.Number == 0) return c;
|
||||
c.VolumeTitle = v.Name;
|
||||
return c;
|
||||
}).OrderBy(c => float.Parse(c.Number), ChapterSortComparer.Default)).ToList();
|
||||
}).OrderBy(c => c.Number.AsFloat(), ChapterSortComparer.Default)).ToList();
|
||||
|
||||
foreach (var chapter in chapters)
|
||||
{
|
||||
|
|
@ -485,12 +490,12 @@ public class SeriesService : ISeriesService
|
|||
var storylineChapters = volumes
|
||||
.Where(v => v.Number == 0)
|
||||
.SelectMany(v => v.Chapters.Where(c => !c.IsSpecial))
|
||||
.OrderBy(c => float.Parse(c.Number), ChapterSortComparer.Default)
|
||||
.OrderBy(c => c.Number.AsFloat(), ChapterSortComparer.Default)
|
||||
.ToList();
|
||||
|
||||
// When there's chapters without a volume number revert to chapter sorting only as opposed to volume then chapter
|
||||
if (storylineChapters.Any()) {
|
||||
retChapters = retChapters.OrderBy(c => float.Parse(c.Number), ChapterSortComparer.Default);
|
||||
retChapters = retChapters.OrderBy(c => c.Number.AsFloat(), ChapterSortComparer.Default);
|
||||
}
|
||||
|
||||
return new SeriesDetailDto()
|
||||
|
|
@ -717,33 +722,8 @@ public class SeriesService : ISeriesService
|
|||
? chapters.Max(c => c.CreatedUtc) + TimeSpan.FromDays(forecastedTimeDifference)
|
||||
: (DateTime?)null;
|
||||
|
||||
// if (nextChapterExpected != null && nextChapterExpected < DateTime.UtcNow)
|
||||
// {
|
||||
// nextChapterExpected = DateTime.UtcNow + TimeSpan.FromDays(forecastedTimeDifference);
|
||||
// }
|
||||
//
|
||||
// var averageTimeDifference = timeDifferences
|
||||
// .Average(td => td.TotalDays);
|
||||
//
|
||||
//
|
||||
// if (averageTimeDifference == 0)
|
||||
// {
|
||||
// return _emptyExpectedChapter;
|
||||
// }
|
||||
//
|
||||
//
|
||||
// // Calculate the forecast for when the next chapter is expected
|
||||
// var nextChapterExpected = chapters.Any()
|
||||
// ? chapters.Max(c => c.CreatedUtc) + TimeSpan.FromDays(averageTimeDifference)
|
||||
// : (DateTime?) null;
|
||||
//
|
||||
// if (nextChapterExpected != null && nextChapterExpected < DateTime.UtcNow)
|
||||
// {
|
||||
// nextChapterExpected = DateTime.UtcNow + TimeSpan.FromDays(averageTimeDifference);
|
||||
// }
|
||||
|
||||
// For number and volume number, we need the highest chapter, not the latest created
|
||||
var lastChapter = chapters.MaxBy(c => float.Parse(c.Number))!;
|
||||
var lastChapter = chapters.MaxBy(c => c.Number.AsFloat())!;
|
||||
float.TryParse(lastChapter.Number, NumberStyles.Number, CultureInfo.InvariantCulture,
|
||||
out var lastChapterNumber);
|
||||
|
||||
|
|
@ -759,7 +739,7 @@ public class SeriesService : ISeriesService
|
|||
|
||||
if (lastChapterNumber > 0)
|
||||
{
|
||||
result.ChapterNumber = lastChapterNumber + 1;
|
||||
result.ChapterNumber = (int) Math.Truncate(lastChapterNumber) + 1;
|
||||
result.VolumeNumber = lastChapter.Volume.Number;
|
||||
result.Title = series.Library.Type switch
|
||||
{
|
||||
|
|
@ -783,9 +763,9 @@ public class SeriesService : ISeriesService
|
|||
return result;
|
||||
}
|
||||
|
||||
private double ExponentialSmoothing(IEnumerable<double> data, double alpha)
|
||||
private static double ExponentialSmoothing(IList<double> data, double alpha)
|
||||
{
|
||||
double forecast = data.First();
|
||||
var forecast = data.First();
|
||||
|
||||
foreach (var value in data)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ using System.Globalization;
|
|||
using System.Linq;
|
||||
using API.Comparators;
|
||||
using API.Entities;
|
||||
using API.Extensions;
|
||||
using AutoMapper;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
|
|
@ -70,7 +71,10 @@ public class TachiyomiService : ITachiyomiService
|
|||
var looseLeafChapterVolume = volumes.Find(v => v.Number == 0);
|
||||
if (looseLeafChapterVolume == null)
|
||||
{
|
||||
var volumeChapter = _mapper.Map<ChapterDto>(volumes.Last().Chapters.OrderBy(c => float.Parse(c.Number), ChapterSortComparerZeroFirst.Default).Last());
|
||||
var volumeChapter = _mapper.Map<ChapterDto>(volumes
|
||||
.Last().Chapters
|
||||
.OrderBy(c => c.Number.AsFloat(), ChapterSortComparerZeroFirst.Default)
|
||||
.Last());
|
||||
if (volumeChapter.Number == "0")
|
||||
{
|
||||
var volume = volumes.First(v => v.Id == volumeChapter.VolumeId);
|
||||
|
|
@ -88,7 +92,9 @@ public class TachiyomiService : ITachiyomiService
|
|||
};
|
||||
}
|
||||
|
||||
var lastChapter = looseLeafChapterVolume.Chapters.OrderBy(c => float.Parse(c.Number), ChapterSortComparer.Default).Last();
|
||||
var lastChapter = looseLeafChapterVolume.Chapters
|
||||
.OrderBy(c => double.Parse(c.Number, CultureInfo.InvariantCulture), ChapterSortComparer.Default)
|
||||
.Last();
|
||||
return _mapper.Map<ChapterDto>(lastChapter);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -194,7 +194,7 @@ public class WordCountAnalyzerService : IWordCountAnalyzerService
|
|||
_logger.LogError(ex, "There was an error reading an epub file for word count, series skipped");
|
||||
await _eventHub.SendMessageAsync(MessageFactory.Error,
|
||||
MessageFactory.ErrorEvent("There was an issue counting words on an epub",
|
||||
$"{series.Name} - {file}"));
|
||||
$"{series.Name} - {file.FilePath}"));
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -279,9 +279,23 @@ public class ParseScannedFiles
|
|||
IEnumerable<string> folders, string libraryName, bool isLibraryScan,
|
||||
IDictionary<string, IList<SeriesModified>> seriesPaths, Func<Tuple<bool, IList<ParserInfo>>, Task>? processSeriesInfos, bool forceCheck = false)
|
||||
{
|
||||
|
||||
await _eventHub.SendMessageAsync(MessageFactory.NotificationProgress, MessageFactory.FileScanProgressEvent("File Scan Starting", libraryName, ProgressEventType.Started));
|
||||
|
||||
foreach (var folderPath in folders)
|
||||
{
|
||||
try
|
||||
{
|
||||
await ProcessFiles(folderPath, isLibraryScan, seriesPaths, ProcessFolder, forceCheck);
|
||||
}
|
||||
catch (ArgumentException ex)
|
||||
{
|
||||
_logger.LogError(ex, "[ScannerService] The directory '{FolderPath}' does not exist", folderPath);
|
||||
}
|
||||
}
|
||||
|
||||
await _eventHub.SendMessageAsync(MessageFactory.NotificationProgress, MessageFactory.FileScanProgressEvent("File Scan Done", libraryName, ProgressEventType.Ended));
|
||||
return;
|
||||
|
||||
async Task ProcessFolder(IList<string> files, string folder)
|
||||
{
|
||||
var normalizedFolder = Parser.Parser.NormalizePath(folder);
|
||||
|
|
@ -340,21 +354,6 @@ public class ParseScannedFiles
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
foreach (var folderPath in folders)
|
||||
{
|
||||
try
|
||||
{
|
||||
await ProcessFiles(folderPath, isLibraryScan, seriesPaths, ProcessFolder, forceCheck);
|
||||
}
|
||||
catch (ArgumentException ex)
|
||||
{
|
||||
_logger.LogError(ex, "[ScannerService] The directory '{FolderPath}' does not exist", folderPath);
|
||||
}
|
||||
}
|
||||
|
||||
await _eventHub.SendMessageAsync(MessageFactory.NotificationProgress, MessageFactory.FileScanProgressEvent("File Scan Done", libraryName, ProgressEventType.Ended));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ public class DefaultParser : IDefaultParser
|
|||
|
||||
ParserInfo ret;
|
||||
|
||||
if (Parser.IsEpub(filePath))
|
||||
if (Parser.IsEpub(filePath)) // NOTE: Will this ever be called? Because we use ReadingService to handle parse
|
||||
{
|
||||
ret = new ParserInfo
|
||||
{
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ using System.IO;
|
|||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using API.Entities.Enums;
|
||||
using API.Extensions;
|
||||
|
||||
namespace API.Services.Tasks.Scanner.Parser;
|
||||
|
||||
|
|
@ -927,7 +928,7 @@ public static class Parser
|
|||
}
|
||||
|
||||
var tokens = range.Replace("_", string.Empty).Split("-");
|
||||
return tokens.Min(float.Parse);
|
||||
return tokens.Min(t => t.AsFloat());
|
||||
}
|
||||
catch
|
||||
{
|
||||
|
|
@ -945,7 +946,7 @@ public static class Parser
|
|||
}
|
||||
|
||||
var tokens = range.Replace("_", string.Empty).Split("-");
|
||||
return tokens.Max(float.Parse);
|
||||
return tokens.Max(t => t.AsFloat());
|
||||
}
|
||||
catch
|
||||
{
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using API.Data;
|
||||
|
|
@ -21,6 +22,8 @@ using Microsoft.Extensions.Logging;
|
|||
|
||||
namespace API.Services.Tasks.Scanner;
|
||||
|
||||
#nullable enable
|
||||
|
||||
public interface IProcessSeries
|
||||
{
|
||||
/// <summary>
|
||||
|
|
@ -208,7 +211,7 @@ public class ProcessSeries : IProcessSeries
|
|||
.ToList()}));
|
||||
|
||||
await _eventHub.SendMessageAsync(MessageFactory.Error,
|
||||
MessageFactory.ErrorEvent($"There was an issue writing to the DB for Series {series}",
|
||||
MessageFactory.ErrorEvent($"There was an issue writing to the DB for Series {series.OriginalName}",
|
||||
ex.Message));
|
||||
return;
|
||||
}
|
||||
|
|
@ -614,7 +617,7 @@ public class ProcessSeries : IProcessSeries
|
|||
// Add files
|
||||
var specialTreatment = info.IsSpecialInfo();
|
||||
AddOrUpdateFileForChapter(chapter, info, forceUpdate);
|
||||
chapter.Number = Parser.Parser.MinNumberFromRange(info.Chapters) + string.Empty;
|
||||
chapter.Number = Parser.Parser.MinNumberFromRange(info.Chapters).ToString(CultureInfo.InvariantCulture);
|
||||
chapter.Range = specialTreatment ? info.Filename : info.Chapters;
|
||||
}
|
||||
|
||||
|
|
@ -886,7 +889,7 @@ public class ProcessSeries : IProcessSeries
|
|||
}
|
||||
}
|
||||
|
||||
action(genre, newTag);
|
||||
action(genre!, newTag);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -240,27 +240,6 @@ public class ScannerService : IScannerService
|
|||
await _eventHub.SendMessageAsync(MessageFactory.NotificationProgress, MessageFactory.LibraryScanProgressEvent(library.Name, ProgressEventType.Started, series.Name));
|
||||
|
||||
await _processSeries.Prime();
|
||||
async Task TrackFiles(Tuple<bool, IList<ParserInfo>> parsedInfo)
|
||||
{
|
||||
var parsedFiles = parsedInfo.Item2;
|
||||
if (parsedFiles.Count == 0) return;
|
||||
|
||||
var foundParsedSeries = new ParsedSeries()
|
||||
{
|
||||
Name = parsedFiles[0].Series,
|
||||
NormalizedName = parsedFiles[0].Series.ToNormalized(),
|
||||
Format = parsedFiles[0].Format
|
||||
};
|
||||
|
||||
// For Scan Series, we need to filter out anything that isn't our Series
|
||||
if (!foundParsedSeries.NormalizedName.Equals(series.NormalizedName) && !foundParsedSeries.NormalizedName.Equals(series.OriginalName?.ToNormalized()))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
await _processSeries.ProcessSeriesAsync(parsedFiles, library, bypassFolderOptimizationChecks);
|
||||
parsedSeries.Add(foundParsedSeries, parsedFiles);
|
||||
}
|
||||
|
||||
_logger.LogInformation("Beginning file scan on {SeriesName}", series.Name);
|
||||
var scanElapsedTime = await ScanFiles(library, new []{ folderPath }, false, TrackFiles, true);
|
||||
|
|
@ -317,6 +296,29 @@ public class ScannerService : IScannerService
|
|||
BackgroundJob.Enqueue(() => _wordCountAnalyzerService.ScanSeries(library.Id, seriesId, false));
|
||||
BackgroundJob.Enqueue(() => _cacheService.CleanupChapters(chapterIds));
|
||||
BackgroundJob.Enqueue(() => _directoryService.ClearDirectory(_directoryService.TempDirectory));
|
||||
return;
|
||||
|
||||
async Task TrackFiles(Tuple<bool, IList<ParserInfo>> parsedInfo)
|
||||
{
|
||||
var parsedFiles = parsedInfo.Item2;
|
||||
if (parsedFiles.Count == 0) return;
|
||||
|
||||
var foundParsedSeries = new ParsedSeries()
|
||||
{
|
||||
Name = parsedFiles[0].Series,
|
||||
NormalizedName = parsedFiles[0].Series.ToNormalized(),
|
||||
Format = parsedFiles[0].Format
|
||||
};
|
||||
|
||||
// For Scan Series, we need to filter out anything that isn't our Series
|
||||
if (!foundParsedSeries.NormalizedName.Equals(series.NormalizedName) && !foundParsedSeries.NormalizedName.Equals(series.OriginalName?.ToNormalized()))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
await _processSeries.ProcessSeriesAsync(parsedFiles, library, bypassFolderOptimizationChecks);
|
||||
parsedSeries.Add(foundParsedSeries, parsedFiles);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<ScanCancelReason> ShouldScanSeries(int seriesId, Library library, IList<string> libraryPaths, Series series, bool bypassFolderChecks = false)
|
||||
|
|
@ -488,38 +490,6 @@ public class ScannerService : IScannerService
|
|||
await _processSeries.Prime();
|
||||
var processTasks = new List<Func<Task>>();
|
||||
|
||||
Task TrackFiles(Tuple<bool, IList<ParserInfo>> parsedInfo)
|
||||
{
|
||||
var skippedScan = parsedInfo.Item1;
|
||||
var parsedFiles = parsedInfo.Item2;
|
||||
if (parsedFiles.Count == 0) return Task.CompletedTask;
|
||||
|
||||
var foundParsedSeries = new ParsedSeries()
|
||||
{
|
||||
Name = parsedFiles[0].Series,
|
||||
NormalizedName = Scanner.Parser.Parser.Normalize(parsedFiles[0].Series),
|
||||
Format = parsedFiles[0].Format
|
||||
};
|
||||
|
||||
if (skippedScan)
|
||||
{
|
||||
seenSeries.AddRange(parsedFiles.Select(pf => new ParsedSeries()
|
||||
{
|
||||
Name = pf.Series,
|
||||
NormalizedName = Scanner.Parser.Parser.Normalize(pf.Series),
|
||||
Format = pf.Format
|
||||
}));
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
totalFiles += parsedFiles.Count;
|
||||
|
||||
|
||||
seenSeries.Add(foundParsedSeries);
|
||||
processTasks.Add(async () => await _processSeries.ProcessSeriesAsync(parsedFiles, library, forceUpdate));
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
var scanElapsedTime = await ScanFiles(library, libraryFolderPaths, shouldUseLibraryScan, TrackFiles, forceUpdate);
|
||||
|
||||
// NOTE: This runs sync after every file is scanned
|
||||
|
|
@ -592,6 +562,39 @@ public class ScannerService : IScannerService
|
|||
await _metadataService.RemoveAbandonedMetadataKeys();
|
||||
|
||||
BackgroundJob.Enqueue(() => _directoryService.ClearDirectory(_directoryService.TempDirectory));
|
||||
return;
|
||||
|
||||
Task TrackFiles(Tuple<bool, IList<ParserInfo>> parsedInfo)
|
||||
{
|
||||
var skippedScan = parsedInfo.Item1;
|
||||
var parsedFiles = parsedInfo.Item2;
|
||||
if (parsedFiles.Count == 0) return Task.CompletedTask;
|
||||
|
||||
var foundParsedSeries = new ParsedSeries()
|
||||
{
|
||||
Name = parsedFiles[0].Series,
|
||||
NormalizedName = Scanner.Parser.Parser.Normalize(parsedFiles[0].Series),
|
||||
Format = parsedFiles[0].Format
|
||||
};
|
||||
|
||||
if (skippedScan)
|
||||
{
|
||||
seenSeries.AddRange(parsedFiles.Select(pf => new ParsedSeries()
|
||||
{
|
||||
Name = pf.Series,
|
||||
NormalizedName = Scanner.Parser.Parser.Normalize(pf.Series),
|
||||
Format = pf.Format
|
||||
}));
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
totalFiles += parsedFiles.Count;
|
||||
|
||||
|
||||
seenSeries.Add(foundParsedSeries);
|
||||
processTasks.Add(async () => await _processSeries.ProcessSeriesAsync(parsedFiles, library, forceUpdate));
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<long> ScanFiles(Library library, IEnumerable<string> dirs,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue