aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDustin Mierau <mierau@users.noreply.github.com>2024-05-01 19:04:19 -0700
committerGitHub <noreply@github.com>2024-05-01 19:04:19 -0700
commita5c1e0b475d14b176479f662bf29163f2220180b (patch)
tree2a458fb29ade4e1c21fa662121bfa88a45c797de
parent44dcb81e80e167374e2d3217b81cf0148fa7d66a (diff)
parentc55cae05aeeb629fc9d2939c5f5e935ebc3ac92b (diff)
Merge pull request #14 from jhalter/get-set-file-info
Add Get/Set file info
-rw-r--r--Hotline.xcodeproj/project.pbxproj8
-rw-r--r--Hotline/Hotline/HotlineClient.swift46
-rw-r--r--Hotline/Hotline/HotlineProtocol.swift8
-rw-r--r--Hotline/Models/FileDetails.swift13
-rw-r--r--Hotline/Models/Hotline.swift11
-rw-r--r--Hotline/macOS/FileDetailsView.swift147
-rw-r--r--Hotline/macOS/FilesView.swift11
7 files changed, 242 insertions, 2 deletions
diff --git a/Hotline.xcodeproj/project.pbxproj b/Hotline.xcodeproj/project.pbxproj
index a954b35..38c6cad 100644
--- a/Hotline.xcodeproj/project.pbxproj
+++ b/Hotline.xcodeproj/project.pbxproj
@@ -7,6 +7,8 @@
objects = {
/* Begin PBXBuildFile section */
+ 11A726082BE0672A000C1DA7 /* FileDetails.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11A726072BE0672A000C1DA7 /* FileDetails.swift */; };
+ 11A7260A2BE0675A000C1DA7 /* FileDetailsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11A726092BE06759000C1DA7 /* FileDetailsView.swift */; };
DA0D698D2B1E7CF700C71DF5 /* UsersView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA0D698C2B1E7CF700C71DF5 /* UsersView.swift */; platformFilter = ios; };
DA0D698F2B1E841600C71DF5 /* MessageBoardView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA0D698E2B1E841600C71DF5 /* MessageBoardView.swift */; platformFilter = ios; };
DA0D69912B1E894800C71DF5 /* FilesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA0D69902B1E894800C71DF5 /* FilesView.swift */; platformFilter = ios; };
@@ -70,6 +72,8 @@
/* End PBXBuildFile section */
/* Begin PBXFileReference section */
+ 11A726072BE0672A000C1DA7 /* FileDetails.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FileDetails.swift; sourceTree = "<group>"; };
+ 11A726092BE06759000C1DA7 /* FileDetailsView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FileDetailsView.swift; sourceTree = "<group>"; };
DA0D698C2B1E7CF700C71DF5 /* UsersView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UsersView.swift; sourceTree = "<group>"; };
DA0D698E2B1E841600C71DF5 /* MessageBoardView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageBoardView.swift; sourceTree = "<group>"; };
DA0D69902B1E894800C71DF5 /* FilesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FilesView.swift; sourceTree = "<group>"; };
@@ -258,6 +262,7 @@
DADDB2892B22B2C60024040D /* Models */ = {
isa = PBXGroup;
children = (
+ 11A726072BE0672A000C1DA7 /* FileDetails.swift */,
DADDB28A2B22B31F0024040D /* Tracker.swift */,
DABE8C052B57A06100884D28 /* Bookmarks.swift */,
DA2863D92B37BF6E00A7D050 /* Preferences.swift */,
@@ -278,6 +283,7 @@
DAE734F72B2E4126000C56F6 /* macOS */ = {
isa = PBXGroup;
children = (
+ 11A726092BE06759000C1DA7 /* FileDetailsView.swift */,
DAE734FA2B2E41F9000C56F6 /* TrackerView.swift */,
DAE734F82B2E4185000C56F6 /* ServerView.swift */,
DAE734FE2B2E6750000C56F6 /* ChatView.swift */,
@@ -396,6 +402,7 @@
DABE8C042B57940B00884D28 /* DAKeychain.swift in Sources */,
DA72A0E22B4DAA4000A0F48A /* NewsArticle.swift in Sources */,
DAE734FB2B2E41F9000C56F6 /* TrackerView.swift in Sources */,
+ 11A726082BE0672A000C1DA7 /* FileDetails.swift in Sources */,
DA9CAFBD2B126D5700CDA197 /* TrackerView.swift in Sources */,
DAB4D87B2B4B78310048A05C /* AnimatedImageView.swift in Sources */,
DAE735032B30C0BB000C56F6 /* MessageView.swift in Sources */,
@@ -409,6 +416,7 @@
DA0D698F2B1E841600C71DF5 /* MessageBoardView.swift in Sources */,
DAE735072B3251B3000C56F6 /* SoundEffects.swift in Sources */,
DAE735012B2E71F2000C56F6 /* FilesView.swift in Sources */,
+ 11A7260A2BE0675A000C1DA7 /* FileDetailsView.swift in Sources */,
DA72A0DD2B4CD0BF00A0F48A /* NewsEditorView.swift in Sources */,
DABFCC292B1530DC009F40D2 /* FoundationExtensions.swift in Sources */,
DA57536C2B36BA1D00FAC277 /* TextDocument.swift in Sources */,
diff --git a/Hotline/Hotline/HotlineClient.swift b/Hotline/Hotline/HotlineClient.swift
index c511555..5ed71a3 100644
--- a/Hotline/Hotline/HotlineClient.swift
+++ b/Hotline/Hotline/HotlineClient.swift
@@ -616,6 +616,52 @@ class HotlineClient: NetSocketDelegate {
}
}
+ @MainActor func sendGetFileInfo(name fileName: String, path filePath: [String], callback: ((FileDetails?) -> Void)? = nil) {
+ var t = HotlineTransaction(type: .getFileInfo)
+ t.setFieldString(type: .fileName, val: fileName)
+ t.setFieldPath(type: .filePath, val: filePath)
+
+ self.sendPacket(t) { reply, err in
+ guard err == nil,
+ let fileName = reply.getField(type: .fileName)?.getString(),
+ let fileCreator = reply.getField(type: .fileCreatorString)?.getString(),
+ let fileType = reply.getField(type: .fileTypeString)?.getString(),
+ let fileTypeString = reply.getField(type: .fileTypeString)?.getString(),
+ let fileCreateDate = reply.getField(type: .fileCreateDate)?.data.readDate(at: 0),
+ let fileModifyDate = reply.getField(type: .fileModifyDate)?.data.readDate(at: 0)
+ else {
+ callback?(nil)
+ return
+ }
+
+
+ // Size field is not included in server reply for folders
+ let fileSize = reply.getField(type: .fileSize)?.getInteger() ?? 0
+
+ // Comment field is not included for if no comment present
+ let fileComment = reply.getField(type: .fileComment)?.getString() ?? ""
+
+ callback?(FileDetails(name: fileName, path: filePath, size: fileSize, comment: fileComment, type: fileType, creator: fileCreator,
+ created: fileCreateDate, modified: fileModifyDate))
+ }
+ }
+
+ @MainActor func sendSetFileInfo(fileName: String, path filePath: [String], fileNewName: String?, comment: String?, encoding: String.Encoding = .utf8) {
+ var t = HotlineTransaction(type: .setFileInfo)
+ t.setFieldString(type: .fileName, val: fileName, encoding: encoding)
+ t.setFieldPath(type: .filePath, val: filePath)
+
+ if fileNewName != nil {
+ t.setFieldString(type: .fileNewName, val: fileNewName!, encoding: encoding)
+ }
+
+ if comment != nil {
+ t.setFieldString(type: .fileComment, val: comment!, encoding: encoding)
+ }
+
+ self.sendPacket(t)
+ }
+
@MainActor func sendDownloadFile(name fileName: String, path filePath: [String], preview: Bool = false, callback: ((Bool, UInt32?, Int?, Int?, Int?) -> Void)? = nil) {
var t = HotlineTransaction(type: .downloadFile)
t.setFieldString(type: .fileName, val: fileName)
diff --git a/Hotline/Hotline/HotlineProtocol.swift b/Hotline/Hotline/HotlineProtocol.swift
index f98e07b..1f9ff3e 100644
--- a/Hotline/Hotline/HotlineProtocol.swift
+++ b/Hotline/Hotline/HotlineProtocol.swift
@@ -752,8 +752,14 @@ enum HotlineTransactionFieldType: UInt16 {
case fileName = 201 // String
case filePath = 202 // Path
case fileTransferOptions = 204 // Integer
+ case fileTypeString = 205 // String
+ case fileCreatorString = 206 // String
case fileSize = 207 // Integer
- // TODO: Add file field types
+ case fileCreateDate = 208
+ case fileModifyDate = 209
+ case fileComment = 210 // Integer
+ case fileNewName = 211 // String
+ case fileType = 213 // Integer
case quotingMessage = 214 // String?
case automaticResponse = 215 // String
case folderItemCount = 220 // Integer
diff --git a/Hotline/Models/FileDetails.swift b/Hotline/Models/FileDetails.swift
new file mode 100644
index 0000000..20b1ee2
--- /dev/null
+++ b/Hotline/Models/FileDetails.swift
@@ -0,0 +1,13 @@
+import UniformTypeIdentifiers
+
+struct FileDetails:Identifiable {
+ let id = UUID()
+ var name: String
+ var path: [String]
+ var size: Int
+ var comment: String
+ var type: String
+ var creator: String
+ var created: Date
+ var modified: Date
+}
diff --git a/Hotline/Models/Hotline.swift b/Hotline/Models/Hotline.swift
index baf87eb..cccb4d4 100644
--- a/Hotline/Models/Hotline.swift
+++ b/Hotline/Models/Hotline.swift
@@ -440,6 +440,17 @@ final class Hotline: HotlineClientDelegate, HotlineFileClientDelegate {
}
}
}
+
+ @MainActor func fileDetails(_ fileName: String, path: [String], complete callback: ((FileDetails?) -> Void)? = nil) {
+ var fullPath: [String] = []
+ if path.count > 1 {
+ fullPath = Array(path[0..<path.count-1])
+ }
+
+ self.client.sendGetFileInfo(name: fileName, path: fullPath) { info in
+ callback?(info)
+ }
+ }
@MainActor func previewFile(_ fileName: String, path: [String], complete callback: ((PreviewFileInfo?) -> Void)? = nil) {
var fullPath: [String] = []
diff --git a/Hotline/macOS/FileDetailsView.swift b/Hotline/macOS/FileDetailsView.swift
new file mode 100644
index 0000000..d3c10e8
--- /dev/null
+++ b/Hotline/macOS/FileDetailsView.swift
@@ -0,0 +1,147 @@
+import Foundation
+import SwiftUI
+
+struct FileDetailsView: View {
+ @Environment(Hotline.self) private var model: Hotline
+ @Environment(\.presentationMode) var presentationMode
+
+ var fd: FileDetails
+
+ @State private var comment: String = ""
+ @State private var filename: String = ""
+
+ var body: some View {
+ VStack (alignment: .leading){
+ Form {
+ HStack(alignment: .center){
+ FileIconView(filename: fd.name)
+ .frame(width: 16, height: 16)
+ TextField("", text: $filename)
+ .disabled(!self.canRename())
+ }
+ HStack(alignment: .center){
+ Text("Type:").bold().padding(.leading, 43)
+ Text(fd.type)
+ }
+ HStack(alignment: .center){
+ Text("Creator:").bold().padding(.leading, 26)
+ Text(fd.creator)
+ }
+ HStack(alignment: .center){
+ Text("Size:").bold().bold().padding(.leading, 48)
+ Text(self.formattedSize(byteCount: fd.size))
+ }
+ HStack(alignment: .center){
+ Text("Created:").bold().padding(.leading, 24)
+ Text("\(FileDetailsView.dateFormatter.string(from: fd.created))")
+ }
+ HStack(alignment: .center){
+ Text("Modified:").bold().padding(.leading, 19)
+ Text("\(FileDetailsView.dateFormatter.string(from: fd.modified))")
+ }
+ HStack(alignment: .center){
+ Text("Comments:").bold().padding(.top, 8)
+ .padding(.leading, 5)
+ }
+
+ VStack(alignment: .trailing){
+ TextEditor(text: $comment)
+ .padding(.leading, 2)
+ .padding(.top, 1)
+ .font(.system(size: 13))
+ .background(Color(nsColor: .textBackgroundColor))
+ .border(Color.secondary, width: 1)
+ .clipShape(RoundedRectangle(cornerRadius: 2, style: .continuous))
+ .disabled(!self.canSetComment())
+ }
+ }
+ .toolbar {
+ ToolbarItem(placement: .cancellationAction) {
+ Button("Cancel") {
+ presentationMode.wrappedValue.dismiss()
+ }
+ }
+
+ ToolbarItem(placement: .primaryAction) {
+ Button{
+ var editedFilename: String?
+ if filename != fd.name {
+ editedFilename = filename
+ }
+
+ var editedComment: String?
+ if comment != fd.comment {
+ editedComment = comment
+ }
+
+ model.client.sendSetFileInfo(fileName: fd.name, path: fd.path, fileNewName: editedFilename, comment: editedComment)
+ presentationMode.wrappedValue.dismiss()
+
+ // TODO: Update the file list if the filename was changed
+ } label: {
+ Text("Save")
+ }.disabled(!isEdited())
+ }
+ }
+
+ .onAppear {
+ self.filename = fd.name
+ self.comment = fd.comment
+ }
+ .padding(EdgeInsets(top: 16, leading: 24, bottom: 16, trailing: 24))
+ }
+ .frame(minWidth: 400, minHeight: 400)
+ }
+
+
+ static var dateFormatter: DateFormatter = {
+ var dateFormatter = DateFormatter()
+ dateFormatter.dateStyle = .long
+ dateFormatter.timeStyle = .short
+
+ // Original format: Fri, Aug 20, 2021, 5:14:07 PM
+ return dateFormatter
+ }()
+
+ static var byteCountSizeFormatter: NumberFormatter = {
+ let numberFormatter = NumberFormatter()
+ numberFormatter.numberStyle = .decimal
+ return numberFormatter
+ }()
+
+ static let byteFormatter = ByteCountFormatter()
+
+ private func formattedFileSize(_ fileSize: UInt) -> String {
+ FileView.byteFormatter.allowedUnits = [.useAll]
+ FileView.byteFormatter.countStyle = .file
+ return FileView.byteFormatter.string(fromByteCount: Int64(fileSize))
+ }
+
+ // Format byte count Int into string like: 23.4M (24,601,664 bytes)
+ private func formattedSize(byteCount: Int) -> String {
+ let formattedByteCount = FileDetailsView.byteCountSizeFormatter.string(from: NSNumber(value:byteCount)) ?? "0"
+ return "\(FileView.byteFormatter.string(fromByteCount: Int64(byteCount))) (\(formattedByteCount) bytes)"
+ }
+
+ private func isEdited() -> Bool {
+ return self.filename != fd.name || self.comment != fd.comment
+ }
+
+ private func canRename() -> Bool {
+ if self.fd.type == "fldr" {
+ return model.access?.contains(.canRenameFolders) == true
+ }
+ return model.access?.contains(.canRenameFiles) == true
+ }
+
+ private func canSetComment() -> Bool {
+ if self.fd.type == "fldr" {
+ return model.access?.contains(.canSetFolderComment) == true
+ }
+ return model.access?.contains(.canSetFileComment) == true
+ }
+}
+
+//#Preview {
+// FileDetailsView(fd: FileDetails(name: "AppleWorks 6.sit", path: [""], size: 24601664, comment: "test comment", type: "SITD", creator: "SIT!", created: Date.now, modified: Date.now ))
+//}
diff --git a/Hotline/macOS/FilesView.swift b/Hotline/macOS/FilesView.swift
index 397feb3..f698b5d 100644
--- a/Hotline/macOS/FilesView.swift
+++ b/Hotline/macOS/FilesView.swift
@@ -102,7 +102,8 @@ struct FilesView: View {
@Environment(\.openWindow) private var openWindow
@State private var selection: FileInfo?
-
+ @State private var fileDetails: FileDetails?
+
private func openPreviewWindow(_ previewInfo: PreviewFileInfo) {
switch previewInfo.previewType {
case .image:
@@ -206,6 +207,11 @@ struct FilesView: View {
ToolbarItem(placement: .primaryAction) {
Button {
+ if let s = selection {
+ model.fileDetails(s.name, path: s.path) { info in
+ fileDetails = info
+ }
+ }
} label: {
Label("Get File Info", systemImage: "info.circle")
}
@@ -224,6 +230,9 @@ struct FilesView: View {
}
}
}
+ .sheet(item: $fileDetails ) { item in
+ FileDetailsView(fd: item)
+ }
}
}