github.com/blong14/gache@v0.0.0-20240124023949-89416fd8bbfa/internal/io/file/dat.go (about)

     1  package file
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/base64"
     6  	"errors"
     7  	"fmt"
     8  	"os"
     9  	"path"
    10  	"strings"
    11  	"sync"
    12  )
    13  
    14  var pageSize int
    15  
    16  func init() {
    17  	pageSize = os.Getpagesize()
    18  }
    19  
    20  func DatFileHeader(file string) []byte {
    21  	return []byte(fmt.Sprintf("begin 0755 %s\n", file))
    22  
    23  }
    24  
    25  func DatFileFooter() []byte {
    26  	return []byte("\nend\n")
    27  }
    28  
    29  func NewDatFile(dir, fileName string) (*os.File, error) {
    30  	file := fmt.Sprintf("%s.dat", fileName)
    31  	p := path.Join(dir, file)
    32  	f, err := os.OpenFile(p, os.O_CREATE|os.O_RDWR, 0644)
    33  	if err != nil {
    34  		return nil, err
    35  	}
    36  	s, err := f.Stat()
    37  	if err != nil {
    38  		return nil, err
    39  	}
    40  	size := s.Size()
    41  	if size == 0 {
    42  		// memory ballast
    43  		buf := bytes.NewBuffer(nil)
    44  		buf.Write(DatFileHeader(file))
    45  		buf.Write(make([]byte, pageSize*pageSize*4))
    46  		buf.Write(DatFileFooter())
    47  		_, err = f.Write(buf.Bytes())
    48  		if err != nil {
    49  			return nil, err
    50  		}
    51  	}
    52  	return f, nil
    53  }
    54  
    55  const (
    56  	DataStartIndex = 23 // file: begin 0755 default.dat\n<data>\nend\n
    57  	DataEndIndex   = -3
    58  )
    59  
    60  var (
    61  	encoding = base64.StdEncoding.WithPadding(base64.NoPadding)
    62  )
    63  
    64  type Decoded struct {
    65  	Data     []byte
    66  	Filename string
    67  	Mode     string
    68  }
    69  
    70  func Decode(data []byte) (*Decoded, error) {
    71  	dec := &Decoded{}
    72  	if len(data) < 2 {
    73  		return dec, errors.New("invalid decode input")
    74  	}
    75  	rows := strings.Split(string(data), "\n")
    76  	if strings.Split(rows[0], " ")[0] != "begin" {
    77  		return dec, errors.New("invalid format")
    78  	}
    79  
    80  	if strings.Split(rows[0], " ")[1] == " " || strings.Split(rows[0], " ")[1] == "" {
    81  		return dec, errors.New("invalid file permissions")
    82  	}
    83  	dec.Mode = strings.Split(rows[0], " ")[1]
    84  
    85  	if strings.Split(rows[0], " ")[2] == " " || strings.Split(rows[0], " ")[2] == "" {
    86  		return dec, errors.New("invalid filename")
    87  	}
    88  	dec.Filename = strings.Split(rows[0], " ")[2]
    89  
    90  	if rows[len(rows)-2] != "end" {
    91  		return dec, errors.New("invalid format: no 'end' marker found")
    92  	}
    93  	if rows[len(rows)-3] != "`" && rows[len(rows)-3] != " " {
    94  		return dec, errors.New("invalid ending format")
    95  	}
    96  
    97  	rows = rows[1 : len(rows)-3]
    98  
    99  	var err error
   100  	dec.Data, err = DecodeBlock(rows)
   101  	return dec, err
   102  }
   103  
   104  // DecodeBlock decodes a uuencoded text block
   105  func DecodeBlock(rows []string) ([]byte, error) {
   106  	data := []byte{}
   107  	for i, row := range rows {
   108  		res, err := DecodeLine(row)
   109  		if err != nil {
   110  			return data, fmt.Errorf("DecodeBlock at line %d: %s", i+1, err)
   111  		}
   112  		data = append(data, res...)
   113  	}
   114  	return data, nil
   115  }
   116  
   117  // DecodeLine decodes a single line of uuencoded text
   118  func DecodeLine(s string) ([]byte, error) {
   119  	if len(s) < 2 {
   120  		return nil, errors.New("invalid line input")
   121  	}
   122  
   123  	// fix up non-standard padding `, to make golang's base64 not freak out
   124  	s = strings.ReplaceAll(s, "`", " ")
   125  
   126  	// data := []byte(s)
   127  	// l := data[0] - 32 // length
   128  	res, err := encoding.DecodeString(s[1:])
   129  	if err != nil {
   130  		return res, err
   131  	}
   132  	// if len(res) < int(l) {
   133  	//	 return nil, errors.New("line decoding failed")
   134  	// }
   135  	return res, nil
   136  }
   137  
   138  // Encode encodes data into uuencoded format, with header and footer
   139  func Encode(data []byte, filename, mode string) ([]byte, error) {
   140  	out := []byte{}
   141  	out = append(out, fmt.Sprintf("begin %s %s\n", mode, filename)...)
   142  
   143  	enc, err := EncodeBlock(data)
   144  	if err != nil {
   145  		return nil, err
   146  	}
   147  	out = append(out, enc...)
   148  
   149  	out = append(out, "`\nend\n"...)
   150  	return out, nil
   151  }
   152  
   153  var pool = sync.Pool{New: func() interface{} { return bytes.NewBuffer(nil) }}
   154  
   155  type Arena []byte
   156  
   157  var mtx sync.Mutex
   158  
   159  func (na *Arena) Get(len_ int) []byte {
   160  	mtx.Lock()
   161  	defer mtx.Unlock()
   162  	if len(*na) == 0 {
   163  		*na = make([]byte, 4096*4096*4)
   164  	}
   165  	offset := (len(*na) - 1) - len_
   166  	if offset <= 0 {
   167  		*na = make([]byte, len(*na)+len_)
   168  		offset = (len(*na) - 1) - len_
   169  	}
   170  	if offset == -2075 {
   171  		fmt.Println("hellow")
   172  	}
   173  	n := (*na)[offset : len(*na)-1]
   174  	*na = (*na)[:offset]
   175  	return n
   176  }
   177  
   178  var byteArena = make(Arena, 4096*4096*4)
   179  
   180  // EncodeBlock encodes data in raw uuencoded format
   181  func EncodeBlock(data []byte) ([]byte, error) {
   182  	out := byteArena.Get(base64.StdEncoding.EncodedLen(len(data)) + 2)
   183  	out[0] = byte(len(data))
   184  	encoding.Encode(out[1:], data)
   185  	out[len(out)-1] = byte('\n')
   186  	return out, nil
   187  }