Skip to content

Commit 2c9ca04

Browse files
authored
Merge pull request #373 from dres-dev/dev
v1.3
2 parents 7a61831 + f09d54b commit 2c9ca04

60 files changed

Lines changed: 1824 additions & 632 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

backend/config/log4j2.xml

Lines changed: 15 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -16,53 +16,38 @@
1616
<MarkerFilter marker="REST" onMatch="ACCEPT" onMismatch="DENY"/>
1717
</File>
1818

19-
<File name="Audit" fileName="logs/audit.log">
19+
<File name="Audit" fileName="logs/audit.log" bufferedIO="true" immediateFlush="true">
2020
<PatternLayout pattern="${LOG_PATTERN}"/>
2121
<MarkerFilter marker="AUDIT" onMatch="ACCEPT" onMismatch="DENY"/>
2222
</File>
2323

2424
<File name="Libraries" fileName="logs/libraries.log">
2525
<PatternLayout pattern="${LOG_PATTERN}"/>
2626
</File>
27+
2728
<File name="Everything" fileName="logs/42.log">
28-
<PatternLayout pattern="${LOG_PATTERN}" />
29+
<PatternLayout pattern="${LOG_PATTERN}"/>
2930
</File>
3031
</Appenders>
3132

3233
<Loggers>
33-
<Logger name="org.eclipse.jetty" additivity="false">
34-
<AppenderRef ref="Console" level="WARN"/>
35-
<AppenderRef ref="Libraries" level="WARN"/>
36-
<AppenderRef ref="Everything" level="INFO" />
37-
</Logger>
38-
39-
<Logger name="io.javalin" additivity="false">
40-
<AppenderRef ref="Console" level="WARN"/>
41-
<AppenderRef ref="Libraries" level="WARN"/>
42-
<AppenderRef ref="Everything" level="INFO" />
34+
<Logger name="org.eclipse.jetty" level="WARN">
35+
<AppenderRef ref="Libraries"/>
4336
</Logger>
4437

45-
<Logger name="org.jline" additivity="false">
46-
<AppenderRef ref="Console" level="WARN"/>
47-
<AppenderRef ref="Libraries" level="WARN"/>
48-
<AppenderRef ref="Everything" level="INFO" />
38+
<Logger name="org.jline" level="WARN">
39+
<AppenderRef ref="Console"/>
40+
<AppenderRef ref="Libraries"/>
4941
</Logger>
5042

51-
<Logger name="*" additivity="false">
52-
<AppenderRef ref="Console" level="ERROR"/>
53-
<AppenderRef ref="Libraries" level="ERROR"/>
54-
<AppenderRef ref="Everything" level="INFO" />
43+
<Logger name="dev.dres" level="INFO">
44+
<AppenderRef ref="RestRequests"/>
45+
<AppenderRef ref="Audit"/>
5546
</Logger>
5647

57-
<Logger name="dev.dres" additivity="false">
58-
<AppenderRef ref="Console" level="INFO"/>
59-
<AppenderRef ref="RestRequests" level="INFO"/>
60-
<AppenderRef ref="Audit" level="INFO"/>
61-
<AppenderRef ref="Everything" level="DEBUG" />
62-
</Logger>
63-
64-
<Root>
65-
<AppenderRef ref="Everything" level="INFO" />
48+
<Root level="INFO">
49+
<AppenderRef ref="Everything"/>
6650
</Root>
6751
</Loggers>
68-
</Configuration>
52+
53+
</Configuration>

backend/src/main/kotlin/dev/dres/DRES.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ import java.nio.file.Paths
1818

