+1
darkfeed/build.gradle.kts
+1
darkfeed/build.gradle.kts
+3
-1
darkfeed/src/main/kotlin/Main.kt
+3
-1
darkfeed/src/main/kotlin/Main.kt
+46
-2
darkfeed/src/main/kotlin/api/AtpIdentityClient.kt
+46
-2
darkfeed/src/main/kotlin/api/AtpIdentityClient.kt
···
1
1
package rs.averyrive.darkfeed.api
2
2
3
+
import com.mayakapps.kache.InMemoryKache
3
4
import io.ktor.client.*
4
5
import io.ktor.client.call.*
5
6
import io.ktor.client.engine.cio.*
···
14
15
import kotlinx.serialization.json.Json
15
16
import org.slf4j.LoggerFactory
16
17
import rs.averyrive.darkfeed.api.model.*
18
+
import kotlin.time.Duration.Companion.hours
19
+
20
+
// Specify size of each cache
21
+
const val CACHE_SIZE: Long = 1 * 1024 * 1024 // 1 MB
17
22
18
23
/** Client that handles identity related requests for ATProto accounts. */
19
24
class AtpIdentityClient {
···
32
37
install(Logging)
33
38
}
34
39
40
+
/** Cache of handle to DID relationships. */
41
+
private val handleDidCache = InMemoryKache<String, Did>(CACHE_SIZE) {
42
+
expireAfterWriteDuration = 1.hours
43
+
}
44
+
45
+
/** Cache of DID to PDS host relationships. */
46
+
private val didPdsCache = InMemoryKache<Did, String>(CACHE_SIZE) {
47
+
expireAfterWriteDuration = 1.hours
48
+
}
49
+
35
50
/**
36
51
* Resolve a handle's DID.
37
52
*
···
46
61
*/
47
62
suspend fun resolveDidFromHandle(handle: String): Did {
48
63
log.debug("Resolving DID for '{}'...", handle)
64
+
65
+
// Return cached DID if it exists.
66
+
handleDidCache.get(handle)?.let { did ->
67
+
log.debug("Returning cached DID for '{}': {}", handle, did.toString())
68
+
return did
69
+
}
49
70
50
71
@Serializable
51
72
data class ResolveHandleResponse(val did: String)
···
101
122
else -> throw RuntimeException("Received unexpected response: ${resolveHandleResponse.bodyAsText()}")
102
123
}
103
124
104
-
log.debug("Resolved DID for '{}': {}", handle, did.toString())
125
+
// Cache the DID for this handle.
126
+
handleDidCache.put(handle, did)
127
+
log.debug("Resolved and cached DID for '{}': {}", handle, did.toString())
105
128
106
129
return did
107
130
}
···
120
143
*/
121
144
suspend fun resolvePdsFromDid(did: Did): String {
122
145
log.debug("Resolving PDS for '{}'...", did.toString())
146
+
147
+
// Return cached PDS host if it exists.
148
+
didPdsCache.get(did)?.let { pdsHost ->
149
+
log.debug("Returning cached PDS host for '{}': {}", did.toString(), pdsHost)
150
+
return pdsHost
151
+
}
123
152
124
153
// Get the DID document, using either `plc.directory` or
125
154
// `/.well-known/did.json` depending on the type of DID.
···
166
195
?.serviceEndpoint
167
196
?: throw PdsNotFoundException(did)
168
197
169
-
log.debug("Resolved PDS for '{}': {}", did.toString(), pdsHost)
198
+
// Cache the PDS for this DID.
199
+
didPdsCache.put(did, pdsHost)
200
+
log.debug("Resolved and cached PDS for '{}': {}", did.toString(), pdsHost)
170
201
171
202
return pdsHost
203
+
}
204
+
205
+
/**
206
+
* Invalidate the PDS associated with the given DID.
207
+
*
208
+
* This should be used when a call to the PDS fails due to the PDS no longer
209
+
* being available. This may indicate that the PDS has moved and should be
210
+
* resolved again.
211
+
*
212
+
* @param did DID to invalidate.
213
+
*/
214
+
suspend fun invalidateCachedPds(did: Did) {
215
+
didPdsCache.remove(did)
172
216
}
173
217
}
174
218
+2
darkfeed/src/main/kotlin/api/BskyApi.kt
+2
darkfeed/src/main/kotlin/api/BskyApi.kt
···
92
92
val record: Generator,
93
93
)
94
94
95
+
// TODO: If this PDS is not accessible, invalidate cache and retry.
95
96
val ownerPdsHost = atpIdentityClient.resolvePdsFromDid(authManager.authAccountDid.toDid())
96
97
97
98
val response = httpClient.post {
···
116
117
@Serializable
117
118
data class Response(val cursor: String?, val records: List<LikeRef>)
118
119
120
+
// TODO: If this PDS is not accessible, invalidate cache and retry.
119
121
val requestorPdsHost = atpIdentityClient.resolvePdsFromDid(actor.toDid())
120
122
121
123
val response = httpClient.get {
+2
gradle/libs.versions.toml
+2
gradle/libs.versions.toml
···
4
4
ktor = "3.0.1"
5
5
auth0 = "4.4.0"
6
6
clikt = "5.0.1"
7
+
kache = "2.1.1"
7
8
8
9
[libraries]
9
10
logback-classic = { group = "ch.qos.logback", name = "logback-classic", version.ref = "logback" }
···
18
19
ktor-serialization-kotlinx-json = { group = "io.ktor", name = "ktor-serialization-kotlinx-json", version.ref = "ktor" }
19
20
java-jwt = { group = "com.auth0", name = "java-jwt", version.ref = "auth0" }
20
21
clikt = { group = "com.github.ajalt.clikt", name = "clikt", version.ref = "clikt" }
22
+
kache = { group = "com.mayakapps.kache", name = "kache", version.ref = "kache" }
21
23
22
24
[plugins]
23
25
kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }