| skipped 17 lines |
18 | 18 | | package tigase.halcyon.core.xmpp.modules.jingle |
19 | 19 | | |
20 | 20 | | import tigase.halcyon.core.AbstractHalcyon |
| 21 | + | import tigase.halcyon.core.Context |
21 | 22 | | import tigase.halcyon.core.eventbus.Event |
22 | 23 | | import tigase.halcyon.core.eventbus.EventDefinition |
23 | 24 | | import tigase.halcyon.core.eventbus.handler |
24 | 25 | | import tigase.halcyon.core.logger.LoggerFactory |
25 | | - | import tigase.halcyon.core.xmpp.BareJID |
26 | | - | import tigase.halcyon.core.xmpp.JID |
27 | | - | import tigase.halcyon.core.xmpp.bareJID |
| 26 | + | import tigase.halcyon.core.utils.Lock |
| 27 | + | import tigase.halcyon.core.xmpp.* |
28 | 28 | | import tigase.halcyon.core.xmpp.modules.presence.ContactChangeStatusEvent |
29 | | - | import tigase.halcyon.core.xmpp.resource |
30 | 29 | | import tigase.halcyon.core.xmpp.stanzas.PresenceType |
31 | 30 | | |
32 | 31 | | abstract class AbstractJingleSessionManager<S : AbstractJingleSession>( |
33 | | - | name: String, private val sessionFactory: SessionFactory<S>, |
| 32 | + | name: String |
34 | 33 | | ) : Jingle.SessionManager { |
| 34 | + | |
| 35 | + | abstract fun createSession(context: Context, jid: JID, sid: String, role: Content.Creator, initiationType: InitiationType): S |
| 36 | + | abstract fun reportIncomingCall(session: S, media: List<Media>) |
35 | 37 | | |
36 | 38 | | private val log = LoggerFactory.logger(name) |
37 | 39 | | |
38 | 40 | | protected var sessions: List<S> = emptyList() |
| 41 | + | private val lock = Lock(); |
39 | 42 | | |
40 | | - | private val jingleEventHandler = JingleEvent.handler { event -> |
41 | | - | when (event.action) { |
42 | | - | Action.SessionInitiate -> sessionInitiated(event) |
43 | | - | Action.SessionAccept -> sessionAccepted(event) |
44 | | - | Action.TransportInfo -> transportInfo(event) |
45 | | - | Action.SessionTerminate -> sessionTerminated(event) |
46 | | - | else -> log.warning { "unsupported event: " + event.action.name } |
47 | | - | } |
48 | | - | } |
49 | 43 | | private val contactChangeStatusEventHandler = handler<ContactChangeStatusEvent> { event -> |
50 | 44 | | if (event.lastReceivedPresence.type == PresenceType.Unavailable) { |
51 | 45 | | val toClose = |
52 | 46 | | sessions.filter { it.jid == event.presence.from && it.account == event.context.boundJID?.bareJID } |
53 | 47 | | toClose.forEach { it.terminate(TerminateReason.Success) } |
54 | | - | } |
55 | | - | } |
56 | | - | private val jingleMessageInitiationEvent = JingleMessageInitiationEvent.handler { event -> |
57 | | - | val account = event.context.boundJID!!.bareJID |
58 | | - | when (event.action) { |
59 | | - | is MessageInitiationAction.Propose -> { |
60 | | - | if (session(account, event.jid, event.action.id) == null) { |
61 | | - | val session = open( |
62 | | - | event.context.getModule(JingleModule.TYPE), |
63 | | - | account, |
64 | | - | event.jid, |
65 | | - | event.action.id, |
66 | | - | Content.Creator.Responder, |
67 | | - | InitiationType.Message |
68 | | - | ) |
69 | | - | val media = event.action.descriptions.filter { isDesciptionSupported(it) }.map { it.media } |
70 | | - | fireIncomingSessionEvent(event.context, session, media) |
71 | | - | } |
72 | | - | } |
73 | | - | |
74 | | - | is MessageInitiationAction.Retract -> sessionTerminated(account, event.jid, event.action.id) |
75 | | - | is MessageInitiationAction.Accept -> sessionTerminated(account, event.action.id) |
76 | | - | is MessageInitiationAction.Reject -> sessionTerminated(account, event.jid, event.action.id) |
77 | | - | is MessageInitiationAction.Proceed -> session(account, event.jid, event.action.id)?.accepted(event.jid) |
78 | 48 | | } |
79 | 49 | | } |
80 | 50 | | |
81 | | - | abstract fun isDesciptionSupported(descrition: MessageInitiationDescription): Boolean |
| 51 | + | abstract fun isDescriptionSupported(descrition: MessageInitiationDescription): Boolean |
82 | 52 | | |
83 | 53 | | fun register(halcyon: AbstractHalcyon) { |
84 | | - | halcyon.eventBus.register(JingleEvent, this.jingleEventHandler) |
85 | 54 | | halcyon.eventBus.register(ContactChangeStatusEvent, this.contactChangeStatusEventHandler) |
86 | | - | halcyon.eventBus.register(JingleMessageInitiationEvent, this.jingleMessageInitiationEvent) |
87 | 55 | | } |
88 | 56 | | |
89 | 57 | | fun unregister(halcyon: AbstractHalcyon) { |
90 | | - | halcyon.eventBus.unregister(JingleEvent, this.jingleEventHandler) |
91 | 58 | | halcyon.eventBus.unregister(ContactChangeStatusEvent, this.contactChangeStatusEventHandler) |
92 | | - | halcyon.eventBus.unregister(JingleMessageInitiationEvent, this.jingleMessageInitiationEvent) |
93 | 59 | | } |
94 | 60 | | |
95 | | - | override fun activateSessionSid(account: BareJID, with: JID): String? { |
96 | | - | return session(account, with, null)?.sid |
| 61 | + | fun session(context: Context, jid: JID, sid: String?): S? { |
| 62 | + | return context.boundJID?.bareJID?.let { account -> |
| 63 | + | session(account, jid, sid); |
| 64 | + | } |
97 | 65 | | } |
98 | 66 | | |
99 | 67 | | fun session(account: BareJID, jid: JID, sid: String?): S? = |
100 | | - | sessions.firstOrNull { it.account == account && (sid == null || it.sid == sid) && (it.jid == jid || (it.jid.resource == null && it.jid.bareJID == jid.bareJID)) } |
| 68 | + | lock.withLock { |
| 69 | + | sessions.firstOrNull { it.account == account && (sid == null || it.sid == sid) && (it.jid == jid || (it.jid.resource == null && it.jid.bareJID == jid.bareJID)) } |
| 70 | + | } |
101 | 71 | | |
102 | 72 | | fun open( |
103 | | - | jingleModule: JingleModule, |
104 | | - | account: BareJID, |
| 73 | + | context: Context, |
105 | 74 | | jid: JID, |
106 | 75 | | sid: String, |
107 | 76 | | role: Content.Creator, |
108 | 77 | | initiationType: InitiationType, |
109 | 78 | | ): S { |
110 | | - | val session = sessionFactory.createSession(this, jingleModule, account, jid, sid, role, initiationType) |
111 | | - | sessions = sessions + session |
112 | | - | return session |
| 79 | + | return lock.withLock { |
| 80 | + | val session = this.createSession(context, jid, sid, role, initiationType); |
| 81 | + | sessions = sessions + session |
| 82 | + | return@withLock session |
| 83 | + | } |
113 | 84 | | } |
114 | 85 | | |
115 | | - | fun close(account: BareJID, jid: JID, sid: String): S? = session(account, jid, sid)?.let { session -> |
116 | | - | sessions = sessions - session |
117 | | - | return session |
| 86 | + | fun close(account: BareJID, jid: JID, sid: String): S? = lock.withLock { |
| 87 | + | return@withLock session(account, jid, sid)?.let { session -> |
| 88 | + | sessions = sessions - session |
| 89 | + | return@let session |
| 90 | + | } |
118 | 91 | | } |
119 | 92 | | |
120 | | - | fun close(session: S) { |
| 93 | + | fun close(session: AbstractJingleSession) { |
121 | 94 | | close(session.account, session.jid, session.sid) |
122 | 95 | | } |
123 | 96 | | |
124 | | - | protected fun sessionInitiated(event: JingleEvent) { |
125 | | - | val account = event.context.boundJID!!.bareJID |
126 | | - | session(account, event.jid, event.sid)?.accepted(event.contents, event.bundle) ?: run { |
127 | | - | val session = open( |
128 | | - | event.context.getModule(JingleModule.TYPE), |
129 | | - | account, |
130 | | - | event.jid, |
131 | | - | event.sid, |
132 | | - | Content.Creator.Responder, |
133 | | - | InitiationType.Iq |
134 | | - | ) |
135 | | - | session.initiated(event.contents, event.bundle) |
136 | | - | fireIncomingSessionEvent(event.context, |
137 | | - | session, |
138 | | - | event.contents.map { it.description?.media }.filterNotNull()) |
139 | | - | } |
| 97 | + | enum class ContentType { |
| 98 | + | audio, |
| 99 | + | video, |
| 100 | + | filetransfer |
| 101 | + | } |
| 102 | + | |
| 103 | + | enum class Media { |
| 104 | + | audio, |
| 105 | + | video |
140 | 106 | | } |
141 | 107 | | |
142 | | - | protected fun sessionAccepted(event: JingleEvent) { |
143 | | - | val account = event.context.boundJID!!.bareJID |
144 | | - | session(account, event.jid, event.sid)?.accepted(event.contents, event.bundle) |
| 108 | + | override fun messageInitiation(context: Context, fromJid: JID, action: MessageInitiationAction) { |
| 109 | + | when (action) { |
| 110 | + | is MessageInitiationAction.Propose -> { |
| 111 | + | if (this.session(context, fromJid, action.id) != null) { |
| 112 | + | return; |
| 113 | + | } |
| 114 | + | val session = open(context, fromJid, action.id, Content.Creator.responder, InitiationType.Message); |
| 115 | + | val media = action.descriptions.map { Media.valueOf(it.media) }; |
| 116 | + | reportIncomingCall(session, media); |
| 117 | + | } |
| 118 | + | is MessageInitiationAction.Retract -> sessionTerminated(context, fromJid, action.id); |
| 119 | + | is MessageInitiationAction.Accept, is MessageInitiationAction.Reject -> sessionTerminated(context.boundJID!!.bareJID, action.id); |
| 120 | + | is MessageInitiationAction.Proceed -> { |
| 121 | + | val session = session(context, fromJid, action.id) ?: return; |
| 122 | + | session.accepted(fromJid); |
| 123 | + | } |
| 124 | + | } |
145 | 125 | | } |
146 | 126 | | |
147 | | - | protected fun sessionTerminated(event: JingleEvent) { |
148 | | - | val account = event.context.boundJID!!.bareJID |
149 | | - | sessionTerminated(account, event.jid, event.sid) |
| 127 | + | override fun sessionInitiated(context: Context, jid: JID, sid: String, contents: List<Content>, bundle: List<String>?) { |
| 128 | + | val sdp = SDP(contents, bundle ?: emptyList()); |
| 129 | + | val media = sdp.contents.map { it.description?.media?.let { Media.valueOf(it)} }.filterNotNull() |
| 130 | + | |
| 131 | + | session(context, jid, sid)?.let { session -> session.initiated(contents, bundle) } ?: { |
| 132 | + | val session = open(context, jid, sid, Content.Creator.responder, InitiationType.Iq); |
| 133 | + | session.initiated(contents, bundle) |
| 134 | + | reportIncomingCall(session, media); |
| 135 | + | } |
150 | 136 | | } |
151 | 137 | | |
152 | | - | protected fun sessionTerminated(account: BareJID, sid: String) { |
153 | | - | val toTerminate = sessions.filter { it.account == account && it.sid == sid } |
154 | | - | toTerminate.forEach { it.terminated() } |
| 138 | + | @Throws(XMPPException::class) |
| 139 | + | override fun sessionAccepted( |
| 140 | + | context: Context, |
| 141 | + | jid: JID, |
| 142 | + | sid: String, |
| 143 | + | contents: List<Content>, |
| 144 | + | bundle: List<String>? |
| 145 | + | ) { |
| 146 | + | val session = session(context, jid, sid) ?: throw XMPPException(ErrorCondition.ItemNotFound); |
155 | 147 | | } |
156 | 148 | | |
157 | | - | protected fun sessionTerminated(account: BareJID, jid: JID, sid: String) { |
158 | | - | session(account, jid, sid)?.terminated() |
| 149 | + | override fun sessionTerminated(context: Context, jid: JID, sid: String) { |
| 150 | + | session(context, jid, sid)?.terminated() |
159 | 151 | | } |
160 | 152 | | |
161 | | - | protected fun transportInfo(event: JingleEvent) { |
162 | | - | val account = event.context.boundJID!!.bareJID |
163 | | - | session(account, event.jid, event.sid)?.let { session -> |
164 | | - | for (content in event.contents) { |
165 | | - | content.transports.flatMap { it.candidates }.forEach { session.addCandidate(it, content.name) } |
166 | | - | } |
| 153 | + | fun sessionTerminated(account: BareJID, sid: String) { |
| 154 | + | val toTerminate = lock.withLock { |
| 155 | + | return@withLock sessions.filter { it.account == account && it.sid == sid } |
167 | 156 | | } |
| 157 | + | toTerminate.forEach { it.terminated() } |
168 | 158 | | } |
169 | 159 | | |
170 | | - | interface SessionFactory<S : AbstractJingleSession> { |
171 | | - | |
172 | | - | fun createSession( |
173 | | - | jingleSessionManager: AbstractJingleSessionManager<S>, |
174 | | - | jingleModule: JingleModule, |
175 | | - | account: BareJID, |
176 | | - | jid: JID, |
177 | | - | sid: String, |
178 | | - | role: Content.Creator, |
179 | | - | initiationType: InitiationType, |
180 | | - | ): S |
| 160 | + | @Throws(XMPPException::class) |
| 161 | + | override fun transportInfo(context: Context, jid: JID, sid: String, contents: List<Content>) { |
| 162 | + | val session = session(context, jid, sid) ?: throw XMPPException(ErrorCondition.ItemNotFound); |
| 163 | + | for (content in contents) { |
| 164 | + | content.transports.flatMap { it.candidates }.forEach { session.addCandidate(it, content.name) } |
| 165 | + | } |
181 | 166 | | } |
182 | 167 | | |
183 | 168 | | protected fun fireIncomingSessionEvent(context: AbstractHalcyon, session: S, media: List<String>) { |
| skipped 12 lines |