diff options
| author | Dustin Mierau <dustin@mierau.me> | 2024-07-24 16:11:16 -0700 |
|---|---|---|
| committer | Dustin Mierau <dustin@mierau.me> | 2024-07-24 16:11:16 -0700 |
| commit | f9e5f8a3a66df6e59726ca1659043fb54ed894e4 (patch) | |
| tree | 4d5a370178cd79c6f9ae5a2bcca1e442327b4193 /Hotline | |
| parent | 8633db7e6ca9f38345d7be0ab90193d6325904bf (diff) | |
Moving to an experimental result builder for Data to simplify construction of headers for Hotline's binary protocol. Fix some UI quirks around file transfers.
Diffstat (limited to 'Hotline')
| -rw-r--r-- | Hotline/Assets.xcassets/File Complete.colorset/Contents.json | 38 | ||||
| -rw-r--r-- | Hotline/Hotline/HotlineDataBuilder.swift | 91 | ||||
| -rw-r--r-- | Hotline/Hotline/HotlineTransferClient.swift | 137 | ||||
| -rw-r--r-- | Hotline/macOS/ServerView.swift | 13 |
4 files changed, 228 insertions, 51 deletions
diff --git a/Hotline/Assets.xcassets/File Complete.colorset/Contents.json b/Hotline/Assets.xcassets/File Complete.colorset/Contents.json new file mode 100644 index 0000000..053701f --- /dev/null +++ b/Hotline/Assets.xcassets/File Complete.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "display-p3", + "components" : { + "alpha" : "1.000", + "blue" : "0x50", + "green" : "0xBC", + "red" : "0x5F" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0x37", + "green" : "0x9D", + "red" : "0x45" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Hotline/Hotline/HotlineDataBuilder.swift b/Hotline/Hotline/HotlineDataBuilder.swift new file mode 100644 index 0000000..87ae966 --- /dev/null +++ b/Hotline/Hotline/HotlineDataBuilder.swift @@ -0,0 +1,91 @@ +import Foundation + +enum DataEndianness { + case big + case little +} + +@resultBuilder +struct DataBuilder { + static var defaultEndian: DataEndianness = .little + + static func buildBlock(_ components: Data...) -> Data { + components.reduce(Data(), +) + } + + static func buildExpression(_ expression: Data) -> Data { + expression + } + + static func buildExpression(_ expression: String) -> Data { + Data(expression.utf8) + } + + static func buildExpression(_ expression: (String, String.Encoding)) -> Data { + expression.0.data(using: expression.1) ?? Data() + } + + static func buildExpression(_ expression: [UInt8]) -> Data { + Data(expression) + } + + static func buildExpression(_ expression: Date) -> Data { + var dateData = Data() + + var year: UInt16 = 0 + var msecs: UInt16 = 0 + var secs: UInt32 = 0 + + if let components = expression.hotlineDateComponents() { + year = components.year + secs = components.seconds + msecs = components.milliseconds + } + + year = DataBuilder.defaultEndian == .big ? year.bigEndian : year.littleEndian + secs = DataBuilder.defaultEndian == .big ? secs.bigEndian : secs.littleEndian + msecs = DataBuilder.defaultEndian == .big ? msecs.bigEndian : msecs.littleEndian + + withUnsafeBytes(of: year) { dateData.append(Data($0)) } + withUnsafeBytes(of: msecs) { dateData.append(Data($0)) } + withUnsafeBytes(of: secs) { dateData.append(Data($0)) } + + return dateData + } + + static func buildExpression(_ expression: Int) -> Data { + print("ADDING BYTE:", expression) + return withUnsafeBytes(of: UInt8(expression)) { Data($0) } + } + + static func buildExpression<T: FixedWidthInteger>(_ expression: (T, DataEndianness)) -> Data { + print("ADDING INTEGER:", expression) + let value = expression.1 == .little ? expression.0.littleEndian : expression.0.bigEndian + return withUnsafeBytes(of: value) { Data($0) } + } + + static func buildExpression<T: FixedWidthInteger>(_ expression: T) -> Data { + buildExpression((expression, DataBuilder.defaultEndian)) + } + + // Support for if statements + static func buildEither(first component: Data) -> Data { + component + } + + static func buildEither(second component: Data) -> Data { + component + } + + // Support for optionals + static func buildOptional(_ component: Data?) -> Data { + component ?? Data() + } +} + +extension Data { + init(endian: DataEndianness = .little, @DataBuilder _ content: () -> Data) { + DataBuilder.defaultEndian = endian + self = content() + } +} diff --git a/Hotline/Hotline/HotlineTransferClient.swift b/Hotline/Hotline/HotlineTransferClient.swift index 6c5db84..262c5ed 100644 --- a/Hotline/Hotline/HotlineTransferClient.swift +++ b/Hotline/Hotline/HotlineTransferClient.swift @@ -42,12 +42,6 @@ protocol HotlineFileUploadClientDelegate: HotlineTransferDelegate { func hotlineFileUploadComplete(client: HotlineFileUploadClient, reference: UInt32) } -//enum HotlineFileTransferType { -// case fileDownload -// case filePreview -// case fileUpload -//} - enum HotlineFileTransferStage: Int { case fileHeader = 1 case fileForkHeader = 2 @@ -257,11 +251,16 @@ class HotlineFileUploadClient: HotlineTransferClient { print("Upload: Sending magic") self.status = .progress(0.0) - var magicData = Data() - magicData.appendUInt32("HTXF".fourCharCode()) - magicData.appendUInt32(self.referenceNumber) - magicData.appendUInt32(self.payloadSize) - magicData.appendUInt32(0) + let magicData = Data(endian: .big) { + "HTXF".fourCharCode() + self.referenceNumber + self.payloadSize + UInt32.zero + } +// magicData.appendUInt32("HTXF".fourCharCode()) +// magicData.appendUInt32(self.referenceNumber) +// magicData.appendUInt32(self.payloadSize) +// magicData.appendUInt32(0) self.stage = .fileHeader self.sendFileData(magicData) @@ -505,11 +504,12 @@ class HotlineFilePreviewClient: HotlineTransferClient { return } - var headerData = Data() - headerData.appendUInt32("HTXF".fourCharCode()) - headerData.appendUInt32(self.referenceNumber) - headerData.appendUInt32(0) - headerData.appendUInt32(0) + let headerData = Data(endian: .big) { + "HTXF".fourCharCode() + self.referenceNumber + UInt32.zero + UInt32.zero + } c.send(content: headerData, completion: .contentProcessed { [weak self] (error) in guard let self = self else { @@ -719,11 +719,21 @@ class HotlineFileDownloadClient: HotlineTransferClient { return } - var headerData = Data() - headerData.appendUInt32("HTXF".fourCharCode()) - headerData.appendUInt32(self.referenceNumber) - headerData.appendUInt32(0) - headerData.appendUInt32(0) + + // Original method +// var headerData = Data() +// headerData.appendUInt32("HTXF".fourCharCode()) +// headerData.appendUInt32(self.referenceNumber) +// headerData.appendUInt32(0) +// headerData.appendUInt32(0) + + // Result builder method + let headerData = Data(endian: .big) { + "HTXF".fourCharCode() + self.referenceNumber + UInt32.zero + UInt32.zero + } c.send(content: headerData, completion: .contentProcessed { [weak self] (error) in guard let self = self else { @@ -1046,14 +1056,21 @@ struct HotlineFileHeader { } func data() -> Data { - var d = Data() - - d.appendUInt32(self.format) - d.appendUInt16(self.version) - d.appendZeros(count: 16) - d.appendUInt16(self.forkCount) + Data(endian: .big) { + self.format + self.version + Data(repeating: 0, count: 16) + self.forkCount + } - return d +// var d = Data() +// +// d.appendUInt32(self.format) +// d.appendUInt16(self.version) +// d.appendZeros(count: 16) +// d.appendUInt16(self.forkCount) +// +// return d } } @@ -1085,14 +1102,21 @@ struct HotlineFileForkHeader { } func data() -> Data { - var d = Data() - - d.appendUInt32(self.forkType) - d.appendUInt32(self.compressionType) - d.appendUInt32(0) - d.appendUInt32(self.dataSize) + Data(endian: .big) { + self.forkType + self.compressionType + UInt32.zero + self.dataSize + } - return d +// var d = Data() +// +// d.appendUInt32(self.forkType) +// d.appendUInt32(self.compressionType) +// d.appendUInt32(0) +// d.appendUInt32(self.dataSize) +// +// return d } var isInfoFork: Bool { @@ -1224,21 +1248,36 @@ struct HotlineFileInfoFork { } func data() -> Data { - var d = Data() - - d.appendUInt32(self.platform) - d.appendUInt32(self.type) - d.appendUInt32(self.creator) - d.appendUInt32(self.flags) - d.appendUInt32(self.platformFlags) - d.appendZeros(count: 32) - d.appendDate(self.createdDate) - d.appendDate(self.modifiedDate) - d.appendUInt16(self.nameScript) + Data(endian: .big) { + self.platform + self.type + self.creator + self.flags + self.platformFlags + Data(repeating: 0, count: 32) + self.createdDate + self.modifiedDate + self.nameScript + UInt16(self.name.count) + (self.name, .macOSRoman) + } - d.appendUInt16(UInt16(self.name.count)) - d.appendString(self.name, encoding: .macOSRoman) - return d +// var d = Data() +// +// d.appendUInt32(self.platform) +// d.appendUInt32(self.type) +// d.appendUInt32(self.creator) +// d.appendUInt32(self.flags) +// d.appendUInt32(self.platformFlags) +// d.appendZeros(count: 32) +// d.appendDate(self.createdDate) +// d.appendDate(self.modifiedDate) +// d.appendUInt16(self.nameScript) +// +// d.appendUInt16(UInt16(self.name.count)) +// d.appendString(self.name, encoding: .macOSRoman) +// +// return d } } diff --git a/Hotline/macOS/ServerView.swift b/Hotline/macOS/ServerView.swift index 5b5b8e3..4e24a1b 100644 --- a/Hotline/macOS/ServerView.swift +++ b/Hotline/macOS/ServerView.swift @@ -579,6 +579,7 @@ struct ServerView: View { struct TransferItemView: View { let transfer: TransferInfo + @Environment(\.controlActiveState) private var controlActiveState @Environment(Hotline.self) private var model: Hotline @State private var hovered: Bool = false @State private var buttonHovered: Bool = false @@ -607,6 +608,7 @@ struct TransferItemView: View { Spacer() FileIconView(filename: transfer.title) .frame(width: 16, height: 16) + .opacity(controlActiveState == .inactive ? 0.5 : 1.0) // .padding(.leading, 2) Spacer() } @@ -631,22 +633,27 @@ struct TransferItemView: View { .buttonStyle(.plain) .padding(0) .frame(width: 16, height: 16) + .opacity(controlActiveState == .inactive ? 0.5 : 1.0) .help(transfer.completed || transfer.failed ? "Remove" : "Cancel Transfer") .onHover { hovered in self.buttonHovered = hovered } } else if transfer.failed { - Image(systemName: "exclamationmark.triangle") + Image(systemName: "exclamationmark.triangle.fill") .resizable() + .symbolRenderingMode(.multicolor) .aspectRatio(contentMode: .fit) .frame(width: 16, height: 16) + .opacity(controlActiveState == .inactive ? 0.5 : 1.0) } else if transfer.completed { Image(systemName: "checkmark.circle.fill") .resizable() + .foregroundStyle(Color.fileComplete) .aspectRatio(contentMode: .fit) .frame(width: 16, height: 16) + .opacity(controlActiveState == .inactive ? 0.5 : 1.0) } else if transfer.progress == 0.0 { ProgressView() @@ -660,7 +667,9 @@ struct TransferItemView: View { } } .onHover { hovered in - self.hovered = hovered + withAnimation(.easeOut(duration: 0.25)) { + self.hovered = hovered + } } .help(formattedProgressHelp()) } |