aboutsummaryrefslogtreecommitdiff
path: root/Hotline
diff options
context:
space:
mode:
authorDustin Mierau <dustin@mierau.me>2024-07-24 16:11:16 -0700
committerDustin Mierau <dustin@mierau.me>2024-07-24 16:11:16 -0700
commitf9e5f8a3a66df6e59726ca1659043fb54ed894e4 (patch)
tree4d5a370178cd79c6f9ae5a2bcca1e442327b4193 /Hotline
parent8633db7e6ca9f38345d7be0ab90193d6325904bf (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.json38
-rw-r--r--Hotline/Hotline/HotlineDataBuilder.swift91
-rw-r--r--Hotline/Hotline/HotlineTransferClient.swift137
-rw-r--r--Hotline/macOS/ServerView.swift13
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())
}