feat: 实现大书远距目录跳转与后台补全优化方案
Phase 1: 稳定性优先 - 新增 RDEPUBJumpSession 保护机制,防止远距跳转后翻页串章 - 升级页图接管条件,增加 JumpSession 保护区检查 - 窗口扩展改为基于当前权威窗口方向 Phase 2: 补全优先级重排 - 新增 RDEPUBBackgroundPriorityPolicy 策略配置 - 实现 hot/warm/cold zone 优先级排序 - 添加失败重试机制(指数退避,最多3次) Phase 3: 分段覆盖与最终收敛 - 新增 RDEPUBBackgroundCoverageStore 分段存储 - 新增 RDEPUBPageMapReconciliationCoordinator 页图接管仲裁 - 实现 LRU 淘汰和内存警告处理 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.7
parent
e976ceebd5
commit
c64460988a
+375
-19
@@ -9,11 +9,69 @@ import Foundation
|
||||
/// - 刷新可见内容并保持位置
|
||||
/// - 重建外部纯文本图书
|
||||
final class RDEPUBReaderPaginationCoordinator {
|
||||
private final class MetadataParseState {
|
||||
var summariesBySpineIndex: [Int: RDEPUBChapterSummary]
|
||||
var totalResolvedCount: Int
|
||||
var lastAppliedCount: Int
|
||||
|
||||
init(
|
||||
summariesBySpineIndex: [Int: RDEPUBChapterSummary],
|
||||
totalResolvedCount: Int,
|
||||
lastAppliedCount: Int
|
||||
) {
|
||||
self.summariesBySpineIndex = summariesBySpineIndex
|
||||
self.totalResolvedCount = totalResolvedCount
|
||||
self.lastAppliedCount = lastAppliedCount
|
||||
}
|
||||
}
|
||||
|
||||
private final class MetadataParseCancellationController {
|
||||
let token: UUID
|
||||
|
||||
private let lock = NSLock()
|
||||
private weak var queue: OperationQueue?
|
||||
private var cancelled = false
|
||||
|
||||
init(token: UUID) {
|
||||
self.token = token
|
||||
}
|
||||
|
||||
func attach(queue: OperationQueue) {
|
||||
let shouldCancelImmediately: Bool
|
||||
lock.lock()
|
||||
self.queue = queue
|
||||
shouldCancelImmediately = cancelled
|
||||
lock.unlock()
|
||||
|
||||
if shouldCancelImmediately {
|
||||
queue.cancelAllOperations()
|
||||
}
|
||||
}
|
||||
|
||||
func cancel() {
|
||||
let queueToCancel: OperationQueue?
|
||||
lock.lock()
|
||||
cancelled = true
|
||||
queueToCancel = queue
|
||||
lock.unlock()
|
||||
queueToCancel?.cancelAllOperations()
|
||||
}
|
||||
|
||||
var isCancelled: Bool {
|
||||
lock.lock()
|
||||
let value = cancelled
|
||||
lock.unlock()
|
||||
return value
|
||||
}
|
||||
}
|
||||
|
||||
private let backgroundInteractionCooldown: CFAbsoluteTime = 0.8
|
||||
/// 每 N 章刷新一次 pageMap,可通过修改此值实测调优。
|
||||
static var pageMapRefreshInterval: Int = 32
|
||||
|
||||
private unowned let context: RDEPUBReaderContext
|
||||
private let metadataParseControlLock = NSLock()
|
||||
private var activeMetadataParseCancellationController: MetadataParseCancellationController?
|
||||
|
||||
init(context: RDEPUBReaderContext) {
|
||||
self.context = context
|
||||
@@ -375,8 +433,12 @@ final class RDEPUBReaderPaginationCoordinator {
|
||||
publication.spine.indices.filter { isBuildableTextSpine(at: $0, in: publication) }
|
||||
}
|
||||
|
||||
private func waitForReadingInteractionToSettle(using context: RDEPUBReaderContext) {
|
||||
private func waitForReadingInteractionToSettle(
|
||||
using context: RDEPUBReaderContext,
|
||||
cancellationController: MetadataParseCancellationController? = nil
|
||||
) {
|
||||
while context.controller != nil,
|
||||
cancellationController?.isCancelled != true,
|
||||
context.secondsSinceLastUserNavigation() < backgroundInteractionCooldown {
|
||||
Thread.sleep(forTimeInterval: 0.08)
|
||||
}
|
||||
@@ -390,12 +452,17 @@ final class RDEPUBReaderPaginationCoordinator {
|
||||
|
||||
// MARK: - 元数据专用解析(Phase 0)
|
||||
|
||||
/// 失败重试配置
|
||||
private static let maxRetryCount = 3
|
||||
private static let retryDelays: [TimeInterval] = [0.5, 2.0, 8.0]
|
||||
|
||||
/// 后台遍历所有章节,只提取轻量元数据(pageCount、pageRanges、fragmentOffsets),
|
||||
/// 写入磁盘摘要缓存,不累积 RDEPUBTextBook。
|
||||
func paginateMetadataOnly(token: UUID, restoreLocation: RDEPUBLocation?) {
|
||||
let context = self.context
|
||||
guard let parser = context.parser,
|
||||
let publication = context.publication else { return }
|
||||
let cancellationController = beginMetadataParseCancellationController(for: token)
|
||||
|
||||
let pageSize = context.currentTextPageSize()
|
||||
let layoutConfig = context.currentTextLayoutConfig(pageSize: pageSize)
|
||||
@@ -409,12 +476,21 @@ final class RDEPUBReaderPaginationCoordinator {
|
||||
|
||||
DispatchQueue.global(qos: .utility).async { [weak self] in
|
||||
guard let self else { return }
|
||||
guard context.controller != nil else { return }
|
||||
defer { self.finishMetadataParseCancellationController(cancellationController) }
|
||||
guard context.controller != nil,
|
||||
!cancellationController.isCancelled,
|
||||
context.paginationToken == token else { return }
|
||||
|
||||
// 预计算所有章节的 contentHash,避免后续重复读盘 + SHA-256
|
||||
let prewarmStart = CFAbsoluteTimeGetCurrent()
|
||||
var contentHashBySpineIndex: [Int: String] = [:]
|
||||
for spineIndex in allBuildableIndices {
|
||||
guard !cancellationController.isCancelled,
|
||||
context.paginationToken == token,
|
||||
context.controller != nil else {
|
||||
RDEPUBBackgroundTrace.log("MetadataParse", "abort during content hash prewarm")
|
||||
return
|
||||
}
|
||||
guard let href = publication.spine.indices.contains(spineIndex)
|
||||
? publication.spine[spineIndex].href : nil,
|
||||
let html = parser.htmlString(forRelativePath: href) else {
|
||||
@@ -447,16 +523,18 @@ final class RDEPUBReaderPaginationCoordinator {
|
||||
let cachedSummaries = restored?.summaries ?? [:]
|
||||
let cachedSpineIndices = Set(cachedSummaries.keys)
|
||||
let resultLock = NSLock()
|
||||
var summariesBySpineIndex = cachedSummaries
|
||||
var totalResolvedCount = cachedSpineIndices.count
|
||||
var lastAppliedCount = cachedSpineIndices.count
|
||||
let parseState = MetadataParseState(
|
||||
summariesBySpineIndex: cachedSummaries,
|
||||
totalResolvedCount: cachedSpineIndices.count,
|
||||
lastAppliedCount: cachedSpineIndices.count
|
||||
)
|
||||
|
||||
if !cachedSpineIndices.isEmpty {
|
||||
RDEPUBBackgroundTrace.log(
|
||||
"MetadataParse",
|
||||
"resumeFromCache cachedChapters=\(cachedSpineIndices.count) total=\(allBuildableIndices.count)"
|
||||
)
|
||||
let cachedMap = self.buildPageMap(from: catalog, summaries: summariesBySpineIndex)
|
||||
let cachedMap = self.buildPageMap(from: catalog, summaries: parseState.summariesBySpineIndex)
|
||||
DispatchQueue.main.async {
|
||||
guard context.paginationToken == token,
|
||||
context.controller != nil else { return }
|
||||
@@ -464,9 +542,36 @@ final class RDEPUBReaderPaginationCoordinator {
|
||||
}
|
||||
}
|
||||
|
||||
let uncachedSpineIndices = allBuildableIndices.filter { !cachedSpineIndices.contains($0) }
|
||||
// 使用优先级排序获取未缓存的 spineIndex
|
||||
let prioritizedSpineIndices: [Int]
|
||||
if let priorityManager = context.runtime?.backgroundPriorityManager {
|
||||
let currentSpineIndex = context.runtime?.locationCoordinator.currentVisibleLocation()
|
||||
.flatMap { context.normalizedSpineIndex(for: $0) }
|
||||
prioritizedSpineIndices = priorityManager.makeMetadataPriorityOrder(
|
||||
allBuildableIndices: allBuildableIndices,
|
||||
currentSpineIndex: currentSpineIndex,
|
||||
cachedSpineIndices: cachedSpineIndices
|
||||
)
|
||||
RDEPUBBackgroundTrace.log(
|
||||
"MetadataParse",
|
||||
"prioritized hot=\(prioritizedSpineIndices.prefix(10).count) total=\(prioritizedSpineIndices.count)"
|
||||
)
|
||||
} else {
|
||||
prioritizedSpineIndices = allBuildableIndices.filter { !cachedSpineIndices.contains($0) }
|
||||
}
|
||||
|
||||
self.waitForReadingInteractionToSettle(using: context)
|
||||
let uncachedSpineIndices = prioritizedSpineIndices
|
||||
|
||||
self.waitForReadingInteractionToSettle(
|
||||
using: context,
|
||||
cancellationController: cancellationController
|
||||
)
|
||||
guard !cancellationController.isCancelled,
|
||||
context.controller != nil,
|
||||
context.paginationToken == token else {
|
||||
RDEPUBBackgroundTrace.log("MetadataParse", "abort before queue start")
|
||||
return
|
||||
}
|
||||
|
||||
let wallClockStart = CFAbsoluteTimeGetCurrent()
|
||||
var totalRenderMs: Double = 0
|
||||
@@ -480,19 +585,29 @@ final class RDEPUBReaderPaginationCoordinator {
|
||||
queue.name = "com.rdreader.metadata.parse"
|
||||
queue.qualityOfService = .utility
|
||||
queue.maxConcurrentOperationCount = workerCount
|
||||
cancellationController.attach(queue: queue)
|
||||
|
||||
let refreshInterval = RDEPUBReaderPaginationCoordinator.pageMapRefreshInterval
|
||||
|
||||
for (offset, spineIndex) in uncachedSpineIndices.enumerated() {
|
||||
queue.addOperation {
|
||||
let operation = BlockOperation()
|
||||
operation.addExecutionBlock { [weak operation] in
|
||||
guard context.controller != nil,
|
||||
context.paginationToken == token else {
|
||||
context.paginationToken == token,
|
||||
!cancellationController.isCancelled,
|
||||
operation?.isCancelled != true else {
|
||||
return
|
||||
}
|
||||
do {
|
||||
RDEPUBBackgroundTrace.log("MetadataParse", "正在解析 spine=\(spineIndex) \(offset + 1)/\(uncachedSpineIndices.count)")
|
||||
|
||||
let renderResult: RDEPUBChapterSummary? = try autoreleasepool { () -> RDEPUBChapterSummary? in
|
||||
guard context.controller != nil,
|
||||
context.paginationToken == token,
|
||||
!cancellationController.isCancelled,
|
||||
operation?.isCancelled != true else {
|
||||
return nil
|
||||
}
|
||||
let chapterBuilder = context.makeTextBookBuilder(layoutConfig: layoutConfig)
|
||||
let renderStart = CFAbsoluteTimeGetCurrent()
|
||||
guard let result = try chapterBuilder.buildChapter(
|
||||
@@ -504,6 +619,13 @@ final class RDEPUBReaderPaginationCoordinator {
|
||||
) else {
|
||||
return nil
|
||||
}
|
||||
guard context.controller != nil,
|
||||
context.paginationToken == token,
|
||||
!cancellationController.isCancelled,
|
||||
operation?.isCancelled != true else {
|
||||
RDEPUBBackgroundTrace.log("MetadataParse", "drop rendered chapter due to cancellation spine=\(spineIndex)")
|
||||
return nil
|
||||
}
|
||||
let renderElapsed = (CFAbsoluteTimeGetCurrent() - renderStart) * 1000
|
||||
|
||||
let chapter = result.chapter
|
||||
@@ -522,6 +644,13 @@ final class RDEPUBReaderPaginationCoordinator {
|
||||
chapterContentHash: cacheKey.chapterContentHash,
|
||||
pageMetadataList: chapter.pages.map { .from($0.metadata) }
|
||||
)
|
||||
guard context.controller != nil,
|
||||
context.paginationToken == token,
|
||||
!cancellationController.isCancelled,
|
||||
operation?.isCancelled != true else {
|
||||
RDEPUBBackgroundTrace.log("MetadataParse", "skip disk write due to cancellation spine=\(spineIndex)")
|
||||
return nil
|
||||
}
|
||||
let writeStart = CFAbsoluteTimeGetCurrent()
|
||||
summaryDiskCache?.write(summary: summary, for: cacheKey)
|
||||
let writeElapsed = (CFAbsoluteTimeGetCurrent() - writeStart) * 1000
|
||||
@@ -540,15 +669,22 @@ final class RDEPUBReaderPaginationCoordinator {
|
||||
}
|
||||
|
||||
guard let renderResult else { return }
|
||||
guard context.controller != nil,
|
||||
context.paginationToken == token,
|
||||
!cancellationController.isCancelled,
|
||||
operation?.isCancelled != true else {
|
||||
return
|
||||
}
|
||||
|
||||
// 锁内只做写入和计数,快照数据后锁外构建 pageMap
|
||||
var snapshot: [Int: RDEPUBChapterSummary]?
|
||||
resultLock.lock()
|
||||
summariesBySpineIndex[spineIndex] = renderResult
|
||||
totalResolvedCount += 1
|
||||
if totalResolvedCount - lastAppliedCount >= refreshInterval || totalResolvedCount == allBuildableIndices.count {
|
||||
lastAppliedCount = totalResolvedCount
|
||||
snapshot = summariesBySpineIndex
|
||||
parseState.summariesBySpineIndex[spineIndex] = renderResult
|
||||
parseState.totalResolvedCount += 1
|
||||
if parseState.totalResolvedCount - parseState.lastAppliedCount >= refreshInterval
|
||||
|| parseState.totalResolvedCount == allBuildableIndices.count {
|
||||
parseState.lastAppliedCount = parseState.totalResolvedCount
|
||||
snapshot = parseState.summariesBySpineIndex
|
||||
}
|
||||
resultLock.unlock()
|
||||
|
||||
@@ -561,20 +697,54 @@ final class RDEPUBReaderPaginationCoordinator {
|
||||
timingLock.unlock()
|
||||
DispatchQueue.main.async {
|
||||
guard context.paginationToken == token,
|
||||
context.controller != nil else { return }
|
||||
context.controller != nil,
|
||||
!cancellationController.isCancelled else { return }
|
||||
context.runtime?.refreshBookPageMapInPlace(partialMap)
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
guard !cancellationController.isCancelled,
|
||||
context.paginationToken == token,
|
||||
context.controller != nil,
|
||||
operation?.isCancelled != true else {
|
||||
return
|
||||
}
|
||||
timingLock.lock()
|
||||
failedChapters += 1
|
||||
timingLock.unlock()
|
||||
RDEPUBBackgroundTrace.log("MetadataParse", "buildChapter FAILED: spine=\(spineIndex) error=\(error)")
|
||||
|
||||
// 添加到重试队列(带退避延迟)
|
||||
self.scheduleRetry(
|
||||
spineIndex: spineIndex,
|
||||
retryCount: 0,
|
||||
token: token,
|
||||
context: context,
|
||||
parser: parser,
|
||||
publication: publication,
|
||||
pageSize: pageSize,
|
||||
layoutConfig: layoutConfig,
|
||||
style: style,
|
||||
renderSignature: renderSignature,
|
||||
summaryDiskCache: summaryDiskCache,
|
||||
contentHashBySpineIndex: contentHashBySpineIndex,
|
||||
resultLock: resultLock,
|
||||
parseState: parseState,
|
||||
allBuildableIndices: allBuildableIndices,
|
||||
catalog: catalog,
|
||||
refreshInterval: refreshInterval,
|
||||
cancellationController: cancellationController
|
||||
)
|
||||
}
|
||||
}
|
||||
queue.addOperation(operation)
|
||||
}
|
||||
queue.waitUntilAllOperationsAreFinished()
|
||||
summaryDiskCache?.flushPendingWrites()
|
||||
if !cancellationController.isCancelled,
|
||||
context.paginationToken == token,
|
||||
context.controller != nil {
|
||||
summaryDiskCache?.flushPendingWrites()
|
||||
}
|
||||
|
||||
let wallClockMs = Int((CFAbsoluteTimeGetCurrent() - wallClockStart) * 1000)
|
||||
timingLock.lock()
|
||||
@@ -595,19 +765,39 @@ final class RDEPUBReaderPaginationCoordinator {
|
||||
context.lastMetadataParseConcurrency = workerCount
|
||||
|
||||
guard context.controller != nil,
|
||||
context.paginationToken == token else {
|
||||
context.paginationToken == token,
|
||||
!cancellationController.isCancelled else {
|
||||
RDEPUBBackgroundTrace.log("MetadataParse", "abort: reader dismissed or token changed")
|
||||
return
|
||||
}
|
||||
|
||||
let finalMergeStart = CFAbsoluteTimeGetCurrent()
|
||||
let pageMap = self.buildPageMap(from: catalog, summaries: summariesBySpineIndex)
|
||||
let pageMap = self.buildPageMap(from: catalog, summaries: parseState.summariesBySpineIndex)
|
||||
let finalMergeMs = Int((CFAbsoluteTimeGetCurrent() - finalMergeStart) * 1000)
|
||||
RDEPUBBackgroundTrace.log(
|
||||
"MetadataParse",
|
||||
"complete chapters=\(pageMap.totalChapters) pages=\(pageMap.totalPages) finalMergeMs=\(finalMergeMs)"
|
||||
)
|
||||
|
||||
// 存储到 BackgroundCoverageStore
|
||||
if let coverageStore = context.runtime?.backgroundCoverageStore {
|
||||
let resolvedSpineIndices = Set(parseState.summariesBySpineIndex.keys)
|
||||
let lowerSpine = resolvedSpineIndices.min() ?? 0
|
||||
let upperSpine = resolvedSpineIndices.max() ?? 0
|
||||
let estimatedBytes = 256 + pageMap.entries.count * 96 + resolvedSpineIndices.count * 16
|
||||
|
||||
let segment = RDEPUBBackgroundCoverageSegment(
|
||||
lowerSpineIndex: lowerSpine,
|
||||
upperSpineIndex: upperSpine,
|
||||
pageMap: pageMap,
|
||||
resolvedSpineIndices: resolvedSpineIndices,
|
||||
generatedAt: CFAbsoluteTimeGetCurrent(),
|
||||
renderSignature: renderSignature,
|
||||
estimatedMemoryBytes: estimatedBytes
|
||||
)
|
||||
coverageStore.addSegment(segment)
|
||||
}
|
||||
|
||||
DispatchQueue.main.async {
|
||||
guard context.paginationToken == token,
|
||||
context.controller != nil else { return }
|
||||
@@ -616,6 +806,172 @@ final class RDEPUBReaderPaginationCoordinator {
|
||||
}
|
||||
}
|
||||
|
||||
/// 调度失败章节的重试
|
||||
private func scheduleRetry(
|
||||
spineIndex: Int,
|
||||
retryCount: Int,
|
||||
token: UUID,
|
||||
context: RDEPUBReaderContext,
|
||||
parser: RDEPUBParser,
|
||||
publication: RDEPUBPublication,
|
||||
pageSize: CGSize,
|
||||
layoutConfig: RDEPUBTextLayoutConfig,
|
||||
style: RDEPUBTextRenderStyle,
|
||||
renderSignature: String,
|
||||
summaryDiskCache: RDEPUBChapterSummaryDiskCache?,
|
||||
contentHashBySpineIndex: [Int: String],
|
||||
resultLock: NSLock,
|
||||
parseState: MetadataParseState,
|
||||
allBuildableIndices: [Int],
|
||||
catalog: [(key: RDEPUBChapterCacheKey, spineIndex: Int, href: String, title: String)],
|
||||
refreshInterval: Int,
|
||||
cancellationController: MetadataParseCancellationController
|
||||
) {
|
||||
guard retryCount < Self.maxRetryCount else {
|
||||
RDEPUBBackgroundTrace.log(
|
||||
"MetadataParse",
|
||||
"spine=\(spineIndex) max retries reached, marking as deferredFailure"
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
let delay = Self.retryDelays[min(retryCount, Self.retryDelays.count - 1)]
|
||||
RDEPUBBackgroundTrace.log(
|
||||
"MetadataParse",
|
||||
"scheduling retry for spine=\(spineIndex) attempt=\(retryCount + 1) delay=\(delay)s"
|
||||
)
|
||||
|
||||
DispatchQueue.global(qos: .utility).asyncAfter(deadline: .now() + delay) { [weak self] in
|
||||
guard let self else { return }
|
||||
guard context.controller != nil,
|
||||
context.paginationToken == token,
|
||||
!cancellationController.isCancelled else {
|
||||
return
|
||||
}
|
||||
|
||||
do {
|
||||
let chapterBuilder = context.makeTextBookBuilder(layoutConfig: layoutConfig)
|
||||
guard let result = try chapterBuilder.buildChapter(
|
||||
parser: parser,
|
||||
publication: publication,
|
||||
spineIndex: spineIndex,
|
||||
pageSize: pageSize,
|
||||
style: style
|
||||
) else {
|
||||
return
|
||||
}
|
||||
guard context.controller != nil,
|
||||
context.paginationToken == token,
|
||||
!cancellationController.isCancelled else {
|
||||
RDEPUBBackgroundTrace.log("MetadataParse", "drop retry result due to cancellation spine=\(spineIndex)")
|
||||
return
|
||||
}
|
||||
|
||||
let chapter = result.chapter
|
||||
let precomputedHash = contentHashBySpineIndex[spineIndex] ?? ""
|
||||
let cacheKey = context.chapterCacheKey(
|
||||
forSpineIndex: spineIndex,
|
||||
precomputedContentHash: precomputedHash,
|
||||
renderSignature: renderSignature
|
||||
)
|
||||
let summary = RDEPUBChapterSummary(
|
||||
pageRanges: chapter.pages.map { .init(location: $0.contentRange.location, length: $0.contentRange.length) },
|
||||
pageCount: chapter.pages.count,
|
||||
fragmentOffsets: chapter.fragmentOffsets,
|
||||
renderSignature: cacheKey.renderSignature,
|
||||
schemaVersion: RDEPUBChapterSummary.currentSchemaVersion,
|
||||
chapterContentHash: cacheKey.chapterContentHash,
|
||||
pageMetadataList: chapter.pages.map { .from($0.metadata) }
|
||||
)
|
||||
guard context.controller != nil,
|
||||
context.paginationToken == token,
|
||||
!cancellationController.isCancelled else {
|
||||
RDEPUBBackgroundTrace.log("MetadataParse", "skip retry disk write due to cancellation spine=\(spineIndex)")
|
||||
return
|
||||
}
|
||||
summaryDiskCache?.write(summary: summary, for: cacheKey)
|
||||
|
||||
resultLock.lock()
|
||||
parseState.summariesBySpineIndex[spineIndex] = summary
|
||||
parseState.totalResolvedCount += 1
|
||||
let shouldRefresh =
|
||||
parseState.totalResolvedCount - parseState.lastAppliedCount >= refreshInterval
|
||||
|| parseState.totalResolvedCount == allBuildableIndices.count
|
||||
if shouldRefresh {
|
||||
parseState.lastAppliedCount = parseState.totalResolvedCount
|
||||
}
|
||||
resultLock.unlock()
|
||||
|
||||
if shouldRefresh {
|
||||
let partialMap = self.buildPageMap(from: catalog, summaries: parseState.summariesBySpineIndex)
|
||||
DispatchQueue.main.async {
|
||||
guard context.paginationToken == token,
|
||||
context.controller != nil,
|
||||
!cancellationController.isCancelled else { return }
|
||||
context.runtime?.refreshBookPageMapInPlace(partialMap)
|
||||
}
|
||||
}
|
||||
|
||||
RDEPUBBackgroundTrace.log(
|
||||
"MetadataParse",
|
||||
"retry succeeded for spine=\(spineIndex) attempt=\(retryCount + 1)"
|
||||
)
|
||||
} catch {
|
||||
RDEPUBBackgroundTrace.log(
|
||||
"MetadataParse",
|
||||
"retry failed for spine=\(spineIndex) attempt=\(retryCount + 1) error=\(error)"
|
||||
)
|
||||
// 继续重试
|
||||
self.scheduleRetry(
|
||||
spineIndex: spineIndex,
|
||||
retryCount: retryCount + 1,
|
||||
token: token,
|
||||
context: context,
|
||||
parser: parser,
|
||||
publication: publication,
|
||||
pageSize: pageSize,
|
||||
layoutConfig: layoutConfig,
|
||||
style: style,
|
||||
renderSignature: renderSignature,
|
||||
summaryDiskCache: summaryDiskCache,
|
||||
contentHashBySpineIndex: contentHashBySpineIndex,
|
||||
resultLock: resultLock,
|
||||
parseState: parseState,
|
||||
allBuildableIndices: allBuildableIndices,
|
||||
catalog: catalog,
|
||||
refreshInterval: refreshInterval,
|
||||
cancellationController: cancellationController
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func cancelActiveMetadataParseWork() {
|
||||
metadataParseControlLock.lock()
|
||||
let controller = activeMetadataParseCancellationController
|
||||
activeMetadataParseCancellationController = nil
|
||||
metadataParseControlLock.unlock()
|
||||
controller?.cancel()
|
||||
}
|
||||
|
||||
private func beginMetadataParseCancellationController(for token: UUID) -> MetadataParseCancellationController {
|
||||
let controller = MetadataParseCancellationController(token: token)
|
||||
metadataParseControlLock.lock()
|
||||
let previous = activeMetadataParseCancellationController
|
||||
activeMetadataParseCancellationController = controller
|
||||
metadataParseControlLock.unlock()
|
||||
previous?.cancel()
|
||||
return controller
|
||||
}
|
||||
|
||||
private func finishMetadataParseCancellationController(_ controller: MetadataParseCancellationController) {
|
||||
metadataParseControlLock.lock()
|
||||
if activeMetadataParseCancellationController === controller {
|
||||
activeMetadataParseCancellationController = nil
|
||||
}
|
||||
metadataParseControlLock.unlock()
|
||||
}
|
||||
|
||||
private func restoreBookPageMapIfPossible(publication: RDEPUBPublication) -> RDEPUBBookPageMap? {
|
||||
guard let summaryDiskCache = context.runtime?.summaryDiskCache,
|
||||
let parser = context.parser else {
|
||||
|
||||
Reference in New Issue
Block a user