mirror of
https://github.com/Suwayomi/Suwayomi-Server.git
synced 2026-06-30 17:34:39 -05:00
fix(opds): handle dead sources and prevent kosync binary hash crashes (#2116)
This commit is contained in:
@@ -50,6 +50,7 @@
|
|||||||
<!-- OPDS errors -->
|
<!-- OPDS errors -->
|
||||||
<string name="opds_error_manga_not_found">Series with ID %1$d not found.</string>
|
<string name="opds_error_manga_not_found">Series with ID %1$d not found.</string>
|
||||||
<string name="opds_error_chapter_not_found">Chapter with index %1$d not found.</string>
|
<string name="opds_error_chapter_not_found">Chapter with index %1$d not found.</string>
|
||||||
|
<string name="opds_error_chapters_not_found">No chapters found or the source is unreachable on page %1$d.</string>
|
||||||
|
|
||||||
<!-- OPDS facets (Filters and Sorting) -->
|
<!-- OPDS facets (Filters and Sorting) -->
|
||||||
<string name="opds_facetgroup_sort_order">Sort Order</string>
|
<string name="opds_facetgroup_sort_order">Sort Order</string>
|
||||||
|
|||||||
@@ -127,29 +127,36 @@ object KoreaderSyncService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
val mangaId = chapterRow[ChapterTable.manga].value
|
val mangaId = chapterRow[ChapterTable.manga].value
|
||||||
|
val isDownloaded = chapterRow[ChapterTable.isDownloaded]
|
||||||
val checksumMethod = serverConfig.koreaderSyncChecksumMethod.value
|
val checksumMethod = serverConfig.koreaderSyncChecksumMethod.value
|
||||||
|
|
||||||
val newHash =
|
val newHash =
|
||||||
when (checksumMethod) {
|
when (checksumMethod) {
|
||||||
KoreaderSyncChecksumMethod.BINARY -> {
|
KoreaderSyncChecksumMethod.BINARY -> {
|
||||||
logger.debug { "[KOSYNC HASH] No hash for chapterId=$chapterId. Generating from downloaded content." }
|
// Only generate binary hash if the chapter is downloaded to avoid fetching missing files
|
||||||
try {
|
if (isDownloaded) {
|
||||||
// Always create a CBZ in memory if it doesn't exist
|
logger.debug { "[KOSYNC HASH] No hash for chapterId=$chapterId. Generating from downloaded content." }
|
||||||
val (stream, _) = ChapterDownloadHelper.getArchiveStreamWithSize(mangaId, chapterId)
|
|
||||||
// Write the stream to a temp file for partial hashing
|
|
||||||
val tempFile = File.createTempFile("kosync-hash-", ".cbz")
|
|
||||||
try {
|
try {
|
||||||
tempFile.outputStream().use { fos ->
|
// Always create a CBZ in memory if it doesn't exist
|
||||||
stream.use { it.copyTo(fos) }
|
val (stream, _) = ChapterDownloadHelper.getArchiveStreamWithSize(mangaId, chapterId)
|
||||||
|
// Write the stream to a temp file for partial hashing
|
||||||
|
val tempFile = File.createTempFile("kosync-hash-", ".cbz")
|
||||||
|
try {
|
||||||
|
tempFile.outputStream().use { fos ->
|
||||||
|
stream.use { it.copyTo(fos) }
|
||||||
|
}
|
||||||
|
// Use the same hashing method as for downloads
|
||||||
|
KoreaderHelper.hashContents(tempFile)
|
||||||
|
} finally {
|
||||||
|
// Always delete the temp file
|
||||||
|
tempFile.delete()
|
||||||
}
|
}
|
||||||
// Use the same hashing method as for downloads
|
} catch (e: Exception) {
|
||||||
KoreaderHelper.hashContents(tempFile)
|
logger.warn(e) { "[KOSYNC HASH] Failed to generate archive stream for chapterId=$chapterId." }
|
||||||
} finally {
|
null
|
||||||
// Always delete the temp file
|
|
||||||
tempFile.delete()
|
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} else {
|
||||||
logger.warn(e) { "[KOSYNC HASH] Failed to generate archive stream for chapterId=$chapterId." }
|
logger.debug { "[KOSYNC HASH] Skipping binary hash for chapterId=$chapterId because it is not downloaded." }
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -175,7 +182,7 @@ object KoreaderSyncService {
|
|||||||
}
|
}
|
||||||
logger.info { "[KOSYNC HASH] Generated and saved new hash for chapterId=$chapterId" }
|
logger.info { "[KOSYNC HASH] Generated and saved new hash for chapterId=$chapterId" }
|
||||||
} else {
|
} else {
|
||||||
logger.warn { "[KOSYNC HASH] Hashing failed for chapterId=$chapterId." }
|
logger.warn { "[KOSYNC HASH] Hashing failed or skipped for chapterId=$chapterId." }
|
||||||
}
|
}
|
||||||
newHash
|
newHash
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -644,6 +644,16 @@ object OpdsFeedBuilder {
|
|||||||
skipMetadata,
|
skipMetadata,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Return a not-found feed if all available chapters are filtered out as unreachable
|
||||||
|
if (skipMetadata && chapterEntries.isEmpty() && totalChapters > 0L) {
|
||||||
|
return buildNotFoundFeed(
|
||||||
|
baseUrl = baseUrl,
|
||||||
|
locale = locale,
|
||||||
|
idPath = "series/$mangaId/chapters",
|
||||||
|
title = MR.strings.opds_error_chapters_not_found.localized(locale, pageNum),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
// If no chapters are found in the database, attempt to fetch them from the source.
|
// If no chapters are found in the database, attempt to fetch them from the source.
|
||||||
if (chapterEntries.isEmpty() && totalChapters == 0L) {
|
if (chapterEntries.isEmpty() && totalChapters == 0L) {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -169,6 +169,8 @@ object ChapterRepository {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}.awaitAll()
|
}.awaitAll()
|
||||||
|
// Exclude unreachable chapters that are not downloaded and have no page count
|
||||||
|
.filter { it.downloaded || it.pageCount > 0 }
|
||||||
|
|
||||||
return Pair(enrichedChapters, totalCount)
|
return Pair(enrichedChapters, totalCount)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user