mirror of
https://github.com/Kareadita/Kavita.git
synced 2025-05-31 04:04:19 -04:00
OPDS Flattening (#1904)
* Flattening OPDS Structure # Changed - Flattened OPDS structure to reduce user taps. * Fixing format * Fixing book series titles * Optimized file size to use pre-calculated data to avoid an I/O touch. * Fixes #1898 by aligning all content headers to the correct MIME types * Remove dead code * Fixed a bug with continue point where it fails on chapters or volumes tagged with a range --------- Co-authored-by: Robbie Davis <robbie@therobbiedavis.com>
This commit is contained in:
parent
449827f285
commit
a8f48a6e9d
@ -1366,6 +1366,45 @@ public class ReaderServiceTests
|
|||||||
Assert.Equal("1", nextChapter.Range);
|
Assert.Equal("1", nextChapter.Range);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task GetContinuePoint_ShouldReturnFirstVolume_WhenFirstVolumeIsAlsoTaggedAsChapter1Through11_WithProgress()
|
||||||
|
{
|
||||||
|
await ResetDb();
|
||||||
|
var series = new SeriesBuilder("Test")
|
||||||
|
.WithVolume(new VolumeBuilder("1")
|
||||||
|
.WithChapter(new ChapterBuilder("1", "1-11").WithPages(3).Build())
|
||||||
|
.Build())
|
||||||
|
.WithVolume(new VolumeBuilder("2")
|
||||||
|
.WithChapter(new ChapterBuilder("0").WithPages(1).Build())
|
||||||
|
.Build())
|
||||||
|
.WithPages(4)
|
||||||
|
.Build();
|
||||||
|
series.Library = new LibraryBuilder("Test LIb", LibraryType.Manga).Build();
|
||||||
|
|
||||||
|
_context.Series.Add(series);
|
||||||
|
|
||||||
|
_context.AppUser.Add(new AppUser()
|
||||||
|
{
|
||||||
|
UserName = "majora2007"
|
||||||
|
});
|
||||||
|
|
||||||
|
await _context.SaveChangesAsync();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
await _readerService.SaveReadingProgress(new ProgressDto()
|
||||||
|
{
|
||||||
|
PageNum = 2,
|
||||||
|
ChapterId = 1,
|
||||||
|
SeriesId = 1,
|
||||||
|
VolumeId = 1
|
||||||
|
}, 1);
|
||||||
|
var nextChapter = await _readerService.GetContinuePoint(1, 1);
|
||||||
|
|
||||||
|
Assert.Equal("1-11", nextChapter.Range);
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task GetContinuePoint_ShouldReturnFirstNonSpecial()
|
public async Task GetContinuePoint_ShouldReturnFirstNonSpecial()
|
||||||
{
|
{
|
||||||
|
@ -78,6 +78,7 @@
|
|||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="7.0.4" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="7.0.4" />
|
||||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="7.0.0" />
|
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="7.0.0" />
|
||||||
<PackageReference Include="Microsoft.IO.RecyclableMemoryStream" Version="2.3.2" />
|
<PackageReference Include="Microsoft.IO.RecyclableMemoryStream" Version="2.3.2" />
|
||||||
|
<PackageReference Include="MimeTypeMapOfficial" Version="1.0.17" />
|
||||||
<PackageReference Include="NetVips" Version="2.2.0" />
|
<PackageReference Include="NetVips" Version="2.2.0" />
|
||||||
<PackageReference Include="NetVips.Native" Version="8.13.2" />
|
<PackageReference Include="NetVips.Native" Version="8.13.2" />
|
||||||
<PackageReference Include="NReco.Logging.File" Version="1.1.6" />
|
<PackageReference Include="NReco.Logging.File" Version="1.1.6" />
|
||||||
|
@ -8,6 +8,7 @@ using API.Extensions;
|
|||||||
using API.Services;
|
using API.Services;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using MimeTypes;
|
||||||
|
|
||||||
namespace API.Controllers;
|
namespace API.Controllers;
|
||||||
|
|
||||||
@ -38,9 +39,9 @@ public class ImageController : BaseApiController
|
|||||||
{
|
{
|
||||||
var path = Path.Join(_directoryService.CoverImageDirectory, await _unitOfWork.ChapterRepository.GetChapterCoverImageAsync(chapterId));
|
var path = Path.Join(_directoryService.CoverImageDirectory, await _unitOfWork.ChapterRepository.GetChapterCoverImageAsync(chapterId));
|
||||||
if (string.IsNullOrEmpty(path) || !_directoryService.FileSystem.File.Exists(path)) return BadRequest($"No cover image");
|
if (string.IsNullOrEmpty(path) || !_directoryService.FileSystem.File.Exists(path)) return BadRequest($"No cover image");
|
||||||
var format = _directoryService.FileSystem.Path.GetExtension(path).Replace(".", string.Empty);
|
var format = _directoryService.FileSystem.Path.GetExtension(path);
|
||||||
|
|
||||||
return PhysicalFile(path, "image/" + format, _directoryService.FileSystem.Path.GetFileName(path));
|
return PhysicalFile(path, MimeTypeMap.GetMimeType(format), _directoryService.FileSystem.Path.GetFileName(path));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -54,9 +55,9 @@ public class ImageController : BaseApiController
|
|||||||
{
|
{
|
||||||
var path = Path.Join(_directoryService.CoverImageDirectory, await _unitOfWork.LibraryRepository.GetLibraryCoverImageAsync(libraryId));
|
var path = Path.Join(_directoryService.CoverImageDirectory, await _unitOfWork.LibraryRepository.GetLibraryCoverImageAsync(libraryId));
|
||||||
if (string.IsNullOrEmpty(path) || !_directoryService.FileSystem.File.Exists(path)) return BadRequest($"No cover image");
|
if (string.IsNullOrEmpty(path) || !_directoryService.FileSystem.File.Exists(path)) return BadRequest($"No cover image");
|
||||||
var format = _directoryService.FileSystem.Path.GetExtension(path).Replace(".", string.Empty);
|
var format = _directoryService.FileSystem.Path.GetExtension(path);
|
||||||
|
|
||||||
return PhysicalFile(path, "image/" + format, _directoryService.FileSystem.Path.GetFileName(path));
|
return PhysicalFile(path, MimeTypeMap.GetMimeType(format), _directoryService.FileSystem.Path.GetFileName(path));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -70,9 +71,9 @@ public class ImageController : BaseApiController
|
|||||||
{
|
{
|
||||||
var path = Path.Join(_directoryService.CoverImageDirectory, await _unitOfWork.VolumeRepository.GetVolumeCoverImageAsync(volumeId));
|
var path = Path.Join(_directoryService.CoverImageDirectory, await _unitOfWork.VolumeRepository.GetVolumeCoverImageAsync(volumeId));
|
||||||
if (string.IsNullOrEmpty(path) || !_directoryService.FileSystem.File.Exists(path)) return BadRequest($"No cover image");
|
if (string.IsNullOrEmpty(path) || !_directoryService.FileSystem.File.Exists(path)) return BadRequest($"No cover image");
|
||||||
var format = _directoryService.FileSystem.Path.GetExtension(path).Replace(".", string.Empty);
|
var format = _directoryService.FileSystem.Path.GetExtension(path);
|
||||||
|
|
||||||
return PhysicalFile(path, "image/" + format, _directoryService.FileSystem.Path.GetFileName(path));
|
return PhysicalFile(path, MimeTypeMap.GetMimeType(format), _directoryService.FileSystem.Path.GetFileName(path));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -86,11 +87,11 @@ public class ImageController : BaseApiController
|
|||||||
{
|
{
|
||||||
var path = Path.Join(_directoryService.CoverImageDirectory, await _unitOfWork.SeriesRepository.GetSeriesCoverImageAsync(seriesId));
|
var path = Path.Join(_directoryService.CoverImageDirectory, await _unitOfWork.SeriesRepository.GetSeriesCoverImageAsync(seriesId));
|
||||||
if (string.IsNullOrEmpty(path) || !_directoryService.FileSystem.File.Exists(path)) return BadRequest($"No cover image");
|
if (string.IsNullOrEmpty(path) || !_directoryService.FileSystem.File.Exists(path)) return BadRequest($"No cover image");
|
||||||
var format = _directoryService.FileSystem.Path.GetExtension(path).Replace(".", string.Empty);
|
var format = _directoryService.FileSystem.Path.GetExtension(path);
|
||||||
|
|
||||||
Response.AddCacheHeader(path);
|
Response.AddCacheHeader(path);
|
||||||
|
|
||||||
return PhysicalFile(path, "image/" + format, _directoryService.FileSystem.Path.GetFileName(path));
|
return PhysicalFile(path, MimeTypeMap.GetMimeType(format), _directoryService.FileSystem.Path.GetFileName(path));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -104,9 +105,9 @@ public class ImageController : BaseApiController
|
|||||||
{
|
{
|
||||||
var path = Path.Join(_directoryService.CoverImageDirectory, await _unitOfWork.CollectionTagRepository.GetCoverImageAsync(collectionTagId));
|
var path = Path.Join(_directoryService.CoverImageDirectory, await _unitOfWork.CollectionTagRepository.GetCoverImageAsync(collectionTagId));
|
||||||
if (string.IsNullOrEmpty(path) || !_directoryService.FileSystem.File.Exists(path)) return BadRequest($"No cover image");
|
if (string.IsNullOrEmpty(path) || !_directoryService.FileSystem.File.Exists(path)) return BadRequest($"No cover image");
|
||||||
var format = _directoryService.FileSystem.Path.GetExtension(path).Replace(".", string.Empty);
|
var format = _directoryService.FileSystem.Path.GetExtension(path);
|
||||||
|
|
||||||
return PhysicalFile(path, "image/" + format, _directoryService.FileSystem.Path.GetFileName(path));
|
return PhysicalFile(path, MimeTypeMap.GetMimeType(format), _directoryService.FileSystem.Path.GetFileName(path));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -119,13 +120,10 @@ public class ImageController : BaseApiController
|
|||||||
public async Task<ActionResult> GetReadingListCoverImage(int readingListId)
|
public async Task<ActionResult> GetReadingListCoverImage(int readingListId)
|
||||||
{
|
{
|
||||||
var path = Path.Join(_directoryService.CoverImageDirectory, await _unitOfWork.ReadingListRepository.GetCoverImageAsync(readingListId));
|
var path = Path.Join(_directoryService.CoverImageDirectory, await _unitOfWork.ReadingListRepository.GetCoverImageAsync(readingListId));
|
||||||
if (string.IsNullOrEmpty(path) || !_directoryService.FileSystem.File.Exists(path))
|
if (string.IsNullOrEmpty(path) || !_directoryService.FileSystem.File.Exists(path)) return BadRequest($"No cover image");
|
||||||
{
|
var format = _directoryService.FileSystem.Path.GetExtension(path);
|
||||||
return BadRequest($"No cover image");
|
|
||||||
}
|
|
||||||
var format = _directoryService.FileSystem.Path.GetExtension(path).Replace(".", string.Empty);
|
|
||||||
|
|
||||||
return PhysicalFile(path, "image/" + format, _directoryService.FileSystem.Path.GetFileName(path));
|
return PhysicalFile(path, MimeTypeMap.GetMimeType(format), _directoryService.FileSystem.Path.GetFileName(path));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -147,9 +145,9 @@ public class ImageController : BaseApiController
|
|||||||
var bookmarkDirectory =
|
var bookmarkDirectory =
|
||||||
(await _unitOfWork.SettingsRepository.GetSettingAsync(ServerSettingKey.BookmarkDirectory)).Value;
|
(await _unitOfWork.SettingsRepository.GetSettingAsync(ServerSettingKey.BookmarkDirectory)).Value;
|
||||||
var file = new FileInfo(Path.Join(bookmarkDirectory, bookmark.FileName));
|
var file = new FileInfo(Path.Join(bookmarkDirectory, bookmark.FileName));
|
||||||
var format = Path.GetExtension(file.FullName).Replace(".", string.Empty);
|
var format = Path.GetExtension(file.FullName);
|
||||||
|
|
||||||
return PhysicalFile(file.FullName, "image/" + format, Path.GetFileName(file.FullName));
|
return PhysicalFile(file.FullName, MimeTypeMap.GetMimeType(format), Path.GetFileName(file.FullName));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -166,8 +164,8 @@ public class ImageController : BaseApiController
|
|||||||
|
|
||||||
var path = Path.Join(_directoryService.TempDirectory, filename);
|
var path = Path.Join(_directoryService.TempDirectory, filename);
|
||||||
if (string.IsNullOrEmpty(path) || !_directoryService.FileSystem.File.Exists(path)) return BadRequest($"File does not exist");
|
if (string.IsNullOrEmpty(path) || !_directoryService.FileSystem.File.Exists(path)) return BadRequest($"File does not exist");
|
||||||
var format = _directoryService.FileSystem.Path.GetExtension(path).Replace(".", string.Empty);
|
var format = _directoryService.FileSystem.Path.GetExtension(path);
|
||||||
|
|
||||||
return PhysicalFile(path, "image/" + format, _directoryService.FileSystem.Path.GetFileName(path));
|
return PhysicalFile(path, MimeTypeMap.GetMimeType(format), _directoryService.FileSystem.Path.GetFileName(path));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -20,6 +20,7 @@ using API.Services;
|
|||||||
using Kavita.Common;
|
using Kavita.Common;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using MimeTypes;
|
||||||
|
|
||||||
namespace API.Controllers;
|
namespace API.Controllers;
|
||||||
|
|
||||||
@ -529,29 +530,39 @@ public class OpdsController : BaseApiController
|
|||||||
var seriesDetail = await _seriesService.GetSeriesDetail(seriesId, userId);
|
var seriesDetail = await _seriesService.GetSeriesDetail(seriesId, userId);
|
||||||
foreach (var volume in seriesDetail.Volumes)
|
foreach (var volume in seriesDetail.Volumes)
|
||||||
{
|
{
|
||||||
// If there is only one chapter to the Volume, we will emulate a volume to flatten the amount of hops a user must go through
|
var chapters = (await _unitOfWork.ChapterRepository.GetChaptersAsync(volume.Id)).OrderBy(x => double.Parse(x.Number),
|
||||||
if (volume.Chapters.Count == 1)
|
_chapterSortComparer);
|
||||||
|
|
||||||
|
foreach (var chapter in chapters)
|
||||||
{
|
{
|
||||||
var firstChapter = volume.Chapters.First();
|
var files = await _unitOfWork.ChapterRepository.GetFilesForChapterAsync(chapter.Id);
|
||||||
var chapter = CreateChapter(apiKey, volume.Name, firstChapter.Summary, firstChapter.Id, volume.Id, seriesId);
|
var chapterTest = await _unitOfWork.ChapterRepository.GetChapterDtoAsync(chapter.Id);
|
||||||
chapter.Id = firstChapter.Id.ToString();
|
foreach (var mangaFile in files)
|
||||||
feed.Entries.Add(chapter);
|
{
|
||||||
}
|
feed.Entries.Add(await CreateChapterWithFile(seriesId, volume.Id, chapter.Id, mangaFile, series, chapterTest, apiKey));
|
||||||
else
|
}
|
||||||
{
|
|
||||||
feed.Entries.Add(CreateVolume(volume, seriesId, apiKey));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var storylineChapter in seriesDetail.StorylineChapters.Where(c => !c.IsSpecial))
|
foreach (var storylineChapter in seriesDetail.StorylineChapters.Where(c => !c.IsSpecial))
|
||||||
{
|
{
|
||||||
feed.Entries.Add(CreateChapter(apiKey, storylineChapter.Title, storylineChapter.Summary, storylineChapter.Id, storylineChapter.VolumeId, seriesId));
|
var files = await _unitOfWork.ChapterRepository.GetFilesForChapterAsync(storylineChapter.Id);
|
||||||
|
var chapterTest = await _unitOfWork.ChapterRepository.GetChapterDtoAsync(storylineChapter.Id);
|
||||||
|
foreach (var mangaFile in files)
|
||||||
|
{
|
||||||
|
feed.Entries.Add(await CreateChapterWithFile(seriesId, storylineChapter.VolumeId, storylineChapter.Id, mangaFile, series, chapterTest, apiKey));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var special in seriesDetail.Specials)
|
foreach (var special in seriesDetail.Specials)
|
||||||
{
|
{
|
||||||
feed.Entries.Add(CreateChapter(apiKey, special.Title, special.Summary, special.Id, special.VolumeId, seriesId));
|
var files = await _unitOfWork.ChapterRepository.GetFilesForChapterAsync(special.Id);
|
||||||
|
var chapterTest = await _unitOfWork.ChapterRepository.GetChapterDtoAsync(special.Id);
|
||||||
|
foreach (var mangaFile in files)
|
||||||
|
{
|
||||||
|
feed.Entries.Add(await CreateChapterWithFile(seriesId, special.VolumeId, special.Id, mangaFile, series, chapterTest, apiKey));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return CreateXmlResult(SerializeXml(feed));
|
return CreateXmlResult(SerializeXml(feed));
|
||||||
@ -570,21 +581,17 @@ public class OpdsController : BaseApiController
|
|||||||
var chapters =
|
var chapters =
|
||||||
(await _unitOfWork.ChapterRepository.GetChaptersAsync(volumeId)).OrderBy(x => double.Parse(x.Number),
|
(await _unitOfWork.ChapterRepository.GetChaptersAsync(volumeId)).OrderBy(x => double.Parse(x.Number),
|
||||||
_chapterSortComparer);
|
_chapterSortComparer);
|
||||||
|
var feed = CreateFeed(series.Name + " - Volume " + volume!.Name + $" - {SeriesService.FormatChapterName(libraryType)}s ",
|
||||||
var feed = CreateFeed(series.Name + " - Volume " + volume!.Name + $" - {SeriesService.FormatChapterName(libraryType)}s ", $"{apiKey}/series/{seriesId}/volume/{volumeId}", apiKey);
|
$"{apiKey}/series/{seriesId}/volume/{volumeId}", apiKey);
|
||||||
SetFeedId(feed, $"series-{series.Id}-volume-{volume.Id}-{SeriesService.FormatChapterName(libraryType)}s");
|
SetFeedId(feed, $"series-{series.Id}-volume-{volume.Id}-{SeriesService.FormatChapterName(libraryType)}s");
|
||||||
foreach (var chapter in chapters)
|
foreach (var chapter in chapters)
|
||||||
{
|
{
|
||||||
feed.Entries.Add(new FeedEntry()
|
var files = await _unitOfWork.ChapterRepository.GetFilesForChapterAsync(chapter.Id);
|
||||||
|
var chapterTest = await _unitOfWork.ChapterRepository.GetChapterDtoAsync(chapter.Id);
|
||||||
|
foreach (var mangaFile in files)
|
||||||
{
|
{
|
||||||
Id = chapter.Id.ToString(),
|
feed.Entries.Add(await CreateChapterWithFile(seriesId, volumeId, chapter.Id, mangaFile, series, chapterTest, apiKey));
|
||||||
Title = SeriesService.FormatChapterTitle(chapter, libraryType),
|
}
|
||||||
Links = new List<FeedLink>()
|
|
||||||
{
|
|
||||||
CreateLink(FeedLinkRelation.SubSection, FeedLinkType.AtomNavigation, Prefix + $"{apiKey}/series/{seriesId}/volume/{volumeId}/chapter/{chapter.Id}"),
|
|
||||||
CreateLink(FeedLinkRelation.Image, FeedLinkType.Image, $"/api/image/chapter-cover?chapterId={chapter.Id}")
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return CreateXmlResult(SerializeXml(feed));
|
return CreateXmlResult(SerializeXml(feed));
|
||||||
@ -604,7 +611,8 @@ public class OpdsController : BaseApiController
|
|||||||
var volume = await _unitOfWork.VolumeRepository.GetVolumeAsync(volumeId);
|
var volume = await _unitOfWork.VolumeRepository.GetVolumeAsync(volumeId);
|
||||||
var files = await _unitOfWork.ChapterRepository.GetFilesForChapterAsync(chapterId);
|
var files = await _unitOfWork.ChapterRepository.GetFilesForChapterAsync(chapterId);
|
||||||
|
|
||||||
var feed = CreateFeed(series.Name + " - Volume " + volume!.Name + $" - {SeriesService.FormatChapterName(libraryType)}s", $"{apiKey}/series/{seriesId}/volume/{volumeId}/chapter/{chapterId}", apiKey);
|
var feed = CreateFeed(series.Name + " - Volume " + volume!.Name + $" - {SeriesService.FormatChapterName(libraryType)}s",
|
||||||
|
$"{apiKey}/series/{seriesId}/volume/{volumeId}/chapter/{chapterId}", apiKey);
|
||||||
SetFeedId(feed, $"series-{series.Id}-volume-{volumeId}-{SeriesService.FormatChapterName(libraryType)}-{chapterId}-files");
|
SetFeedId(feed, $"series-{series.Id}-volume-{volumeId}-{SeriesService.FormatChapterName(libraryType)}-{chapterId}-files");
|
||||||
foreach (var mangaFile in files)
|
foreach (var mangaFile in files)
|
||||||
{
|
{
|
||||||
@ -727,25 +735,6 @@ public class OpdsController : BaseApiController
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private static FeedEntry CreateVolume(VolumeDto volumeDto, int seriesId, string apiKey)
|
|
||||||
{
|
|
||||||
return new FeedEntry()
|
|
||||||
{
|
|
||||||
Id = volumeDto.Id.ToString(),
|
|
||||||
Title = volumeDto.Name,
|
|
||||||
Summary = volumeDto.Chapters.First().Summary ?? string.Empty,
|
|
||||||
Links = new List<FeedLink>()
|
|
||||||
{
|
|
||||||
CreateLink(FeedLinkRelation.SubSection, FeedLinkType.AtomNavigation,
|
|
||||||
Prefix + $"{apiKey}/series/{seriesId}/volume/{volumeDto.Id}"),
|
|
||||||
CreateLink(FeedLinkRelation.Image, FeedLinkType.Image,
|
|
||||||
$"/api/image/volume-cover?volumeId={volumeDto.Id}"),
|
|
||||||
CreateLink(FeedLinkRelation.Thumbnail, FeedLinkType.Image,
|
|
||||||
$"/api/image/volume-cover?volumeId={volumeDto.Id}")
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private static FeedEntry CreateChapter(string apiKey, string title, string summary, int chapterId, int volumeId, int seriesId)
|
private static FeedEntry CreateChapter(string apiKey, string title, string summary, int chapterId, int volumeId, int seriesId)
|
||||||
{
|
{
|
||||||
return new FeedEntry()
|
return new FeedEntry()
|
||||||
@ -768,6 +757,7 @@ public class OpdsController : BaseApiController
|
|||||||
private async Task<FeedEntry> CreateChapterWithFile(int seriesId, int volumeId, int chapterId, MangaFile mangaFile, SeriesDto series, ChapterDto chapter, string apiKey)
|
private async Task<FeedEntry> CreateChapterWithFile(int seriesId, int volumeId, int chapterId, MangaFile mangaFile, SeriesDto series, ChapterDto chapter, string apiKey)
|
||||||
{
|
{
|
||||||
var fileSize =
|
var fileSize =
|
||||||
|
mangaFile.Bytes > 0 ? DirectoryService.GetHumanReadableBytes(mangaFile.Bytes) :
|
||||||
DirectoryService.GetHumanReadableBytes(_directoryService.GetTotalSize(new List<string>()
|
DirectoryService.GetHumanReadableBytes(_directoryService.GetTotalSize(new List<string>()
|
||||||
{mangaFile.FilePath}));
|
{mangaFile.FilePath}));
|
||||||
var fileType = _downloadService.GetContentTypeFromFile(mangaFile.FilePath);
|
var fileType = _downloadService.GetContentTypeFromFile(mangaFile.FilePath);
|
||||||
@ -775,15 +765,23 @@ public class OpdsController : BaseApiController
|
|||||||
var libraryType = await _unitOfWork.LibraryRepository.GetLibraryTypeAsync(series.LibraryId);
|
var libraryType = await _unitOfWork.LibraryRepository.GetLibraryTypeAsync(series.LibraryId);
|
||||||
var volume = await _unitOfWork.VolumeRepository.GetVolumeDtoAsync(volumeId, await GetUser(apiKey));
|
var volume = await _unitOfWork.VolumeRepository.GetVolumeDtoAsync(volumeId, await GetUser(apiKey));
|
||||||
|
|
||||||
var title = $"{series.Name} - ";
|
var title = $"{series.Name}";
|
||||||
|
|
||||||
if (volume.Chapters.Count == 1)
|
if (volume.Chapters.Count == 1)
|
||||||
{
|
{
|
||||||
SeriesService.RenameVolumeName(volume.Chapters.First(), volume, libraryType);
|
SeriesService.RenameVolumeName(volume.Chapters.First(), volume, libraryType);
|
||||||
title += $"{volume.Name}";
|
if (volume.Name != "0")
|
||||||
|
{
|
||||||
|
title += $" - {volume.Name}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (volume.Number != 0)
|
||||||
|
{
|
||||||
|
title = $" - {series.Name} - Volume {volume.Name} - {SeriesService.FormatChapterTitle(chapter, libraryType)}";
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
title = $"{series.Name} - {SeriesService.FormatChapterTitle(chapter, libraryType)}";
|
title = $" - {series.Name} - {SeriesService.FormatChapterTitle(chapter, libraryType)}";
|
||||||
}
|
}
|
||||||
|
|
||||||
// Chunky requires a file at the end. Our API ignores this
|
// Chunky requires a file at the end. Our API ignores this
|
||||||
@ -841,7 +839,7 @@ public class OpdsController : BaseApiController
|
|||||||
if (string.IsNullOrEmpty(path) || !System.IO.File.Exists(path)) return BadRequest($"No such image for page {pageNumber}");
|
if (string.IsNullOrEmpty(path) || !System.IO.File.Exists(path)) return BadRequest($"No such image for page {pageNumber}");
|
||||||
|
|
||||||
var content = await _directoryService.ReadFileAsync(path);
|
var content = await _directoryService.ReadFileAsync(path);
|
||||||
var format = Path.GetExtension(path).Replace(".", string.Empty);
|
var format = Path.GetExtension(path);
|
||||||
|
|
||||||
// Calculates SHA1 Hash for byte[]
|
// Calculates SHA1 Hash for byte[]
|
||||||
Response.AddCacheHeader(content);
|
Response.AddCacheHeader(content);
|
||||||
@ -856,7 +854,7 @@ public class OpdsController : BaseApiController
|
|||||||
LibraryId =libraryId
|
LibraryId =libraryId
|
||||||
}, await GetUser(apiKey));
|
}, await GetUser(apiKey));
|
||||||
|
|
||||||
return File(content, "image/" + format);
|
return File(content, MimeTypeMap.GetMimeType(format));
|
||||||
}
|
}
|
||||||
catch (Exception)
|
catch (Exception)
|
||||||
{
|
{
|
||||||
@ -873,9 +871,9 @@ public class OpdsController : BaseApiController
|
|||||||
if (files.Length == 0) return BadRequest("Cannot find icon");
|
if (files.Length == 0) return BadRequest("Cannot find icon");
|
||||||
var path = files[0];
|
var path = files[0];
|
||||||
var content = await _directoryService.ReadFileAsync(path);
|
var content = await _directoryService.ReadFileAsync(path);
|
||||||
var format = Path.GetExtension(path).Replace(".", string.Empty);
|
var format = Path.GetExtension(path);
|
||||||
|
|
||||||
return File(content, "image/" + format);
|
return File(content, MimeTypeMap.GetMimeType(format));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -19,6 +19,7 @@ using Microsoft.AspNetCore.Authorization;
|
|||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Microsoft.IdentityModel.Tokens;
|
using Microsoft.IdentityModel.Tokens;
|
||||||
|
using MimeTypes;
|
||||||
|
|
||||||
namespace API.Controllers;
|
namespace API.Controllers;
|
||||||
|
|
||||||
@ -103,9 +104,9 @@ public class ReaderController : BaseApiController
|
|||||||
{
|
{
|
||||||
var path = _cacheService.GetCachedPagePath(chapter.Id, page);
|
var path = _cacheService.GetCachedPagePath(chapter.Id, page);
|
||||||
if (string.IsNullOrEmpty(path) || !System.IO.File.Exists(path)) return BadRequest($"No such image for page {page}. Try refreshing to allow re-cache.");
|
if (string.IsNullOrEmpty(path) || !System.IO.File.Exists(path)) return BadRequest($"No such image for page {page}. Try refreshing to allow re-cache.");
|
||||||
var format = Path.GetExtension(path).Replace(".", string.Empty);
|
var format = Path.GetExtension(path);
|
||||||
|
|
||||||
return PhysicalFile(path, "image/" + format, Path.GetFileName(path), true);
|
return PhysicalFile(path, MimeTypeMap.GetMimeType(format), Path.GetFileName(path), true);
|
||||||
}
|
}
|
||||||
catch (Exception)
|
catch (Exception)
|
||||||
{
|
{
|
||||||
@ -124,8 +125,8 @@ public class ReaderController : BaseApiController
|
|||||||
var images = _cacheService.GetCachedPages(chapterId);
|
var images = _cacheService.GetCachedPages(chapterId);
|
||||||
|
|
||||||
var path = await _readerService.GetThumbnail(chapter, pageNum, images);
|
var path = await _readerService.GetThumbnail(chapter, pageNum, images);
|
||||||
var format = Path.GetExtension(path).Replace(".", string.Empty); // TODO: Make this an extension
|
var format = Path.GetExtension(path);
|
||||||
return PhysicalFile(path, "image/" + format, Path.GetFileName(path), true);
|
return PhysicalFile(path, MimeTypeMap.GetMimeType(format), Path.GetFileName(path), true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -154,9 +155,9 @@ public class ReaderController : BaseApiController
|
|||||||
{
|
{
|
||||||
var path = _cacheService.GetCachedBookmarkPagePath(seriesId, page);
|
var path = _cacheService.GetCachedBookmarkPagePath(seriesId, page);
|
||||||
if (string.IsNullOrEmpty(path) || !System.IO.File.Exists(path)) return BadRequest($"No such image for page {page}");
|
if (string.IsNullOrEmpty(path) || !System.IO.File.Exists(path)) return BadRequest($"No such image for page {page}");
|
||||||
var format = Path.GetExtension(path).Replace(".", string.Empty);
|
var format = Path.GetExtension(path);
|
||||||
|
|
||||||
return PhysicalFile(path, "image/" + format, Path.GetFileName(path));
|
return PhysicalFile(path, MimeTypeMap.GetMimeType(format), Path.GetFileName(path));
|
||||||
}
|
}
|
||||||
catch (Exception)
|
catch (Exception)
|
||||||
{
|
{
|
||||||
|
@ -4,6 +4,7 @@ using System.IO;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using API.Entities;
|
using API.Entities;
|
||||||
using Microsoft.AspNetCore.StaticFiles;
|
using Microsoft.AspNetCore.StaticFiles;
|
||||||
|
using MimeTypes;
|
||||||
|
|
||||||
namespace API.Services;
|
namespace API.Services;
|
||||||
|
|
||||||
@ -47,7 +48,7 @@ public class DownloadService : IDownloadService
|
|||||||
".zip" => "application/zip",
|
".zip" => "application/zip",
|
||||||
".tar.gz" => "application/gzip",
|
".tar.gz" => "application/gzip",
|
||||||
".pdf" => "application/pdf",
|
".pdf" => "application/pdf",
|
||||||
_ => contentType
|
_ => MimeTypeMap.GetMimeType(contentType)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -495,7 +495,7 @@ 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
|
// 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.
|
// If there are any volumes that have progress, return those. If not, move on.
|
||||||
var currentlyReadingChapter = volumeChapters
|
var currentlyReadingChapter = volumeChapters
|
||||||
.OrderBy(c => double.Parse(c.Range), _chapterSortComparer)
|
.OrderBy(c => double.Parse(c.Number), _chapterSortComparer) // BUG: This is throwing an exception when Range is 1-11
|
||||||
.FirstOrDefault(chapter => chapter.PagesRead < chapter.Pages && chapter.PagesRead > 0);
|
.FirstOrDefault(chapter => chapter.PagesRead < chapter.Pages && chapter.PagesRead > 0);
|
||||||
if (currentlyReadingChapter != null) return currentlyReadingChapter;
|
if (currentlyReadingChapter != null) return currentlyReadingChapter;
|
||||||
|
|
||||||
|
@ -466,10 +466,14 @@ public class SeriesService : ISeriesService
|
|||||||
if (string.IsNullOrEmpty(title)) return;
|
if (string.IsNullOrEmpty(title)) return;
|
||||||
volume.Name += $" - {title}";
|
volume.Name += $" - {title}";
|
||||||
}
|
}
|
||||||
else
|
else if (volume.Name != "0")
|
||||||
{
|
{
|
||||||
volume.Name += $" - {firstChapter.TitleName}";
|
volume.Name += $" - {firstChapter.TitleName}";
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
volume.Name += $"";
|
||||||
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
"name": "GPL-3.0",
|
"name": "GPL-3.0",
|
||||||
"url": "https://github.com/Kareadita/Kavita/blob/develop/LICENSE"
|
"url": "https://github.com/Kareadita/Kavita/blob/develop/LICENSE"
|
||||||
},
|
},
|
||||||
"version": "0.7.1.27"
|
"version": "0.7.1.28"
|
||||||
},
|
},
|
||||||
"servers": [
|
"servers": [
|
||||||
{
|
{
|
||||||
|
Loading…
x
Reference in New Issue
Block a user