4
4
5
5
package io.gitpod.jetbrains.remote
6
6
7
+ import com.intellij.openapi.client.ClientSessionsManager
8
+ import org.jetbrains.ide.RestService
9
+ import com.intellij.codeWithMe.ClientId
10
+ import com.intellij.ide.BrowserUtil
7
11
import com.intellij.ide.plugins.PluginManagerCore
8
12
import com.intellij.notification.NotificationAction
9
13
import com.intellij.notification.NotificationGroupManager
10
14
import com.intellij.notification.NotificationType
11
15
import com.intellij.openapi.Disposable
16
+ import com.intellij.openapi.client.ClientProjectSession
17
+ import com.intellij.openapi.client.ClientSession
12
18
import com.intellij.openapi.components.Service
13
19
import com.intellij.openapi.diagnostic.thisLogger
14
20
import com.intellij.openapi.extensions.PluginId
@@ -23,6 +29,10 @@ import io.gitpod.jetbrains.remote.utils.Retrier.retry
23
29
import io.gitpod.supervisor.api.*
24
30
import io.gitpod.supervisor.api.Info.WorkspaceInfoResponse
25
31
import io.gitpod.supervisor.api.Notification.*
32
+ import io.gitpod.supervisor.api.Status.OnPortExposedAction
33
+ import io.gitpod.supervisor.api.Status.PortsStatus
34
+ import io.gitpod.supervisor.api.Status.PortsStatusRequest
35
+ import io.gitpod.supervisor.api.Status.PortsStatusResponse
26
36
import io.grpc.ManagedChannel
27
37
import io.grpc.ManagedChannelBuilder
28
38
import io.grpc.stub.ClientCallStreamObserver
@@ -46,6 +56,7 @@ import java.util.concurrent.CancellationException
46
56
import java.util.concurrent.CompletableFuture
47
57
import javax.websocket.DeploymentException
48
58
59
+
49
60
@Service
50
61
class GitpodManager : Disposable {
51
62
@@ -199,6 +210,127 @@ class GitpodManager : Disposable {
199
210
}
200
211
}
201
212
213
+ private val portsObserveJob = GlobalScope .launch {
214
+ if (application.isHeadlessEnvironment) {
215
+ return @launch
216
+ }
217
+
218
+ // Ignore ports that aren't actually used by the user (e.g. ports used internally by JetBrains IDEs)
219
+ val ignorePorts = listOf (5990 , 5991 , 6942 , 6943 )
220
+ val portsStatus = hashMapOf<Int , PortsStatus >()
221
+
222
+ val status = StatusServiceGrpc .newStub(supervisorChannel)
223
+ while (isActive) {
224
+ try {
225
+ val project = RestService .getLastFocusedOrOpenedProject()
226
+
227
+ var session: ClientSession ? = null
228
+ if (project != null ) {
229
+ session = ClientSessionsManager .getProjectSessions(project, false ).first()
230
+ }
231
+ if (session == null ) {
232
+ session = ClientSessionsManager .getAppSessions(false ).first()
233
+ }
234
+
235
+ val gitpodClientProjectSessionTracker = GitpodClientProjectSessionTracker (session as ClientProjectSession )
236
+
237
+ val f = CompletableFuture <Void >()
238
+ status.portsStatus(
239
+ PortsStatusRequest .newBuilder().setObserve(true ).build(),
240
+ object : ClientResponseObserver <PortsStatusRequest , PortsStatusResponse > {
241
+
242
+ override fun beforeStart (requestStream : ClientCallStreamObserver <PortsStatusRequest >) {
243
+ lifetime.onTerminationOrNow {
244
+ requestStream.cancel(null , null )
245
+ }
246
+ }
247
+
248
+ override fun onNext (ps : PortsStatusResponse ) {
249
+ for (port in ps.portsList) {
250
+ // Avoiding undesired notifications
251
+ if (ignorePorts.contains(port.localPort)) {
252
+ continue
253
+ }
254
+
255
+ val hasPreviousStatus = portsStatus.containsKey(port.localPort)
256
+
257
+ if (! hasPreviousStatus) {
258
+ portsStatus[port.localPort] = port
259
+ }
260
+
261
+ val wasServed = portsStatus[port.localPort]?.served!!
262
+ val wasExposed = portsStatus[port.localPort]?.hasExposed()!!
263
+ val wasServedExposed = wasServed && wasExposed
264
+ val isServedExposed = port.served && port.hasExposed()
265
+
266
+ // If the initial update received shows that the port is served and exposed, then notify
267
+ val isFirstUpdate = ! hasPreviousStatus && wasServedExposed && isServedExposed
268
+
269
+ // If the port changes its status to served and exposed, notify the user
270
+ val shouldSendNotification = isFirstUpdate || ! wasServedExposed && isServedExposed
271
+
272
+ portsStatus[port.localPort] = port
273
+
274
+ if (shouldSendNotification) {
275
+ if (port.exposed.onExposed.number == OnPortExposedAction .ignore_VALUE) {
276
+ continue
277
+ }
278
+
279
+ if (port.exposed.onExposed.number == OnPortExposedAction .open_browser_VALUE) {
280
+ ClientId .withClientId(session.clientId) {
281
+ BrowserUtil .browse(port.exposed.url)
282
+ gitpodClientProjectSessionTracker.trackEvent(" jb_execute_command_gitpod_ports" , mapOf (" action" to " openBrowser" ))
283
+ }
284
+ continue
285
+ }
286
+
287
+ if (port.exposed.onExposed.number == OnPortExposedAction .open_preview_VALUE) {
288
+ ClientId .withClientId(session.clientId) {
289
+ BrowserUtil .browse(port.exposed.url)
290
+ gitpodClientProjectSessionTracker.trackEvent(" jb_execute_command_gitpod_ports" , mapOf (" action" to " openBrowser" ))
291
+ }
292
+ continue
293
+ }
294
+
295
+ val message = " A service is available on port ${port.localPort} "
296
+ val notification = notificationGroup.createNotification(message, NotificationType .INFORMATION )
297
+
298
+ val lambda = {
299
+ BrowserUtil .browse(port.exposed.url)
300
+ gitpodClientProjectSessionTracker.trackEvent(" jb_execute_command_gitpod_ports" , mapOf (" action" to " openBrowser" ))
301
+ }
302
+
303
+ val action = NotificationAction .createSimpleExpiring(" Open Browser" , lambda)
304
+ notification.addAction(action)
305
+ notification.notify(null )
306
+ }
307
+ }
308
+ }
309
+
310
+ override fun onError (t : Throwable ) {
311
+ f.completeExceptionally(t)
312
+ }
313
+
314
+ override fun onCompleted () {
315
+ f.complete(null )
316
+ }
317
+ })
318
+ f.await()
319
+ } catch (t: Throwable ) {
320
+ if (t is CancellationException ) {
321
+ throw t
322
+ }
323
+ thisLogger().error(" gitpod: failed to stream ports status: " , t)
324
+ }
325
+ delay(1000L )
326
+ }
327
+ }
328
+ init {
329
+ lifetime.onTerminationOrNow {
330
+ portsObserveJob.cancel()
331
+ }
332
+ }
333
+
202
334
val pendingInfo = CompletableFuture <WorkspaceInfoResponse >()
203
335
private val infoJob = GlobalScope .launch {
204
336
if (application.isHeadlessEnvironment) {
0 commit comments