temp for tree extraction
This commit is contained in:
201
internal/storage/oplog_reader.go
Normal file
201
internal/storage/oplog_reader.go
Normal file
@ -0,0 +1,201 @@
|
||||
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
|
||||
}
|
Reference in New Issue
Block a user