From c65606fbda9aa453d25fea9752e4243172c5357d Mon Sep 17 00:00:00 2001
From: Janne Mareike Koschinski <janne@kuschku.de>
Date: Sun, 28 Feb 2021 01:38:52 +0100
Subject: [PATCH] Implement all syncables

---
 .../client/exceptions/IrcListException.kt     |   4 +-
 .../client/syncables/ClientBacklogManager.kt  | 198 +++++++++++++
 .../client/syncables/ClientIrcListHelper.kt   |  79 ++++++
 .../protocol/syncables/AliasManager.kt        |   2 +-
 .../protocol/syncables/BacklogManager.kt      |  12 +
 .../protocol/syncables/BufferSyncer.kt        | 207 ++++++++++++++
 .../protocol/syncables/BufferViewConfig.kt    | 266 ++++++++++++++++++
 .../protocol/syncables/BufferViewManager.kt   |  66 +++++
 .../protocol/syncables/CertManager.kt         |   2 +-
 .../libquassel/protocol/syncables/CoreInfo.kt |   2 +-
 .../protocol/syncables/DccConfig.kt           |   2 +-
 .../syncables/HighlightRuleManager.kt         |   2 +-
 .../libquassel/protocol/syncables/Identity.kt | 232 +++++++++++++++
 .../protocol/syncables/IrcChannel.kt          |   2 +-
 .../protocol/syncables/IrcListHelper.kt       |  11 +
 .../libquassel/protocol/syncables/IrcUser.kt  |   2 +-
 .../libquassel/protocol/syncables/Network.kt  |   2 +-
 .../protocol/syncables/NetworkConfig.kt       | 119 ++++++++
 .../libquassel/protocol/syncables/Session.kt  |  19 +-
 .../syncables/state/BufferSyncerState.kt      |  51 ++++
 .../syncables/state/BufferViewConfigState.kt  |  25 ++
 .../syncables/state/BufferViewManagerState.kt |  10 +
 .../protocol/syncables/state/IdentityState.kt |  26 ++
 .../syncables/state/NetworkConfigState.kt     |  11 +
 .../syncables/stubs/BacklogManagerStub.kt     |  17 +-
 .../libquassel/protocol/util/move.kt          |  25 ++
 .../libquassel/protocol/util/pairs.kt         |   3 +-
 .../libquassel/protocol/util/plus.kt          |  25 ++
 .../libquassel/protocol/util/remove.kt        |  14 +
 29 files changed, 1418 insertions(+), 18 deletions(-)
 rename libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/state/IrcListHelperState.kt => libquassel-client/src/main/kotlin/de/justjanne/libquassel/client/exceptions/IrcListException.kt (73%)
 create mode 100644 libquassel-client/src/main/kotlin/de/justjanne/libquassel/client/syncables/ClientBacklogManager.kt
 create mode 100644 libquassel-client/src/main/kotlin/de/justjanne/libquassel/client/syncables/ClientIrcListHelper.kt
 create mode 100644 libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/util/move.kt
 create mode 100644 libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/util/plus.kt
 create mode 100644 libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/util/remove.kt

diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/state/IrcListHelperState.kt b/libquassel-client/src/main/kotlin/de/justjanne/libquassel/client/exceptions/IrcListException.kt
similarity index 73%
rename from libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/state/IrcListHelperState.kt
rename to libquassel-client/src/main/kotlin/de/justjanne/libquassel/client/exceptions/IrcListException.kt
index 182244f..2b1dd3d 100644
--- a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/state/IrcListHelperState.kt
+++ b/libquassel-client/src/main/kotlin/de/justjanne/libquassel/client/exceptions/IrcListException.kt
@@ -8,4 +8,6 @@
  * obtain one at https://mozilla.org/MPL/2.0/.
  */
 
-package de.justjanne.libquassel.protocol.syncables.state
+package de.justjanne.libquassel.client.exceptions
+
+class IrcListException(message: String) : Exception(message)
diff --git a/libquassel-client/src/main/kotlin/de/justjanne/libquassel/client/syncables/ClientBacklogManager.kt b/libquassel-client/src/main/kotlin/de/justjanne/libquassel/client/syncables/ClientBacklogManager.kt
new file mode 100644
index 0000000..7bbe4e0
--- /dev/null
+++ b/libquassel-client/src/main/kotlin/de/justjanne/libquassel/client/syncables/ClientBacklogManager.kt
@@ -0,0 +1,198 @@
+/*
+ * libquassel
+ * Copyright (c) 2021 Janne Mareike Koschinski
+ * Copyright (c) 2021 The Quassel Project
+ *
+ * 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.syncables
+
+import de.justjanne.bitflags.of
+import de.justjanne.libquassel.protocol.models.flags.MessageFlag
+import de.justjanne.libquassel.protocol.models.flags.MessageFlags
+import de.justjanne.libquassel.protocol.models.flags.MessageType
+import de.justjanne.libquassel.protocol.models.flags.MessageTypes
+import de.justjanne.libquassel.protocol.models.ids.BufferId
+import de.justjanne.libquassel.protocol.models.ids.MsgId
+import de.justjanne.libquassel.protocol.syncables.BacklogManager
+import de.justjanne.libquassel.protocol.syncables.Session
+import de.justjanne.libquassel.protocol.variant.QVariantList
+import kotlin.coroutines.Continuation
+import kotlin.coroutines.resume
+import kotlin.coroutines.suspendCoroutine
+
+class ClientBacklogManager(
+  session: Session
+) : BacklogManager(session) {
+  private val bufferListeners =
+    mutableMapOf<BacklogData.Buffer, Continuation<BacklogData.Buffer>>()
+  private val bufferFilteredListeners =
+    mutableMapOf<BacklogData.BufferFiltered, Continuation<BacklogData.BufferFiltered>>()
+  private val allListeners =
+    mutableMapOf<BacklogData.All, Continuation<BacklogData.All>>()
+  private val allFilteredListeners =
+    mutableMapOf<BacklogData.AllFiltered, Continuation<BacklogData.AllFiltered>>()
+
+  suspend fun backlog(
+    bufferId: BufferId,
+    first: MsgId = MsgId(-1),
+    last: MsgId = MsgId(-1),
+    limit: Int = -1,
+    additional: Int = 0
+  ) = suspendCoroutine<BacklogData.Buffer> {
+    val data = BacklogData.Buffer(bufferId, first, last, limit, additional)
+    bufferListeners[data] = it
+  }
+
+  suspend fun backlogFiltered(
+    bufferId: BufferId,
+    first: MsgId = MsgId(-1),
+    last: MsgId = MsgId(-1),
+    limit: Int = -1,
+    additional: Int = 0,
+    type: MessageTypes = MessageType.all,
+    flags: MessageFlags = MessageFlag.all
+  ) = suspendCoroutine<BacklogData.BufferFiltered> {
+    val data = BacklogData.BufferFiltered(bufferId, first, last, limit, additional, type, flags)
+    bufferFilteredListeners[data] = it
+  }
+
+  suspend fun backlogAll(
+    first: MsgId = MsgId(-1),
+    last: MsgId = MsgId(-1),
+    limit: Int = -1,
+    additional: Int = 0
+  ) = suspendCoroutine<BacklogData.All> {
+    val data = BacklogData.All(first, last, limit, additional)
+    allListeners[data] = it
+  }
+
+  suspend fun backlogAllFiltered(
+    first: MsgId = MsgId(-1),
+    last: MsgId = MsgId(-1),
+    limit: Int = -1,
+    additional: Int = 0,
+    type: MessageTypes = MessageType.all,
+    flags: MessageFlags = MessageFlag.all
+  ) = suspendCoroutine<BacklogData.AllFiltered> {
+    val data = BacklogData.AllFiltered(first, last, limit, additional, type, flags)
+    allFilteredListeners[data] = it
+  }
+
+  override fun receiveBacklog(
+    bufferId: BufferId,
+    first: MsgId,
+    last: MsgId,
+    limit: Int,
+    additional: Int,
+    messages: QVariantList
+  ) {
+    val data = BacklogData.Buffer(
+      bufferId,
+      first,
+      last,
+      limit,
+      additional
+    )
+    bufferListeners[data]?.resume(data.copy(messages = messages))
+    super.receiveBacklog(bufferId, first, last, limit, additional, messages)
+  }
+
+  override fun receiveBacklogFiltered(
+    bufferId: BufferId,
+    first: MsgId,
+    last: MsgId,
+    limit: Int,
+    additional: Int,
+    type: Int,
+    flags: Int,
+    messages: QVariantList
+  ) {
+    val data = BacklogData.BufferFiltered(
+      bufferId,
+      first,
+      last,
+      limit,
+      additional,
+      MessageType.of(type.toUInt()),
+      MessageFlag.of(flags.toUInt())
+    )
+    bufferFilteredListeners[data]?.resume(data.copy(messages = messages))
+    super.receiveBacklogFiltered(bufferId, first, last, limit, additional, type, flags, messages)
+  }
+
+  override fun receiveBacklogAll(first: MsgId, last: MsgId, limit: Int, additional: Int, messages: QVariantList) {
+    val data = BacklogData.All(
+      first,
+      last,
+      limit,
+      additional
+    )
+    allListeners[data]?.resume(data.copy(messages = messages))
+    super.receiveBacklogAll(first, last, limit, additional, messages)
+  }
+
+  override fun receiveBacklogAllFiltered(
+    first: MsgId,
+    last: MsgId,
+    limit: Int,
+    additional: Int,
+    type: Int,
+    flags: Int,
+    messages: QVariantList
+  ) {
+    val data = BacklogData.AllFiltered(
+      first,
+      last,
+      limit,
+      additional,
+      MessageType.of(type.toUInt()),
+      MessageFlag.of(flags.toUInt()),
+    )
+    allFilteredListeners[data]?.resume(data.copy(messages = messages))
+    super.receiveBacklogAllFiltered(first, last, limit, additional, type, flags, messages)
+  }
+
+  sealed class BacklogData {
+    data class Buffer(
+      val bufferId: BufferId,
+      val first: MsgId = MsgId(-1),
+      val last: MsgId = MsgId(-1),
+      val limit: Int = -1,
+      val additional: Int = 0,
+      val messages: QVariantList = emptyList()
+    ) : BacklogData()
+
+    data class BufferFiltered(
+      val bufferId: BufferId,
+      val first: MsgId = MsgId(-1),
+      val last: MsgId = MsgId(-1),
+      val limit: Int = -1,
+      val additional: Int = 0,
+      val type: MessageTypes = MessageType.all,
+      val flags: MessageFlags = MessageFlag.all,
+      val messages: QVariantList = emptyList()
+    ) : BacklogData()
+
+    data class All(
+      val first: MsgId = MsgId(-1),
+      val last: MsgId = MsgId(-1),
+      val limit: Int = -1,
+      val additional: Int = 0,
+      val messages: QVariantList = emptyList()
+    ) : BacklogData()
+
+    data class AllFiltered(
+      val first: MsgId = MsgId(-1),
+      val last: MsgId = MsgId(-1),
+      val limit: Int = -1,
+      val additional: Int = 0,
+      val type: MessageTypes = MessageType.all,
+      val flags: MessageFlags = MessageFlag.all,
+      val messages: QVariantList = emptyList()
+    ) : BacklogData()
+  }
+}
diff --git a/libquassel-client/src/main/kotlin/de/justjanne/libquassel/client/syncables/ClientIrcListHelper.kt b/libquassel-client/src/main/kotlin/de/justjanne/libquassel/client/syncables/ClientIrcListHelper.kt
new file mode 100644
index 0000000..e75b723
--- /dev/null
+++ b/libquassel-client/src/main/kotlin/de/justjanne/libquassel/client/syncables/ClientIrcListHelper.kt
@@ -0,0 +1,79 @@
+/*
+ * libquassel
+ * Copyright (c) 2021 Janne Mareike Koschinski
+ * Copyright (c) 2021 The Quassel Project
+ *
+ * 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.syncables
+
+import de.justjanne.libquassel.client.exceptions.IrcListException
+import de.justjanne.libquassel.protocol.models.QStringList
+import de.justjanne.libquassel.protocol.models.ids.NetworkId
+import de.justjanne.libquassel.protocol.syncables.IrcListHelper
+import de.justjanne.libquassel.protocol.syncables.Session
+import de.justjanne.libquassel.protocol.variant.QVariantList
+import de.justjanne.libquassel.protocol.variant.into
+import kotlin.coroutines.Continuation
+import kotlin.coroutines.resume
+import kotlin.coroutines.suspendCoroutine
+
+class ClientIrcListHelper(
+  session: Session
+) : IrcListHelper(session) {
+  private val waitingContinuations = mutableMapOf<NetworkId, Continuation<List<ChannelDescription>>>()
+  private val readyContinuations = mutableMapOf<NetworkId, Continuation<List<ChannelDescription>>>()
+
+  suspend fun channelList(
+    networkId: NetworkId,
+    channelFilters: List<String>
+  ) = suspendCoroutine<List<ChannelDescription>> {
+    waitingContinuations[networkId] = it
+    requestChannelList(networkId, channelFilters)
+  }
+
+  override fun reportFinishedList(netId: NetworkId) {
+    val continuation = waitingContinuations.remove(netId)
+    if (continuation != null) {
+      readyContinuations[netId] = continuation
+      requestChannelList(netId, emptyList())
+    }
+    super.reportFinishedList(netId)
+  }
+
+  override fun reportError(error: String?) {
+    for (continuation in waitingContinuations.values + readyContinuations.values) {
+      continuation.resumeWith(Result.failure(IrcListException(error ?: "Unknown Error")))
+    }
+    super.reportError(error)
+  }
+
+  override fun receiveChannelList(netId: NetworkId, channelFilters: QStringList, channels: QVariantList) {
+    readyContinuations[netId]?.resume(
+      channels.mapNotNull {
+        val list = it.into<QVariantList>().orEmpty()
+        if (list.size == 3) {
+          ChannelDescription(
+            netId,
+            list[0].into(""),
+            list[1].into(0u),
+            list[2].into(""),
+          )
+        } else {
+          null
+        }
+      }
+    )
+    super.receiveChannelList(netId, channelFilters, channels)
+  }
+
+  data class ChannelDescription(
+    val netId: NetworkId,
+    val channelName: String,
+    val userCount: UInt,
+    val topic: String
+  )
+}
diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/AliasManager.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/AliasManager.kt
index 95fd8c0..8b1e1fd 100644
--- a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/AliasManager.kt
+++ b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/AliasManager.kt
@@ -23,7 +23,7 @@ import de.justjanne.libquassel.protocol.variant.into
 import de.justjanne.libquassel.protocol.variant.qVariant
 import kotlinx.coroutines.flow.MutableStateFlow
 
-class AliasManager constructor(
+open class AliasManager(
   session: Session
 ) : SyncableObject(session, "AliasManager"), AliasManagerStub {
   override fun toVariantMap(): QVariantMap = mapOf(
diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/BacklogManager.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/BacklogManager.kt
index 30254bc..d6a15b6 100644
--- a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/BacklogManager.kt
+++ b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/BacklogManager.kt
@@ -9,3 +9,15 @@
  */
 
 package de.justjanne.libquassel.protocol.syncables
+
+import de.justjanne.libquassel.protocol.syncables.stubs.BacklogManagerStub
+import de.justjanne.libquassel.protocol.variant.QVariantMap
+import de.justjanne.libquassel.protocol.variant.QVariant_
+
+open class BacklogManager(
+  session: Session
+) : SyncableObject(session, "BacklogManager"), BacklogManagerStub {
+
+  override fun fromVariantMap(properties: QVariantMap) = Unit
+  override fun toVariantMap() = mapOf<String, QVariant_>()
+}
diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/BufferSyncer.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/BufferSyncer.kt
index 30254bc..ae81bcb 100644
--- a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/BufferSyncer.kt
+++ b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/BufferSyncer.kt
@@ -9,3 +9,210 @@
  */
 
 package de.justjanne.libquassel.protocol.syncables
