package freelist import ( "bytes" "fmt" "os" "gordenko.dev/dima/qb/bin" "gordenko.dev/dima/qb/util" ) const ( crcSize = 4 ptrSize = 4 ) type FreeList struct { file *os.File pageSize int pointersOnPage int flushTreshold int // кількість вільних сторінок коли вже треба писати на диск pages int free []uint32 } type Options struct { PageSize int File *os.File } func New(opt Options) (*FreeList, error) { s := &FreeList{ file: opt.File, pageSize: opt.PageSize, pointersOnPage: (opt.PageSize - crcSize) / ptrSize, } s.flushTreshold = int(float64(s.pointersOnPage) * 1.3) info, err := s.file.Stat() if err != nil { return nil, err } fileSize := info.Size() if fileSize > 0 { if (fileSize % int64(s.pageSize)) > 0 { return nil, fmt.Errorf("file size %d is not a multiple of page size %d", fileSize, s.pageSize) } s.pages = int(fileSize) / s.pageSize } return s, nil } func (s *FreeList) AddPageNumbers(pageNumbers []uint32) (err error) { if len(pageNumbers) == 0 { return } s.free = append(s.free, pageNumbers...) for len(s.free) > s.flushTreshold { err = s.save() if err != nil { return } // викидаю номери сторінок, які записав на диск copy(s.free, s.free[s.pointersOnPage:]) s.free = s.free[:len(s.free)-s.pointersOnPage] } return } func (s *FreeList) save() error { buf := make([]byte, s.pageSize) i := crcSize for _, pageNo := range s.free[:s.pointersOnPage] { bin.PutUint32(buf[i:], pageNo) i += ptrSize } bin.PutUint32(buf, util.CalcChecksum(buf[crcSize:])) // off := int64(s.pages * s.pageSize) n, err := s.file.WriteAt(buf, off) if err != nil { return err } if n != s.pageSize { return fmt.Errorf("written size %d bytes not equal page size %d", n, s.pageSize) } err = s.file.Sync() if err != nil { return err } s.pages++ return nil } func (s *FreeList) loadLastPage() error { var ( buf = make([]byte, s.pageSize) off = int64((s.pages - 1) * s.pageSize) ) n, err := s.file.ReadAt(buf, off) if err != nil { return fmt.Errorf("file.Seek: %s", err) } if n != s.pageSize { return fmt.Errorf("read size %d bytes not equal page size %d", n, s.pageSize) } // check crc checksum := bin.GetUint32(buf[0:]) if util.CalcChecksum(buf[crcSize:]) != checksum { return fmt.Errorf("page %d is corrupted", s.pages) } for i := crcSize; i < len(buf); i += ptrSize { s.free = append(s.free, bin.GetUint32(buf[i:])) } s.pages-- err = s.file.Truncate(int64(s.pages * s.pageSize)) if err != nil { return err } return nil } func (s *FreeList) GetPageNumber() (pageNo uint32, err error) { if len(s.free) == 0 { if s.pages == 0 { return } err = s.loadLastPage() if err != nil { return } } lastIdx := len(s.free) - 1 pageNo = s.free[lastIdx] s.free = s.free[:lastIdx] return } // для відновлення недозаповненнох сторінки із снапшота func (s *FreeList) Restore(serialized []byte) error { if (len(serialized) % ptrSize) != 0 { return fmt.Errorf("wrong size") } for i := 0; i < len(serialized); i += ptrSize { s.free = append(s.free, bin.GetUint32(serialized[i:])) } return nil } // для запису в snapshot func (s *FreeList) Serialize() ([]byte, error) { w := bytes.NewBuffer(nil) for _, pageNo := range s.free { bin.WriteUint32(w, pageNo) } return w.Bytes(), nil }