diff --git a/weed/storage/needle/needle_read_options.go b/weed/storage/needle/needle_read_options.go new file mode 100644 index 000000000..6ddc2d88e --- /dev/null +++ b/weed/storage/needle/needle_read_options.go @@ -0,0 +1,24 @@ +package needle + +import ( + "github.com/seaweedfs/seaweedfs/weed/storage/backend" + . "github.com/seaweedfs/seaweedfs/weed/storage/types" +) + +// NeedleReadOptions specifies which parts of the Needle to read. +type NeedleReadOptions struct { + ReadHeader bool // always true for any read + ReadData bool // read the Data field + ReadMeta bool // read metadata fields (Name, Mime, LastModified, Ttl, Pairs, etc.) +} + +// ReadFromFile reads the Needle from the backend file according to the specified options. +// For now, this is equivalent to ReadData (reads everything). +func (n *Needle) ReadFromFile(r backend.BackendStorageFile, offset int64, size Size, version Version, opts NeedleReadOptions) error { + // Always read header and body for now (full read) + bytes, err := ReadNeedleBlob(r, offset, size, version) + if err != nil { + return err + } + return n.ReadBytes(bytes, offset, size, version) +} diff --git a/weed/storage/needle/needle_read_options_test.go b/weed/storage/needle/needle_read_options_test.go new file mode 100644 index 000000000..e50b77b59 --- /dev/null +++ b/weed/storage/needle/needle_read_options_test.go @@ -0,0 +1,92 @@ +package needle + +import ( + "bytes" + "io" + "reflect" + "testing" + "time" + + . "github.com/seaweedfs/seaweedfs/weed/storage/types" +) + +type mockBackend struct { + data []byte +} + +func (m *mockBackend) ReadAt(p []byte, off int64) (n int, err error) { + if int(off) >= len(m.data) { + return 0, io.EOF + } + n = copy(p, m.data[off:]) + if n < len(p) { + return n, io.EOF + } + return n, nil +} + +func (m *mockBackend) GetStat() (int64, time.Time, error) { + return int64(len(m.data)), time.Time{}, nil +} + +func (m *mockBackend) Name() string { + return "mock" +} + +func (m *mockBackend) Close() error { + return nil +} + +func (m *mockBackend) Sync() error { + return nil +} + +func (m *mockBackend) Truncate(size int64) error { + m.data = m.data[:size] + return nil +} + +func (m *mockBackend) WriteAt(p []byte, off int64) (n int, err error) { + return 0, nil +} + +func TestReadFromFile_EquivalenceWithReadData(t *testing.T) { + n := &Needle{ + Cookie: 0x12345678, + Id: 0x1122334455667788, + Data: []byte("hello world"), + Flags: 0xFF, + Name: []byte("filename.txt"), + Mime: []byte("text/plain"), + LastModified: 0x1234567890, + Ttl: nil, + Pairs: []byte("key=value"), + PairsSize: 9, + Checksum: 0xCAFEBABE, + AppendAtNs: 0xDEADBEEF, + } + buf := &bytes.Buffer{} + _, _, err := writeNeedleV2(n, 0, buf) + if err != nil { + t.Fatalf("writeNeedleV2 failed: %v", err) + } + backend := &mockBackend{data: buf.Bytes()} + size := Size(len(buf.Bytes()) - NeedleHeaderSize - NeedleChecksumSize - int(PaddingLength(Size(len(buf.Bytes())-NeedleHeaderSize-NeedleChecksumSize), Version2))) + + // Old method + nOld := &Needle{} + errOld := nOld.ReadData(backend, 0, size, Version2) + + // New method + nNew := &Needle{} + opts := NeedleReadOptions{ReadHeader: true, ReadData: true, ReadMeta: true} + errNew := nNew.ReadFromFile(backend, 0, size, Version2, opts) + + if (errOld != nil) != (errNew != nil) || (errOld != nil && errOld.Error() != errNew.Error()) { + t.Errorf("error mismatch: old=%v new=%v", errOld, errNew) + } + + if !reflect.DeepEqual(nOld, nNew) { + t.Errorf("needle mismatch: old=%+v new=%+v", nOld, nNew) + } +}