mirror of
https://github.com/Aminark/legado-ios.git
synced 2026-10-24 21:01:22 +08:00
feat: 支持 EPUB 阅读并改善规则兼容与阅读稳定性
支持 EPUB 2/3 导入、目录、封面和章节插图,保留本地书管理与阅读进度。 修复 JSON 模板、动态替换与请求参数兼容,以及 RSS 翻页和连续滚动定位。 更新编辑器工具栏和回归测试,发布版本为 0.1.12(build 18)。
This commit is contained in:
@@ -0,0 +1,89 @@
|
||||
import Foundation
|
||||
import Testing
|
||||
@testable import LegadoCore
|
||||
|
||||
// Opt-in only: point LEGADO_SOURCE_PROBE_FILE at a downloaded JSON source pack.
|
||||
// No remote source or response is included in the application or normal test run.
|
||||
struct BookSourceRemoteProbe {
|
||||
@Test(.enabled(if: ProcessInfo.processInfo.environment["LEGADO_SOURCE_CHAIN_FILE"] != nil))
|
||||
func sampleReadingPipelines() async throws {
|
||||
let path = try #require(ProcessInfo.processInfo.environment["LEGADO_SOURCE_CHAIN_FILE"])
|
||||
let sources = try BookSourceImporter().decode(Data(contentsOf: URL(fileURLWithPath: path)))
|
||||
var reports: [BookSourceDebugReport] = []
|
||||
let transport = ProbeTransport()
|
||||
let keyword = ProcessInfo.processInfo.environment["LEGADO_SOURCE_PROBE_KEYWORD"] ?? "斗罗大陆"
|
||||
for source in sources {
|
||||
var report = await BookSourceDebugger(transport: transport).run(source: source, keyword: keyword)
|
||||
report.contentPreview = nil
|
||||
reports.append(report)
|
||||
print("SOURCE_CHAIN \(source.bookSourceName) " + String(decoding: try JSONEncoder().encode(report), as: UTF8.self))
|
||||
}
|
||||
if let output = ProcessInfo.processInfo.environment["LEGADO_SOURCE_CHAIN_OUTPUT"] {
|
||||
try JSONEncoder().encode(reports).write(to: URL(fileURLWithPath: output))
|
||||
try JSONEncoder().encode(await transport.records()).write(to: URL(fileURLWithPath: output + ".requests.json"))
|
||||
}
|
||||
#expect(reports.count == sources.count)
|
||||
}
|
||||
|
||||
@Test(.enabled(if: ProcessInfo.processInfo.environment["LEGADO_SOURCE_PROBE_FILE"] != nil))
|
||||
func sampleSearches() async throws {
|
||||
guard let path = ProcessInfo.processInfo.environment["LEGADO_SOURCE_PROBE_FILE"] else { return }
|
||||
let sources = try BookSourceImporter().decode(Data(contentsOf: URL(fileURLWithPath: path)))
|
||||
let transport = ProbeTransport()
|
||||
let service = BookSearchService(transport: transport, networkPolicy: .init(), history: .init())
|
||||
let keyword = ProcessInfo.processInfo.environment["LEGADO_SOURCE_PROBE_KEYWORD"] ?? "斗罗大陆"
|
||||
let page = Int(ProcessInfo.processInfo.environment["LEGADO_SOURCE_PROBE_PAGE"] ?? "1") ?? 1
|
||||
var rows: [[String: JSONValue]] = []
|
||||
for await outcome in service.searchOutcomes(keyword: keyword, page: page, sources: sources, maximumConcurrentSources: 3, includeDisabled: true) {
|
||||
let source = try #require(sources.first { $0.id == outcome.sourceID })
|
||||
let row: [String: JSONValue] = [
|
||||
"name": .string(source.bookSourceName), "source": .string(source.id),
|
||||
"count": .number(Double(outcome.books.count)),
|
||||
"books": .array(outcome.books.prefix(3).map { .object([
|
||||
"name": .string($0.name), "url": .string($0.bookURL), "author": .string($0.author),
|
||||
"cover": .string($0.coverURL ?? ""), "wordCount": .string($0.wordCount ?? "")
|
||||
]) }),
|
||||
"diagnostics": .array(outcome.diagnostics.map { .object(["code": .string($0.code), "message": .string($0.message), "rule": .string($0.rule)]) })
|
||||
]
|
||||
rows.append(row)
|
||||
print("SOURCE_PROBE " + JSONValue.object(row).stringValue)
|
||||
}
|
||||
let report = JSONValue.object(["keyword": .string(keyword), "page": .number(Double(page)), "outcomes": .array(rows.map(JSONValue.object)), "requests": .array(await transport.records())])
|
||||
if let output = ProcessInfo.processInfo.environment["LEGADO_SOURCE_PROBE_OUTPUT"] {
|
||||
try JSONEncoder().encode(report).write(to: URL(fileURLWithPath: output))
|
||||
}
|
||||
#expect(rows.count == Set(sources.map(\.id)).count)
|
||||
}
|
||||
}
|
||||
|
||||
private actor ProbeTransport: HTTPTransport {
|
||||
private var values: [JSONValue] = []
|
||||
private let transport = URLSessionHTTPTransport()
|
||||
|
||||
func data(for request: URLRequest) async throws -> HTTPResponse {
|
||||
do {
|
||||
let response = try await transport.data(for: request)
|
||||
var record: [String: JSONValue] = [
|
||||
"url": .string(request.url?.absoluteString ?? ""),
|
||||
"finalURL": .string(response.finalURL.absoluteString),
|
||||
"status": .number(Double(response.statusCode)),
|
||||
"bytes": .number(Double(response.data.count)),
|
||||
"preview": .string(String(response.decodedText.prefix(240)))
|
||||
]
|
||||
if let directory = ProcessInfo.processInfo.environment["LEGADO_SOURCE_PROBE_SNAPSHOT_DIR"] {
|
||||
let folder = URL(fileURLWithPath: directory, isDirectory: true)
|
||||
try FileManager.default.createDirectory(at: folder, withIntermediateDirectories: true)
|
||||
let file = folder.appendingPathComponent(UUID().uuidString + ".response")
|
||||
try response.data.write(to: file)
|
||||
record["snapshot"] = .string(file.path)
|
||||
}
|
||||
values.append(.object(record))
|
||||
return response
|
||||
} catch {
|
||||
values.append(.object(["url": .string(request.url?.absoluteString ?? ""), "error": .string(error.localizedDescription)]))
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
func records() -> [JSONValue] { values }
|
||||
}
|
||||
Reference in New Issue
Block a user