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+ }
0 commit comments