From 9f152ea01d199a6f11d13c5981d4d3dd336bdc2c Mon Sep 17 00:00:00 2001 From: Mino260806 Date: Sun, 4 Sep 2022 23:41:21 +0100 Subject: [PATCH] Fix storage getting full while moving multiple items to the recycle bin at once --- .../gallery/pro/activities/MainActivity.kt | 7 +-- .../gallery/pro/activities/MediaActivity.kt | 7 +-- .../gallery/pro/activities/SearchActivity.kt | 7 +-- .../pro/activities/ViewPagerActivity.kt | 4 +- .../gallery/pro/extensions/Activity.kt | 44 +++++++++++++++++-- .../gallery/pro/extensions/Context.kt | 12 +++++ 6 files changed, 66 insertions(+), 15 deletions(-) diff --git a/app/src/main/kotlin/com/simplemobiletools/gallery/pro/activities/MainActivity.kt b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/activities/MainActivity.kt index fad2c47b8..c870b1ff2 100644 --- a/app/src/main/kotlin/com/simplemobiletools/gallery/pro/activities/MainActivity.kt +++ b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/activities/MainActivity.kt @@ -656,9 +656,10 @@ class MainActivity : SimpleActivity(), DirectoryOperationsListener { val pathsToDelete = ArrayList() itemsToDelete.mapTo(pathsToDelete) { it.path } - movePathsInRecycleBin(pathsToDelete) { - if (it) { - deleteFilteredFileDirItems(itemsToDelete, folders) + movePathsInRecycleBin(pathsToDelete) { wasSuccess, range -> + if (wasSuccess) { + val itemsInRange = itemsToDelete.subList(range.first, range.second) + deleteFilteredFileDirItems(ArrayList(itemsInRange), ArrayList()) } else { toast(R.string.unknown_error_occurred) } diff --git a/app/src/main/kotlin/com/simplemobiletools/gallery/pro/activities/MediaActivity.kt b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/activities/MediaActivity.kt index c2a21cda3..b656d525c 100644 --- a/app/src/main/kotlin/com/simplemobiletools/gallery/pro/activities/MediaActivity.kt +++ b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/activities/MediaActivity.kt @@ -887,9 +887,10 @@ class MediaActivity : SimpleActivity(), MediaOperationsListener { val movingItems = resources.getQuantityString(R.plurals.moving_items_into_bin, filtered.size, filtered.size) toast(movingItems) - movePathsInRecycleBin(filtered.map { it.path } as ArrayList) { - if (it) { - deleteFilteredFiles(filtered) + movePathsInRecycleBin(filtered.map { it.path } as ArrayList) { wasSuccess, range -> + if (wasSuccess) { + val itemsInRange = filtered.subList(range.first, range.second) + deleteFilteredFiles(ArrayList(itemsInRange)) } else { toast(R.string.unknown_error_occurred) } diff --git a/app/src/main/kotlin/com/simplemobiletools/gallery/pro/activities/SearchActivity.kt b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/activities/SearchActivity.kt index c261bd270..c44c7c633 100644 --- a/app/src/main/kotlin/com/simplemobiletools/gallery/pro/activities/SearchActivity.kt +++ b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/activities/SearchActivity.kt @@ -267,9 +267,10 @@ class SearchActivity : SimpleActivity(), MediaOperationsListener { val movingItems = resources.getQuantityString(R.plurals.moving_items_into_bin, filtered.size, filtered.size) toast(movingItems) - movePathsInRecycleBin(filtered.map { it.path } as ArrayList) { - if (it) { - deleteFilteredFiles(filtered) + movePathsInRecycleBin(filtered.map { it.path } as ArrayList) { wasSuccess, range -> + if (wasSuccess) { + val itemsInRange = filtered.subList(range.first, range.second) + deleteFilteredFiles(ArrayList(itemsInRange)) } else { toast(R.string.unknown_error_occurred) } diff --git a/app/src/main/kotlin/com/simplemobiletools/gallery/pro/activities/ViewPagerActivity.kt b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/activities/ViewPagerActivity.kt index a9d1df0fa..30b183e07 100644 --- a/app/src/main/kotlin/com/simplemobiletools/gallery/pro/activities/ViewPagerActivity.kt +++ b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/activities/ViewPagerActivity.kt @@ -1161,8 +1161,8 @@ class ViewPagerActivity : SimpleActivity(), ViewPager.OnPageChangeListener, View onPageSelected(0) } - movePathsInRecycleBin(arrayListOf(path)) { - if (it) { + movePathsInRecycleBin(arrayListOf(path)) { wasSuccess, _ -> + if (wasSuccess) { tryDeleteFileDirItem(fileDirItem, false, false) { mIgnoredPaths.remove(fileDirItem.path) if (media.isEmpty()) { diff --git a/app/src/main/kotlin/com/simplemobiletools/gallery/pro/extensions/Activity.kt b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/extensions/Activity.kt index 150325f48..cc5264ac5 100644 --- a/app/src/main/kotlin/com/simplemobiletools/gallery/pro/extensions/Activity.kt +++ b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/extensions/Activity.kt @@ -44,6 +44,7 @@ import com.squareup.picasso.Picasso import java.io.* import java.text.SimpleDateFormat import java.util.* +import kotlin.collections.ArrayList fun Activity.sharePath(path: String) { sharePathIntent(path, BuildConfig.APPLICATION_ID) @@ -313,18 +314,45 @@ fun BaseSimpleActivity.tryDeleteFileDirItem( } } -fun BaseSimpleActivity.movePathsInRecycleBin(paths: ArrayList, callback: ((wasSuccess: Boolean) -> Unit)?) { +fun BaseSimpleActivity.movePathsInRecycleBin(paths: ArrayList, callback: ((wasSuccess: Boolean, movedRange: Pair) -> Unit)?) { ensureBackgroundThread { var pathsCnt = paths.size val OTGPath = config.OTGPath - for (source in paths) { + var availableSize = getAvailableInternalMemorySize().toLong() + + // If available size is more than 200MB, keep a safe distance from + // exceeding the remaining size + if (availableSize > 1024 * 1024 * 200) + availableSize -= 1024 * 1024 * 50 + var totalCopiedSize = 0L + + var lastDeletedIndex = -1 + val ensureSizeIsAvailable = { index: Int, size: Long -> + // Try to delete already moved files if space if not sufficient + if (size <= availableSize && totalCopiedSize > 0 && totalCopiedSize + size > availableSize) { + // Return true in wasSuccess for now, callback will be called + // at the end of the function with the correct parameter + callback?.invoke(true, Pair(lastDeletedIndex + 1, index + 1)) + availableSize = getAvailableInternalMemorySize() + totalCopiedSize = 0L + lastDeletedIndex = index + } + + size <= availableSize + } + + for ((index, source) in paths.withIndex()) { if (OTGPath.isNotEmpty() && source.startsWith(OTGPath)) { var inputStream: InputStream? = null var out: OutputStream? = null try { val destination = "$recycleBinPath/$source" val fileDocument = getSomeDocumentFile(source) + val originalSize = fileDocument?.getItemSize(true)!! + if (!ensureSizeIsAvailable(index, originalSize)) + continue + inputStream = applicationContext.contentResolver.openInputStream(fileDocument?.uri!!) out = getFileOutputStreamSync(destination, source.getMimeType()) @@ -339,9 +367,11 @@ fun BaseSimpleActivity.movePathsInRecycleBin(paths: ArrayList, callback: out?.flush() - if (fileDocument.getItemSize(true) == copiedSize && getDoesFilePathExist(destination)) { + if (originalSize == copiedSize && getDoesFilePathExist(destination)) { mediaDB.updateDeleted("$RECYCLE_BIN$source", System.currentTimeMillis(), source) pathsCnt-- + + totalCopiedSize += copiedSize } } catch (e: Exception) { showErrorToast(e) @@ -352,6 +382,10 @@ fun BaseSimpleActivity.movePathsInRecycleBin(paths: ArrayList, callback: } } else { val file = File(source) + val originalSize = file.length() + if (!ensureSizeIsAvailable(index, originalSize)) + continue + val internalFile = File(recycleBinPath, source) val lastModified = file.lastModified() try { @@ -362,6 +396,8 @@ fun BaseSimpleActivity.movePathsInRecycleBin(paths: ArrayList, callback: if (config.keepLastModified && lastModified != 0L) { internalFile.setLastModified(lastModified) } + + totalCopiedSize += originalSize } } catch (e: Exception) { showErrorToast(e) @@ -369,7 +405,7 @@ fun BaseSimpleActivity.movePathsInRecycleBin(paths: ArrayList, callback: } } } - callback?.invoke(pathsCnt == 0) + callback?.invoke(pathsCnt == 0, Pair(lastDeletedIndex + 1, paths.size)) } } diff --git a/app/src/main/kotlin/com/simplemobiletools/gallery/pro/extensions/Context.kt b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/extensions/Context.kt index 774c1d90c..7e102d8e1 100644 --- a/app/src/main/kotlin/com/simplemobiletools/gallery/pro/extensions/Context.kt +++ b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/extensions/Context.kt @@ -8,7 +8,9 @@ import android.database.Cursor import android.graphics.Bitmap import android.graphics.drawable.PictureDrawable import android.media.AudioManager +import android.os.Environment import android.os.Process +import android.os.StatFs import android.provider.MediaStore.Files import android.provider.MediaStore.Images import android.widget.ImageView @@ -43,6 +45,7 @@ import java.nio.ByteBuffer import java.nio.channels.FileChannel import kotlin.collections.set + val Context.audioManager get() = getSystemService(Context.AUDIO_SERVICE) as AudioManager fun Context.getHumanizedFilename(path: String): String { @@ -1103,3 +1106,12 @@ fun Context.getFileDateTaken(path: String): Long { return 0L } + +// https://stackoverflow.com/questions/8133417/android-get-free-size-of-internal-external-memory +fun Context.getAvailableInternalMemorySize(): Long { + val path: File = filesDir + val stat = StatFs(path.path) + val blockSize = stat.blockSizeLong + val availableBlocks = stat.availableBlocksLong + return availableBlocks * blockSize +}