12
12
package com.redhat.devtools.gateway
13
13
14
14
import com.intellij.openapi.application.ApplicationManager
15
- import com.intellij.openapi.application.EDT
16
- import com.intellij.openapi.application.ModalityState
17
15
import com.intellij.openapi.diagnostic.thisLogger
18
- import com.intellij.openapi.progress.ProcessCanceledException
19
16
import com.intellij.openapi.progress.ProgressIndicator
20
- import com.intellij.openapi.ui.DialogWrapper
21
- import com.intellij.openapi.ui.Messages
17
+ import com.intellij.openapi.progress.ProgressManager
18
+ import com.intellij.openapi.progress.Task
22
19
import com.intellij.ui.dsl.builder.Align.Companion.CENTER
23
20
import com.intellij.ui.dsl.builder.panel
24
- import com.intellij.util.ui.JBUI
25
21
import com.jetbrains.gateway.api.ConnectionRequestor
26
22
import com.jetbrains.gateway.api.GatewayConnectionHandle
27
23
import com.jetbrains.gateway.api.GatewayConnectionProvider
@@ -34,18 +30,11 @@ import com.redhat.devtools.gateway.util.messageWithoutPrefix
34
30
import com.redhat.devtools.gateway.view.ui.Dialogs
35
31
import io.kubernetes.client.openapi.ApiException
36
32
import kotlinx.coroutines.CompletableDeferred
37
- import kotlinx.coroutines.Dispatchers
38
- import kotlinx.coroutines.withContext
39
- import java.awt.BorderLayout
40
- import java.awt.Dimension
41
- import javax.swing.Action
42
- import javax.swing.Box
43
- import javax.swing.BoxLayout
33
+ import kotlinx.coroutines.ExperimentalCoroutinesApi
34
+ import kotlinx.coroutines.suspendCancellableCoroutine
44
35
import javax.swing.JComponent
45
- import javax.swing.JLabel
46
- import javax.swing.JPanel
47
- import javax.swing.JProgressBar
48
36
import javax.swing.Timer
37
+ import kotlin.coroutines.resume
49
38
50
39
private const val DW_NAMESPACE = " dwNamespace"
51
40
private const val DW_NAME = " dwName"
@@ -57,107 +46,118 @@ private const val DW_NAME = "dwName"
57
46
*/
58
47
class DevSpacesConnectionProvider : GatewayConnectionProvider {
59
48
49
+ @OptIn(ExperimentalCoroutinesApi ::class )
60
50
@Suppress(" UnstableApiUsage" )
61
51
override suspend fun connect (
62
52
parameters : Map <String , String >,
63
53
requestor : ConnectionRequestor
64
54
): GatewayConnectionHandle ? {
65
- val indicator = StartupProgressIndicator (" Connecting to Remote IDE..." )
66
- ApplicationManager .getApplication().invokeAndWait { indicator.show() }
67
-
68
- return withContext(Dispatchers .IO ) {
69
- try {
70
- indicator.setText(" Connecting to DevSpace..." )
71
- indicator.setIndeterminate(true )
72
-
73
- val handle = doConnect(parameters, indicator)
74
-
75
- val thinClient = handle.clientHandle
76
- ? : throw RuntimeException (" Failed to obtain ThinClientHandle" )
77
-
78
- indicator.setText(" Waiting for remote IDE to start..." )
79
- indicator.setText2(" Launching environment and initializing IDE window…" )
80
-
81
- // Observe signals on thinClient to detect ready/error
82
- val readyDeferred = CompletableDeferred <GatewayConnectionHandle ?>()
83
-
84
- thinClient.onClientPresenceChanged.advise(thinClient.lifetime,
85
- onClientPresenceChanged(readyDeferred, indicator, handle))
86
- thinClient.clientFailedToOpenProject.advise(thinClient.lifetime,
87
- onClientFailedToOpenProject(readyDeferred, indicator))
88
- thinClient.clientClosed.advise(thinClient.lifetime,
89
- onClientClosed(readyDeferred, indicator))
55
+ return suspendCancellableCoroutine { cont ->
56
+ ProgressManager .getInstance().runProcessWithProgressSynchronously(
57
+ {
58
+ val indicator = ProgressManager .getInstance().progressIndicator
59
+ try {
60
+ indicator.isIndeterminate = true
61
+ indicator.text = " Connecting to DevSpace..."
62
+
63
+ val handle = doConnect(parameters, indicator)
64
+ val thinClient =
65
+ handle.clientHandle ? : throw RuntimeException (" Failed to obtain ThinClientHandle" )
66
+
67
+ indicator.text = " Waiting for remote IDE to start..."
68
+
69
+ val ready = CompletableDeferred <GatewayConnectionHandle ?>()
70
+
71
+ thinClient.onClientPresenceChanged.advise(
72
+ thinClient.lifetime,
73
+ onClientPresenceChanged(ready, indicator, handle)
74
+ )
75
+ thinClient.clientFailedToOpenProject.advise(
76
+ thinClient.lifetime,
77
+ onClientFailedToOpenProject(ready, indicator)
78
+ )
79
+ thinClient.clientClosed.advise(
80
+ thinClient.lifetime,
81
+ onClientClosed(ready, indicator)
82
+ )
83
+ ready.invokeOnCompletion { error ->
84
+ if (error == null ) {
85
+ cont.resume(ready.getCompleted())
86
+ } else {
87
+ cont.resumeWith(Result .failure(error))
88
+ }
89
+ }
90
+ } catch (e: ApiException ) {
91
+ indicator.text = " Connection failed"
92
+ runDelayed(2000 , { indicator.stop() })
93
+ if (! (handleUnauthorizedError(e) || handleNotFoundError(e))) {
94
+ // Dialogs.error is suspend — use a blocking non-suspending dialog instead
95
+ Dialogs .error(
96
+ e.messageWithoutPrefix() ? : " Could not connect to workspace." ,
97
+ " Connection Error"
98
+ )
99
+ }
90
100
91
- readyDeferred.await( )
92
- } catch (e: ApiException ) {
93
- indicator.setText( " Connection failed " )
94
- delayedClose(indicator)
95
- if ( ! (handleUnauthorizedError(e) || handleNotFoundError(e))) {
96
- Dialogs .error(e.messageWithoutPrefix() ? : " Could not connect to workspace. " , " Connection Error" )
97
- }
98
- null
99
- } catch (e : Exception ) {
100
- indicator.setText( " Unexpected error: ${e.message} " )
101
- delayedClose(indicator)
102
- Dialogs .error(e.messageWithoutPrefix() ? : " Could not connect to workspace. " , " Connection Error " )
101
+ if (cont.isActive) cont.resume( null )
102
+ } catch (e: Exception ) {
103
+ runDelayed( 2000 ) { indicator.stop() }
104
+ Dialogs .error(
105
+ e.message ? : " Could not connect to workspace. " ,
106
+ " Connection Error"
107
+ )
108
+ cont.resume( null )
109
+ }
110
+ },
111
+ " Connecting to Remote IDE... " ,
112
+ true ,
103
113
null
104
- } finally {
105
- withContext(Dispatchers .EDT ) {
106
- if (indicator.isShowing) indicator.close(DialogWrapper .OK_EXIT_CODE )
107
- }
108
- }
114
+ )
109
115
}
110
116
}
111
117
112
- private fun delayedClose (indicator : StartupProgressIndicator ) {
113
- Timer (2000 ) {
114
- indicator.close(DialogWrapper .CANCEL_EXIT_CODE )
115
- }.start()
116
- }
117
-
118
118
private fun onClientPresenceChanged (
119
- readyDeferred : CompletableDeferred <GatewayConnectionHandle ?>,
120
- indicator : StartupProgressIndicator ,
119
+ ready : CompletableDeferred <GatewayConnectionHandle ?>,
120
+ indicator : ProgressIndicator ,
121
121
handle : GatewayConnectionHandle
122
122
): (Unit ) -> Unit = {
123
123
ApplicationManager .getApplication().invokeLater {
124
- if (! readyDeferred .isCompleted) {
125
- indicator.setText( " Remote IDE has started successfully" )
126
- indicator.setText2( " Opening project window…" )
127
- Timer (3000 ) {
128
- indicator.close( DialogWrapper . OK_EXIT_CODE )
129
- readyDeferred .complete(handle)
130
- }.start()
124
+ if (! ready .isCompleted) {
125
+ indicator.text = " Remote IDE has started successfully"
126
+ indicator.text2 = " Opening project window…"
127
+ runDelayed (3000 ) {
128
+ indicator.stop( )
129
+ ready .complete(handle)
130
+ }
131
131
}
132
132
}
133
133
}
134
134
135
135
private fun onClientFailedToOpenProject (
136
- readyDeferred : CompletableDeferred <GatewayConnectionHandle ?>,
137
- indicator : StartupProgressIndicator
136
+ ready : CompletableDeferred <GatewayConnectionHandle ?>,
137
+ indicator : ProgressIndicator
138
138
): (Int ) -> Unit = { errorCode ->
139
139
ApplicationManager .getApplication().invokeLater {
140
- if (! readyDeferred .isCompleted) {
141
- indicator.setText( " Failed to open remote project (code: $errorCode )" )
142
- Timer (2000 ) {
143
- indicator.close( DialogWrapper . CANCEL_EXIT_CODE )
144
- readyDeferred .complete(null )
145
- }.start()
140
+ if (! ready .isCompleted) {
141
+ indicator.text = " Failed to open remote project (code: $errorCode )"
142
+ runDelayed (2000 ) {
143
+ indicator.stop( )
144
+ ready .complete(null )
145
+ }
146
146
}
147
147
}
148
148
}
149
149
150
150
private fun onClientClosed (
151
- readyDeferred : CompletableDeferred <GatewayConnectionHandle ?>,
152
- indicator : StartupProgressIndicator
151
+ ready : CompletableDeferred <GatewayConnectionHandle ?>,
152
+ indicator : ProgressIndicator
153
153
): (Unit ) -> Unit = {
154
154
ApplicationManager .getApplication().invokeLater {
155
- if (! readyDeferred .isCompleted) {
156
- indicator.setText( " Remote IDE closed unexpectedly." )
157
- Timer (2000 ) {
158
- indicator.close( DialogWrapper . CANCEL_EXIT_CODE )
159
- readyDeferred .complete(null )
160
- }.start()
155
+ if (! ready .isCompleted) {
156
+ indicator.text = " Remote IDE closed unexpectedly."
157
+ runDelayed (2000 ) {
158
+ indicator.stop( )
159
+ ready .complete(null )
160
+ }
161
161
}
162
162
}
163
163
}
@@ -166,11 +166,11 @@ class DevSpacesConnectionProvider : GatewayConnectionProvider {
166
166
@Throws(IllegalArgumentException ::class )
167
167
private fun doConnect (
168
168
parameters : Map <String , String >,
169
- indicator : ProgressIndicator ? = null
169
+ indicator : ProgressIndicator
170
170
): GatewayConnectionHandle {
171
171
thisLogger().debug(" Launched Dev Spaces connection provider" , parameters)
172
172
173
- indicator? .text2 = " Preparing connection environment…"
173
+ indicator.text2 = " Preparing connection environment…"
174
174
175
175
val dwNamespace = parameters[DW_NAMESPACE ]
176
176
if (dwNamespace.isNullOrBlank()) {
@@ -184,17 +184,17 @@ class DevSpacesConnectionProvider : GatewayConnectionProvider {
184
184
throw IllegalArgumentException (" Query parameter \" $DW_NAME \" is missing" )
185
185
}
186
186
187
- indicator? .text2 = " Initializing Kubernetes connection…"
187
+ indicator.text2 = " Initializing Kubernetes connection…"
188
188
val ctx = DevSpacesContext ()
189
189
ctx.client = OpenShiftClientFactory ().create()
190
190
191
- indicator? .text2 = " Fetching DevWorkspace “$dwName ” from namespace “$dwNamespace ”…"
191
+ indicator.text2 = " Fetching DevWorkspace “$dwName ” from namespace “$dwNamespace ”…"
192
192
ctx.devWorkspace = DevWorkspaces (ctx.client).get(dwNamespace, dwName)
193
193
194
- indicator? .text2 = " Establishing remote IDE connection…"
194
+ indicator.text2 = " Establishing remote IDE connection…"
195
195
val thinClient = DevSpacesConnection (ctx).connect({}, {}, {})
196
196
197
- indicator? .text2 = " Connection established successfully."
197
+ indicator.text2 = " Connection established successfully."
198
198
return DevSpacesConnectionHandle (thinClient.lifetime, thinClient, { createComponent(dwName) }, dwName)
199
199
}
200
200
@@ -220,7 +220,7 @@ class DevSpacesConnectionProvider : GatewayConnectionProvider {
220
220
}
221
221
}
222
222
223
- private suspend fun handleUnauthorizedError (err : ApiException ): Boolean {
223
+ private fun handleUnauthorizedError (err : ApiException ): Boolean {
224
224
if (! err.isUnauthorized()) return false
225
225
226
226
val tokenNote = if (KubeConfigBuilder .isTokenAuthUsed())
@@ -234,7 +234,7 @@ class DevSpacesConnectionProvider : GatewayConnectionProvider {
234
234
return true
235
235
}
236
236
237
- private suspend fun handleNotFoundError (err : ApiException ): Boolean {
237
+ private fun handleNotFoundError (err : ApiException ): Boolean {
238
238
if (! err.isNotFound()) return false
239
239
240
240
val message = """
@@ -248,97 +248,10 @@ class DevSpacesConnectionProvider : GatewayConnectionProvider {
248
248
return true
249
249
}
250
250
251
- // Common progress dialog to monitor the connection and the renote IDE readiness
252
- private class StartupProgressIndicator (
253
- initialTitle : String = " Progress Indicator"
254
- ) : DialogWrapper(false ), ProgressIndicator {
255
- private val progressBar = JProgressBar ().apply { isIndeterminate = true }
256
- private val mainTextLabel = JLabel (" Initializing..." )
257
- private val subTextLabel = JLabel (" " )
258
-
259
- @Volatile
260
- private var canceled = false
261
-
262
- init {
263
- title = initialTitle
264
- isModal = false
265
- isResizable = true
266
- init ()
267
- }
268
-
269
- fun setIndeterminateValue (indeterminate : Boolean ) = ApplicationManager .getApplication().invokeLater {
270
- progressBar.isIndeterminate = indeterminate
271
- }
272
-
273
- fun setFractionValue (fraction : Double ) = ApplicationManager .getApplication().invokeLater {
274
- progressBar.isIndeterminate = false
275
- progressBar.value = (fraction * 100 ).toInt()
276
- }
277
-
278
- override fun createCenterPanel (): JComponent = JPanel (BorderLayout (5 , 5 )).apply {
279
- border = JBUI .Borders .empty(10 )
280
- layout = BoxLayout (this , BoxLayout .Y_AXIS )
281
- add(mainTextLabel)
282
- add(Box .createVerticalStrut(4 ))
283
- add(subTextLabel)
284
- add(Box .createVerticalStrut(8 ))
285
- add(progressBar)
286
- }
287
-
288
- override fun createActions (): Array <Action > = emptyArray()
289
- override fun getPreferredFocusedComponent () = mainTextLabel
290
- override fun getPreferredSize (): Dimension = Dimension (400 , 120 )
291
- override fun getText (): String = mainTextLabel.text
292
- override fun getText2 (): String = subTextLabel.text
293
-
294
- override fun setText (text : String? ) = ApplicationManager .getApplication().invokeLater {
295
- mainTextLabel.text = shortenMessage(text ? : " " )
296
- subTextLabel.text = " "
297
- }
298
-
299
- override fun setText2 (text : String? ) = ApplicationManager .getApplication().invokeLater {
300
- subTextLabel.text = shortenMessage(text ? : " " )
301
- }
302
-
303
- override fun setIndeterminate (indeterminate : Boolean ) = setIndeterminateValue(indeterminate)
304
- override fun isIndeterminate (): Boolean = progressBar.isIndeterminate
305
-
306
- override fun setFraction (fraction : Double ) = setFractionValue(fraction)
307
- override fun getFraction (): Double = progressBar.value / 100.0
308
-
309
- override fun cancel () {
310
- canceled = true
311
- close(CANCEL_EXIT_CODE )
312
- }
313
-
314
- override fun isCanceled (): Boolean = canceled
315
-
316
- override fun start () {}
317
- override fun stop () {}
318
- override fun isRunning (): Boolean = isShowing
319
- override fun pushState () {}
320
- override fun popState () {}
321
-
322
- override fun getModalityState (): ModalityState {
323
- return if (isShowing) ModalityState .current() else ModalityState .nonModal()
324
- }
325
-
326
- override fun setModalityProgress (progressIndicator : ProgressIndicator ? ) {}
327
-
328
- override fun checkCanceled () {
329
- if (isCanceled) throw ProcessCanceledException ()
330
- }
331
-
332
- override fun isPopupWasShown (): Boolean {
333
- return isShowing
334
- }
335
-
336
- private fun shortenMessage (message : String , maxLength : Int = 100): String {
337
- if (message.length <= maxLength) return message
338
-
339
- val head = message.take(maxLength / 2 - 3 )
340
- val tail = message.takeLast(maxLength / 2 - 3 )
341
- return " $head …$tail "
342
- }
251
+ private fun runDelayed (delay : Int , runnable : () -> Unit ) {
252
+ Timer (delay) {
253
+ runnable.invoke()
254
+ }.start()
343
255
}
256
+
344
257
}
0 commit comments