feat: implement submission (still needs testing)
This commit is contained in:
parent
c9a2aabc60
commit
74bbc91ea4
@ -3,8 +3,7 @@ S-DOM is a plugin for IntelliJ IDEA that allows you to submit your code to the D
|
||||
It is currently in development and not yet ready for use.
|
||||
|
||||
## TODO
|
||||
- Implement submissions
|
||||
- Implement reading results
|
||||
- Implement reading detailed results
|
||||
- Implement reading problems (and testcases)
|
||||
|
||||
## References
|
||||
|
@ -7,19 +7,20 @@ import com.intellij.ide.passwordSafe.PasswordSafe
|
||||
import com.intellij.openapi.application.ApplicationManager
|
||||
|
||||
object SDCredentials {
|
||||
private fun createCredentialAttributes(): CredentialAttributes {
|
||||
return CredentialAttributes(generateServiceName("s-dom", "httpAuth"))
|
||||
private fun createCredentialAttributes(name: String): CredentialAttributes {
|
||||
return CredentialAttributes(generateServiceName("s-dom", name))
|
||||
}
|
||||
|
||||
var credentials: Pair<String?, String?>
|
||||
get() = PasswordSafe.instance[createCredentialAttributes()]
|
||||
get() = PasswordSafe.instance[createCredentialAttributes("httpAuth")]
|
||||
.run { this?.userName to this?.getPasswordAsString() }
|
||||
set(value) {
|
||||
PasswordSafe.instance[createCredentialAttributes()] = Credentials(value.first, value.second)
|
||||
PasswordSafe.instance[createCredentialAttributes("httpAuth")] = Credentials(value.first, value.second)
|
||||
}
|
||||
|
||||
fun logOut() {
|
||||
PasswordSafe.instance[createCredentialAttributes()] = null
|
||||
PasswordSafe.instance[createCredentialAttributes("httpAuth")] = null
|
||||
PasswordSafe.instance[createCredentialAttributes("teamId")] = null
|
||||
}
|
||||
|
||||
var url: String
|
||||
@ -27,4 +28,10 @@ object SDCredentials {
|
||||
set(value) {
|
||||
ApplicationManager.getApplication().getService(SDSettings::class.java).state.url = value
|
||||
}
|
||||
|
||||
var teamId: String?
|
||||
get() = PasswordSafe.instance[createCredentialAttributes("teamId")]?.userName
|
||||
set(value) {
|
||||
PasswordSafe.instance[createCredentialAttributes("teamId")] = Credentials(value)
|
||||
}
|
||||
}
|
@ -8,10 +8,7 @@ import com.intellij.openapi.diagnostic.Logger
|
||||
import com.intellij.openapi.project.Project
|
||||
import io.gitlab.jfronny.sdom.actions.SDGetContestsAction
|
||||
import io.gitlab.jfronny.sdom.actions.SDGetProblemsAction
|
||||
import io.gitlab.jfronny.sdom.model.Contest
|
||||
import io.gitlab.jfronny.sdom.model.Problem
|
||||
import io.gitlab.jfronny.sdom.model.SDLoginResult
|
||||
import io.gitlab.jfronny.sdom.model.SDResult
|
||||
import io.gitlab.jfronny.sdom.model.*
|
||||
import io.ktor.client.*
|
||||
import io.ktor.client.call.*
|
||||
import io.ktor.client.engine.java.*
|
||||
@ -21,8 +18,15 @@ import io.ktor.client.plugins.contentnegotiation.*
|
||||
import io.ktor.client.request.*
|
||||
import io.ktor.http.*
|
||||
import io.ktor.serialization.kotlinx.json.*
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.SharedFlow
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.serialization.json.Json
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.util.Base64
|
||||
import java.util.zip.ZipEntry
|
||||
import java.util.zip.ZipOutputStream
|
||||
|
||||
object SDom {
|
||||
private const val CONTEST_ID_PROPERTY = "io.gitlab.jfronny.sdom.contestId"
|
||||
@ -61,7 +65,7 @@ object SDom {
|
||||
)
|
||||
})
|
||||
private val logoutListeners: MutableList<() -> Unit> = mutableListOf()
|
||||
private val resultFlowListeners: MutableList<(SharedFlow<Result<SDResult>>, Problem) -> Unit> = mutableListOf()
|
||||
private val resultFlowListeners: MutableList<(SharedFlow<Result<SDJudgement>>, Contest, Problem) -> Unit> = mutableListOf()
|
||||
|
||||
fun registerLoginListener(listener: () -> Unit) {
|
||||
loginListeners.add(listener)
|
||||
@ -71,7 +75,7 @@ object SDom {
|
||||
logoutListeners.add(listener)
|
||||
}
|
||||
|
||||
fun registerResultFlowListener(listener: (SharedFlow<Result<SDResult>>, Problem) -> Unit) {
|
||||
fun registerResultFlowListener(listener: (SharedFlow<Result<SDJudgement>>, Contest, Problem) -> Unit) {
|
||||
resultFlowListeners.add(listener)
|
||||
}
|
||||
|
||||
@ -84,6 +88,7 @@ object SDom {
|
||||
|
||||
if (!result.enabled) throw Exception("User is not enabled")
|
||||
SDCredentials.credentials = Pair(username, password)
|
||||
SDCredentials.teamId = result.teamId
|
||||
SDCredentials.url = fixedApi
|
||||
|
||||
loginListeners.forEach { ApplicationManager.getApplication().invokeLater(it) }
|
||||
@ -103,6 +108,7 @@ object SDom {
|
||||
}
|
||||
var problems: List<Problem> = listOf()
|
||||
var currentProblem: Problem? = null
|
||||
var judgementTypes: Map<String, SDJudgementType>? = null
|
||||
|
||||
suspend fun getContests() {
|
||||
val result = client.get("${SDCredentials.url}contests")
|
||||
@ -143,8 +149,63 @@ object SDom {
|
||||
currentProblem = null
|
||||
}
|
||||
}
|
||||
judgementTypes = client.get("${SDCredentials.url}contests/${currentContest!!.id}/judgement-types")
|
||||
.body<List<SDJudgementType>>()
|
||||
.associateBy { it.id }
|
||||
}
|
||||
|
||||
suspend fun submitSolution(
|
||||
contest: Contest, problem: Problem, solution: String, fileName: String
|
||||
): SharedFlow<Result<SDJudgement>> = coroutineScope {
|
||||
val resultFlow: MutableSharedFlow<Result<SDJudgement>> = MutableSharedFlow()
|
||||
launch {
|
||||
try {
|
||||
val response: SDSubmission = client.post("${SDCredentials.url}contests/${contest.id}/submissions") {
|
||||
contentType(ContentType.Application.Json)
|
||||
setBody(
|
||||
SDAddSubmission(
|
||||
problem = problem.id,
|
||||
problemId = problem.id,
|
||||
language = "cpp",
|
||||
languageId = "cpp",
|
||||
entryPoint = null,
|
||||
files = listOf(SDAddSubmissionFile(zip(fileName, solution)))
|
||||
)
|
||||
)
|
||||
}.body()
|
||||
|
||||
println(response)
|
||||
if (response.importError != null) {
|
||||
resultFlow.emit(Result.failure(Exception(response.importError)))
|
||||
return@launch
|
||||
}
|
||||
do {
|
||||
val result: List<SDJudgement> = client.get("${SDCredentials.url}contests/${contest.id}/judgements?submission_id=${response.id}").body()
|
||||
if (result.isNotEmpty()) {
|
||||
result.forEach { resultFlow.emit(Result.success(it)) }
|
||||
break
|
||||
} else {
|
||||
Thread.sleep(1000)
|
||||
}
|
||||
} while (true)
|
||||
} catch (e: Exception) {
|
||||
resultFlow.emit(Result.failure(e))
|
||||
}
|
||||
}
|
||||
resultFlowListeners.forEach { it -> ApplicationManager.getApplication().invokeLater { it(resultFlow, contest, problem) } }
|
||||
return@coroutineScope resultFlow
|
||||
}
|
||||
|
||||
private fun zip(name: String, content: String): String =
|
||||
String(Base64.getEncoder().encode(ByteArrayOutputStream().use { baos ->
|
||||
ZipOutputStream(baos).use {
|
||||
it.putNextEntry(ZipEntry(name))
|
||||
it.write(content.toByteArray())
|
||||
it.closeEntry()
|
||||
}
|
||||
baos.toByteArray()
|
||||
}))
|
||||
|
||||
val loggedIn: Boolean
|
||||
get() = SDCredentials.credentials.first != null && SDCredentials.credentials.second != null
|
||||
|
||||
|
@ -0,0 +1,83 @@
|
||||
package io.gitlab.jfronny.sdom.actions
|
||||
|
||||
import com.intellij.icons.AllIcons
|
||||
import com.intellij.notification.Notification
|
||||
import com.intellij.notification.NotificationAction
|
||||
import com.intellij.notification.NotificationGroupManager
|
||||
import com.intellij.notification.NotificationType
|
||||
import com.intellij.openapi.actionSystem.ActionUpdateThread
|
||||
import com.intellij.openapi.actionSystem.AnActionEvent
|
||||
import com.intellij.openapi.actionSystem.CommonDataKeys
|
||||
import com.intellij.openapi.diagnostic.LogLevel
|
||||
import com.intellij.openapi.diagnostic.Logger
|
||||
import com.intellij.testFramework.utils.editor.getVirtualFile
|
||||
import io.gitlab.jfronny.sdom.SDom
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class SDSubmitAction(text: String) : NotificationAction(text) {
|
||||
constructor() : this("")
|
||||
|
||||
override fun getActionUpdateThread(): ActionUpdateThread = ActionUpdateThread.BGT
|
||||
|
||||
override fun actionPerformed(e: AnActionEvent, notification: Notification) = actionPerformed(e)
|
||||
override fun actionPerformed(e: AnActionEvent) {
|
||||
println("Submitting")
|
||||
val editor = e.getData(CommonDataKeys.EDITOR)
|
||||
e.project?.let { project ->
|
||||
if (editor == null) {
|
||||
NotificationGroupManager.getInstance()
|
||||
.getNotificationGroup("sdom.notifications")
|
||||
.createNotification("You have to select a file", NotificationType.ERROR)
|
||||
.setTitle("No file selected")
|
||||
.addAction(SDSubmitAction("Retry"))
|
||||
.notify(e.project)
|
||||
return
|
||||
}
|
||||
val currentFile = editor.document.text
|
||||
val fileName = editor.document.getVirtualFile().name
|
||||
|
||||
val contest = SDom.currentContest
|
||||
if (contest == null) {
|
||||
NotificationGroupManager.getInstance()
|
||||
.getNotificationGroup("sdom.notifications")
|
||||
.createNotification("You have to select a contest", NotificationType.ERROR)
|
||||
.setTitle("No contest selected")
|
||||
.addAction(SDContestSelectionNotificationAction("Select Contest"))
|
||||
.addAction(SDSubmitAction("Retry"))
|
||||
.notify(e.project)
|
||||
return
|
||||
}
|
||||
val problem = SDom.currentProblem
|
||||
if (problem == null) {
|
||||
NotificationGroupManager.getInstance()
|
||||
.getNotificationGroup("sdom.notifications")
|
||||
.createNotification("You have to select a problem", NotificationType.ERROR)
|
||||
.setTitle("No problem selected")
|
||||
.addAction(SDProblemSelectionNotificationAction("Select Problem"))
|
||||
.addAction(SDSubmitAction("Retry"))
|
||||
.notify(e.project)
|
||||
return
|
||||
}
|
||||
CoroutineScope(Job() + Dispatchers.IO).launch {
|
||||
val result = SDom.submitSolution(contest, problem, currentFile, fileName)
|
||||
println(result.first())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val icon = AllIcons.Actions.Upload
|
||||
override fun update(e: AnActionEvent) {
|
||||
e.presentation.isEnabledAndVisible = e.project != null
|
||||
e.presentation.icon = icon
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val LOG = Logger.getInstance(SDSubmitAction::class.java).apply {
|
||||
setLevel(LogLevel.DEBUG)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
package io.gitlab.jfronny.sdom.model
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class SDAddSubmission(
|
||||
val problem: String?,
|
||||
@SerialName("problem_id") val problemId: String?,
|
||||
val language: String?,
|
||||
@SerialName("language_id") val languageId: String?,
|
||||
//@SerialName("team_id") val teamId: String? = null,
|
||||
//@SerialName("user_id") val userId: String? = null,
|
||||
//val time: String? = null, // DateTime
|
||||
@SerialName("entry_point") val entryPoint: String?,
|
||||
//val id: String? = null,
|
||||
val files: List<SDAddSubmissionFile>,
|
||||
//val code: List<String>?, // binary
|
||||
)
|
@ -0,0 +1,9 @@
|
||||
package io.gitlab.jfronny.sdom.model
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class SDAddSubmissionFile(
|
||||
val data: String,
|
||||
val mime: String? = "application/zip"
|
||||
)
|
18
src/main/kotlin/io/gitlab/jfronny/sdom/model/SDJudgement.kt
Normal file
18
src/main/kotlin/io/gitlab/jfronny/sdom/model/SDJudgement.kt
Normal file
@ -0,0 +1,18 @@
|
||||
package io.gitlab.jfronny.sdom.model
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class SDJudgement(
|
||||
@SerialName("start_time") val startTime: String?,
|
||||
@SerialName("start_contest_time") val startContestTime: String,
|
||||
@SerialName("end_time") val endTime: String?,
|
||||
@SerialName("end_contest_time") val endContestTime: String?,
|
||||
@SerialName("submission_id") val submissionId: String,
|
||||
val id: String,
|
||||
val valid: Boolean,
|
||||
@SerialName("judgement_type_id") val judgementTypeId: String?,
|
||||
@SerialName("judgehost") val judgeHost: String?,
|
||||
@SerialName("max_run_time") val maxRunTime: Float?,
|
||||
)
|
@ -0,0 +1,8 @@
|
||||
package io.gitlab.jfronny.sdom.model
|
||||
|
||||
data class SDJudgementType(
|
||||
val id: String,
|
||||
val name: String,
|
||||
val penalty: Boolean,
|
||||
val solved: Boolean
|
||||
)
|
@ -9,7 +9,7 @@ data class SDLoginResult(
|
||||
@SerialName("last_api_login_time") val lastApiLoginTime: String?, // DateTime
|
||||
@SerialName("first_login_time") val firstLoginTime: String?, // DateTime
|
||||
val team: String?,
|
||||
@SerialName("team_id") val teamId: Int?,
|
||||
@SerialName("team_id") val teamId: String?,
|
||||
val roles: List<String>,
|
||||
val type: String?,
|
||||
val id: String,
|
||||
|
@ -1,4 +0,0 @@
|
||||
package io.gitlab.jfronny.sdom.model
|
||||
|
||||
class SDResult {
|
||||
}
|
16
src/main/kotlin/io/gitlab/jfronny/sdom/model/SDSubmission.kt
Normal file
16
src/main/kotlin/io/gitlab/jfronny/sdom/model/SDSubmission.kt
Normal file
@ -0,0 +1,16 @@
|
||||
package io.gitlab.jfronny.sdom.model
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
|
||||
data class SDSubmission(
|
||||
@SerialName("language_id") val languageId: String,
|
||||
val time: String,
|
||||
@SerialName("contest_time") val contestTime: String,
|
||||
@SerialName("team_id") val teamId: String,
|
||||
@SerialName("problem_id") val problemId: String,
|
||||
val files: List<FileWithName>?,
|
||||
val id: String,
|
||||
@SerialName("external_id") val externalId: String?,
|
||||
@SerialName("entry_point") val entryPoint: String?,
|
||||
@SerialName("import_error") val importError: String?,
|
||||
)
|
@ -5,8 +5,10 @@ import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.wm.ToolWindow
|
||||
import com.intellij.openapi.wm.ToolWindowFactory
|
||||
import io.gitlab.jfronny.sdom.SDom
|
||||
import io.gitlab.jfronny.sdom.model.Contest
|
||||
import io.gitlab.jfronny.sdom.model.Problem
|
||||
import io.gitlab.jfronny.sdom.model.SDResult
|
||||
import io.gitlab.jfronny.sdom.model.SDJudgement
|
||||
import io.gitlab.jfronny.sdom.ui.SDResultPanel
|
||||
import io.gitlab.jfronny.sdom.ui.SDSubmitPanel
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
@ -30,8 +32,13 @@ class SDToolWindowFactory : ToolWindowFactory, DumbAware {
|
||||
contentManager.addContent(loggedOutContent)
|
||||
}
|
||||
|
||||
fun showResultContent(resultFlow: Flow<Result<SDResult>>, task: Problem) {
|
||||
|
||||
fun showResultContent(resultFlow: Flow<Result<SDJudgement>>, contest: Contest, problem: Problem) {
|
||||
val resultPanel = SDResultPanel(project, resultFlow, contest, problem)
|
||||
val resultContent = contentManager.factory.createContent(resultPanel.component, "Result", false).apply {
|
||||
preferredFocusableComponent = resultPanel.preferredFocusableComponent
|
||||
}
|
||||
contentManager.addContent(resultContent)
|
||||
contentManager.setSelectedContent(resultContent)
|
||||
}
|
||||
|
||||
SDom.registerLoginListener(::showSubmitContent)
|
||||
|
@ -2,23 +2,53 @@ package io.gitlab.jfronny.sdom.ui
|
||||
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.ui.ComponentContainer
|
||||
import io.gitlab.jfronny.sdom.model.SDResult
|
||||
import com.intellij.ui.JBColor
|
||||
import io.gitlab.jfronny.sdom.SDom
|
||||
import io.gitlab.jfronny.sdom.model.Contest
|
||||
import io.gitlab.jfronny.sdom.model.Problem
|
||||
import io.gitlab.jfronny.sdom.model.SDJudgement
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.launch
|
||||
import java.awt.BorderLayout
|
||||
import javax.swing.JComponent
|
||||
import javax.swing.JLabel
|
||||
import javax.swing.JPanel
|
||||
|
||||
class SDResultPanel(
|
||||
val project: Project,
|
||||
resultFlow: Flow<Result<SDResult>>
|
||||
resultFlow: Flow<Result<SDJudgement>>,
|
||||
contest: Contest,
|
||||
problem: Problem,
|
||||
) : ComponentContainer {
|
||||
override fun dispose() {
|
||||
TODO("Not yet implemented")
|
||||
private val panel = JPanel(BorderLayout())
|
||||
|
||||
init {
|
||||
CoroutineScope(Job() + Dispatchers.IO).launch {
|
||||
val resultResult = resultFlow.first()
|
||||
|
||||
if (resultResult.isSuccess) {
|
||||
val result = resultResult.getOrThrow()
|
||||
if (!result.valid) {
|
||||
panel.add(JLabel("Judgement failed"), BorderLayout.CENTER)
|
||||
return@launch
|
||||
}
|
||||
val parsedResult =
|
||||
result.judgementTypeId
|
||||
?.let { SDom.judgementTypes?.get(it) }
|
||||
?.let { it.name to it.solved }
|
||||
?: ("Unknown" to false)
|
||||
panel.add(JLabel(parsedResult.first).apply { foreground = if (parsedResult.second) JBColor.GREEN else JBColor.RED }, BorderLayout.CENTER)
|
||||
} else {
|
||||
panel.add(JLabel("Judgement failed"), BorderLayout.CENTER)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun getComponent(): JComponent {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override fun getPreferredFocusableComponent(): JComponent {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
override fun dispose() {}
|
||||
override fun getComponent(): JComponent = panel
|
||||
override fun getPreferredFocusableComponent(): JComponent = panel // TODO focus on actual content once it's there
|
||||
}
|
@ -56,7 +56,11 @@
|
||||
text="Refresh Problems">
|
||||
<add-to-group group-id="io.gitlab.jfronny.sdom.actions.SDToolbarActions"/>
|
||||
</action>
|
||||
<!-- TODO: add submission action -->
|
||||
<action id="io.gitlab.jfronny.sdom.actions.SDSubmitAction"
|
||||
class="io.gitlab.jfronny.sdom.actions.SDSubmitAction"
|
||||
text="Submit Solution">
|
||||
<add-to-group group-id="io.gitlab.jfronny.sdom.actions.SDToolbarActions"/>
|
||||
</action>
|
||||
<action class="io.gitlab.jfronny.sdom.actions.SDLogoutAction"
|
||||
id="io.gitlab.jfronny.sdom.actions.SDLogoutAction"
|
||||
text="Log out of DOMjudge"/>
|
||||
|
Loading…
Reference in New Issue
Block a user