Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 18 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,12 @@ Usage is pretty simple.

## Installation:

`go get github.com/ivahaev/amigo`
`go get github.com/intersvyaz/amigo`

## Using
Import module to your project:
```go
import "github.com/ivahaev/amigo"
import "github.com/intersvyaz/amigo"
```

Then use:
Expand All @@ -23,7 +23,7 @@ package main
import (
"fmt"

"github.com/ivahaev/amigo"
"github.com/intersvyaz/amigo"
)

// Creating hanlder functions
Expand Down Expand Up @@ -69,6 +69,21 @@ func main() {
// You need to catch them in event channel, DefaultHandler or specified HandlerFunction
fmt.Println(result, err)
}

// Use ActionKV when you need duplicate fields (e.g. multiple Variable)
if a.Connected() {
kv := []amigo.KV{
{Key: "Action", Value: "Originate"},
{Key: "Channel", Value: "SIP/100"},
{Key: "Exten", Value: "200"},
{Key: "Context", Value: "default"},
{Key: "Priority", Value: "1"},
{Key: "Variable", Value: "foo=1"},
{Key: "Variable", Value: "bar=2"},
}
result, err := a.ActionKV(kv)
fmt.Println(result, err)
}

ch := make(chan bool)
<-ch
Expand Down
36 changes: 31 additions & 5 deletions ami.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ type amiAdapter struct {
dialTimeout time.Duration

actionsChan chan map[string]string
actionsChanKV chan []KV
responseChans map[string]chan map[string]string
pingerChan chan struct{}
mutex *sync.RWMutex
Expand All @@ -60,6 +61,7 @@ func newAMIAdapter(s *Settings, eventEmitter func(string, string)) (*amiAdapter,
a.reconnect = true

a.actionsChan = make(chan map[string]string)
a.actionsChanKV = make(chan []KV)
a.responseChans = make(map[string]chan map[string]string)
a.eventsChan = make(chan map[string]string, 8192)
a.pingerChan = make(chan struct{})
Expand Down Expand Up @@ -216,6 +218,17 @@ func (a *amiAdapter) writer(conn net.Conn, stop <-chan struct{}, writeErrChan ch
writeErrChan <- err
return
}
case kv := <-a.actionsChanKV:
if !kvHasConnID(kv, a.id) {
// action sent before reconnect, need to be ignored
continue
}
data := serializeKV(kv)
_, err := conn.Write(data)
if err != nil {
writeErrChan <- err
return
}
}
}
}
Expand Down Expand Up @@ -340,17 +353,17 @@ func readMessage(r *bufio.Reader) (m map[string]string, err error) {
return m, err
}

// Append the current line to the complete line buffer
completeLine.Write(tmpkv)
// Append the current line to the complete line buffer
completeLine.Write(tmpkv)

if isprefix {
// If the line is a prefix, continue reading more
continue
}
}

// We have a complete line now
kv := completeLine.Bytes()
completeLine.Reset() // Reset the buffer for the next line
kv := completeLine.Bytes()
completeLine.Reset() // Reset the buffer for the next line

var key string
i := bytes.IndexByte(kv, ':')
Expand Down Expand Up @@ -447,3 +460,16 @@ func (a *amiAdapter) reader(conn net.Conn, stop <-chan struct{}, readErrChan cha
}
}
}

func serializeKV(kv []KV) []byte {
var outBuf bytes.Buffer

for _, p := range kv {
outBuf.WriteString(p.Key)
outBuf.WriteString(": ")
outBuf.WriteString(p.Value)
outBuf.WriteString("\r\n")
}
outBuf.WriteString("\r\n")
return outBuf.Bytes()
}
93 changes: 93 additions & 0 deletions amigo.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,11 @@ type agiCommand struct {
dateTime time.Time
}

type KV struct {
Key string
Value string
}

// New creates new Amigo struct with credentials provided and returns pointer to it
// Usage: New(username string, secret string, [host string, [port string]])
func New(settings *Settings) *Amigo {
Expand Down Expand Up @@ -105,6 +110,94 @@ func (a *Amigo) Action(action map[string]string) (map[string]string, error) {
return result, nil
}

// KV represents a single AMI field (allows duplicates).

// ActionKV executes AMI Action with duplicate fields support (e.g. multiple Variable:)
func (a *Amigo) ActionKV(kv []KV) (map[string]string, error) {
if !a.Connected() {
return nil, errNotConnected
}

a.mutex.Lock()
defer a.mutex.Unlock()
result := a.ami.execKV(kv)
if a.capitalizeProps {
e := map[string]string{}
for k, v := range result {
e[strings.ToUpper(k)] = v
}
return e, nil
}

if strings.ToLower(actionName(kv)) == "logoff" {
a.ami.reconnect = false
}
return result, nil
}

func actionName(kv []KV) string {
for _, p := range kv {
if p.Key == "Action" {
return p.Value
}
}
return ""
}

func (a *amiAdapter) execKV(kv []KV) map[string]string {
kv = append([]KV{}, kv...)
kv = append(kv, KV{Key: amigoConnIDKey, Value: a.id})
actionID := getKV(kv, "ActionID")
if actionID == "" {
actionID = uuid.NewV4()
kv = append(kv, KV{Key: "ActionID", Value: actionID})
}

resChan := make(chan map[string]string)
a.mutex.Lock()
a.responseChans[actionID] = resChan
a.mutex.Unlock()

a.actionsChanKV <- kv

time.AfterFunc(a.actionTimeout, func() {
a.mutex.RLock()
_, ok := a.responseChans[actionID]
a.mutex.RUnlock()
if ok {
a.mutex.Lock()
if ch, ok := a.responseChans[actionID]; ok {
delete(a.responseChans, actionID)
a.mutex.Unlock()
ch <- map[string]string{"Error": "Timeout"}
return
}
a.mutex.Unlock()
}
})

response := <-resChan
return response
}

func getKV(kv []KV, key string) string {
for _, p := range kv {
if p.Key == key {
return p.Value
}
}
return ""
}

func kvHasConnID(kv []KV, id string) bool {
for _, p := range kv {
if p.Key == amigoConnIDKey && p.Value == id {
return true
}
}
return false
}

// AgiAction used to execute Agi Actions in Asterisk. Returns full response.
// Usage amigo.AgiAction(channel, command string)
func (a *Amigo) AgiAction(channel, command string) (map[string]string, error) {
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
module github.com/ivahaev/amigo
module github.com/intersvyaz/amigo

go 1.13