Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • justJanne/libquassel
1 result
Show changes
Showing
with 551 additions and 365 deletions
/*
* libquassel
* Copyright (c) 2021 Janne Mareike Koschinski
*
* This Source Code Form is subject to the terms of the Mozilla Public License,
* v. 2.0. If a copy of the MPL was not distributed with this file, You can
* obtain one at https://mozilla.org/MPL/2.0/.
*/
package de.justjanne.libquassel.client.util
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.launch
import org.slf4j.LoggerFactory
import kotlin.coroutines.Continuation
import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine
class CoroutineKeyedQueue<Key, Value> {
private val waiting = mutableMapOf<Key, MutableList<Continuation<Value>>>()
suspend fun wait(vararg keys: Key, beforeWait: (suspend CoroutineScope.() -> Unit)? = null): Value = coroutineScope {
suspendCoroutine { continuation ->
for (key in keys) {
waiting.getOrPut(key, ::mutableListOf).add(continuation)
}
beforeWait?.let { launch(block = it) }
}
}
fun resume(key: Key, value: Value) {
val queue = waiting[key]
if (queue == null) {
logger.warn("Trying to resume message with unknown key: $key")
}
val continuations = queue.orEmpty().distinct()
for (continuation in continuations) {
continuation.resume(value)
}
for (it in waiting.keys) {
waiting[it]?.removeAll(continuations)
}
}
companion object {
private val logger = LoggerFactory.getLogger(CoroutineKeyedQueue::class.java)
}
}
/*
* libquassel
* Copyright (c) 2021 Janne Mareike Koschinski
*
* This Source Code Form is subject to the terms of the Mozilla Public License,
* v. 2.0. If a copy of the MPL was not distributed with this file, You can
* obtain one at https://mozilla.org/MPL/2.0/.
*/
package de.justjanne.libquassel.client
import de.justjanne.libquassel.client.session.ClientSession
import de.justjanne.libquassel.client.testutil.QuasselCoreContainer
import de.justjanne.libquassel.client.testutil.TestX509TrustManager
import de.justjanne.libquassel.protocol.connection.ProtocolFeature
import de.justjanne.libquassel.protocol.connection.ProtocolMeta
import de.justjanne.libquassel.protocol.connection.ProtocolVersion
import de.justjanne.libquassel.protocol.exceptions.HandshakeException
import de.justjanne.libquassel.protocol.features.FeatureSet
import de.justjanne.libquassel.protocol.io.CoroutineChannel
import de.justjanne.libquassel.protocol.models.ids.BufferId
import de.justjanne.libquassel.protocol.session.CoreState
import de.justjanne.testcontainersci.api.providedContainer
import de.justjanne.testcontainersci.extension.CiContainers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withTimeout
import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.assertThrows
import org.slf4j.LoggerFactory
import java.net.InetSocketAddress
import javax.net.ssl.SSLContext
import kotlin.test.assertEquals
@ExperimentalCoroutinesApi
@CiContainers
class ClientTest {
private val quassel = providedContainer("QUASSEL_CONTAINER") {
QuasselCoreContainer()
}
private val logger = LoggerFactory.getLogger(ClientTest::class.java)
private val username = "AzureDiamond"
private val password = "hunter2"
@Test
fun testConnect(): Unit = runBlocking {
val channel = CoroutineChannel()
channel.connect(
InetSocketAddress(
quassel.address,
quassel.getMappedPort(4242)
)
)
val session = ClientSession(
channel, ProtocolFeature.all,
listOf(
ProtocolMeta(
ProtocolVersion.Datastream,
0x0000u
)
),
SSLContext.getInstance("TLSv1.3").apply {
init(null, arrayOf(TestX509TrustManager), null)
}
)
val coreState: CoreState = session.handshakeHandler.init(
"Quasseltest v0.1",
"2021-06-06",
FeatureSet.all()
)
assertTrue(coreState is CoreState.Unconfigured)
assertThrows<HandshakeException.SetupException> {
session.handshakeHandler.configureCore(
username,
password,
"MongoDB",
emptyMap(),
"OAuth2",
emptyMap()
)
}
session.handshakeHandler.configureCore(
username,
password,
"SQLite",
emptyMap(),
"Database",
emptyMap()
)
assertThrows<HandshakeException.LoginException> {
session.handshakeHandler.login("acidburn", "ineverweardresses")
}
session.handshakeHandler.login(username, password)
session.baseInitHandler.waitForInitDone()
logger.trace("Init Done")
withTimeout(5_000L) {
assertEquals(
emptyList(),
session.backlogManager.backlog(bufferId = BufferId(1), limit = 5)
)
logger.trace("Backlog Test #1 Done")
assertEquals(
emptyList(),
session.backlogManager.backlogAll(limit = 5)
)
logger.trace("Backlog Test #2 Done")
assertEquals(
emptyList(),
session.backlogManager.backlogForward(bufferId = BufferId(1), limit = 5)
)
logger.trace("Backlog Test #3 Done")
}
channel.close()
}
}
/*
* libquassel
* Copyright (c) 2024 Janne Mareike Koschinski
*
* This Source Code Form is subject to the terms of the Mozilla Public License,
* v. 2.0. If a copy of the MPL was not distributed with this file, You can
* obtain one at https: //mozilla.org/MPL/2.0/.
*/
package de.justjanne.libquassel.client
import androidx.room.Room
import androidx.sqlite.driver.bundled.BundledSQLiteDriver
import dagger.Binds
import dagger.Component
import dagger.Module
import de.justjanne.libquassel.backend.AliasManagerPersister
import de.justjanne.libquassel.backend.BacklogManagerPersister
import de.justjanne.libquassel.backend.BufferSyncerPersister
import de.justjanne.libquassel.backend.BufferViewConfigPersister
import de.justjanne.libquassel.backend.BufferViewManagerPersister
import de.justjanne.libquassel.backend.CertManagerPersister
import de.justjanne.libquassel.backend.CoreInfoPersister
import de.justjanne.libquassel.backend.HighlightRuleManagerPersister
import de.justjanne.libquassel.backend.IdentityPersister
import de.justjanne.libquassel.backend.IgnoreListManagerPersister
import de.justjanne.libquassel.backend.IrcChannelPersister
import de.justjanne.libquassel.backend.IrcListHelperPersister
import de.justjanne.libquassel.backend.IrcUserPersister
import de.justjanne.libquassel.backend.NetworkConfigPersister
import de.justjanne.libquassel.backend.NetworkPersister
import de.justjanne.libquassel.backend.RpcPersister
import de.justjanne.libquassel.persistence.AppDatabase
import de.justjanne.libquassel.persistence.TodoEntity
import de.justjanne.libquassel.protocol.api.ObjectName
import de.justjanne.libquassel.protocol.api.client.AliasManagerClientApi
import de.justjanne.libquassel.protocol.api.client.BacklogManagerClientApi
import de.justjanne.libquassel.protocol.api.client.BufferSyncerClientApi
import de.justjanne.libquassel.protocol.api.client.BufferViewConfigClientApi
import de.justjanne.libquassel.protocol.api.client.BufferViewManagerClientApi
import de.justjanne.libquassel.protocol.api.client.CertManagerClientApi
import de.justjanne.libquassel.protocol.api.client.CoreInfoClientApi
import de.justjanne.libquassel.protocol.api.client.HighlightRuleManagerClientApi
import de.justjanne.libquassel.protocol.api.client.IdentityClientApi
import de.justjanne.libquassel.protocol.api.client.IgnoreListManagerClientApi
import de.justjanne.libquassel.protocol.api.client.IrcChannelClientApi
import de.justjanne.libquassel.protocol.api.client.IrcListHelperClientApi
import de.justjanne.libquassel.protocol.api.client.IrcUserClientApi
import de.justjanne.libquassel.protocol.api.client.NetworkClientApi
import de.justjanne.libquassel.protocol.api.client.NetworkConfigClientApi
import de.justjanne.libquassel.protocol.api.client.RpcClientApi
import de.justjanne.libquassel.protocol.api.dispatcher.ClientDispatcherModule
import de.justjanne.libquassel.protocol.api.dispatcher.RpcDispatcher
import de.justjanne.libquassel.protocol.api.dispatcher.SyncHandler
import de.justjanne.libquassel.protocol.api.proxy.ClientProxyModule
import de.justjanne.libquassel.protocol.api.proxy.Proxy
import de.justjanne.libquassel.protocol.api.server.AliasManagerServerApi
import de.justjanne.libquassel.protocol.api.server.BacklogManagerServerApi
import de.justjanne.libquassel.protocol.api.server.BufferSyncerServerApi
import de.justjanne.libquassel.protocol.api.server.BufferViewConfigServerApi
import de.justjanne.libquassel.protocol.api.server.BufferViewManagerServerApi
import de.justjanne.libquassel.protocol.api.server.CertManagerServerApi
import de.justjanne.libquassel.protocol.api.server.HighlightRuleManagerServerApi
import de.justjanne.libquassel.protocol.api.server.IdentityServerApi
import de.justjanne.libquassel.protocol.api.server.IgnoreListManagerServerApi
import de.justjanne.libquassel.protocol.api.server.IrcListHelperServerApi
import de.justjanne.libquassel.protocol.api.server.NetworkConfigServerApi
import de.justjanne.libquassel.protocol.api.server.NetworkServerApi
import de.justjanne.libquassel.protocol.exceptions.RpcInvocationFailedException
import de.justjanne.libquassel.protocol.models.ids.BufferId
import de.justjanne.libquassel.protocol.models.ids.MsgId
import de.justjanne.libquassel.protocol.models.types.QtType
import de.justjanne.libquassel.protocol.variant.QVariantList
import de.justjanne.libquassel.protocol.variant.QVariant_
import de.justjanne.libquassel.protocol.variant.qVariant
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.runBlocking
import org.junit.jupiter.api.Test
import javax.inject.Inject
import javax.inject.Singleton
import kotlin.test.assertFails
import kotlin.test.assertFailsWith
@Singleton
class ProxyImpl @Inject constructor() : Proxy {
override fun sync(className: String, objectName: ObjectName, function: String, params: QVariantList) {
println("making sync call: $className $objectName $function $params")
}
override fun rpc(function: String, params: QVariantList) {
println("making rpc call: $function $params")
}
}
@Module
interface ClientModule {
@Binds fun bindAliasManagerPersister(impl: AliasManagerPersister): AliasManagerClientApi
@Binds fun bindBacklogManagerPersister(impl: BacklogManagerPersister): BacklogManagerClientApi
@Binds fun bindBufferSyncerPersister(impl: BufferSyncerPersister): BufferSyncerClientApi
@Binds fun bindBufferViewConfigPersister(impl: BufferViewConfigPersister): BufferViewConfigClientApi
@Binds fun bindBufferViewManagerPersister(impl: BufferViewManagerPersister): BufferViewManagerClientApi
@Binds fun bindCertManagerPersister(impl: CertManagerPersister): CertManagerClientApi
@Binds fun bindCoreInfoPersister(impl: CoreInfoPersister): CoreInfoClientApi
@Binds fun bindHighlightRuleManagerPersister(impl: HighlightRuleManagerPersister): HighlightRuleManagerClientApi
@Binds fun bindIdentityPersister(impl: IdentityPersister): IdentityClientApi
@Binds fun bindIgnoreListManagerPersister(impl: IgnoreListManagerPersister): IgnoreListManagerClientApi
@Binds fun bindIrcChannelPersister(impl: IrcChannelPersister): IrcChannelClientApi
@Binds fun bindIrcListHelperPersister(impl: IrcListHelperPersister): IrcListHelperClientApi
@Binds fun bindIrcUserPersister(impl: IrcUserPersister): IrcUserClientApi
@Binds fun bindNetworkConfigPersister(impl: NetworkConfigPersister): NetworkConfigClientApi
@Binds fun bindNetworkPersister(impl: NetworkPersister): NetworkClientApi
@Binds fun bindRpcPersister(impl: RpcPersister): RpcClientApi
@Binds fun bindProxy(impl: ProxyImpl): Proxy
}
@Singleton
class QuasselApiClient @Inject constructor(
val aliasManager: AliasManagerServerApi,
val backlogManager: BacklogManagerServerApi,
val bufferSyncer: BufferSyncerServerApi,
val bufferViewConfig: BufferViewConfigServerApi,
val bufferViewManager: BufferViewManagerServerApi,
val certManager: CertManagerServerApi,
val highlightRuleManager: HighlightRuleManagerServerApi,
val identity: IdentityServerApi,
val ignoreListManager: IgnoreListManagerServerApi,
val ircListHelper: IrcListHelperServerApi,
val network: NetworkServerApi,
val networkConfig: NetworkConfigServerApi,
)
@Singleton
@Component(modules = [ClientModule::class, ClientDispatcherModule::class, ClientProxyModule::class])
interface ClientComponent {
fun sync(): SyncHandler
fun rpc(): RpcDispatcher
fun api(): QuasselApiClient
}
class QuasselApiTest {
@Test
fun test() {
val client = DaggerClientComponent.builder().build()
client.sync().invoke(
"AliasManager",
ObjectName(""),
"update",
listOf(qVariant(emptyMap<String, QVariant_>(), QtType.QVariantMap))
)
assertFailsWith<RpcInvocationFailedException.UnknownMethodException> {
client.sync().invoke(
"AliasManager",
ObjectName(""),
"unknown",
listOf(qVariant(emptyMap<String, QVariant_>(), QtType.QVariantMap))
)
}
assertFails {
client.sync().invoke("AliasManager", ObjectName(""), "update", emptyList())
}
client.api().aliasManager.requestUpdate(emptyMap())
client.api().aliasManager.addAlias("name", "expansion")
client.api().backlogManager.requestBacklog(BufferId(-1))
client.api().bufferSyncer.requestSetLastSeenMsg(BufferId(1), MsgId(1337))
client.api().bufferViewConfig.requestSetBufferViewName(ObjectName("1"), "test")
client.api().bufferViewManager.requestDeleteBufferView(1)
client.api().certManager.requestUpdate(ObjectName(""), emptyMap())
client.api().highlightRuleManager.requestRemoveHighlightRule(5)
client.api().identity.requestUpdate(ObjectName(""), emptyMap())
println("hi!")
}
@Test
fun testDb() = runBlocking {
val db = Room.inMemoryDatabaseBuilder<AppDatabase>()
.setDriver(BundledSQLiteDriver())
.setQueryCoroutineContext(Dispatchers.IO)
.build()
val dao = db.getDao()
println(dao.count())
dao.insert(
TodoEntity(
title = "kids",
content = "bring kids home from school"
)
)
println(dao.count())
}
}
/*
* libquassel
* Copyright (c) 2021 Janne Mareike Koschinski
*
* This Source Code Form is subject to the terms of the Mozilla Public License,
* v. 2.0. If a copy of the MPL was not distributed with this file, You can
* obtain one at https://mozilla.org/MPL/2.0/.
*/
package de.justjanne.libquassel.client.testutil
import org.slf4j.LoggerFactory
import org.testcontainers.containers.BindMode
import org.testcontainers.containers.GenericContainer
import org.testcontainers.containers.output.Slf4jLogConsumer
import org.testcontainers.utility.DockerImageName
class QuasselCoreContainer : GenericContainer<QuasselCoreContainer>(
DockerImageName.parse("k8r.eu/justjanne/quassel-docker:v0.14.0")
) {
init {
withExposedPorts(QUASSEL_PORT)
withClasspathResourceMapping(
"/quasseltest.crt",
"/quasseltest.crt",
BindMode.READ_WRITE
)
withEnv("SSL_CERT_FILE", "/quasseltest.crt")
withClasspathResourceMapping(
"/quasseltest.key",
"/quasseltest.key",
BindMode.READ_WRITE
)
withEnv("SSL_KEY_FILE", "/quasseltest.key")
withEnv("SSL_REQUIRED", "true")
}
override fun start() {
super.start()
followOutput(Slf4jLogConsumer(logger))
}
companion object {
@JvmStatic
private val logger = LoggerFactory.getLogger(QuasselCoreContainer::class.java)
const val QUASSEL_PORT = 4242
}
}
/*
* libquassel
* Copyright (c) 2021 Janne Mareike Koschinski
*
* This Source Code Form is subject to the terms of the Mozilla Public License,
* v. 2.0. If a copy of the MPL was not distributed with this file, You can
* obtain one at https://mozilla.org/MPL/2.0/.
*/
package de.justjanne.libquassel.client.testutil
import java.security.cert.X509Certificate
import javax.net.ssl.X509TrustManager
object TestX509TrustManager : X509TrustManager {
override fun checkClientTrusted(chain: Array<out X509Certificate>?, authType: String?) {
// FIXME: accept everything
}
override fun checkServerTrusted(chain: Array<out X509Certificate>?, authType: String?) {
// FIXME: accept everything
}
override fun getAcceptedIssuers(): Array<X509Certificate> {
// FIXME: accept nothing
return emptyArray()
}
}
......@@ -9,55 +9,75 @@
package de.justjanne.libquassel.generator
import com.squareup.kotlinpoet.ANY
import com.squareup.kotlinpoet.ClassName
import com.squareup.kotlinpoet.MAP
import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy
import com.squareup.kotlinpoet.STRING
import de.justjanne.libquassel.annotations.ProtocolSide
import com.squareup.kotlinpoet.MemberName.Companion.member
import de.justjanne.libquassel.generator.rpcmodel.RpcModel
import transformName
import de.justjanne.libquassel.generator.util.transformName
object Constants {
fun invokerName(model: RpcModel.ObjectModel, side: ProtocolSide) = ClassName(
TYPENAME_INVOKER.packageName,
"${model.rpcName}${transformName(side.name)}Invoker"
)
fun dispatcherName(
model: RpcModel.ObjectModel,
) = ClassName(
TYPENAME_SYNCDISPATCHER.packageName,
"${model.rpcName}${transformName(model.side.name)}Dispatcher", )
val TYPENAME_ANY = ANY.copy(nullable = true)
val TYPENAME_SYNCABLESTUB = ClassName(
"de.justjanne.libquassel.protocol.syncables",
"SyncableStub"
)
val TYPENAME_INVOKER = ClassName(
"de.justjanne.libquassel.protocol.syncables.invoker",
"Invoker"
)
val TYPENAME_INVOKERREGISTRY = ClassName(
"de.justjanne.libquassel.protocol.syncables.invoker",
"InvokerRegistry"
)
val TYPENAME_INVOKERMAP = MAP.parameterizedBy(STRING, TYPENAME_INVOKER)
val TYPENAME_UNKNOWN_METHOD_EXCEPTION = ClassName(
"de.justjanne.libquassel.protocol.exceptions",
"RpcInvocationFailedException", "UnknownMethodException"
)
val TYPENAME_WRONG_OBJECT_TYPE_EXCEPTION = ClassName(
"de.justjanne.libquassel.protocol.exceptions",
"RpcInvocationFailedException", "WrongObjectTypeException"
)
val TYPENAME_QVARIANTLIST = ClassName(
"de.justjanne.libquassel.protocol.variant",
"QVariantList"
)
val TYPENAME_QVARIANT_INTOORTHROW = ClassName(
"de.justjanne.libquassel.protocol.variant",
"intoOrThrow"
)
val TYPENAME_GENERATED = ClassName(
"de.justjanne.libquassel.annotations",
"Generated"
)
val TYPENAME_DAGGER_MODULE = ClassName("dagger", "Module")
val TYPENAME_DAGGER_PROVIDES = ClassName("dagger", "Provides")
val TYPENAME_DAGGER_BINDS = ClassName("dagger", "Binds")
val TYPENAME_DAGGER_INTOMAP = ClassName("dagger.multibindings", "IntoMap")
val TYPENAME_DAGGER_STRINGKEY = ClassName("dagger.multibindings", "StringKey")
val TYPENAME_JAVAX_INJECT = ClassName("javax.inject", "Inject")
val TYPENAME_PROXY =
ClassName(
"de.justjanne.libquassel.protocol.api.proxy",
"Proxy"
)
val TYPENAME_SYNCDISPATCHER =
ClassName(
"de.justjanne.libquassel.protocol.api.dispatcher",
"SyncDispatcher",
)
val TYPENAME_RPCDISPATCHER =
ClassName(
"de.justjanne.libquassel.protocol.api.dispatcher",
"RpcDispatcher",
)
val TYPENAME_UNKNOWN_METHOD_EXCEPTION =
ClassName(
"de.justjanne.libquassel.protocol.exceptions",
"RpcInvocationFailedException",
"UnknownMethodException",
)
val TYPENAME_WRONG_OBJECT_TYPE_EXCEPTION =
ClassName(
"de.justjanne.libquassel.protocol.exceptions",
"RpcInvocationFailedException",
"WrongObjectTypeException",
)
val TYPENAME_QVARIANTLIST =
ClassName(
"de.justjanne.libquassel.protocol.variant",
"QVariantList",
)
val TYPENAME_QVARIANT_INTOORTHROW =
ClassName(
"de.justjanne.libquassel.protocol.variant",
"intoOrThrow",
)
val TYPENAME_OBJECTNAME =
ClassName(
"de.justjanne.libquassel.protocol.api",
"ObjectName",
)
val MEMBERNAME_OBJECTNAME_EMPTY = TYPENAME_OBJECTNAME.nestedClass("Companion").member("EMPTY")
val TYPENAME_RPCPARAM = ClassName("de.justjanne.libquassel.annotations", "RpcParam")
val TYPENAME_QTTYPE = ClassName("de.justjanne.libquassel.protocol.models.types", "QtType")
val TYPENAME_QUASSELTYPE = ClassName("de.justjanne.libquassel.protocol.models.types", "QuasselType")
val TYPENAME_GENERATED =
ClassName(
"de.justjanne.libquassel.annotations",
"Generated",
)
init {
System.setProperty("idea.io.use.nio2", "true")
......
/*
* libquassel
* Copyright (c) 2021 Janne Mareike Koschinski
*
* This Source Code Form is subject to the terms of the Mozilla Public License,
* v. 2.0. If a copy of the MPL was not distributed with this file, You can
* obtain one at https://mozilla.org/MPL/2.0/.
*/
package de.justjanne.libquassel.generator
import com.squareup.kotlinpoet.AnnotationSpec
import com.squareup.kotlinpoet.ClassName
import com.squareup.kotlinpoet.FileSpec
import com.squareup.kotlinpoet.FunSpec
import com.squareup.kotlinpoet.KModifier
import com.squareup.kotlinpoet.TypeSpec
import de.justjanne.libquassel.annotations.ProtocolSide
import de.justjanne.libquassel.generator.kotlinmodel.KotlinModel
import de.justjanne.libquassel.generator.rpcmodel.RpcModel
import de.justjanne.libquassel.generator.util.transformName
import de.justjanne.libquassel.generator.visitors.DispatcherGenerator
object DispatcherModuleGenerator {
fun generateModule(side: ProtocolSide, objects: List<RpcModel.ObjectModel>): KotlinModel.FileModel? {
if (objects.isEmpty()) return null
val name =
ClassName(
Constants.TYPENAME_SYNCDISPATCHER.packageName,
"${transformName(side.name)}DispatcherModule",
)
return KotlinModel.FileModel(
objects.map(RpcModel.ObjectModel::source),
FileSpec.builder(name.packageName, name.simpleName)
.addType(
TypeSpec.interfaceBuilder(name)
.addAnnotation(Constants.TYPENAME_DAGGER_MODULE)
.addFunctions(
objects
.filter { it.side != side }
.map {
if (it.rpcName.isEmpty()) {
val className = DispatcherGenerator.toClassName(it)
FunSpec.builder("provide${Constants.dispatcherName(it).simpleName}")
.addModifiers(KModifier.ABSTRACT)
.addParameter("impl", className)
.returns(Constants.TYPENAME_RPCDISPATCHER)
.addAnnotation(Constants.TYPENAME_DAGGER_BINDS)
.build()
} else {
val className = DispatcherGenerator.toClassName(it)
FunSpec.builder("provide${Constants.dispatcherName(it).simpleName}")
.addModifiers(KModifier.ABSTRACT)
.addParameter("impl", className)
.returns(Constants.TYPENAME_SYNCDISPATCHER)
.addAnnotation(Constants.TYPENAME_DAGGER_BINDS)
.addAnnotation(Constants.TYPENAME_DAGGER_INTOMAP)
.addAnnotation(
AnnotationSpec.builder(Constants.TYPENAME_DAGGER_STRINGKEY).addMember("%S", it.rpcName).build()
)
.build()
}
}
)
.build(),
).build(),
)
}
}
......@@ -15,32 +15,31 @@ import com.google.devtools.ksp.processing.Resolver
import com.google.devtools.ksp.processing.SymbolProcessor
import com.google.devtools.ksp.symbol.KSAnnotated
import de.justjanne.libquassel.annotations.ProtocolSide
import de.justjanne.libquassel.annotations.SyncedObject
import de.justjanne.libquassel.generator.visitors.KSDeclarationParser
import de.justjanne.libquassel.annotations.RpcApi
import de.justjanne.libquassel.generator.visitors.KotlinModelGenerator
import de.justjanne.libquassel.generator.visitors.KotlinSaver
import de.justjanne.libquassel.generator.visitors.RpcModelProcessor
import de.justjanne.libquassel.generator.visitors.RpcObjectCollector
import de.justjanne.libquassel.generator.visitors.DispatcherGenerator
import de.justjanne.libquassel.generator.visitors.ProxyGenerator
import de.justjanne.libquassel.generator.visitors.RpcModelCollector
class InvokerProcessor(
private val codeGenerator: CodeGenerator,
private val logger: KSPLogger
private val logger: KSPLogger,
) : SymbolProcessor {
override fun process(resolver: Resolver): List<KSAnnotated> {
val annotationModels = resolver.getSymbolsWithAnnotation(SyncedObject::class.java.canonicalName)
val rpcModels = annotationModels.mapNotNull { it.accept(KSDeclarationParser(resolver, logger), Unit) }
val invokerFiles = rpcModels.flatMap {
listOfNotNull(
it.accept(RpcModelProcessor(), ProtocolSide.CLIENT),
it.accept(RpcModelProcessor(), ProtocolSide.CORE),
) + InvokerRegistryGenerator.generateRegistry(
RpcObjectCollector().apply {
rpcModels.forEach { it.accept(this, Unit) }
}.objects
)
}
invokerFiles.forEach {
it.accept(KotlinSaver(), codeGenerator)
}
val annotationModels = resolver.getSymbolsWithAnnotation(RpcApi::class.java.canonicalName)
val rpcModels = annotationModels.mapNotNull { it.accept(KotlinModelGenerator(resolver, logger), Unit) }
val rpcObjects = RpcModelCollector().apply {
rpcModels.forEach { it.accept(this) }
}.objects
rpcModels.flatMap { listOf(it.accept(DispatcherGenerator()), it.accept(ProxyGenerator())) }
.plusElement(DispatcherModuleGenerator.generateModule(ProtocolSide.CLIENT, rpcObjects))
.plusElement(DispatcherModuleGenerator.generateModule(ProtocolSide.CORE, rpcObjects))
.plusElement(ProxyModuleGenerator.generateModule(ProtocolSide.CLIENT, rpcObjects))
.plusElement(ProxyModuleGenerator.generateModule(ProtocolSide.CORE, rpcObjects))
.filterNotNull()
.forEach { it.accept(KotlinSaver(), codeGenerator) }
return emptyList()
}
......
......@@ -13,8 +13,9 @@ import com.google.devtools.ksp.processing.SymbolProcessorEnvironment
import com.google.devtools.ksp.processing.SymbolProcessorProvider
class InvokerProcessorProvider : SymbolProcessorProvider {
override fun create(environment: SymbolProcessorEnvironment) = InvokerProcessor(
environment.codeGenerator,
environment.logger
)
override fun create(environment: SymbolProcessorEnvironment) =
InvokerProcessor(
environment.codeGenerator,
environment.logger,
)
}
......@@ -11,57 +11,44 @@ package de.justjanne.libquassel.generator
import com.squareup.kotlinpoet.ClassName
import com.squareup.kotlinpoet.FileSpec
import com.squareup.kotlinpoet.FunSpec
import com.squareup.kotlinpoet.KModifier
import com.squareup.kotlinpoet.PropertySpec
import com.squareup.kotlinpoet.TypeSpec
import com.squareup.kotlinpoet.buildCodeBlock
import de.justjanne.libquassel.annotations.ProtocolSide
import de.justjanne.libquassel.generator.kotlinmodel.KotlinModel
import de.justjanne.libquassel.generator.rpcmodel.RpcModel
import de.justjanne.libquassel.generator.util.kotlinpoet.withIndent
import de.justjanne.libquassel.generator.util.transformName
import de.justjanne.libquassel.generator.visitors.ProxyGenerator
object InvokerRegistryGenerator {
private fun generateCodeBlock(
objects: List<RpcModel.ObjectModel>,
side: ProtocolSide
) = buildCodeBlock {
add("mapOf(\n")
withIndent {
for (syncable in objects) {
addStatement("%S to %T,", syncable.rpcName, Constants.invokerName(syncable, side))
}
}
if (objects.isEmpty()) {
add("\n")
}
add(")")
}
fun generateRegistry(objects: List<RpcModel.ObjectModel>): KotlinModel.FileModel {
val name = ClassName(
Constants.TYPENAME_INVOKER.packageName,
"GeneratedInvokerRegistry"
)
object ProxyModuleGenerator {
fun generateModule(side: ProtocolSide, objects: List<RpcModel.ObjectModel>): KotlinModel.FileModel? {
if (objects.isEmpty()) return null
val name =
ClassName(
Constants.TYPENAME_PROXY.packageName,
"${transformName(side.name)}ProxyModule",
)
return KotlinModel.FileModel(
objects.map(RpcModel.ObjectModel::source),
FileSpec.builder(name.packageName, name.simpleName)
.addType(
TypeSpec.objectBuilder("GeneratedInvokerRegistry")
.addSuperinterface(Constants.TYPENAME_INVOKERREGISTRY)
.addProperty(
PropertySpec.builder("clientInvokers", Constants.TYPENAME_INVOKERMAP)
.addModifiers(KModifier.OVERRIDE)
.initializer(generateCodeBlock(objects, ProtocolSide.CLIENT))
.build()
)
.addProperty(
PropertySpec.builder("coreInvokers", Constants.TYPENAME_INVOKERMAP)
.addModifiers(KModifier.OVERRIDE)
.initializer(generateCodeBlock(objects, ProtocolSide.CORE))
.build()
TypeSpec.interfaceBuilder(name)
.addAnnotation(Constants.TYPENAME_DAGGER_MODULE)
.addFunctions(
objects
.filter { it.side == side }
.map {
val className = ProxyGenerator.toClassName(it)
FunSpec.builder("provide${Constants.dispatcherName(it).simpleName}")
.addModifiers(KModifier.ABSTRACT)
.addParameter("impl", className)
.returns(it.name)
.addAnnotation(Constants.TYPENAME_DAGGER_BINDS)
.build()
}
)
.build()
).build()
.build(),
).build(),
)
}
}
......@@ -11,26 +11,34 @@ package de.justjanne.libquassel.generator.annotation
import com.google.devtools.ksp.processing.Resolver
import com.google.devtools.ksp.symbol.KSAnnotated
import com.google.devtools.ksp.symbol.KSType
import de.justjanne.libquassel.annotations.ProtocolSide
import de.justjanne.libquassel.annotations.SyncedCall
import de.justjanne.libquassel.generator.util.findAnnotationWithType
import de.justjanne.libquassel.generator.util.getMember
import de.justjanne.libquassel.generator.util.toEnum
import de.justjanne.libquassel.annotations.RpcCall
import de.justjanne.libquassel.generator.util.ksp.findAnnotationWithType
import de.justjanne.libquassel.generator.util.ksp.getMember
data class RpcFunctionAnnotation(
val name: String?,
val target: ProtocolSide?
val type: Type
) {
companion object {
fun of(it: KSAnnotated, resolver: Resolver): RpcFunctionAnnotation? {
val annotation = it.findAnnotationWithType<SyncedCall>(resolver)
?: return null
return RpcFunctionAnnotation(
name = annotation.getMember<String>("name")?.ifBlank { null },
target = annotation.getMember<KSType>("target")
?.toEnum<ProtocolSide>(),
)
}
fun of(
it: KSAnnotated,
resolver: Resolver,
): RpcFunctionAnnotation? =
it.findAnnotationWithType<RpcCall>(resolver)?.let { annotation ->
RpcFunctionAnnotation(
name = annotation.getMember<String>("name")?.ifBlank { null },
type = Type.RPC,
)
} ?: it.findAnnotationWithType<RpcCall>(resolver)?.let { annotation ->
RpcFunctionAnnotation(
name = annotation.getMember<String>("name")?.ifBlank { null },
type = Type.SYNC,
)
}
}
enum class Type {
RPC,
SYNC
}
}
......@@ -11,19 +11,30 @@ package de.justjanne.libquassel.generator.annotation
import com.google.devtools.ksp.processing.Resolver
import com.google.devtools.ksp.symbol.KSAnnotated
import de.justjanne.libquassel.annotations.SyncedObject
import de.justjanne.libquassel.generator.util.findAnnotationWithType
import de.justjanne.libquassel.generator.util.getMember
import com.google.devtools.ksp.symbol.KSType
import de.justjanne.libquassel.annotations.ProtocolSide
import de.justjanne.libquassel.annotations.RpcApi
import de.justjanne.libquassel.generator.util.ksp.findAnnotationWithType
import de.justjanne.libquassel.generator.util.ksp.getMember
import de.justjanne.libquassel.generator.util.ksp.toEnum
data class RpcObjectAnnotation(
val name: String?
val name: String,
val side: ProtocolSide,
) {
companion object {
fun of(it: KSAnnotated, resolver: Resolver): RpcObjectAnnotation? {
val annotation = it.findAnnotationWithType<SyncedObject>(resolver)
?: return null
fun of(
it: KSAnnotated,
resolver: Resolver,
): RpcObjectAnnotation? {
val annotation =
it.findAnnotationWithType<RpcApi>(resolver)
?: return null
return RpcObjectAnnotation(
name = annotation.getMember("name"),
name = annotation.getMember("name")
?: return null,
side = annotation.getMember<KSType>("side")?.toEnum<ProtocolSide>()
?: return null,
)
}
}
......
/*
* libquassel
* Copyright (c) 2024 Janne Mareike Koschinski
*
* This Source Code Form is subject to the terms of the Mozilla Public License,
* v. 2.0. If a copy of the MPL was not distributed with this file, You can
* obtain one at https://mozilla.org/MPL/2.0/.
*/
package de.justjanne.libquassel.generator.annotation
import com.google.devtools.ksp.symbol.KSAnnotated
import com.squareup.kotlinpoet.ClassName
import com.squareup.kotlinpoet.MemberName
import com.squareup.kotlinpoet.MemberName.Companion.member
import de.justjanne.libquassel.generator.Constants
import de.justjanne.libquassel.generator.util.ksp.asClassName
data class RpcParameterAnnotation(
val type: MemberName
) {
companion object {
private fun remapTypes(type: ClassName): ClassName? = when (type) {
Constants.TYPENAME_RPCPARAM.nestedClass("UserType") -> Constants.TYPENAME_QUASSELTYPE
Constants.TYPENAME_RPCPARAM -> Constants.TYPENAME_QTTYPE
else -> type.enclosingClassName()?.let {
remapTypes(it)?.nestedClass(type.simpleName)
}
}
fun of(
it: KSAnnotated,
): RpcParameterAnnotation? =
it.annotations.mapNotNull {
val annotationType = it.annotationType.resolve().asClassName()
annotationType.enclosingClassName()
?.let { remapTypes(it) }
?.let { RpcParameterAnnotation(it.member(annotationType.simpleName)) }
}.firstOrNull()
}
}
......@@ -17,19 +17,26 @@ import com.squareup.kotlinpoet.FileSpec
sealed class KotlinModel {
data class FileModel(
val source: List<KSClassDeclaration>,
val data: FileSpec
val data: FileSpec,
) : KotlinModel() {
override fun <D, R> accept(visitor: KotlinModelVisitor<D, R>, data: D) =
visitor.visitFileModel(this, data)
override fun <D, R> accept(
visitor: KotlinModelVisitor<D, R>,
data: D,
) = visitor.visitFileModel(this, data)
}
data class FunctionModel(
val source: KSFunctionDeclaration,
val data: CodeBlock
val data: CodeBlock,
) : KotlinModel() {
override fun <D, R> accept(visitor: KotlinModelVisitor<D, R>, data: D) =
visitor.visitFunctionModel(this, data)
override fun <D, R> accept(
visitor: KotlinModelVisitor<D, R>,
data: D,
) = visitor.visitFunctionModel(this, data)
}
abstract fun <D, R> accept(visitor: KotlinModelVisitor<D, R>, data: D): R
abstract fun <D, R> accept(
visitor: KotlinModelVisitor<D, R>,
data: D,
): R
}
......@@ -10,6 +10,13 @@
package de.justjanne.libquassel.generator.kotlinmodel
interface KotlinModelVisitor<D, R> {
fun visitFileModel(model: KotlinModel.FileModel, data: D): R
fun visitFunctionModel(model: KotlinModel.FunctionModel, data: D): R
fun visitFileModel(
model: KotlinModel.FileModel,
data: D,
): R
fun visitFunctionModel(
model: KotlinModel.FunctionModel,
data: D,
): R
}