Files
legado-ios/Sources/LegadoCore/Import/RSSSourceImporter.swift
T

89 lines
3.5 KiB
Swift

import Foundation
public struct RSSSourceImportPayload: Sendable {
public var sources: [RssSource]
public var subscriptionURLs: [String]
public init(sources: [RssSource] = [], subscriptionURLs: [String] = []) {
self.sources = sources
self.subscriptionURLs = subscriptionURLs
}
}
public enum RSSSourceImportError: Error, LocalizedError, Sendable {
case emptyDocument
case wrongFormat
case invalidSource
case duplicate(String)
public var errorDescription: String? {
switch self {
case .emptyDocument: "导入内容为空"
case .wrongFormat: "RSS源格式错误"
case .invalidSource: "不是订阅源"
case .duplicate(let url): "RSS源已存在:\(url)"
}
}
}
public struct RSSSourceImporter: Sendable {
public init() {}
public func decode(_ data: Data) throws -> RSSSourceImportPayload {
var data = data
if data.starts(with: [0xEF, 0xBB, 0xBF]) { data = Data(data.dropFirst(3)) }
guard !data.isEmpty else { throw RSSSourceImportError.emptyDocument }
let decoder = JSONDecoder()
if let manifest = try? decoder.decode(SubscriptionManifest.self, from: data), !manifest.sourceUrls.isEmpty {
return .init(subscriptionURLs: manifest.sourceUrls)
}
let sources: [RssSource]
if let values = try? decoder.decode([RssSource].self, from: data) { sources = values }
else if let value = try? decoder.decode(RssSource.self, from: data) { sources = [value] }
else { throw RSSSourceImportError.wrongFormat }
guard sources.isEmpty || sources.first?.sourceUrl.isEmpty == false else { throw RSSSourceImportError.invalidSource }
return .init(sources: sources)
}
public func merge(
_ incoming: [RssSource],
into existing: [RssSource],
duplicatePolicy: DuplicatePolicy = .replace,
addGroup: String? = nil
) throws -> [RssSource] {
var result = existing
var indexes = Dictionary(uniqueKeysWithValues: result.enumerated().map { ($0.element.sourceUrl, $0.offset) })
for original in incoming where !original.sourceUrl.isEmpty {
var source = original
if let addGroup, !addGroup.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty { source.addGroups(addGroup) }
if let index = indexes[source.sourceUrl] {
switch duplicatePolicy {
case .replace: result[index] = source
case .skip: continue
case .fail: throw RSSSourceImportError.duplicate(source.sourceUrl)
}
} else {
indexes[source.sourceUrl] = result.count
result.append(source)
}
}
return result
}
public func defaultSelection(incoming: [RssSource], existing: [RssSource]) -> [Bool] {
let current = Dictionary(uniqueKeysWithValues: existing.map { ($0.sourceUrl, $0.lastUpdateTime) })
return incoming.map { source in
guard let oldTime = current[source.sourceUrl] else { return true }
return oldTime < source.lastUpdateTime
}
}
public func encode(_ sources: [RssSource], prettyPrinted: Bool = true) throws -> Data {
let encoder = JSONEncoder()
if prettyPrinted { encoder.outputFormatting = [.prettyPrinted, .sortedKeys, .withoutEscapingSlashes] }
return try encoder.encode(sources)
}
private struct SubscriptionManifest: Decodable {
var sourceUrls: [String]
}
}