105 lines
3.2 KiB
Go
105 lines
3.2 KiB
Go
package sipcall
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"git.ipao.vip/rogee/go-sip/internal/asterisk"
|
|
"github.com/CyCoreSystems/ari/v5"
|
|
"golang.org/x/sys/unix"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"regexp"
|
|
"time"
|
|
)
|
|
|
|
type nativeHost struct {
|
|
config Config
|
|
client ari.Client
|
|
lock *os.File
|
|
}
|
|
|
|
var activeChannels = regexp.MustCompile(`(?m)^([0-9]+) active channels?\s*$`)
|
|
|
|
func (h *nativeHost) CheckIdle(ctx context.Context) error {
|
|
if h.lock == nil {
|
|
f, err := os.OpenFile(filepath.Join(h.config.ConfigDir, ".sip-call.lock"), os.O_CREATE|os.O_RDWR|unix.O_NOFOLLOW, 0600)
|
|
if err != nil {
|
|
return fmt.Errorf("open test lock: %w", err)
|
|
}
|
|
if err = unix.Flock(int(f.Fd()), unix.LOCK_EX|unix.LOCK_NB); err != nil {
|
|
return errors.Join(errors.New("another sip-call owns this Asterisk configuration"), f.Close())
|
|
}
|
|
h.lock = f
|
|
}
|
|
data, err := asteriskCLI(ctx, h.config, "core show channels count")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
m := activeChannels.FindSubmatch(data)
|
|
if len(m) != 2 || string(m[1]) != "0" {
|
|
return errors.New("Asterisk idle state unconfirmed or active channels exist; no configuration changes or dialing allowed")
|
|
}
|
|
if h.client == nil {
|
|
h.client, err = (asterisk.Loader{ConfigDir: h.config.ConfigDir}).OpenLineTestARI()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
apps, err := h.client.Application().List(nil)
|
|
if err != nil {
|
|
return fmt.Errorf("read native ARI applications: %w", err)
|
|
}
|
|
for _, app := range apps {
|
|
if app == nil || app.ID != "sip-call" {
|
|
return errors.New("another ARI application is active; stop business services before the isolated line test")
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
func (h *nativeHost) Apply(ctx context.Context, c Config) (func() error, error) {
|
|
h.config = c
|
|
return applyConfiguration(ctx, c, func(ctx context.Context, s string) ([]byte, error) { return asteriskCLI(ctx, c, s) })
|
|
}
|
|
func (h *nativeHost) Dial(ctx context.Context, request asterisk.NativeDial) (testCall, error) {
|
|
call, err := asterisk.DialLineTest(ctx, h.client, request)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &nativeTestCall{call}, nil
|
|
}
|
|
func (h *nativeHost) Close() error {
|
|
if h.client != nil {
|
|
h.client.Close()
|
|
}
|
|
if h.lock != nil {
|
|
return errors.Join(unix.Flock(int(h.lock.Fd()), unix.LOCK_UN), h.lock.Close())
|
|
}
|
|
return nil
|
|
}
|
|
|
|
type nativeTestCall struct{ *asterisk.NativeCall }
|
|
|
|
func (c *nativeTestCall) Done() context.Context { return c.Context }
|
|
|
|
func asteriskCLI(ctx context.Context, c Config, command string) ([]byte, error) {
|
|
bounded, cancel := context.WithTimeout(ctx, 5*time.Second)
|
|
defer cancel()
|
|
cmd := exec.CommandContext(bounded, c.Binary, "-C", filepath.Join(c.ConfigDir, "asterisk.conf"), "-rx", command)
|
|
cmd.Env = append(os.Environ(), "LD_LIBRARY_PATH="+c.LibraryDir)
|
|
out, err := cmd.CombinedOutput()
|
|
if c.resultDir != "" {
|
|
log, logErr := os.OpenFile(filepath.Join(c.resultDir, "asterisk-cli.log"), os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0600)
|
|
if logErr == nil {
|
|
_, logErr = fmt.Fprintf(log, "[%s] %s\n%s\n", time.Now().UTC().Format(time.RFC3339Nano), command, out)
|
|
logErr = errors.Join(logErr, log.Close())
|
|
}
|
|
err = errors.Join(err, logErr)
|
|
}
|
|
if err != nil {
|
|
return nil, fmt.Errorf("Asterisk command %q failed: %w", command, err)
|
|
}
|
|
return out, nil
|
|
}
|