fix: database update not deleting in certain circumstances, storage persisting and removing the volatile convo info for thread deletion / creation, NTS hidden getter values in shared library

This commit is contained in:
0x330a 2023-03-31 09:01:52 +11:00
parent f111513211
commit 38454bedfc
11 changed files with 207 additions and 66 deletions

View File

@ -205,7 +205,7 @@ object FullBackupImporter {
db.query(ThreadDatabase.TABLE_NAME, arrayOf(ThreadDatabase.ID), db.query(ThreadDatabase.TABLE_NAME, arrayOf(ThreadDatabase.ID),
ThreadDatabase.EXPIRES_IN + " > 0", null, null, null, null).use { cursor -> ThreadDatabase.EXPIRES_IN + " > 0", null, null, null, null).use { cursor ->
while (cursor != null && cursor.moveToNext()) { while (cursor != null && cursor.moveToNext()) {
DatabaseComponent.get(context).threadDatabase().update(cursor.getLong(0), false) DatabaseComponent.get(context).threadDatabase().update(cursor.getLong(0), false, true)
} }
} }
} }

View File

@ -238,6 +238,10 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
} }
val recipient = Recipient.from(this, address, false) val recipient = Recipient.from(this, address, false)
threadId = storage.getOrCreateThreadIdFor(recipient.address) threadId = storage.getOrCreateThreadIdFor(recipient.address)
// assume created thread
if (recipient.isContactRecipient && !recipient.isLocalNumber) {
storage.setRecipientApproved(recipient, true) // assume approved when we CREATE the thread, not send first message
}
} }
} ?: finish() } ?: finish()
} }
@ -706,7 +710,7 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
// region Animation & Updating // region Animation & Updating
override fun onModified(recipient: Recipient) { override fun onModified(recipient: Recipient) {
runOnUiThread { runOnUiThread {
val threadRecipient = viewModel.recipient ?: return@runOnUiThread val threadRecipient = viewModel.recipient ?: return@runOnUiThread Log.d("Loki-DBG", "Recipient no longer here, go back?")
if (threadRecipient.isContactRecipient) { if (threadRecipient.isContactRecipient) {
binding?.blockedBanner?.isVisible = threadRecipient.isBlocked binding?.blockedBanner?.isVisible = threadRecipient.isBlocked
} }

View File

@ -186,7 +186,7 @@ class MmsDatabase(context: Context, databaseHelper: SQLCipherOpenHelper) : Messa
) )
get(context).groupReceiptDatabase() get(context).groupReceiptDatabase()
.update(ourAddress, id, status, timestamp) .update(ourAddress, id, status, timestamp)
get(context).threadDatabase().update(threadId, false) get(context).threadDatabase().update(threadId, false, true)
notifyConversationListeners(threadId) notifyConversationListeners(threadId)
} }
} }
@ -264,7 +264,7 @@ class MmsDatabase(context: Context, databaseHelper: SQLCipherOpenHelper) : Messa
" WHERE " + ID + " = ?", arrayOf(id.toString() + "") " WHERE " + ID + " = ?", arrayOf(id.toString() + "")
) )
if (threadId.isPresent) { if (threadId.isPresent) {
get(context).threadDatabase().update(threadId.get(), false) get(context).threadDatabase().update(threadId.get(), false, true)
} }
} }
@ -651,7 +651,7 @@ class MmsDatabase(context: Context, databaseHelper: SQLCipherOpenHelper) : Messa
) )
if (!MmsSmsColumns.Types.isExpirationTimerUpdate(mailbox)) { if (!MmsSmsColumns.Types.isExpirationTimerUpdate(mailbox)) {
if (runThreadUpdate) { if (runThreadUpdate) {
get(context).threadDatabase().update(threadId, true) get(context).threadDatabase().update(threadId, true, true)
} }
} }
notifyConversationListeners(threadId) notifyConversationListeners(threadId)
@ -790,7 +790,7 @@ class MmsDatabase(context: Context, databaseHelper: SQLCipherOpenHelper) : Messa
setLastSeen(threadId) setLastSeen(threadId)
setHasSent(threadId, true) setHasSent(threadId, true)
if (runThreadUpdate) { if (runThreadUpdate) {
update(threadId, true) update(threadId, true, true)
} }
} }
return messageId return messageId
@ -925,7 +925,7 @@ class MmsDatabase(context: Context, databaseHelper: SQLCipherOpenHelper) : Messa
groupReceiptDatabase.deleteRowsForMessage(messageId) groupReceiptDatabase.deleteRowsForMessage(messageId)
val database = databaseHelper.writableDatabase val database = databaseHelper.writableDatabase
database!!.delete(TABLE_NAME, ID_WHERE, arrayOf(messageId.toString())) database!!.delete(TABLE_NAME, ID_WHERE, arrayOf(messageId.toString()))
val threadDeleted = get(context).threadDatabase().update(threadId, false) val threadDeleted = get(context).threadDatabase().update(threadId, false, true)
notifyConversationListeners(threadId) notifyConversationListeners(threadId)
notifyStickerListeners() notifyStickerListeners()
notifyStickerPackListeners() notifyStickerPackListeners()
@ -942,7 +942,7 @@ class MmsDatabase(context: Context, databaseHelper: SQLCipherOpenHelper) : Messa
val database = databaseHelper.writableDatabase val database = databaseHelper.writableDatabase
database!!.delete(TABLE_NAME, ID_IN, arrayOf(messageIds.joinToString(","))) database!!.delete(TABLE_NAME, ID_IN, arrayOf(messageIds.joinToString(",")))
val threadDeleted = get(context).threadDatabase().update(threadId, false) val threadDeleted = get(context).threadDatabase().update(threadId, false, true)
notifyConversationListeners(threadId) notifyConversationListeners(threadId)
notifyStickerListeners() notifyStickerListeners()
notifyStickerPackListeners() notifyStickerPackListeners()
@ -1140,7 +1140,7 @@ class MmsDatabase(context: Context, databaseHelper: SQLCipherOpenHelper) : Messa
} }
val threadDb = get(context).threadDatabase() val threadDb = get(context).threadDatabase()
for (threadId in threadIds) { for (threadId in threadIds) {
val threadDeleted = threadDb.update(threadId, false) val threadDeleted = threadDb.update(threadId, false, true)
notifyConversationListeners(threadId) notifyConversationListeners(threadId)
} }
notifyStickerListeners() notifyStickerListeners()

