From b31f50c65413505da860a98474169f47a1a565bc Mon Sep 17 00:00:00 2001
From: Janne Koschinski <janne@kuschku.de>
Date: Wed, 6 Dec 2017 01:17:46 +0100
Subject: [PATCH] Implement persistent message storage

---
 .../persistence/QuasselBacklogStorage.kt      | 35 ++++++++++++++
 .../persistence/QuasselDatabase.kt            | 19 ++++++--
 .../quasseldroid_ng/service/QuasselService.kt |  3 +-
 .../ui/chat/BufferListAdapter.kt              |  8 +++-
 .../quasseldroid_ng/ui/chat/ChatActivity.kt   | 14 +-----
 gradle/wrapper/gradle-wrapper.properties      |  2 +-
 .../de/kuschku/libquassel/protocol/Message.kt |  2 -
 .../kuschku/libquassel/protocol/QVariant.kt   | 46 ++++++++-----------
 .../quassel/syncables/BacklogManager.kt       | 22 +++++++--
 .../quassel/syncables/RpcHandler.kt           |  9 ++--
 .../libquassel/session/BacklogStorage.kt      | 14 ++++++
 .../libquassel/session/CoreConnection.kt      |  1 -
 .../de/kuschku/libquassel/session/Session.kt  |  6 +--
 .../libquassel/session/SessionManager.kt      |  4 +-
 14 files changed, 121 insertions(+), 64 deletions(-)
 create mode 100644 app/src/main/java/de/kuschku/quasseldroid_ng/persistence/QuasselBacklogStorage.kt
 create mode 100644 lib/src/main/java/de/kuschku/libquassel/session/BacklogStorage.kt

diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/persistence/QuasselBacklogStorage.kt b/app/src/main/java/de/kuschku/quasseldroid_ng/persistence/QuasselBacklogStorage.kt
new file mode 100644
index 000000000..f78640484
--- /dev/null
+++ b/app/src/main/java/de/kuschku/quasseldroid_ng/persistence/QuasselBacklogStorage.kt
@@ -0,0 +1,35 @@
+package de.kuschku.quasseldroid_ng.persistence
+
+import de.kuschku.libquassel.protocol.BufferId
+import de.kuschku.libquassel.protocol.Message
+import de.kuschku.libquassel.session.BacklogStorage
+
+class QuasselBacklogStorage(private val db: QuasselDatabase) : BacklogStorage {
+  override fun storeMessages(vararg messages: Message) {
+    for (message in messages) {
+      db.message().save(QuasselDatabase.DatabaseMessage(
+        messageId = message.messageId,
+        time = message.time,
+        type = message.type.value,
+        flag = message.flag.value,
+        bufferId = message.bufferInfo.bufferId,
+        sender = message.sender,
+        senderPrefixes = message.senderPrefixes,
+        content = message.content
+      ))
+    }
+  }
+
+  override fun clearMessages(bufferId: BufferId, idRange: IntRange) {
+    db.message().clearMessages(bufferId, idRange)
+  }
+
+  override fun clearMessages(bufferId: BufferId) {
+    db.message().clearMessages(bufferId)
+  }
+
+  override fun clearMessages() {
+    db.message().clearMessages()
+  }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/persistence/QuasselDatabase.kt b/app/src/main/java/de/kuschku/quasseldroid_ng/persistence/QuasselDatabase.kt
index 1001be61f..fa26a136d 100644
--- a/app/src/main/java/de/kuschku/quasseldroid_ng/persistence/QuasselDatabase.kt
+++ b/app/src/main/java/de/kuschku/quasseldroid_ng/persistence/QuasselDatabase.kt
@@ -49,19 +49,24 @@ abstract class QuasselDatabase : RoomDatabase() {
     @Query("SELECT * FROM message WHERE bufferId = :bufferId ORDER BY messageId DESC LIMIT 1")
     fun findLastByBufferId(bufferId: Int): DatabaseMessage
 
-    @Update(onConflict = OnConflictStrategy.REPLACE)
-    fun save(entity: DatabaseMessage)
+    @Insert(onConflict = OnConflictStrategy.REPLACE)
+    fun save(vararg entities: DatabaseMessage)
 
     @Query("UPDATE message SET bufferId = :bufferId1 WHERE bufferId = :bufferId2")
     fun merge(@IntRange(from = 0) bufferId1: Int, @IntRange(from = 0) bufferId2: Int)
 
+    @Query("DELETE FROM message")
+    fun clearMessages()
+
     @Query("DELETE FROM message WHERE bufferId = :bufferId")
-    fun clearBuffer(@IntRange(from = 0) bufferId: Int)
+    fun clearMessages(@IntRange(from = 0) bufferId: Int)
+
+    @Query("DELETE FROM message WHERE bufferId = :bufferId AND messageId >= :first AND messageId <= :last")
+    fun clearMessages(@IntRange(from = 0) bufferId: Int, first: Int, last: Int)
   }
 
   object Creator {
     private var database: QuasselDatabase? = null
-      private set
 
     // For Singleton instantiation
     private val LOCK = Any()
@@ -71,7 +76,7 @@ abstract class QuasselDatabase : RoomDatabase() {
         synchronized(LOCK) {
           if (database == null) {
             database = Room.databaseBuilder(context.applicationContext,
-                                            QuasselDatabase::class.java, DATABASE_NAME)
+              QuasselDatabase::class.java, DATABASE_NAME)
               .build()
           }
         }
@@ -84,3 +89,7 @@ abstract class QuasselDatabase : RoomDatabase() {
     const val DATABASE_NAME = "persistence-clientData"
   }
 }
+
+fun QuasselDatabase.MessageDao.clearMessages(@IntRange(from = 0) bufferId: Int, idRange: kotlin.ranges.IntRange) {
+  this.clearMessages(bufferId, idRange.first, idRange.last)
+}
\ No newline at end of file
diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/service/QuasselService.kt b/app/src/main/java/de/kuschku/quasseldroid_ng/service/QuasselService.kt
index 50a88bf38..76a57ee88 100644
--- a/app/src/main/java/de/kuschku/quasseldroid_ng/service/QuasselService.kt
+++ b/app/src/main/java/de/kuschku/quasseldroid_ng/service/QuasselService.kt
@@ -8,6 +8,7 @@ import de.kuschku.libquassel.protocol.*
 import de.kuschku.libquassel.session.*
 import de.kuschku.quasseldroid_ng.BuildConfig
 import de.kuschku.quasseldroid_ng.R
+import de.kuschku.quasseldroid_ng.persistence.QuasselBacklogStorage
 import de.kuschku.quasseldroid_ng.persistence.QuasselDatabase
 import de.kuschku.quasseldroid_ng.util.AndroidHandlerThread
 import de.kuschku.quasseldroid_ng.util.compatibility.AndroidHandlerService
@@ -99,7 +100,7 @@ class QuasselService : LifecycleService() {
     handler.onCreate()
     super.onCreate()
     database = QuasselDatabase.Creator.init(application)
-    sessionManager = SessionManager(ISession.NULL)
+    sessionManager = SessionManager(ISession.NULL, QuasselBacklogStorage(database))
     clientData = ClientData(
       identifier = "${resources.getString(R.string.app_name)} ${BuildConfig.VERSION_NAME}",
       buildDate = Instant.ofEpochSecond(BuildConfig.GIT_COMMIT_DATE),
diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/BufferListAdapter.kt b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/BufferListAdapter.kt
index 066856f2c..f11f3f84c 100644
--- a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/BufferListAdapter.kt
+++ b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/BufferListAdapter.kt
@@ -12,6 +12,7 @@ import android.widget.TextView
 import butterknife.BindView
 import butterknife.ButterKnife
 import de.kuschku.libquassel.quassel.BufferInfo
+import de.kuschku.libquassel.util.hasFlag
 
 class BufferListAdapter(
   lifecycleOwner: LifecycleOwner,
@@ -25,7 +26,7 @@ class BufferListAdapter(
     liveData.observe(lifecycleOwner, Observer { list: List<BufferInfo>? ->
       runInBackground {
         val old = data
-        val new = list ?: emptyList()
+        val new = list?.sortedBy(BufferInfo::networkId) ?: emptyList()
         val result = DiffUtil.calculateDiff(
           object : DiffUtil.Callback() {
             override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int)
@@ -65,7 +66,10 @@ class BufferListAdapter(
     }
 
     fun bind(info: BufferInfo) {
-      text.text = "${info.networkId}/${info.bufferName}"
+      text.text = when {
+        info.type.hasFlag(BufferInfo.Type.StatusBuffer) -> "Network ${info.networkId}"
+        else -> "${info.networkId}/${info.bufferName}"
+      }
     }
   }
 }
diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/ChatActivity.kt b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/ChatActivity.kt
index 89ec4a464..8767ac897 100644
--- a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/ChatActivity.kt
+++ b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/ChatActivity.kt
@@ -20,7 +20,6 @@ import de.kuschku.libquassel.session.SessionManager
 import de.kuschku.libquassel.session.SocketAddress
 import de.kuschku.libquassel.util.compatibility.LoggingHandler
 import de.kuschku.libquassel.util.compatibility.LoggingHandler.LogLevel.INFO
-import de.kuschku.malheur.CrashHandler
 import de.kuschku.quasseldroid_ng.Keys
 import de.kuschku.quasseldroid_ng.R
 import de.kuschku.quasseldroid_ng.persistence.AccountDatabase
@@ -112,17 +111,6 @@ class ChatActivity : ServiceBoundActivity() {
               true
             )
           }
-          CrashHandler.handle(
-            IllegalArgumentException(
-              "WRONG!",
-              RuntimeException(
-                "WRONG!",
-                NullPointerException(
-                  "Super wrong!"
-                )
-              )
-            )
-          )
         }
       }
     })
@@ -131,7 +119,7 @@ class ChatActivity : ServiceBoundActivity() {
       errorList.text = ""
     }
 
-    state.observeSticky(this, Observer {
+    state.observe(this, Observer {
       val status = it ?: ConnectionState.DISCONNECTED
 
       snackbar?.dismiss()
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index b4444cb3f..1ae31fe93 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -2,5 +2,5 @@ distributionBase=GRADLE_USER_HOME
 distributionPath=wrapper/dists
 zipStoreBase=GRADLE_USER_HOME
 zipStorePath=wrapper/dists
-distributionUrl=https\://repo.gradle.org/gradle/dist-snapshots/gradle-kotlin-dsl-4.3-20171010191714+0000-all.zip
+distributionUrl=https://services.gradle.org/distributions/gradle-4.3.1-all.zip
 
diff --git a/lib/src/main/java/de/kuschku/libquassel/protocol/Message.kt b/lib/src/main/java/de/kuschku/libquassel/protocol/Message.kt
index 67c4138a2..cb8be217a 100644
--- a/lib/src/main/java/de/kuschku/libquassel/protocol/Message.kt
+++ b/lib/src/main/java/de/kuschku/libquassel/protocol/Message.kt
@@ -60,6 +60,4 @@ class Message(
   override fun toString(): String {
     return "Message(messageId=$messageId, time=$time, type=$type, flag=$flag, bufferInfo=$bufferInfo, sender='$sender', senderPrefixes='$senderPrefixes', content='$content')"
   }
-
-
 }
diff --git a/lib/src/main/java/de/kuschku/libquassel/protocol/QVariant.kt b/lib/src/main/java/de/kuschku/libquassel/protocol/QVariant.kt
index db7dbdca9..933c3d2c0 100644
--- a/lib/src/main/java/de/kuschku/libquassel/protocol/QVariant.kt
+++ b/lib/src/main/java/de/kuschku/libquassel/protocol/QVariant.kt
@@ -5,41 +5,35 @@ class QVariant<T>(val data: T?, val type: MetaType<T>) {
   constructor(data: T?, type: QType) : this(data, type.typeName)
   constructor(data: T?, type: String) : this(data, MetaType.Companion.get(type))
 
-  private fun <U> coerce(): QVariant<U> {
-    return this as QVariant<U>
-  }
-
   fun or(defValue: T): T {
     return data ?: defValue
   }
 
-  fun <U> _value(defValue: U): U {
-    return this.coerce<U>().data ?: defValue
-  }
-
-  fun <U> _valueOr(f: () -> U): U {
-    return this.coerce<U>().data ?: f()
-  }
-
-  fun <U> _valueOrThrow(): U = this._valueOrThrow(NullPointerException())
-
-  fun <U> _valueOrThrow(e: Throwable): U {
-    return this.coerce<U>().data ?: throw e
-  }
-
   override fun toString(): String {
     return "QVariant(${type.name}, $data)"
   }
 }
 
-fun <U> QVariant_?.value(): U?
-  = this?._value<U?>(null)
+@PublishedApi
+internal inline fun <reified U> QVariant_?.coerce(): QVariant<U>? {
+  return if (this?.data is U) {
+    this as QVariant<U>
+  } else {
+    null
+  }
+}
+
+inline fun <reified U> QVariant_?.value(): U?
+  = this?.value<U?>(null)
+
+inline fun <reified U> QVariant_?.value(defValue: U): U
+  = this.coerce<U>()?.data ?: defValue
 
-fun <U> QVariant_?.value(defValue: U): U
-  = this?._value(defValue) ?: defValue
+inline fun <reified U> QVariant_?.valueOr(f: () -> U): U
+  = this.coerce<U>()?.data ?: f()
 
-fun <U> QVariant_?.valueOr(f: () -> U): U
-  = this?._valueOr(f) ?: f()
+inline fun <reified U> QVariant_?.valueOrThrow(e: Throwable = NullPointerException()): U
+  = this.coerce<U>()?.data ?: throw e
 
-fun <U> QVariant_?.valueOrThrow(e: Throwable = NullPointerException()): U
-  = this?._valueOrThrow<U>(e) ?: throw e
+inline fun <reified U> QVariant_?.valueOrThrow(e: () -> Throwable): U
+  = this.coerce<U>()?.data ?: throw e()
diff --git a/lib/src/main/java/de/kuschku/libquassel/quassel/syncables/BacklogManager.kt b/lib/src/main/java/de/kuschku/libquassel/quassel/syncables/BacklogManager.kt
index e970e0856..598f70e39 100644
--- a/lib/src/main/java/de/kuschku/libquassel/quassel/syncables/BacklogManager.kt
+++ b/lib/src/main/java/de/kuschku/libquassel/quassel/syncables/BacklogManager.kt
@@ -1,19 +1,31 @@
 package de.kuschku.libquassel.quassel.syncables
 
-import de.kuschku.libquassel.protocol.BufferId
-import de.kuschku.libquassel.protocol.MsgId
-import de.kuschku.libquassel.protocol.QVariantList
+import de.kuschku.libquassel.protocol.*
 import de.kuschku.libquassel.quassel.syncables.interfaces.IBacklogManager
+import de.kuschku.libquassel.session.BacklogStorage
 import de.kuschku.libquassel.session.SignalProxy
+import de.kuschku.libquassel.util.compatibility.LoggingHandler
+import de.kuschku.libquassel.util.compatibility.log
 
-class BacklogManager constructor(
-  proxy: SignalProxy
+class BacklogManager(
+  proxy: SignalProxy,
+  private val backlogStorage: BacklogStorage
 ) : SyncableObject(proxy, "BacklogManager"), IBacklogManager {
   override fun receiveBacklog(bufferId: BufferId, first: MsgId, last: MsgId, limit: Int,
                               additional: Int, messages: QVariantList) {
+    for (message: Message in messages.mapNotNull<QVariant_, Message>(QVariant_::value)) {
+      if (message.bufferInfo.bufferId != bufferId) {
+        // Check if it works here
+        log(LoggingHandler.LogLevel.ERROR, "message has inconsistent bufferid: $bufferId != ${message.bufferInfo.bufferId}")
+      }
+      backlogStorage.storeMessages(message)
+    }
   }
 
   override fun receiveBacklogAll(first: MsgId, last: MsgId, limit: Int, additional: Int,
                                  messages: QVariantList) {
+    for (message: Message in messages.mapNotNull<QVariant_, Message>(QVariant_::value)) {
+      backlogStorage.storeMessages(message)
+    }
   }
 }
diff --git a/lib/src/main/java/de/kuschku/libquassel/quassel/syncables/RpcHandler.kt b/lib/src/main/java/de/kuschku/libquassel/quassel/syncables/RpcHandler.kt
index fa9001ca0..2cf9c2190 100644
--- a/lib/src/main/java/de/kuschku/libquassel/quassel/syncables/RpcHandler.kt
+++ b/lib/src/main/java/de/kuschku/libquassel/quassel/syncables/RpcHandler.kt
@@ -5,12 +5,15 @@ import de.kuschku.libquassel.protocol.primitive.serializer.StringSerializer
 import de.kuschku.libquassel.quassel.BufferInfo
 import de.kuschku.libquassel.quassel.syncables.interfaces.INetwork
 import de.kuschku.libquassel.quassel.syncables.interfaces.IRpcHandler
+import de.kuschku.libquassel.session.BacklogStorage
 import de.kuschku.libquassel.session.Session
-import de.kuschku.libquassel.session.SignalProxy
 import de.kuschku.libquassel.util.helpers.deserializeString
 import java.nio.ByteBuffer
 
-class RpcHandler(override val session: Session) : IRpcHandler {
+class RpcHandler(
+  override val session: Session,
+  private val backlogStorage: BacklogStorage
+) : IRpcHandler {
   override fun displayStatusMsg(net: String, msg: String) {
   }
 
@@ -42,7 +45,7 @@ class RpcHandler(override val session: Session) : IRpcHandler {
   }
 
   override fun displayMsg(message: Message) {
-    println(message)
+    backlogStorage.storeMessages(message)
   }
 
   override fun requestCreateIdentity(identity: QVariantMap, additional: QVariantMap) {
diff --git a/lib/src/main/java/de/kuschku/libquassel/session/BacklogStorage.kt b/lib/src/main/java/de/kuschku/libquassel/session/BacklogStorage.kt
new file mode 100644
index 000000000..52aaa577a
--- /dev/null
+++ b/lib/src/main/java/de/kuschku/libquassel/session/BacklogStorage.kt
@@ -0,0 +1,14 @@
+package de.kuschku.libquassel.session
+
+import de.kuschku.libquassel.protocol.BufferId
+import de.kuschku.libquassel.protocol.Message
+
+interface BacklogStorage {
+  fun storeMessages(vararg messages: Message)
+
+  fun clearMessages(bufferId: BufferId, idRange: IntRange)
+
+  fun clearMessages(bufferId: BufferId)
+
+  fun clearMessages()
+}
\ No newline at end of file
diff --git a/lib/src/main/java/de/kuschku/libquassel/session/CoreConnection.kt b/lib/src/main/java/de/kuschku/libquassel/session/CoreConnection.kt
index 968896ea4..f5776b8c9 100644
--- a/lib/src/main/java/de/kuschku/libquassel/session/CoreConnection.kt
+++ b/lib/src/main/java/de/kuschku/libquassel/session/CoreConnection.kt
@@ -114,7 +114,6 @@ class CoreConnection(
 
   override fun close() {
     try {
-      channel?.close()
       interrupt()
       handlerService.quit()
       setState(ConnectionState.DISCONNECTED)
diff --git a/lib/src/main/java/de/kuschku/libquassel/session/Session.kt b/lib/src/main/java/de/kuschku/libquassel/session/Session.kt
index 3546516f7..14edd30e5 100644
--- a/lib/src/main/java/de/kuschku/libquassel/session/Session.kt
+++ b/lib/src/main/java/de/kuschku/libquassel/session/Session.kt
@@ -5,7 +5,6 @@ import de.kuschku.libquassel.protocol.message.HandshakeMessage
 import de.kuschku.libquassel.protocol.message.SignalProxyMessage
 import de.kuschku.libquassel.quassel.QuasselFeature
 import de.kuschku.libquassel.quassel.syncables.*
-import de.kuschku.libquassel.quassel.syncables.interfaces.invokers.Invokers
 import de.kuschku.libquassel.util.compatibility.HandlerService
 import de.kuschku.libquassel.util.compatibility.LoggingHandler.LogLevel.DEBUG
 import de.kuschku.libquassel.util.compatibility.LoggingHandler.LogLevel.INFO
@@ -19,6 +18,7 @@ class Session(
   val trustManager: X509TrustManager,
   address: SocketAddress,
   handlerService: HandlerService,
+  backlogStorage: BacklogStorage,
   private val userData: Pair<String, String>
 ) : ProtocolHandler(), ISession {
   var coreFeatures: Quassel_Features = Quassel_Feature.NONE
@@ -27,7 +27,7 @@ class Session(
   override val state = coreConnection.state
 
   override val aliasManager = AliasManager(this)
-  override val backlogManager = BacklogManager(this)
+  override val backlogManager = BacklogManager(this, backlogStorage)
   override val bufferSyncer = BufferSyncer(this)
   override val bufferViewManager = BufferViewManager(this)
   override val certManagers = mutableMapOf<IdentityId, CertManager>()
@@ -39,7 +39,7 @@ class Session(
   override val networks = mutableMapOf<NetworkId, Network>()
   override val networkConfig = NetworkConfig(this)
 
-  override var rpcHandler: RpcHandler? = RpcHandler(this)
+  override var rpcHandler: RpcHandler? = RpcHandler(this, backlogStorage)
 
   init {
     coreConnection.start()
diff --git a/lib/src/main/java/de/kuschku/libquassel/session/SessionManager.kt b/lib/src/main/java/de/kuschku/libquassel/session/SessionManager.kt
index cb4006c0a..0385cf9c6 100644
--- a/lib/src/main/java/de/kuschku/libquassel/session/SessionManager.kt
+++ b/lib/src/main/java/de/kuschku/libquassel/session/SessionManager.kt
@@ -13,7 +13,7 @@ import io.reactivex.Observable
 import io.reactivex.subjects.BehaviorSubject
 import javax.net.ssl.X509TrustManager
 
-class SessionManager(offlineSession: ISession) : ISession {
+class SessionManager(offlineSession: ISession, private val backlogStorage: BacklogStorage) : ISession {
   override val aliasManager: AliasManager?
     get() = session.or(lastSession).aliasManager
   override val backlogManager: BacklogManager?
@@ -69,7 +69,7 @@ class SessionManager(offlineSession: ISession) : ISession {
     lastHandlerService = handlerService
     lastUserData = userData
     lastShouldReconnect = shouldReconnect
-    inProgressSession.onNext(Session(clientData, trustManager, address, handlerService(), userData))
+    inProgressSession.onNext(Session(clientData, trustManager, address, handlerService(), backlogStorage, userData))
   }
 
   private var lastClientData: ClientData? = null
-- 
GitLab