+
+import de.justjanne.bitflags.none
+import de.justjanne.bitflags.of
+import de.justjanne.bitflags.toBits
+import de.justjanne.libquassel.protocol.models.BufferInfo
+import de.justjanne.libquassel.protocol.models.flags.MessageType
+import de.justjanne.libquassel.protocol.models.flags.MessageTypes
+import de.justjanne.libquassel.protocol.models.ids.BufferId
+import de.justjanne.libquassel.protocol.models.ids.MsgId
+import de.justjanne.libquassel.protocol.models.ids.isValid
+import de.justjanne.libquassel.protocol.models.types.QtType
+import de.justjanne.libquassel.protocol.models.types.QuasselType
+import de.justjanne.libquassel.protocol.syncables.state.BufferSyncerState
+import de.justjanne.libquassel.protocol.syncables.stubs.BufferSyncerStub
+import de.justjanne.libquassel.protocol.util.pairs
+import de.justjanne.libquassel.protocol.util.update
+import de.justjanne.libquassel.protocol.variant.QVariantList
+import de.justjanne.libquassel.protocol.variant.QVariantMap
+import de.justjanne.libquassel.protocol.variant.into
+import de.justjanne.libquassel.protocol.variant.qVariant
+import kotlinx.coroutines.flow.MutableStateFlow
+
+open class BufferSyncer(
+  session: Session
+) : SyncableObject(session, "BufferSyncer"), BufferSyncerStub {
+  override fun toVariantMap() = mapOf(
+    "Activities" to qVariant(
+      state().activities.flatMap { (key, value) ->
+        listOf(
+          qVariant(key, QuasselType.BufferId),
+          qVariant(value.toBits(), QtType.UInt)
+        )
+      },
+      QtType.QVariantList
+    ),
+    "HighlightCounts" to qVariant(
+      state().highlightCounts.flatMap { (key, value) ->
+        listOf(
+          qVariant(key, QuasselType.BufferId),
+          qVariant(value, QtType.Int)
+        )
+      },
+      QtType.QVariantList
+    ),
+    "LastSeenMsg" to qVariant(
+      state().lastSeenMsg.flatMap { (key, value) ->
+        listOf(
+          qVariant(key, QuasselType.BufferId),
+          qVariant(value, QuasselType.MsgId)
+        )
+      },
+      QtType.QVariantList
+    ),
+    "MarkerLines" to qVariant(
+      state().markerLines.flatMap { (key, value) ->
+        listOf(
+          qVariant(key, QuasselType.BufferId),
+          qVariant(value, QuasselType.MsgId)
+        )
+      },
+      QtType.QVariantList
+    ),
+  )
+
+  override fun fromVariantMap(properties: QVariantMap) {
+    state.update {
+      copy(
+        activities = properties["Activities"].into<QVariantList>()?.pairs { a, b ->
+          Pair(
+            a.into<BufferId>() ?: return@pairs null,
+            MessageType.of(b.into<UInt>() ?: return@pairs null)
+          )
+        }?.filterNotNull()?.toMap().orEmpty(),
+        highlightCounts = properties["HighlightCounts"].into<QVariantList>()?.pairs { a, b ->
+          Pair(
+            a.into<BufferId>() ?: return@pairs null,
+            b.into<Int>() ?: return@pairs null
+          )
+        }?.filterNotNull()?.toMap().orEmpty(),
+        lastSeenMsg = properties["LastSeenMsg"].into<QVariantList>()?.pairs { a, b ->
+          Pair(
+            a.into<BufferId>() ?: return@pairs null,
+            b.into<MsgId>() ?: return@pairs null
+          )
+        }?.filterNotNull()?.toMap().orEmpty(),
+        markerLines = properties["MarkerLines"].into<QVariantList>()?.pairs { a, b ->
+          Pair(
+            a.into<BufferId>() ?: return@pairs null,
+            b.into<MsgId>() ?: return@pairs null
+          )
+        }?.filterNotNull()?.toMap().orEmpty()
+      )
+    }
+  }
+
+  fun lastSeenMsg(buffer: BufferId): MsgId = state().lastSeenMsg[buffer] ?: MsgId(0)
+
+  fun markerLine(buffer: BufferId): MsgId = state().markerLines[buffer] ?: MsgId(0)
+
+  fun activity(buffer: BufferId): MessageTypes =
+    state().activities[buffer] ?: MessageType.none()
+
+  fun highlightCount(buffer: BufferId): Int = state().highlightCounts[buffer] ?: 0
+
+  fun bufferInfo(bufferId: BufferId) = state().bufferInfos[bufferId]
+
+  fun bufferInfos(): Collection<BufferInfo> = state().bufferInfos.values.toList()
+
+  override fun mergeBuffersPermanently(buffer: BufferId, buffer2: BufferId) {
+    removeBuffer(buffer2)
+    super.mergeBuffersPermanently(buffer, buffer2)
+  }
+
+  override fun removeBuffer(buffer: BufferId) {
+    state.update {
+      copy(
+        activities = activities - buffer,
+        lastSeenMsg = lastSeenMsg - buffer,
+        markerLines = markerLines - buffer,
+        highlightCounts = highlightCounts - buffer,
+        bufferInfos = bufferInfos - buffer
+      )
+    }
+    super.removeBuffer(buffer)
+  }
+
+  override fun setLastSeenMsg(buffer: BufferId, msgId: MsgId) {
+    if (!msgId.isValid() || lastSeenMsg(buffer) >= msgId) {
+      return
+    }
+
+    state.update {
+      copy(lastSeenMsg = lastSeenMsg + Pair(buffer, msgId))
+    }
+
+    super.setLastSeenMsg(buffer, msgId)
+  }
+
+  override fun setMarkerLine(buffer: BufferId, msgId: MsgId) {
+    if (!msgId.isValid() || markerLine(buffer) >= msgId) {
+      return
+    }
+
+    state.update {
+      copy(markerLines = markerLines + Pair(buffer, msgId))
+    }
+
+    super.setMarkerLine(buffer, msgId)
+  }
+
+  override fun setBufferActivity(buffer: BufferId, types: Int) {
+    state.update {
+      copy(activities = activities + Pair(buffer, MessageType.of(types.toUInt())))
+    }
+
+    super.setBufferActivity(buffer, types)
+  }
+
+  fun setBufferActivity(buffer: BufferId, types: MessageTypes) {
+    val oldTypes = activity(buffer)
+
+    state.update {
+      copy(activities = activities + Pair(buffer, types))
+    }
+
+    if ((types - oldTypes).isNotEmpty()) {
+      val bufferInfo = bufferInfo(buffer)
+
+      if (bufferInfo != null) {
+        session.bufferViewManager().handleBuffer(bufferInfo, true)
+      }
+    }
+
+    super.setBufferActivity(buffer, types.toBits().toInt())
+  }
+
+  override fun setHighlightCount(buffer: BufferId, count: Int) {
+    state.update {
+      copy(highlightCounts = highlightCounts + Pair(buffer, count))
+    }
+    super.setHighlightCount(buffer, count)
+  }
+
+  fun bufferInfoUpdated(info: BufferInfo) {
+    val oldInfo = bufferInfo(info.bufferId)
+    if (info != oldInfo) {
+      state.update {
+        copy(bufferInfos = bufferInfos + Pair(info.bufferId, info))
+      }
+
+      if (oldInfo != null) {
+        session.bufferViewManager().handleBuffer(info)
+      }
+    }
+  }
+
+  @Suppress("NOTHING_TO_INLINE")
+  inline fun state() = flow().value
+
+  @Suppress("NOTHING_TO_INLINE")
+  inline fun flow() = state
+
+  @PublishedApi
+  internal val state = MutableStateFlow(
+    BufferSyncerState()
+  )
+}
diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/BufferViewConfig.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/BufferViewConfig.kt
index 30254bc..583f119 100644
--- a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/BufferViewConfig.kt
+++ b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/BufferViewConfig.kt
@@ -9,3 +9,269 @@
  */
 
 package de.justjanne.libquassel.protocol.syncables
