diff --git a/libquassel-api/src/main/kotlin/de/justjanne/libquassel/protocol/api/dto/BufferSyncerDto.kt b/libquassel-api/src/main/kotlin/de/justjanne/libquassel/protocol/api/dto/BufferSyncerDto.kt
new file mode 100644
index 0000000000000000000000000000000000000000..2791ced5a8aa64c20590c9a56bbb42abff5ba82c
--- /dev/null
+++ b/libquassel-api/src/main/kotlin/de/justjanne/libquassel/protocol/api/dto/BufferSyncerDto.kt
@@ -0,0 +1,107 @@
+/*
+ * libquassel
+ * Copyright (c) 2025 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.protocol.api.dto
+
+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.models.types.QuasselType
+import de.justjanne.libquassel.protocol.util.collections.pairs
+import de.justjanne.libquassel.protocol.variant.QVariantList
+import de.justjanne.libquassel.protocol.variant.QVariantMap
+import de.justjanne.libquassel.protocol.variant.QVariant_
+import de.justjanne.libquassel.protocol.variant.into
+import de.justjanne.libquassel.protocol.variant.qVariant
+
+private typealias BufferMap = Map<BufferId, QVariant_>
+
+private fun BufferMap.toVariantList(): QVariantList = entries.flatMap {
+  listOf(qVariant(it.key, QuasselType.BufferId), it.value)
+}
+
+private fun QVariantList.toBufferMap(): BufferMap = pairs { key, value ->
+  Pair(key.into<BufferId>() ?: return@pairs null, value)
+}.filterNotNull().toMap()
+
+data class BufferSyncerDto(
+  val activities: Map<BufferId, Int>,
+  val highlightCounts: Map<BufferId, Int>,
+  val lastSeenMsg: Map<BufferId, MsgId>,
+  val markerLines: Map<BufferId, MsgId>,
+) {
+
+  fun serialize(): QVariantMap = mapOf(
+    "Activities" to qVariant(
+      activities.flatMap {
+        listOf(
+          qVariant(it.key, QuasselType.BufferId),
+          qVariant(it.value, QtType.Int),
+        )
+      },
+      QtType.QVariantList
+    ),
+    "HighlightCounts" to qVariant(
+      highlightCounts.flatMap {
+        listOf(
+          qVariant(it.key, QuasselType.BufferId),
+          qVariant(it.value, QtType.Int),
+        )
+      },
+      QtType.QVariantList
+    ),
+    "LastSeenMsg" to qVariant(
+      lastSeenMsg.flatMap {
+        listOf(
+          qVariant(it.key, QuasselType.BufferId),
+          qVariant(it.value, QuasselType.MsgId),
+        )
+      },
+      QtType.QVariantList
+    ),
+    "MarkerLines" to qVariant(
+      markerLines.flatMap {
+        listOf(
+          qVariant(it.key, QuasselType.BufferId),
+          qVariant(it.value, QuasselType.MsgId),
+        )
+      },
+      QtType.QVariantList
+    ),
+  )
+
+  companion object {
+    fun deserialize(data: QVariantMap) = BufferSyncerDto(
+      activities = data["Activities"].into<QVariantList>().orEmpty().pairs { key, value ->
+        Pair(
+          key.into<BufferId>() ?: return@pairs null,
+          value.into<Int>() ?: return@pairs null
+        )
+      }.filterNotNull().toMap(),
+      highlightCounts = data["HighlightCounts"].into<QVariantList>().orEmpty().pairs { key, value ->
+        Pair(
+          key.into<BufferId>() ?: return@pairs null,
+          value.into<Int>() ?: return@pairs null
+        )
+      }.filterNotNull().toMap(),
+      lastSeenMsg = data["LastSeenMsg"].into<QVariantList>().orEmpty().pairs { key, value ->
+        Pair(
+          key.into<BufferId>() ?: return@pairs null,
+          value.into<MsgId>() ?: return@pairs null
+        )
+      }.filterNotNull().toMap(),
+      markerLines = data["MarkerLines"].into<QVariantList>().orEmpty().pairs { key, value ->
+        Pair(
+          key.into<BufferId>() ?: return@pairs null,
+          value.into<MsgId>() ?: return@pairs null
+        )
+      }.filterNotNull().toMap(),
+    )
+  }
+}
diff --git a/libquassel-api/src/main/kotlin/de/justjanne/libquassel/protocol/api/proxy/Proxy.kt b/libquassel-api/src/main/kotlin/de/justjanne/libquassel/protocol/api/proxy/Proxy.kt
index b7fc7f6895b42e64cf24d5b9a5c1fa6392d5f5c6..51aa0e678904d6f0ab3e15855219d09a9c6b1917 100644
--- a/libquassel-api/src/main/kotlin/de/justjanne/libquassel/protocol/api/proxy/Proxy.kt
+++ b/libquassel-api/src/main/kotlin/de/justjanne/libquassel/protocol/api/proxy/Proxy.kt
@@ -14,11 +14,11 @@ import de.justjanne.libquassel.protocol.variant.QVariantList
 import de.justjanne.libquassel.protocol.variant.QVariant_
 
 interface Proxy {
-  fun sync(className: String, objectName: ObjectName, function: String, params: QVariantList)
-  fun sync(className: String, objectName: ObjectName, function: String, vararg arg: QVariant_) =
+  suspend fun sync(className: String, objectName: ObjectName, function: String, params: QVariantList)
+  suspend fun sync(className: String, objectName: ObjectName, function: String, vararg arg: QVariant_) =
     sync(className, objectName, function, arg.toList())
 
-  fun rpc(function: String, params: QVariantList)
-  fun rpc(function: String, vararg arg: QVariant_) =
+  suspend fun rpc(function: String, params: QVariantList)
+  suspend fun rpc(function: String, vararg arg: QVariant_) =
     rpc(function, arg.toList())
 }
diff --git a/libquassel-client/src/main/kotlin/de/justjanne/libquassel/backend/BufferSyncerPersister.kt b/libquassel-client/src/main/kotlin/de/justjanne/libquassel/backend/BufferSyncerPersister.kt
index c28b7f8f6b575b9190a0a900478d9da7f0a899a5..43bf4f468f652ca282e74150ca0b20d22676a143 100644
--- a/libquassel-client/src/main/kotlin/de/justjanne/libquassel/backend/BufferSyncerPersister.kt
+++ b/libquassel-client/src/main/kotlin/de/justjanne/libquassel/backend/BufferSyncerPersister.kt
@@ -9,20 +9,52 @@
 
 package de.justjanne.libquassel.backend
 
+import de.justjanne.libquassel.persistence.BufferRepository
 import de.justjanne.libquassel.protocol.api.client.BufferSyncerClientApi
+import de.justjanne.libquassel.protocol.api.dto.BufferSyncerDto
 import de.justjanne.libquassel.protocol.models.ids.BufferId
 import de.justjanne.libquassel.protocol.models.ids.MsgId
 import de.justjanne.libquassel.protocol.variant.QVariantMap
 import javax.inject.Inject
 
-class BufferSyncerPersister @Inject constructor() : BufferSyncerClientApi {
+class BufferSyncerPersister @Inject constructor(
+  private val repository: BufferRepository,
+) : BufferSyncerClientApi {
+  override suspend fun update(properties: QVariantMap) {
+    val data = BufferSyncerDto.deserialize(properties)
+    repository.syncActivites(data.activities)
+    repository.syncHighlightCounts(data.highlightCounts)
+    repository.syncLastSeenMsgs(data.lastSeenMsg)
+    repository.syncMarkerLines(data.markerLines)
+  }
+
   override suspend fun markBufferAsRead(buffer: BufferId)  = Unit
-  override suspend fun mergeBuffersPermanently(buffer: BufferId, buffer2: BufferId)  = Unit
-  override suspend fun removeBuffer(buffer: BufferId)  = Unit
-  override suspend fun renameBuffer(buffer: BufferId, newName: String)  = Unit
-  override suspend fun setMarkerLine(buffer: BufferId, msgId: MsgId)  = Unit
-  override suspend fun setLastSeenMsg(buffer: BufferId, msgId: MsgId)  = Unit
-  override suspend fun setBufferActivity(buffer: BufferId, types: Int)  = Unit
-  override suspend fun setHighlightCount(buffer: BufferId, count: Int)  = Unit
-  override suspend fun update(properties: QVariantMap)  = Unit
+
+  override suspend fun mergeBuffersPermanently(buffer: BufferId, buffer2: BufferId) {
+    repository.delete(buffer2)
+  }
+
+  override suspend fun removeBuffer(buffer: BufferId) {
+    repository.delete(buffer)
+  }
+
+  override suspend fun renameBuffer(buffer: BufferId, newName: String) {
+    repository.rename(buffer, newName)
+  }
+
+  override suspend fun setMarkerLine(buffer: BufferId, msgId: MsgId) {
+    repository.setMarkerLine(buffer, msgId)
+  }
+
+  override suspend fun setLastSeenMsg(buffer: BufferId, msgId: MsgId) {
+    repository.setLastSeenMsg(buffer, msgId)
+  }
+
+  override suspend fun setBufferActivity(buffer: BufferId, types: Int) {
+    repository.setBufferActivity(buffer, types)
+  }
+
+  override suspend fun setHighlightCount(buffer: BufferId, count: Int) {
+    repository.setHighlightCount(buffer, count)
+  }
 }
diff --git a/libquassel-client/src/main/kotlin/de/justjanne/libquassel/connection/ClientSessionHandler.kt b/libquassel-client/src/main/kotlin/de/justjanne/libquassel/connection/ClientSessionHandler.kt
index 30e83a8b8b5ea3610cb1eabcba7a8e8f89750573..c470ea87cbe7eaeeb687ea1ff1f2a74c5833ac09 100644
--- a/libquassel-client/src/main/kotlin/de/justjanne/libquassel/connection/ClientSessionHandler.kt
+++ b/libquassel-client/src/main/kotlin/de/justjanne/libquassel/connection/ClientSessionHandler.kt
@@ -9,6 +9,7 @@
 
 package de.justjanne.libquassel.connection
 
+import de.justjanne.libquassel.persistence.BufferRepository
 import de.justjanne.libquassel.protocol.api.ObjectName
 import de.justjanne.libquassel.protocol.api.dispatcher.RpcDispatcher
 import de.justjanne.libquassel.protocol.api.dispatcher.SyncHandler
@@ -23,21 +24,32 @@ class ClientSessionHandler(
   private val handshake: HandshakeState,
   private val sync: SyncHandler,
   private val rpc: RpcDispatcher,
+  private val bufferRepository: BufferRepository,
 ) : ConnectionHandler<Unit> {
   val toInit = MutableStateFlow<List<Pair<String, String>>?>(null)
 
   override suspend fun handle(connection: Connection) = runCatching {
     suspend fun Connection.send(message: SignalProxyMessage) = send(true) {
+      println("Send: $message")
       SignalProxyMessageSerializer.serialize(it, message, handshake.clientInitAck.featureSet)
     }
 
-    toInit.value = listOf(
-      Pair("AliasManager", "")
+    val initRequests = listOf(
+      Pair("AliasManager", ""),
+      Pair("BufferSyncer", "")
     )
-    connection.send(SignalProxyMessage.InitRequest("AliasManager", ""))
+
+    bufferRepository.sync(handshake.sessionInit.bufferInfos)
+
+    toInit.value = initRequests
+    for ((className, objectName) in initRequests) {
+      connection.send(SignalProxyMessage.InitRequest(className, objectName))
+    }
 
     while (true) {
-      when (val message = connection.read { SignalProxyMessageSerializer.deserialize(it, handshake.clientInitAck.featureSet) }) {
+      val message = connection.read { SignalProxyMessageSerializer.deserialize(it, handshake.clientInitAck.featureSet) }
+      println("Receive: $message")
+      when (message) {
         is SignalProxyMessage.HeartBeat -> connection.send(SignalProxyMessage.HeartBeatReply(message.timestamp))
         is SignalProxyMessage.HeartBeatReply -> Unit
         is SignalProxyMessage.InitData -> {
diff --git a/libquassel-client/src/test/kotlin/de/justjanne/libquassel/client/QuasselApiTest.kt b/libquassel-client/src/test/kotlin/de/justjanne/libquassel/client/QuasselApiTest.kt
index ac19ec3ac76873b577e121ff8ef1e5150f4114d5..bacd285d3ae9d0d21e93341b269112ba8c329e46 100644
--- a/libquassel-client/src/test/kotlin/de/justjanne/libquassel/client/QuasselApiTest.kt
+++ b/libquassel-client/src/test/kotlin/de/justjanne/libquassel/client/QuasselApiTest.kt
@@ -12,6 +12,7 @@ package de.justjanne.libquassel.client
 import androidx.room.Room
 import androidx.sqlite.driver.bundled.BundledSQLiteDriver
 import dagger.Binds
+import dagger.BindsInstance
 import dagger.Component
 import dagger.Module
 import dagger.Provides
@@ -35,6 +36,7 @@ import de.justjanne.libquassel.backend.RpcPersister
 import de.justjanne.libquassel.connection.ChannelConnection
 import de.justjanne.libquassel.connection.ClientHandshakeHandler
 import de.justjanne.libquassel.connection.ClientSessionHandler
+import de.justjanne.libquassel.connection.Connection
 import de.justjanne.libquassel.connection.MagicHandler
 import de.justjanne.libquassel.persistence.AliasEntity
 import de.justjanne.libquassel.persistence.AppDatabase
@@ -72,6 +74,7 @@ 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.api.server.RpcServerApi
 import de.justjanne.libquassel.protocol.connection.ClientHeader
 import de.justjanne.libquassel.protocol.connection.ProtocolFeature
 import de.justjanne.libquassel.protocol.connection.ProtocolMeta
@@ -79,10 +82,13 @@ import de.justjanne.libquassel.protocol.connection.ProtocolVersion
 import de.justjanne.libquassel.protocol.exceptions.RpcInvocationFailedException
 import de.justjanne.libquassel.protocol.features.FeatureSet
 import de.justjanne.libquassel.protocol.io.CoroutineChannel
+import de.justjanne.libquassel.protocol.models.BufferInfo
 import de.justjanne.libquassel.protocol.models.HandshakeMessage
+import de.justjanne.libquassel.protocol.models.SignalProxyMessage
 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.serializers.SignalProxyMessageSerializer
 import de.justjanne.libquassel.protocol.variant.QVariantList
 import de.justjanne.libquassel.protocol.variant.QVariant_
 import de.justjanne.libquassel.protocol.variant.qVariant
@@ -108,13 +114,26 @@ import kotlin.time.Duration.Companion.milliseconds
 import kotlin.time.Duration.Companion.seconds
 
 @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")
+class ProxyImpl @Inject constructor(
+  private val connection: Connection,
+  private val featureSet: FeatureSet,
+) : Proxy {
+  override suspend fun sync(className: String, objectName: ObjectName, function: String, params: QVariantList) {
+    val message = SignalProxyMessage.Sync(className, objectName.objectName, function, params)
+    println("Send: $message")
+    connection.send(true) {
+      SignalProxyMessageSerializer.serialize(it, message, featureSet)
+    }
+    println("Sent: $message")
   }
 
-  override fun rpc(function: String, params: QVariantList) {
-    println("making rpc call: $function $params")
+  override suspend fun rpc(function: String, params: QVariantList) {
+    val message = SignalProxyMessage.Rpc(function, params)
+    println("Send: $message")
+    connection.send(true) {
+      SignalProxyMessageSerializer.serialize(it, message, featureSet)
+    }
+    println("Sent: $message")
   }
 }
 
@@ -152,6 +171,8 @@ class DatabaseModule {
   @Provides
   fun provideAliasRepository(database: AppDatabase) = database.alias()
   @Provides
+  fun provideBufferRepository(database: AppDatabase) = database.buffer()
+  @Provides
   fun provideMessageRepository(database: AppDatabase) = database.message()
 }
 
@@ -169,6 +190,7 @@ class QuasselApiClient @Inject constructor(
   val ircListHelper: IrcListHelperServerApi,
   val network: NetworkServerApi,
   val networkConfig: NetworkConfigServerApi,
+  val rpc: RpcServerApi,
 )
 
 @Singleton
@@ -178,6 +200,13 @@ interface ClientComponent {
   fun sync(): SyncHandler
   fun rpc(): RpcDispatcher
   fun api(): QuasselApiClient
+
+  @Component.Builder
+  interface Builder {
+    fun build(): ClientComponent
+    @BindsInstance fun connection(connection: Connection): Builder
+    @BindsInstance fun featureSet(featureSet: FeatureSet): Builder
+  }
 }
 
 class QuasselApiTest {
@@ -244,7 +273,6 @@ class QuasselApiTest {
 
   @Test
   fun testConnection() = runBlocking {
-    val di = DaggerClientComponent.builder().build()
     val channel = CoroutineChannel()
     withTimeout(500.milliseconds) {
       channel.connect(InetSocketAddress("decentralised.chat", 4242), keepAlive = true)
@@ -264,15 +292,23 @@ class QuasselApiTest {
         HandshakeMessage.ClientLogin(Const.user, Const.pass),
       ).handle(connection)
     }.getOrThrow()
-    val sessionHandler = ClientSessionHandler(handshake, di.sync(), di.rpc())
+    val di = DaggerClientComponent.builder()
+      .connection(connection)
+      .featureSet(handshake.clientInitAck.featureSet)
+      .build()
+    val sessionHandler = ClientSessionHandler(handshake, di.sync(), di.rpc(), di.db().buffer())
+    launch {
+      sessionHandler.handle(connection)
+    }
     withTimeout(2.seconds) {
-      launch {
-        sessionHandler.handle(connection)
-      }
       sessionHandler.toInit.first { it != null && it.isEmpty() }
-      channel.close()
     }
+    val bufferInfo = handshake.sessionInit.bufferInfos.first { it.bufferName == "#quassel-test" }
+    di.api().rpc.sendInput(bufferInfo, "/SAY Test from libquassel?")
+    delay(15.seconds)
+    channel.close()
     println(di.db().alias().getAll())
+    println(di.db().buffer().getAll())
     Unit
   }
 }
diff --git a/libquassel-persistence/src/main/kotlin/de/justjanne/libquassel/persistence/AliasRepository.kt b/libquassel-persistence/src/main/kotlin/de/justjanne/libquassel/persistence/AliasRepository.kt
index 73575405c0e2fcedbaf4d3328a728203b585b49c..05231f4224fbc1910c7743a0fc0e53419c409370 100644
--- a/libquassel-persistence/src/main/kotlin/de/justjanne/libquassel/persistence/AliasRepository.kt
+++ b/libquassel-persistence/src/main/kotlin/de/justjanne/libquassel/persistence/AliasRepository.kt
@@ -14,7 +14,6 @@ import androidx.room.Insert
 import androidx.room.OnConflictStrategy
 import androidx.room.Query
 import androidx.room.Transaction
-import de.justjanne.libquassel.protocol.models.ids.BufferId
 import kotlinx.coroutines.flow.Flow
 
 @Dao
@@ -39,36 +38,3 @@ interface AliasRepository {
     }
   }
 }
-
-@Dao
-interface MessageRepository {
-  @Query("SELECT * FROM MessageEntity WHERE buffer = :buffer ORDER BY messageId ASC")
-  suspend fun getAll(buffer: BufferId): List<MessageEntity>
-
-  @Query("SELECT * FROM MessageEntity WHERE buffer = :buffer ORDER BY messageId ASC")
-  fun collectAll(buffer: BufferId): Flow<List<MessageEntity>>
-
-  @Query("SELECT * FROM messageentity WHERE buffer = :buffer ORDER BY messageId DESC LIMIT 1")
-  suspend fun getLast(buffer: BufferId): MessageEntity?
-
-  @Query("SELECT * FROM messageentity WHERE buffer = :buffer ORDER BY messageId DESC LIMIT 1")
-  suspend fun collectLast(buffer: BufferId): MessageEntity?
-
-  @Insert(onConflict = OnConflictStrategy.REPLACE)
-  suspend fun insert(vararg item: MessageEntity)
-
-  @Query("DELETE FROM MessageEntity WHERE buffer = :buffer")
-  suspend fun clear(buffer: BufferId)
-
-  @Transaction
-  suspend fun sync(buffer: BufferId, data: List<MessageEntity>) {
-    val newMessages = data.map(MessageEntity::messageId).toSet()
-    val last = getLast(buffer)
-    if (last != null && !newMessages.contains(last.messageId)) {
-      clear(buffer)
-    }
-    for (item in data) {
-      insert(item)
-    }
-  }
-}
diff --git a/libquassel-persistence/src/main/kotlin/de/justjanne/libquassel/persistence/AppDatabase.kt b/libquassel-persistence/src/main/kotlin/de/justjanne/libquassel/persistence/AppDatabase.kt
index dc784cf0e336087f12b7cf28d46f754522c3bd25..2f15b6c42afa57ab13c698c2dd90e1ba6295247b 100644
--- a/libquassel-persistence/src/main/kotlin/de/justjanne/libquassel/persistence/AppDatabase.kt
+++ b/libquassel-persistence/src/main/kotlin/de/justjanne/libquassel/persistence/AppDatabase.kt
@@ -16,6 +16,7 @@ import androidx.room.TypeConverters
 @Database(
   entities = [
     AliasEntity::class,
+    BufferEntity::class,
     MessageEntity::class,
   ],
   version = 1,
@@ -23,5 +24,6 @@ import androidx.room.TypeConverters
 @TypeConverters(InstantConverter::class)
 abstract class AppDatabase : RoomDatabase() {
   abstract fun alias(): AliasRepository
+  abstract fun buffer(): BufferRepository
   abstract fun message(): MessageRepository
 }
diff --git a/libquassel-persistence/src/main/kotlin/de/justjanne/libquassel/persistence/BufferEntity.kt b/libquassel-persistence/src/main/kotlin/de/justjanne/libquassel/persistence/BufferEntity.kt
new file mode 100644
index 0000000000000000000000000000000000000000..a951bc3cac411faea1bbf45150384468ba145344
--- /dev/null
+++ b/libquassel-persistence/src/main/kotlin/de/justjanne/libquassel/persistence/BufferEntity.kt
@@ -0,0 +1,30 @@
+/*
+ * libquassel
+ * Copyright (c) 2025 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.persistence
+
+import androidx.room.Entity
+import androidx.room.PrimaryKey
+import de.justjanne.libquassel.protocol.models.ids.BufferId
+import de.justjanne.libquassel.protocol.models.ids.MsgId
+import de.justjanne.libquassel.protocol.models.ids.NetworkId
+
+@Entity
+data class BufferEntity(
+  @PrimaryKey
+  val id: BufferId,
+  val network: NetworkId,
+  val type: Int,
+  val group: Int,
+  val name: String?,
+  val activity: Int?,
+  val highlightCount: Int?,
+  val lastSeenMsg: MsgId?,
+  val markerLine: MsgId?
+)
diff --git a/libquassel-persistence/src/main/kotlin/de/justjanne/libquassel/persistence/BufferRepository.kt b/libquassel-persistence/src/main/kotlin/de/justjanne/libquassel/persistence/BufferRepository.kt
new file mode 100644
index 0000000000000000000000000000000000000000..c835c2d7ed6915735c17c7e181f5bc3261a61a5b
--- /dev/null
+++ b/libquassel-persistence/src/main/kotlin/de/justjanne/libquassel/persistence/BufferRepository.kt
@@ -0,0 +1,100 @@
+/*
+ * libquassel
+ * Copyright (c) 2025 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.persistence
+
+import androidx.room.Dao
+import androidx.room.Insert
+import androidx.room.OnConflictStrategy
+import androidx.room.Query
+import androidx.room.Transaction
+import de.justjanne.bitflags.toBits
+import de.justjanne.libquassel.protocol.models.BufferInfo
+import de.justjanne.libquassel.protocol.models.ids.BufferId
+import de.justjanne.libquassel.protocol.models.ids.MsgId
+import kotlinx.coroutines.flow.Flow
+
+@Dao
+interface BufferRepository {
+  @Query("SELECT * FROM BufferEntity")
+  suspend fun getAll(): List<BufferEntity>
+
+  @Query("SELECT * FROM BufferEntity")
+  fun collectAll(): Flow<List<BufferEntity>>
+
+  @Insert(onConflict = OnConflictStrategy.REPLACE)
+  suspend fun insert(vararg item: BufferEntity)
+
+  @Query("UPDATE BufferEntity SET markerLine = :msgId WHERE id = :bufferId")
+  suspend fun setMarkerLine(bufferId: BufferId, msgId: MsgId)
+
+  @Query("UPDATE BufferEntity SET lastSeenMsg = :msgId WHERE id = :bufferId")
+  suspend fun setLastSeenMsg(bufferId: BufferId, msgId: MsgId)
+
+  @Query("UPDATE BufferEntity SET activity = :activity WHERE id = :bufferId")
+  suspend fun setBufferActivity(bufferId: BufferId, activity: Int)
+
+  @Query("UPDATE BufferEntity SET highlightCount = :count WHERE id = :bufferId")
+  suspend fun setHighlightCount(bufferId: BufferId, count: Int)
+
+  @Query("UPDATE BufferEntity SET name = :name WHERE id = :bufferId")
+  suspend fun rename(bufferId: BufferId, name: String?)
+
+  @Query("DELETE FROM BufferEntity WHERE id = :bufferId")
+  suspend fun delete(bufferId: BufferId)
+
+  @Query("DELETE FROM BufferEntity")
+  suspend fun clear()
+
+  @Transaction
+  suspend fun sync(data: List<BufferInfo>) {
+    clear()
+    for (item in data) {
+      insert(BufferEntity(
+        id = item.bufferId,
+        network = item.networkId,
+        type = item.type.toBits().toInt(),
+        group = item.groupId,
+        name = item.bufferName,
+        activity = null,
+        highlightCount = null,
+        lastSeenMsg = null,
+        markerLine = null
+      ))
+    }
+  }
+
+  @Transaction
+  suspend fun syncActivites(data: Map<BufferId, Int>) {
+    for (item in data) {
+      setBufferActivity(item.key, item.value)
+    }
+  }
+
+  @Transaction
+  suspend fun syncHighlightCounts(data: Map<BufferId, Int>) {
+    for (item in data) {
+      setHighlightCount(item.key, item.value)
+    }
+  }
+
+  @Transaction
+  suspend fun syncLastSeenMsgs(data: Map<BufferId, MsgId>) {
+    for (item in data) {
+      setLastSeenMsg(item.key, item.value)
+    }
+  }
+
+  @Transaction
+  suspend fun syncMarkerLines(data: Map<BufferId, MsgId>) {
+    for (item in data) {
+      setMarkerLine(item.key, item.value)
+    }
+  }
+}
diff --git a/libquassel-persistence/src/main/kotlin/de/justjanne/libquassel/persistence/MessageRepository.kt b/libquassel-persistence/src/main/kotlin/de/justjanne/libquassel/persistence/MessageRepository.kt
new file mode 100644
index 0000000000000000000000000000000000000000..7cb353a61ade13c6f317924c5bd0a38520dc36a9
--- /dev/null
+++ b/libquassel-persistence/src/main/kotlin/de/justjanne/libquassel/persistence/MessageRepository.kt
@@ -0,0 +1,51 @@
+/*
+ * libquassel
+ * Copyright (c) 2025 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.persistence
+
+import androidx.room.Dao
+import androidx.room.Insert
+import androidx.room.OnConflictStrategy
+import androidx.room.Query
+import androidx.room.Transaction
+import de.justjanne.libquassel.protocol.models.ids.BufferId
+import kotlinx.coroutines.flow.Flow
+
+@Dao
+interface MessageRepository {
+  @Query("SELECT * FROM MessageEntity WHERE buffer = :buffer ORDER BY messageId ASC")
+  suspend fun getAll(buffer: BufferId): List<MessageEntity>
+
+  @Query("SELECT * FROM MessageEntity WHERE buffer = :buffer ORDER BY messageId ASC")
+  fun collectAll(buffer: BufferId): Flow<List<MessageEntity>>
+
+  @Query("SELECT * FROM messageentity WHERE buffer = :buffer ORDER BY messageId DESC LIMIT 1")
+  suspend fun getLast(buffer: BufferId): MessageEntity?
+
+  @Query("SELECT * FROM messageentity WHERE buffer = :buffer ORDER BY messageId DESC LIMIT 1")
+  suspend fun collectLast(buffer: BufferId): MessageEntity?
+
+  @Insert(onConflict = OnConflictStrategy.REPLACE)
+  suspend fun insert(vararg item: MessageEntity)
+
+  @Query("DELETE FROM MessageEntity WHERE buffer = :buffer")
+  suspend fun clear(buffer: BufferId)
+
+  @Transaction
+  suspend fun sync(buffer: BufferId, data: List<MessageEntity>) {
+    val newMessages = data.map(MessageEntity::messageId).toSet()
+    val last = getLast(buffer)
+    if (last != null && !newMessages.contains(last.messageId)) {
+      clear(buffer)
+    }
+    for (item in data) {
+      insert(item)
+    }
+  }
+}