1919
object DRES {
2020

21+
const val VERSION = "1.2.2"
22+
2123
/** Application root; shoud pe relative to JAR file or classes path. */
2224
val rootPath = File(FFmpegUtil::class.java.protectionDomain.codeSource.location.toURI()).toPath()
2325

backend/src/main/kotlin/dev/dres/api/cli/CompetitionRunCommand.kt

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import dev.dres.data.model.submissions.aspects.TemporalSubmissionAspect
2121
import dev.dres.data.model.submissions.aspects.TextAspect
2222
import dev.dres.run.*
2323
import dev.dres.utilities.extensions.UID
24+
import dev.dres.utilities.extensions.toDateString
2425
import java.io.FileOutputStream
2526
import java.io.OutputStream
2627
import java.nio.file.Files
@@ -129,7 +130,7 @@ class CompetitionRunCommand(internal val runs: DAO<Competition>) : NoOpCliktComm
129130
private val plain by option("-p", "--plain", help = "Plain print. No fancy tables").flag(default = false)
130131
override fun run() {
131132
if (plain) {
132-
this@CompetitionRunCommand.runs.forEach {
133+
this@CompetitionRunCommand.runs.sortedByDescending { it.started }.forEach {
133134
println(
134135
"${
135136
RunSummary(
@@ -151,10 +152,10 @@ class CompetitionRunCommand(internal val runs: DAO<Competition>) : NoOpCliktComm
151152
paddingRight = 1
152153
}
153154
header {
154-
row("id", "name", "description", "lastTask", "status")
155+
row("id", "name", "description", "lastTask", "status", "start", "end")
155156
}
156157
body {
157-
this@CompetitionRunCommand.runs.forEach {
158+
this@CompetitionRunCommand.runs.sortedByDescending { it.started }.forEach {
158159
val status = if (it.hasStarted && !it.hasEnded && !it.isRunning) {
159160
"started"
160161
} else if (it.hasStarted && !it.hasEnded && it.isRunning) {
@@ -172,7 +173,9 @@ class CompetitionRunCommand(internal val runs: DAO<Competition>) : NoOpCliktComm
172173
it.description.description,
173174
if (it is InteractiveSynchronousCompetition) it.currentTask?.description?.name
174175
?: "N/A" else "N/A",
175-
status
176+
status,
177+
it.started?.toDateString() ?: "-",
178+
it.ended?.toDateString() ?: "-",
176179
)
177180
}
178181
}

backend/src/main/kotlin/dev/dres/api/rest/RestApi.kt

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ object RestApi {
5353
* Did you follow our convention?
5454
* - `GET <entity>/{<entityId>}` with entity being an entity of the system in singular. Note the id is prefixed with the entity name
5555
* - `GET <entity>/list` with entity being an entity of the system in singular, returning a list of all of these entities
56-
* - Above naming scheme applies also for nested / context sensitive entities
56+
* - Above naming scheme applies also for nested / context-sensitive entities
5757
* - REST conventions for `POST`, `PATCH` and `DELETE` methods apply
5858
*/
5959
val apiRestHandlers = listOf(
@@ -120,6 +120,11 @@ object RestApi {
120120
dataAccessLayer.mediaSegmentItemIdIndex,
121121
config
122122
),
123+
JsonBatchSubmissionHandler(
124+
dataAccessLayer.collections,
125+
dataAccessLayer.mediaItemCollectionNameIndex,
126+
dataAccessLayer.mediaSegmentItemIdIndex
127+
),
123128

124129
// Log
125130
QueryLogHandler(),
@@ -161,6 +166,7 @@ object RestApi {
161166
OverwriteSubmissionStatusRunAdminHandler(),
162167
ListPastTasksPerTaskRunAdminHandler(),
163168
OverviewRunAdminHandler(),
169+
UpdateRunPropertiesAdminHandler(),
164170

165171
// Judgement
166172
NextOpenJudgementHandler(dataAccessLayer.collections),
@@ -176,14 +182,16 @@ object RestApi {
176182

177183
// Status
178184
CurrentTimeHandler(),
185+
InfoHandler(),
186+
AdminInfoHandler(),
179187

180188
//API Client
181189
ListCompetitionRunClientInfoHandler(),
182190
CompetitionRunClientCurrentTaskInfoHandler(),
183191

184192
// Downloads
185193
DownloadHandler.CompetitionRun(dataAccessLayer.runs),
186-
DownloadHandler.CompetitionRunScore(dataAccessLayer.runs),
194+
DownloadHandler.CompetitionRunScoreHandler(dataAccessLayer.runs),
187195
DownloadHandler.CompetitionDesc(dataAccessLayer.competitions)
188196
)
189197

@@ -249,7 +257,7 @@ object RestApi {
249257
}) from ${it.req.remoteAddr}"
250258
)
251259
if (it.path().startsWith("/api/")) { //do not cache api requests
252-
it.header("Cache-Control", "max-age=0")
260+
it.header("Cache-Control", "no-store")
253261
}
254262
}.error(401) {
255263
it.json(ErrorStatus("Unauthorized request!"))
@@ -298,6 +306,13 @@ object RestApi {
298306

299307
}
300308

309+
private val pool = QueuedThreadPool(
310+
1000, 8, 60000, -1, null, null, NamedThreadFactory("JavalinPool")
311+
)
312+
313+
val readyThreadCount: Int
314+
get() = pool.readyThreads
315+
301316
private fun setupHttpServer(config: Config): Server {
302317

303318
val httpConfig = HttpConfiguration().apply {
@@ -309,9 +324,7 @@ object RestApi {
309324
}
310325
}
311326

312-
val pool = QueuedThreadPool(
313-
1000, 8, 60000, -1, null, null, NamedThreadFactory("JavalinPool")
314-
)
327+
315328

316329
if (config.enableSsl) {
317330
val httpsConfig = HttpConfiguration(httpConfig).apply {

backend/src/main/kotlin/dev/dres/api/rest/handler/AbstractPreviewHandler.kt

Lines changed: 53 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -28,14 +28,15 @@ import java.nio.file.Files
2828
import java.nio.file.Path
2929
import java.nio.file.Paths
3030

31-
abstract class AbstractPreviewHandler(private val collections: DAO<MediaCollection>, private val itemIndex: DaoIndexer<MediaItem, Pair<UID, String>>, config: Config) : GetRestHandler<Any>, AccessManagedRestHandler {
31+
abstract class AbstractPreviewHandler(
32+
private val collections: DAO<MediaCollection>,
33+
private val itemIndex: DaoIndexer<MediaItem, Pair<UID, String>>,
34+
config: Config
35+
) : GetRestHandler<Any>, AccessManagedRestHandler {
3236
override val apiVersion = "v1"
3337
override val permittedRoles = setOf(RestApiRole.VIEWER)
3438
private val cacheLocation = Paths.get(config.cachePath + "/previews")
3539

36-
// private val waitingMap = ConcurrentHashMap<Path, Long>()
37-
// private val timeOut = 10_000
38-
3940
protected fun handlePreviewRequest(collectionId: UID, itemName: String, time: Long?, ctx: Context) {
4041

4142
val item = itemIndex[collectionId to itemName].firstOrNull()
@@ -48,11 +49,13 @@ abstract class AbstractPreviewHandler(private val collections: DAO<MediaCollecti
4849

4950
}
5051

52+
//private val missingImage = this.javaClass.getResourceAsStream("/img/missing.png")!!.readAllBytes()
53+
//private val waitingImage = this.javaClass.getResourceAsStream("/img/loading.png")!!.readAllBytes()
5154

5255
protected fun handlePreviewRequest(item: MediaItem, time: Long?, ctx: Context) {
5356

5457
val collection = this.collections[item.collection]
55-
?: throw ErrorStatusException(404, "Collection ${item.collection} does not exist.", ctx)
58+
?: throw ErrorStatusException(404, "Collection ${item.collection} does not exist.", ctx)
5659

5760
val basePath = File(collection.basePath)
5861

@@ -81,24 +84,13 @@ abstract class AbstractPreviewHandler(private val collections: DAO<MediaCollecti
8184
if (Files.exists(imgPath)) { //if file is available, send contents immediately
8285
ctx.header("Cache-Control", "max-age=31622400")
8386
ctx.sendFile(imgPath.toFile())
84-
}else { //if not, wait for it if necessary
87+
} else { //if not, schedule and return error
8588

8689
FFmpegUtil.extractFrame(Path.of(collection.basePath, item.location), time, imgPath)
87-
88-
val future = FFmpegUtil.previewImageStream(imgPath)
89-
90-
if (future == null) { //image does not exist and is not scheduled
91-
ctx.status(404)
92-
ctx.header("Cache-Control", "max-age=31622400")
93-
ctx.contentType("image/png")
94-
ctx.result(this.javaClass.getResourceAsStream("/img/missing.png")!!)
95-
} else {
96-
ctx.contentType("image/jpg")
97-
ctx.future(future)
98-
}
90+
ctx.status(408)
91+
ctx.header("Cache-Control", "max-age=30")
9992

10093
}
101-
10294
}
10395
}
10496

@@ -108,26 +100,34 @@ abstract class AbstractPreviewHandler(private val collections: DAO<MediaCollecti
108100

109101
}
110102

111-
class MediaPreviewHandler(collections: DAO<MediaCollection>, itemIndex: DaoIndexer<MediaItem, Pair<UID, String>>, config: Config) : AbstractPreviewHandler(collections, itemIndex, config) {
112-
113-
@OpenApi(summary = "Returns a preview image from a collection item",
114-
path = "/api/v1/preview/item/{collection}/{item}/{time}",
115-
pathParams = [
116-
OpenApiParam("collectionId", String::class, "Unique ID of the collection."),
117-
OpenApiParam("item", String::class, "Name of the MediaItem"),
118-
OpenApiParam("time", Long::class, "Time into the video in milliseconds (for videos only).")
119-
],
120-
tags = ["Media"],
121-
responses = [OpenApiResponse("200", [OpenApiContent(type = "image/png")]), OpenApiResponse("401"), OpenApiResponse("400")],
122-
ignore = true
103+
class MediaPreviewHandler(
104+
collections: DAO<MediaCollection>,
105+
itemIndex: DaoIndexer<MediaItem, Pair<UID, String>>,
106+
config: Config
107+
) : AbstractPreviewHandler(collections, itemIndex, config) {
108+
109+
@OpenApi(
110+
summary = "Returns a preview image from a collection item",
111+
path = "/api/v1/preview/item/{collection}/{item}/{time}",
112+
pathParams = [
113+
OpenApiParam("collectionId", String::class, "Unique ID of the collection."),
114+
OpenApiParam("item", String::class, "Name of the MediaItem"),
115+
OpenApiParam("time", Long::class, "Time into the video in milliseconds (for videos only).")
116+
],
117+
tags = ["Media"],
118+
responses = [OpenApiResponse(
119+
"200",
120+
[OpenApiContent(type = "image/png")]
121+
), OpenApiResponse("401"), OpenApiResponse("400")],
122+
ignore = true
123123
)
124124
override fun get(ctx: Context) {
125125

126126
try {
127127
val params = ctx.pathParamMap()
128128

129129
val collectionId = params["collection"]?.UID()
130-
?: throw ErrorStatusException(400, "Collection ID not specified or invalid.", ctx)
130+
?: throw ErrorStatusException(400, "Collection ID not specified or invalid.", ctx)
131131
val itemName = params["item"] ?: throw ErrorStatusException(400, "Item name not specified.", ctx)
132132
val time = params["time"]?.toLongOrNull()
133133

@@ -147,17 +147,25 @@ class MediaPreviewHandler(collections: DAO<MediaCollection>, itemIndex: DaoIndex
147147
}
148148

149149

150-
class SubmissionPreviewHandler(collections: DAO<MediaCollection>, itemIndex: DaoIndexer<MediaItem, Pair<UID, String>>, config: Config) : AbstractPreviewHandler(collections, itemIndex, config) {
151-
152-
@OpenApi(summary = "Returns a preview image for a submission",
153-
path = "/api/v1/preview/submission/{runId}/{submissionId}",
154-
pathParams = [
155-
OpenApiParam("runId", String::class, "Competition Run ID"),
156-
OpenApiParam("submissionId", String::class, "Subission ID")
157-
],
158-
tags = ["Media"],
159-
responses = [OpenApiResponse("200", [OpenApiContent(type = "image/png")]), OpenApiResponse("401"), OpenApiResponse("400")],
160-
ignore = true
150+
class SubmissionPreviewHandler(
151+
collections: DAO<MediaCollection>,
152+
itemIndex: DaoIndexer<MediaItem, Pair<UID, String>>,
153+
config: Config
154+
) : AbstractPreviewHandler(collections, itemIndex, config) {
155+
156+
@OpenApi(
157+
summary = "Returns a preview image for a submission",
158+
path = "/api/v1/preview/submission/{runId}/{submissionId}",
159+
pathParams = [
160+
OpenApiParam("runId", String::class, "Competition Run ID"),
161+
OpenApiParam("submissionId", String::class, "Subission ID")
162+
],
163+
tags = ["Media"],
164+
responses = [OpenApiResponse(
165+
"200",
166+
[OpenApiContent(type = "image/png")]
167+
), OpenApiResponse("401"), OpenApiResponse("400")],
168+
ignore = true
161169
)
162170
override fun get(ctx: Context) {
163171

@@ -187,11 +195,13 @@ class SubmissionPreviewHandler(collections: DAO<MediaCollection>, itemIndex: Dao
187195
if (submission is TemporalSubmissionAspect) submission.start else null, ctx
188196
)
189197
}
198+
190199
is TextAspect -> {
191200
ctx.header("Cache-Control", "max-age=31622400")
192201
ctx.contentType("image/png")
193202
ctx.result(this.javaClass.getResourceAsStream("/img/text.png")!!)
194203
}
204+
195205
else -> {
196206
ctx.header("Cache-Control", "max-age=31622400")
197207
ctx.contentType("image/png")

0 commit comments

Comments
 (0)