From 6a0ecced9021482f1f2f05d6ec63c1e4cd33c8a0 Mon Sep 17 00:00:00 2001 From: Ophestra Date: Fri, 31 Oct 2025 04:20:22 +0900 Subject: [PATCH] internal/store: expose save via handle The handle is otherwise inaccessible without the compat interface. This change also moves compatibility methods to separate adapter structs to avoid inadvertently using them. Signed-off-by: Ophestra --- internal/store/compat.go | 31 ++++++++++++++++--------------- internal/store/segment.go | 18 ++++++++++++++---- internal/store/segment_test.go | 7 +++++-- 3 files changed, 35 insertions(+), 21 deletions(-) diff --git a/internal/store/compat.go b/internal/store/compat.go index aca26b8..c0d4057 100644 --- a/internal/store/compat.go +++ b/internal/store/compat.go @@ -22,20 +22,20 @@ type Compat interface { List() (identities []int, err error) } -func (s *Store) Do(identity int, f func(c Cursor)) (bool, error) { - if h, err := s.Handle(identity); err != nil { - return false, err - } else { - return h.do(f) - } -} - // storeAdapter satisfies [Compat] via [Store]. type storeAdapter struct { msg message.Msg *Store } +func (s storeAdapter) Do(identity int, f func(c Cursor)) (bool, error) { + if h, err := s.Handle(identity); err != nil { + return false, err + } else { + return handleAdapter{h}.do(f) + } +} + func (s storeAdapter) List() ([]int, error) { segments, n, err := s.Segments() if err != nil { @@ -71,8 +71,11 @@ type Cursor interface { Len() (int, error) } +// handleAdapter satisfies [Cursor] via [Handle]. +type handleAdapter struct{ *Handle } + // do implements [Compat.Do] on [Handle]. -func (h *Handle) do(f func(c Cursor)) (bool, error) { +func (h handleAdapter) do(f func(c Cursor)) (bool, error) { if unlock, err := h.Lock(); err != nil { return false, err } else { @@ -85,15 +88,13 @@ func (h *Handle) do(f func(c Cursor)) (bool, error) { /* these compatibility methods must only be called while fileMu is held */ -func (h *Handle) Save(state *hst.State) error { - return (&EntryHandle{nil, h.Path.Append(state.ID.String()), state.ID}).Save(state) -} +func (h handleAdapter) Save(state *hst.State) error { _, err := h.Handle.Save(state); return err } -func (h *Handle) Destroy(id hst.ID) error { +func (h handleAdapter) Destroy(id hst.ID) error { return (&EntryHandle{nil, h.Path.Append(id.String()), id}).Destroy() } -func (h *Handle) Load() (map[hst.ID]*hst.State, error) { +func (h handleAdapter) Load() (map[hst.ID]*hst.State, error) { entries, n, err := h.Entries() if err != nil { return nil, err @@ -114,7 +115,7 @@ func (h *Handle) Load() (map[hst.ID]*hst.State, error) { return r, err } -func (h *Handle) Len() (int, error) { +func (h handleAdapter) Len() (int, error) { entries, _, err := h.Entries() if err != nil { return -1, err diff --git a/internal/store/segment.go b/internal/store/segment.go index 3885959..79f652e 100644 --- a/internal/store/segment.go +++ b/internal/store/segment.go @@ -54,11 +54,11 @@ func (eh *EntryHandle) Destroy() error { return nil } -// Save encodes [hst.State] and writes it to the underlying file. +// save encodes [hst.State] and writes it to the underlying file. // An error is returned if a file already exists with the same identifier. -// Save does not validate the embedded [hst.Config]. -// A non-nil error returned by Save is of type [hst.AppError]. -func (eh *EntryHandle) Save(state *hst.State) error { +// save does not validate the embedded [hst.Config]. +// A non-nil error returned by save is of type [hst.AppError]. +func (eh *EntryHandle) save(state *hst.State) error { f, err := eh.open(os.O_RDWR|os.O_CREATE|os.O_EXCL, 0600) if err != nil { return err @@ -123,6 +123,16 @@ func (h *Handle) Lock() (unlock func(), err error) { return } +// Save attempts to save [hst.State] as a segment entry, and returns its [EntryHandle]. +// Must be called while holding [Handle.Lock]. +// An error is returned if an entry already exists with the same identifier. +// Save does not validate the embedded [hst.Config]. +// A non-nil error returned by Save is of type [hst.AppError]. +func (h *Handle) Save(state *hst.State) (*EntryHandle, error) { + eh := EntryHandle{nil, h.Path.Append(state.ID.String()), state.ID} + return &eh, eh.save(state) +} + // Entries returns an iterator over all [EntryHandle] held in this segment. // Must be called while holding [Handle.Lock]. // A non-nil error attached to a [EntryHandle] indicates a malformed identifier and is of type [hst.AppError]. diff --git a/internal/store/segment_test.go b/internal/store/segment_test.go index 4751c01..dbd1acd 100644 --- a/internal/store/segment_test.go +++ b/internal/store/segment_test.go @@ -30,6 +30,9 @@ func newHandle(base *check.Absolute, identity int) *store.Handle //go:linkname open hakurei.app/internal/store.(*EntryHandle).open func open(eh *store.EntryHandle, flag int, perm os.FileMode) (*os.File, error) +//go:linkname save hakurei.app/internal/store.(*EntryHandle).save +func save(eh *store.EntryHandle, state *hst.State) error + func TestStateEntryHandle(t *testing.T) { t.Parallel() @@ -44,7 +47,7 @@ func TestStateEntryHandle(t *testing.T) { if err := eh.Destroy(); !reflect.DeepEqual(err, wantErr()) { t.Errorf("destroy: error = %v, want %v", err, wantErr()) } - if err := eh.Save(nil); !reflect.DeepEqual(err, wantErr()) { + if err := save(&eh, nil); !reflect.DeepEqual(err, wantErr()) { t.Errorf("save: error = %v, want %v", err, wantErr()) } if _, err := eh.Load(nil); !reflect.DeepEqual(err, wantErr()) { @@ -90,7 +93,7 @@ func TestStateEntryHandle(t *testing.T) { eh := store.EntryHandle{Pathname: check.MustAbs(t.TempDir()).Append("entry"), ID: newTemplateState().ID} - if err := eh.Save(newTemplateState()); err != nil { + if err := save(&eh, newTemplateState()); err != nil { t.Fatalf("save: error = %v", err) }