@@ -20,13 +20,16 @@ import com.jetbrains.toolbox.api.remoteDev.RemoteProvider
20
20
import com.jetbrains.toolbox.api.remoteDev.RemoteProviderEnvironment
21
21
import com.jetbrains.toolbox.api.ui.actions.ActionDescription
22
22
import com.jetbrains.toolbox.api.ui.components.UiPage
23
+ import kotlinx.coroutines.ExperimentalCoroutinesApi
23
24
import kotlinx.coroutines.Job
24
- import kotlinx.coroutines.delay
25
+ import kotlinx.coroutines.channels.Channel
25
26
import kotlinx.coroutines.flow.MutableStateFlow
26
27
import kotlinx.coroutines.flow.StateFlow
27
28
import kotlinx.coroutines.flow.update
28
29
import kotlinx.coroutines.isActive
29
30
import kotlinx.coroutines.launch
31
+ import kotlinx.coroutines.selects.onTimeout
32
+ import kotlinx.coroutines.selects.select
30
33
import okhttp3.OkHttpClient
31
34
import java.net.URI
32
35
import java.net.URL
@@ -35,18 +38,20 @@ import kotlin.time.Duration.Companion.seconds
35
38
import com.jetbrains.toolbox.api.ui.components.AccountDropdownField as DropDownMenu
36
39
import com.jetbrains.toolbox.api.ui.components.AccountDropdownField as dropDownFactory
37
40
41
+ @OptIn(ExperimentalCoroutinesApi ::class )
38
42
class CoderRemoteProvider (
39
43
private val context : CoderToolboxContext ,
40
44
private val httpClient : OkHttpClient ,
41
45
) : RemoteProvider(" Coder" ) {
42
46
// Current polling job.
43
47
private var pollJob: Job ? = null
44
- private var lastEnvironments: Set <CoderRemoteEnvironment >? = null
48
+ private val lastEnvironments = mutableSetOf <CoderRemoteEnvironment >()
45
49
46
- private val cSettings = context.settingsStore.readOnly()
50
+ private val settings = context.settingsStore.readOnly()
47
51
48
52
// Create our services from the Toolbox ones.
49
- private val settingsPage: CoderSettingsPage = CoderSettingsPage (context)
53
+ private val triggerSshConfig = Channel <Boolean >(Channel .CONFLATED )
54
+ private val settingsPage: CoderSettingsPage = CoderSettingsPage (context, triggerSshConfig)
50
55
private val dialogUi = DialogUi (context)
51
56
52
57
// The REST client, if we are signed in
@@ -92,7 +97,7 @@ class CoderRemoteProvider(
92
97
}?.map { agent ->
93
98
// If we have an environment already, update that.
94
99
val env = CoderRemoteEnvironment (context, client, ws, agent)
95
- lastEnvironments? .firstOrNull { it == env }?.let {
100
+ lastEnvironments.firstOrNull { it == env }?.let {
96
101
it.update(ws, agent)
97
102
it
98
103
} ? : env
@@ -107,9 +112,7 @@ class CoderRemoteProvider(
107
112
108
113
// Reconfigure if a new environment is found.
109
114
// TODO@JB: Should we use the add/remove listeners instead?
110
- val newEnvironments = lastEnvironments
111
- ?.let { resolvedEnvironments.subtract(it) }
112
- ? : resolvedEnvironments
115
+ val newEnvironments = resolvedEnvironments.subtract(lastEnvironments)
113
116
if (newEnvironments.isNotEmpty()) {
114
117
context.logger.info(" Found new environment(s), reconfiguring CLI: $newEnvironments " )
115
118
cli.configSsh(newEnvironments.map { it.name }.toSet())
@@ -124,8 +127,10 @@ class CoderRemoteProvider(
124
127
true
125
128
}
126
129
}
127
-
128
- lastEnvironments = resolvedEnvironments
130
+ lastEnvironments.apply {
131
+ clear()
132
+ addAll(resolvedEnvironments)
133
+ }
129
134
} catch (_: CancellationException ) {
130
135
context.logger.debug(" ${client.url} polling loop canceled" )
131
136
break
@@ -136,7 +141,17 @@ class CoderRemoteProvider(
136
141
break
137
142
}
138
143
// TODO: Listening on a web socket might be better?
139
- delay(5 .seconds)
144
+ select<Unit > {
145
+ onTimeout(5 .seconds) {
146
+ context.logger.trace(" workspace poller waked up by the 5 seconds timeout" )
147
+ }
148
+ triggerSshConfig.onReceive { shouldTrigger ->
149
+ if (shouldTrigger) {
150
+ context.logger.trace(" workspace poller waked up because it should reconfigure the ssh configurations" )
151
+ cli.configSsh(lastEnvironments.map { it.name }.toSet())
152
+ }
153
+ }
154
+ }
140
155
}
141
156
}
142
157
@@ -178,7 +193,7 @@ class CoderRemoteProvider(
178
193
override fun close () {
179
194
pollJob?.cancel()
180
195
client?.close()
181
- lastEnvironments = null
196
+ lastEnvironments.clear()
182
197
environments.value = LoadableState .Value (emptyList())
183
198
isInitialized.update { false }
184
199
}
@@ -270,7 +285,7 @@ class CoderRemoteProvider(
270
285
var autologinEx: Exception ? = null
271
286
context.secrets.lastToken.let { lastToken ->
272
287
context.secrets.lastDeploymentURL.let { lastDeploymentURL ->
273
- if (autologin && lastDeploymentURL.isNotBlank() && (lastToken.isNotBlank() || ! cSettings .requireTokenAuth)) {
288
+ if (autologin && lastDeploymentURL.isNotBlank() && (lastToken.isNotBlank() || ! settings .requireTokenAuth)) {
274
289
try {
275
290
return createConnectPage(URL (lastDeploymentURL), lastToken)
276
291
} catch (ex: Exception ) {
@@ -342,7 +357,7 @@ class CoderRemoteProvider(
342
357
if (it.isNotBlank() && context.secrets.lastDeploymentURL == deploymentURL.toString()) {
343
358
it to SettingSource .LAST_USED
344
359
} else {
345
- cSettings .token(deploymentURL)
360
+ settings .token(deploymentURL)
346
361
}
347
362
}
348
363
0 commit comments