mirror of
https://github.com/Aminark/legado-ios.git
synced 2026-10-20 19:21:22 +08:00
142 lines
5.7 KiB
Swift
142 lines
5.7 KiB
Swift
#if SWIFT_PACKAGE
|
|
import LegadoCore
|
|
#endif
|
|
import Foundation
|
|
|
|
@MainActor
|
|
final class ReplaceRuleLibraryModel: ObservableObject {
|
|
@Published private(set) var rules: [ReplaceRule] = []
|
|
@Published var notice: Notice?
|
|
|
|
struct Notice: Identifiable {
|
|
enum Kind { case success, error }
|
|
let id = UUID()
|
|
let kind: Kind
|
|
let title: String
|
|
let detail: String
|
|
}
|
|
|
|
private let repository: any ReplaceRuleRepository
|
|
private let importer = ReplaceRuleImporter()
|
|
|
|
init(repository: any ReplaceRuleRepository) { self.repository = repository }
|
|
|
|
static func live() -> ReplaceRuleLibraryModel {
|
|
let base = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask)[0]
|
|
.appendingPathComponent("Legado", isDirectory: true)
|
|
return ReplaceRuleLibraryModel(repository: FileReplaceRuleRepository(fileURL: base.appendingPathComponent("replace-rules.json")))
|
|
}
|
|
|
|
static func demo() -> ReplaceRuleLibraryModel {
|
|
ReplaceRuleLibraryModel(repository: InMemoryReplaceRuleRepository())
|
|
}
|
|
|
|
func load() async {
|
|
do { rules = try await repository.load() }
|
|
catch { notice = .init(kind: .error, title: "净化规则读取失败", detail: error.localizedDescription) }
|
|
}
|
|
|
|
func importFiles(_ urls: [URL]) async {
|
|
do {
|
|
var updated = rules
|
|
var count = 0
|
|
for url in urls {
|
|
let granted = url.startAccessingSecurityScopedResource()
|
|
defer { if granted { url.stopAccessingSecurityScopedResource() } }
|
|
let incoming = try importer.decode(Data(contentsOf: url, options: .mappedIfSafe))
|
|
updated = importer.merge(incoming, into: updated)
|
|
count += incoming.count
|
|
}
|
|
try await repository.save(updated)
|
|
rules = updated
|
|
notice = .init(kind: .success, title: "净化规则已导入", detail: "本次读取 \(count) 条,当前共 \(rules.count) 条。")
|
|
} catch {
|
|
notice = .init(kind: .error, title: "净化规则导入失败", detail: error.localizedDescription)
|
|
}
|
|
}
|
|
|
|
func importRemoteURL(_ value: String) async { await importText(value) }
|
|
|
|
func importText(_ value: String) async {
|
|
do {
|
|
let input = try RuleImportInput.parse(value, kind: "replaceRule")
|
|
let data: Data
|
|
switch input {
|
|
case .json(let value): data = Data(value.utf8)
|
|
case .remote:
|
|
let (body, response) = try await URLSessionHTTPTransport.shared.download(for: input.request()!)
|
|
if let response = response as? HTTPURLResponse, !(200...299).contains(response.statusCode) { throw URLError(.badServerResponse) }
|
|
data = body
|
|
}
|
|
let incoming = try importer.decode(data)
|
|
let updated = importer.merge(incoming, into: rules)
|
|
try await repository.save(updated)
|
|
rules = updated
|
|
notice = .init(kind: .success, title: "净化规则已导入", detail: "本次读取 \(incoming.count) 条,当前共 \(rules.count) 条。")
|
|
} catch { notice = .init(kind: .error, title: "净化规则导入失败", detail: error.localizedDescription) }
|
|
}
|
|
|
|
func newRule(copying original: ReplaceRule? = nil) -> ReplaceRule {
|
|
let now = Int64(Date().timeIntervalSince1970 * 1_000)
|
|
let maximumID = rules.map(\.id).max() ?? 0
|
|
let id = max(now, maximumID < Int64.max ? maximumID + 1 : now)
|
|
var rule = original ?? ReplaceRule(id: id, name: "", pattern: "")
|
|
rule.id = id
|
|
if original != nil { rule.name += " 副本" }
|
|
rule.sortOrder = (rules.map(\.sortOrder).filter { $0 != Int.min && $0 < Int.max }.max() ?? -1) + 1
|
|
return rule
|
|
}
|
|
|
|
func save(_ rule: ReplaceRule) async -> Bool {
|
|
do { try ReplaceRuleValidator.validate(rule) }
|
|
catch { notice = .init(kind: .error, title: "规则无效", detail: error.localizedDescription); return false }
|
|
return await persist(importer.merge([rule], into: rules))
|
|
}
|
|
|
|
func move(from offsets: IndexSet, to destination: Int) async {
|
|
var updated = rules
|
|
updated.move(fromOffsets: offsets, toOffset: destination)
|
|
for index in updated.indices { updated[index].sortOrder = index }
|
|
await persist(updated)
|
|
}
|
|
|
|
func remove(id: Int64) async { await persist(rules.filter { $0.id != id }) }
|
|
|
|
func applySilentSubscription(_ data: Data) async throws {
|
|
let incoming = try importer.decode(data)
|
|
let changed = incoming.filter { value in
|
|
guard let old = rules.first(where: { $0.id == value.id }) else { return true }
|
|
return old.pattern != value.pattern || old.replacement != value.replacement
|
|
}
|
|
guard !changed.isEmpty else { return }
|
|
let updated = importer.merge(changed, into: rules)
|
|
try await repository.save(updated)
|
|
rules = updated
|
|
}
|
|
|
|
func setEnabled(_ enabled: Bool, for id: Int64) async {
|
|
guard let index = rules.firstIndex(where: { $0.id == id }) else { return }
|
|
var updated = rules
|
|
updated[index].isEnabled = enabled
|
|
await persist(updated)
|
|
}
|
|
|
|
func remove(at offsets: IndexSet) async {
|
|
var updated = rules
|
|
updated.remove(atOffsets: offsets)
|
|
await persist(updated)
|
|
}
|
|
|
|
@discardableResult
|
|
private func persist(_ updated: [ReplaceRule]) async -> Bool {
|
|
do {
|
|
try await repository.save(updated)
|
|
rules = updated
|
|
return true
|
|
} catch {
|
|
notice = .init(kind: .error, title: "净化规则保存失败", detail: error.localizedDescription)
|
|
return false
|
|
}
|
|
}
|
|
}
|