+
+import de.justjanne.bitflags.of
+import de.justjanne.bitflags.toBits
+import de.justjanne.libquassel.protocol.models.BufferInfo
+import de.justjanne.libquassel.protocol.models.flags.BufferActivity
+import de.justjanne.libquassel.protocol.models.flags.BufferType
+import de.justjanne.libquassel.protocol.models.ids.BufferId
+import de.justjanne.libquassel.protocol.models.ids.NetworkId
+import de.justjanne.libquassel.protocol.models.types.QtType
+import de.justjanne.libquassel.protocol.models.types.QuasselType
+import de.justjanne.libquassel.protocol.syncables.state.BufferViewConfigState
+import de.justjanne.libquassel.protocol.syncables.stubs.BufferViewConfigStub
+import de.justjanne.libquassel.protocol.util.insert
+import de.justjanne.libquassel.protocol.util.move
+import de.justjanne.libquassel.protocol.util.update
+import de.justjanne.libquassel.protocol.variant.QVariantList
+import de.justjanne.libquassel.protocol.variant.QVariantMap
+import de.justjanne.libquassel.protocol.variant.into
+import de.justjanne.libquassel.protocol.variant.qVariant
+import kotlinx.coroutines.flow.MutableStateFlow
+
+open class BufferViewConfig(
+  bufferViewId: Int,
+  session: Session
+) : SyncableObject(session, "BufferViewConfig"), BufferViewConfigStub {
+  override fun fromVariantMap(properties: QVariantMap) {
+    state.update {
+      copy(
+        buffers = properties["BufferList"].into<QVariantList>()
+          ?.mapNotNull { it.into<BufferId>() }
+          .orEmpty(),
+        removedBuffers = properties["RemovedBuffers"].into<QVariantList>()
+          ?.mapNotNull { it.into<BufferId>() }
+          ?.toSet()
+          .orEmpty(),
+        temporarilyRemovedBuffers = properties["TemporarilyRemovedBuffers"].into<QVariantList>()
+          ?.mapNotNull { it.into<BufferId>() }
+          ?.toSet()
+          .orEmpty(),
+        bufferViewName = properties["bufferViewName"].into(bufferViewName()),
+        networkId = properties["networkId"].into(networkId()),
+        addNewBuffersAutomatically = properties["addNewBuffersAutomatically"].into(addNewBuffersAutomatically()),
+        sortAlphabetically = properties["sortAlphabetically"].into(sortAlphabetically()),
+        hideInactiveBuffers = properties["hideInactiveBuffers"].into(hideInactiveBuffers()),
+        hideInactiveNetworks = properties["hideInactiveNetworks"].into(hideInactiveNetworks()),
+        disableDecoration = properties["disableDecoration"].into(disableDecoration()),
+        allowedBufferTypes = properties["allowedBufferTypes"].into(allowedBufferTypes()),
+        minimumActivity = properties["minimumActivity"].into(minimumActivity()),
+        showSearch = properties["showSearch"].into(showSearch()),
+      )
+    }
+  }
+
+  override fun toVariantMap() = mapOf(
+    "BufferList" to qVariant(
+      buffers().map {
+        qVariant(it, QuasselType.BufferId)
+      },
+      QtType.QVariantList
+    ),
+    "RemovedBuffers" to qVariant(
+      removedBuffers().map {
+        qVariant(it, QuasselType.BufferId)
+      },
+      QtType.QVariantList
+    ),
+    "TemporarilyRemovedBuffers" to qVariant(
+      temporarilyRemovedBuffers().map {
+        qVariant(it, QuasselType.BufferId)
+      },
+      QtType.QVariantList
+    ),
+    "bufferViewName" to qVariant(bufferViewName(), QtType.QString),
+    "networkId" to qVariant(networkId(), QuasselType.NetworkId),
+    "addNewBuffersAutomatically" to qVariant(addNewBuffersAutomatically(), QtType.Bool),
+    "sortAlphabetically" to qVariant(sortAlphabetically(), QtType.Bool),
+    "hideInactiveBuffers" to qVariant(hideInactiveBuffers(), QtType.Bool),
+    "hideInactiveNetworks" to qVariant(hideInactiveNetworks(), QtType.Bool),
+    "disableDecoration" to qVariant(disableDecoration(), QtType.Bool),
+    "allowedBufferTypes" to qVariant(allowedBufferTypes().toBits().toInt(), QtType.Int),
+    "minimumActivity" to qVariant(minimumActivity().toBits().toInt(), QtType.Int),
+    "showSearch" to qVariant(showSearch(), QtType.Bool)
+  )
+
+  fun bufferViewId() = state().bufferViewId
+  fun bufferViewName() = state().bufferViewName
+  fun networkId() = state().networkId
+  fun addNewBuffersAutomatically() = state().addNewBuffersAutomatically
+  fun sortAlphabetically() = state().sortAlphabetically
+  fun hideInactiveBuffers() = state().hideInactiveBuffers
+  fun hideInactiveNetworks() = state().hideInactiveNetworks
+  fun disableDecoration() = state().disableDecoration
+  fun allowedBufferTypes() = state().allowedBufferTypes
+  fun minimumActivity() = state().minimumActivity
+  fun showSearch() = state().showSearch
+
+  fun buffers() = state().buffers
+  fun removedBuffers() = state().removedBuffers
+  fun temporarilyRemovedBuffers() = state().temporarilyRemovedBuffers
+
+  override fun addBuffer(buffer: BufferId, pos: Int) {
+    state.update {
+      copy(
+        buffers = buffers.insert(buffer, pos),
+        removedBuffers = removedBuffers - buffer,
+        temporarilyRemovedBuffers = temporarilyRemovedBuffers - buffer
+      )
+    }
+
+    super.addBuffer(buffer, pos)
+  }
+
+  override fun removeBuffer(buffer: BufferId) {
+    state.update {
+      copy(
+        buffers = buffers - buffer,
+        removedBuffers = removedBuffers - buffer,
+        temporarilyRemovedBuffers = temporarilyRemovedBuffers + buffer
+      )
+    }
+
+    super.removeBuffer(buffer)
+  }
+
+  override fun removeBufferPermanently(buffer: BufferId) {
+    state.update {
+      copy(
+        buffers = buffers - buffer,
+        removedBuffers = removedBuffers + buffer,
+        temporarilyRemovedBuffers = temporarilyRemovedBuffers - buffer
+      )
+    }
+
+    super.removeBufferPermanently(buffer)
+  }
+
+  override fun moveBuffer(buffer: BufferId, pos: Int) {
+    if (!buffers().contains(buffer)) {
+      return
+    }
+
+    state.update {
+      copy(
+        buffers = buffers.move(buffer, pos),
+        removedBuffers = removedBuffers - buffer,
+        temporarilyRemovedBuffers = temporarilyRemovedBuffers - buffer
+      )
+    }
+
+    super.moveBuffer(buffer, pos)
+  }
+
+  override fun setBufferViewName(value: String) {
+    state.update {
+      copy(bufferViewName = value)
+    }
+    super.setBufferViewName(value)
+  }
+
+  override fun setAddNewBuffersAutomatically(value: Boolean) {
+    state.update {
+      copy(addNewBuffersAutomatically = value)
+    }
+    super.setAddNewBuffersAutomatically(value)
+  }
+
+  override fun setAllowedBufferTypes(value: Int) {
+    state.update {
+      copy(allowedBufferTypes = BufferType.of(value.toUShort()))
+    }
+    super.setAllowedBufferTypes(value)
+  }
+
+  override fun setDisableDecoration(value: Boolean) {
+    state.update {
+      copy(disableDecoration = value)
+    }
+    super.setDisableDecoration(value)
+  }
+
+  override fun setHideInactiveBuffers(value: Boolean) {
+    state.update {
+      copy(hideInactiveBuffers = value)
+    }
+    super.setHideInactiveBuffers(value)
+  }
+
+  override fun setHideInactiveNetworks(value: Boolean) {
+    state.update {
+      copy(hideInactiveNetworks = value)
+    }
+    super.setHideInactiveNetworks(value)
+  }
+
+  override fun setMinimumActivity(value: Int) {
+    state.update {
+      copy(minimumActivity = BufferActivity.of(value.toUInt()))
+    }
+    super.setMinimumActivity(value)
+  }
+
+  override fun setNetworkId(value: NetworkId) {
+    state.update {
+      copy(networkId = value)
+    }
+    super.setNetworkId(value)
+  }
+
+  override fun setShowSearch(value: Boolean) {
+    state.update {
+      copy(showSearch = value)
+    }
+    super.setShowSearch(value)
+  }
+
+  override fun setSortAlphabetically(value: Boolean) {
+    state.update {
+      copy(sortAlphabetically = value)
+    }
+    super.setSortAlphabetically(value)
+  }
+
+  fun insertBufferSorted(info: BufferInfo) {
+    requestAddBuffer(
+      info.bufferId,
+      buffers()
+        .asSequence()
+        .map(session.bufferSyncer()::bufferInfo)
+        .withIndex()
+        .mapNotNull { (index, value) -> IndexedValue(index, value ?: return@mapNotNull null) }
+        .filter { (_, value) -> value.networkId == info.networkId }
+        .find { (_, value) ->
+          String.CASE_INSENSITIVE_ORDER.compare(value.bufferName, info.bufferName) > 0
+        }?.index ?: buffers().size
+    )
+  }
+
+  fun handleBuffer(info: BufferInfo, unhide: Boolean = false) {
+    if (addNewBuffersAutomatically() &&
+      !buffers().contains(info.bufferId) &&
+      !temporarilyRemovedBuffers().contains(info.bufferId) &&
+      !removedBuffers().contains(info.bufferId) &&
+      !info.type.contains(BufferType.Status)
+    ) {
+      insertBufferSorted(info)
+    } else if (unhide &&
+      !buffers().contains(info.bufferId) &&
+      temporarilyRemovedBuffers().contains(info.bufferId)
+    ) {
+      insertBufferSorted(info)
+    }
+  }
+
+  @Suppress("NOTHING_TO_INLINE")
+  inline fun state() = flow().value
+
+  @Suppress("NOTHING_TO_INLINE")
+  inline fun flow() = state
+
+  @PublishedApi
+  internal val state = MutableStateFlow(
+    BufferViewConfigState(
+      bufferViewId = bufferViewId
+    )
+  )
+}
diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/BufferViewManager.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/BufferViewManager.kt
index 30254bc..dde3434 100644
--- a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/BufferViewManager.kt
+++ b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/BufferViewManager.kt
@@ -9,3 +9,69 @@
  */
 
 package de.justjanne.libquassel.protocol.syncables
