| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138 |
- package faultRecordFile
- import (
- "archive/tar"
- "bytes"
- "compress/bzip2"
- "compress/gzip"
- "errors"
- "fmt"
- "io"
- "path"
- "sort"
- "strconv"
- "strings"
- )
- const (
- maxEntrySize = 16 << 20 // 单个文件上限
- maxArchiveRead = 64 << 20 // 解压总量上限
- fileFailure = "failuredata"
- fileWaveform = "waveformdata"
- indexDirPrefix = "index_"
- )
- // RecordFiles 压缩包中一条记录的两个文件(十六进制文本原文)
- type RecordFiles struct {
- Batch string `json:"batch"`
- Sn string `json:"sn"`
- Index int `json:"index"`
- FailureData []byte `json:"-"`
- WaveformData []byte `json:"-"`
- }
- // openArchive 按文件头判断压缩格式;扩展名为 .tar.bz2 的文件实际可能是 gzip
- func openArchive(data []byte) (io.Reader, error) {
- switch {
- case len(data) >= 2 && data[0] == 0x1f && data[1] == 0x8b:
- return gzip.NewReader(bytes.NewReader(data))
- case len(data) >= 3 && string(data[:3]) == "BZh":
- return bzip2.NewReader(bytes.NewReader(data)), nil
- default:
- return bytes.NewReader(data), nil // 未压缩的 tar
- }
- }
- // ExtractArchive 解出 <batch>/<sn>/index_<n>/{failuredata,waveformdata},其他文件忽略
- func ExtractArchive(data []byte) ([]RecordFiles, error) {
- zr, err := openArchive(data)
- if err != nil {
- return nil, fmt.Errorf("open archive: %w", err)
- }
- tr := tar.NewReader(io.LimitReader(zr, maxArchiveRead))
- records := map[string]*RecordFiles{}
- for {
- h, err := tr.Next()
- if errors.Is(err, io.EOF) {
- break
- }
- if err != nil {
- return nil, fmt.Errorf("read archive: %w", err)
- }
- if h.Typeflag != tar.TypeReg {
- continue
- }
- parts := strings.Split(strings.Trim(path.Clean(h.Name), "/"), "/")
- if len(parts) < 4 {
- continue
- }
- name := parts[len(parts)-1]
- if name != fileFailure && name != fileWaveform {
- continue
- }
- idxStr, ok := strings.CutPrefix(parts[len(parts)-2], indexDirPrefix)
- if !ok {
- continue
- }
- idx, err := strconv.Atoi(idxStr)
- if err != nil {
- continue
- }
- if h.Size > maxEntrySize {
- return nil, fmt.Errorf("%s too large: %d bytes", h.Name, h.Size)
- }
- content, err := io.ReadAll(io.LimitReader(tr, maxEntrySize))
- if err != nil {
- return nil, fmt.Errorf("read %s: %w", h.Name, err)
- }
- batch, sn := parts[len(parts)-4], parts[len(parts)-3]
- key := batch + "/" + sn + "/" + idxStr
- rf, ok := records[key]
- if !ok {
- rf = &RecordFiles{Batch: batch, Sn: sn, Index: idx}
- records[key] = rf
- }
- if name == fileFailure {
- rf.FailureData = bytes.TrimSpace(content)
- } else {
- rf.WaveformData = bytes.TrimSpace(content)
- }
- }
- list := make([]RecordFiles, 0, len(records))
- for _, rf := range records {
- list = append(list, *rf)
- }
- sort.Slice(list, func(i, j int) bool {
- if list[i].Sn != list[j].Sn {
- return list[i].Sn < list[j].Sn
- }
- return list[i].Index < list[j].Index
- })
- return list, nil
- }
- // ParseArchive 解压并解析全部记录;缺少任一文件的记录返回错误
- func ParseArchive(data []byte, deviceType string) ([]Record, error) {
- files, err := ExtractArchive(data)
- if err != nil {
- return nil, err
- }
- if len(files) == 0 {
- return nil, fmt.Errorf("no record found in archive")
- }
- records := make([]Record, 0, len(files))
- for _, f := range files {
- if len(f.FailureData) == 0 || len(f.WaveformData) == 0 {
- return nil, fmt.Errorf("record %s/%s/index_%d is incomplete", f.Batch, f.Sn, f.Index)
- }
- rec, err := ParseRecordFiles(f, deviceType)
- if err != nil {
- return nil, fmt.Errorf("record %s/%s/index_%d: %w", f.Batch, f.Sn, f.Index, err)
- }
- records = append(records, rec)
- }
- return records, nil
- }
|