]> git.r.bdr.sh - rbdr/mobius/blame_incremental - hotline/flattened_file_object.go
Convert more bespoke methods to io.Reader/io.Writer interfaces
[rbdr/mobius] / hotline / flattened_file_object.go
... / ...
CommitLineData
1package hotline
2
3import (
4 "bytes"
5 "encoding/binary"
6 "io"
7 "slices"
8)
9
10type flattenedFileObject struct {
11 FlatFileHeader FlatFileHeader
12 FlatFileInformationForkHeader FlatFileForkHeader
13 FlatFileInformationFork FlatFileInformationFork
14 FlatFileDataForkHeader FlatFileForkHeader
15 FlatFileResForkHeader FlatFileForkHeader
16}
17
18// FlatFileHeader is the first section of a "Flattened File Object". All fields have static values.
19type FlatFileHeader struct {
20 Format [4]byte // Always "FILP"
21 Version [2]byte // Always 1
22 RSVD [16]byte // Always empty zeros
23 ForkCount [2]byte // Number of forks, either 2 or 3 if there is a resource fork
24}
25
26type FlatFileInformationFork struct {
27 Platform []byte // Operating System used. ("AMAC" or "MWIN")
28 TypeSignature []byte // File type signature
29 CreatorSignature []byte // File creator signature
30 Flags []byte
31 PlatformFlags []byte
32 RSVD []byte
33 CreateDate []byte
34 ModifyDate []byte
35 NameScript []byte
36 NameSize []byte // Length of file name (Maximum 128 characters)
37 Name []byte // File name
38 CommentSize []byte // Length of the comment
39 Comment []byte // File comment
40}
41
42func NewFlatFileInformationFork(fileName string, modifyTime []byte, typeSignature string, creatorSignature string) FlatFileInformationFork {
43 return FlatFileInformationFork{
44 Platform: []byte("AMAC"), // TODO: Remove hardcode to support "AWIN" Platform (maybe?)
45 TypeSignature: []byte(typeSignature), // TODO: Don't infer types from filename
46 CreatorSignature: []byte(creatorSignature), // TODO: Don't infer types from filename
47 Flags: []byte{0, 0, 0, 0}, // TODO: What is this?
48 PlatformFlags: []byte{0, 0, 1, 0}, // TODO: What is this?
49 RSVD: make([]byte, 32), // Unimplemented in Hotline Protocol
50 CreateDate: modifyTime, // some filesystems don't support createTime
51 ModifyDate: modifyTime,
52 NameScript: make([]byte, 2), // TODO: What is this?
53 Name: []byte(fileName),
54 CommentSize: []byte{0, 0},
55 Comment: []byte{}, // TODO: implement (maybe?)
56 }
57}
58
59func (ffif *FlatFileInformationFork) friendlyType() []byte {
60 if name, ok := friendlyCreatorNames[string(ffif.TypeSignature)]; ok {
61 return []byte(name)
62 }
63 return ffif.TypeSignature
64}
65
66func (ffif *FlatFileInformationFork) friendlyCreator() []byte {
67 if name, ok := friendlyCreatorNames[string(ffif.CreatorSignature)]; ok {
68 return []byte(name)
69 }
70 return ffif.CreatorSignature
71}
72
73func (ffif *FlatFileInformationFork) setComment(comment []byte) error {
74 ffif.CommentSize = make([]byte, 2)
75 ffif.Comment = comment
76 binary.BigEndian.PutUint16(ffif.CommentSize, uint16(len(comment)))
77
78 // TODO: return err if comment is too long
79 return nil
80}
81
82// DataSize calculates the size of the flat file information fork, which is
83// 72 bytes for the fixed length fields plus the length of the Name + Comment
84func (ffif *FlatFileInformationFork) DataSize() []byte {
85 size := make([]byte, 4)
86
87 dataSize := len(ffif.Name) + len(ffif.Comment) + 74 // 74 = len of fixed size headers
88
89 binary.BigEndian.PutUint32(size, uint32(dataSize))
90
91 return size
92}
93
94func (ffif *FlatFileInformationFork) Size() [4]byte {
95 size := [4]byte{}
96
97 dataSize := len(ffif.Name) + len(ffif.Comment) + 74 // 74 = len of fixed size headers
98
99 binary.BigEndian.PutUint32(size[:], uint32(dataSize))
100
101 return size
102}
103
104func (ffo *flattenedFileObject) TransferSize(offset int64) []byte {
105 // get length of the flattenedFileObject, including the info fork
106 b, _ := io.ReadAll(ffo)
107 payloadSize := len(b)
108
109 // length of data fork
110 dataSize := binary.BigEndian.Uint32(ffo.FlatFileDataForkHeader.DataSize[:])
111
112 // length of resource fork
113 resForkSize := binary.BigEndian.Uint32(ffo.FlatFileResForkHeader.DataSize[:])
114
115 size := make([]byte, 4)
116 binary.BigEndian.PutUint32(size, dataSize+resForkSize+uint32(payloadSize)-uint32(offset))
117
118 return size
119}
120
121func (ffif *FlatFileInformationFork) ReadNameSize() []byte {
122 size := make([]byte, 2)
123 binary.BigEndian.PutUint16(size, uint16(len(ffif.Name)))
124
125 return size
126}
127
128type FlatFileForkHeader struct {
129 ForkType [4]byte // Either INFO, DATA or MACR
130 CompressionType [4]byte
131 RSVD [4]byte
132 DataSize [4]byte
133}
134
135func (ffif *FlatFileInformationFork) Read(p []byte) (int, error) {
136 return copy(p,
137 slices.Concat(
138 ffif.Platform,
139 ffif.TypeSignature,
140 ffif.CreatorSignature,
141 ffif.Flags,
142 ffif.PlatformFlags,
143 ffif.RSVD,
144 ffif.CreateDate,
145 ffif.ModifyDate,
146 ffif.NameScript,
147 ffif.ReadNameSize(),
148 ffif.Name,
149 ffif.CommentSize,
150 ffif.Comment,
151 ),
152 ), io.EOF
153}
154
155// Write implements the io.Writeer interface for FlatFileInformationFork
156func (ffif *FlatFileInformationFork) Write(p []byte) (int, error) {
157 nameSize := p[70:72]
158 bs := binary.BigEndian.Uint16(nameSize)
159 total := 72 + bs
160
161 ffif.Platform = p[0:4]
162 ffif.TypeSignature = p[4:8]
163 ffif.CreatorSignature = p[8:12]
164 ffif.Flags = p[12:16]
165 ffif.PlatformFlags = p[16:20]
166 ffif.RSVD = p[20:52]
167 ffif.CreateDate = p[52:60]
168 ffif.ModifyDate = p[60:68]
169 ffif.NameScript = p[68:70]
170 ffif.NameSize = p[70:72]
171 ffif.Name = p[72:total]
172
173 if len(p) > int(total) {
174 ffif.CommentSize = p[total : total+2]
175 commentLen := binary.BigEndian.Uint16(ffif.CommentSize)
176
177 commentStartPos := int(total) + 2
178 commentEndPos := int(total) + 2 + int(commentLen)
179
180 ffif.Comment = p[commentStartPos:commentEndPos]
181
182 total = uint16(commentEndPos)
183 }
184
185 return int(total), nil
186}
187
188func (ffif *FlatFileInformationFork) UnmarshalBinary(b []byte) error {
189 nameSize := b[70:72]
190 bs := binary.BigEndian.Uint16(nameSize)
191 nameEnd := 72 + bs
192
193 ffif.Platform = b[0:4]
194 ffif.TypeSignature = b[4:8]
195 ffif.CreatorSignature = b[8:12]
196 ffif.Flags = b[12:16]
197 ffif.PlatformFlags = b[16:20]
198 ffif.RSVD = b[20:52]
199 ffif.CreateDate = b[52:60]
200 ffif.ModifyDate = b[60:68]
201 ffif.NameScript = b[68:70]
202 ffif.NameSize = b[70:72]
203 ffif.Name = b[72:nameEnd]
204
205 if len(b) > int(nameEnd) {
206 ffif.CommentSize = b[nameEnd : nameEnd+2]
207 commentLen := binary.BigEndian.Uint16(ffif.CommentSize)
208
209 commentStartPos := int(nameEnd) + 2
210 commentEndPos := int(nameEnd) + 2 + int(commentLen)
211
212 ffif.Comment = b[commentStartPos:commentEndPos]
213 }
214
215 return nil
216}
217
218// Read implements the io.Reader interface for flattenedFileObject
219func (ffo *flattenedFileObject) Read(p []byte) (int, error) {
220 return copy(p, slices.Concat(
221 ffo.FlatFileHeader.Format[:],
222 ffo.FlatFileHeader.Version[:],
223 ffo.FlatFileHeader.RSVD[:],
224 ffo.FlatFileHeader.ForkCount[:],
225 []byte("INFO"),
226 []byte{0, 0, 0, 0},
227 make([]byte, 4),
228 ffo.FlatFileInformationFork.DataSize(),
229 ffo.FlatFileInformationFork.Platform,
230 ffo.FlatFileInformationFork.TypeSignature,
231 ffo.FlatFileInformationFork.CreatorSignature,
232 ffo.FlatFileInformationFork.Flags,
233 ffo.FlatFileInformationFork.PlatformFlags,
234 ffo.FlatFileInformationFork.RSVD,
235 ffo.FlatFileInformationFork.CreateDate,
236 ffo.FlatFileInformationFork.ModifyDate,
237 ffo.FlatFileInformationFork.NameScript,
238 ffo.FlatFileInformationFork.ReadNameSize(),
239 ffo.FlatFileInformationFork.Name,
240 ffo.FlatFileInformationFork.CommentSize,
241 ffo.FlatFileInformationFork.Comment,
242 ffo.FlatFileDataForkHeader.ForkType[:],
243 ffo.FlatFileDataForkHeader.CompressionType[:],
244 ffo.FlatFileDataForkHeader.RSVD[:],
245 ffo.FlatFileDataForkHeader.DataSize[:],
246 ),
247 ), io.EOF
248}
249
250func (ffo *flattenedFileObject) ReadFrom(r io.Reader) (int64, error) {
251 var n int64
252
253 if err := binary.Read(r, binary.BigEndian, &ffo.FlatFileHeader); err != nil {
254 return n, err
255 }
256
257 if err := binary.Read(r, binary.BigEndian, &ffo.FlatFileInformationForkHeader); err != nil {
258 return n, err
259 }
260
261 dataLen := binary.BigEndian.Uint32(ffo.FlatFileInformationForkHeader.DataSize[:])
262 ffifBuf := make([]byte, dataLen)
263 if _, err := io.ReadFull(r, ffifBuf); err != nil {
264 return n, err
265 }
266
267 _, err := io.Copy(&ffo.FlatFileInformationFork, bytes.NewReader(ffifBuf))
268 if err != nil {
269 return n, err
270 }
271
272 if err := binary.Read(r, binary.BigEndian, &ffo.FlatFileDataForkHeader); err != nil {
273 return n, err
274 }
275
276 return n, nil
277}
278
279func (ffo *flattenedFileObject) dataSize() int64 {
280 return int64(binary.BigEndian.Uint32(ffo.FlatFileDataForkHeader.DataSize[:]))
281}
282
283func (ffo *flattenedFileObject) rsrcSize() int64 {
284 return int64(binary.BigEndian.Uint32(ffo.FlatFileResForkHeader.DataSize[:]))
285}