]> git.r.bdr.sh - rbdr/mobius/blob - hotline/flattened_file_object.go
Merge pull request #11 from jhalter/remove_placeholder_todo_file_comment
[rbdr/mobius] / hotline / flattened_file_object.go
1 package hotline
2
3 import (
4 "encoding/binary"
5 "os"
6 )
7
8 type flattenedFileObject struct {
9 FlatFileHeader FlatFileHeader
10 FlatFileInformationForkHeader FlatFileInformationForkHeader
11 FlatFileInformationFork FlatFileInformationFork
12 FlatFileDataForkHeader FlatFileDataForkHeader
13 FileData []byte
14 }
15
16 // FlatFileHeader is the first section of a "Flattened File Object". All fields have static values.
17 type FlatFileHeader struct {
18 Format [4]byte // Always "FILP"
19 Version [2]byte // Always 1
20 RSVD [16]byte // Always empty zeros
21 ForkCount [2]byte // Always 2
22 }
23
24 // NewFlatFileHeader returns a FlatFileHeader struct
25 func NewFlatFileHeader() FlatFileHeader {
26 return FlatFileHeader{
27 Format: [4]byte{0x46, 0x49, 0x4c, 0x50}, // FILP
28 Version: [2]byte{0, 1},
29 RSVD: [16]byte{},
30 ForkCount: [2]byte{0, 2},
31 }
32 }
33
34 // FlatFileInformationForkHeader is the second section of a "Flattened File Object"
35 type FlatFileInformationForkHeader struct {
36 ForkType []byte // Always "INFO"
37 CompressionType []byte // Always 0; Compression was never implemented in the Hotline protocol
38 RSVD []byte // Always zeros
39 DataSize []byte // Size of the flat file information fork
40 }
41
42 type FlatFileInformationFork struct {
43 Platform []byte // Operating System used. ("AMAC" or "MWIN")
44 TypeSignature []byte // File type signature
45 CreatorSignature []byte // File creator signature
46 Flags []byte
47 PlatformFlags []byte
48 RSVD []byte
49 CreateDate []byte
50 ModifyDate []byte
51 NameScript []byte // TODO: what is this?
52 NameSize []byte // Length of file name (Maximum 128 characters)
53 Name []byte // File name
54 CommentSize []byte // Length of file comment
55 Comment []byte // File comment
56 }
57
58 func NewFlatFileInformationFork(fileName string, modifyTime []byte) FlatFileInformationFork {
59 return FlatFileInformationFork{
60 Platform: []byte("AMAC"), // TODO: Remove hardcode to support "AWIN" Platform (maybe?)
61 TypeSignature: []byte(fileTypeFromFilename(fileName).TypeCode), // TODO: Don't infer types from filename
62 CreatorSignature: []byte(fileTypeFromFilename(fileName).CreatorCode), // TODO: Don't infer types from filename
63 Flags: []byte{0, 0, 0, 0}, // TODO: What is this?
64 PlatformFlags: []byte{0, 0, 1, 0}, // TODO: What is this?
65 RSVD: make([]byte, 32), // Unimplemented in Hotline Protocol
66 CreateDate: modifyTime, // some filesystems don't support createTime
67 ModifyDate: modifyTime,
68 NameScript: make([]byte, 2), // TODO: What is this?
69 Name: []byte(fileName),
70 CommentSize: []byte{0, 0},
71 Comment: []byte{}, // TODO: implement (maybe?)
72 }
73 }
74
75 // DataSize calculates the size of the flat file information fork, which is
76 // 72 bytes for the fixed length fields plus the length of the Name + Comment
77 func (ffif FlatFileInformationFork) DataSize() []byte {
78 size := make([]byte, 4)
79
80 //TODO: Can I do math directly on two byte slices?
81 dataSize := len(ffif.Name) + len(ffif.Comment) + 74
82
83 binary.BigEndian.PutUint32(size, uint32(dataSize))
84
85 return size
86 }
87
88 func (ffo flattenedFileObject) TransferSize() []byte {
89 payloadSize := len(ffo.BinaryMarshal())
90 dataSize := binary.BigEndian.Uint32(ffo.FlatFileDataForkHeader.DataSize)
91
92 transferSize := make([]byte, 4)
93 binary.BigEndian.PutUint32(transferSize, dataSize+uint32(payloadSize))
94
95 return transferSize
96 }
97
98 func (ffif FlatFileInformationFork) ReadNameSize() []byte {
99 size := make([]byte, 2)
100 binary.BigEndian.PutUint16(size, uint16(len(ffif.Name)))
101
102 return size
103 }
104
105 type FlatFileDataForkHeader struct {
106 ForkType []byte
107 CompressionType []byte
108 RSVD []byte
109 DataSize []byte
110 }
111
112 // ReadFlattenedFileObject parses a byte slice into a flattenedFileObject
113 func ReadFlattenedFileObject(bytes []byte) flattenedFileObject {
114 nameSize := bytes[110:112]
115 bs := binary.BigEndian.Uint16(nameSize)
116
117 nameEnd := 112 + bs
118
119 commentSize := bytes[nameEnd : nameEnd+2]
120 commentLen := binary.BigEndian.Uint16(commentSize)
121
122 commentStartPos := int(nameEnd) + 2
123 commentEndPos := int(nameEnd) + 2 + int(commentLen)
124
125 comment := bytes[commentStartPos:commentEndPos]
126
127 //dataSizeField := bytes[nameEnd+14+commentLen : nameEnd+18+commentLen]
128 //dataSize := binary.BigEndian.Uint32(dataSizeField)
129
130 ffo := flattenedFileObject{
131 FlatFileHeader: NewFlatFileHeader(),
132 FlatFileInformationForkHeader: FlatFileInformationForkHeader{
133 ForkType: bytes[24:28],
134 CompressionType: bytes[28:32],
135 RSVD: bytes[32:36],
136 DataSize: bytes[36:40],
137 },
138 FlatFileInformationFork: FlatFileInformationFork{
139 Platform: bytes[40:44],
140 TypeSignature: bytes[44:48],
141 CreatorSignature: bytes[48:52],
142 Flags: bytes[52:56],
143 PlatformFlags: bytes[56:60],
144 RSVD: bytes[60:92],
145 CreateDate: bytes[92:100],
146 ModifyDate: bytes[100:108],
147 NameScript: bytes[108:110],
148 NameSize: bytes[110:112],
149 Name: bytes[112:nameEnd],
150 CommentSize: bytes[nameEnd : nameEnd+2],
151 Comment: comment,
152 },
153 FlatFileDataForkHeader: FlatFileDataForkHeader{
154 ForkType: bytes[commentEndPos : commentEndPos+4],
155 CompressionType: bytes[commentEndPos+4 : commentEndPos+8],
156 RSVD: bytes[commentEndPos+8 : commentEndPos+12],
157 DataSize: bytes[commentEndPos+12 : commentEndPos+16],
158 },
159 }
160
161 return ffo
162 }
163
164 func (f flattenedFileObject) BinaryMarshal() []byte {
165 var out []byte
166 out = append(out, f.FlatFileHeader.Format[:]...)
167 out = append(out, f.FlatFileHeader.Version[:]...)
168 out = append(out, f.FlatFileHeader.RSVD[:]...)
169 out = append(out, f.FlatFileHeader.ForkCount[:]...)
170
171 out = append(out, []byte("INFO")...)
172 out = append(out, []byte{0, 0, 0, 0}...)
173 out = append(out, make([]byte, 4)...)
174 out = append(out, f.FlatFileInformationFork.DataSize()...)
175
176 out = append(out, f.FlatFileInformationFork.Platform...)
177 out = append(out, f.FlatFileInformationFork.TypeSignature...)
178 out = append(out, f.FlatFileInformationFork.CreatorSignature...)
179 out = append(out, f.FlatFileInformationFork.Flags...)
180 out = append(out, f.FlatFileInformationFork.PlatformFlags...)
181 out = append(out, f.FlatFileInformationFork.RSVD...)
182 out = append(out, f.FlatFileInformationFork.CreateDate...)
183 out = append(out, f.FlatFileInformationFork.ModifyDate...)
184 out = append(out, f.FlatFileInformationFork.NameScript...)
185 out = append(out, f.FlatFileInformationFork.ReadNameSize()...)
186 out = append(out, f.FlatFileInformationFork.Name...)
187 out = append(out, f.FlatFileInformationFork.CommentSize...)
188 out = append(out, f.FlatFileInformationFork.Comment...)
189
190 out = append(out, f.FlatFileDataForkHeader.ForkType...)
191 out = append(out, f.FlatFileDataForkHeader.CompressionType...)
192 out = append(out, f.FlatFileDataForkHeader.RSVD...)
193 out = append(out, f.FlatFileDataForkHeader.DataSize...)
194
195 return out
196 }
197
198 func NewFlattenedFileObject(fileRoot string, filePath, fileName []byte) (*flattenedFileObject, error) {
199 fullFilePath, err := readPath(fileRoot, filePath, fileName)
200 if err != nil {
201 return nil, err
202 }
203 file, err := os.Open(fullFilePath)
204 if err != nil {
205 return nil, err
206 }
207 defer func(file *os.File) { _ = file.Close() }(file)
208
209 fileInfo, err := file.Stat()
210 if err != nil {
211 return nil, err
212 }
213
214 dataSize := make([]byte, 4)
215 binary.BigEndian.PutUint32(dataSize, uint32(fileInfo.Size()))
216
217 mTime := toHotlineTime(fileInfo.ModTime())
218
219 return &flattenedFileObject{
220 FlatFileHeader: NewFlatFileHeader(),
221 FlatFileInformationFork: NewFlatFileInformationFork(string(fileName), mTime),
222 FlatFileDataForkHeader: FlatFileDataForkHeader{
223 ForkType: []byte("DATA"),
224 CompressionType: []byte{0, 0, 0, 0},
225 RSVD: []byte{0, 0, 0, 0},
226 DataSize: dataSize,
227 },
228 }, nil
229 }