diff --git a/src/main/kotlin/com/github/pushpavel/autocp/gather/base/ProblemGatheringBridge.kt b/src/main/kotlin/com/github/pushpavel/autocp/gather/base/ProblemGatheringBridge.kt
index 7c8584f8..5b24bc14 100644
--- a/src/main/kotlin/com/github/pushpavel/autocp/gather/base/ProblemGatheringBridge.kt
+++ b/src/main/kotlin/com/github/pushpavel/autocp/gather/base/ProblemGatheringBridge.kt
@@ -3,11 +3,17 @@ package com.github.pushpavel.autocp.gather.base
import com.github.pushpavel.autocp.common.helpers.ioScope
import com.github.pushpavel.autocp.common.res.R
import com.github.pushpavel.autocp.gather.models.ProblemJson
+import com.intellij.openapi.Disposable
+import com.intellij.openapi.application.ApplicationManager
+import com.intellij.openapi.components.Service
import com.intellij.openapi.project.DumbAware
import com.intellij.openapi.project.Project
import com.intellij.openapi.startup.ProjectActivity
+import kotlinx.coroutines.CancellationException
+import kotlinx.coroutines.Job
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.delay
+import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
import kotlinx.serialization.SerializationException
import kotlinx.serialization.json.Json
@@ -15,21 +21,41 @@ import java.net.SocketTimeoutException
/**
* Bridge between Competitive Companion extension and [BatchProcessor]
+ * Application-level service to ensure only one instance is running
*/
-class ProblemGatheringBridge : ProjectActivity, DumbAware {
+@Service(Service.Level.APP)
+class ProblemGatheringBridge : Disposable {
private val scope = ioScope()
private val serializer = Json { ignoreUnknownKeys = true }
+
+ @Volatile
+ private var isRunning = false
+
+ @Volatile
+ private var serverJob: Job? = null
- fun runActivity(project: Project) {
+ companion object {
+ fun getInstance(): ProblemGatheringBridge {
+ return ApplicationManager.getApplication().getService(ProblemGatheringBridge::class.java)
+ }
+ }
+
+ fun start() {
+ if (isRunning) {
+ return
+ }
+
+ isRunning = true
+
// initialize server
- scope.launch {
+ serverJob = scope.launch {
try {
val serverSocket = openServerSocketAsync(
R.others.competitiveCompanionPorts
).await() ?: throw ProblemGatheringErr.AllPortsTakenErr(R.others.competitiveCompanionPorts)
serverSocket.use {
- while (true) {
+ while (isActive) {
try {
coroutineScope {
val message = listenForMessageAsync(
@@ -51,12 +77,31 @@ class ProblemGatheringBridge : ProjectActivity, DumbAware {
} catch (e: ProblemGatheringErr) {
R.notify.problemGatheringErr(e)
} catch (e: Exception) {
- R.notify.problemGatheringUncaught(e)
+ if (e !is CancellationException) {
+ R.notify.problemGatheringUncaught(e)
+ }
} finally {
+ isRunning = false
BatchProcessor.interruptBatch()
}
}
}
- override suspend fun execute(project: Project) = runActivity(project)
+ fun stop() {
+ serverJob?.cancel()
+ isRunning = false
+ }
+
+ override fun dispose() {
+ stop()
+ }
+}
+
+/**
+ * Starts the ProblemGatheringBridge service when the first project opens
+ */
+class ProblemGatheringBridgeStarter : ProjectActivity, DumbAware {
+ override suspend fun execute(project: Project) {
+ ProblemGatheringBridge.getInstance().start()
+ }
}
diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml
index 873a4b4d..a3b943e0 100644
--- a/src/main/resources/META-INF/plugin.xml
+++ b/src/main/resources/META-INF/plugin.xml
@@ -53,7 +53,8 @@
-
+
+