202 lines
4.8 KiB
Go
202 lines
4.8 KiB
Go
package storage
|
|
|
|
import (
|
|
"encoding/binary"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
|
|
"git.dws.rip/DWS/onyx/internal/models"
|
|
)
|
|
|
|
// OplogReader handles reading entries from the oplog file
|
|
type OplogReader struct {
|
|
path string
|
|
}
|
|
|
|
// NewOplogReader creates a new oplog reader for the given file path
|
|
func NewOplogReader(path string) *OplogReader {
|
|
return &OplogReader{
|
|
path: path,
|
|
}
|
|
}
|
|
|
|
// ReadLastEntry reads the last (most recent) entry in the oplog
|
|
func (r *OplogReader) ReadLastEntry() (*models.OplogEntry, error) {
|
|
file, err := os.Open(r.path)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to open oplog file: %w", err)
|
|
}
|
|
defer file.Close()
|
|
|
|
var lastEntry *models.OplogEntry
|
|
|
|
// Read through all entries to find the last one
|
|
for {
|
|
// Read entry length (4 bytes)
|
|
var entryLen uint32
|
|
err := binary.Read(file, binary.LittleEndian, &entryLen)
|
|
if err != nil {
|
|
if err == io.EOF {
|
|
break
|
|
}
|
|
return nil, fmt.Errorf("failed to read entry length: %w", err)
|
|
}
|
|
|
|
// Read the entry data
|
|
entryData := make([]byte, entryLen)
|
|
n, err := file.Read(entryData)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to read entry data: %w", err)
|
|
}
|
|
if n != int(entryLen) {
|
|
return nil, fmt.Errorf("incomplete entry data read: expected %d bytes, got %d", entryLen, n)
|
|
}
|
|
|
|
// Deserialize the entry
|
|
entry, err := models.DeserializeOplogEntry(entryData)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to deserialize entry: %w", err)
|
|
}
|
|
|
|
lastEntry = entry
|
|
}
|
|
|
|
if lastEntry == nil {
|
|
return nil, fmt.Errorf("oplog is empty")
|
|
}
|
|
|
|
return lastEntry, nil
|
|
}
|
|
|
|
// ReadEntry reads a specific entry by ID
|
|
func (r *OplogReader) ReadEntry(id uint64) (*models.OplogEntry, error) {
|
|
file, err := os.Open(r.path)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to open oplog file: %w", err)
|
|
}
|
|
defer file.Close()
|
|
|
|
// Read through all entries to find the one with matching ID
|
|
for {
|
|
// Read entry length (4 bytes)
|
|
var entryLen uint32
|
|
err := binary.Read(file, binary.LittleEndian, &entryLen)
|
|
if err != nil {
|
|
if err == io.EOF {
|
|
break
|
|
}
|
|
return nil, fmt.Errorf("failed to read entry length: %w", err)
|
|
}
|
|
|
|
// Read the entry data
|
|
entryData := make([]byte, entryLen)
|
|
n, err := file.Read(entryData)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to read entry data: %w", err)
|
|
}
|
|
if n != int(entryLen) {
|
|
return nil, fmt.Errorf("incomplete entry data read: expected %d bytes, got %d", entryLen, n)
|
|
}
|
|
|
|
// Deserialize the entry
|
|
entry, err := models.DeserializeOplogEntry(entryData)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to deserialize entry: %w", err)
|
|
}
|
|
|
|
if entry.ID == id {
|
|
return entry, nil
|
|
}
|
|
}
|
|
|
|
return nil, fmt.Errorf("entry with ID %d not found", id)
|
|
}
|
|
|
|
// GetUndoStack returns a stack of entries that can be undone (in reverse order)
|
|
func (r *OplogReader) GetUndoStack() ([]*models.OplogEntry, error) {
|
|
entries, err := r.ReadAllEntries()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Filter out entries that have already been undone
|
|
// For now, we return all entries in reverse order
|
|
// In the future, we might track undone entries separately
|
|
var undoStack []*models.OplogEntry
|
|
for i := len(entries) - 1; i >= 0; i-- {
|
|
undoStack = append(undoStack, entries[i])
|
|
}
|
|
|
|
return undoStack, nil
|
|
}
|
|
|
|
// ReadAllEntries reads all entries from the oplog in order
|
|
func (r *OplogReader) ReadAllEntries() ([]*models.OplogEntry, error) {
|
|
file, err := os.Open(r.path)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to open oplog file: %w", err)
|
|
}
|
|
defer file.Close()
|
|
|
|
var entries []*models.OplogEntry
|
|
|
|
// Read through all entries
|
|
for {
|
|
// Read entry length (4 bytes)
|
|
var entryLen uint32
|
|
err := binary.Read(file, binary.LittleEndian, &entryLen)
|
|
if err != nil {
|
|
if err == io.EOF {
|
|
break
|
|
}
|
|
return nil, fmt.Errorf("failed to read entry length: %w", err)
|
|
}
|
|
|
|
// Read the entry data
|
|
entryData := make([]byte, entryLen)
|
|
n, err := file.Read(entryData)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to read entry data: %w", err)
|
|
}
|
|
if n != int(entryLen) {
|
|
return nil, fmt.Errorf("incomplete entry data read: expected %d bytes, got %d", entryLen, n)
|
|
}
|
|
|
|
// Deserialize the entry
|
|
entry, err := models.DeserializeOplogEntry(entryData)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to deserialize entry: %w", err)
|
|
}
|
|
|
|
entries = append(entries, entry)
|
|
}
|
|
|
|
return entries, nil
|
|
}
|
|
|
|
// Count returns the total number of entries in the oplog
|
|
func (r *OplogReader) Count() (int, error) {
|
|
entries, err := r.ReadAllEntries()
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
return len(entries), nil
|
|
}
|
|
|
|
// IsEmpty checks if the oplog is empty
|
|
func (r *OplogReader) IsEmpty() (bool, error) {
|
|
file, err := os.Open(r.path)
|
|
if err != nil {
|
|
return false, fmt.Errorf("failed to open oplog file: %w", err)
|
|
}
|
|
defer file.Close()
|
|
|
|
stat, err := file.Stat()
|
|
if err != nil {
|
|
return false, fmt.Errorf("failed to stat file: %w", err)
|
|
}
|
|
|
|
return stat.Size() == 0, nil
|
|
}
|