77 lines
3.3 KiB
Go
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
|
|
}
|