feat: optimize reader caching and document formats

This commit is contained in:
shenlei
2026-07-20 17:22:06 +09:00
parent 52f803e8db
commit 68d9363f0a
35 changed files with 3771 additions and 2229 deletions
@@ -73,7 +73,11 @@ public final class RDEpubURLReaderController: UIViewController {
}
public func applyDemoDisplayType(_ displayType: RDEpubReaderView.DisplayType) {
readerController?.configuration.displayType = displayType
if let readerController {
readerController.configuration.displayType = displayType
} else {
cbzReaderController?.setDisplayType(displayType)
}
emitDemoState(prefix: "display=\(displayType.demoArgumentValue)")
}
@@ -145,14 +149,16 @@ public final class RDEpubURLReaderController: UIViewController {
private func embedReaderController() {
let controller: UIViewController
if bookURL.pathExtension.lowercased() == "epub" {
switch RDReaderDocumentFormat(fileURL: bookURL) {
case .epub:
controller = RDEPUBReaderController(
epubURL: bookURL,
configuration: epubConfiguration
)
} else {
case .cbz:
controller = RDCBZReaderController(bookURL: bookURL, configuration: epubConfiguration)
case .mobi, .plainText:
let bookIdentifier = bookURL.lastPathComponent
let bookTitle = bookURL.deletingPathExtension().lastPathComponent
let pageSize = currentTextPageSize()
let renderStyle = currentTextRenderStyle()
let safeInsets = RDEPUBSafeArea.resolve(view.safeAreaInsets)
@@ -176,23 +182,33 @@ public final class RDEpubURLReaderController: UIViewController {
imageMaxHeightRatio: 0.85
)
)
if let textBook = try? builder.build(textFileURL: bookURL, pageSize: pageSize, style: renderStyle) {
do {
let source: (textURL: URL, title: String)
if RDReaderDocumentFormat(fileURL: bookURL) == .mobi {
let document = try RDMOBIParser().parse(fileURL: bookURL)
source = (try cachedMOBITextURL(text: document.text), document.title)
} else {
source = (bookURL, bookURL.deletingPathExtension().lastPathComponent)
}
let textBook = try builder.build(textFileURL: source.textURL, pageSize: pageSize, style: renderStyle)
controller = RDEPUBReaderController(
textBook: textBook,
bookIdentifier: bookIdentifier,
title: bookTitle,
textFileURL: bookURL,
title: source.title,
textFileURL: source.textURL,
configuration: epubConfiguration
)
} else {
let fallback = UIViewController()
let textView = UITextView()
textView.isEditable = false
textView.text = rd_decodeTextFile(url: bookURL)
fallback.view = textView
controller = fallback
} catch {
controller = makeErrorController(error)
}
case nil:
controller = makeErrorController(
NSError(
domain: "RDEpubReaderView.UnsupportedFormat",
code: 1,
userInfo: [NSLocalizedDescriptionKey: "不支持此文件格式"]
)
)
}
embeddedController = controller
addChild(controller)
@@ -213,8 +229,15 @@ public final class RDEpubURLReaderController: UIViewController {
embeddedController as? RDEPUBReaderController
}
private var cbzReaderController: RDCBZReaderController? {
embeddedController as? RDCBZReaderController
}
@discardableResult
private func performDemoPageNavigation(_ pageNumber: Int, animated: Bool) -> Bool {
if let cbzReaderController {
return cbzReaderController.goToPage(pageNumber, animated: animated)
}
guard let readerController else { return false }
guard pageNumber > 0 else { return false }
@@ -451,18 +474,41 @@ public final class RDEpubURLReaderController: UIViewController {
)
}
private func rd_decodeTextFile(url: URL) -> String {
if let content = try? NSString(contentsOf: url, encoding: String.Encoding.utf8.rawValue) as String {
return content
}
if let content = try? NSString(contentsOf: url, encoding: 0x80000632) as String {
return content
}
if let content = try? NSString(contentsOf: url, encoding: 0x80000631) as String {
return content
}
return ""
private func cachedMOBITextURL(text: String) throws -> URL {
let attributes = try? FileManager.default.attributesOfItem(atPath: bookURL.path)
let size = (attributes?[.size] as? NSNumber)?.uint64Value ?? 0
let modified = (attributes?[.modificationDate] as? Date)?.timeIntervalSince1970 ?? 0
let safeName = bookURL.deletingPathExtension().lastPathComponent
.replacingOccurrences(of: #"[^A-Za-z0-9._-]"#, with: "_", options: .regularExpression)
let base = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first
?? FileManager.default.temporaryDirectory
let directory = base.appendingPathComponent("ssreaderview-mobi", isDirectory: true)
.appendingPathComponent("\(safeName)-\(size)-\(Int64(modified))", isDirectory: true)
let textURL = directory.appendingPathComponent("content.txt")
if FileManager.default.fileExists(atPath: textURL.path) { return textURL }
try FileManager.default.createDirectory(at: directory, withIntermediateDirectories: true)
try Data(text.utf8).write(to: textURL, options: .atomic)
return textURL
}
private func makeErrorController(_ error: Error) -> UIViewController {
let fallback = UIViewController()
fallback.view.backgroundColor = .systemBackground
let label = UILabel()
label.numberOfLines = 0
label.textAlignment = .center
label.textColor = .secondaryLabel
label.text = error.localizedDescription
label.translatesAutoresizingMaskIntoConstraints = false
fallback.view.addSubview(label)
NSLayoutConstraint.activate([
label.leadingAnchor.constraint(equalTo: fallback.view.leadingAnchor, constant: 24),
label.trailingAnchor.constraint(equalTo: fallback.view.trailingAnchor, constant: -24),
label.centerYAnchor.constraint(equalTo: fallback.view.centerYAnchor)
])
return fallback
}
}
extension RDEpubURLReaderController: RDEPUBReaderDelegate {