Marco Romano
10 months ago
committed by
GitHub
9 changed files with 282 additions and 1 deletions
@ -0,0 +1,29 @@
@@ -0,0 +1,29 @@
|
||||
/* |
||||
* Copyright (c) 2023 New Vector Ltd |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0 |
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
plugins { |
||||
id("io.element.android-library") |
||||
alias(libs.plugins.anvil) |
||||
} |
||||
|
||||
android { |
||||
namespace = "io.element.android.features.cachecleaner.api" |
||||
} |
||||
|
||||
dependencies { |
||||
implementation(projects.libraries.architecture) |
||||
implementation(libs.androidx.startup) |
||||
} |
@ -0,0 +1,26 @@
@@ -0,0 +1,26 @@
|
||||
/* |
||||
* Copyright (c) 2023 New Vector Ltd |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0 |
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
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() |
||||
} |
@ -0,0 +1,25 @@
@@ -0,0 +1,25 @@
|
||||
/* |
||||
* Copyright (c) 2023 New Vector Ltd |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0 |
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
package io.element.android.features.cachecleaner.api |
||||
|
||||
import com.squareup.anvil.annotations.ContributesTo |
||||
import io.element.android.libraries.di.AppScope |
||||
|
||||
@ContributesTo(AppScope::class) |
||||
interface CacheCleanerBindings { |
||||
fun cacheCleaner(): CacheCleaner |
||||
} |
@ -0,0 +1,29 @@
@@ -0,0 +1,29 @@
|
||||
/* |
||||
* Copyright (c) 2022 New Vector Ltd |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0 |
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
package io.element.android.features.cachecleaner.api |
||||
|
||||
import android.content.Context |
||||
import androidx.startup.Initializer |
||||
import io.element.android.libraries.architecture.bindings |
||||
|
||||
class CacheCleanerInitializer : Initializer<Unit> { |
||||
override fun create(context: Context) { |
||||
context.bindings<CacheCleanerBindings>().cacheCleaner().clearCache() |
||||
} |
||||
|
||||
override fun dependencies(): List<Class<out Initializer<*>>> = emptyList() |
||||
} |
@ -0,0 +1,41 @@
@@ -0,0 +1,41 @@
|
||||
/* |
||||
* Copyright (c) 2023 New Vector Ltd |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0 |
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
plugins { |
||||
id("io.element.android-compose-library") |
||||
alias(libs.plugins.anvil) |
||||
} |
||||
|
||||
android { |
||||
namespace = "io.element.android.features.cachecleaner.impl" |
||||
} |
||||
|
||||
anvil { |
||||
generateDaggerFactories.set(true) |
||||
} |
||||
|
||||
dependencies { |
||||
implementation(projects.anvilannotations) |
||||
anvil(projects.anvilcodegen) |
||||
api(projects.features.cachecleaner.api) |
||||
implementation(projects.libraries.core) |
||||
implementation(projects.libraries.architecture) |
||||
|
||||
testImplementation(libs.test.junit) |
||||
testImplementation(libs.coroutines.test) |
||||
testImplementation(libs.test.truth) |
||||
testImplementation(projects.tests.testutils) |
||||
} |
@ -0,0 +1,59 @@
@@ -0,0 +1,59 @@
|
||||
/* |
||||
* Copyright (c) 2023 New Vector Ltd |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0 |
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
package io.element.android.features.cachecleaner.impl |
||||
|
||||
import com.squareup.anvil.annotations.ContributesBinding |
||||
import io.element.android.features.cachecleaner.api.CacheCleaner |
||||
import io.element.android.libraries.core.coroutine.CoroutineDispatchers |
||||
import io.element.android.libraries.di.AppScope |
||||
import io.element.android.libraries.di.CacheDirectory |
||||
import kotlinx.coroutines.CoroutineScope |
||||
import kotlinx.coroutines.launch |
||||
import timber.log.Timber |
||||
import java.io.File |
||||
import javax.inject.Inject |
||||
|
||||
/** |
||||
* Default implementation of [CacheCleaner]. |
||||
*/ |
||||
@ContributesBinding(AppScope::class) |
||||
class DefaultCacheCleaner @Inject constructor( |
||||
private val scope: 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() { |
||||
scope.launch(dispatchers.io) { |
||||
runCatching { |
||||
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,71 @@
@@ -0,0 +1,71 @@
|
||||
/* |
||||
* Copyright (c) 2023 New Vector Ltd |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0 |
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
package io.element.android.features.cachecleaner.impl |
||||
|
||||
import com.google.common.truth.Truth |
||||
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 { |
||||
Truth.assertThat(exists()).isTrue() |
||||
Truth.assertThat(isDirectory).isTrue() |
||||
Truth.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( |
||||
scope = this, |
||||
dispatchers = this.testCoroutineDispatchers(true), |
||||
cacheDir = temporaryFolder.root, |
||||
) |
||||
} |
Loading…
Reference in new issue