View File

@ -147,7 +147,7 @@ public class SmsDatabase extends MessagingDatabase {
long threadId = getThreadIdForMessage(id); long threadId = getThreadIdForMessage(id);
DatabaseComponent.get(context).threadDatabase().update(threadId, false); DatabaseComponent.get(context).threadDatabase().update(threadId, false, true);
notifyConversationListeners(threadId); notifyConversationListeners(threadId);
} }
@ -236,7 +236,7 @@ public class SmsDatabase extends MessagingDatabase {
long threadId = getThreadIdForMessage(id); long threadId = getThreadIdForMessage(id);
DatabaseComponent.get(context).threadDatabase().update(threadId, false); DatabaseComponent.get(context).threadDatabase().update(threadId, false, true);
notifyConversationListeners(threadId); notifyConversationListeners(threadId);
} }
@ -299,7 +299,7 @@ public class SmsDatabase extends MessagingDatabase {
ID + " = ?", ID + " = ?",
new String[] {String.valueOf(cursor.getLong(cursor.getColumnIndexOrThrow(ID)))}); new String[] {String.valueOf(cursor.getLong(cursor.getColumnIndexOrThrow(ID)))});
DatabaseComponent.get(context).threadDatabase().update(threadId, false); DatabaseComponent.get(context).threadDatabase().update(threadId, false, true);
notifyConversationListeners(threadId); notifyConversationListeners(threadId);
foundMessage = true; foundMessage = true;
} }
@ -383,7 +383,7 @@ public class SmsDatabase extends MessagingDatabase {
long threadId = getThreadIdForMessage(messageId); long threadId = getThreadIdForMessage(messageId);
DatabaseComponent.get(context).threadDatabase().update(threadId, true); DatabaseComponent.get(context).threadDatabase().update(threadId, true, true);
notifyConversationListeners(threadId); notifyConversationListeners(threadId);
notifyConversationListListeners(); notifyConversationListListeners();
@ -470,7 +470,7 @@ public class SmsDatabase extends MessagingDatabase {
long messageId = db.insert(TABLE_NAME, null, values); long messageId = db.insert(TABLE_NAME, null, values);
if (runThreadUpdate) { if (runThreadUpdate) {
DatabaseComponent.get(context).threadDatabase().update(threadId, true); DatabaseComponent.get(context).threadDatabase().update(threadId, true, true);
} }
if (message.getSubscriptionId() != -1) { if (message.getSubscriptionId() != -1) {
@ -546,7 +546,7 @@ public class SmsDatabase extends MessagingDatabase {
} }
if (runThreadUpdate) { if (runThreadUpdate) {
DatabaseComponent.get(context).threadDatabase().update(threadId, true); DatabaseComponent.get(context).threadDatabase().update(threadId, true, true);
} }
DatabaseComponent.get(context).threadDatabase().setLastSeen(threadId); DatabaseComponent.get(context).threadDatabase().setLastSeen(threadId);
@ -595,7 +595,7 @@ public class SmsDatabase extends MessagingDatabase {
SQLiteDatabase db = databaseHelper.getWritableDatabase(); SQLiteDatabase db = databaseHelper.getWritableDatabase();
long threadId = getThreadIdForMessage(messageId); long threadId = getThreadIdForMessage(messageId);
db.delete(TABLE_NAME, ID_WHERE, new String[] {messageId+""}); db.delete(TABLE_NAME, ID_WHERE, new String[] {messageId+""});
boolean threadDeleted = DatabaseComponent.get(context).threadDatabase().update(threadId, false); boolean threadDeleted = DatabaseComponent.get(context).threadDatabase().update(threadId, false, true);
notifyConversationListeners(threadId); notifyConversationListeners(threadId);
return threadDeleted; return threadDeleted;
} }
@ -619,7 +619,7 @@ public class SmsDatabase extends MessagingDatabase {
ID + " IN (" + StringUtils.join(argsArray, ',') + ")", ID + " IN (" + StringUtils.join(argsArray, ',') + ")",
argValues argValues
); );
boolean threadDeleted = DatabaseComponent.get(context).threadDatabase().update(threadId, false); boolean threadDeleted = DatabaseComponent.get(context).threadDatabase().update(threadId, false, true);
notifyConversationListeners(threadId); notifyConversationListeners(threadId);
return threadDeleted; return threadDeleted;
} }

