github.com/jhalter/mobius@v0.12.1/hotline/files.go (about) 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 return nil, err 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 }