1
+ // Copyright (c) 2022 Gitpod GmbH. All rights reserved.
2
+ // Licensed under the GNU Affero General Public License (AGPL).
3
+ // See License-AGPL.txt in the project root for license information.
4
+
5
+ package io.gitpod.jetbrains.remote
6
+
7
+ import com.intellij.notification.NotificationAction
8
+ import com.intellij.notification.NotificationGroupManager
9
+ import com.intellij.notification.NotificationType
10
+ import com.intellij.openapi.Disposable
11
+ import com.intellij.openapi.components.Service
12
+ import com.intellij.openapi.diagnostic.thisLogger
13
+ import com.intellij.remoteDev.util.onTerminationOrNow
14
+ import com.jetbrains.rd.util.lifetime.Lifetime
15
+ import git4idea.config.GitVcsApplicationSettings
16
+ import io.gitpod.jetbrains.remote.services.SupervisorInfoService
17
+ import io.gitpod.supervisor.api.Notification.NotifyRequest
18
+ import io.gitpod.supervisor.api.Notification.NotifyResponse
19
+ import io.gitpod.supervisor.api.Notification.RespondRequest
20
+ import io.gitpod.supervisor.api.Notification.SubscribeRequest
21
+ import io.gitpod.supervisor.api.Notification.SubscribeResponse
22
+ import io.gitpod.supervisor.api.NotificationServiceGrpc
23
+ import io.grpc.stub.ClientCallStreamObserver
24
+ import io.grpc.stub.ClientResponseObserver
25
+ import io.grpc.stub.StreamObserver
26
+ import kotlinx.coroutines.GlobalScope
27
+ import kotlinx.coroutines.future.await
28
+ import kotlinx.coroutines.isActive
29
+ import kotlinx.coroutines.launch
30
+ import java.util.concurrent.CancellationException
31
+ import java.util.concurrent.CompletableFuture
32
+ import kotlinx.coroutines.*
33
+
34
+ @Service
35
+ class GitpodManager : Disposable {
36
+
37
+ init {
38
+ GitVcsApplicationSettings .getInstance().isUseCredentialHelper = true
39
+ }
40
+
41
+ private val lifetime = Lifetime .Eternal .createNested()
42
+
43
+ private val notificationGroup = NotificationGroupManager .getInstance().getNotificationGroup(" Gitpod Notifications" )
44
+ private val notificationsJob = GlobalScope .launch {
45
+ val notifications = NotificationServiceGrpc .newStub(SupervisorInfoService .channel)
46
+ val futureNotifications = NotificationServiceGrpc .newFutureStub(SupervisorInfoService .channel)
47
+ while (isActive) {
48
+ try {
49
+ val f = CompletableFuture <Void >()
50
+ notifications.subscribe(SubscribeRequest .newBuilder().build(), object : ClientResponseObserver <SubscribeRequest , SubscribeResponse > {
51
+
52
+ override fun beforeStart (requestStream : ClientCallStreamObserver <SubscribeRequest >) {
53
+ // TODO(ak): actually should be bound to cancellation of notifications job
54
+ lifetime.onTerminationOrNow {
55
+ requestStream.cancel(null , null )
56
+ }
57
+ }
58
+
59
+ override fun onNext (n : SubscribeResponse ) {
60
+ val request = n.request
61
+ val type = when (request.level) {
62
+ NotifyRequest .Level .ERROR -> NotificationType .ERROR
63
+ NotifyRequest .Level .WARNING -> NotificationType .WARNING
64
+ else -> NotificationType .INFORMATION
65
+ }
66
+ val notification = notificationGroup.createNotification(request.message, type)
67
+ for (action in request.actionsList) {
68
+ notification.addAction(NotificationAction .createSimple(action) {
69
+ futureNotifications.respond(RespondRequest .newBuilder()
70
+ .setRequestId(n.requestId)
71
+ .setResponse(NotifyResponse .newBuilder().setAction(action).build())
72
+ .build())
73
+ })
74
+ }
75
+ notification.notify(null )
76
+ }
77
+
78
+ override fun onError (t : Throwable ) {
79
+ f.completeExceptionally(t)
80
+ }
81
+
82
+ override fun onCompleted () {
83
+ f.complete(null )
84
+ }
85
+ })
86
+ f.await()
87
+ } catch (t: Throwable ) {
88
+ if (t is CancellationException ) {
89
+ throw t
90
+ }
91
+ thisLogger().error(" gitpod: failed to stream notifications: " , t)
92
+ }
93
+ delay(1000L )
94
+ }
95
+ }
96
+ init {
97
+ lifetime.onTerminationOrNow {
98
+ notificationsJob.cancel()
99
+ }
100
+ }
101
+
102
+ override fun dispose () {
103
+ lifetime.terminate()
104
+ }
105
+ }
0 commit comments