Add save file option for profile sharing

Add "Save File" and "Save Content JSON" options to profile share menu,
allowing users to save profiles directly to a chosen location using
fileExporter instead of the system share sheet.
This commit is contained in:
世界
2026-01-01 20:25:28 +08:00
parent 55e01a89b5
commit 313cb5d213
11 changed files with 1674 additions and 26 deletions
@@ -0,0 +1,120 @@
import Compression
import Foundation
final class LubyTransformEncoder {
let k: Int
let sliceSize: Int
let checksum: UInt32
let bytes: Int
private let sourceBlocks: [Data]
init(data: Data, sliceSize: Int = 500, compress: Bool = true) {
self.sliceSize = sliceSize
let compressed: Data
if compress {
compressed = Self.deflateCompress(data) ?? data
} else {
compressed = data
}
bytes = compressed.count
sourceBlocks = Self.sliceData(compressed, sliceSize: sliceSize)
k = sourceBlocks.count
checksum = CRC32.checksum(data, k: k)
}
private static func deflateCompress(_ data: Data) -> Data? {
let sourceSize = data.count
let destinationSize = sourceSize + 1024
let destinationBuffer = UnsafeMutablePointer<UInt8>.allocate(capacity: destinationSize)
defer { destinationBuffer.deallocate() }
let compressedSize = data.withUnsafeBytes { sourcePtr -> Int in
guard let baseAddress = sourcePtr.baseAddress else { return 0 }
return compression_encode_buffer(
destinationBuffer,
destinationSize,
baseAddress.assumingMemoryBound(to: UInt8.self),
sourceSize,
nil,
COMPRESSION_ZLIB
)
}
guard compressedSize > 0 else { return nil }
return Data(bytes: destinationBuffer, count: compressedSize)
}
private static func sliceData(_ data: Data, sliceSize: Int) -> [Data] {
var blocks: [Data] = []
var offset = 0
while offset < data.count {
let end = min(offset + sliceSize, data.count)
var block = data.subdata(in: offset ..< end)
if block.count < sliceSize {
block.append(Data(count: sliceSize - block.count))
}
blocks.append(block)
offset += sliceSize
}
return blocks
}
func createBlock(indices: [Int]) -> EncodedBlock {
var result = Data(count: sliceSize)
for index in indices {
let source = sourceBlocks[index]
for i in 0 ..< sliceSize {
result[i] ^= source[i]
}
}
return EncodedBlock(
indices: indices,
data: result,
k: k,
bytes: bytes,
checksum: checksum
)
}
// Ideal Soliton Distribution for degree selection
private func getRandomDegree() -> Int {
var probabilities = [Double](repeating: 0, count: k)
probabilities[0] = 1.0 / Double(k)
for d in 2 ... k {
probabilities[d - 1] = 1.0 / Double(d * (d - 1))
}
var cumulative = [Double](repeating: 0, count: k)
cumulative[0] = probabilities[0]
for i in 1 ..< k {
cumulative[i] = cumulative[i - 1] + probabilities[i]
}
let random = Double.random(in: 0 ... 1)
for i in 0 ..< k {
if random < cumulative[i] {
return i + 1
}
}
return k
}
private func getRandomIndices(degree: Int) -> [Int] {
var indices = Set<Int>()
while indices.count < degree {
indices.insert(Int.random(in: 0 ..< k))
}
return Array(indices)
}
func fountain() -> AnyIterator<EncodedBlock> {
AnyIterator {
let degree = self.getRandomDegree()
let indices = self.getRandomIndices(degree: degree)
return self.createBlock(indices: indices)
}
}
}