From ea97362f8214a1c6fee5eb276988a03fcfc70258 Mon Sep 17 00:00:00 2001 From: Vishal Nehra Date: Wed, 19 Jul 2023 01:20:09 +0530 Subject: [PATCH] Fix delete permanently logic; configure spotless --- .github/workflows/android-main.yml | 45 +++++++ build.gradle | 21 +++ spotless.license-java | 16 +++ trashbin/build.gradle | 31 +++++ .../amaze/trashbin/ExampleInstrumentedTest.kt | 24 +++- .../com/amaze/trashbin/SingletonHolder.kt | 22 +++- .../main/java/com/amaze/trashbin/TrashBin.kt | 124 ++++++++++++------ .../java/com/amaze/trashbin/TrashBinConfig.kt | 84 ++++-------- .../java/com/amaze/trashbin/TrashBinFile.kt | 30 ++++- .../com/amaze/trashbin/TrashBinMetadata.kt | 90 +++++++++++++ .../com/amaze/trashbin/ExampleUnitTest.kt | 21 ++- 11 files changed, 396 insertions(+), 112 deletions(-) create mode 100644 .github/workflows/android-main.yml create mode 100644 spotless.license-java create mode 100644 trashbin/src/main/java/com/amaze/trashbin/TrashBinMetadata.kt diff --git a/.github/workflows/android-main.yml b/.github/workflows/android-main.yml new file mode 100644 index 0000000..cb5d30e --- /dev/null +++ b/.github/workflows/android-main.yml @@ -0,0 +1,45 @@ +name: Android Main CI + +on: + push: + branches: + - 'main' + - 'release/*' + +concurrency: + group: build-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +jobs: + check_spotless: + name: Check spotless + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v3 + - name: Set up JDK 11 + uses: actions/setup-java@v3 + with: + distribution: "temurin" + java-version: 11 + - name: Check formatting using spotless + uses: gradle/gradle-build-action@v2.4.2 + with: + arguments: spotlessCheck + build: + name: Build debug + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v3 + with: + fetch-depth: 0 + - name: Set up JDK 11 + uses: actions/setup-java@v3 + with: + distribution: "temurin" + java-version: 11 + - name: Build with Gradle + uses: gradle/gradle-build-action@v2.4.2 + with: + arguments: build + env: + TZ: UTC \ No newline at end of file diff --git a/build.gradle b/build.gradle index 2536974..9775e14 100644 --- a/build.gradle +++ b/build.gradle @@ -3,4 +3,25 @@ plugins { id 'com.android.application' version '7.3.1' apply false id 'com.android.library' version '7.3.1' apply false id 'org.jetbrains.kotlin.android' version '1.7.20' apply false + id "com.diffplug.spotless" version "5.14.2" +} + +spotless { + java { + licenseHeaderFile 'spotless.license-java' + target 'trashbin/src/**/*.java' + googleJavaFormat('1.13.0') + removeUnusedImports() // removes any unused imports + importOrder 'java', 'javax', 'org', 'com', 'android', 'androidx', '' + } + kotlin { + licenseHeaderFile 'spotless.license-java' + target 'trashbin/src/**/*.kt' + ktlint("0.43.2").userData(['disabled_rules': 'no-wildcard-imports', + 'kotlin_imports_layout': 'idea', 'indent_size': '4', + 'max_line_length': '100']) + trimTrailingWhitespace() + indentWithSpaces() + endWithNewline() + } } \ No newline at end of file diff --git a/spotless.license-java b/spotless.license-java new file mode 100644 index 0000000..1eece3e --- /dev/null +++ b/spotless.license-java @@ -0,0 +1,16 @@ +/** + * Copyright 2023 Arpit Khurana , Vishal Nehra , + * Emmanuel Messulam, Raymond Lai and Contributors. + * + * 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. + */ diff --git a/trashbin/build.gradle b/trashbin/build.gradle index 57c2fe0..6dfa517 100644 --- a/trashbin/build.gradle +++ b/trashbin/build.gradle @@ -3,6 +3,8 @@ plugins { id 'org.jetbrains.kotlin.android' } +apply plugin: 'maven-publish' + android { namespace 'com.amaze.trashbin' compileSdk 33 @@ -39,4 +41,33 @@ dependencies { testImplementation 'junit:junit:4.13.2' androidTestImplementation 'androidx.test.ext:junit:1.1.5' androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1' +} + +afterEvaluate { + publishing { + publications { + release(MavenPublication) { + from components.release + pom { + groupId = 'com.amaze.trashbin' + artifactId = 'amaze-trash-bin' + licenses { + license { + name = 'Apache License' + url = 'http://www.apache.org/licenses/LICENSE-2.0' + } + } + developers { + developer { + id = 'vishalnehra' + name = 'Vishal Nehra' + email = 'vishalmeham2@gmail.com' + } + } + description = 'Library to manage your deleted files in a trash bin' + version = '1.0.0' + } + } + } + } } \ No newline at end of file diff --git a/trashbin/src/androidTest/java/com/amaze/trashbin/ExampleInstrumentedTest.kt b/trashbin/src/androidTest/java/com/amaze/trashbin/ExampleInstrumentedTest.kt index 7d0ea1c..577613e 100644 --- a/trashbin/src/androidTest/java/com/amaze/trashbin/ExampleInstrumentedTest.kt +++ b/trashbin/src/androidTest/java/com/amaze/trashbin/ExampleInstrumentedTest.kt @@ -1,13 +1,27 @@ +/** + * Copyright 2023 Arpit Khurana , Vishal Nehra , + * Emmanuel Messulam, Raymond Lai and Contributors. + * + * 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 com.amaze.trashbin -import androidx.test.platform.app.InstrumentationRegistry import androidx.test.ext.junit.runners.AndroidJUnit4 - +import androidx.test.platform.app.InstrumentationRegistry +import org.junit.Assert.* import org.junit.Test import org.junit.runner.RunWith -import org.junit.Assert.* - /** * Instrumented test, which will execute on an Android device. * @@ -21,4 +35,4 @@ class ExampleInstrumentedTest { val appContext = InstrumentationRegistry.getInstrumentation().targetContext assertEquals("com.amaze.trashbin.test", appContext.packageName) } -} \ No newline at end of file +} diff --git a/trashbin/src/main/java/com/amaze/trashbin/SingletonHolder.kt b/trashbin/src/main/java/com/amaze/trashbin/SingletonHolder.kt index 3681627..fcf8d6b 100644 --- a/trashbin/src/main/java/com/amaze/trashbin/SingletonHolder.kt +++ b/trashbin/src/main/java/com/amaze/trashbin/SingletonHolder.kt @@ -1,6 +1,24 @@ +/** + * Copyright 2023 Arpit Khurana , Vishal Nehra , + * Emmanuel Messulam, Raymond Lai and Contributors. + * + * 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 com.amaze.trashbin -open class SingletonSingleArgHolder(private val constructor: (A, B, C) -> T) { +open class SingletonSingleArgHolder( + private val constructor: (A, B, C) -> T +) { @Volatile private var instance: T? = null @@ -9,4 +27,4 @@ open class SingletonSingleArgHolder(private val constru instance ?: synchronized(this) { instance ?: constructor(arg1, arg2, arg3).also { instance = it } } -} \ No newline at end of file +} diff --git a/trashbin/src/main/java/com/amaze/trashbin/TrashBin.kt b/trashbin/src/main/java/com/amaze/trashbin/TrashBin.kt index 161e68c..782c094 100644 --- a/trashbin/src/main/java/com/amaze/trashbin/TrashBin.kt +++ b/trashbin/src/main/java/com/amaze/trashbin/TrashBin.kt @@ -1,3 +1,19 @@ +/** + * Copyright 2023 Arpit Khurana , Vishal Nehra , + * Emmanuel Messulam, Raymond Lai and Contributors. + * + * 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 com.amaze.trashbin import android.util.Log @@ -19,24 +35,29 @@ typealias ListTrashBinFilesCallback = suspend (parentTrashBinPath: String) -> Li * @param deletePermanentlyCallback - callback to invoke for cleanup automatically when any bin action is performed. * Pass null if you want to invoke cleanup manually through triggerCleanup. This will be executed in the same thread where your bin functions are executed. */ -class TrashBin private constructor(var trashConfig: TrashBinConfig, - var deletePermanentlyCallback: DeletePermanentlyCallback?, - var listTrashBinFilesCallback: ListTrashBinFilesCallback? = null) { +class TrashBin private constructor( + var trashConfig: TrashBinConfig, + var deletePermanentlyCallback: DeletePermanentlyCallback?, + var listTrashBinFilesCallback: + ListTrashBinFilesCallback? = null +) { private var metadata: TrashBinMetadata? = null companion object : SingletonSingleArgHolder(::TrashBin) + ListTrashBinFilesCallback>(::TrashBin) init { trashConfig.getTrashBinFilesDirectory() metadata = getTrashBinMetadata() } - suspend fun deletePermanently(files: List, - deletePermanentlyCallback: DeletePermanentlyCallback, - doTriggerCleanup: Boolean = true): Boolean { + suspend fun deletePermanently( + files: List, + deletePermanentlyCallback: DeletePermanentlyCallback, + doTriggerCleanup: Boolean = true + ): Boolean { if (files.isEmpty()) { Log.i(javaClass.simpleName, "Empty files list to delete permanently") return true @@ -44,20 +65,29 @@ class TrashBin private constructor(var trashConfig: TrashBinConfig, var totalSize = 0L val filesMetadata = ArrayList(getTrashBinMetadata().files) files.forEach { - val didDelete = deletePermanentlyCallback.invoke(it.getDeletedPath(trashConfig)) - if (didDelete) { - var indexToRemove = -1 - for (i in filesMetadata.indices) { - if (it.path == filesMetadata[i].path) { - indexToRemove = i - break - } + // try to find file in metadata + var indexToRemove = -1 + for (i in filesMetadata.indices) { + if (it.path == filesMetadata[i].path) { + indexToRemove = i + break } - if (indexToRemove != -1) { + } + if (indexToRemove != -1) { + // found file in metadata, call delete with trash bin path + val didDelete = deletePermanentlyCallback.invoke(it.getDeletedPath(trashConfig)) + if (didDelete) { filesMetadata.removeAt(indexToRemove) + Log.w( + javaClass.simpleName, + "TrashBin: deleting file in trashbin " + + it.path + ) } } else { - Log.w(javaClass.simpleName, "Failed to delete from bin " + it.path) + // file not found in metadata, call delete on original file + deletePermanentlyCallback.invoke(it.path) + Log.w(javaClass.simpleName, "TrashBin: deleting original file " + it.path) } } @@ -65,14 +95,18 @@ class TrashBin private constructor(var trashConfig: TrashBinConfig, totalSize += it.sizeBytes } filesMetadata.sortedBy { - trashBinFile -> + trashBinFile -> trashBinFile.deleteTime?.times(-1) } writeMetadataAndTriggerCleanup(filesMetadata, totalSize, doTriggerCleanup) return true } - suspend fun moveToBin(files: List, doTriggerCleanup: Boolean = true, moveFilesCallback: MoveFilesCallback): Boolean { + suspend fun moveToBin( + files: List, + doTriggerCleanup: Boolean = true, + moveFilesCallback: MoveFilesCallback + ): Boolean { if (files.isEmpty()) { Log.i(javaClass.simpleName, "Empty files list to move to bin") return true @@ -89,15 +123,18 @@ class TrashBin private constructor(var trashConfig: TrashBinConfig, } } filesMetadata.sortBy { - trashBinFile -> + trashBinFile -> trashBinFile.deleteTime } writeMetadataAndTriggerCleanup(filesMetadata, totalSize, doTriggerCleanup) return true } - suspend fun restore(files: List, doTriggerCleanup: Boolean = true, - moveFilesCallback: MoveFilesCallback): Boolean { + suspend fun restore( + files: List, + doTriggerCleanup: Boolean = true, + moveFilesCallback: MoveFilesCallback + ): Boolean { if (files.isEmpty()) { Log.i(javaClass.simpleName, "Empty files list to restore") return true @@ -125,7 +162,7 @@ class TrashBin private constructor(var trashConfig: TrashBinConfig, totalSize += it.sizeBytes } filesMetadata.sortedBy { - trashBinFile -> + trashBinFile -> trashBinFile.deleteTime?.times(-1) } writeMetadataAndTriggerCleanup(filesMetadata, totalSize, doTriggerCleanup) @@ -133,13 +170,16 @@ class TrashBin private constructor(var trashConfig: TrashBinConfig, } suspend fun emptyBin(deletePermanentlyCallback: DeletePermanentlyCallback): Boolean { - return deletePermanently(metadata?.files ?: emptyList(), deletePermanentlyCallback, true) + return deletePermanently( + metadata?.files ?: emptyList(), + deletePermanentlyCallback, true + ) } suspend fun restoreBin(moveFilesCallback: MoveFilesCallback): Boolean { return restore(metadata?.files ?: emptyList(), true, moveFilesCallback) } - + fun listFilesInBin(): List { return getTrashBinMetadata().files } @@ -194,18 +234,20 @@ class TrashBin private constructor(var trashConfig: TrashBinConfig, return true } - /** * impacts performance, removes physical file if not present in metadata, * or removes from metadata if physical file not present */ - suspend fun removeRogueFiles(files: List, - listTrashBinFilesCallback: ListTrashBinFilesCallback, - deletePermanentlyCallback: DeletePermanentlyCallback): Boolean { + suspend fun removeRogueFiles( + files: List, + listTrashBinFilesCallback: ListTrashBinFilesCallback, + deletePermanentlyCallback: + DeletePermanentlyCallback + ): Boolean { val physicalFilesList = listTrashBinFilesCallback .invoke(trashConfig.getTrashBinFilesDirectory()) if (physicalFilesList.size > files.size) { - for(i in physicalFilesList.indices) { + for (i in physicalFilesList.indices) { var foundPhysicalFile = false for (j in files.indices) { if (physicalFilesList[i].path == files[j].path) { @@ -214,12 +256,15 @@ class TrashBin private constructor(var trashConfig: TrashBinConfig, } } if (!foundPhysicalFile) { - deletePermanently(listOf(physicalFilesList[i]), deletePermanentlyCallback, false) + deletePermanently( + listOf(physicalFilesList[i]), deletePermanentlyCallback, + false + ) } } } else { val mutableMetaFiles = ArrayList(files) - for(i in mutableMetaFiles.indices) { + for (i in mutableMetaFiles.indices) { var foundFileMetadata = false for (j in physicalFilesList.indices) { if (physicalFilesList[i].path == mutableMetaFiles[j].path) { @@ -237,14 +282,17 @@ class TrashBin private constructor(var trashConfig: TrashBinConfig, return true } - - private suspend fun writeMetadataAndTriggerCleanup(files: List, totalSize: Long, - doTriggerCleanup: Boolean = true) { + private suspend fun writeMetadataAndTriggerCleanup( + files: List, + totalSize: Long, + doTriggerCleanup: Boolean = true + ) { metadata?.config = trashConfig metadata?.files = files metadata?.totalSize = totalSize - if (trashConfig.deleteRogueFiles && listTrashBinFilesCallback != null - && deletePermanentlyCallback != null) { + if (trashConfig.deleteRogueFiles && listTrashBinFilesCallback != null && + deletePermanentlyCallback != null + ) { // trigger rogue file deletion removeRogueFiles(files, listTrashBinFilesCallback!!, deletePermanentlyCallback!!) } else { @@ -262,4 +310,4 @@ class TrashBin private constructor(var trashConfig: TrashBinConfig, gson.toJson(meta, writer) } } -} \ No newline at end of file +} diff --git a/trashbin/src/main/java/com/amaze/trashbin/TrashBinConfig.kt b/trashbin/src/main/java/com/amaze/trashbin/TrashBinConfig.kt index f2d8492..67da0e9 100644 --- a/trashbin/src/main/java/com/amaze/trashbin/TrashBinConfig.kt +++ b/trashbin/src/main/java/com/amaze/trashbin/TrashBinConfig.kt @@ -1,9 +1,22 @@ +/** + * Copyright 2023 Arpit Khurana , Vishal Nehra , + * Emmanuel Messulam, Raymond Lai and Contributors. + * + * 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 com.amaze.trashbin -import android.os.Build import java.io.File -import java.time.LocalDateTime -import java.time.ZoneOffset /** * path to store trash files and metadata. eg /storage/ID/.demo/TrashBinFiles @@ -11,9 +24,14 @@ import java.time.ZoneOffset * triggerCleanupAutomatically - library automatically triggers a cleanup after every delete / move operation * setting this false means you're responsible to trigger the cleanup at your own discretion */ -data class TrashBinConfig(val basePath: String, val retentionDays: Int, val retentionBytes: Long, - val retentionNumOfFiles: Int, - val deleteRogueFiles: Boolean, val triggerCleanupAutomatically: Boolean) { +data class TrashBinConfig( + val basePath: String, + val retentionDays: Int, + val retentionBytes: Long, + val retentionNumOfFiles: Int, + val deleteRogueFiles: Boolean, + val triggerCleanupAutomatically: Boolean +) { companion object { const val RETENTION_DAYS_INFINITE = -1 @@ -40,57 +58,3 @@ data class TrashBinConfig(val basePath: String, val retentionDays: Int, val rete return basePath + File.separator + TRASH_BIN_META_FILE } } - -data class TrashBinMetadata(var config: TrashBinConfig, var totalSize: Long, var files: List) { - - /** - * Returns percent of trash bin memory used - */ - fun getCapacity(): Int { - val numOfFiles = files.size - val totalBytes = totalSize - var capacityNumOfFiles = 0 - var capacityBytes = 0 - if (config.retentionNumOfFiles != TrashBinConfig.RETENTION_NUM_OF_FILES) { - capacityNumOfFiles = (numOfFiles / config.retentionNumOfFiles) * 100 - } - if (config.retentionBytes != TrashBinConfig.RETENTION_BYTES_INFINITE) { - capacityBytes = ((totalBytes / config.retentionBytes) * 100).toInt() - } - return if (capacityBytes > capacityNumOfFiles) { - capacityBytes - } else if (capacityNumOfFiles > capacityBytes) { - capacityBytes - } else { - TrashBinConfig.TRASH_BIN_CAPACITY_INVALID - } - } - - fun getFilesWithDeletionCriteria(): List { - var totalBytes = totalSize - var numOfFiles = files.size - return files.sortedBy { it.deleteTime }.filter { - file -> - if (config.retentionNumOfFiles != TrashBinConfig.RETENTION_NUM_OF_FILES && numOfFiles > config.retentionNumOfFiles) { - numOfFiles-- - return@filter true - } - if (config.retentionBytes != TrashBinConfig.RETENTION_BYTES_INFINITE && totalBytes > config.retentionBytes) { - totalBytes -= file.sizeBytes - return@filter true - } - if (config.retentionDays != TrashBinConfig.RETENTION_DAYS_INFINITE) { - return@filter if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - LocalDateTime.ofEpochSecond(file.deleteTime ?: (System.currentTimeMillis()/1000), 0, ZoneOffset.UTC).plusDays( - config.retentionDays.toLong() - ).isBefore(LocalDateTime.now()) - } else { - val secondsToAdd: Long = (config.retentionDays * 24 * 60 * 60).toLong() - val newEpochSeconds: Long = (file.deleteTime ?: (System.currentTimeMillis() / 1000)) + secondsToAdd - newEpochSeconds < System.currentTimeMillis()/1000 - } - } - return@filter false - } - } -} \ No newline at end of file diff --git a/trashbin/src/main/java/com/amaze/trashbin/TrashBinFile.kt b/trashbin/src/main/java/com/amaze/trashbin/TrashBinFile.kt index 6ed17bb..9312ad3 100644 --- a/trashbin/src/main/java/com/amaze/trashbin/TrashBinFile.kt +++ b/trashbin/src/main/java/com/amaze/trashbin/TrashBinFile.kt @@ -1,3 +1,19 @@ +/** + * Copyright 2023 Arpit Khurana , Vishal Nehra , + * Emmanuel Messulam, Raymond Lai and Contributors. + * + * 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 com.amaze.trashbin import android.os.Build @@ -12,8 +28,13 @@ import java.time.ZoneOffset * @param sizeBytes size of file * @param deleteTime time of deletion, provide custom or initialized by default implementation to current time */ -data class TrashBinFile(val fileName: String, val isDirectory: Boolean, val path: String, val sizeBytes: Long, - var deleteTime: Long? = null) { +data class TrashBinFile( + val fileName: String, + val isDirectory: Boolean, + val path: String, + val sizeBytes: Long, + var deleteTime: Long? = null +) { init { if (deleteTime == null) { @@ -26,6 +47,7 @@ data class TrashBinFile(val fileName: String, val isDirectory: Boolean, val path } fun getDeletedPath(config: TrashBinConfig): String { - return config.basePath + File.separator + TrashBinConfig.TRASH_BIN_DIR + File.separator + fileName + return config.basePath + File.separator + TrashBinConfig.TRASH_BIN_DIR + + File.separator + fileName } -} \ No newline at end of file +} diff --git a/trashbin/src/main/java/com/amaze/trashbin/TrashBinMetadata.kt b/trashbin/src/main/java/com/amaze/trashbin/TrashBinMetadata.kt new file mode 100644 index 0000000..3ca73d0 --- /dev/null +++ b/trashbin/src/main/java/com/amaze/trashbin/TrashBinMetadata.kt @@ -0,0 +1,90 @@ +/** + * Copyright 2023 Arpit Khurana , Vishal Nehra , + * Emmanuel Messulam, Raymond Lai and Contributors. + * + * 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 com.amaze.trashbin + +import android.os.Build +import java.time.LocalDateTime +import java.time.ZoneOffset + +data class TrashBinMetadata( + var config: TrashBinConfig, + var totalSize: Long, + var files: List +) { + + /** + * Returns percent of trash bin memory used + */ + fun getCapacity(): Int { + val numOfFiles = files.size + val totalBytes = totalSize + var capacityNumOfFiles = 0 + var capacityBytes = 0 + if (config.retentionNumOfFiles != TrashBinConfig.RETENTION_NUM_OF_FILES) { + capacityNumOfFiles = (numOfFiles / config.retentionNumOfFiles) * 100 + } + if (config.retentionBytes != TrashBinConfig.RETENTION_BYTES_INFINITE) { + capacityBytes = ((totalBytes / config.retentionBytes) * 100).toInt() + } + return if (capacityBytes > capacityNumOfFiles) { + capacityBytes + } else if (capacityNumOfFiles > capacityBytes) { + capacityBytes + } else { + TrashBinConfig.TRASH_BIN_CAPACITY_INVALID + } + } + + fun getFilesWithDeletionCriteria(): List { + var totalBytes = totalSize + var numOfFiles = files.size + return files.sortedBy { it.deleteTime }.filter { + file -> + if (config.retentionNumOfFiles != TrashBinConfig.RETENTION_NUM_OF_FILES && + numOfFiles > config.retentionNumOfFiles + ) { + numOfFiles-- + return@filter true + } + if (config.retentionBytes != TrashBinConfig.RETENTION_BYTES_INFINITE && + totalBytes > config.retentionBytes + ) { + totalBytes -= file.sizeBytes + return@filter true + } + if (config.retentionDays != TrashBinConfig.RETENTION_DAYS_INFINITE) { + return@filter if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + LocalDateTime.ofEpochSecond( + file.deleteTime + ?: (System.currentTimeMillis() / 1000), + 0, ZoneOffset.UTC + ).plusDays( + config.retentionDays.toLong() + ).isBefore(LocalDateTime.now()) + } else { + val secondsToAdd: Long = (config.retentionDays * 24 * 60 * 60).toLong() + val newEpochSeconds: Long = ( + file.deleteTime + ?: (System.currentTimeMillis() / 1000) + ) + secondsToAdd + newEpochSeconds < System.currentTimeMillis() / 1000 + } + } + return@filter false + } + } +} diff --git a/trashbin/src/test/java/com/amaze/trashbin/ExampleUnitTest.kt b/trashbin/src/test/java/com/amaze/trashbin/ExampleUnitTest.kt index 9714271..c5a9fa4 100644 --- a/trashbin/src/test/java/com/amaze/trashbin/ExampleUnitTest.kt +++ b/trashbin/src/test/java/com/amaze/trashbin/ExampleUnitTest.kt @@ -1,8 +1,23 @@ +/** + * Copyright 2023 Arpit Khurana , Vishal Nehra , + * Emmanuel Messulam, Raymond Lai and Contributors. + * + * 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 com.amaze.trashbin -import org.junit.Test - import org.junit.Assert.* +import org.junit.Test /** * Example local unit test, which will execute on the development machine (host). @@ -14,4 +29,4 @@ class ExampleUnitTest { fun addition_isCorrect() { assertEquals(4, 2 + 2) } -} \ No newline at end of file +}