Skip to content

Commit f617de5

Browse files
Merge branch 'dev' into fix/SES-4464-reply-mesasge-details
2 parents 8cbb73f + 675b179 commit f617de5

File tree

13 files changed

+346
-331
lines changed

13 files changed

+346
-331
lines changed

app/build.gradle.kts

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -386,9 +386,6 @@ dependencies {
386386
implementation(libs.subsampling.scale.image.view) {
387387
exclude(group = "com.android.support", module = "support-annotations")
388388
}
389-
implementation(libs.tooltips) {
390-
exclude(group = "com.android.support", module = "appcompat-v7")
391-
}
392389
implementation(libs.stream)
393390
implementation(libs.androidx.sqlite.ktx)
394391
implementation(libs.sqlcipher.android)
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
package org.session.libsession.avatars
2+
3+
import android.app.Application
4+
import kotlinx.coroutines.CoroutineScope
5+
import kotlinx.coroutines.Dispatchers
6+
import kotlinx.coroutines.launch
7+
import kotlinx.coroutines.withContext
8+
import org.session.libsession.utilities.recipients.RemoteFile
9+
import org.session.libsignal.utilities.Log
10+
import org.thoughtcrime.securesms.attachments.RemoteFileDownloadWorker
11+
import org.thoughtcrime.securesms.database.RecipientSettingsDatabase
12+
import org.thoughtcrime.securesms.dependencies.ManagerScope
13+
import org.thoughtcrime.securesms.glide.RecipientAvatarDownloadManager
14+
import java.io.File
15+
import javax.inject.Inject
16+
import javax.inject.Singleton
17+
18+
@Singleton
19+
class AvatarCacheCleaner @Inject constructor(
20+
private val application: Application,
21+
private val recipientAvatarDownloadManager: RecipientAvatarDownloadManager,
22+
private val recipientSettingsDatabase: RecipientSettingsDatabase,
23+
@param:ManagerScope private val coroutineScope: CoroutineScope
24+
) {
25+
26+
companion object {
27+
const val TAG = "AvatarCacheCleaner"
28+
}
29+
30+
/**
31+
* Deletes avatar files under cache/remote_files that are no longer referenced
32+
* in the current config. Returns number of files deleted.
33+
*/
34+
private suspend fun cleanUpAvatars(): Int = withContext(Dispatchers.IO) {
35+
// 1) Build the set of still-wanted Avatars from:
36+
// -> Config
37+
// -> Recipient Settings DB
38+
39+
// config
40+
val avatarsFromConfig: Set<RemoteFile> = recipientAvatarDownloadManager.getAllAvatars()
41+
// recipient_settings
42+
val recipientAvatars : Set<RemoteFile> = recipientSettingsDatabase.getAllReferencedAvatarFiles()
43+
44+
// 3) Union of everything we want to keep
45+
val filesToKeep: Set<RemoteFile> =
46+
(avatarsFromConfig + recipientAvatars).toSet()
47+
48+
// 4) Map to actual files (same hashing/location as downloader)
49+
val wantedFiles: Set<File> = filesToKeep
50+
.map { RemoteFileDownloadWorker.computeFileName(application, it) }
51+
.toSet()
52+
53+
// 5) Delete everything not wanted in cache/remote_files
54+
val files = RemoteFileDownloadWorker.listDownloadedFiles(application)
55+
var deleted = 0
56+
for (file in files) {
57+
if (file !in wantedFiles && file.delete()) deleted++
58+
}
59+
60+
deleted
61+
}
62+
63+
fun launchAvatarCleanup() {
64+
coroutineScope.launch(Dispatchers.IO) {
65+
val deleted = cleanUpAvatars()
66+
Log.d(TAG, "Avatar cache removed: $deleted files")
67+
}
68+
}
69+
}

app/src/main/java/org/session/libsession/utilities/ConfigFactoryProtocol.kt

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,52 @@ fun ConfigFactoryProtocol.userConfigsChanged(
151151
}
152152
}
153153

