package main import ( "fmt" "sync" "time" ) // RingBuf implements an indefinitely writable circular buffer with a fixed size. Old data is overridden if write circle loops. Somewhat threadsafe. type RingBuf struct { sync.RWMutex data []byte writePos int written int loop bool } // NewRingBuf initializes a new RingBuf with a fixed size > 0 func NewRingBuf(size int) *RingBuf { if size <= 0 { return nil } rb := &RingBuf{ data: make([]byte, size), writePos: 0, written: 0, loop: false, } return rb } func (rb *RingBuf) WriteLine(in string) (int, error) { return rb.Write([]byte(fmt.Sprintf("[%s]: %s\n", time.Now().Format("2006-01-02 15:04:05"), in))) } func (rb *RingBuf) WriteError(err error) (int, error) { return rb.Write([]byte(fmt.Sprintf("[%s]: ⚠️ %s\n", time.Now().Format("2006-01-02 15:04:05"), err.Error()))) } // Write writes all data from input buf to RingBuf, overriding looped data func (rb *RingBuf) Write(buf []byte) (int, error) { rb.Lock() defer rb.Unlock() inLen := len(buf) bufLen := len(rb.data) if !rb.loop && rb.written < bufLen { rb.written = rb.written + inLen rb.loop = rb.written >= bufLen } // throw away bytes which would get looped over if input bigger than data if inLen > bufLen { buf = buf[inLen-bufLen:] } // Copy to data field bytesToEnd := bufLen - rb.writePos copy(rb.data[rb.writePos:], buf) if len(buf) > bytesToEnd { copy(rb.data, buf[bytesToEnd:]) } // Move writePos rb.writePos = ((rb.writePos + len(buf)) % bufLen) return inLen, nil } // Size returns the maximum size of the RingBuf func (rb *RingBuf) Size() int { return len(rb.data) } // Bytes returns content of RingBuf. DON'T WRITE TO SLICE! func (rb *RingBuf) Bytes() []byte { rb.RLock() defer rb.RUnlock() bufLen := len(rb.data) if rb.loop { out := make([]byte, bufLen) if rb.writePos == 0 { copy(out, rb.data) } else { copy(out, rb.data[rb.writePos:]) copy(out[bufLen-rb.writePos:], rb.data[:rb.writePos]) } return out } out := make([]byte, rb.writePos) copy(out, rb.data[:rb.writePos]) return out } // Restart restarts RingBuf from 0 func (rb *RingBuf) Restart() { rb.writePos = 0 rb.written = 0 rb.loop = false } // String returns content of RingBuf as string func (rb *RingBuf) String() string { return string(rb.Bytes()) }