feat: Sdk 封装
This commit is contained in:
78
Sources/DuooomiBleSDK/Services/AniConverter.swift
Normal file
78
Sources/DuooomiBleSDK/Services/AniConverter.swift
Normal file
@@ -0,0 +1,78 @@
|
||||
import Foundation
|
||||
|
||||
struct AniConverterError: LocalizedError {
|
||||
let message: String
|
||||
var errorDescription: String? { message }
|
||||
}
|
||||
|
||||
/// 将源视频/图片转换为设备可用的 ANI 文件。
|
||||
final class AniConverter {
|
||||
private let config: DuooomiBleConfig
|
||||
|
||||
private var endpoint: URL {
|
||||
config.apiHost.appendingPathComponent("api/auth/loomart/file/convert-to-ani")
|
||||
}
|
||||
|
||||
private let session: URLSession = {
|
||||
let cfg = URLSessionConfiguration.default
|
||||
cfg.timeoutIntervalForRequest = 120
|
||||
cfg.timeoutIntervalForResource = 180
|
||||
cfg.waitsForConnectivity = true
|
||||
return URLSession(configuration: cfg)
|
||||
}()
|
||||
|
||||
init(config: DuooomiBleConfig) {
|
||||
self.config = config
|
||||
}
|
||||
|
||||
/// 调用 ANI 转换接口,返回 ani 文件的 CDN URL。
|
||||
func convert(fileUrl: String) async throws -> String {
|
||||
do {
|
||||
return try await performConvert(fileUrl: fileUrl)
|
||||
} catch let error as NSError where error.code == NSURLErrorNetworkConnectionLost {
|
||||
return try await performConvert(fileUrl: fileUrl)
|
||||
}
|
||||
}
|
||||
|
||||
private func performConvert(fileUrl: String) async throws -> String {
|
||||
var request = URLRequest(url: endpoint)
|
||||
request.httpMethod = "POST"
|
||||
request.setValue("application/json", forHTTPHeaderField: "content-type")
|
||||
request.setValue(config.apiKey, forHTTPHeaderField: "x-api-key")
|
||||
request.timeoutInterval = 120
|
||||
|
||||
let body: [String: Any] = [
|
||||
"videoUrl": fileUrl,
|
||||
"width": 360,
|
||||
"height": 360,
|
||||
"fps": "24",
|
||||
]
|
||||
request.httpBody = try JSONSerialization.data(withJSONObject: body)
|
||||
|
||||
let (data, response) = try await session.data(for: request)
|
||||
|
||||
if let http = response as? HTTPURLResponse, !(200..<300).contains(http.statusCode) {
|
||||
throw AniConverterError(message: "HTTP \(http.statusCode)")
|
||||
}
|
||||
|
||||
guard let json = try JSONSerialization.jsonObject(with: data) as? [String: Any] else {
|
||||
throw AniConverterError(message: "Invalid JSON response")
|
||||
}
|
||||
|
||||
guard (json["success"] as? Bool) == true,
|
||||
let outer = json["data"] as? [String: Any] else {
|
||||
throw AniConverterError(message: "ANI API reported failure")
|
||||
}
|
||||
|
||||
if (outer["status"] as? Bool) != true {
|
||||
let msg = (outer["msg"] as? String) ?? "ANI conversion failed"
|
||||
throw AniConverterError(message: msg)
|
||||
}
|
||||
|
||||
guard let aniUrl = outer["data"] as? String, !aniUrl.isEmpty else {
|
||||
throw AniConverterError(message: "Missing ani url in response")
|
||||
}
|
||||
|
||||
return aniUrl
|
||||
}
|
||||
}
|
||||
60
Sources/DuooomiBleSDK/Services/FirmwareService.swift
Normal file
60
Sources/DuooomiBleSDK/Services/FirmwareService.swift
Normal file
@@ -0,0 +1,60 @@
|
||||
import Foundation
|
||||
|
||||
/// 固件信息
|
||||
public struct FirmwareInfo: Codable, Equatable, Sendable {
|
||||
public let version: String
|
||||
public let fileUrl: String
|
||||
public let description: String?
|
||||
public let fileSize: String?
|
||||
public let fileMd5: String?
|
||||
public let identifier: String?
|
||||
public let status: String?
|
||||
}
|
||||
|
||||
struct FirmwareResponse: Codable {
|
||||
let success: Bool
|
||||
let data: FirmwareInfo?
|
||||
}
|
||||
|
||||
/// 固件服务内部实现
|
||||
final class FirmwareService {
|
||||
private let config: DuooomiBleConfig
|
||||
private let latestPath = "api/auth/loomart/firmware/latest-published"
|
||||
|
||||
init(config: DuooomiBleConfig) {
|
||||
self.config = config
|
||||
}
|
||||
|
||||
func fetchLatest(identifier: String? = nil, status: String? = nil) async throws -> FirmwareInfo? {
|
||||
let id = (identifier ?? config.firmwareIdentifier).trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
let st = status ?? config.firmwareStatus
|
||||
guard !id.isEmpty else {
|
||||
throw DuooomiBleError.transferFailed("Invalid firmware identifier")
|
||||
}
|
||||
|
||||
var comps = URLComponents(url: config.apiHost.appendingPathComponent(latestPath), resolvingAgainstBaseURL: false)!
|
||||
comps.queryItems = [
|
||||
URLQueryItem(name: "identifier", value: id),
|
||||
URLQueryItem(name: "status", value: st)
|
||||
]
|
||||
guard let url = comps.url else {
|
||||
throw DuooomiBleError.transferFailed("Invalid firmware URL")
|
||||
}
|
||||
|
||||
var req = URLRequest(url: url)
|
||||
req.httpMethod = "GET"
|
||||
req.setValue(config.apiKey, forHTTPHeaderField: "x-api-key")
|
||||
req.setValue("application/json", forHTTPHeaderField: "accept")
|
||||
|
||||
let (data, resp) = try await URLSession.shared.data(for: req)
|
||||
guard let http = resp as? HTTPURLResponse else {
|
||||
throw DuooomiBleError.transferFailed("Invalid response")
|
||||
}
|
||||
guard (200...299).contains(http.statusCode) else {
|
||||
throw DuooomiBleError.transferFailed("HTTP \(http.statusCode)")
|
||||
}
|
||||
|
||||
let decoded = try JSONDecoder().decode(FirmwareResponse.self, from: data)
|
||||
return decoded.data
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user