]> git.r.bdr.sh - rbdr/mobius/blob - hotline/files.go
Ignore files with incompatible names
[rbdr/mobius] / hotline / files.go
1 package hotline
2
3 import (
4 "encoding/binary"
5 "errors"
6 "io/fs"
7 "os"
8 "path/filepath"
9 "regexp"
10 "strings"
11 )
12
13 func fileTypeFromFilename(filename string) fileType {
14 fileExt := strings.ToLower(filepath.Ext(filename))
15 ft, ok := fileTypes[fileExt]
16 if ok {
17 return ft
18 }
19 return defaultFileType
20 }
21
22 func fileTypeFromInfo(info fs.FileInfo) (ft fileType, err error) {
23 if info.IsDir() {
24 ft.CreatorCode = "n/a "
25 ft.TypeCode = "fldr"
26 } else {
27 ft = fileTypeFromFilename(info.Name())
28 }
29
30 return ft, nil
31 }
32
33 const maxFileSize = 4294967296
34
35 func getFileNameList(path string, ignoreList []string) (fields []Field, err error) {
36 files, err := os.ReadDir(path)
37 if err != nil {
38 return fields, nil
39 }
40
41 for _, file := range files {
42 var fnwi FileNameWithInfo
43
44 if ignoreFile(file.Name(), ignoreList) {
45 continue
46 }
47
48 fileCreator := make([]byte, 4)
49
50 fileInfo, err := file.Info()
51 if err != nil {
52 return fields, err
53 }
54
55 // Check if path is a symlink. If so, follow it.
56 if fileInfo.Mode()&os.ModeSymlink != 0 {
57 resolvedPath, err := os.Readlink(filepath.Join(path, file.Name()))
58 if err != nil {
59 return fields, err
60 }
61
62 rFile, err := os.Stat(resolvedPath)
63 if errors.Is(err, os.ErrNotExist) {
64 continue
65 }
66 if err != nil {
67 return fields, err
68 }
69
70 if rFile.IsDir() {
71 dir, err := os.ReadDir(filepath.Join(path, file.Name()))
72 if err != nil {
73 return fields, err
74 }
75
76 var c uint32
77 for _, f := range dir {
78 if !ignoreFile(f.Name(), ignoreList) {
79 c += 1
80 }
81 }
82
83 binary.BigEndian.PutUint32(fnwi.FileSize[:], c)
84 copy(fnwi.Type[:], "fldr")
85 copy(fnwi.Creator[:], fileCreator)
86 } else {
87 binary.BigEndian.PutUint32(fnwi.FileSize[:], uint32(rFile.Size()))
88 copy(fnwi.Type[:], fileTypeFromFilename(rFile.Name()).TypeCode)
89 copy(fnwi.Creator[:], fileTypeFromFilename(rFile.Name()).CreatorCode)
90 }
91 } else if file.IsDir() {
92 dir, err := os.ReadDir(filepath.Join(path, file.Name()))
93 if err != nil {
94 return fields, err
95 }
96
97 var c uint32
98 for _, f := range dir {
99 if !ignoreFile(f.Name(), ignoreList) {
100 c += 1
101 }
102 }
103
104 binary.BigEndian.PutUint32(fnwi.FileSize[:], c)
105 copy(fnwi.Type[:], "fldr")
106 copy(fnwi.Creator[:], fileCreator)
107 } else {
108 // the Hotline protocol does not support file sizes > 4GiB due to the 4 byte field size, so skip them
109 if fileInfo.Size() > maxFileSize {
110 continue
111 }
112
113 hlFile, err := newFileWrapper(&OSFileStore{}, path+"/"+file.Name(), 0)
114 if err != nil {
115 return nil, err
116 }
117
118 copy(fnwi.FileSize[:], hlFile.totalSize())
119 copy(fnwi.Type[:], hlFile.ffo.FlatFileInformationFork.TypeSignature)
120 copy(fnwi.Creator[:], hlFile.ffo.FlatFileInformationFork.CreatorSignature)
121 }
122
123 strippedName := strings.ReplaceAll(file.Name(), ".incomplete", "")
124 strippedName, err = txtEncoder.String(strippedName)
125 if err != nil {
126 continue
127 }
128
129 nameSize := make([]byte, 2)
130 binary.BigEndian.PutUint16(nameSize, uint16(len(strippedName)))
131 copy(fnwi.NameSize[:], nameSize)
132
133 fnwi.name = []byte(strippedName)
134
135 b, err := fnwi.MarshalBinary()
136 if err != nil {
137 return nil, err
138 }
139 fields = append(fields, NewField(FieldFileNameWithInfo, b))
140 }
141
142 return fields, nil
143 }
144
145 func CalcTotalSize(filePath string) ([]byte, error) {
146 var totalSize uint32
147 err := filepath.Walk(filePath, func(path string, info os.FileInfo, err error) error {
148 if err != nil {
149 return err
150 }
151
152 if info.IsDir() {
153 return nil
154 }
155
156 totalSize += uint32(info.Size())
157
158 return nil
159 })
160 if err != nil {
161 return nil, err
162 }
163
164 bs := make([]byte, 4)
165 binary.BigEndian.PutUint32(bs, totalSize)
166
167 return bs, nil
168 }
169
170 func CalcItemCount(filePath string) ([]byte, error) {
171 var itemcount uint16
172 err := filepath.Walk(filePath, func(path string, info os.FileInfo, err error) error {
173 if err != nil {
174 return err
175 }
176
177 if !strings.HasPrefix(info.Name(), ".") {
178 itemcount += 1
179 }
180
181 return nil
182 })
183 if err != nil {
184 return nil, err
185 }
186
187 bs := make([]byte, 2)
188 binary.BigEndian.PutUint16(bs, itemcount-1)
189
190 return bs, nil
191 }
192
193 func EncodeFilePath(filePath string) []byte {
194 pathSections := strings.Split(filePath, "/")
195 pathItemCount := make([]byte, 2)
196 binary.BigEndian.PutUint16(pathItemCount, uint16(len(pathSections)))
197
198 bytes := pathItemCount
199
200 for _, section := range pathSections {
201 bytes = append(bytes, []byte{0, 0}...)
202
203 pathStr := []byte(section)
204 bytes = append(bytes, byte(len(pathStr)))
205 bytes = append(bytes, pathStr...)
206 }
207
208 return bytes
209 }
210
211 func ignoreFile(fileName string, ignoreList []string) bool {
212 // skip files that match any regular expression present in the IgnoreFiles list
213 matchIgnoreFilter := 0
214 for _, pattern := range ignoreList {
215 if match, _ := regexp.MatchString(pattern, fileName); match {
216 matchIgnoreFilter += 1
217 }
218 }
219 return matchIgnoreFilter > 0
220 }