154+
/** All addresses that exist in config and therefore must be kept. */
155+
fun ConfigFactoryProtocol.allConfigAddresses(): Set<Address> {
156+
val (contacts, blinded, groups) = withUserConfigs { config ->
157+
Triple(config.contacts.all(), config.contacts.allBlinded(), config.userGroups.all())
158+
}
159+
160+
val contactsAddress : Set<Address> =
161+
contacts.asSequence().map { Address.fromSerialized(it.id) }.toSet()
162+
val blindedAddress : Set<Address> =
163+
blinded.asSequence().map { Address.fromSerialized(it.id) }.toSet()
164+
165+
val closedIds = mutableListOf<AccountId>()
166+
val groupAddresses: Set<Address> = buildSet {
167+
groups.forEach { groupInfo ->
168+
when (groupInfo) {
169+
is GroupInfo.LegacyGroupInfo -> {
170+
add(Address.LegacyGroup(groupInfo.accountId))
171+
groupInfo.members.keys.forEach { add(Address.fromSerialized(it)) }
172+
}
173+
is GroupInfo.ClosedGroupInfo -> {
174+
val groupId = AccountId(groupInfo.groupAccountId)
175+
closedIds += groupId
176+
add(Address.Group(groupId))
177+
}
178+
is GroupInfo.CommunityGroupInfo -> {
179+
add(Address.Community(groupInfo.community.baseUrl, groupInfo.community.room))
180+
}
181+
}
182+
}
183+
}
184+
185+
val closedMemberAddresses: Set<Address> = buildSet {
186+
closedIds.forEach { groupId ->
187+
withGroupConfigs(groupId) { config ->
188+
config.groupMembers.all().forEach { add(Address.fromSerialized(it.accountId())) }
189+
}
190+
}
191+
}
192+
193+
return buildSet {
194+
addAll(contactsAddress)
195+
addAll(blindedAddress)
196+
addAll(groupAddresses)
197+
addAll(closedMemberAddresses)
198+
}
199+
}
154200

