From: Jeff Halter Date: Thu, 26 May 2022 00:44:55 +0000 (-0700) Subject: Add partial support for file create/modify timestamps X-Git-Url: https://git.r.bdr.sh/rbdr/mobius/commitdiff_plain/29f329aedcdcf4e07a6dc3d9161439dd35ecfe11 Add partial support for file create/modify timestamps This replaces hardcoded placeholder create/modify timestamps with the real times, mostly. Create time is OS specific, so for now I'm ignoring it at using the modify time as a stand-in. --- diff --git a/hotline/flattened_file_object.go b/hotline/flattened_file_object.go index bd281e0..0a14df0 100644 --- a/hotline/flattened_file_object.go +++ b/hotline/flattened_file_object.go @@ -55,17 +55,17 @@ type FlatFileInformationFork struct { Comment []byte // File comment } -func NewFlatFileInformationFork(fileName string) FlatFileInformationFork { +func NewFlatFileInformationFork(fileName string, modifyTime []byte) FlatFileInformationFork { return FlatFileInformationFork{ - Platform: []byte("AMAC"), // TODO: Remove hardcode to support "AWIN" Platform (maybe?) - TypeSignature: []byte(fileTypeFromFilename(fileName).TypeCode), // TODO: Don't infer types from filename - CreatorSignature: []byte(fileTypeFromFilename(fileName).CreatorCode), // TODO: Don't infer types from filename - Flags: []byte{0, 0, 0, 0}, // TODO: What is this? - PlatformFlags: []byte{0, 0, 1, 0}, // TODO: What is this? - RSVD: make([]byte, 32), // Unimplemented in Hotline Protocol - CreateDate: []byte{0x07, 0x70, 0x00, 0x00, 0xba, 0x74, 0x24, 0x73}, // TODO: implement - ModifyDate: []byte{0x07, 0x70, 0x00, 0x00, 0xba, 0x74, 0x24, 0x73}, // TODO: implement - NameScript: make([]byte, 2), // TODO: What is this? + Platform: []byte("AMAC"), // TODO: Remove hardcode to support "AWIN" Platform (maybe?) + TypeSignature: []byte(fileTypeFromFilename(fileName).TypeCode), // TODO: Don't infer types from filename + CreatorSignature: []byte(fileTypeFromFilename(fileName).CreatorCode), // TODO: Don't infer types from filename + Flags: []byte{0, 0, 0, 0}, // TODO: What is this? + PlatformFlags: []byte{0, 0, 1, 0}, // TODO: What is this? + RSVD: make([]byte, 32), // Unimplemented in Hotline Protocol + CreateDate: modifyTime, // some filesystems don't support createTime + ModifyDate: modifyTime, + NameScript: make([]byte, 2), // TODO: What is this? Name: []byte(fileName), CommentSize: []byte{0, 4}, Comment: []byte("TODO"), // TODO: implement (maybe?) @@ -204,7 +204,7 @@ func NewFlattenedFileObject(fileRoot string, filePath, fileName []byte) (*flatte if err != nil { return nil, err } - defer file.Close() + defer func(file *os.File) { _ = file.Close() }(file) fileInfo, err := file.Stat() if err != nil { @@ -214,9 +214,11 @@ func NewFlattenedFileObject(fileRoot string, filePath, fileName []byte) (*flatte dataSize := make([]byte, 4) binary.BigEndian.PutUint32(dataSize, uint32(fileInfo.Size())) + mTime := toHotlineTime(fileInfo.ModTime()) + return &flattenedFileObject{ FlatFileHeader: NewFlatFileHeader(), - FlatFileInformationFork: NewFlatFileInformationFork(string(fileName)), + FlatFileInformationFork: NewFlatFileInformationFork(string(fileName), mTime), FlatFileDataForkHeader: FlatFileDataForkHeader{ ForkType: []byte("DATA"), CompressionType: []byte{0, 0, 0, 0}, diff --git a/hotline/flattened_file_object_test.go b/hotline/flattened_file_object_test.go index 5bbaf1d..12b6d32 100644 --- a/hotline/flattened_file_object_test.go +++ b/hotline/flattened_file_object_test.go @@ -43,7 +43,7 @@ func TestNewFlattenedFileObject(t *testing.T) { want: &flattenedFileObject{ FlatFileHeader: NewFlatFileHeader(), FlatFileInformationForkHeader: FlatFileInformationForkHeader{}, - FlatFileInformationFork: NewFlatFileInformationFork("testfile.txt"), + FlatFileInformationFork: NewFlatFileInformationFork("testfile.txt", make([]byte, 8)), FlatFileDataForkHeader: FlatFileDataForkHeader{ ForkType: []byte("DATA"), CompressionType: []byte{0, 0, 0, 0}, @@ -67,9 +67,14 @@ func TestNewFlattenedFileObject(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, err := NewFlattenedFileObject(tt.args.fileRoot, tt.args.filePath, tt.args.fileName) - if !tt.wantErr(t, err, fmt.Sprintf("NewFlattenedFileObject(%v, %v, %v)", tt.args.fileRoot, tt.args.filePath, tt.args.fileName)) { + if tt.wantErr(t, err, fmt.Sprintf("NewFlattenedFileObject(%v, %v, %v)", tt.args.fileRoot, tt.args.filePath, tt.args.fileName)) { return } + + // Clear the file timestamp fields to work around problems running the tests in multiple timezones + // TODO: revisit how to test this by mocking the stat calls + got.FlatFileInformationFork.CreateDate = make([]byte, 8) + got.FlatFileInformationFork.ModifyDate = make([]byte, 8) assert.Equalf(t, tt.want, got, "NewFlattenedFileObject(%v, %v, %v)", tt.args.fileRoot, tt.args.filePath, tt.args.fileName) }) } diff --git a/hotline/time.go b/hotline/time.go new file mode 100644 index 0000000..3b87864 --- /dev/null +++ b/hotline/time.go @@ -0,0 +1,25 @@ +package hotline + +import ( + "encoding/binary" + "time" +) + +// toHotlineTime converts a time.Time to the 8 byte Hotline time format: +// Year (2 bytes), milliseconds (2 bytes) and seconds (4 bytes) +func toHotlineTime(t time.Time) (b []byte) { + yearBytes := make([]byte, 2) + secondBytes := make([]byte, 4) + + // Get a time.Time for January 1st 00:00 from t so we can calculate the difference in seconds from t + startOfYear := time.Date(t.Year(), time.January, 1, 0, 0, 0, 0, time.Local) + + binary.BigEndian.PutUint16(yearBytes, uint16(t.Year())) + binary.BigEndian.PutUint32(secondBytes, uint32(t.Sub(startOfYear).Seconds())) + + b = append(b, yearBytes...) + b = append(b, []byte{0, 0}...) + b = append(b, secondBytes...) + + return b +} diff --git a/hotline/transaction_handlers_test.go b/hotline/transaction_handlers_test.go index f8d3b2e..b734049 100644 --- a/hotline/transaction_handlers_test.go +++ b/hotline/transaction_handlers_test.go @@ -469,7 +469,10 @@ func TestHandleGetFileInfo(t *testing.T) { ID: &[]byte{0x00, 0x01}, Server: &Server{ Config: &Config{ - FileRoot: func() string { path, _ := os.Getwd(); return path + "/test/config/Files" }(), + FileRoot: func() string { + path, _ := os.Getwd() + return path + "/test/config/Files" + }(), }, }, }, @@ -493,8 +496,8 @@ func TestHandleGetFileInfo(t *testing.T) { NewField(fieldFileCreatorString, []byte("ttxt")), NewField(fieldFileComment, []byte("TODO")), NewField(fieldFileType, []byte("TEXT")), - NewField(fieldFileCreateDate, []byte{0x07, 0x70, 0x00, 0x00, 0xba, 0x74, 0x24, 0x73}), - NewField(fieldFileModifyDate, []byte{0x07, 0x70, 0x00, 0x00, 0xba, 0x74, 0x24, 0x73}), + NewField(fieldFileCreateDate, make([]byte, 8)), + NewField(fieldFileModifyDate, make([]byte, 8)), NewField(fieldFileSize, []byte{0x0, 0x0, 0x0, 0x17}), }, }, @@ -511,6 +514,11 @@ func TestHandleGetFileInfo(t *testing.T) { t.Errorf("HandleGetFileInfo() error = %v, wantErr %v", err, tt.wantErr) return } + + // Clear the file timestamp fields to work around problems running the tests in multiple timezones + // TODO: revisit how to test this by mocking the stat calls + gotRes[0].Fields[5].Data = make([]byte, 8) + gotRes[0].Fields[6].Data = make([]byte, 8) if !assert.Equal(t, tt.wantRes, gotRes) { t.Errorf("HandleGetFileInfo() gotRes = %v, want %v", gotRes, tt.wantRes) }