+
+import de.justjanne.libquassel.protocol.models.BufferInfo
+import de.justjanne.libquassel.protocol.models.types.QtType
+import de.justjanne.libquassel.protocol.syncables.state.BufferViewManagerState
+import de.justjanne.libquassel.protocol.syncables.stubs.BufferViewManagerStub
+import de.justjanne.libquassel.protocol.util.update
+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
+import kotlinx.coroutines.flow.MutableStateFlow
+
+open class BufferViewManager(
+  session: Session
+) : SyncableObject(session, "BufferViewManager"), BufferViewManagerStub {
+  override fun fromVariantMap(properties: QVariantMap) {
+    properties["BufferViewIds"].into<QVariantList>()
+      ?.mapNotNull<QVariant_, Int>(QVariant_::into)
+      ?.forEach(this::addBufferViewConfig)
+  }
+
+  override fun toVariantMap() = mapOf(
+    "BufferViewIds" to qVariant(
+      state().bufferViewConfigs.map {
+        qVariant(it.key, QtType.Int)
+      },
+      QtType.QVariantList
+    )
+  )
+
+  fun contains(bufferViewId: Int) = state().contains(bufferViewId)
+  fun bufferViewConfig(bufferViewId: Int) = state().bufferViewConfig(bufferViewId)
+  fun bufferViewConfigs() = state().bufferViewConfigs()
+
+  override fun addBufferViewConfig(bufferViewConfigId: Int) {
+    if (contains(bufferViewConfigId)) {
+      return
+    }
+
+    val config = BufferViewConfig(bufferViewConfigId, session)
+    session.synchronize(config)
+    state.update {
+      copy(bufferViewConfigs = bufferViewConfigs + Pair(bufferViewConfigId, config))
+    }
+
+    super.addBufferViewConfig(bufferViewConfigId)
+  }
+
+  fun handleBuffer(info: BufferInfo, unhide: Boolean = false) {
+    for (bufferViewConfig in bufferViewConfigs()) {
+      bufferViewConfig.handleBuffer(info, unhide)
+    }
+  }
+
+  @Suppress("NOTHING_TO_INLINE")
+  inline fun state() = flow().value
+
+  @Suppress("NOTHING_TO_INLINE")
+  inline fun flow() = state
+
+  @PublishedApi
+  internal val state = MutableStateFlow(
+    BufferViewManagerState()
+  )
+}
diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/CertManager.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/CertManager.kt
index f2d5172..a420f96 100644
--- a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/CertManager.kt
+++ b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/CertManager.kt
@@ -29,7 +29,7 @@ import java.security.PrivateKey
 import java.security.cert.Certificate
 import java.security.cert.CertificateFactory
 
