Skip to content
Snippets Groups Projects
Commit b31f50c6 authored by Janne Mareike Koschinski's avatar Janne Mareike Koschinski
Browse files

Implement persistent message storage

parent 1e6e0bd3
Branches
Tags
No related merge requests found
Showing
with 121 additions and 64 deletions
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
......@@ -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()
......@@ -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
......@@ -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),
......
......@@ -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}"
}
}
}
}
......@@ -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()
......
......@@ -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
......@@ -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')"
}
}
......@@ -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
override fun toString(): String {
return "QVariant(${type.name}, $data)"
}
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)"
@PublishedApi
internal inline fun <reified U> QVariant_?.coerce(): QVariant<U>? {
return if (this?.data is U) {
this as QVariant<U>
} else {
null
}
}
fun <U> QVariant_?.value(): U?
= this?._value<U?>(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()
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)
}
}
}
......@@ -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) {
......
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
......@@ -114,7 +114,6 @@ class CoreConnection(
override fun close() {
try {
channel?.close()
interrupt()
handlerService.quit()
setState(ConnectionState.DISCONNECTED)
......
......@@ -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()
......
......@@ -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
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment