feat: chapter runtime refactoring and related updates
- Refactor chapter runtime: replace window coordinator/snapshot with warmup orchestrator - Update EPUB core: parser, reading session, JS bridge, navigator layout - Update reader controller: data source, location resolution, persistence - Update chapter runtime: data cache, loader, runtime store, disk cache, warmup orchestrator - Remove deprecated navigation state machine and pagination state - Update text rendering: book cache, HTML normalizer - Update UI: text content view, dark image adjuster, text selection controller - Update settings and reader configuration - Add CODE_REVIEW.md and AUDIT_FINAL.md documentation - Update pod dependencies (remove SSAlertSwift, SnapKit) - Update podspec and pod configuration files Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -29,7 +29,11 @@ extension RDEPUBParser {
|
||||
let extractionURL = temporaryExtractionDirectory(for: epubURL)
|
||||
|
||||
if fileManager.fileExists(atPath: extractionURL.path) {
|
||||
return extractionURL
|
||||
let containerURL = extractionURL.appendingPathComponent("META-INF/container.xml")
|
||||
if fileManager.fileExists(atPath: containerURL.path) {
|
||||
return extractionURL
|
||||
}
|
||||
try? fileManager.removeItem(at: extractionURL)
|
||||
}
|
||||
|
||||
guard let archive = Archive(url: epubURL, accessMode: .read) else {
|
||||
@@ -38,24 +42,50 @@ extension RDEPUBParser {
|
||||
|
||||
try fileManager.createDirectory(at: extractionURL, withIntermediateDirectories: true)
|
||||
|
||||
for entry in archive {
|
||||
guard let destinationURL = validatedExtractionDestination(for: entry.path, extractionRoot: extractionURL) else {
|
||||
throw RDEPUBParserError.invalidArchiveEntryPath(entry.path)
|
||||
}
|
||||
switch entry.type {
|
||||
case .directory:
|
||||
try fileManager.createDirectory(at: destinationURL, withIntermediateDirectories: true)
|
||||
case .file:
|
||||
try fileManager.createDirectory(at: destinationURL.deletingLastPathComponent(), withIntermediateDirectories: true)
|
||||
_ = try archive.extract(entry, to: destinationURL)
|
||||
case .symlink:
|
||||
continue
|
||||
do {
|
||||
for entry in archive {
|
||||
if shouldSkipEntry(entry.path) { continue }
|
||||
|
||||
guard let destinationURL = validatedExtractionDestination(for: entry.path, extractionRoot: extractionURL) else {
|
||||
if isCriticalEntry(entry.path) {
|
||||
throw RDEPUBParserError.invalidArchiveEntryPath(entry.path)
|
||||
}
|
||||
continue
|
||||
}
|
||||
switch entry.type {
|
||||
case .directory:
|
||||
try fileManager.createDirectory(at: destinationURL, withIntermediateDirectories: true)
|
||||
case .file:
|
||||
try fileManager.createDirectory(at: destinationURL.deletingLastPathComponent(), withIntermediateDirectories: true)
|
||||
_ = try archive.extract(entry, to: destinationURL)
|
||||
case .symlink:
|
||||
continue
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
try? fileManager.removeItem(at: extractionURL)
|
||||
throw error
|
||||
}
|
||||
|
||||
return extractionURL
|
||||
}
|
||||
|
||||
private func isCriticalEntry(_ entryPath: String) -> Bool {
|
||||
let lowercased = entryPath.lowercased()
|
||||
return lowercased == "mimetype"
|
||||
|| lowercased.hasPrefix("meta-inf/")
|
||||
|| lowercased.hasSuffix(".opf")
|
||||
}
|
||||
|
||||
private func shouldSkipEntry(_ entryPath: String) -> Bool {
|
||||
let lowercased = entryPath.lowercased()
|
||||
if lowercased.hasPrefix("__macosx/") { return true }
|
||||
if lowercased.hasPrefix(".ds_store") { return true }
|
||||
if lowercased.contains("/.ds_store") { return true }
|
||||
if lowercased.hasSuffix("/thumbs.db") { return true }
|
||||
return false
|
||||
}
|
||||
|
||||
private func validatedExtractionDestination(for entryPath: String, extractionRoot: URL) -> URL? {
|
||||
|
||||
if entryPath.hasPrefix("/") {
|
||||
|
||||
Reference in New Issue
Block a user