Lots of changes to get magazines working.
Some notes is that magazines in reading list mode do not properly load up. Parsing code still needs some work. Need to restrict to really just a small set of conventions until community can give me real data to code against.
This commit is contained in:
parent
95e7ad0f5b
commit
b3f6a574cd
23 changed files with 315 additions and 62 deletions
|
@ -82,6 +82,7 @@ public class BookController : BaseApiController
|
|||
SeriesFormat = dto.SeriesFormat,
|
||||
SeriesId = dto.SeriesId,
|
||||
LibraryId = dto.LibraryId,
|
||||
LibraryType = dto.LibraryType,
|
||||
IsSpecial = dto.IsSpecial,
|
||||
Pages = dto.Pages,
|
||||
});
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
|
@ -504,4 +505,14 @@ public class LibraryController : BaseApiController
|
|||
{
|
||||
return Ok(await _unitOfWork.LibraryRepository.GetLibraryTypeAsync(libraryId));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return pairs of all types
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[HttpGet("types")]
|
||||
public async Task<ActionResult<IEnumerable<LibraryTypeDto>>> GetLibraryTypes()
|
||||
{
|
||||
return Ok(await _unitOfWork.LibraryRepository.GetLibraryTypesAsync(User.GetUserId()));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -244,6 +244,7 @@ public class ReaderController : BaseApiController
|
|||
SeriesFormat = dto.SeriesFormat,
|
||||
SeriesId = dto.SeriesId,
|
||||
LibraryId = dto.LibraryId,
|
||||
LibraryType = dto.LibraryType,
|
||||
IsSpecial = dto.IsSpecial,
|
||||
Pages = dto.Pages,
|
||||
SeriesTotalPages = series.Pages,
|
||||
|
@ -284,6 +285,7 @@ public class ReaderController : BaseApiController
|
|||
return Ok(info);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Returns various information about all bookmark files for a Series. Side effect: This will cache the bookmark images for reading.
|
||||
/// </summary>
|
||||
|
|
12
API/DTOs/LibraryTypeDto.cs
Normal file
12
API/DTOs/LibraryTypeDto.cs
Normal file
|
@ -0,0 +1,12 @@
|
|||
using API.Entities.Enums;
|
||||
|
||||
namespace API.DTOs;
|
||||
|
||||
/// <summary>
|
||||
/// Simple pairing of LibraryId and LibraryType
|
||||
/// </summary>
|
||||
public class LibraryTypeDto
|
||||
{
|
||||
public int LibraryId { get; set; }
|
||||
public LibraryType LibraryType { get; set; }
|
||||
}
|
|
@ -15,4 +15,5 @@ public class BookInfoDto : IChapterInfoDto
|
|||
public int Pages { get; set; }
|
||||
public bool IsSpecial { get; set; }
|
||||
public string ChapterTitle { get; set; } = default! ;
|
||||
public LibraryType LibraryType { get; set; }
|
||||
}
|
||||
|
|
|
@ -14,5 +14,6 @@ public interface IChapterInfoDto
|
|||
public int Pages { get; set; }
|
||||
public bool IsSpecial { get; set; }
|
||||
public string ChapterTitle { get; set; }
|
||||
public LibraryType LibraryType { get; set; }
|
||||
|
||||
}
|
||||
|
|
|
@ -112,11 +112,11 @@ public class ChapterRepository : IChapterRepository
|
|||
LibraryId = data.LibraryId,
|
||||
Pages = data.Pages,
|
||||
ChapterTitle = data.TitleName,
|
||||
LibraryType = data.LibraryType
|
||||
LibraryType = data.LibraryType,
|
||||
})
|
||||
.AsNoTracking()
|
||||
.AsSplitQuery()
|
||||
.SingleOrDefaultAsync();
|
||||
.FirstOrDefaultAsync();
|
||||
|
||||
return chapterInfo;
|
||||
}
|
||||
|
|
|
@ -57,6 +57,7 @@ public interface ILibraryRepository
|
|||
Task<bool> GetAllowsScrobblingBySeriesId(int seriesId);
|
||||
|
||||
Task<IDictionary<int, LibraryType>> GetLibraryTypesBySeriesIdsAsync(IList<int> seriesIds);
|
||||
Task<IEnumerable<LibraryTypeDto>> GetLibraryTypesAsync(int userId);
|
||||
}
|
||||
|
||||
public class LibraryRepository : ILibraryRepository
|
||||
|
@ -365,4 +366,17 @@ public class LibraryRepository : ILibraryRepository
|
|||
})
|
||||
.ToDictionaryAsync(entity => entity.Id, entity => entity.Type);
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<LibraryTypeDto>> GetLibraryTypesAsync(int userId)
|
||||
{
|
||||
return await _context.Library
|
||||
.Where(l => l.AppUsers.Any(u => u.Id == userId))
|
||||
.Select(l => new LibraryTypeDto()
|
||||
{
|
||||
LibraryType = l.Type,
|
||||
LibraryId = l.Id
|
||||
})
|
||||
.AsSplitQuery()
|
||||
.ToListAsync();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -34,6 +34,8 @@ public class DefaultParser : IDefaultParser
|
|||
public ParserInfo? Parse(string filePath, string rootPath, LibraryType type = LibraryType.Manga)
|
||||
{
|
||||
var fileName = _directoryService.FileSystem.Path.GetFileNameWithoutExtension(filePath);
|
||||
|
||||
// We can now remove this as there is the ability to turn off images for non-image libraries
|
||||
// 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;
|
||||
|
||||
|
@ -49,7 +51,7 @@ public class DefaultParser : IDefaultParser
|
|||
// If library type is Image or this is not a cover image in a non-image library, then use dedicated parsing mechanism
|
||||
if (type == LibraryType.Image || Parser.IsImage(filePath))
|
||||
{
|
||||
// TODO: We can move this up one level
|
||||
// TODO: We can move this up one level (out of DefaultParser - If we do different Parsers)
|
||||
return ParseImage(filePath, rootPath, ret);
|
||||
}
|
||||
|
||||
|
@ -136,21 +138,33 @@ public class DefaultParser : IDefaultParser
|
|||
var folders = _directoryService.GetFoldersTillRoot(libraryPath, filePath).ToList();
|
||||
// Usually the LAST folder is the Series and everything up to can have Volume
|
||||
|
||||
|
||||
if (string.IsNullOrEmpty(ret.Series))
|
||||
{
|
||||
ret.Series = Parser.CleanTitle(folders[^1]);
|
||||
}
|
||||
var hasGeoCode = !string.IsNullOrEmpty(Parser.ParseGeoCode(ret.Series));
|
||||
foreach (var folder in folders[..^1])
|
||||
{
|
||||
if (ret.Volumes == Parser.DefaultVolume)
|
||||
{
|
||||
var vol = Parser.ParseYear(folder);
|
||||
if (vol != folder)
|
||||
if (!string.IsNullOrEmpty(vol) && vol != folder)
|
||||
{
|
||||
ret.Volumes = vol;
|
||||
}
|
||||
}
|
||||
|
||||
// If folder has a language code in it, then we add that to the Series (Wired (UK))
|
||||
if (!hasGeoCode)
|
||||
{
|
||||
var geoCode = Parser.ParseGeoCode(folder);
|
||||
if (!string.IsNullOrEmpty(geoCode))
|
||||
{
|
||||
ret.Series = $"{ret.Series} ({geoCode})";
|
||||
hasGeoCode = true;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -638,9 +638,10 @@ public static partial class Parser
|
|||
|
||||
#region Magazine
|
||||
|
||||
private static readonly Dictionary<string, int> _monthMappings = CreateMonthMappings();
|
||||
private static readonly Regex[] MagazineSeriesRegex = new[]
|
||||
{
|
||||
private static readonly HashSet<string> GeoCodes = new(CreateCountryCodes());
|
||||
private static readonly Dictionary<string, int> MonthMappings = CreateMonthMappings();
|
||||
private static readonly Regex[] MagazineSeriesRegex =
|
||||
[
|
||||
// 3D World - 2018 UK, 3D World - 022014
|
||||
new Regex(
|
||||
@"^(?<Series>.+?)(_|\s)*-(_|\s)*\d{4,6}.*",
|
||||
|
@ -649,10 +650,14 @@ public static partial class Parser
|
|||
new Regex(
|
||||
@"^(?<Series>.+?)(_|\s)*-(_|\s)*.*",
|
||||
MatchOptions, RegexTimeout),
|
||||
// AIR International #1 // This breaks the way the code works
|
||||
// new Regex(
|
||||
// @"^(?<Series>.+?)(_|\s)+?#",
|
||||
// MatchOptions, RegexTimeout)
|
||||
// The New Yorker - April 2, 2018 USA
|
||||
// AIR International Magazine 2006
|
||||
// AIR International Vol. 14 No. 3 (ISSN 1011-3250)
|
||||
};
|
||||
];
|
||||
|
||||
private static readonly Regex[] MagazineVolumeRegex = new[]
|
||||
{
|
||||
|
@ -846,6 +851,17 @@ public static partial class Parser
|
|||
return DefaultVolume;
|
||||
}
|
||||
|
||||
private static string[] CreateCountryCodes()
|
||||
{
|
||||
var codes = CultureInfo.GetCultures(CultureTypes.SpecificCultures)
|
||||
.Select(culture => new RegionInfo(culture.Name).TwoLetterISORegionName)
|
||||
.Distinct()
|
||||
.OrderBy(code => code)
|
||||
.ToList();
|
||||
codes.Add("UK");
|
||||
return codes.ToArray();
|
||||
}
|
||||
|
||||
|
||||
private static Dictionary<string, int> CreateMonthMappings()
|
||||
{
|
||||
|
@ -879,7 +895,7 @@ public static partial class Parser
|
|||
var value = groups["Chapter"].Value;
|
||||
// If value has non-digits, we need to convert to a digit
|
||||
if (IsNumberRegex().IsMatch(value)) return FormatValue(value, false);
|
||||
if (_monthMappings.TryGetValue(value, out var parsedMonth))
|
||||
if (MonthMappings.TryGetValue(value, out var parsedMonth))
|
||||
{
|
||||
return FormatValue($"{parsedMonth}", false);
|
||||
}
|
||||
|
@ -889,7 +905,36 @@ public static partial class Parser
|
|||
return DefaultChapter;
|
||||
}
|
||||
|
||||
public static string ParseYear(string value)
|
||||
/// <summary>
|
||||
/// Tries to parse a GeoCode (UK, US) out of a string
|
||||
/// </summary>
|
||||
/// <param name="value"></param>
|
||||
/// <returns></returns>
|
||||
public static string? ParseGeoCode(string? value)
|
||||
{
|
||||
if (string.IsNullOrEmpty(value)) return value;
|
||||
const string pattern = @"\b(?:\(|\[|\{)([A-Z]{2})(?:\)|\]|\})\b|^([A-Z]{2})$";
|
||||
|
||||
// Match the pattern in the input string
|
||||
var match = Regex.Match(value, pattern, RegexOptions.IgnoreCase);
|
||||
|
||||
if (match.Success)
|
||||
{
|
||||
// Extract the GeoCode from the first capturing group if it exists,
|
||||
// otherwise, extract the GeoCode from the second capturing group
|
||||
var extractedCode = match.Groups[1].Success ? match.Groups[1].Value : match.Groups[2].Value;
|
||||
|
||||
// Validate the extracted GeoCode against the list of valid GeoCodes
|
||||
if (GeoCodes.Contains(extractedCode))
|
||||
{
|
||||
return extractedCode;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static string? ParseYear(string? value)
|
||||
{
|
||||
if (string.IsNullOrEmpty(value)) return value;
|
||||
return YearRegex.Match(value).Value;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue