Add cancellation to binary uploading

This commit is contained in:
J-Jamet
2021-02-10 17:28:00 +01:00
parent 2e5ce5e94f
commit 751392d656
8 changed files with 105 additions and 51 deletions

View File

@@ -221,7 +221,9 @@ class EntryActivity : LockingActivity() {
registerProgressTask() registerProgressTask()
onActionTaskListener = object : AttachmentFileNotificationService.ActionTaskListener { onActionTaskListener = object : AttachmentFileNotificationService.ActionTaskListener {
override fun onAttachmentAction(fileUri: Uri, entryAttachmentState: EntryAttachmentState) { override fun onAttachmentAction(fileUri: Uri, entryAttachmentState: EntryAttachmentState) {
entryContentsView?.putAttachment(entryAttachmentState) if (entryAttachmentState.streamDirection != StreamDirection.UPLOAD) {
entryContentsView?.putAttachment(entryAttachmentState)
}
} }
} }
} }

View File

@@ -372,6 +372,9 @@ class EntryEditActivity : LockingActivity(),
} }
} }
} }
AttachmentState.CANCELED -> {
entryEditFragment?.removeAttachment(entryAttachmentState)
}
AttachmentState.ERROR -> { AttachmentState.ERROR -> {
entryEditFragment?.removeAttachment(entryAttachmentState) entryEditFragment?.removeAttachment(entryAttachmentState)
coordinatorLayout?.let { coordinatorLayout?.let {
@@ -525,6 +528,7 @@ class EntryEditActivity : LockingActivity(),
* Saves the new entry or update an existing entry in the database * Saves the new entry or update an existing entry in the database
*/ */
private fun saveEntry() { private fun saveEntry() {
mAttachmentFileBinderManager?.stopUploadAllAttachments()
// Get the temp entry // Get the temp entry
entryEditFragment?.getEntryInfo()?.let { newEntryInfo -> entryEditFragment?.getEntryInfo()?.let { newEntryInfo ->
@@ -542,6 +546,7 @@ class EntryEditActivity : LockingActivity(),
when (attachmentState.downloadState) { when (attachmentState.downloadState) {
AttachmentState.START, AttachmentState.START,
AttachmentState.IN_PROGRESS, AttachmentState.IN_PROGRESS,
AttachmentState.CANCELED,
AttachmentState.ERROR -> { AttachmentState.ERROR -> {
// Remove attachment not finished from info // Remove attachment not finished from info
newEntryInfo.attachments = newEntryInfo.attachments.toMutableList().apply { newEntryInfo.attachments = newEntryInfo.attachments.toMutableList().apply {
@@ -785,6 +790,7 @@ class EntryEditActivity : LockingActivity(),
.setMessage(R.string.discard_changes) .setMessage(R.string.discard_changes)
.setNegativeButton(android.R.string.cancel, null) .setNegativeButton(android.R.string.cancel, null)
.setPositiveButton(R.string.discard) { _, _ -> .setPositiveButton(R.string.discard) { _, _ ->
mAttachmentFileBinderManager?.stopUploadAllAttachments()
backPressedAlreadyApproved = true backPressedAlreadyApproved = true
approved.invoke() approved.invoke()
}.create().show() }.create().show()

View File

@@ -142,6 +142,7 @@ class EntryAttachmentsItemsAdapter(context: Context)
} }
AttachmentState.NULL, AttachmentState.NULL,
AttachmentState.ERROR, AttachmentState.ERROR,
AttachmentState.CANCELED,
AttachmentState.COMPLETE -> { AttachmentState.COMPLETE -> {
holder.binaryFileProgressContainer.visibility = View.GONE holder.binaryFileProgressContainer.visibility = View.GONE
holder.binaryFileProgress.visibility = View.GONE holder.binaryFileProgress.visibility = View.GONE
@@ -159,8 +160,13 @@ class EntryAttachmentsItemsAdapter(context: Context)
holder.binaryFileDeleteButton.visibility = View.GONE holder.binaryFileDeleteButton.visibility = View.GONE
holder.binaryFileProgress.apply { holder.binaryFileProgress.apply {
visibility = when (entryAttachmentState.downloadState) { visibility = when (entryAttachmentState.downloadState) {
AttachmentState.NULL, AttachmentState.COMPLETE, AttachmentState.ERROR -> View.GONE AttachmentState.NULL,
AttachmentState.START, AttachmentState.IN_PROGRESS -> View.VISIBLE AttachmentState.COMPLETE,
AttachmentState.CANCELED,
AttachmentState.ERROR -> View.GONE
AttachmentState.START,
AttachmentState.IN_PROGRESS -> View.VISIBLE
} }
progress = entryAttachmentState.downloadProgression progress = entryAttachmentState.downloadProgression
} }

View File

@@ -76,5 +76,5 @@ data class EntryAttachmentState(var attachment: Attachment,
} }
enum class AttachmentState { enum class AttachmentState {
NULL, START, IN_PROGRESS, COMPLETE, ERROR NULL, START, IN_PROGRESS, COMPLETE, CANCELED, ERROR
} }

View File

@@ -35,10 +35,8 @@ import com.kunzisoft.keepass.model.AttachmentState
import com.kunzisoft.keepass.model.EntryAttachmentState import com.kunzisoft.keepass.model.EntryAttachmentState
import com.kunzisoft.keepass.model.StreamDirection import com.kunzisoft.keepass.model.StreamDirection
import com.kunzisoft.keepass.stream.readAllBytes import com.kunzisoft.keepass.stream.readAllBytes
import com.kunzisoft.keepass.timeout.TimeoutHelper
import com.kunzisoft.keepass.utils.UriUtil import com.kunzisoft.keepass.utils.UriUtil
import kotlinx.coroutines.* import kotlinx.coroutines.*
import java.io.BufferedInputStream
import java.util.* import java.util.*
import java.util.concurrent.CopyOnWriteArrayList import java.util.concurrent.CopyOnWriteArrayList
@@ -101,12 +99,15 @@ class AttachmentFileNotificationService: LockNotificationService() {
when(intent?.action) { when(intent?.action) {
ACTION_ATTACHMENT_FILE_START_UPLOAD -> { ACTION_ATTACHMENT_FILE_START_UPLOAD -> {
actionUploadOrDownload(downloadFileUri, actionStartUploadOrDownload(downloadFileUri,
intent, intent,
StreamDirection.UPLOAD) StreamDirection.UPLOAD)
} }
ACTION_ATTACHMENT_FILE_STOP_UPLOAD -> {
actionStopUpload()
}
ACTION_ATTACHMENT_FILE_START_DOWNLOAD -> { ACTION_ATTACHMENT_FILE_START_DOWNLOAD -> {
actionUploadOrDownload(downloadFileUri, actionStartUploadOrDownload(downloadFileUri,
intent, intent,
StreamDirection.DOWNLOAD) StreamDirection.DOWNLOAD)
} }
@@ -216,15 +217,22 @@ class AttachmentFileNotificationService: LockNotificationService() {
setDeleteIntent(pendingDeleteIntent) setDeleteIntent(pendingDeleteIntent)
setOngoing(false) setOngoing(false)
} }
AttachmentState.CANCELED -> {
setContentText(getString(R.string.download_canceled))
setDeleteIntent(pendingDeleteIntent)
setOngoing(false)
}
AttachmentState.ERROR -> { AttachmentState.ERROR -> {
setContentText(getString(R.string.error_file_not_create)) setContentText(getString(R.string.error_file_not_create))
setDeleteIntent(pendingDeleteIntent)
setOngoing(false) setOngoing(false)
} }
} }
} }
when (attachmentNotification.entryAttachmentState.downloadState) { when (attachmentNotification.entryAttachmentState.downloadState) {
AttachmentState.ERROR, AttachmentState.COMPLETE,
AttachmentState.COMPLETE -> { AttachmentState.CANCELED,
AttachmentState.ERROR -> {
stopForeground(false) stopForeground(false)
notificationManager?.notify(attachmentNotification.notificationId, builder.build()) notificationManager?.notify(attachmentNotification.notificationId, builder.build())
} else -> { } else -> {
@@ -235,6 +243,7 @@ class AttachmentFileNotificationService: LockNotificationService() {
override fun onDestroy() { override fun onDestroy() {
attachmentNotificationList.forEach { attachmentNotification -> attachmentNotificationList.forEach { attachmentNotification ->
attachmentNotification.attachmentFileAction?.cancel()
attachmentNotification.attachmentFileAction?.listener = null attachmentNotification.attachmentFileAction?.listener = null
notificationManager?.cancel(attachmentNotification.notificationId) notificationManager?.cancel(attachmentNotification.notificationId)
} }
@@ -263,10 +272,10 @@ class AttachmentFileNotificationService: LockNotificationService() {
} }
} }
private fun actionUploadOrDownload(downloadFileUri: Uri?, private fun actionStartUploadOrDownload(fileUri: Uri?,
intent: Intent, intent: Intent,
streamDirection: StreamDirection) { streamDirection: StreamDirection) {
if (downloadFileUri != null if (fileUri != null
&& intent.hasExtra(ATTACHMENT_KEY)) { && intent.hasExtra(ATTACHMENT_KEY)) {
try { try {
intent.getParcelableExtra<Attachment>(ATTACHMENT_KEY)?.let { entryAttachment -> intent.getParcelableExtra<Attachment>(ATTACHMENT_KEY)?.let { entryAttachment ->
@@ -274,7 +283,7 @@ class AttachmentFileNotificationService: LockNotificationService() {
val nextNotificationId = (attachmentNotificationList.maxByOrNull { it.notificationId } val nextNotificationId = (attachmentNotificationList.maxByOrNull { it.notificationId }
?.notificationId ?: notificationId) + 1 ?.notificationId ?: notificationId) + 1
val entryAttachmentState = EntryAttachmentState(entryAttachment, streamDirection) val entryAttachmentState = EntryAttachmentState(entryAttachment, streamDirection)
val attachmentNotification = AttachmentNotification(downloadFileUri, nextNotificationId, entryAttachmentState) val attachmentNotification = AttachmentNotification(fileUri, nextNotificationId, entryAttachmentState)
// Add action to the list on start // Add action to the list on start
attachmentNotificationList.add(attachmentNotification) attachmentNotificationList.add(attachmentNotification)
@@ -287,11 +296,24 @@ class AttachmentFileNotificationService: LockNotificationService() {
} }
} }
} catch (e: Exception) { } catch (e: Exception) {
Log.e(TAG, "Unable to upload/download $downloadFileUri", e) Log.e(TAG, "Unable to upload/download $fileUri", e)
} }
} }
} }
private fun actionStopUpload() {
try {
// Stop each upload
attachmentNotificationList.filter {
it.entryAttachmentState.streamDirection == StreamDirection.UPLOAD
}.forEach {
it.attachmentFileAction?.cancel()
}
} catch (e: Exception) {
Log.e(TAG, "Unable to stop upload", e)
}
}
private class AttachmentFileAction( private class AttachmentFileAction(
private val attachmentNotification: AttachmentNotification, private val attachmentNotification: AttachmentNotification,
private val contentResolver: ContentResolver) { private val contentResolver: ContentResolver) {
@@ -308,8 +330,6 @@ class AttachmentFileNotificationService: LockNotificationService() {
// on pre execute // on pre execute
CoroutineScope(Dispatchers.Main).launch { CoroutineScope(Dispatchers.Main).launch {
TimeoutHelper.temporarilyDisableTimeout()
attachmentNotification.attachmentFileAction = this@AttachmentFileAction attachmentNotification.attachmentFileAction = this@AttachmentFileAction
attachmentNotification.entryAttachmentState.apply { attachmentNotification.entryAttachmentState.apply {
downloadState = AttachmentState.START downloadState = AttachmentState.START
@@ -320,53 +340,62 @@ class AttachmentFileNotificationService: LockNotificationService() {
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
// on Progress with thread // on Progress with thread
val asyncResult: Deferred<Boolean> = async { val asyncAction = launch {
var progressResult = true attachmentNotification.entryAttachmentState.apply {
try { try {
attachmentNotification.entryAttachmentState.apply { downloadState = AttachmentState.IN_PROGRESS
downloadState = AttachmentState.IN_PROGRESS
when (streamDirection) { when (streamDirection) {
StreamDirection.UPLOAD -> { StreamDirection.UPLOAD -> {
uploadToDatabase( uploadToDatabase(
attachmentNotification.uri, attachmentNotification.uri,
attachment.binaryAttachment, attachment.binaryAttachment,
contentResolver, 1024) { percent -> contentResolver, 1024,
publishProgress(percent) { // Cancellation
downloadState == AttachmentState.CANCELED
}
) { percent ->
publishProgress(percent)
}
}
StreamDirection.DOWNLOAD -> {
downloadFromDatabase(
attachmentNotification.uri,
attachment.binaryAttachment,
contentResolver, 1024) { percent ->
publishProgress(percent)
}
} }
} }
StreamDirection.DOWNLOAD -> { } catch (e: Exception) {
downloadFromDatabase( e.printStackTrace()
attachmentNotification.uri, downloadState = AttachmentState.ERROR
attachment.binaryAttachment,
contentResolver, 1024) { percent ->
publishProgress(percent)
}
}
}
} }
} catch (e: Exception) {
e.printStackTrace()
progressResult = false
} }
progressResult attachmentNotification.entryAttachmentState.downloadState
} }
// on post execute // on post execute
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {
val result = asyncResult.await() asyncAction.join()
attachmentNotification.attachmentFileAction = null
attachmentNotification.entryAttachmentState.apply { attachmentNotification.entryAttachmentState.apply {
downloadState = if (result) AttachmentState.COMPLETE else AttachmentState.ERROR if (downloadState != AttachmentState.CANCELED
downloadProgression = 100 && downloadState != AttachmentState.ERROR) {
downloadState = AttachmentState.COMPLETE
downloadProgression = 100
}
} }
attachmentNotification.attachmentFileAction = null
listener?.onUpdate(attachmentNotification) listener?.onUpdate(attachmentNotification)
TimeoutHelper.releaseTemporarilyDisableTimeout()
} }
} }
} }
fun cancel() {
attachmentNotification.entryAttachmentState.downloadState = AttachmentState.CANCELED
}
fun downloadFromDatabase(attachmentToUploadUri: Uri, fun downloadFromDatabase(attachmentToUploadUri: Uri,
binaryAttachment: BinaryAttachment, binaryAttachment: BinaryAttachment,
contentResolver: ContentResolver, contentResolver: ContentResolver,
@@ -396,13 +425,14 @@ class AttachmentFileNotificationService: LockNotificationService() {
binaryAttachment: BinaryAttachment, binaryAttachment: BinaryAttachment,
contentResolver: ContentResolver, contentResolver: ContentResolver,
bufferSize: Int = DEFAULT_BUFFER_SIZE, bufferSize: Int = DEFAULT_BUFFER_SIZE,
canceled: ()-> Boolean = { false },
update: ((percent: Int)->Unit)? = null) { update: ((percent: Int)->Unit)? = null) {
var dataUploaded = 0L var dataUploaded = 0L
val fileSize = contentResolver.openFileDescriptor(attachmentFromDownloadUri, "r")?.statSize ?: 0 val fileSize = contentResolver.openFileDescriptor(attachmentFromDownloadUri, "r")?.statSize ?: 0
UriUtil.getUriInputStream(contentResolver, attachmentFromDownloadUri)?.use { inputStream -> UriUtil.getUriInputStream(contentResolver, attachmentFromDownloadUri)?.use { inputStream ->
Database.getInstance().loadedCipherKey?.let { binaryCipherKey -> Database.getInstance().loadedCipherKey?.let { binaryCipherKey ->
binaryAttachment.getGzipOutputDataStream(binaryCipherKey).use { outputStream -> binaryAttachment.getGzipOutputDataStream(binaryCipherKey).use { outputStream ->
inputStream.readAllBytes(bufferSize) { buffer -> inputStream.readAllBytes(bufferSize, canceled) { buffer ->
outputStream.write(buffer) outputStream.write(buffer)
dataUploaded += buffer.size dataUploaded += buffer.size
try { try {
@@ -422,7 +452,9 @@ class AttachmentFileNotificationService: LockNotificationService() {
val currentTime = System.currentTimeMillis() val currentTime = System.currentTimeMillis()
if (previousSaveTime + updateMinFrequency < currentTime) { if (previousSaveTime + updateMinFrequency < currentTime) {
attachmentNotification.entryAttachmentState.apply { attachmentNotification.entryAttachmentState.apply {
downloadState = AttachmentState.IN_PROGRESS if (downloadState != AttachmentState.CANCELED) {
downloadState = AttachmentState.IN_PROGRESS
}
downloadProgression = percent downloadProgression = percent
} }
CoroutineScope(Dispatchers.Main).launch { CoroutineScope(Dispatchers.Main).launch {
@@ -444,6 +476,7 @@ class AttachmentFileNotificationService: LockNotificationService() {
private const val CHANNEL_ATTACHMENT_ID = "com.kunzisoft.keepass.notification.channel.attachment" private const val CHANNEL_ATTACHMENT_ID = "com.kunzisoft.keepass.notification.channel.attachment"
const val ACTION_ATTACHMENT_FILE_START_UPLOAD = "ACTION_ATTACHMENT_FILE_START_UPLOAD" const val ACTION_ATTACHMENT_FILE_START_UPLOAD = "ACTION_ATTACHMENT_FILE_START_UPLOAD"
const val ACTION_ATTACHMENT_FILE_STOP_UPLOAD = "ACTION_ATTACHMENT_FILE_STOP_UPLOAD"
const val ACTION_ATTACHMENT_FILE_START_DOWNLOAD = "ACTION_ATTACHMENT_FILE_START_DOWNLOAD" const val ACTION_ATTACHMENT_FILE_START_DOWNLOAD = "ACTION_ATTACHMENT_FILE_START_DOWNLOAD"
const val ACTION_ATTACHMENT_REMOVE = "ACTION_ATTACHMENT_REMOVE" const val ACTION_ATTACHMENT_REMOVE = "ACTION_ATTACHMENT_REMOVE"

View File

@@ -31,10 +31,11 @@ import java.util.*
*/ */
@Throws(IOException::class) @Throws(IOException::class)
fun InputStream.readAllBytes(bufferSize: Int = DEFAULT_BUFFER_SIZE, fun InputStream.readAllBytes(bufferSize: Int = DEFAULT_BUFFER_SIZE,
cancelCondition: ()-> Boolean = { false },
readBytes: (bytesRead: ByteArray) -> Unit) { readBytes: (bytesRead: ByteArray) -> Unit) {
val buffer = ByteArray(bufferSize) val buffer = ByteArray(bufferSize)
var read = 0 var read = 0
while (read != -1) { while (read != -1 && !cancelCondition()) {
read = this.read(buffer, 0, buffer.size) read = this.read(buffer, 0, buffer.size)
if (read != -1) { if (read != -1) {
val optimizedBuffer: ByteArray = if (buffer.size == read) { val optimizedBuffer: ByteArray = if (buffer.size == read) {

View File

@@ -34,6 +34,7 @@ import com.kunzisoft.keepass.model.EntryAttachmentState
import com.kunzisoft.keepass.services.AttachmentFileNotificationService import com.kunzisoft.keepass.services.AttachmentFileNotificationService
import com.kunzisoft.keepass.services.AttachmentFileNotificationService.Companion.ACTION_ATTACHMENT_FILE_START_DOWNLOAD import com.kunzisoft.keepass.services.AttachmentFileNotificationService.Companion.ACTION_ATTACHMENT_FILE_START_DOWNLOAD
import com.kunzisoft.keepass.services.AttachmentFileNotificationService.Companion.ACTION_ATTACHMENT_FILE_START_UPLOAD import com.kunzisoft.keepass.services.AttachmentFileNotificationService.Companion.ACTION_ATTACHMENT_FILE_START_UPLOAD
import com.kunzisoft.keepass.services.AttachmentFileNotificationService.Companion.ACTION_ATTACHMENT_FILE_STOP_UPLOAD
import com.kunzisoft.keepass.services.AttachmentFileNotificationService.Companion.ACTION_ATTACHMENT_REMOVE import com.kunzisoft.keepass.services.AttachmentFileNotificationService.Companion.ACTION_ATTACHMENT_REMOVE
class AttachmentFileBinderManager(private val activity: FragmentActivity) { class AttachmentFileBinderManager(private val activity: FragmentActivity) {
@@ -120,6 +121,10 @@ class AttachmentFileBinderManager(private val activity: FragmentActivity) {
}, ACTION_ATTACHMENT_FILE_START_UPLOAD) }, ACTION_ATTACHMENT_FILE_START_UPLOAD)
} }
fun stopUploadAllAttachments() {
start(null, ACTION_ATTACHMENT_FILE_STOP_UPLOAD)
}
fun startDownloadAttachment(downloadFileUri: Uri, fun startDownloadAttachment(downloadFileUri: Uri,
attachment: Attachment) { attachment: Attachment) {
start(Bundle().apply { start(Bundle().apply {

View File

@@ -514,6 +514,7 @@
<string name="download_progression">In progress: %1$d%%</string> <string name="download_progression">In progress: %1$d%%</string>
<string name="download_finalization">Finalizing…</string> <string name="download_finalization">Finalizing…</string>
<string name="download_complete">Complete!</string> <string name="download_complete">Complete!</string>
<string name="download_canceled">Canceled!</string>
<string name="encryption_rijndael">Rijndael (AES)</string> <string name="encryption_rijndael">Rijndael (AES)</string>
<string name="encryption_twofish">Twofish</string> <string name="encryption_twofish">Twofish</string>
<string name="encryption_chacha20">ChaCha20</string> <string name="encryption_chacha20">ChaCha20</string>