forked from Ivasoft/mattermost-mobile
* Disallow the same server from being added the twice * Add Default Server name config & EMM managed config * Update android/app/src/main/res/values/strings.xml Co-authored-by: Jason Frerich <jason.frerich@mattermost.com> Co-authored-by: Jason Frerich <jason.frerich@mattermost.com>
544 lines
22 KiB
Kotlin
544 lines
22 KiB
Kotlin
package com.mattermost.helpers
|
|
|
|
import android.content.Context
|
|
import android.net.Uri
|
|
import android.text.TextUtils
|
|
import com.facebook.react.bridge.Arguments
|
|
import com.facebook.react.bridge.NoSuchKeyException
|
|
import com.facebook.react.bridge.ReadableArray
|
|
import com.facebook.react.bridge.ReadableMap
|
|
import com.nozbe.watermelondb.Database
|
|
import com.nozbe.watermelondb.mapCursor
|
|
import org.json.JSONArray
|
|
import org.json.JSONException
|
|
import org.json.JSONObject
|
|
import java.lang.Exception
|
|
import java.util.*
|
|
|
|
class DatabaseHelper {
|
|
var defaultDatabase: Database? = null
|
|
private set
|
|
|
|
val onlyServerUrl: String?
|
|
get() {
|
|
val query = "SELECT url FROM Servers WHERE last_active_at != 0 AND identifier != ''"
|
|
val cursor = defaultDatabase!!.rawQuery(query)
|
|
if (cursor.count == 1) {
|
|
cursor.moveToFirst()
|
|
val url = cursor.getString(0)
|
|
cursor.close()
|
|
return url
|
|
}
|
|
return null
|
|
}
|
|
|
|
fun init(context: Context) {
|
|
if (defaultDatabase == null) {
|
|
setDefaultDatabase(context)
|
|
}
|
|
}
|
|
|
|
fun getServerUrlForIdentifier(identifier: String): String? {
|
|
val args: Array<Any?> = arrayOf(identifier)
|
|
val query = "SELECT url FROM Servers WHERE identifier=?"
|
|
val cursor = defaultDatabase!!.rawQuery(query, args)
|
|
if (cursor.count == 1) {
|
|
cursor.moveToFirst()
|
|
val url = cursor.getString(0)
|
|
cursor.close()
|
|
return url
|
|
}
|
|
return null
|
|
}
|
|
|
|
fun find(db: Database, tableName: String, id: String?): ReadableMap? {
|
|
val args: Array<Any?> = arrayOf(id)
|
|
try {
|
|
db.rawQuery("select * from $tableName where id == ? limit 1", args).use { cursor ->
|
|
if (cursor.count <= 0) {
|
|
return null
|
|
}
|
|
val resultMap = Arguments.createMap()
|
|
cursor.moveToFirst()
|
|
resultMap.mapCursor(cursor)
|
|
return resultMap
|
|
}
|
|
} catch (e: Exception) {
|
|
return null
|
|
}
|
|
}
|
|
|
|
fun getDatabaseForServer(context: Context?, serverUrl: String): Database? {
|
|
val args: Array<Any?> = arrayOf(serverUrl)
|
|
val query = "SELECT db_path FROM Servers WHERE url=?"
|
|
val cursor = defaultDatabase!!.rawQuery(query, args)
|
|
if (cursor.count == 1) {
|
|
cursor.moveToFirst()
|
|
val databasePath = cursor.getString(0)
|
|
cursor.close()
|
|
return Database(databasePath, context!!)
|
|
}
|
|
return null
|
|
}
|
|
|
|
fun queryIds(db: Database, tableName: String, ids: Array<String>): List<String> {
|
|
val list: MutableList<String> = ArrayList()
|
|
val args = TextUtils.join(",", Arrays.stream(ids).map { value: String? -> "?" }.toArray())
|
|
try {
|
|
db.rawQuery("select distinct id from $tableName where id IN ($args)", ids as Array<Any?>).use { cursor ->
|
|
if (cursor.count > 0) {
|
|
while (cursor.moveToNext()) {
|
|
list.add(cursor.getString(cursor.getColumnIndex("id")))
|
|
}
|
|
}
|
|
}
|
|
} catch (e: Exception) {
|
|
e.printStackTrace()
|
|
}
|
|
return list
|
|
}
|
|
|
|
fun queryByColumn(db: Database, tableName: String, columnName: String, values: Array<Any?>): List<String> {
|
|
val list: MutableList<String> = ArrayList()
|
|
val args = TextUtils.join(",", Arrays.stream(values).map { value: Any? -> "?" }.toArray())
|
|
try {
|
|
db.rawQuery("select distinct $columnName from $tableName where $columnName IN ($args)", values).use { cursor ->
|
|
if (cursor.count > 0) {
|
|
while (cursor.moveToNext()) {
|
|
list.add(cursor.getString(cursor.getColumnIndex(columnName)))
|
|
}
|
|
}
|
|
}
|
|
} catch (e: Exception) {
|
|
e.printStackTrace()
|
|
}
|
|
return list
|
|
}
|
|
|
|
fun queryCurrentUserId(db: Database): String? {
|
|
val result = find(db, "System", "currentUserId")!!
|
|
return result.getString("value")
|
|
}
|
|
|
|
fun queryPostSinceForChannel(db: Database?, channelId: String): Double? {
|
|
if (db != null) {
|
|
val postsInChannelQuery = "SELECT earliest, latest FROM PostsInChannel WHERE channel_id=? ORDER BY latest DESC LIMIT 1"
|
|
val cursor1 = db.rawQuery(postsInChannelQuery, arrayOf(channelId))
|
|
if (cursor1.count == 1) {
|
|
cursor1.moveToFirst()
|
|
val earliest = cursor1.getDouble(0)
|
|
val latest = cursor1.getDouble(1)
|
|
cursor1.close()
|
|
val postQuery = "SELECT create_at FROM POST WHERE channel_id= ? AND delete_at=0 AND create_at BETWEEN ? AND ? ORDER BY create_at DESC"
|
|
val cursor2 = db.rawQuery(postQuery, arrayOf(channelId, earliest, latest))
|
|
if (cursor2.count >= 60) {
|
|
cursor2.moveToFirst()
|
|
val createAt = cursor2.getDouble(0)
|
|
cursor2.close()
|
|
return createAt
|
|
}
|
|
}
|
|
}
|
|
return null
|
|
}
|
|
|
|
fun handlePosts(db: Database, postsData: ReadableMap?, channelId: String) {
|
|
// Posts, PostInChannel, PostInThread, Reactions, Files, CustomEmojis, Users
|
|
if (postsData != null) {
|
|
val ordered = postsData.getArray("order")?.toArrayList()
|
|
val posts = ReadableMapUtils.toJSONObject(postsData.getMap("posts")).toMap()
|
|
val previousPostId = postsData.getString("prev_post_id")
|
|
val postsInThread = hashMapOf<String, List<JSONObject>>()
|
|
var earliest = 0.0
|
|
var latest = 0.0
|
|
|
|
if (ordered != null && posts.isNotEmpty()) {
|
|
val firstId = ordered.first()
|
|
val lastId = ordered.last()
|
|
var prevPostId = ""
|
|
|
|
val sortedPosts = posts.toList().sortedBy { (_, value) ->
|
|
((value as Map<*, *>).get("create_at") as Double)
|
|
}
|
|
|
|
sortedPosts.forEachIndexed { index, it ->
|
|
val key = it.first
|
|
if (it.second != null) {
|
|
val post = (it.second as MutableMap<String, Any?>)
|
|
|
|
if (index == 0) {
|
|
post.putIfAbsent("prev_post_id", previousPostId)
|
|
} else if (!prevPostId.isNullOrEmpty()) {
|
|
post.putIfAbsent("prev_post_id", prevPostId)
|
|
}
|
|
|
|
if (lastId == key) {
|
|
earliest = post.get("create_at") as Double
|
|
}
|
|
if (firstId == key) {
|
|
latest = post.get("create_at") as Double
|
|
}
|
|
|
|
val jsonPost = JSONObject(post)
|
|
val rootId = post.get("root_id") as? String
|
|
|
|
if (!rootId.isNullOrEmpty()) {
|
|
var thread = postsInThread.get(rootId)?.toMutableList()
|
|
if (thread == null) {
|
|
thread = mutableListOf()
|
|
}
|
|
|
|
thread.add(jsonPost)
|
|
postsInThread.put(rootId, thread.toList())
|
|
}
|
|
|
|
if (find(db, "Post", key) == null) {
|
|
insertPost(db, jsonPost)
|
|
} else {
|
|
updatePost(db, jsonPost)
|
|
}
|
|
|
|
if (ordered.contains(key)) {
|
|
prevPostId = key
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
handlePostsInChannel(db, channelId, earliest, latest)
|
|
handlePostsInThread(db, postsInThread)
|
|
}
|
|
}
|
|
|
|
fun handleUsers(db: Database, users: ReadableArray) {
|
|
for (i in 0 until users.size()) {
|
|
val user = users.getMap(i)
|
|
val roles = user.getString("roles") ?: ""
|
|
val isBot = try {
|
|
user.getBoolean("is_bot")
|
|
} catch (e: NoSuchKeyException) {
|
|
false
|
|
}
|
|
|
|
|
|
db.execute(
|
|
"insert into User (id, auth_service, update_at, delete_at, email, first_name, is_bot, is_guest, " +
|
|
"last_name, last_picture_update, locale, nickname, position, roles, status, username, notify_props, " +
|
|
"props, timezone, _status) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'created')",
|
|
arrayOf(
|
|
user.getString("id"),
|
|
user.getString("auth_service"),
|
|
user.getDouble("update_at"),
|
|
user.getDouble("delete_at"),
|
|
user.getString("email"),
|
|
user.getString("first_name"),
|
|
isBot,
|
|
roles.contains("system_guest"),
|
|
user.getString("last_name"),
|
|
user.getDouble("last_picture_update"),
|
|
user.getString("locale"),
|
|
user.getString("nickname"),
|
|
user.getString("position"),
|
|
roles,
|
|
"",
|
|
user.getString("username"),
|
|
"{}",
|
|
ReadableMapUtils.toJSONObject(user.getMap("props") ?: Arguments.createMap()).toString(),
|
|
ReadableMapUtils.toJSONObject(user.getMap("timezone") ?: Arguments.createMap()).toString(),
|
|
)
|
|
)
|
|
}
|
|
}
|
|
|
|
private fun setDefaultDatabase(context: Context) {
|
|
val databaseName = "app.db"
|
|
val databasePath = Uri.fromFile(context.filesDir).toString() + "/" + databaseName
|
|
defaultDatabase = Database(databasePath, context)
|
|
}
|
|
|
|
private fun insertPost(db: Database, post: JSONObject) {
|
|
var metadata: JSONObject? = null
|
|
var reactions: JSONArray? = null
|
|
var customEmojis: JSONArray? = null
|
|
var files: JSONArray? = null
|
|
|
|
try {
|
|
metadata = post.getJSONObject("metadata")
|
|
reactions = metadata.remove("reactions") as JSONArray?
|
|
customEmojis = metadata.remove("emojis") as JSONArray?
|
|
files = metadata.remove("files") as JSONArray?
|
|
} catch (e: Exception) {
|
|
// no metadata found
|
|
metadata = JSONObject()
|
|
}
|
|
|
|
db.execute(
|
|
"insert into Post " +
|
|
"(id, channel_id, create_at, delete_at, update_at, edit_at, is_pinned, message, metadata, original_id, pending_post_id, " +
|
|
"previous_post_id, root_id, type, user_id, props, _status)" +
|
|
" values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'created')",
|
|
arrayOf(
|
|
post.getString("id"),
|
|
post.getString("channel_id"),
|
|
post.getDouble("create_at"),
|
|
post.getDouble("delete_at"),
|
|
post.getDouble("update_at"),
|
|
post.getDouble("edit_at"),
|
|
post.getBoolean("is_pinned"),
|
|
post.getString("message"),
|
|
metadata.toString(),
|
|
post.getString("original_id"),
|
|
post.getString("pending_post_id"),
|
|
post.getString("prev_post_id"),
|
|
post.getString("root_id"),
|
|
post.getString("type"),
|
|
post.getString("user_id"),
|
|
post.getJSONObject("props").toString()
|
|
)
|
|
)
|
|
|
|
if (reactions != null && reactions.length() > 0) {
|
|
insertReactions(db, reactions)
|
|
}
|
|
|
|
if (customEmojis != null && customEmojis.length() > 0) {
|
|
insertCustomEmojis(db, customEmojis)
|
|
}
|
|
|
|
if (files != null && files.length() > 0) {
|
|
insertFiles(db, files)
|
|
}
|
|
}
|
|
|
|
private fun updatePost(db: Database, post: JSONObject) {
|
|
var metadata: JSONObject? = null
|
|
var reactions: JSONArray? = null
|
|
var customEmojis: JSONArray? = null
|
|
|
|
try {
|
|
metadata = post.getJSONObject("metadata")
|
|
reactions = metadata.remove("reactions") as JSONArray?
|
|
customEmojis = metadata.remove("emojis") as JSONArray?
|
|
metadata.remove("files")
|
|
} catch (e: Exception) {
|
|
// no metadata found
|
|
metadata = JSONObject()
|
|
}
|
|
|
|
db.execute(
|
|
"update Post SET channel_id = ?, create_at = ?, delete_at = ?, update_at =?, edit_at =?, " +
|
|
"is_pinned = ?, message = ?, metadata = ?, original_id = ?, pending_post_id = ?, previous_post_id = ?, " +
|
|
"root_id = ?, type = ?, user_id = ?, props = ?, _status = 'updated' " +
|
|
"where id = ?",
|
|
arrayOf(
|
|
post.getString("channel_id"),
|
|
post.getDouble("create_at"),
|
|
post.getDouble("delete_at"),
|
|
post.getDouble("update_at"),
|
|
post.getDouble("edit_at"),
|
|
post.getBoolean("is_pinned"),
|
|
post.getString("message"),
|
|
metadata.toString(),
|
|
post.getString("original_id"),
|
|
post.getString("pending_post_id"),
|
|
post.getString("prev_post_id"),
|
|
post.getString("root_id"),
|
|
post.getString("type"),
|
|
post.getString("user_id"),
|
|
post.getJSONObject("props").toString(),
|
|
post.getString("id"),
|
|
)
|
|
)
|
|
|
|
if (reactions != null && reactions.length() > 0) {
|
|
db.execute("delete from Reaction where post_id = ?", arrayOf(post.getString("id")))
|
|
insertReactions(db, reactions)
|
|
}
|
|
|
|
if (customEmojis != null && customEmojis.length() > 0) {
|
|
insertCustomEmojis(db, customEmojis)
|
|
}
|
|
}
|
|
|
|
private fun insertCustomEmojis(db: Database, customEmojis: JSONArray) {
|
|
for (i in 0 until customEmojis.length()) {
|
|
val emoji = customEmojis.getJSONObject(i)
|
|
if(find(db, "CustomEmoji", emoji.getString("id")) == null) {
|
|
db.execute(
|
|
"insert into CustomEmoji (id, name, _status) values (?, ?, 'created')",
|
|
arrayOf(
|
|
emoji.getString("id"),
|
|
emoji.getString("name"),
|
|
)
|
|
)
|
|
}
|
|
}
|
|
}
|
|
|
|
private fun insertFiles(db: Database, files: JSONArray) {
|
|
for (i in 0 until files.length()) {
|
|
val file = files.getJSONObject(i)
|
|
val miniPreview = try { file.getString("mini_preview") } catch (e: JSONException) { "" };
|
|
val height = try { file.getInt("height") } catch (e: JSONException) { 0 };
|
|
val width = try { file.getInt("width") } catch (e: JSONException) { 0 };
|
|
db.execute(
|
|
"insert into File (id, extension, height, image_thumbnail, local_path, mime_type, name, post_id, size, width, _status) " +
|
|
"values (?, ?, ?, ?, '', ?, ?, ?, ?, ?, 'created')",
|
|
arrayOf(
|
|
file.getString("id"),
|
|
file.getString("extension"),
|
|
height,
|
|
miniPreview,
|
|
file.getString("mime_type"),
|
|
file.getString("name"),
|
|
file.getString("post_id"),
|
|
file.getDouble("size"),
|
|
width
|
|
)
|
|
)
|
|
}
|
|
}
|
|
|
|
private fun insertReactions(db: Database, reactions: JSONArray) {
|
|
for (i in 0 until reactions.length()) {
|
|
val reaction = reactions.getJSONObject(i)
|
|
val id = RandomId.generate()
|
|
db.execute(
|
|
"insert into Reaction (id, create_at, emoji_name, post_id, user_id, _status) " +
|
|
"values (?, ?, ?, ?, ?, 'created')",
|
|
arrayOf(
|
|
id,
|
|
reaction.getDouble("create_at"),
|
|
reaction.getString("emoji_name"),
|
|
reaction.getString("post_id"),
|
|
reaction.getString("user_id")
|
|
)
|
|
)
|
|
}
|
|
}
|
|
|
|
private fun handlePostsInChannel(db: Database, channelId: String, earliest: Double, latest: Double) {
|
|
db.rawQuery("select id, channel_id, earliest, latest from PostsInChannel where channel_id = ?", arrayOf(channelId)).use { cursor ->
|
|
if (cursor.count == 0) {
|
|
// create new post in channel
|
|
insertPostInChannel(db, channelId, earliest, latest)
|
|
return
|
|
}
|
|
|
|
val resultArray = Arguments.createArray()
|
|
while (cursor.moveToNext()) {
|
|
val cursorMap = Arguments.createMap()
|
|
cursorMap.mapCursor(cursor)
|
|
resultArray.pushMap(cursorMap)
|
|
}
|
|
|
|
val chunk = findPostInChannel(resultArray, earliest, latest)
|
|
if (chunk != null) {
|
|
db.execute(
|
|
"update PostsInChannel set earliest = ?, latest = ?, _status = 'updated' where id = ?",
|
|
arrayOf(
|
|
minOf(earliest, chunk.getDouble("earliest")),
|
|
maxOf(latest, chunk.getDouble("latest")),
|
|
chunk.getString("id")
|
|
)
|
|
)
|
|
return
|
|
}
|
|
|
|
val newChunk = insertPostInChannel(db, channelId, earliest, latest)
|
|
mergePostsInChannel(db, resultArray, newChunk)
|
|
}
|
|
}
|
|
|
|
private fun findPostInChannel(chunks: ReadableArray, earliest: Double, latest: Double): ReadableMap? {
|
|
for (i in 0 until chunks.size()) {
|
|
val chunk = chunks.getMap(i)
|
|
if (earliest >= chunk.getDouble("earliest") || latest <= chunk.getDouble("latest")) {
|
|
return chunk;
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
private fun insertPostInChannel(db: Database, channelId: String, earliest: Double, latest: Double): ReadableMap {
|
|
val id = RandomId.generate()
|
|
db.execute("insert into PostsInChannel (id, channel_id, earliest, latest, _status) values (?, ?, ?, ?, 'created')",
|
|
arrayOf(id, channelId, earliest, latest))
|
|
|
|
val map = Arguments.createMap()
|
|
map.putString("id", id)
|
|
map.putString("channel_id", channelId)
|
|
map.putDouble("earliest", earliest)
|
|
map.putDouble("latest", latest)
|
|
return map
|
|
}
|
|
|
|
private fun mergePostsInChannel(db: Database, existingChunks: ReadableArray, newChunk: ReadableMap) {
|
|
for (i in 0 until existingChunks.size()) {
|
|
val chunk = existingChunks.getMap(i)
|
|
if (newChunk.getDouble("earliest") <= chunk.getDouble("earliest") &&
|
|
newChunk.getDouble("latest") >= chunk.getDouble("latest")) {
|
|
db.execute("delete from PostsInChannel where id = ?", arrayOf(chunk.getString("id")))
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
private fun handlePostsInThread(db: Database, postsInThread: Map<String, List<JSONObject>>) {
|
|
postsInThread.forEach { (key, list) ->
|
|
val sorted = list.sortedBy { it.getDouble("create_at") }
|
|
val earliest = sorted.first().getDouble("create_at")
|
|
val latest = sorted.last().getDouble("create_at")
|
|
db.rawQuery("select * from PostsInThread where root_id = ? order by latest desc", arrayOf(key)).use { cursor ->
|
|
if (cursor.count > 0) {
|
|
cursor.moveToFirst()
|
|
val cursorMap = Arguments.createMap()
|
|
cursorMap.mapCursor(cursor)
|
|
db.execute(
|
|
"update PostsInThread set earliest = ?, latest = ?, _status = 'updated' where id = ?",
|
|
arrayOf(
|
|
minOf(earliest, cursorMap.getDouble("earliest")),
|
|
maxOf(latest, cursorMap.getDouble("latest")),
|
|
key
|
|
)
|
|
)
|
|
return
|
|
}
|
|
|
|
val id = RandomId.generate()
|
|
db.execute(
|
|
"insert into PostsInThread (id, root_id, earliest, latest, _status) " +
|
|
"values (?, ?, ?, ?, 'created')",
|
|
arrayOf(id, key, earliest, latest)
|
|
)
|
|
}
|
|
}
|
|
}
|
|
|
|
private fun JSONObject.toMap(): Map<String, *> = keys().asSequence().associateWith {
|
|
when (val value = this[it])
|
|
{
|
|
is JSONArray ->
|
|
{
|
|
val map = (0 until value.length()).associate { Pair(it.toString(), value[it]) }
|
|
JSONObject(map).toMap().values.toList()
|
|
}
|
|
is JSONObject -> value.toMap()
|
|
JSONObject.NULL -> null
|
|
else -> value
|
|
}
|
|
}
|
|
|
|
companion object {
|
|
var instance: DatabaseHelper? = null
|
|
get() {
|
|
if (field == null) {
|
|
field = DatabaseHelper()
|
|
}
|
|
return field
|
|
}
|
|
private set
|
|
}
|
|
}
|