forked from dsutanto/bChot-android
First Commit
This commit is contained in:
20
features/cachecleaner/api/build.gradle.kts
Normal file
20
features/cachecleaner/api/build.gradle.kts
Normal file
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2023, 2024 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
plugins {
|
||||
id("io.element.android-library")
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "io.element.android.features.cachecleaner.api"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(projects.libraries.architecture)
|
||||
implementation(libs.androidx.startup)
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2023-2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.features.cachecleaner.api
|
||||
|
||||
interface CacheCleaner {
|
||||
/**
|
||||
* Clear the cache subdirs holding temporarily decrypted content (such as media and voice messages).
|
||||
*
|
||||
* Will fail silently in case of errors while deleting the files.
|
||||
*/
|
||||
fun clearCache()
|
||||
}
|
||||
28
features/cachecleaner/impl/build.gradle.kts
Normal file
28
features/cachecleaner/impl/build.gradle.kts
Normal file
@@ -0,0 +1,28 @@
|
||||
import extension.setupDependencyInjection
|
||||
import extension.testCommonDependencies
|
||||
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2023, 2024 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
plugins {
|
||||
id("io.element.android-compose-library")
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "io.element.android.features.cachecleaner.impl"
|
||||
}
|
||||
|
||||
setupDependencyInjection()
|
||||
|
||||
dependencies {
|
||||
api(projects.features.cachecleaner.api)
|
||||
implementation(projects.libraries.core)
|
||||
implementation(projects.libraries.architecture)
|
||||
|
||||
testCommonDependencies(libs)
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2023-2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.features.cachecleaner.impl
|
||||
|
||||
import dev.zacsweers.metro.AppScope
|
||||
import dev.zacsweers.metro.ContributesTo
|
||||
import io.element.android.features.cachecleaner.api.CacheCleaner
|
||||
|
||||
@ContributesTo(AppScope::class)
|
||||
interface CacheCleanerBindings {
|
||||
fun cacheCleaner(): CacheCleaner
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2023-2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.features.cachecleaner.impl
|
||||
|
||||
import dev.zacsweers.metro.AppScope
|
||||
import dev.zacsweers.metro.ContributesBinding
|
||||
import io.element.android.features.cachecleaner.api.CacheCleaner
|
||||
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
|
||||
import io.element.android.libraries.core.extensions.runCatchingExceptions
|
||||
import io.element.android.libraries.di.CacheDirectory
|
||||
import io.element.android.libraries.di.annotations.AppCoroutineScope
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.launch
|
||||
import timber.log.Timber
|
||||
import java.io.File
|
||||
|
||||
/**
|
||||
* Default implementation of [CacheCleaner].
|
||||
*/
|
||||
@ContributesBinding(AppScope::class)
|
||||
class DefaultCacheCleaner(
|
||||
@AppCoroutineScope
|
||||
private val coroutineScope: CoroutineScope,
|
||||
private val dispatchers: CoroutineDispatchers,
|
||||
@CacheDirectory private val cacheDir: File,
|
||||
) : CacheCleaner {
|
||||
companion object {
|
||||
val SUBDIRS_TO_CLEANUP = listOf("temp/media", "temp/voice")
|
||||
}
|
||||
|
||||
override fun clearCache() {
|
||||
coroutineScope.launch(dispatchers.io) {
|
||||
runCatchingExceptions {
|
||||
SUBDIRS_TO_CLEANUP.forEach {
|
||||
File(cacheDir.path, it).apply {
|
||||
if (exists()) {
|
||||
if (!deleteRecursively()) error("Failed to delete recursively cache directory $this")
|
||||
}
|
||||
if (!mkdirs()) error("Failed to create cache directory $this")
|
||||
}
|
||||
}
|
||||
}.onFailure {
|
||||
Timber.e(it, "Failed to clear cache")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2023-2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.features.cachecleaner.impl
|
||||
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import io.element.android.tests.testutils.testCoroutineDispatchers
|
||||
import kotlinx.coroutines.test.TestScope
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.rules.TemporaryFolder
|
||||
import java.io.File
|
||||
|
||||
class DefaultCacheCleanerTest {
|
||||
@get:Rule
|
||||
val temporaryFolder = TemporaryFolder()
|
||||
|
||||
@Test
|
||||
fun `calling clearCache actually removes file in the SUBDIRS_TO_CLEANUP list`() = runTest {
|
||||
// Create temp subdirs and fill with 2 files each
|
||||
DefaultCacheCleaner.SUBDIRS_TO_CLEANUP.forEach {
|
||||
File(temporaryFolder.root, it).apply {
|
||||
mkdirs()
|
||||
File(this, "temp1").createNewFile()
|
||||
File(this, "temp2").createNewFile()
|
||||
}
|
||||
}
|
||||
|
||||
// Clear cache
|
||||
aCacheCleaner().clearCache()
|
||||
|
||||
// Check the files are gone but the sub dirs are not.
|
||||
DefaultCacheCleaner.SUBDIRS_TO_CLEANUP.forEach {
|
||||
File(temporaryFolder.root, it).apply {
|
||||
assertThat(exists()).isTrue()
|
||||
assertThat(isDirectory).isTrue()
|
||||
assertThat(listFiles()).isEmpty()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `clear cache fails silently`() = runTest {
|
||||
// Set cache dir as unreadable, unwritable and unexecutable so that the deletion fails.
|
||||
check(temporaryFolder.root.setReadable(false))
|
||||
check(temporaryFolder.root.setWritable(false))
|
||||
check(temporaryFolder.root.setExecutable(false))
|
||||
|
||||
aCacheCleaner().clearCache()
|
||||
}
|
||||
|
||||
private fun TestScope.aCacheCleaner() = DefaultCacheCleaner(
|
||||
coroutineScope = this,
|
||||
dispatchers = this.testCoroutineDispatchers(true),
|
||||
cacheDir = temporaryFolder.root,
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user