@@ -6,10 +6,10 @@ import (
66 "fmt"
77 "net/http"
88 "net/url"
9- "strconv"
109 "strings"
1110 "sync"
1211
12+ "github.com/google/uuid"
1313 "github.com/pkg/errors"
1414 "github.com/regiohelden/innovazammad/config"
1515 "github.com/regiohelden/innovazammad/innovaphone"
@@ -47,10 +47,16 @@ func (s State) MarshalJSON() ([]byte, error) {
4747 return []byte (fmt .Sprintf ("\" %s\" " , s .String ())), nil
4848}
4949
50- type callStateMap map [int ]State
50+ type callEntry struct {
51+ State State
52+ UUID uuid.UUID
53+ sync.Mutex
54+ }
55+
56+ type callMap map [int ]* callEntry
5157
5258// String is used by expvar
53- func (cs callStateMap ) String () string {
59+ func (cs callMap ) String () string {
5460 out , err := json .Marshal (cs )
5561 if err != nil {
5662 return err .Error ()
@@ -60,7 +66,7 @@ func (cs callStateMap) String() string {
6066
6167// Session keeps track of the states successfully submitted to Zammad
6268type Session struct {
63- callStateMap callStateMap
69+ callMap callMap
6470 sync.Mutex
6571}
6672
@@ -73,52 +79,67 @@ type stateTransition struct {
7379// The result should be used as a singleton.
7480func NewSession () * Session {
7581 calls := & Session {
76- callStateMap : callStateMap {},
82+ callMap : callMap {},
7783 }
78- expvar .Publish ("Session" , calls .callStateMap )
84+ expvar .Publish ("Session" , calls .callMap )
7985 return calls
8086}
8187
8288// Get returns a State for the given callID. If no state is known, will return StateNew.
83- func (zs * Session ) get (callID int ) State {
84- if _ , ok := zs .callStateMap [callID ]; ! ok {
85- return StateNew
89+ func (zs * Session ) get (callID int ) (* callEntry , error ) {
90+ zs .Lock ()
91+ defer zs .Unlock ()
92+ if _ , ok := zs .callMap [callID ]; ! ok {
93+ UUID , err := uuid .NewRandom ()
94+ if err != nil {
95+ return nil , err
96+ }
97+ return & callEntry {
98+ State : StateNew ,
99+ UUID : UUID ,
100+ }, nil
86101 }
87- return zs .callStateMap [callID ]
102+ return zs .callMap [callID ], nil
88103}
89104
90- func (zs * Session ) update (callID int , state State ) {
91- if state == StateDisconnected {
92- delete (zs .callStateMap , callID )
105+ func (zs * Session ) setOrUpdate (callID int , newEntry * callEntry ) {
106+ if newEntry .State == StateDisconnected {
107+ zs .Lock ()
108+ defer zs .Unlock ()
109+ delete (zs .callMap , callID )
110+ } else if curEntry , ok := zs .callMap [callID ]; ok {
111+ curEntry .State = newEntry .State
93112 } else {
94- zs .callStateMap [callID ] = state
113+ zs .callMap [callID ] = newEntry
95114 }
96115}
97116
98117// Submit sends a call to Zammad, if we are aware of it and can correctly map its state to some known Zammad state
99118func (zs * Session ) Submit (call * innovaphone.CallInSession ) error {
100- zs .Lock ()
101- defer zs .Unlock ()
102-
103119 src , dst , err := call .GetSourceAndDestination ()
104120 if err != nil {
105121 return err
106122 }
107123 dir := call .GetDirection ()
108124 newState := call .GetState ()
109125
110- curState := zs .get (call .Call )
111- var setState State // used to only update our state if the call was submitted
126+ entry , err := zs .get (call .Call )
127+ if err != nil {
128+ return err
129+ }
130+ // ensure we serialize access to one specific call
131+ entry .Lock ()
132+ defer entry .Unlock ()
112133
113134 // only skip new calls, because a call-forwarding may send the call outside of the filtered group, meaning we have to
114135 // handle calls we would otherwise filter out based on involved numbers
115- if curState == StateNew && ! call .ShouldHandle () {
136+ if entry . State == StateNew && ! call .ShouldHandle () {
116137 logrus .WithField ("call" , call ).Debugf ("skipping call for number not in group '%s'" , config .Global .PBX .FilterOnGroup )
117138 return nil
118139 }
119140
120141 content := url.Values {
121- "callId" : []string {strconv . Itoa ( call . Call )},
142+ "callId" : []string {entry . UUID . String ( )},
122143 "from" : []string {normalizeNumber (src .E164 )},
123144 "to" : []string {normalizeNumber (dst .E164 )},
124145 "direction" : []string {dir .String ()},
@@ -132,38 +153,38 @@ func (zs *Session) Submit(call *innovaphone.CallInSession) error {
132153 user = src .Cn
133154 }
134155
135- transition := stateTransition {curState : curState , newState : newState }
156+ transition := stateTransition {curState : entry . State , newState : newState }
136157 switch transition {
137158 case
138159 stateTransition {StateNew , innovaphone .StateCallProc },
139160 stateTransition {StateNew , innovaphone .StateAlert }:
140161 // we do not get StateAlert on outgoing calls, so we have to assume StateCallProc is enough
141162 content .Set ("event" , "newCall" )
142163 content .Set ("user" , user )
143- setState = StateRinging
164+ entry . State = StateRinging
144165 case stateTransition {StateRinging , innovaphone .StateConnect }:
145166 content .Set ("event" , "answer" )
146167 if dir == innovaphone .DirectionInbound {
147168 content .Set ("user" , user )
148169 content .Set ("answeringNumber" , normalizeNumber (dst .E164 ))
149170 }
150- setState = StateConnected
171+ entry . State = StateConnected
151172 case stateTransition {StateRinging , innovaphone .StateDisconnectSent }:
152173 content .Set ("event" , "hangup" )
153174 content .Set ("cause" , "cancel" )
154- setState = StateDisconnected
175+ entry . State = StateDisconnected
155176 case stateTransition {StateRinging , innovaphone .StateDisconnectReceived }:
156177 content .Set ("event" , "hangup" )
157178 content .Set ("cause" , "noAnswer" )
158- setState = StateDisconnected
179+ entry . State = StateDisconnected
159180 case
160181 stateTransition {StateConnected , innovaphone .StateDisconnectSent },
161182 stateTransition {StateConnected , innovaphone .StateDisconnectReceived }:
162183 content .Set ("event" , "hangup" )
163184 content .Set ("cause" , "normalClearing" )
164- setState = StateDisconnected
185+ entry . State = StateDisconnected
165186 default :
166- logrus .WithField ("call" , call ).Debugf ("ignoring unknown state transition %s → %s" , curState , newState )
187+ logrus .WithField ("call" , call ).Debugf ("ignoring unknown state transition %s → %s" , transition . curState , transition . newState )
167188 return nil
168189 }
169190
@@ -180,7 +201,7 @@ func (zs *Session) Submit(call *innovaphone.CallInSession) error {
180201 if resp .StatusCode < 200 || resp .StatusCode > 299 {
181202 return fmt .Errorf ("could not submit to Zammad: %s" , resp .Status )
182203 }
183- zs .update (call .Call , setState )
204+ zs .setOrUpdate (call .Call , entry )
184205 return nil
185206}
186207
0 commit comments