155201
/**
156202
* Wait until all configs of given group are pushed to the server.

app/src/main/java/org/thoughtcrime/securesms/attachments/RemoteFileDownloadWorker.kt

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -261,6 +261,8 @@ class RemoteFileDownloadWorker @AssistedInject constructor(
261261
private const val ARG_COMMUNITY_ROOM_ID = "community_room_id"
262262
private const val ARG_COMMUNITY_FILE_ID = "community_file_id"
263263

264+
private const val SUBDIRECTORY = "remote_files"
265+
264266
private fun RemoteFile.sha256Hash(): String {
265267
val hash = MessageDigest.getInstance("SHA-256")
266268
when (this) {
@@ -278,9 +280,12 @@ class RemoteFileDownloadWorker @AssistedInject constructor(
278280
return hash.digest().toHexString()
279281
}
280282

283+
private fun downloadsDirectory(context: Context): File =
284+
File(context.cacheDir, SUBDIRECTORY)
285+
281286
// Deterministically get the file path for the given remote file.
282287
fun computeFileName(context: Context, remote: RemoteFile): File {
283-
return File(context.cacheDir, "remote_files/${remote.sha256Hash()}")
288+
return File(downloadsDirectory(context), remote.sha256Hash())
284289
}
285290

286291
fun cancelAll(context: Context) {
@@ -297,6 +302,12 @@ class RemoteFileDownloadWorker @AssistedInject constructor(
297302
)
298303
}
299304

305+
/** Returns all currently downloaded files (may be empty). */
306+
fun listDownloadedFiles(context: Context): List<File> {
307+
val directory = downloadsDirectory(context)
308+
return directory.listFiles()?.toList().orEmpty()
309+
}
310+
300311
/**
301312
* @param isOldAvatarOf used to indicate that this file is an avatar of a specific address. This
302313
* information is optional and only used for migration purposes (to move the avatar from

app/src/main/java/org/thoughtcrime/securesms/configs/ConfigToDatabaseSync.kt

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ import kotlinx.coroutines.flow.collectLatest
99
import kotlinx.coroutines.flow.combine
1010
import kotlinx.coroutines.flow.distinctUntilChanged
1111
import kotlinx.coroutines.flow.filterNotNull
12-
import kotlinx.coroutines.flow.first
1312
import kotlinx.coroutines.flow.flatMapLatest
1413
import kotlinx.coroutines.flow.map
1514
import kotlinx.coroutines.flow.onStart
@@ -19,6 +18,7 @@ import network.loki.messenger.R
1918
import network.loki.messenger.libsession_util.ReadableGroupInfoConfig
2019
import network.loki.messenger.libsession_util.util.Conversation
2120
import network.loki.messenger.libsession_util.util.UserPic
21+
import org.session.libsession.avatars.AvatarCacheCleaner
2222
import org.session.libsession.database.StorageProtocol
2323
import org.session.libsession.messaging.sending_receiving.notifications.MessageNotifier
2424
import org.session.libsession.messaging.sending_receiving.notifications.PushRegistryV1
@@ -30,6 +30,7 @@ import org.session.libsession.utilities.Address.Companion.fromSerialized
3030
import org.session.libsession.utilities.ConfigFactoryProtocol
3131
import org.session.libsession.utilities.TextSecurePreferences
3232
import org.session.libsession.utilities.UserConfigType
33+
import org.session.libsession.utilities.allConfigAddresses
3334
import org.session.libsession.utilities.getGroup
3435
import org.session.libsession.utilities.userConfigsChanged
3536
import org.session.libsignal.crypto.ecc.DjbECPrivateKey
@@ -45,6 +46,7 @@ import org.thoughtcrime.securesms.database.LokiMessageDatabase
4546
import org.thoughtcrime.securesms.database.LokiThreadDatabase
4647
import org.thoughtcrime.securesms.database.MmsDatabase
4748
import org.thoughtcrime.securesms.database.MmsSmsDatabase
49+
import org.thoughtcrime.securesms.database.RecipientSettingsDatabase
4850
import org.thoughtcrime.securesms.database.SmsDatabase
4951
import org.thoughtcrime.securesms.database.ThreadDatabase
5052
import org.thoughtcrime.securesms.database.model.MmsMessageRecord
@@ -83,6 +85,8 @@ class ConfigToDatabaseSync @Inject constructor(
8385
private val mmsSmsDatabase: MmsSmsDatabase,
8486
private val lokiMessageDatabase: LokiMessageDatabase,
8587
private val messageNotifier: MessageNotifier,
88+
private val recipientSettingsDatabase: RecipientSettingsDatabase,
89+
private val avatarCacheCleaner: AvatarCacheCleaner,
8690
@param:ManagerScope private val scope: CoroutineScope,
8791
) : OnAppStartupComponent {
8892
init {
@@ -148,6 +152,9 @@ class ConfigToDatabaseSync @Inject constructor(
148152
}
149153
}
150154
}
155+
156+
// Initiate cleanup in recipient_settings
157+
pruneRecipientSettingsAndAvatars()
151158
}
152159

153160
// If we created threads, we need to update the thread database with the creation date.
@@ -167,6 +174,20 @@ class ConfigToDatabaseSync @Inject constructor(
167174
}
168175
}
169176

177+
private fun pruneRecipientSettingsAndAvatars() {
178+
val addressesToKeep: Set<Address> = buildSet {
179+
addAll(configFactory.allConfigAddresses())
180+
addAll(mmsSmsDatabase.getAllReferencedAddresses())
181+
}
182+
183+
val removed = recipientSettingsDatabase.cleanupRecipientSettings(addressesToKeep)
184+
Log.d(TAG, "Recipient settings pruned: $removed orphan rows")
185+
186+
if (removed > 0) {
187+
avatarCacheCleaner.launchAvatarCleanup()
188+
}
189+
}
190+
170191
private fun deleteGroupData(address: Address.Group) {
171192
lokiAPIDatabase.clearLastMessageHashes(address.accountId.hexString)
172193
lokiAPIDatabase.clearReceivedMessageHashValues(address.accountId.hexString)

app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -356,7 +356,6 @@ class ConversationActivityV2 : ScreenLockActionBarActivity(), InputBarDelegate,
356356
private val adapter by lazy {
357357
val adapter = ConversationAdapter(
358358
this,
359-
null,
360359
storage.getLastSeen(viewModel.threadId),
361360
false,
362361
onItemPress = { message, position, view, event ->
@@ -367,7 +366,7 @@ class ConversationActivityV2 : ScreenLockActionBarActivity(), InputBarDelegate,
367366
},
368367
onItemLongPress = { message, position, view ->
369368
// long pressing message for blocked users should show unblock dialog
370-
if (viewModel.recipient.blocked == true) unblock()
369+
if (viewModel.recipient.blocked) unblock()
371370
else {
372371
if (!viewModel.isMessageRequestThread) {
373372
showConversationReaction(message, view)
@@ -384,6 +383,7 @@ class ConversationActivityV2 : ScreenLockActionBarActivity(), InputBarDelegate,
384383
downloadPendingAttachment = viewModel::downloadPendingAttachment,
385384
retryFailedAttachments = viewModel::retryFailedAttachments,
386385
glide = glide,
386+
threadRecipientProvider = viewModel::recipient,
387387
)
388388
adapter.visibleMessageViewDelegate = this
389389
adapter

0 commit comments

Comments
 (0)