Files
go-sip/internal/agent/recording_delivery_recover.go
T

77 lines
3.3 KiB
Go

package agent
import (
"context"
"encoding/json"
"errors"
agentpb "git.ipao.vip/rogee/go-sip/gen/agent"
)
// RetryRecovered resumes one privately persisted failed upload. The original
// destination, asset digest, upload identity and final result remain fixed;
// only the short-lived grant may be reissued. Unknown PUTs stay blocked.
func (d *RecordingDelivery) RetryRecovered(ctx context.Context, bucket, objectKey string) (RecordingRecoveryEntry, error) {
if d == nil || d.Recovery == nil {
return RecordingRecoveryEntry{}, ErrRecordingRecoveryIncomplete
}
d.mu.Lock()
defer d.mu.Unlock()
d.attempted = true
d.ended = true // SaveFailure follows an acknowledged ReportCallEnded.
return d.Recovery.Retry(ctx, bucket, objectKey, d.reissueRecordingGrant, d.reportRecoveredResult)
}
func (d *RecordingDelivery) sameOriginalCall(entry RecordingRecoveryEntry) bool {
return d.Call.SourceEventID != "" && entry.SourceEventID == d.Call.SourceEventID && entry.CallID == entry.SourceEventID
}
func (d *RecordingDelivery) reissueRecordingGrant(ctx context.Context, entry RecordingRecoveryEntry) (RecoveryTarget, error) {
if !d.sameOriginalCall(entry) {
return RecoveryTarget{}, ErrRecordingRecoveryIncomplete
}
var original struct {
Recording struct {
Status string `json:"status"`
Bucket string `json:"bucket"`
ObjectKey string `json:"object_key"`
Format string `json:"format"`
Channels int `json:"channels"`
SampleRateHz int `json:"sample_rate_hz"`
DurationMS int64 `json:"duration_ms"`
SizeBytes int64 `json:"size_bytes"`
ChecksumSHA256 string `json:"checksum_sha256"`
} `json:"recording"`
}
if err := json.Unmarshal(entry.ResultPayload, &original); err != nil {
return RecoveryTarget{}, errors.Join(ErrRecordingRecoveryIncomplete, err)
}
r := original.Recording
if r.Status != "uploaded" || r.Bucket != entry.Bucket || r.ObjectKey != entry.ObjectKey || r.Format != "wav" || r.Channels != 1 || r.SampleRateHz != 16000 || r.DurationMS < 0 || r.SizeBytes != entry.SizeBytes || r.ChecksumSHA256 != entry.SHA256 {
return RecoveryTarget{}, ErrRecordingRecoveryIncomplete
}
asset := &agentpb.AssetDescriptor{
Kind: agentpb.AssetKind_ASSET_KIND_RECORDING, AssetId: entry.RecordingID,
CallId: entry.CallID, ExecutionId: entry.SourceEventID,
Format: r.Format, Channels: int32(r.Channels), SampleRateHz: int32(r.SampleRateHz),
DurationMs: r.DurationMS, SizeBytes: r.SizeBytes, ChecksumSha256: r.ChecksumSHA256,
}
grant, err := d.Call.RequestUpload(ctx, asset, entry.UploadID)
if err != nil {
return RecoveryTarget{}, err
}
return RecoveryTarget{Bucket: grant.Bucket, Grant: grant}, nil
}
func (d *RecordingDelivery) reportRecoveredResult(ctx context.Context, entry RecordingRecoveryEntry) error {
if !d.sameOriginalCall(entry) || entry.Uploaded == nil || entry.Uploaded.StatusCode < 200 || entry.Uploaded.StatusCode >= 300 || entry.Uploaded.SizeBytes != entry.SizeBytes || entry.Uploaded.SHA256 != entry.SHA256 {
return ErrRecordingRecoveryIncomplete
}
_, err := d.Call.ReportFinal(ctx, entry.ResultPayload, &agentpb.UploadObservation{
UploadId: entry.UploadID, RecordingId: entry.RecordingID,
PutStatusCode: int32(entry.Uploaded.StatusCode), SizeBytes: entry.Uploaded.SizeBytes,
ChecksumSha256: entry.Uploaded.SHA256,
})
return err
}