- Rename source module from RDReaderView to RDEpubReaderView - Move all source files from Sources/RDReaderView/ to Sources/RDEpubReaderView/ - Update podspec: RDReaderView.podspec -> RDEpubReaderView.podspec - Update Podfile, demo project, and CocoaPods config for new pod name - Delete old RDReaderView pod support files from ReadViewDemo/Pods - Add new RDEpubReaderView pod support files - Update documentation (API ref, architecture, UML, conventions, etc.) - Add FixedLayoutRotationTests - Update .gitignore: exclude .DS_Store, manual unpack backups, _ssoft-output
64 lines
2.7 KiB
Swift
64 lines
2.7 KiB
Swift
import Foundation
|
||
|
||
/// 加密 EPUB 资源数据提供者。
|
||
///
|
||
/// 宿主 App 实现该协议以支持“解压目录内单文件加密”的 EPUB:
|
||
/// SDK 读取章节 HTML、图片、内联 CSS 等资源时会优先向 provider 索取解密后的数据,
|
||
/// provider 返回 nil 表示该文件无需解密,SDK 回退为直接读取磁盘明文
|
||
/// (明文书、或加密书内的明文文件均走该兜底路径,因此对明文书零影响)。
|
||
///
|
||
/// 密钥推导与解密算法完全由宿主实现,SDK 不持有任何密钥。
|
||
///
|
||
/// 注意:方法会在排版 / 渲染的后台队列被同步调用,实现必须线程安全,
|
||
/// 且除必要的文件 I/O 与解密计算外不得阻塞(例如不要在其中发起网络请求)。
|
||
public protocol RDEPUBResourceDataProvider: AnyObject {
|
||
|
||
/// 返回指定文件解密后的数据。
|
||
/// - Parameter fileURL: 解压目录内资源文件的绝对路径
|
||
/// - Returns: 解密后的数据;返回 nil 表示该文件不需要解密,由 SDK 直读磁盘
|
||
func resourceData(at fileURL: URL) -> Data?
|
||
}
|
||
|
||
/// 加密资源访问登记表。
|
||
///
|
||
/// DTCoreText 在解析 `<img>` 时由自己内部加载图片文件,拿不到 parser 实例,
|
||
/// 因此这里以弱引用登记所有启用了 provider 的 parser,
|
||
/// 供 `RDEPUBDecryptingImageAttachment` 按文件路径反查所属书籍的 provider。
|
||
enum RDEPUBResourceAccessRegistry {
|
||
|
||
private struct WeakParserBox {
|
||
weak var parser: RDEPUBParser?
|
||
}
|
||
|
||
private static let lock = NSLock()
|
||
|
||
private static var boxes: [WeakParserBox] = []
|
||
|
||
/// 登记启用了 provider 的 parser(重复登记会被去重,弱引用失效项顺带清理)
|
||
static func register(_ parser: RDEPUBParser) {
|
||
lock.lock()
|
||
defer { lock.unlock() }
|
||
boxes.removeAll { $0.parser == nil || $0.parser === parser }
|
||
boxes.append(WeakParserBox(parser: parser))
|
||
#if canImport(DTCoreText)
|
||
RDEPUBDecryptingImageAttachment.registerTagClassIfNeeded()
|
||
#endif
|
||
}
|
||
|
||
/// 按文件路径反查 provider 并返回解密数据;
|
||
/// 仅当文件位于某个已登记 parser 的解压目录内才会命中
|
||
static func providedResourceData(at fileURL: URL) -> Data? {
|
||
let targetPath = fileURL.resolvingSymlinksInPath().standardizedFileURL.path
|
||
lock.lock()
|
||
let parsers = boxes.compactMap(\.parser)
|
||
lock.unlock()
|
||
for parser in parsers {
|
||
guard let rootURL = parser.extractionRootURL else { continue }
|
||
let rootPath = rootURL.resolvingSymlinksInPath().standardizedFileURL.path
|
||
guard targetPath.hasPrefix(rootPath + "/") else { continue }
|
||
return parser.providedResourceData(at: fileURL)
|
||
}
|
||
return nil
|
||
}
|
||
}
|