Files

179 lines
4.6 KiB
Go

package docrevision
import (
"context"
"fmt"
)
func (s *Service) GetRevisionState(ctx context.Context, docID string) (*RevisionState, error) {
if err := requireDocID(docID); err != nil {
return nil, err
}
// T20 B3: doc 级锁 → 全局内存锁;hydrate 的 PG RTT 只阻塞同文档。
g := s.lockDoc(docID)
defer g.Release()
s.mu.Lock()
defer s.mu.Unlock()
if err := s.hydrateDocLocked(ctx, docID); err != nil {
return nil, err
}
root, ok := s.roots[docID]
if !ok || root == nil || root.LedgerVersionID == nil || root.HeadSnapshotID == nil {
return nil, ErrNotFound
}
ver := s.versions[*root.LedgerVersionID]
head := s.snapshots[*root.HeadSnapshotID]
if ver == nil || head == nil {
return nil, ErrNotFound
}
caps := []string{"read"}
if root.LifecycleState == "drafting" {
caps = append(caps, "edit")
}
if s.DiffUI {
caps = append(caps, "diff", "overlay", "history")
}
st := &RevisionState{
DocID: docID,
CurrentVersionNo: ver.VersionNo,
VersionStatus: ver.Status,
BaselineSnapshotID: ver.BaselineSnapshotID,
HeadSnapshotID: ver.HeadSnapshotID,
HeadRevisionNo: root.HeadRevisionNo,
HeadSHA256: head.ContentSHA256,
ManualChangeCount: ver.HumanRevisionCount,
Capabilities: caps,
ETag: fmt.Sprintf("%d:%d", ver.HeadSnapshotID, root.HeadRevisionNo),
HeadBlockMap: append([]BlockNode(nil), head.BlockMap...),
}
s.attachPendingCandidateLocked(st, docID)
return st, nil
}
func (s *Service) ListVersions(ctx context.Context, docID string) ([]Version, error) {
if err := requireDocID(docID); err != nil {
return nil, err
}
g := s.lockDoc(docID)
defer g.Release()
s.mu.Lock()
defer s.mu.Unlock()
if err := s.hydrateDocLocked(ctx, docID); err != nil {
return nil, err
}
var out []Version
for _, v := range s.versions {
if v.DocID == docID {
out = append(out, *v)
}
}
for i := 0; i < len(out); i++ {
for j := i + 1; j < len(out); j++ {
if out[j].VersionNo < out[i].VersionNo {
out[i], out[j] = out[j], out[i]
}
}
}
return out, nil
}
func (s *Service) GetVersionContent(ctx context.Context, docID string, versionNo int, view string) (string, string, error) {
if err := requireDocID(docID); err != nil {
return "", "", err
}
g := s.lockDoc(docID)
defer g.Release()
s.mu.Lock()
defer s.mu.Unlock()
if err := s.hydrateDocLocked(ctx, docID); err != nil {
return "", "", err
}
var ver *Version
for _, v := range s.versions {
if v.DocID == docID && v.VersionNo == versionNo {
ver = v
break
}
}
if ver == nil {
return "", "", ErrNotFound
}
sid := ver.HeadSnapshotID
if view == "baseline" {
sid = ver.BaselineSnapshotID
}
snap := s.snapshots[sid]
if snap == nil || s.blobs[snap.ContentSHA256] == nil {
return "", "", ErrNotFound
}
return s.blobs[snap.ContentSHA256].ContentMarkdown, snap.ContentSHA256, nil
}
func (s *Service) DiffVersions(ctx context.Context, docID string, fromNo, toNo int, fromView, toView string) (*DiffResult, error) {
fromMD, _, err := s.GetVersionContent(ctx, docID, fromNo, fromView)
if err != nil {
return nil, err
}
toMD, _, err := s.GetVersionContent(ctx, docID, toNo, toView)
if err != nil {
return nil, err
}
fromBlocks := BuildBlockMap(fromMD, nil)
toBlocks := BuildBlockMap(toMD, fromBlocks)
d := ComputeDiff(fromMD, toMD, fromBlocks, toBlocks)
return &d, nil
}
func (s *Service) ManualOverlay(ctx context.Context, docID string) (*DiffResult, error) {
if err := requireDocID(docID); err != nil {
return nil, err
}
g := s.lockDoc(docID)
defer g.Release()
s.mu.Lock()
defer s.mu.Unlock()
if err := s.hydrateDocLocked(ctx, docID); err != nil {
return nil, err
}
d := s.manualOverlayLocked(docID)
return &d, nil
}
func (s *Service) GetCandidate(ctx context.Context, docID string, candidateID int64) (*AgentCandidate, error) {
if err := requireDocID(docID); err != nil {
return nil, err
}
g := s.lockDoc(docID)
defer g.Release()
s.mu.Lock()
defer s.mu.Unlock()
if err := s.hydrateDocLocked(ctx, docID); err != nil {
return nil, err
}
c, ok := s.candidates[candidateID]
if !ok || c.DocID != docID {
return nil, ErrNotFound
}
cp := *c
return &cp, nil
}
func (s *Service) GetChangeReport(ctx context.Context, docID string, reportID int64) (*ChangeReport, error) {
if err := requireDocID(docID); err != nil {
return nil, err
}
g := s.lockDoc(docID)
defer g.Release()
s.mu.Lock()
defer s.mu.Unlock()
if err := s.hydrateDocLocked(ctx, docID); err != nil {
return nil, err
}
r, ok := s.reports[reportID]
if !ok || r.DocID != docID {
return nil, ErrNotFound
}
cp := *r
return &cp, nil
}