140 lines
4.5 KiB
Go
140 lines
4.5 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"crypto/sha256"
|
|
"encoding/hex"
|
|
"errors"
|
|
"fmt"
|
|
"path/filepath"
|
|
"strings"
|
|
"time"
|
|
|
|
agentpb "git.ipao.vip/rogee/go-sip/gen/agent"
|
|
"git.ipao.vip/rogee/go-sip/internal/agent"
|
|
"git.ipao.vip/rogee/go-sip/internal/callruntime"
|
|
"git.ipao.vip/rogee/go-sip/internal/config"
|
|
"git.ipao.vip/rogee/go-sip/internal/rpc"
|
|
)
|
|
|
|
func uploadCallRecordings(ctx context.Context, cfg config.Config, result callruntime.Result) ([]map[string]any, error) {
|
|
if strings.TrimSpace(cfg.DispatcherGRPCEndpoint) == "" {
|
|
return nil, nil
|
|
}
|
|
for name, value := range map[string]string{
|
|
"AGENT_CALL_TENANT_ID": cfg.CallTenantID,
|
|
"AGENT_CALL_TENANT_KEY": cfg.CallTenantKey,
|
|
"AGENT_CALL_TASK_ID": cfg.CallTaskID,
|
|
"AGENT_CALL_TASK_ITEM_ID": cfg.CallTaskItemID,
|
|
} {
|
|
if strings.TrimSpace(value) == "" {
|
|
return nil, fmt.Errorf("%s is required when Dispatcher OSS upload is enabled", name)
|
|
}
|
|
}
|
|
if result.ChannelID == "" {
|
|
return nil, errors.New("call result has no channel ID for upload binding")
|
|
}
|
|
executionID := cfg.CallExecutionID
|
|
if executionID == "" {
|
|
executionID = result.ChannelID
|
|
}
|
|
binding := &agentpb.ExecutionBinding{
|
|
TenantId: cfg.CallTenantID,
|
|
TenantKey: cfg.CallTenantKey,
|
|
ExecutionId: executionID,
|
|
TaskId: cfg.CallTaskID,
|
|
TaskItemId: cfg.CallTaskItemID,
|
|
TaskRevision: 1,
|
|
CallId: result.ChannelID,
|
|
AttemptId: result.ChannelID,
|
|
AgentVersionId: cfg.Version,
|
|
}
|
|
client, err := rpc.DialFromFiles(cfg.DispatcherGRPCEndpoint, cfg.MTLSCAFile, cfg.MTLSCertFile, cfg.MTLSKeyFile, cfg.DispatcherGRPCServerName)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("dial Dispatcher gRPC service: %w", err)
|
|
}
|
|
defer client.Close()
|
|
spool, err := agent.NewSpool(cfg.SpoolRoot, time.Now)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
uploader := agent.UploadClient{Now: time.Now}
|
|
facts := append(append([]callruntime.RecordingFact(nil), result.InboundRecordings...), result.OutboundRecordings...)
|
|
if len(facts) == 0 {
|
|
return nil, errors.New("call produced no recordings for OSS upload")
|
|
}
|
|
uploaded := make([]map[string]any, 0, len(facts))
|
|
for index, recording := range facts {
|
|
if recording.Path == "" || recording.Bytes <= 0 || recording.SHA256 == "" {
|
|
return nil, fmt.Errorf("recording %d is missing path, size or checksum", index+1)
|
|
}
|
|
assetID := recordingAssetID(recording.Segment, recording.Path, index+1)
|
|
asset := &agentpb.AssetDescriptor{
|
|
Kind: agentpb.AssetKind_ASSET_KIND_RECORDING,
|
|
AssetId: assetID,
|
|
CallId: result.ChannelID,
|
|
ExecutionId: executionID,
|
|
Format: "wav",
|
|
SizeBytes: int64(recording.Bytes),
|
|
ChecksumSha256: recording.SHA256,
|
|
Channels: 1,
|
|
SampleRateHz: 16000,
|
|
DurationMs: recording.DurationMS,
|
|
}
|
|
record, err := uploadRecording(ctx, cfg, client, uploader, spool, binding, asset, recording.Path)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
uploaded = append(uploaded, map[string]any{
|
|
"upload_id": record.UploadID, "asset_id": asset.AssetId, "object_key": record.ObjectKey,
|
|
"bytes": record.Result.SizeBytes, "sha256": record.Result.SHA256,
|
|
"status_code": record.Result.StatusCode, "notification_state": record.State,
|
|
})
|
|
}
|
|
return uploaded, nil
|
|
}
|
|
|
|
func recordingAssetID(segment, path string, index int) string {
|
|
raw := segment
|
|
if raw == "" {
|
|
raw = fmt.Sprintf("segment-%d", index)
|
|
}
|
|
raw += "-" + filepath.Base(path)
|
|
var builder strings.Builder
|
|
for _, r := range raw {
|
|
if r == '/' || r == '\\' || r == ' ' || r == '\t' || r == '\n' || r == '\r' {
|
|
builder.WriteByte('-')
|
|
continue
|
|
}
|
|
builder.WriteRune(r)
|
|
}
|
|
assetID := strings.Trim(builder.String(), "-")
|
|
if assetID == "" {
|
|
assetID = fmt.Sprintf("segment-%d", index)
|
|
}
|
|
if len([]byte(assetID)) > 120 {
|
|
digest := sha256.Sum256([]byte(assetID))
|
|
assetID = "recording-" + hex.EncodeToString(digest[:16])
|
|
}
|
|
return assetID
|
|
}
|
|
|
|
func stableUploadID(binding *agentpb.ExecutionBinding, asset *agentpb.AssetDescriptor) string {
|
|
value := binding.ExecutionId + "\x00" + asset.AssetId + "\x00" + asset.ChecksumSha256
|
|
digest := sha256.Sum256([]byte(value))
|
|
return "upload-" + hex.EncodeToString(digest[:16])
|
|
}
|
|
|
|
func uploadMeta(cfg config.Config, phase, uploadID string) *agentpb.RequestMeta {
|
|
operationID := "recording-" + phase + "-" + uploadID
|
|
return &agentpb.RequestMeta{
|
|
ProtocolVersion: "agent.v1",
|
|
RequestId: operationID,
|
|
TraceId: operationID,
|
|
OperationId: operationID,
|
|
IdempotencyKey: operationID,
|
|
AgentId: cfg.AgentID,
|
|
CellId: cfg.CellID,
|
|
}
|
|
}
|