-class CertManager(
+open class CertManager(
   identityId: IdentityId,
   session: Session
 ) : SyncableObject(session, "CertManager"), CertManagerStub {
diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/CoreInfo.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/CoreInfo.kt
index 95933ef..d78c7e3 100644
--- a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/CoreInfo.kt
+++ b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/CoreInfo.kt
@@ -23,7 +23,7 @@ import de.justjanne.libquassel.protocol.variant.qVariant
 import kotlinx.coroutines.flow.MutableStateFlow
 import org.threeten.bp.Instant
 
-class CoreInfo constructor(
+open class CoreInfo(
   session: Session
 ) : SyncableObject(session, "CoreInfo"), CoreInfoStub {
   override fun fromVariantMap(properties: QVariantMap) {
diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/DccConfig.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/DccConfig.kt
index 991995b..b199aa0 100644
--- a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/DccConfig.kt
+++ b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/DccConfig.kt
@@ -23,7 +23,7 @@ import de.justjanne.libquassel.protocol.variant.qVariant
 import kotlinx.coroutines.flow.MutableStateFlow
 import java.net.InetAddress
 
-class DccConfig constructor(
+open class DccConfig(
   session: Session
 ) : SyncableObject(session, "DccConfig"), DccConfigStub {
   override fun init() {
diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/HighlightRuleManager.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/HighlightRuleManager.kt
index 20dd19c..cc24585 100644
--- a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/HighlightRuleManager.kt
+++ b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/HighlightRuleManager.kt
@@ -23,7 +23,7 @@ import de.justjanne.libquassel.protocol.variant.into
 import de.justjanne.libquassel.protocol.variant.qVariant
 import kotlinx.coroutines.flow.MutableStateFlow
 
-class HighlightRuleManager(
+open class HighlightRuleManager(
   session: Session
 ) : SyncableObject(session, "HighlightRuleManager"), HighlightRuleManagerStub {
   override fun fromVariantMap(properties: QVariantMap) {
diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/Identity.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/Identity.kt
index 30254bc..6690de8 100644
--- a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/Identity.kt
+++ b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/Identity.kt
@@ -9,3 +9,235 @@
  */
 
 package de.justjanne.libquassel.protocol.syncables
+
+import de.justjanne.libquassel.protocol.models.QStringList
+import de.justjanne.libquassel.protocol.models.ids.IdentityId
+import de.justjanne.libquassel.protocol.models.types.QtType
+import de.justjanne.libquassel.protocol.models.types.QuasselType
+import de.justjanne.libquassel.protocol.syncables.state.IdentityState
+import de.justjanne.libquassel.protocol.syncables.stubs.IdentityStub
+import de.justjanne.libquassel.protocol.util.update
+import de.justjanne.libquassel.protocol.variant.QVariantMap
+import de.justjanne.libquassel.protocol.variant.into
+import de.justjanne.libquassel.protocol.variant.qVariant
+import kotlinx.coroutines.flow.MutableStateFlow
+
+open class Identity(
+  session: Session
+) : SyncableObject(session, "Identity"), IdentityStub {
+  override fun init() {
+    renameObject(state().identifier())
+  }
+
+  override fun fromVariantMap(properties: QVariantMap) {
+    state.update {
+      copy(
+        identityId = properties["identityId"].into(identityId),
+        identityName = properties["identityName"].into(identityName),
+        realName = properties["realName"].into(realName),
+        nicks = properties["nicks"].into(nicks),
+        awayNick = properties["awayNick"].into(awayNick),
+        awayNickEnabled = properties["awayNickEnabled"].into(awayNickEnabled),
+        awayReason = properties["awayReason"].into(awayReason),
+        awayReasonEnabled = properties["awayReasonEnabled"].into(awayReasonEnabled),
+        autoAwayEnabled = properties["autoAwayEnabled"].into(autoAwayEnabled),
+        autoAwayTime = properties["autoAwayTime"].into(autoAwayTime),
+        autoAwayReason = properties["autoAwayReason"].into(autoAwayReason),
+        autoAwayReasonEnabled = properties["autoAwayReasonEnabled"].into(autoAwayReasonEnabled),
+        detachAwayEnabled = properties["detachAwayEnabled"].into(detachAwayEnabled),
+        detachAwayReason = properties["detachAwayReason"].into(detachAwayReason),
+        detachAwayReasonEnabled = properties["detachAwayReasonEnabled"].into(detachAwayReasonEnabled),
+        ident = properties["ident"].into(ident),
+        kickReason = properties["kickReason"].into(kickReason),
+        partReason = properties["partReason"].into(partReason),
+        quitReason = properties["quitReason"].into(quitReason),
+      )
+    }
+  }
+
+  override fun toVariantMap() = mapOf(
+    "identityId" to qVariant(id(), QuasselType.IdentityId),
+    "identityName" to qVariant(identityName(), QtType.QString),
+    "realName" to qVariant(realName(), QtType.QString),
+    "nicks" to qVariant(nicks(), QtType.QStringList),
+    "awayNick" to qVariant(awayNick(), QtType.QString),
+    "awayNickEnabled" to qVariant(awayNickEnabled(), QtType.Bool),
+    "awayReason" to qVariant(awayReason(), QtType.QString),
+    "awayReasonEnabled" to qVariant(awayReasonEnabled(), QtType.Bool),
+    "autoAwayEnabled" to qVariant(autoAwayEnabled(), QtType.Bool),
+    "autoAwayTime" to qVariant(autoAwayTime(), QtType.Int),
+    "autoAwayReason" to qVariant(autoAwayReason(), QtType.QString),
+    "autoAwayReasonEnabled" to qVariant(autoAwayReasonEnabled(), QtType.Bool),
+    "detachAwayEnabled" to qVariant(detachAwayEnabled(), QtType.Bool),
+    "detachAwayReason" to qVariant(detachAwayReason(), QtType.QString),
+    "detachAwayReasonEnabled" to qVariant(detachAwayReasonEnabled(), QtType.Bool),
+    "ident" to qVariant(ident(), QtType.QString),
+    "kickReason" to qVariant(kickReason(), QtType.QString),
+    "partReason" to qVariant(partReason(), QtType.QString),
+    "quitReason" to qVariant(quitReason(), QtType.QString)
+  )
+
+  fun id() = state().identityId
+  fun identityName() = state().identityName
+  fun realName() = state().realName
+  fun nicks() = state().nicks
+  fun awayNick() = state().awayNick
+  fun awayNickEnabled() = state().awayNickEnabled
+  fun awayReason() = state().awayReason
+  fun awayReasonEnabled() = state().awayReasonEnabled
+  fun autoAwayEnabled() = state().autoAwayEnabled
+  fun autoAwayTime() = state().autoAwayTime
+  fun autoAwayReason() = state().autoAwayReason
+  fun autoAwayReasonEnabled() = state().autoAwayReasonEnabled
+  fun detachAwayEnabled() = state().detachAwayEnabled
+  fun detachAwayReason() = state().detachAwayReason
+  fun detachAwayReasonEnabled() = state().detachAwayReasonEnabled
+  fun ident() = state().ident
+  fun kickReason() = state().kickReason
+  fun partReason() = state().partReason
+  fun quitReason() = state().quitReason
+
+  override fun setAutoAwayEnabled(enabled: Boolean) {
+    state.update {
+      copy(autoAwayEnabled = enabled)
+    }
+    super.setAutoAwayEnabled(enabled)
+  }
+
+  override fun setAutoAwayReason(reason: String?) {
+    state.update {
+      copy(autoAwayReason = reason ?: "")
+    }
+    super.setAutoAwayReason(reason)
+  }
+
+  override fun setAutoAwayReasonEnabled(enabled: Boolean) {
+    state.update {
+      copy(autoAwayReasonEnabled = enabled)
+    }
+    super.setAutoAwayReasonEnabled(enabled)
+  }
+
+  override fun setAutoAwayTime(time: Int) {
+    state.update {
+      copy(autoAwayTime = time)
+    }
+    super.setAutoAwayTime(time)
+  }
+
+  override fun setAwayNick(awayNick: String?) {
+    state.update {
+      copy(awayNick = awayNick ?: "")
+    }
+    super.setAwayNick(awayNick)
+  }
+
+  override fun setAwayNickEnabled(enabled: Boolean) {
+    state.update {
+      copy(awayNickEnabled = enabled)
+    }
+    super.setAwayNickEnabled(enabled)
+  }
+
+  override fun setAwayReason(awayReason: String?) {
+    state.update {
+      copy(awayReason = awayReason ?: "")
+    }
+    super.setAwayReason(awayReason)
+  }
+
+  override fun setAwayReasonEnabled(enabled: Boolean) {
+    state.update {
+      copy(awayReasonEnabled = enabled)
+    }
+    super.setAwayReasonEnabled(enabled)
+  }
+
+  override fun setDetachAwayEnabled(enabled: Boolean) {
+    state.update {
+      copy(detachAwayEnabled = enabled)
+    }
+    super.setDetachAwayEnabled(enabled)
+  }
+
+  override fun setDetachAwayReason(reason: String?) {
+    state.update {
+      copy(detachAwayReason = reason ?: "")
+    }
+    super.setDetachAwayReason(reason)
+  }
+
+  override fun setDetachAwayReasonEnabled(enabled: Boolean) {
+    state.update {
+      copy(detachAwayReasonEnabled = enabled)
+    }
+    super.setDetachAwayReasonEnabled(enabled)
+  }
+
+  override fun setId(id: IdentityId) {
+    state.update {
+      copy(identityId = id)
+    }
+    super.setId(id)
+  }
+
+  override fun setIdent(ident: String?) {
+    state.update {
+      copy(ident = ident ?: "")
+    }
+    super.setIdent(ident)
+  }
+
+  override fun setIdentityName(name: String?) {
+    state.update {
+      copy(identityName = name ?: "")
+    }
+    super.setIdentityName(name)
+  }
+
+  override fun setKickReason(reason: String?) {
+    state.update {
+      copy(kickReason = reason ?: "")
+    }
+    super.setKickReason(reason)
+  }
+
+  override fun setNicks(nicks: QStringList) {
+    state.update {
+      copy(nicks = nicks.map { it ?: "" })
+    }
+    super.setNicks(nicks)
+  }
+
+  override fun setPartReason(reason: String?) {
+    state.update {
+      copy(partReason = reason ?: "")
+    }
+    super.setPartReason(reason)
+  }
+
+  override fun setQuitReason(reason: String?) {
+    state.update {
+      copy(quitReason = reason ?: "")
+    }
+    super.setQuitReason(reason)
+  }
+
+  override fun setRealName(realName: String?) {
+    state.update {
+      copy(realName = realName ?: "")
+    }
+    super.setRealName(realName)
+  }
+
+  @Suppress("NOTHING_TO_INLINE")
+  inline fun state() = flow().value
+
+  @Suppress("NOTHING_TO_INLINE")
+  inline fun flow() = state
+
+  @PublishedApi
+  internal val state = MutableStateFlow(
+    IdentityState()
+  )
+}
diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/IrcChannel.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/IrcChannel.kt
index 885b4f0..210546c 100644
--- a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/IrcChannel.kt
+++ b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/IrcChannel.kt
@@ -24,7 +24,7 @@ import de.justjanne.libquassel.protocol.variant.into
 import de.justjanne.libquassel.protocol.variant.qVariant
 import kotlinx.coroutines.flow.MutableStateFlow
 
-class IrcChannel(
+open class IrcChannel(
   name: String,
   network: NetworkId,
   session: Session
diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/IrcListHelper.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/IrcListHelper.kt
index 30254bc..c997cb7 100644
--- a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/IrcListHelper.kt
+++ b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/IrcListHelper.kt
@@ -9,3 +9,14 @@
  */
 
 package de.justjanne.libquassel.protocol.syncables
+
+import de.justjanne.libquassel.protocol.syncables.stubs.IrcListHelperStub
+import de.justjanne.libquassel.protocol.variant.QVariantMap
+import de.justjanne.libquassel.protocol.variant.QVariant_
+
+open class IrcListHelper(
+  session: Session
+) : SyncableObject(session, "IrcListHelper"), IrcListHelperStub {
+  override fun fromVariantMap(properties: QVariantMap) = Unit
+  override fun toVariantMap() = emptyMap<String, QVariant_>()
+}
diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/IrcUser.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/IrcUser.kt
index 115f120..36b4fb2 100644
--- a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/IrcUser.kt
+++ b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/IrcUser.kt
@@ -24,7 +24,7 @@ import kotlinx.coroutines.flow.MutableStateFlow
 import org.threeten.bp.Instant
 import org.threeten.bp.temporal.Temporal
 
-class IrcUser(
+open class IrcUser(
   hostmask: String,
   network: NetworkId,
   session: Session
diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/Network.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/Network.kt
index 80bbef5..67a9bcc 100644
--- a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/Network.kt
+++ b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/Network.kt
@@ -34,7 +34,7 @@ import de.justjanne.libquassel.protocol.variant.qVariant
 import kotlinx.coroutines.flow.MutableStateFlow
 import java.nio.ByteBuffer
 
-class Network constructor(
+open class Network(
   networkId: NetworkId,
   session: Session
 ) : SyncableObject(session, "Network"), NetworkStub {
diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/NetworkConfig.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/NetworkConfig.kt
index 30254bc..36b4edc 100644
--- a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/NetworkConfig.kt
+++ b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/NetworkConfig.kt
@@ -9,3 +9,122 @@
  */
 
 package de.justjanne.libquassel.protocol.syncables
+
+import de.justjanne.libquassel.protocol.models.types.QtType
+import de.justjanne.libquassel.protocol.syncables.state.NetworkConfigState
+import de.justjanne.libquassel.protocol.syncables.stubs.NetworkConfigStub
+import de.justjanne.libquassel.protocol.util.update
+import de.justjanne.libquassel.protocol.variant.QVariantMap
+import de.justjanne.libquassel.protocol.variant.into
+import de.justjanne.libquassel.protocol.variant.qVariant
+import kotlinx.coroutines.flow.MutableStateFlow
+
+open class NetworkConfig(
+  session: Session
+) : SyncableObject(session, "NetworkConfig"), NetworkConfigStub {
+  override fun init() {
+    renameObject("GlobalNetworkConfig")
+  }
+
+  override fun fromVariantMap(properties: QVariantMap) {
+    state.update {
+      copy(
+        pingTimeoutEnabled = properties["pingTimeoutEnabled"].into(pingTimeoutEnabled),
+        pingInterval = properties["pingInterval"].into(pingInterval),
+        maxPingCount = properties["maxPingCount"].into(maxPingCount),
+        autoWhoEnabled = properties["autoWhoEnabled"].into(autoWhoEnabled),
+        autoWhoInterval = properties["autoWhoInterval"].into(autoWhoInterval),
+        autoWhoNickLimit = properties["autoWhoNickLimit"].into(autoWhoNickLimit),
+        autoWhoDelay = properties["autoWhoDelay"].into(autoWhoDelay),
+        standardCtcp = properties["standardCtcp"].into(standardCtcp),
+      )
+    }
+  }
+
+  override fun toVariantMap() = mapOf(
+    "pingTimeoutEnabled" to qVariant(pingTimeoutEnabled(), QtType.Bool),
+    "pingInterval" to qVariant(pingInterval(), QtType.Int),
+    "maxPingCount" to qVariant(maxPingCount(), QtType.Int),
+    "autoWhoEnabled" to qVariant(autoWhoEnabled(), QtType.Bool),
+    "autoWhoInterval" to qVariant(autoWhoInterval(), QtType.Int),
+    "autoWhoNickLimit" to qVariant(autoWhoNickLimit(), QtType.Int),
+    "autoWhoDelay" to qVariant(autoWhoDelay(), QtType.Int),
+    "standardCtcp" to qVariant(standardCtcp(), QtType.Bool)
+  )
+
+  fun pingTimeoutEnabled() = state().pingTimeoutEnabled
+  fun pingInterval() = state().pingInterval
+  fun maxPingCount() = state().maxPingCount
+  fun autoWhoEnabled() = state().autoWhoEnabled
+  fun autoWhoInterval() = state().autoWhoInterval
+  fun autoWhoNickLimit() = state().autoWhoNickLimit
+  fun autoWhoDelay() = state().autoWhoDelay
+  fun standardCtcp() = state().standardCtcp
+
+  override fun setAutoWhoDelay(delay: Int) {
+    state.update {
+      copy(autoWhoDelay = delay)
+    }
+    super.setAutoWhoDelay(delay)
+  }
+
+  override fun setAutoWhoEnabled(enabled: Boolean) {
+    state.update {
+      copy(autoWhoEnabled = enabled)
+    }
+    super.setAutoWhoEnabled(enabled)
+  }
+
+  override fun setAutoWhoInterval(interval: Int) {
+    state.update {
+      copy(autoWhoInterval = interval)
+    }
+    super.setAutoWhoInterval(interval)
+  }
+
+  override fun setAutoWhoNickLimit(limit: Int) {
+    state.update {
+      copy(autoWhoNickLimit = limit)
+    }
+    super.setAutoWhoNickLimit(limit)
+  }
+
+  override fun setMaxPingCount(count: Int) {
+    state.update {
+      copy(maxPingCount = count)
+    }
+    super.setMaxPingCount(count)
+  }
+
+  override fun setPingInterval(interval: Int) {
+    state.update {
+      copy(pingInterval = interval)
+    }
+    super.setPingInterval(interval)
+  }
+
+  override fun setPingTimeoutEnabled(enabled: Boolean) {
+    state.update {
+      copy(pingTimeoutEnabled = enabled)
+    }
+    super.setPingTimeoutEnabled(enabled)
+  }
+
+  override fun setStandardCtcp(enabled: Boolean) {
+    state.update {
+      copy(standardCtcp = enabled)
+    }
+    super.setStandardCtcp(enabled)
+  }
+
+  @Suppress("NOTHING_TO_INLINE")
+  inline fun state() = flow().value
+
+  @Suppress("NOTHING_TO_INLINE")
+  inline fun flow() = state
+
+  @PublishedApi
+  internal val state = MutableStateFlow(
+    NetworkConfigState()
+  )
+}
diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/Session.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/Session.kt
index 75fd70a..42c2184 100644
--- a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/Session.kt
+++ b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/Session.kt
@@ -14,17 +14,30 @@ import de.justjanne.libquassel.annotations.ProtocolSide
 import de.justjanne.libquassel.protocol.models.SignalProxyMessage
 import de.justjanne.libquassel.protocol.models.ids.IdentityId
 import de.justjanne.libquassel.protocol.models.ids.NetworkId
-import de.justjanne.libquassel.protocol.syncables.stubs.IdentityStub
+import de.justjanne.libquassel.protocol.syncables.stubs.BacklogManagerStub
+import de.justjanne.libquassel.protocol.syncables.stubs.IgnoreListManagerStub
+import de.justjanne.libquassel.protocol.syncables.stubs.IrcListHelperStub
 import de.justjanne.libquassel.protocol.syncables.stubs.RpcHandlerStub
 import de.justjanne.libquassel.protocol.variant.QVariantList
 
 interface Session : RpcHandlerStub {
   val protocolSide: ProtocolSide
-
   val objectRepository: ObjectRepository
 
   fun network(id: NetworkId): Network?
-  fun identity(id: IdentityId): IdentityStub
+  fun identity(id: IdentityId): Identity
+
+  fun aliasManager(): AliasManager
+  fun bufferSyncer(): BufferSyncer
+  fun backlogManager(): BacklogManagerStub
+  fun bufferViewManager(): BufferViewManager
+  fun ignoreListManager(): IgnoreListManagerStub
+  fun highlightRuleManager(): HighlightRuleManager
+  fun ircListHelper(): IrcListHelperStub
+
+  fun coreInfo(): CoreInfo
+  fun dccConfig(): DccConfig
+  fun networkConfig(): NetworkConfig
 
   fun synchronize(it: SyncableObject)
   fun stopSynchronize(it: SyncableObject)
diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/state/BufferSyncerState.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/state/BufferSyncerState.kt
index 182244f..b479fbc 100644
--- a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/state/BufferSyncerState.kt
+++ b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/state/BufferSyncerState.kt
@@ -9,3 +9,54 @@
  */
 
 package de.justjanne.libquassel.protocol.syncables.state
+
+import de.justjanne.libquassel.protocol.models.BufferInfo
+import de.justjanne.libquassel.protocol.models.flags.BufferTypes
+import de.justjanne.libquassel.protocol.models.flags.MessageTypes
+import de.justjanne.libquassel.protocol.models.ids.BufferId
+import de.justjanne.libquassel.protocol.models.ids.MsgId
+import de.justjanne.libquassel.protocol.models.ids.NetworkId
+
+data class BufferSyncerState(
+  val activities: Map<BufferId, MessageTypes> = emptyMap(),
+  val highlightCounts: Map<BufferId, Int> = emptyMap(),
+  val lastSeenMsg: Map<BufferId, MsgId> = emptyMap(),
+  val markerLines: Map<BufferId, MsgId> = emptyMap(),
+  val bufferInfos: Map<BufferId, BufferInfo> = emptyMap()
+) {
+  fun where(
+    bufferName: String? = null,
+    bufferId: BufferId? = null,
+    networkId: NetworkId? = null,
+    type: BufferTypes? = null,
+    groupId: Int? = null,
+    networkState: NetworkState? = null
+  ) = bufferInfos.values.asSequence()
+    .filter {
+      bufferName == null ||
+        networkState == null ||
+        networkState.caseMapper().equalsIgnoreCase(it.bufferName, bufferName)
+    }
+    .filter { bufferId == null || it.bufferId == bufferId }
+    .filter { networkId == null || it.networkId == networkId }
+    .filter { type == null || it.type == type }
+    .filter { groupId == null || it.groupId == groupId }
+
+  fun find(
+    bufferName: String? = null,
+    bufferId: BufferId? = null,
+    networkId: NetworkId? = null,
+    type: BufferTypes? = null,
+    groupId: Int? = null,
+    networkState: NetworkState? = null
+  ) = where(bufferName, bufferId, networkId, type, groupId, networkState).firstOrNull()
+
+  fun all(
+    bufferName: String? = null,
+    bufferId: BufferId? = null,
+    networkId: NetworkId? = null,
+    type: BufferTypes? = null,
+    groupId: Int? = null,
+    networkState: NetworkState? = null
+  ) = where(bufferName, bufferId, networkId, type, groupId, networkState).toList()
+}
diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/state/BufferViewConfigState.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/state/BufferViewConfigState.kt
index 182244f..663ba1e 100644
--- a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/state/BufferViewConfigState.kt
+++ b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/state/BufferViewConfigState.kt
@@ -9,3 +9,28 @@
  */
 
 package de.justjanne.libquassel.protocol.syncables.state
+
+import de.justjanne.bitflags.none
+import de.justjanne.libquassel.protocol.models.flags.BufferActivities
+import de.justjanne.libquassel.protocol.models.flags.BufferActivity
+import de.justjanne.libquassel.protocol.models.flags.BufferType
+import de.justjanne.libquassel.protocol.models.flags.BufferTypes
+import de.justjanne.libquassel.protocol.models.ids.BufferId
+import de.justjanne.libquassel.protocol.models.ids.NetworkId
+
+data class BufferViewConfigState(
+  val bufferViewId: Int,
+  val bufferViewName: String = "",
+  val networkId: NetworkId = NetworkId(0),
+  val addNewBuffersAutomatically: Boolean = true,
+  val sortAlphabetically: Boolean = true,
+  val hideInactiveBuffers: Boolean = false,
+  val hideInactiveNetworks: Boolean = false,
+  val disableDecoration: Boolean = false,
+  val allowedBufferTypes: BufferTypes = BufferType.all,
+  val minimumActivity: BufferActivities = BufferActivity.none(),
+  val showSearch: Boolean = false,
+  val buffers: List<BufferId> = emptyList(),
+  val removedBuffers: Set<BufferId> = emptySet(),
+  val temporarilyRemovedBuffers: Set<BufferId> = emptySet(),
+)
diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/state/BufferViewManagerState.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/state/BufferViewManagerState.kt
index 182244f..785a6f4 100644
--- a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/state/BufferViewManagerState.kt
+++ b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/state/BufferViewManagerState.kt
@@ -9,3 +9,13 @@
  */
 
 package de.justjanne.libquassel.protocol.syncables.state
+
+import de.justjanne.libquassel.protocol.syncables.BufferViewConfig
+
+data class BufferViewManagerState(
+  val bufferViewConfigs: Map<Int, BufferViewConfig> = emptyMap()
+) {
+  fun contains(bufferViewId: Int) = bufferViewConfigs.containsKey(bufferViewId)
+  fun bufferViewConfig(bufferViewId: Int) = bufferViewConfigs[bufferViewId]
+  fun bufferViewConfigs() = bufferViewConfigs.values
+}
diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/state/IdentityState.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/state/IdentityState.kt
index 182244f..d814eb7 100644
--- a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/state/IdentityState.kt
+++ b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/state/IdentityState.kt
@@ -9,3 +9,29 @@
  */
 
 package de.justjanne.libquassel.protocol.syncables.state
+
+import de.justjanne.libquassel.protocol.models.ids.IdentityId
+
+data class IdentityState(
+  val identityId: IdentityId = IdentityId(-1),
+  val identityName: String = "<empty>",
+  val realName: String = "",
+  val nicks: List<String> = listOf("quassel"),
+  val awayNick: String = "",
+  val awayNickEnabled: Boolean = false,
+  val awayReason: String = "Gone fishing.",
+  val awayReasonEnabled: Boolean = true,
+  val autoAwayEnabled: Boolean = false,
+  val autoAwayTime: Int = 10,
+  val autoAwayReason: String = "Not here. No really. not here!",
+  val autoAwayReasonEnabled: Boolean = false,
+  val detachAwayEnabled: Boolean = false,
+  val detachAwayReason: String = "All Quassel clients vanished from the face of the earth...",
+  val detachAwayReasonEnabled: Boolean = false,
+  val ident: String = "quassel",
+  val kickReason: String = "Kindergarten is elsewhere!",
+  val partReason: String = "http://quassel-irc.org - Chat comfortably. Anywhere.",
+  val quitReason: String = "http://quassel-irc.org - Chat comfortably. Anywhere."
+) {
+  fun identifier() = "${identityId.id}"
+}
diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/state/NetworkConfigState.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/state/NetworkConfigState.kt
index 182244f..5733a20 100644
--- a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/state/NetworkConfigState.kt
+++ b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/state/NetworkConfigState.kt
@@ -9,3 +9,14 @@
  */
 
 package de.justjanne.libquassel.protocol.syncables.state
+
+data class NetworkConfigState(
+  val pingTimeoutEnabled: Boolean = true,
+  val pingInterval: Int = 30,
+  val maxPingCount: Int = 6,
+  val autoWhoEnabled: Boolean = true,
+  val autoWhoInterval: Int = 90,
+  val autoWhoNickLimit: Int = 200,
+  val autoWhoDelay: Int = 5,
+  val standardCtcp: Boolean = false
+)
diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/stubs/BacklogManagerStub.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/stubs/BacklogManagerStub.kt
index 77656fb..f9dc5b8 100644
--- a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/stubs/BacklogManagerStub.kt
+++ b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/stubs/BacklogManagerStub.kt
@@ -18,6 +18,7 @@ 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.syncables.SyncableStub
+import de.justjanne.libquassel.protocol.variant.QVariantList
 import de.justjanne.libquassel.protocol.variant.qVariant
 
 @SyncedObject("BacklogManager")
@@ -108,7 +109,8 @@ interface BacklogManagerStub : SyncableStub {
     first: MsgId = MsgId(-1),
     last: MsgId = MsgId(-1),
     limit: Int = -1,
-    additional: Int = 0
+    additional: Int = 0,
+    messages: QVariantList
   ) {
     sync(
       target = ProtocolSide.CLIENT,
@@ -118,6 +120,7 @@ interface BacklogManagerStub : SyncableStub {
       qVariant(last, QuasselType.MsgId),
       qVariant(limit, QtType.Int),
       qVariant(additional, QtType.Int),
+      qVariant(messages, QtType.QVariantList),
     )
   }
 
@@ -129,7 +132,8 @@ interface BacklogManagerStub : SyncableStub {
     limit: Int = -1,
     additional: Int = 0,
     type: Int = -1,
-    flags: Int = -1
+    flags: Int = -1,
+    messages: QVariantList
   ) {
     sync(
       target = ProtocolSide.CLIENT,
@@ -141,6 +145,7 @@ interface BacklogManagerStub : SyncableStub {
       qVariant(additional, QtType.Int),
       qVariant(type, QtType.Int),
       qVariant(flags, QtType.Int),
+      qVariant(messages, QtType.QVariantList),
     )
   }
 
@@ -149,7 +154,8 @@ interface BacklogManagerStub : SyncableStub {
     first: MsgId = MsgId(-1),
     last: MsgId = MsgId(-1),
     limit: Int = -1,
-    additional: Int = 0
+    additional: Int = 0,
+    messages: QVariantList
   ) {
     sync(
       target = ProtocolSide.CLIENT,
@@ -158,6 +164,7 @@ interface BacklogManagerStub : SyncableStub {
       qVariant(last, QuasselType.MsgId),
       qVariant(limit, QtType.Int),
       qVariant(additional, QtType.Int),
+      qVariant(messages, QtType.QVariantList),
     )
   }
 
@@ -168,7 +175,8 @@ interface BacklogManagerStub : SyncableStub {
     limit: Int = -1,
     additional: Int = 0,
     type: Int = -1,
-    flags: Int = -1
+    flags: Int = -1,
+    messages: QVariantList
   ) {
     sync(
       target = ProtocolSide.CLIENT,
@@ -179,6 +187,7 @@ interface BacklogManagerStub : SyncableStub {
       qVariant(additional, QtType.Int),
       qVariant(type, QtType.Int),
       qVariant(flags, QtType.Int),
+      qVariant(messages, QtType.QVariantList),
     )
   }
 }
diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/util/move.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/util/move.kt
new file mode 100644
index 0000000..63d7016
--- /dev/null
+++ b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/util/move.kt
@@ -0,0 +1,25 @@
+/*
+ * libquassel
+ * Copyright (c) 2021 Janne Mareike Koschinski
+ * Copyright (c) 2021 The Quassel Project
+ *
+ * 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.util
+
+@Suppress("NOTHING_TO_INLINE")
+inline fun <T> List<T>.move(value: T, pos: Int = size): List<T> {
+  val newPos = pos.coerceIn(0, size)
+  val oldPos = indexOf(value)
+
+  return if (oldPos > newPos) {
+    remove(value).insert(value, newPos)
+  } else if (newPos > oldPos) {
+    remove(value).insert(value, newPos - 1)
+  } else {
+    this
+  }
+}
diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/util/pairs.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/util/pairs.kt
index 794cb1a..49305be 100644
--- a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/util/pairs.kt
+++ b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/util/pairs.kt
@@ -14,14 +14,13 @@ package de.justjanne.libquassel.protocol.util
  * Returns a partitioned list of pairs
  */
 fun <T> Iterable<T>.pairs(): List<Pair<T, T>> {
-  zipWithNext()
   return pairs { a, b -> Pair(a, b) }
 }
 
 /**
  * Returns a partitioned list of pairs transformed with the given transformer
  */
-inline fun <T, R> Iterable<T>.pairs(transform: (a: T, b: T) -> R): List<R> {
+inline fun <T, R> Iterable<T>.pairs(crossinline transform: (a: T, b: T) -> R): List<R> {
   val iterator = iterator()
   val result = mutableListOf<R>()
   while (true) {
diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/util/plus.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/util/plus.kt
new file mode 100644
index 0000000..ff91f83
--- /dev/null
+++ b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/util/plus.kt
@@ -0,0 +1,25 @@
+/*
+ * libquassel
+ * Copyright (c) 2021 Janne Mareike Koschinski
+ * Copyright (c) 2021 The Quassel Project
+ *
+ * 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.util
+
+@Suppress("NOTHING_TO_INLINE")
+inline fun <T> List<T>.insert(value: T, pos: Int = size): List<T> {
+  return if (pos <= 0) {
+    listOf(value) + this
+  } else if (pos >= size) {
+    this + value
+  } else {
+    val before = subList(0, pos)
+    val after = subList(pos, size)
+
+    before + value + after
+  }
+}
diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/util/remove.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/util/remove.kt
new file mode 100644
index 0000000..fde9dcf
--- /dev/null
+++ b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/util/remove.kt
@@ -0,0 +1,14 @@
+/*
+ * libquassel
+ * Copyright (c) 2021 Janne Mareike Koschinski
+ * Copyright (c) 2021 The Quassel Project
+ *
+ * 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.util
+
+@Suppress("NOTHING_TO_INLINE")
+inline fun <T> List<T>.remove(value: T): List<T> = this.filter { it != value }
-- 
GitLab