View File

@ -89,7 +89,72 @@ import org.thoughtcrime.securesms.util.SessionMetaProtocol
import java.security.MessageDigest import java.security.MessageDigest
import network.loki.messenger.libsession_util.util.Contact as LibSessionContact import network.loki.messenger.libsession_util.util.Contact as LibSessionContact
class Storage(context: Context, helper: SQLCipherOpenHelper, private val configFactory: ConfigFactory) : Database(context, helper), StorageProtocol { class Storage(context: Context, helper: SQLCipherOpenHelper, private val configFactory: ConfigFactory) : Database(context, helper), StorageProtocol,
ThreadDatabase.ConversationThreadUpdateListener {
init {
DatabaseComponent.get(context).threadDatabase().setUpdateListener(this)
}
// TODO: maybe add time here from formation / creation message
override fun threadCreated(address: Address, threadId: Long) {
val volatile = configFactory.convoVolatile ?: return
if (address.isGroup) {
val groups = configFactory.userGroups ?: return
if (address.isClosedGroup) {
val sessionId = GroupUtil.doubleDecodeGroupId(address.serialize())
val legacyGroup = groups.getOrConstructLegacyGroupInfo(sessionId)
groups.set(legacyGroup)
val newVolatileParams = volatile.getOrConstructLegacyGroup(sessionId).copy(
lastRead = SnodeAPI.nowWithOffset,
)
volatile.set(newVolatileParams)
} else if (address.isOpenGroup) {
// these should be added on the group join / group info fetch
Log.w("Loki", "Thread created called for open group address, not adding any extra information")
}
} else if (address.isContact) {
// don't update our own address into the contacts DB
if (getUserPublicKey() != address.serialize()) {
val contacts = configFactory.contacts ?: return
contacts.upsertContact(address.serialize())
} else {
val userProfile = configFactory.user ?: return
userProfile.setNtsHidden(false)
}
val newVolatileParams = volatile.getOrConstructOneToOne(address.serialize()).copy(
lastRead = SnodeAPI.nowWithOffset
)
volatile.set(newVolatileParams)
}
}
override fun threadDeleted(address: Address, threadId: Long) {
val volatile = configFactory.convoVolatile ?: return
if (address.isGroup) {
val groups = configFactory.userGroups ?: return
if (address.isClosedGroup) {
val sessionId = GroupUtil.doubleDecodeGroupId(address.serialize())
volatile.eraseLegacyClosedGroup(sessionId)
groups.eraseLegacyGroup(sessionId)
} else if (address.isOpenGroup) {
// these should be removed in the group leave / handling new configs
Log.w("Loki", "Thread delete called for open group address, expecting to be handled elsewhere")
}
} else {
volatile.eraseOneToOne(address.serialize())
if (getUserPublicKey() != address.serialize()) {
val contacts = configFactory.contacts ?: return
contacts.upsertContact(address.serialize()) {
// hidden = true TODO: maybe this?
}
} else {
val userProfile = configFactory.user ?: return
userProfile.setNtsHidden(true)
}
}
ConfigurationMessageUtilities.forceSyncConfigurationNowIfNeeded(context)
}
override fun getUserPublicKey(): String? { override fun getUserPublicKey(): String? {
return TextSecurePreferences.getLocalNumber(context) return TextSecurePreferences.getLocalNumber(context)
@ -167,7 +232,7 @@ class Storage(context: Context, helper: SQLCipherOpenHelper, private val configF
override fun updateThread(threadId: Long, unarchive: Boolean) { override fun updateThread(threadId: Long, unarchive: Boolean) {
val threadDb = DatabaseComponent.get(context).threadDatabase() val threadDb = DatabaseComponent.get(context).threadDatabase()
threadDb.update(threadId, unarchive) threadDb.update(threadId, unarchive, false)
} }
override fun persist(message: VisibleMessage, override fun persist(message: VisibleMessage,
@ -349,6 +414,16 @@ class Storage(context: Context, helper: SQLCipherOpenHelper, private val configF
profileManager.setProfileKey(context, recipient, userPic.key) profileManager.setProfileKey(context, recipient, userPic.key)
setUserProfilePictureURL(userPic.url) setUserProfilePictureURL(userPic.url)
} }
if (userProfile.getNtsHidden()) {
// delete nts thread if needed
val ourThread = getThreadId(recipient) ?: return
deleteConversation(ourThread)
} else {
// create note to self thread if needed (?)
val ourThread = getOrCreateThreadIdFor(recipient.address)
setPinned(ourThread, userProfile.getNtsPriority() > 0)
}
} }
private fun updateContacts(contacts: Contacts) { private fun updateContacts(contacts: Contacts) {
@ -378,15 +453,9 @@ class Storage(context: Context, helper: SQLCipherOpenHelper, private val configF
val extracted = convos.all() val extracted = convos.all()
for (conversation in extracted) { for (conversation in extracted) {
val threadId = when (conversation) { val threadId = when (conversation) {
is Conversation.OneToOne -> conversation.sessionId.let { is Conversation.OneToOne -> getOrCreateThreadIdFor(fromSerialized(conversation.sessionId))
getOrCreateThreadIdFor(fromSerialized(it)) is Conversation.LegacyGroup -> getOrCreateThreadIdFor("", conversation.groupId,null)
} is Conversation.Community -> getOrCreateThreadIdFor("",null, "${conversation.baseCommunityInfo.baseUrl}.${conversation.baseCommunityInfo.room}")
is Conversation.LegacyGroup -> conversation.groupId.let {
getOrCreateThreadIdFor("", it,null)
}
is Conversation.Community -> conversation.baseCommunityInfo.baseUrl.let {
getOrCreateThreadIdFor("",null, it)
}
} }
if (threadId >= 0) { if (threadId >= 0) {
markConversationAsRead(threadId, conversation.lastRead) markConversationAsRead(threadId, conversation.lastRead)
@ -408,8 +477,8 @@ class Storage(context: Context, helper: SQLCipherOpenHelper, private val configF
val existingCommunities: Map<Long, OpenGroup> = allOpenGroups.filterKeys { it !in toDeleteCommunities.keys } val existingCommunities: Map<Long, OpenGroup> = allOpenGroups.filterKeys { it !in toDeleteCommunities.keys }
val toAddCommunities = communities.filter { it.community.fullUrl() !in existingCommunities.map { it.value.joinURL } } val toAddCommunities = communities.filter { it.community.fullUrl() !in existingCommunities.map { it.value.joinURL } }
val existingJoinUrls = existingCommunities.values.map { it.joinURL } val existingJoinUrls = existingCommunities.values.map { it.joinURL }
val existingClosedGroups = getAllGroups().filter { it.isClosedGroup } val existingClosedGroups = getAllGroups().filter { it.isClosedGroup }
val lgcIds = lgc.map { it.sessionId } val lgcIds = lgc.map { it.sessionId }
val toDeleteClosedGroups = existingClosedGroups.filter { group -> val toDeleteClosedGroups = existingClosedGroups.filter { group ->
@ -837,7 +906,7 @@ class Storage(context: Context, helper: SQLCipherOpenHelper, private val configF
override fun setExpirationTimer(address: String, duration: Int) { override fun setExpirationTimer(address: String, duration: Int) {
val recipient = Recipient.from(context, fromSerialized(address), false) val recipient = Recipient.from(context, fromSerialized(address), false)
DatabaseComponent.get(context).recipientDatabase().setExpireMessages(recipient, duration) DatabaseComponent.get(context).recipientDatabase().setExpireMessages(recipient, duration)
if (recipient.isContactRecipient) { if (recipient.isContactRecipient && !recipient.isLocalNumber) {
configFactory.contacts?.upsertContact(address) { configFactory.contacts?.upsertContact(address) {
this.expiryMode = if (duration != 0) { this.expiryMode = if (duration != 0) {
ExpiryMode.AfterRead(duration.toLong()) ExpiryMode.AfterRead(duration.toLong())
@ -1074,6 +1143,33 @@ class Storage(context: Context, helper: SQLCipherOpenHelper, private val configF
override fun setPinned(threadID: Long, isPinned: Boolean) { override fun setPinned(threadID: Long, isPinned: Boolean) {
val threadDB = DatabaseComponent.get(context).threadDatabase() val threadDB = DatabaseComponent.get(context).threadDatabase()
threadDB.setPinned(threadID, isPinned) threadDB.setPinned(threadID, isPinned)
val threadRecipient = getRecipientForThread(threadID) ?: return
if (threadRecipient.isLocalNumber) {
val user = configFactory.user ?: return
user.setNtsPriority(if (isPinned) 1 else 0)
} else if (threadRecipient.isContactRecipient) {
val contacts = configFactory.contacts ?: return
contacts.upsertContact(threadRecipient.address.serialize()) {
priority = if (isPinned) 1 else 0
}
} else if (threadRecipient.isGroupRecipient) {
val groups = configFactory.userGroups ?: return
if (threadRecipient.isClosedGroupRecipient) {
val sessionId = GroupUtil.doubleDecodeGroupId(threadRecipient.address.serialize())
val newGroupInfo = groups.getOrConstructLegacyGroupInfo(sessionId).copy (
priority = if (isPinned) 1 else 0
)
groups.set(newGroupInfo)
} else if (threadRecipient.isOpenGroupRecipient) {
val openGroup = getOpenGroup(threadID) ?: return
val (baseUrl, room, pubKeyHex) = BaseCommunityInfo.parseFullUrl(openGroup.joinURL) ?: return
val newGroupInfo = groups.getOrConstructCommunityInfo(baseUrl, room, Hex.toStringCondensed(pubKeyHex)).copy (
priority = if (isPinned) 1 else 0
)
groups.set(newGroupInfo)
}
}
ConfigurationMessageUtilities.forceSyncConfigurationNowIfNeeded(context)
} }
override fun isPinned(threadID: Long): Boolean { override fun isPinned(threadID: Long): Boolean {
@ -1234,6 +1330,7 @@ class Storage(context: Context, helper: SQLCipherOpenHelper, private val configF
override fun setRecipientApproved(recipient: Recipient, approved: Boolean) { override fun setRecipientApproved(recipient: Recipient, approved: Boolean) {
DatabaseComponent.get(context).recipientDatabase().setApproved(recipient, approved) DatabaseComponent.get(context).recipientDatabase().setApproved(recipient, approved)
if (recipient.isLocalNumber || !recipient.isContactRecipient) return
configFactory.contacts?.upsertContact(recipient.address.serialize()) { configFactory.contacts?.upsertContact(recipient.address.serialize()) {
this.approved = approved this.approved = approved
} }
@ -1241,6 +1338,7 @@ class Storage(context: Context, helper: SQLCipherOpenHelper, private val configF
override fun setRecipientApprovedMe(recipient: Recipient, approvedMe: Boolean) { override fun setRecipientApprovedMe(recipient: Recipient, approvedMe: Boolean) {
DatabaseComponent.get(context).recipientDatabase().setApprovedMe(recipient, approvedMe) DatabaseComponent.get(context).recipientDatabase().setApprovedMe(recipient, approvedMe)
if (recipient.isLocalNumber || !recipient.isContactRecipient) return
configFactory.contacts?.upsertContact(recipient.address.serialize()) { configFactory.contacts?.upsertContact(recipient.address.serialize()) {
this.approvedMe = approvedMe this.approvedMe = approvedMe
} }
@ -1376,7 +1474,7 @@ class Storage(context: Context, helper: SQLCipherOpenHelper, private val configF
override fun setBlocked(recipients: List<Recipient>, isBlocked: Boolean, fromConfigUpdate: Boolean) { override fun setBlocked(recipients: List<Recipient>, isBlocked: Boolean, fromConfigUpdate: Boolean) {
val recipientDb = DatabaseComponent.get(context).recipientDatabase() val recipientDb = DatabaseComponent.get(context).recipientDatabase()
recipientDb.setBlocked(recipients, isBlocked) recipientDb.setBlocked(recipients, isBlocked)
recipients.filter { it.isContactRecipient }.forEach { recipient -> recipients.filter { it.isContactRecipient && !it.isLocalNumber }.forEach { recipient ->
configFactory.contacts?.upsertContact(recipient.address.serialize()) { configFactory.contacts?.upsertContact(recipient.address.serialize()) {
this.blocked = isBlocked this.blocked = isBlocked
} }

View File

@ -72,6 +72,11 @@ import java.util.Set;
public class ThreadDatabase extends Database { public class ThreadDatabase extends Database {
public interface ConversationThreadUpdateListener {
public void threadCreated(@NonNull Address address, long threadId);
public void threadDeleted(@NonNull Address address, long threadId);
}
private static final String TAG = ThreadDatabase.class.getSimpleName(); private static final String TAG = ThreadDatabase.class.getSimpleName();
private final Map<Long, Address> addressCache = new HashMap<>(); private final Map<Long, Address> addressCache = new HashMap<>();
@ -139,10 +144,16 @@ public class ThreadDatabase extends Database {
"ADD COLUMN " + UNREAD_MENTION_COUNT + " INTEGER DEFAULT 0;"; "ADD COLUMN " + UNREAD_MENTION_COUNT + " INTEGER DEFAULT 0;";
} }
private ConversationThreadUpdateListener updateListener;
public ThreadDatabase(Context context, SQLCipherOpenHelper databaseHelper) { public ThreadDatabase(Context context, SQLCipherOpenHelper databaseHelper) {
super(context, databaseHelper); super(context, databaseHelper);
} }
public void setUpdateListener(ConversationThreadUpdateListener updateListener) {
this.updateListener = updateListener;
}
private long createThreadForRecipient(Address address, boolean group, int distributionType) { private long createThreadForRecipient(Address address, boolean group, int distributionType) {
ContentValues contentValues = new ContentValues(4); ContentValues contentValues = new ContentValues(4);
long date = System.currentTimeMillis(); long date = System.currentTimeMillis();
@ -205,10 +216,14 @@ public class ThreadDatabase extends Database {
} }
private void deleteThread(long threadId) { private void deleteThread(long threadId) {
Recipient recipient = getRecipientForThreadId(threadId);
SQLiteDatabase db = databaseHelper.getWritableDatabase(); SQLiteDatabase db = databaseHelper.getWritableDatabase();
db.delete(TABLE_NAME, ID_WHERE, new String[] {threadId + ""}); int numberRemoved = db.delete(TABLE_NAME, ID_WHERE, new String[] {threadId + ""});
addressCache.remove(threadId); addressCache.remove(threadId);
notifyConversationListListeners(); notifyConversationListListeners();
if (updateListener != null && numberRemoved > 0 && recipient != null) {
updateListener.threadDeleted(recipient.getAddress(), threadId);
}
} }
private void deleteThreads(Set<Long> threadIds) { private void deleteThreads(Set<Long> threadIds) {
@ -276,7 +291,7 @@ public class ThreadDatabase extends Database {
DatabaseComponent.get(context).smsDatabase().deleteMessagesInThreadBeforeDate(threadId, lastTweetDate); DatabaseComponent.get(context).smsDatabase().deleteMessagesInThreadBeforeDate(threadId, lastTweetDate);
DatabaseComponent.get(context).mmsDatabase().deleteMessagesInThreadBeforeDate(threadId, lastTweetDate); DatabaseComponent.get(context).mmsDatabase().deleteMessagesInThreadBeforeDate(threadId, lastTweetDate);
update(threadId, false); update(threadId, false, true);
notifyConversationListeners(threadId); notifyConversationListeners(threadId);
} }
} finally { } finally {
@ -289,7 +304,7 @@ public class ThreadDatabase extends Database {
Log.i("ThreadDatabase", "Trimming thread: " + threadId + " before :"+timestamp); Log.i("ThreadDatabase", "Trimming thread: " + threadId + " before :"+timestamp);
DatabaseComponent.get(context).smsDatabase().deleteMessagesInThreadBeforeDate(threadId, timestamp); DatabaseComponent.get(context).smsDatabase().deleteMessagesInThreadBeforeDate(threadId, timestamp);
DatabaseComponent.get(context).mmsDatabase().deleteMessagesInThreadBeforeDate(threadId, timestamp); DatabaseComponent.get(context).mmsDatabase().deleteMessagesInThreadBeforeDate(threadId, timestamp);
update(threadId, false); update(threadId, false, true);
notifyConversationListeners(threadId); notifyConversationListeners(threadId);
} }
@ -475,7 +490,7 @@ public class ThreadDatabase extends Database {
} }
public Cursor getApprovedConversationList() { public Cursor getApprovedConversationList() {
String where = "((" + MESSAGE_COUNT + " != 0 AND (" + HAS_SENT + " = 1 OR " + RecipientDatabase.APPROVED + " = 1 OR "+ GroupDatabase.TABLE_NAME +"."+GROUP_ID+" LIKE '"+CLOSED_GROUP_PREFIX+"%')) OR " + GroupDatabase.TABLE_NAME + "." + GROUP_ID + " LIKE '" + OPEN_GROUP_PREFIX + "%') " + String where = "((" + HAS_SENT + " = 1 OR " + RecipientDatabase.APPROVED + " = 1 OR "+ GroupDatabase.TABLE_NAME +"."+GROUP_ID+" LIKE '"+CLOSED_GROUP_PREFIX+"%') OR " + GroupDatabase.TABLE_NAME + "." + GROUP_ID + " LIKE '" + OPEN_GROUP_PREFIX + "%') " +
"AND " + ARCHIVED + " = 0 "; "AND " + ARCHIVED + " = 0 ";
return getConversationList(where); return getConversationList(where);
} }
@ -640,13 +655,19 @@ public class ThreadDatabase extends Database {
try { try {
cursor = db.query(TABLE_NAME, new String[]{ID}, where, recipientsArg, null, null, null); cursor = db.query(TABLE_NAME, new String[]{ID}, where, recipientsArg, null, null, null);
long threadId;
boolean created = false;
if (cursor != null && cursor.moveToFirst()) { if (cursor != null && cursor.moveToFirst()) {
return cursor.getLong(cursor.getColumnIndexOrThrow(ID)); threadId = cursor.getLong(cursor.getColumnIndexOrThrow(ID));
} else { } else {
DatabaseComponent.get(context).recipientDatabase().setProfileSharing(recipient, true); DatabaseComponent.get(context).recipientDatabase().setProfileSharing(recipient, true);
return createThreadForRecipient(recipient.getAddress(), recipient.isGroupRecipient(), distributionType); threadId = createThreadForRecipient(recipient.getAddress(), recipient.isGroupRecipient(), distributionType);
created = true;
} }
if (created && updateListener != null) {
updateListener.threadCreated(recipient.getAddress(), threadId);
}
return threadId;
} finally { } finally {
if (cursor != null) if (cursor != null)
cursor.close(); cursor.close();
@ -687,11 +708,11 @@ public class ThreadDatabase extends Database {
notifyConversationListeners(threadId); notifyConversationListeners(threadId);
} }
public boolean update(long threadId, boolean unarchive) { public boolean update(long threadId, boolean unarchive, boolean shouldDeleteOnEmpty) {
MmsSmsDatabase mmsSmsDatabase = DatabaseComponent.get(context).mmsSmsDatabase(); MmsSmsDatabase mmsSmsDatabase = DatabaseComponent.get(context).mmsSmsDatabase();
long count = mmsSmsDatabase.getConversationCount(threadId); long count = mmsSmsDatabase.getConversationCount(threadId);
boolean shouldDeleteEmptyThread = deleteThreadOnEmpty(threadId); boolean shouldDeleteEmptyThread = !shouldDeleteOnEmpty ? false : deleteThreadOnEmpty(threadId);
if (count == 0 && shouldDeleteEmptyThread) { if (count == 0 && shouldDeleteEmptyThread) {
deleteThread(threadId); deleteThread(threadId);

View File

@ -573,7 +573,7 @@ class HomeActivity : PassphraseRequiredActionBarActivity(),
private fun setConversationPinned(threadId: Long, pinned: Boolean) { private fun setConversationPinned(threadId: Long, pinned: Boolean) {
lifecycleScope.launch(Dispatchers.IO) { lifecycleScope.launch(Dispatchers.IO) {
threadDb.setPinned(threadId, pinned) storage.setPinned(threadId, pinned)
homeViewModel.tryUpdateChannel() homeViewModel.tryUpdateChannel()
} }
} }

View File

@ -22,28 +22,40 @@ import org.session.libsession.messaging.utilities.SessionId
import org.session.libsession.utilities.Address import org.session.libsession.utilities.Address
import org.session.libsession.utilities.GroupUtil import org.session.libsession.utilities.GroupUtil
import org.session.libsession.utilities.TextSecurePreferences import org.session.libsession.utilities.TextSecurePreferences
import org.session.libsession.utilities.WindowDebouncer
import org.session.libsignal.utilities.Hex import org.session.libsignal.utilities.Hex
import org.session.libsignal.utilities.Log import org.session.libsignal.utilities.Log
import org.session.libsignal.utilities.toHexString import org.session.libsignal.utilities.toHexString
import org.thoughtcrime.securesms.dependencies.DatabaseComponent import org.thoughtcrime.securesms.dependencies.DatabaseComponent
import java.util.*
object ConfigurationMessageUtilities { object ConfigurationMessageUtilities {
val debouncer = WindowDebouncer(3000, Timer())
private fun scheduleConfigSync(userPublicKey: String) {
debouncer.publish {
// don't schedule job if we already have one
val storage = MessagingModuleConfiguration.shared.storage
val ourDestination = Destination.Contact(userPublicKey)
val currentStorageJob = storage.getConfigSyncJob(ourDestination)
if (currentStorageJob != null) {
(currentStorageJob as ConfigurationSyncJob).shouldRunAgain.set(true)
Log.d("Loki-DBG", "Not scheduling another one")
return@publish
}
val newConfigSync = ConfigurationSyncJob(ourDestination)
Log.d("Loki", "Scheduling new ConfigurationSyncJob")
JobQueue.shared.add(newConfigSync)
}
}
@JvmStatic @JvmStatic
fun syncConfigurationIfNeeded(context: Context) { fun syncConfigurationIfNeeded(context: Context) {
// add if check here to schedule new config job process and return early // add if check here to schedule new config job process and return early
val userPublicKey = TextSecurePreferences.getLocalNumber(context) ?: return val userPublicKey = TextSecurePreferences.getLocalNumber(context) ?: return
val storage = MessagingModuleConfiguration.shared.storage
if (ConfigBase.isNewConfigEnabled) { if (ConfigBase.isNewConfigEnabled) {
// don't schedule job if we already have one scheduleConfigSync(userPublicKey)
val ourDestination = Destination.Contact(userPublicKey)
if (storage.getConfigSyncJob(ourDestination) != null) {
Log.d("Loki", "ConfigSyncJob is already running for our destination")
return
}
val newConfigSync = ConfigurationSyncJob(ourDestination)
Log.d("Loki", "Scheduling new ConfigurationSyncJob")
JobQueue.shared.add(newConfigSync)
return return
} }
val lastSyncTime = TextSecurePreferences.getLastConfigurationSyncTime(context) val lastSyncTime = TextSecurePreferences.getLastConfigurationSyncTime(context)
@ -70,22 +82,11 @@ object ConfigurationMessageUtilities {
fun forceSyncConfigurationNowIfNeeded(context: Context): Promise<Unit, Exception> { fun forceSyncConfigurationNowIfNeeded(context: Context): Promise<Unit, Exception> {
// add if check here to schedule new config job process and return early // add if check here to schedule new config job process and return early
val userPublicKey = TextSecurePreferences.getLocalNumber(context) ?: return Promise.ofFail(NullPointerException("User Public Key is null")) val userPublicKey = TextSecurePreferences.getLocalNumber(context) ?: return Promise.ofFail(NullPointerException("User Public Key is null"))
val storage = MessagingModuleConfiguration.shared.storage
if (ConfigBase.isNewConfigEnabled) { if (ConfigBase.isNewConfigEnabled) {
// schedule job if none exist // schedule job if none exist
// don't schedule job if we already have one // don't schedule job if we already have one
Log.d("Loki-DBG", "Forcing config sync") Log.d("Loki-DBG", "Forcing config sync")
val ourDestination = Destination.Contact(userPublicKey) scheduleConfigSync(userPublicKey)
val currentStorageJob = storage.getConfigSyncJob(ourDestination)
if (currentStorageJob != null) {
(currentStorageJob as ConfigurationSyncJob).shouldRunAgain.set(true)
Log.d("Loki-DBG", "Not scheduling another one")
return Promise.ofFail(NullPointerException("A job is already pending or in progress, don't schedule another job"))
}
val newConfigSync = ConfigurationSyncJob(ourDestination)
Log.d("Loki", "Scheduling new ConfigurationSyncJob")
JobQueue.shared.add(newConfigSync)
// treat this promise as succeeding now (so app continues running and doesn't block UI)
return Promise.ofSuccess(Unit) return Promise.ofSuccess(Unit)
} }
val contacts = ContactUtilities.getAllContacts(context).filter { recipient -> val contacts = ContactUtilities.getAllContacts(context).filter { recipient ->

View File

@ -90,3 +90,15 @@ Java_network_loki_messenger_libsession_1util_UserProfile_setNtsHidden(JNIEnv *en
auto profile = ptrToProfile(env, thiz); auto profile = ptrToProfile(env, thiz);
profile->set_nts_hidden(is_hidden); profile->set_nts_hidden(is_hidden);
} }
extern "C"
JNIEXPORT jint JNICALL
Java_network_loki_messenger_libsession_1util_UserProfile_getNtsPriority(JNIEnv *env, jobject thiz) {
auto profile = ptrToProfile(env, thiz);
return profile->get_nts_priority();
}
extern "C"
JNIEXPORT jboolean JNICALL
Java_network_loki_messenger_libsession_1util_UserProfile_getNtsHidden(JNIEnv *env, jobject thiz) {
auto profile = ptrToProfile(env, thiz);
return profile->get_nts_hidden();
}

View File

@ -64,7 +64,7 @@ class Contacts(pointer: Long) : ConfigBase(pointer) {
/** /**
* Similar to [updateIfExists], but will create the underlying contact if it doesn't exist before passing to [updateFunction] * Similar to [updateIfExists], but will create the underlying contact if it doesn't exist before passing to [updateFunction]
*/ */
fun upsertContact(sessionId: String, updateFunction: Contact.()->Unit) { fun upsertContact(sessionId: String, updateFunction: Contact.()->Unit = {}) {
val contact = getOrConstruct(sessionId) val contact = getOrConstruct(sessionId)
updateFunction(contact) updateFunction(contact)
set(contact) set(contact)
@ -95,7 +95,9 @@ class UserProfile(pointer: Long) : ConfigBase(pointer) {
external fun getPic(): UserPic external fun getPic(): UserPic
external fun setPic(userPic: UserPic) external fun setPic(userPic: UserPic)
external fun setNtsPriority(priority: Int) external fun setNtsPriority(priority: Int)
external fun getNtsPriority(): Int
external fun setNtsHidden(isHidden: Boolean) external fun setNtsHidden(isHidden: Boolean)
external fun getNtsHidden(): Boolean
} }
class ConversationVolatileConfig(pointer: Long): ConfigBase(pointer) { class ConversationVolatileConfig(pointer: Long): ConfigBase(pointer) {

View File

@ -86,7 +86,9 @@ class OpenGroupPoller(private val server: String, private val executorService: S
isCaughtUp = true isCaughtUp = true
} }
} }
executorService?.schedule(this@OpenGroupPoller::poll, pollInterval, TimeUnit.MILLISECONDS) if (hasStarted) {
executorService?.schedule(this@OpenGroupPoller::poll, pollInterval, TimeUnit.MILLISECONDS)
}
}.fail { }.fail {
updateCapabilitiesIfNeeded(isPostCapabilitiesRetry, it) updateCapabilitiesIfNeeded(isPostCapabilitiesRetry, it)
}.map { } }.map { }
@ -115,6 +117,7 @@ class OpenGroupPoller(private val server: String, private val executorService: S
roomToken: String, roomToken: String,
pollInfo: OpenGroupApi.RoomPollInfo pollInfo: OpenGroupApi.RoomPollInfo
) { ) {
if (!hasStarted) return
val storage = MessagingModuleConfiguration.shared.storage val storage = MessagingModuleConfiguration.shared.storage
val groupId = "$server.$roomToken" val groupId = "$server.$roomToken"
val dbGroupId = GroupUtil.getEncodedOpenGroupID(groupId.toByteArray()) val dbGroupId = GroupUtil.getEncodedOpenGroupID(groupId.toByteArray())