Skip to content

Commit aaf53ec

Browse files
committed
Add AoC input API code
1 parent 3f5fb49 commit aaf53ec

File tree

3 files changed

+169
-0
lines changed

3 files changed

+169
-0
lines changed

build.gradle.kts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
plugins {
2+
kotlin("jvm") version libs.versions.kotlin
3+
id("maven-publish")
4+
}
5+
6+
group = "com.github.jsoberg"
7+
version = "0.1"
8+
9+
repositories {
10+
mavenCentral()
11+
}
12+
13+
dependencies {
14+
implementation(libs.kotlin.stdlib)
15+
implementation(libs.kotlin.coroutines)
16+
implementation(libs.ktor.client.core)
17+
implementation(libs.ktor.client.okhttp)
18+
19+
testImplementation(libs.test.assertk)
20+
testImplementation(libs.test.junitJupiter)
21+
testImplementation(libs.test.kotlin.coroutines)
22+
}
23+
24+
tasks.withType<Test> {
25+
useJUnitPlatform()
26+
}
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
package com.soberg.kotlin.aoc.api
2+
3+
import io.ktor.client.HttpClient
4+
import io.ktor.client.request.get
5+
import io.ktor.client.request.header
6+
import io.ktor.client.statement.HttpResponse
7+
import io.ktor.client.statement.bodyAsText
8+
import java.nio.file.Files
9+
import kotlin.io.path.Path
10+
import kotlin.io.path.createFile
11+
import kotlin.io.path.exists
12+
import kotlin.io.path.readLines
13+
import kotlin.io.path.writeLines
14+
15+
object AdventOfCodeInputApi {
16+
17+
suspend fun readInputFromNetwork(
18+
cachingStrategy: CachingStrategy,
19+
year: Int,
20+
day: Int,
21+
sessionToken: String,
22+
): Result<List<String>> {
23+
// If cache exists, read from it and return immediately.
24+
readFromCache(cachingStrategy, year, day)?.let { cachedLines ->
25+
return Result.success(cachedLines)
26+
}
27+
28+
return runCatching {
29+
tryReadInputFromNetwork(year, day, sessionToken)
30+
}.onSuccess { lines ->
31+
// Side effect, store in cache.
32+
writeToCache(cachingStrategy, year, day, lines)
33+
}
34+
}
35+
36+
private fun readFromCache(
37+
cachingStrategy: CachingStrategy,
38+
year: Int,
39+
day: Int,
40+
): List<String>? = when (cachingStrategy) {
41+
CachingStrategy.None -> null
42+
is CachingStrategy.LocalTextFile -> {
43+
val path = Path(cachingStrategy.cacheDirPath, "$year", "$day.txt")
44+
if (path.exists()) {
45+
path.readLines()
46+
} else {
47+
null
48+
}
49+
}
50+
}
51+
52+
private suspend fun tryReadInputFromNetwork(
53+
year: Int,
54+
day: Int,
55+
sessionToken: String,
56+
): List<String> {
57+
val response: HttpResponse = AdventOfCodeKtorClient.create().use { client ->
58+
readInputFromNetwork(client, year, day, sessionToken)
59+
}
60+
if (response.status.value in 200..299) {
61+
return response.bodyAsText().lines()
62+
} else {
63+
error("Unexpected response code ${response.status.value}")
64+
}
65+
}
66+
67+
private suspend fun readInputFromNetwork(
68+
client: HttpClient,
69+
year: Int,
70+
day: Int,
71+
sessionToken: String,
72+
) = client.get("https://adventofcode.com/$year/day/$day/input") {
73+
header("Cookie", "session=$sessionToken")
74+
}
75+
76+
private fun writeToCache(
77+
cachingStrategy: CachingStrategy,
78+
year: Int,
79+
day: Int,
80+
lines: List<String>,
81+
) {
82+
when (cachingStrategy) {
83+
is CachingStrategy.LocalTextFile -> {
84+
val path = Path(cachingStrategy.cacheDirPath, "$year", "$day.txt")
85+
if (!path.exists()) {
86+
Files.createDirectories(path.parent)
87+
path.createFile()
88+
}
89+
path.writeLines(lines)
90+
}
91+
92+
CachingStrategy.None -> {
93+
/* Do nothing. */
94+
}
95+
}
96+
}
97+
98+
sealed interface CachingStrategy {
99+
/** Don't try to cache this to a local file, instead just always read from network. */
100+
data object None : CachingStrategy
101+
102+
/** Cache to a local text file from network on first read, then return from the local text file. */
103+
data class LocalTextFile(
104+
val cacheDirPath: String,
105+
) : CachingStrategy
106+
}
107+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package com.soberg.kotlin.aoc.api
2+
3+
import io.ktor.client.HttpClient
4+
import io.ktor.client.HttpClientConfig
5+
import io.ktor.client.engine.HttpClientEngine
6+
import io.ktor.client.engine.HttpClientEngineConfig
7+
import io.ktor.client.engine.HttpClientEngineFactory
8+
import io.ktor.client.engine.okhttp.OkHttp
9+
import io.ktor.client.plugins.HttpTimeout
10+
import io.ktor.client.plugins.defaultRequest
11+
import io.ktor.http.ContentType
12+
import io.ktor.http.contentType
13+
import kotlin.time.Duration.Companion.seconds
14+
15+
internal object AdventOfCodeKtorClient {
16+
private val Timeout = 20.seconds
17+
18+
fun create(
19+
engineFactory: HttpClientEngineFactory<HttpClientEngineConfig> = OkHttp,
20+
) = HttpClient(engineFactory) { applyConfig() }
21+
22+
fun create(
23+
engine: HttpClientEngine,
24+
) = HttpClient(engine) { applyConfig() }
25+
26+
private fun HttpClientConfig<*>.applyConfig() {
27+
install(HttpTimeout) {
28+
socketTimeoutMillis = Timeout.inWholeMilliseconds
29+
requestTimeoutMillis = Timeout.inWholeMilliseconds
30+
connectTimeoutMillis = Timeout.inWholeMilliseconds
31+
}
32+
defaultRequest {
33+
contentType(ContentType.Text.Plain)
34+
}
35+
}
36+
}

0 commit comments

Comments
 (0)