code.vegaprotocol.io/vega@v0.79.0/core/broker/file_client.go (about)

     1  // Copyright (C) 2023 Gobalsky Labs Limited
     2  //
     3  // This program is free software: you can redistribute it and/or modify
     4  // it under the terms of the GNU Affero General Public License as
     5  // published by the Free Software Foundation, either version 3 of the
     6  // License, or (at your option) any later version.
     7  //
     8  // This program is distributed in the hope that it will be useful,
     9  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    10  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    11  // GNU Affero General Public License for more details.
    12  //
    13  // You should have received a copy of the GNU Affero General Public License
    14  // along with this program.  If not, see <http://www.gnu.org/licenses/>.
    15  
    16  package broker
    17  
    18  import (
    19  	"encoding/binary"
    20  	"fmt"
    21  	"os"
    22  	"path/filepath"
    23  	"sync"
    24  
    25  	"code.vegaprotocol.io/vega/core/events"
    26  	"code.vegaprotocol.io/vega/logging"
    27  
    28  	"google.golang.org/protobuf/proto"
    29  )
    30  
    31  type FileClient struct {
    32  	log *logging.Logger
    33  
    34  	config *FileConfig
    35  
    36  	file   *os.File
    37  	mut    sync.RWMutex
    38  	seqNum uint64
    39  }
    40  
    41  const (
    42  	NumberOfSeqNumBytes = 8
    43  	NumberOfSizeBytes   = 4
    44  
    45  	namedFileClientLogger = "file-client"
    46  )
    47  
    48  func NewFileClient(log *logging.Logger, config *FileConfig) (*FileClient, error) {
    49  	log = log.Named(namedFileClientLogger)
    50  	fc := &FileClient{
    51  		log:    log,
    52  		config: config,
    53  	}
    54  
    55  	filePath, err := filepath.Abs(config.File)
    56  	if err != nil {
    57  		return nil, fmt.Errorf("unable to determine absolute path of file %s: %w", config.File, err)
    58  	}
    59  
    60  	fc.file, err = os.Create(filePath)
    61  
    62  	if err != nil {
    63  		return nil, fmt.Errorf("unable to create file %s: %w", filePath, err)
    64  	}
    65  
    66  	log.Infof("persisting events to: %s\n", filePath)
    67  
    68  	return fc, nil
    69  }
    70  
    71  func (fc *FileClient) SendBatch(evts []events.Event) error {
    72  	for _, evt := range evts {
    73  		if err := fc.Send(evt); err != nil {
    74  			return err
    75  		}
    76  	}
    77  	return nil
    78  }
    79  
    80  func (fc *FileClient) Send(event events.Event) error {
    81  	fc.mut.RLock()
    82  	defer fc.mut.RUnlock()
    83  
    84  	err := WriteToBufferFile(fc.file, fc.seqNum, event)
    85  	fc.seqNum++
    86  
    87  	if err != nil {
    88  		return fmt.Errorf("failed to write event to buffer file: %w", err)
    89  	}
    90  
    91  	return nil
    92  }
    93  
    94  func WriteToBufferFile(bufferFile *os.File, bufferSeqNum uint64, event events.Event) error {
    95  	rawEvent, err := proto.Marshal(event.StreamMessage())
    96  	if err != nil {
    97  		return fmt.Errorf("failed to marshal bus event:%w", err)
    98  	}
    99  	return WriteRawToBufferFile(bufferFile, bufferSeqNum, rawEvent)
   100  }
   101  
   102  func WriteRawToBufferFile(bufferFile *os.File, bufferSeqNum uint64, rawEvent []byte) error {
   103  	seqNumBytes := make([]byte, NumberOfSeqNumBytes)
   104  	sizeBytes := make([]byte, NumberOfSizeBytes)
   105  
   106  	size := NumberOfSeqNumBytes + uint32(len(rawEvent))
   107  
   108  	binary.BigEndian.PutUint64(seqNumBytes, bufferSeqNum)
   109  	binary.BigEndian.PutUint32(sizeBytes, size)
   110  	allBytes := append([]byte{}, sizeBytes...)
   111  	allBytes = append(allBytes, seqNumBytes...)
   112  	allBytes = append(allBytes, rawEvent...)
   113  	_, err := bufferFile.Write(allBytes)
   114  	if err != nil {
   115  		return fmt.Errorf("failed to write to buffer file:%w", err)
   116  	}
   117  
   118  	return nil
   119  }
   120  
   121  // Replace - in case of a config change, just replace the file we're writing to with the new one.
   122  func (fc *FileClient) Replace(newFC *FileClient) {
   123  	fc.mut.Lock()
   124  	defer fc.mut.Unlock()
   125  	fc.config = newFC.config
   126  	_ = fc.file.Close()
   127  	fc.file = newFC.file
   128  }
   129  
   130  func (fc *FileClient) Close() error {
   131  	return fc.file.Close()
   132  }