[Gekidou MM-40097 MM-44133] Save thread related data when client receives a push notification (#6275)
* Android changes * Fixed updating records from native side * Android: Handle crt related issues * Android threads fix * Android misc fixes * Android addressing feedback * ios changes WIP * Update Podfile.lock * iOS changes * iOS feedback * iOS updates the existing record like android * Android misc Co-authored-by: Mattermod <mattermod@users.noreply.github.com>
This commit is contained in:
parent
db171cc7dd
commit
1b5e41b424
7 changed files with 407 additions and 25 deletions
|
|
@ -142,7 +142,7 @@ class DatabaseHelper {
|
|||
return null
|
||||
}
|
||||
|
||||
fun handlePosts(db: Database, postsData: ReadableMap?, channelId: String) {
|
||||
fun handlePosts(db: Database, postsData: ReadableMap?, channelId: String, receivingThreads: Boolean) {
|
||||
// Posts, PostInChannel, PostInThread, Reactions, Files, CustomEmojis, Users
|
||||
if (postsData != null) {
|
||||
val ordered = postsData.getArray("order")?.toArrayList()
|
||||
|
|
@ -205,11 +205,38 @@ class DatabaseHelper {
|
|||
}
|
||||
}
|
||||
|
||||
handlePostsInChannel(db, channelId, earliest, latest)
|
||||
if (!receivingThreads) {
|
||||
handlePostsInChannel(db, channelId, earliest, latest)
|
||||
}
|
||||
handlePostsInThread(db, postsInThread)
|
||||
}
|
||||
}
|
||||
|
||||
fun handleThreads(db: Database, threads: ReadableArray) {
|
||||
for (i in 0 until threads.size()) {
|
||||
val thread = threads.getMap(i)
|
||||
val threadId = thread.getString("id")
|
||||
|
||||
// Insert/Update the thread
|
||||
val existingRecord = find(db, "Thread", threadId)
|
||||
if (existingRecord == null) {
|
||||
insertThread(db, thread)
|
||||
} else {
|
||||
updateThread(db, thread, existingRecord)
|
||||
}
|
||||
|
||||
// Delete existing and insert thread participants
|
||||
val participants = thread.getArray("participants")
|
||||
if (participants != null) {
|
||||
db.execute("delete from ThreadParticipant where thread_id = ?", arrayOf(threadId))
|
||||
|
||||
if (participants.size() > 0) {
|
||||
insertThreadParticipants(db, threadId!!, participants)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun handleUsers(db: Database, users: ReadableArray) {
|
||||
for (i in 0 until users.size()) {
|
||||
val user = users.getMap(i)
|
||||
|
|
@ -220,6 +247,8 @@ class DatabaseHelper {
|
|||
false
|
||||
}
|
||||
|
||||
val lastPictureUpdate = try { user.getDouble("last_picture_update") } catch (e: NoSuchKeyException) { 0 }
|
||||
|
||||
|
||||
db.execute(
|
||||
"insert into User (id, auth_service, update_at, delete_at, email, first_name, is_bot, is_guest, " +
|
||||
|
|
@ -235,7 +264,7 @@ class DatabaseHelper {
|
|||
isBot,
|
||||
roles.contains("system_guest"),
|
||||
user.getString("last_name"),
|
||||
user.getDouble("last_picture_update"),
|
||||
lastPictureUpdate,
|
||||
user.getString("locale"),
|
||||
user.getString("nickname"),
|
||||
user.getString("position"),
|
||||
|
|
@ -250,6 +279,27 @@ class DatabaseHelper {
|
|||
}
|
||||
}
|
||||
|
||||
fun getServerVersion(db: Database): String? {
|
||||
val config = getSystemConfig(db)
|
||||
if (config != null) {
|
||||
return config.getString("Version")
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
private fun getSystemConfig(db: Database): JSONObject? {
|
||||
val configRecord = find(db, "System", "config")
|
||||
if (configRecord != null) {
|
||||
val value = configRecord.getString("value");
|
||||
try {
|
||||
return JSONObject(value)
|
||||
} catch(e: JSONException) {
|
||||
return null
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
private fun setDefaultDatabase(context: Context) {
|
||||
val databaseName = "app.db"
|
||||
val databasePath = Uri.fromFile(context.filesDir).toString() + "/" + databaseName
|
||||
|
|
@ -360,6 +410,67 @@ class DatabaseHelper {
|
|||
}
|
||||
}
|
||||
|
||||
private fun insertThread(db: Database, thread: ReadableMap) {
|
||||
// These fields are not present when we extract threads from posts
|
||||
val isFollowing = try { thread.getBoolean("is_following") } catch (e: NoSuchKeyException) { false };
|
||||
val lastViewedAt = try { thread.getDouble("last_viewed_at") } catch (e: NoSuchKeyException) { 0 };
|
||||
val unreadReplies = try { thread.getInt("unread_replies") } catch (e: NoSuchKeyException) { 0 };
|
||||
val unreadMentions = try { thread.getInt("unread_mentions") } catch (e: NoSuchKeyException) { 0 };
|
||||
|
||||
db.execute(
|
||||
"insert into Thread " +
|
||||
"(id, last_reply_at, last_viewed_at, reply_count, is_following, unread_replies, unread_mentions, _status)" +
|
||||
" values (?, ?, ?, ?, ?, ?, ?, 'created')",
|
||||
arrayOf(
|
||||
thread.getString("id"),
|
||||
thread.getDouble("last_reply_at") ?: 0,
|
||||
lastViewedAt,
|
||||
thread.getInt("reply_count") ?: 0,
|
||||
isFollowing,
|
||||
unreadReplies,
|
||||
unreadMentions
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
private fun updateThread(db: Database, thread: ReadableMap, existingRecord: ReadableMap) {
|
||||
// These fields are not present when we extract threads from posts
|
||||
val isFollowing = try { thread.getBoolean("is_following") } catch (e: NoSuchKeyException) { existingRecord.getInt("is_following") == 1 };
|
||||
val lastViewedAt = try { thread.getDouble("last_viewed_at") } catch (e: NoSuchKeyException) { existingRecord.getDouble("last_viewed_at") };
|
||||
val unreadReplies = try { thread.getInt("unread_replies") } catch (e: NoSuchKeyException) { existingRecord.getInt("unread_replies") };
|
||||
val unreadMentions = try { thread.getInt("unread_mentions") } catch (e: NoSuchKeyException) { existingRecord.getInt("unread_mentions") };
|
||||
|
||||
db.execute(
|
||||
"update Thread SET last_reply_at = ?, last_viewed_at = ?, reply_count = ?, is_following = ?, unread_replies = ?, unread_mentions = ?, _status = 'updated' where id = ?",
|
||||
arrayOf(
|
||||
thread.getDouble("last_reply_at") ?: 0,
|
||||
lastViewedAt,
|
||||
thread.getInt("reply_count") ?: 0,
|
||||
isFollowing,
|
||||
unreadReplies,
|
||||
unreadMentions,
|
||||
thread.getString("id")
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
private fun insertThreadParticipants(db: Database, threadId: String, participants: ReadableArray) {
|
||||
for (i in 0 until participants.size()) {
|
||||
val participant = participants.getMap(i)
|
||||
val id = RandomId.generate()
|
||||
db.execute(
|
||||
"insert into ThreadParticipant " +
|
||||
"(id, thread_id, user_id, _status)" +
|
||||
" values (?, ?, ?, 'created')",
|
||||
arrayOf(
|
||||
id,
|
||||
threadId,
|
||||
participant.getString("id")
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun insertCustomEmojis(db: Database, customEmojis: JSONArray) {
|
||||
for (i in 0 until customEmojis.length()) {
|
||||
val emoji = customEmojis.getJSONObject(i)
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import android.os.Bundle
|
|||
import com.facebook.react.bridge.Arguments
|
||||
import com.facebook.react.bridge.ReadableArray
|
||||
import com.facebook.react.bridge.ReadableMap
|
||||
import com.facebook.react.bridge.WritableNativeArray
|
||||
import com.nozbe.watermelondb.Database
|
||||
import java.io.IOException
|
||||
import java.util.concurrent.Executors
|
||||
|
|
@ -30,6 +31,8 @@ class PushNotificationDataRunnable {
|
|||
try {
|
||||
val serverUrl: String = initialData.getString("server_url") ?: return
|
||||
val channelId = initialData.getString("channel_id")
|
||||
val rootId = initialData.getString("root_id")
|
||||
val isCRTEnabled = initialData.getString("is_crt_enabled") == "true"
|
||||
val db = DatabaseHelper.instance!!.getDatabaseForServer(context, serverUrl)
|
||||
|
||||
if (db != null) {
|
||||
|
|
@ -38,12 +41,19 @@ class PushNotificationDataRunnable {
|
|||
var userIdsToLoad: ReadableArray? = null
|
||||
var usernamesToLoad: ReadableArray? = null
|
||||
|
||||
var threads: ReadableArray? = null
|
||||
var usersFromThreads: ReadableArray? = null
|
||||
val receivingThreads = isCRTEnabled && !rootId.isNullOrEmpty()
|
||||
|
||||
coroutineScope {
|
||||
if (channelId != null) {
|
||||
postData = fetchPosts(db, serverUrl, channelId)
|
||||
postData = fetchPosts(db, serverUrl, channelId, isCRTEnabled, rootId)
|
||||
|
||||
posts = postData?.getMap("posts")
|
||||
userIdsToLoad = postData?.getArray("userIdsToLoad")
|
||||
usernamesToLoad = postData?.getArray("usernamesToLoad")
|
||||
threads = postData?.getArray("threads")
|
||||
usersFromThreads = postData?.getArray("usersFromThreads")
|
||||
|
||||
if (userIdsToLoad != null && userIdsToLoad!!.size() > 0) {
|
||||
val users = fetchUsersById(serverUrl, userIdsToLoad!!)
|
||||
|
|
@ -59,7 +69,11 @@ class PushNotificationDataRunnable {
|
|||
|
||||
db.transaction {
|
||||
if (posts != null && channelId != null) {
|
||||
DatabaseHelper.instance!!.handlePosts(db, posts!!.getMap("data"), channelId)
|
||||
DatabaseHelper.instance!!.handlePosts(db, posts!!.getMap("data"), channelId, receivingThreads)
|
||||
}
|
||||
|
||||
if (threads != null) {
|
||||
DatabaseHelper.instance!!.handleThreads(db, threads!!)
|
||||
}
|
||||
|
||||
if (userIdsToLoad != null && userIdsToLoad!!.size() > 0) {
|
||||
|
|
@ -69,6 +83,10 @@ class PushNotificationDataRunnable {
|
|||
if (usernamesToLoad != null && usernamesToLoad!!.size() > 0) {
|
||||
DatabaseHelper.instance!!.handleUsers(db, usernamesToLoad!!)
|
||||
}
|
||||
|
||||
if (usersFromThreads != null) {
|
||||
DatabaseHelper.instance!!.handleUsers(db, usersFromThreads!!)
|
||||
}
|
||||
}
|
||||
|
||||
db.close()
|
||||
|
|
@ -78,14 +96,28 @@ class PushNotificationDataRunnable {
|
|||
}
|
||||
}
|
||||
|
||||
private suspend fun fetchPosts(db: Database, serverUrl: String, channelId: String): ReadableMap? {
|
||||
private suspend fun fetchPosts(db: Database, serverUrl: String, channelId: String, isCRTEnabled: Boolean, rootId: String?): ReadableMap? {
|
||||
val regex = Regex("""\B@(([a-z0-9-._]*[a-z0-9_])[.-]*)""", setOf(RegexOption.IGNORE_CASE))
|
||||
val since = DatabaseHelper.instance!!.queryPostSinceForChannel(db, channelId)
|
||||
val currentUserId = DatabaseHelper.instance!!.queryCurrentUserId(db)?.removeSurrounding("\"")
|
||||
val currentUser = DatabaseHelper.instance!!.find(db, "User", currentUserId)
|
||||
val currentUsername = currentUser?.getString("username")
|
||||
val queryParams = if (since == null) "?page=0&per_page=60" else "?since=${since.toLong()}"
|
||||
val endpoint = "/api/v4/channels/$channelId/posts$queryParams"
|
||||
|
||||
var additionalParams = ""
|
||||
if (isCRTEnabled) {
|
||||
additionalParams = "&collapsedThreads=true&collapsedThreadsExtended=true"
|
||||
}
|
||||
|
||||
var endpoint: String
|
||||
val receivingThreads = isCRTEnabled && !rootId.isNullOrEmpty()
|
||||
if (receivingThreads) {
|
||||
var queryParams = "?skipFetchThreads=false&perPage=60&fromCreatedAt=0&direction=up"
|
||||
endpoint = "/api/v4/posts/$rootId/thread$queryParams$additionalParams"
|
||||
} else {
|
||||
var queryParams = if (since == null) "?page=0&per_page=60" else "?since=${since.toLong()}"
|
||||
endpoint = "/api/v4/channels/$channelId/posts$queryParams$additionalParams"
|
||||
}
|
||||
|
||||
val postsResponse = fetch(serverUrl, endpoint)
|
||||
val results = Arguments.createMap()
|
||||
|
||||
|
|
@ -100,6 +132,12 @@ class PushNotificationDataRunnable {
|
|||
val iterator = posts.keySetIterator()
|
||||
val userIds = mutableListOf<String>()
|
||||
val usernames = mutableListOf<String>()
|
||||
|
||||
val threads = WritableNativeArray()
|
||||
val threadParticipantUserIds = mutableListOf<String>() // Used to exclude the "userIds" present in the thread participants
|
||||
val threadParticipantUsernames = mutableListOf<String>() // Used to exclude the "usernames" present in the thread participants
|
||||
val threadParticipantUsers = HashMap<String, ReadableMap>() // All unique users from thread participants are stored here
|
||||
|
||||
while(iterator.hasNextKey()) {
|
||||
val key = iterator.nextKey()
|
||||
val post = posts.getMap(key)
|
||||
|
|
@ -117,6 +155,38 @@ class PushNotificationDataRunnable {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isCRTEnabled) {
|
||||
// Add root post as a thread
|
||||
val rootId = post?.getString("root_id")
|
||||
if (rootId.isNullOrEmpty()) {
|
||||
threads.pushMap(post!!)
|
||||
}
|
||||
|
||||
// Add participant userIds and usernames to exclude them from getting fetched again
|
||||
val participants = post.getArray("participants")
|
||||
if (participants != null) {
|
||||
for (i in 0 until participants.size()) {
|
||||
val participant = participants.getMap(i)
|
||||
|
||||
val userId = participant.getString("id")
|
||||
if (userId != currentUserId && userId != null) {
|
||||
if (!threadParticipantUserIds.contains(userId)) {
|
||||
threadParticipantUserIds.add(userId)
|
||||
}
|
||||
|
||||
if (!threadParticipantUsers.containsKey(userId)) {
|
||||
threadParticipantUsers[userId!!] = participant
|
||||
}
|
||||
}
|
||||
|
||||
val username = participant.getString("username")
|
||||
if (username != null && username != currentUsername && !threadParticipantUsernames.contains(username)) {
|
||||
threadParticipantUsernames.add(username)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val existingUserIds = DatabaseHelper.instance!!.queryIds(db, "User", userIds.toTypedArray())
|
||||
|
|
@ -124,6 +194,27 @@ class PushNotificationDataRunnable {
|
|||
userIds.removeAll { it in existingUserIds }
|
||||
usernames.removeAll { it in existingUsernames }
|
||||
|
||||
if (threadParticipantUserIds.size > 0) {
|
||||
// Do not fetch users found in thread participants as we get the user's data in the posts response already
|
||||
userIds.removeAll { it in threadParticipantUserIds }
|
||||
usernames.removeAll { it in threadParticipantUsernames }
|
||||
|
||||
// Get users from thread participants
|
||||
val existingThreadParticipantUserIds = DatabaseHelper.instance!!.queryIds(db, "User", threadParticipantUserIds.toTypedArray())
|
||||
|
||||
// Exclude the thread participants already present in the DB from getting inserted again
|
||||
val usersFromThreads = WritableNativeArray()
|
||||
threadParticipantUsers.forEach{ (userId, user) ->
|
||||
if (!existingThreadParticipantUserIds.contains(userId)) {
|
||||
usersFromThreads.pushMap(user)
|
||||
}
|
||||
}
|
||||
|
||||
if (usersFromThreads.size() > 0) {
|
||||
results.putArray("usersFromThreads", usersFromThreads)
|
||||
}
|
||||
}
|
||||
|
||||
if (userIds.size > 0) {
|
||||
results.putArray("userIdsToLoad", ReadableArrayUtils.toWritableArray(userIds.toTypedArray()))
|
||||
}
|
||||
|
|
@ -131,11 +222,13 @@ class PushNotificationDataRunnable {
|
|||
if (usernames.size > 0) {
|
||||
results.putArray("usernamesToLoad", ReadableArrayUtils.toWritableArray(usernames.toTypedArray()))
|
||||
}
|
||||
|
||||
if (threads.size() > 0) {
|
||||
results.putArray("threads", threads)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return results
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -31,11 +31,20 @@ public struct PostData: Codable {
|
|||
}
|
||||
|
||||
extension Network {
|
||||
public func fetchPostsForChannel(withId channelId: String, withSince since: Int64?, withServerUrl serverUrl: String, completionHandler: @escaping ResponseHandler) {
|
||||
let queryParams = since == nil ?
|
||||
"?page=0&per_page=\(POST_CHUNK_SIZE)" :
|
||||
"?since=\(since!)"
|
||||
let endpoint = "/channels/\(channelId)/posts\(queryParams)"
|
||||
public func fetchPostsForChannel(withId channelId: String, withSince since: Int64?, withServerUrl serverUrl: String, withIsCRTEnabled isCRTEnabled: Bool, withRootId rootId: String, completionHandler: @escaping ResponseHandler) {
|
||||
|
||||
let additionalParams = isCRTEnabled ? "&collapsedThreads=true&collapsedThreadsExtended=true" : ""
|
||||
|
||||
let endpoint: String
|
||||
if (isCRTEnabled && !rootId.isEmpty) {
|
||||
let queryParams = "?skipFetchThreads=false&perPage=60&fromCreatedAt=0&direction=up"
|
||||
endpoint = "/posts/\(rootId)/thread\(queryParams)\(additionalParams)"
|
||||
} else {
|
||||
let queryParams = since == nil ?
|
||||
"?page=0&per_page=\(POST_CHUNK_SIZE)" :
|
||||
"?since=\(since!)"
|
||||
endpoint = "/channels/\(channelId)/posts\(queryParams)\(additionalParams)"
|
||||
}
|
||||
let url = buildApiUrl(serverUrl, endpoint)
|
||||
|
||||
return request(url, withMethod: "GET", withServerUrl: serverUrl, completionHandler: completionHandler)
|
||||
|
|
|
|||
|
|
@ -114,24 +114,32 @@ extension Network {
|
|||
let group = DispatchGroup()
|
||||
|
||||
let channelId = notification.userInfo["channel_id"] as! String
|
||||
let rootId = notification.userInfo.index(forKey: "root_id") != nil ? notification.userInfo["root_id"] as! String : ""
|
||||
let serverUrl = notification.userInfo["server_url"] as! String
|
||||
let isCRTEnabled = notification.userInfo["is_crt_enabled"] as! Bool
|
||||
let currentUser = try! Database.default.queryCurrentUser(serverUrl)
|
||||
let currentUserId = currentUser?[Expression<String>("id")]
|
||||
let currentUsername = currentUser?[Expression<String>("username")]
|
||||
|
||||
var postData: PostData? = nil
|
||||
var threads: [Post] = []
|
||||
var userIdsToLoad: Set<String> = Set()
|
||||
var usernamesToLoad: Set<String> = Set()
|
||||
var users: Set<User> = Set()
|
||||
|
||||
group.enter()
|
||||
let since = try? Database.default.queryPostsSinceForChannel(withId: channelId, withServerUrl: serverUrl)
|
||||
self.fetchPostsForChannel(withId: channelId, withSince: since, withServerUrl: serverUrl) { data, response, error in
|
||||
self.fetchPostsForChannel(withId: channelId, withSince: since, withServerUrl: serverUrl, withIsCRTEnabled: isCRTEnabled, withRootId: rootId) { data, response, error in
|
||||
if self.responseOK(response), let data = data {
|
||||
postData = try! JSONDecoder().decode(PostData.self, from: data)
|
||||
if postData?.posts.count ?? 0 > 0 {
|
||||
var authorIds: Set<String> = Set()
|
||||
var usernames: Set<String> = Set()
|
||||
|
||||
var threadParticipantUserIds: Set<String> = Set() // Used to exclude the "userIds" present in the thread participants
|
||||
var threadParticipantUsernames: Set<String> = Set() // Used to exclude the "usernames" present in the thread participants
|
||||
var threadParticipantUsers = [String: User]() // All unique users from thread participants are stored here
|
||||
|
||||
postData!.posts.forEach{post in
|
||||
if (currentUserId != nil && post.user_id != currentUserId) {
|
||||
authorIds.insert(post.user_id)
|
||||
|
|
@ -141,11 +149,41 @@ extension Network {
|
|||
usernames.insert($0)
|
||||
}
|
||||
}
|
||||
|
||||
if (isCRTEnabled) {
|
||||
// Add root post as a thread
|
||||
let rootId = post.root_id
|
||||
if (rootId.isEmpty) {
|
||||
threads.append(post)
|
||||
}
|
||||
|
||||
let participants = post.participants ?? []
|
||||
if (participants.count > 0) {
|
||||
participants.forEach { participant in
|
||||
let userId = participant.id
|
||||
if (userId != currentUserId) {
|
||||
threadParticipantUserIds.insert(userId)
|
||||
if (threadParticipantUsers[userId] != nil) {
|
||||
threadParticipantUsers[userId] = participant
|
||||
}
|
||||
}
|
||||
|
||||
let username = participant.username
|
||||
if (username != "" && username != currentUsername) {
|
||||
threadParticipantUsernames.insert(username)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (authorIds.count > 0) {
|
||||
if let existingIds = try? Database.default.queryUsers(byIds: authorIds, withServerUrl: serverUrl) {
|
||||
userIdsToLoad = authorIds.filter { !existingIds.contains($0) }
|
||||
// Filter the users found in the thread participants list
|
||||
if (threadParticipantUserIds.count > 0) {
|
||||
userIdsToLoad = userIdsToLoad.filter{ !threadParticipantUserIds.contains($0) }
|
||||
}
|
||||
if (userIdsToLoad.count > 0) {
|
||||
group.enter()
|
||||
self.fetchUsers(byIds: Array(userIdsToLoad), withServerUrl: serverUrl) { data, response, error in
|
||||
|
|
@ -158,10 +196,14 @@ extension Network {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (usernames.count > 0) {
|
||||
if let existingUsernames = try? Database.default.queryUsers(byUsernames: usernames, withServerUrl: serverUrl) {
|
||||
usernamesToLoad = usernames.filter{ !existingUsernames.contains($0)}
|
||||
// Filter the users found in the thread participants list
|
||||
if (threadParticipantUsernames.count > 0) {
|
||||
usernamesToLoad = usernamesToLoad.filter{ !threadParticipantUsernames.contains($0) }
|
||||
}
|
||||
if (usernamesToLoad.count > 0) {
|
||||
group.enter()
|
||||
|
||||
|
|
@ -175,6 +217,18 @@ extension Network {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (threadParticipantUserIds.count > 0) {
|
||||
if let existingThreadParticipantUserIds = try? Database.default.queryUsers(byIds: threadParticipantUserIds, withServerUrl: serverUrl) {
|
||||
threadParticipantUsers.forEach { (userId: String, user: User) in
|
||||
if (!existingThreadParticipantUserIds.contains(userId)) {
|
||||
users.insert(user)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -182,12 +236,16 @@ extension Network {
|
|||
}
|
||||
|
||||
group.wait()
|
||||
|
||||
|
||||
group.enter()
|
||||
if (postData != nil && postData?.posts != nil && postData!.posts.count > 0) {
|
||||
if let db = try? Database.default.getDatabaseForServer(serverUrl) {
|
||||
let receivingThreads = isCRTEnabled && !rootId.isEmpty
|
||||
try? db.transaction {
|
||||
try? Database.default.handlePostData(db, postData!, channelId, since != nil)
|
||||
try? Database.default.handlePostData(db, postData!, channelId, since != nil, receivingThreads)
|
||||
if (threads.count > 0) {
|
||||
try? Database.default.handleThreads(db, threads)
|
||||
}
|
||||
if (users.count > 0) {
|
||||
try? Database.default.insertUsers(db, users)
|
||||
}
|
||||
|
|
@ -195,7 +253,7 @@ extension Network {
|
|||
}
|
||||
}
|
||||
group.leave()
|
||||
|
||||
|
||||
if let contentHandler = contentHandler {
|
||||
contentHandler(notification)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,6 +25,11 @@ public struct Post: Codable {
|
|||
let pending_post_id: String
|
||||
let metadata: String
|
||||
var prev_post_id: String
|
||||
// CRT
|
||||
let participants: [User]?
|
||||
let last_reply_at: Int64
|
||||
let reply_count: Int
|
||||
let is_following: Bool
|
||||
|
||||
public enum PostKeys: String, CodingKey {
|
||||
case id = "id"
|
||||
|
|
@ -42,6 +47,11 @@ public struct Post: Codable {
|
|||
case props = "props"
|
||||
case pending_post_id = "pending_post_id"
|
||||
case metadata = "metadata"
|
||||
// CRT
|
||||
case participants = "participants"
|
||||
case last_reply_at = "last_reply_at"
|
||||
case reply_count = "reply_count"
|
||||
case is_following = "is_following"
|
||||
}
|
||||
|
||||
public init(from decoder: Decoder) throws {
|
||||
|
|
@ -64,6 +74,11 @@ public struct Post: Codable {
|
|||
pending_post_id = try values.decode(String.self, forKey: .pending_post_id)
|
||||
let propsData = try values.decode([String:Any].self, forKey: .props)
|
||||
props = Database.default.json(from: propsData) ?? "{}"
|
||||
// CRT
|
||||
participants = try values.decodeIfPresent([User].self, forKey: .participants) ?? []
|
||||
last_reply_at = try values.decodeIfPresent(Int64.self, forKey: .last_reply_at) ?? 0
|
||||
reply_count = try values.decodeIfPresent(Int.self, forKey: .reply_count) ?? 0
|
||||
is_following = try values.decodeIfPresent(Bool.self, forKey: .is_following) ?? false
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -82,6 +97,12 @@ struct PostSetters {
|
|||
let emojiSetters: [[Setter]]
|
||||
}
|
||||
|
||||
struct ThreadSetters {
|
||||
let id: String
|
||||
let threadSetters: [Setter]
|
||||
let threadParticipantSetters: [[Setter]]
|
||||
}
|
||||
|
||||
extension Database {
|
||||
public func queryPostsSinceForChannel(withId channelId: String, withServerUrl serverUrl: String) throws -> Int64? {
|
||||
let db = try getDatabaseForServer(serverUrl)
|
||||
|
|
@ -142,16 +163,22 @@ extension Database {
|
|||
|
||||
return (0, 0)
|
||||
}
|
||||
|
||||
public func handlePostData(_ db: Connection, _ postData: PostData, _ channelId: String, _ usedSince: Bool = false) throws {
|
||||
|
||||
public func handlePostData(_ db: Connection, _ postData: PostData, _ channelId: String, _ usedSince: Bool = false, _ receivingThreads: Bool = false) throws {
|
||||
let sortedChainedPosts = chainAndSortPosts(postData)
|
||||
try insertOrUpdatePosts(db, sortedChainedPosts, channelId)
|
||||
let earliest = sortedChainedPosts.first!.create_at
|
||||
let latest = sortedChainedPosts.last!.create_at
|
||||
try handlePostsInChannel(db, channelId, earliest, latest, usedSince)
|
||||
if (!receivingThreads) {
|
||||
try handlePostsInChannel(db, channelId, earliest, latest, usedSince)
|
||||
}
|
||||
try handlePostsInThread(db, postData.posts)
|
||||
}
|
||||
|
||||
public func handleThreads(_ db: Connection, _ threads: [Post]) throws {
|
||||
try insertThreads(db, threads)
|
||||
}
|
||||
|
||||
private func handlePostsInChannel(_ db: Connection, _ channelId: String, _ earliest: Int64, _ latest: Int64, _ usedSince: Bool = false) throws {
|
||||
if usedSince {
|
||||
try? updatePostsInChannelLatestOnly(db, channelId, latest)
|
||||
|
|
@ -283,6 +310,23 @@ extension Database {
|
|||
}
|
||||
}
|
||||
|
||||
private func insertThreads(_ db: Connection, _ posts: [Post]) throws {
|
||||
let setters = try createThreadSetters(db, from: posts)
|
||||
for setter in setters {
|
||||
let insertThread = threadTable.insert(or: .replace, setter.threadSetters)
|
||||
try db.run(insertThread)
|
||||
|
||||
let threadIdCol = Expression<String>("thread_id")
|
||||
let deletePreviousThreadParticipants = threadParticipantTable.where(threadIdCol == setter.id).delete()
|
||||
try db.run(deletePreviousThreadParticipants)
|
||||
|
||||
if !setter.threadParticipantSetters.isEmpty {
|
||||
let insertThreadParticipants = threadParticipantTable.insertMany(setter.threadParticipantSetters)
|
||||
try db.run(insertThreadParticipants)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func createPostSetters(from posts: [Post]) -> [PostSetters] {
|
||||
let id = Expression<String>("id")
|
||||
let createAt = Expression<Int64>("create_at")
|
||||
|
|
@ -431,6 +475,71 @@ extension Database {
|
|||
emojiSetters: emojiSetters)
|
||||
}
|
||||
|
||||
private func createThreadSetters(_ db: Connection, from posts: [Post]) throws -> [ThreadSetters] {
|
||||
let id = Expression<String>("id")
|
||||
let lastReplyAt = Expression<Int64>("last_reply_at")
|
||||
let replyCount = Expression<Int>("reply_count")
|
||||
let isFollowing = Expression<Bool>("is_following")
|
||||
let statusCol = Expression<String>("_status")
|
||||
|
||||
var threadsSetters: [ThreadSetters] = []
|
||||
|
||||
for post in posts {
|
||||
|
||||
let query = threadTable
|
||||
.select(id)
|
||||
.where(id == post.id)
|
||||
|
||||
if let _ = try? db.pluck(query) {
|
||||
let updateQuery = threadTable
|
||||
.where(id == post.id)
|
||||
.update(lastReplyAt <- post.last_reply_at,
|
||||
replyCount <- post.reply_count,
|
||||
isFollowing <- post.is_following,
|
||||
statusCol <- "updated"
|
||||
)
|
||||
try db.run(updateQuery)
|
||||
} else {
|
||||
var setter = [Setter]()
|
||||
setter.append(id <- post.id)
|
||||
setter.append(lastReplyAt <- post.last_reply_at)
|
||||
setter.append(replyCount <- post.reply_count)
|
||||
setter.append(isFollowing <- post.is_following)
|
||||
setter.append(statusCol <- "created")
|
||||
|
||||
let threadSetter = ThreadSetters(
|
||||
id: post.id,
|
||||
threadSetters: setter,
|
||||
threadParticipantSetters: createThreadParticipantSetters(from: post)
|
||||
)
|
||||
threadsSetters.append(threadSetter)
|
||||
}
|
||||
}
|
||||
|
||||
return threadsSetters
|
||||
}
|
||||
|
||||
private func createThreadParticipantSetters(from post: Post) -> [[Setter]] {
|
||||
|
||||
var participantSetters = [[Setter]]()
|
||||
|
||||
let id = Expression<String>("id")
|
||||
let userId = Expression<String>("user_id")
|
||||
let threadId = Expression<String>("thread_id")
|
||||
let statusCol = Expression<String>("_status")
|
||||
|
||||
for p in post.participants ?? [] {
|
||||
var participantSetter = [Setter]()
|
||||
participantSetter.append(id <- generateId() as String)
|
||||
participantSetter.append(userId <- p.id)
|
||||
participantSetter.append(threadId <- post.id)
|
||||
participantSetter.append(statusCol <- "created")
|
||||
participantSetters.append(participantSetter)
|
||||
}
|
||||
|
||||
return participantSetters
|
||||
}
|
||||
|
||||
private func createPostsInThreadSetters(_ db: Connection, from posts: [Post]) throws -> [[Setter]] {
|
||||
var setters = [[Setter]]()
|
||||
var postsInThread = [String: [Post]]()
|
||||
|
|
|
|||
|
|
@ -61,7 +61,7 @@ public struct User: Codable, Hashable {
|
|||
roles = try container.decode(String.self, forKey: .roles)
|
||||
is_guest = roles.contains("system_guest")
|
||||
last_name = try container.decode(String.self, forKey: .last_name)
|
||||
last_picture_update = try container.decode(Int64.self, forKey: .last_picture_update)
|
||||
last_picture_update = try container.decodeIfPresent(Int64.self, forKey: .last_picture_update) ?? 0
|
||||
locale = try container.decode(String.self, forKey: .locale)
|
||||
nickname = try container.decode(String.self, forKey: .nickname)
|
||||
position = try container.decode(String.self, forKey: .position)
|
||||
|
|
|
|||
|
|
@ -58,6 +58,8 @@ public class Database: NSObject {
|
|||
internal var fileTable = Table("File")
|
||||
internal var emojiTable = Table("CustomEmoji")
|
||||
internal var userTable = Table("User")
|
||||
internal var threadTable = Table("Thread")
|
||||
internal var threadParticipantTable = Table("ThreadParticipant")
|
||||
|
||||
@objc public static let `default` = Database()
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue