Files
sing-box-for-apple/Library/Database/Profile+Transferable.swift
T

181 lines
6.5 KiB
Swift

import Foundation
import Libbox
import SwiftUI
import UniformTypeIdentifiers
public extension Profile {
func toContent() throws -> LibboxProfileContent {
let content = LibboxProfileContent()
content.name = name
content.type = Int32(type.rawValue)
content.config = try read()
if type != .local {
content.remotePath = remoteURL!
}
if type == .remote {
content.autoUpdate = autoUpdate
content.autoUpdateInterval = autoUpdateInterval
if let lastUpdated {
content.lastUpdated = Int64(lastUpdated.timeIntervalSince1970 * 1000)
}
}
return content
}
}
public func dateFromTimestamp(_ timestamp: Int64) -> Date {
if timestamp > 100_000_000_000 {
return Date(timeIntervalSince1970: Double(timestamp) / 1000)
} else {
return Date(timeIntervalSince1970: Double(timestamp))
}
}
@available(iOS 16.0, macOS 13.0, *)
extension Profile: Transferable {
public static var transferRepresentation: some TransferRepresentation {
ProxyRepresentation { profile in
try TypedProfile(profile.toContent())
}
}
}
public extension LibboxProfileContent {
static func from(_ data: Data) throws -> LibboxProfileContent {
var error: NSError?
let content = LibboxDecodeProfileContent(data, &error)
if let error {
throw error
}
return content!
}
@discardableResult
func importProfile() async throws -> Profile {
let nextProfileID = try await ProfileManager.nextID()
let profileConfigDirectory = FilePath.sharedDirectory.appendingPathComponent("configs", isDirectory: true)
try FileManager.default.createDirectory(at: profileConfigDirectory, withIntermediateDirectories: true)
let profileConfig = profileConfigDirectory.appendingPathComponent("config_\(nextProfileID).json")
try config.write(to: profileConfig, atomically: true, encoding: .utf8)
var lastUpdatedAt: Date?
if lastUpdated > 0 {
lastUpdatedAt = dateFromTimestamp(lastUpdated)
}
let uniqueProfileName = try await ProfileManager.uniqueName(name)
let profile = Profile(name: uniqueProfileName, type: ProfileType(rawValue: Int(type))!, path: profileConfig.relativePath, remoteURL: remotePath, autoUpdate: autoUpdate, autoUpdateInterval: autoUpdateInterval, lastUpdated: lastUpdatedAt)
try await ProfileManager.create(profile)
await SharedPreferences.selectedProfileID.set(profile.mustID)
return profile
}
func generateShareFile() throws -> URL {
let shareDirectory = FilePath.cacheDirectory.appendingPathComponent("share", isDirectory: true)
try FileManager.default.createDirectory(at: shareDirectory, withIntermediateDirectories: true)
let shareFile = shareDirectory.appendingPathComponent("\(name).bpf")
try encode()!.write(to: shareFile)
return shareFile
}
}
public extension String {
func generateShareFile(name: String) throws -> URL {
let shareDirectory = FilePath.cacheDirectory.appendingPathComponent("share", isDirectory: true)
try FileManager.default.createDirectory(at: shareDirectory, withIntermediateDirectories: true)
let shareFile = shareDirectory.appendingPathComponent(name)
try write(to: shareFile, atomically: true, encoding: .utf8)
return shareFile
}
}
@available(iOS 16.0, macOS 13.0, *)
public struct TypedProfile: Transferable, Codable {
public let content: LibboxProfileContent
public init(_ content: LibboxProfileContent) {
self.content = content
}
public init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
let data = try container.decode(Data.self)
try self.init(.from(data))
}
public static var transferRepresentation: some TransferRepresentation {
FileRepresentation(contentType: .profile) { typed in
try SentTransferredFile(typed.content.generateShareFile(), allowAccessingOriginalFile: true)
} importing: { received in
try TypedProfile(.from(Data(contentsOf: received.file)))
}
}
public func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try container.encode(content.encode()!)
}
}
public extension UTType {
static var profile: UTType { .init(exportedAs: AppConfiguration.profileUTType) }
}
#if !os(tvOS)
// MARK: - FileDocument for Export
public struct ProfileExportDocument: FileDocument {
public static var readableContentTypes: [UTType] { [.profile] }
public let data: Data
public let filename: String
public init(content: LibboxProfileContent) throws {
guard let encoded = content.encode() else {
throw NSError(domain: "ProfileExportDocument", code: -1, userInfo: [NSLocalizedDescriptionKey: "Failed to encode profile"])
}
data = encoded
filename = "\(content.name).bpf"
}
public init(configuration: ReadConfiguration) throws {
guard let data = configuration.file.regularFileContents else {
throw CocoaError(.fileReadCorruptFile)
}
self.data = data
filename = "profile.bpf"
}
public func fileWrapper(configuration _: WriteConfiguration) throws -> FileWrapper {
FileWrapper(regularFileWithContents: data)
}
}
public struct ProfileJSONExportDocument: FileDocument {
public static var readableContentTypes: [UTType] { [.json] }
public let content: String
public let filename: String
public init(jsonContent: String, name: String) {
content = jsonContent
filename = "\(name).json"
}
public init(configuration: ReadConfiguration) throws {
guard let data = configuration.file.regularFileContents,
let content = String(data: data, encoding: .utf8)
else {
throw CocoaError(.fileReadCorruptFile)
}
self.content = content
filename = "profile.json"
}
public func fileWrapper(configuration _: WriteConfiguration) throws -> FileWrapper {
guard let data = content.data(using: .utf8) else {
throw CocoaError(.fileWriteInapplicableStringEncoding)
}
return FileWrapper(regularFileWithContents: data)
}
}
#endif