func (g *Group) RotateFile() {
	g.mtx.Lock()
	defer g.mtx.Unlock()
	headPath := g.Head.Path
	if err := g.headBuf.Flush(); err != nil {
		panic(err)
	}
	if err := g.Head.Sync(); err != nil {
		panic(err)
	}
	if err := g.Head.closeFile(); err != nil {
		panic(err)
	}
	indexPath := filePathForIndex(headPath, g.maxIndex, g.maxIndex+1)
	if err := os.Rename(headPath, indexPath); err != nil {
		panic(err)
	}
	g.maxIndex++
}
RotateFile causes group to close the current head and assign it some index.
Note it does not create a new head.
package autofile
import (
	"bufio"
	"errors"
	"fmt"
	"io"
	"os"
	"path/filepath"
	"regexp"
	"strconv"
	"strings"
	"sync"
	"time"
	"github.com/tendermint/tendermint/libs/service"
)
const (
	defaultGroupCheckDuration = 5000 * time.Millisecond
	defaultHeadSizeLimit      = 10 * 1024 * 1024        
	defaultTotalSizeLimit     = 1 * 1024 * 1024 * 1024  
	maxFilesToRemove          = 4                       
)
type Group struct {
	service.BaseService
	ID                 string
	Head               *AutoFile  
	headBuf            *bufio.Writer
	Dir                string  
	ticker             *time.Ticker
	mtx                sync.Mutex
	headSizeLimit      int64
	totalSizeLimit     int64
	groupCheckDuration time.Duration
	minIndex           int  
	maxIndex           int  
	doneProcessTicks chan struct{}
}
func OpenGroup(headPath string, groupOptions ...func(*Group)) (*Group, error) {
	dir, err := filepath.Abs(filepath.Dir(headPath))
	if err != nil {
		return nil, err
	}
	head, err := OpenAutoFile(headPath)
	if err != nil {
		return nil, err
	}
	g := &Group{
		ID:                 "group:" + head.ID,
		Head:               head,
		headBuf:            bufio.NewWriterSize(head, 4096*10),
		Dir:                dir,
		headSizeLimit:      defaultHeadSizeLimit,
		totalSizeLimit:     defaultTotalSizeLimit,
		groupCheckDuration: defaultGroupCheckDuration,
		minIndex:           0,
		maxIndex:           0,
		doneProcessTicks:   make(chan struct{}),
	}
	for _, option := range groupOptions {
		option(g)
	}
	g.BaseService = *service.NewBaseService(nil, "Group", g)
	gInfo := g.readGroupInfo()
	g.minIndex = gInfo.MinIndex
	g.maxIndex = gInfo.MaxIndex
	return g, nil
}
func GroupCheckDuration(duration time.Duration) func(*Group) {
	return func(g *Group) {
		g.groupCheckDuration = duration
	}
}
func GroupHeadSizeLimit(limit int64) func(*Group) {
	return func(g *Group) {
		g.headSizeLimit = limit
	}
}
func GroupTotalSizeLimit(limit int64) func(*Group) {
	return func(g *Group) {
		g.totalSizeLimit = limit
	}
}
func (g *Group) OnStart() error {
	g.ticker = time.NewTicker(g.groupCheckDuration)
	go g.processTicks()
	return nil
}
func (g *Group) OnStop() {
	g.ticker.Stop()
	if err := g.FlushAndSync(); err != nil {
		g.Logger.Error("Error flushing to disk", "err", err)
	}
}
func (g *Group) Wait() {
	<-g.doneProcessTicks
}
func (g *Group) Close() {
	if err := g.FlushAndSync(); err != nil {
		g.Logger.Error("Error flushing to disk", "err", err)
	}
	g.mtx.Lock()
	_ = g.Head.closeFile()
	g.mtx.Unlock()
}
func (g *Group) HeadSizeLimit() int64 {
	g.mtx.Lock()
	defer g.mtx.Unlock()
	return g.headSizeLimit
}
func (g *Group) TotalSizeLimit() int64 {
	g.mtx.Lock()
	defer g.mtx.Unlock()
	return g.totalSizeLimit
}
func (g *Group) MaxIndex() int {
	g.mtx.Lock()
	defer g.mtx.Unlock()
	return g.maxIndex
}
func (g *Group) MinIndex() int {
	g.mtx.Lock()
	defer g.mtx.Unlock()
	return g.minIndex
}
func (g *Group) Write(p []byte) (nn int, err error) {
	g.mtx.Lock()
	defer g.mtx.Unlock()
	return g.headBuf.Write(p)
}
func (g *Group) WriteLine(line string) error {
	g.mtx.Lock()
	defer g.mtx.Unlock()
	_, err := g.headBuf.Write([]byte(line + "\n"))
	return err
}
func (g *Group) Buffered() int {
	g.mtx.Lock()
	defer g.mtx.Unlock()
	return g.headBuf.Buffered()
}
func (g *Group) FlushAndSync() error {
	g.mtx.Lock()
	defer g.mtx.Unlock()
	err := g.headBuf.Flush()
	if err == nil {
		err = g.Head.Sync()
	}
	return err
}
func (g *Group) processTicks() {
	defer close(g.doneProcessTicks)
	for {
		select {
		case <-g.ticker.C:
			g.checkHeadSizeLimit()
			g.checkTotalSizeLimit()
		case <-g.Quit():
			return
		}
	}
}
func (g *Group) checkHeadSizeLimit() {
	limit := g.HeadSizeLimit()
	if limit == 0 {
		return
	}
	size, err := g.Head.Size()
	if err != nil {
		g.Logger.Error("Group's head may grow without bound", "head", g.Head.Path, "err", err)
		return
	}
	if size >= limit {
		g.RotateFile()
	}
}
func (g *Group) checkTotalSizeLimit() {
	limit := g.TotalSizeLimit()
	if limit == 0 {
		return
	}
	gInfo := g.readGroupInfo()
	totalSize := gInfo.TotalSize
	for i := 0; i < maxFilesToRemove; i++ {
		index := gInfo.MinIndex + i
		if totalSize < limit {
			return
		}
		if index == gInfo.MaxIndex {
			g.Logger.Error("Group's head may grow without bound", "head", g.Head.Path)
			return
		}
		pathToRemove := filePathForIndex(g.Head.Path, index, gInfo.MaxIndex)
		fInfo, err := os.Stat(pathToRemove)
		if err != nil {
			g.Logger.Error("Failed to fetch info for file", "file", pathToRemove)
			continue
		}
		err = os.Remove(pathToRemove)
		if err != nil {
			g.Logger.Error("Failed to remove path", "path", pathToRemove)
			return
		}
		totalSize -= fInfo.Size()
	}
}