From 9c1bec94c9874e83233f283d2578ad9004d9431a Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 19 Jul 2021 10:57:35 +0200 Subject: [PATCH] Create AtomicFileCreator class to avoid code copy/paste --- .../internal/session/DefaultFileService.kt | 23 +++++------ .../internal/util/file/AtomicFileCreator.kt | 38 +++++++++++++++++++ 2 files changed, 47 insertions(+), 14 deletions(-) create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/file/AtomicFileCreator.kt diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultFileService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultFileService.kt index ba32bca6d6..c118352527 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultFileService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultFileService.kt @@ -33,6 +33,7 @@ import org.matrix.android.sdk.internal.di.SessionDownloadsDirectory import org.matrix.android.sdk.internal.di.UnauthenticatedWithCertificateWithProgress import org.matrix.android.sdk.internal.session.download.DownloadProgressInterceptor.Companion.DOWNLOAD_PROGRESS_INTERCEPTOR_HEADER import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers +import org.matrix.android.sdk.internal.util.file.AtomicFileCreator import org.matrix.android.sdk.internal.util.md5 import org.matrix.android.sdk.internal.util.writeToFile import timber.log.Timber @@ -131,14 +132,11 @@ internal class DefaultFileService @Inject constructor( Timber.v("Response size ${response.body?.contentLength()} - Stream available: ${!source.exhausted()}") // Write the file to cache (encrypted version if the file is encrypted) - // Write to a tmp file first, so if we abort before done, we don't have a broken cached file - val tmpFile = File(cachedFiles.file.parentFile, "${cachedFiles.file.name}.tmp") - if (tmpFile.exists()) { - Timber.v("## FileService: discard aborted tmp file ${tmpFile.path}") - } - writeToFile(source.inputStream(), tmpFile) + // Write to a part file first, so if we abort before done, we don't have a broken cached file + val atomicFileCreator = AtomicFileCreator(cachedFiles.file) + writeToFile(source.inputStream(), atomicFileCreator.partFile) response.close() - tmpFile.renameTo(cachedFiles.file) + atomicFileCreator.commit() } else { Timber.v("## FileService: cache hit for $url") } @@ -151,13 +149,10 @@ internal class DefaultFileService @Inject constructor( Timber.v("## FileService: decrypt file") // Ensure the parent folder exists cachedFiles.decryptedFile.parentFile?.mkdirs() - // Write to a tmp file first, so if we abort before done, we don't have a broken cached file - val tmpFile = File(cachedFiles.decryptedFile.parentFile, "${cachedFiles.decryptedFile.name}.tmp") - if (tmpFile.exists()) { - Timber.v("## FileService: discard aborted tmp file ${tmpFile.path}") - } + // Write to a part file first, so if we abort before done, we don't have a broken cached file + val atomicFileCreator = AtomicFileCreator(cachedFiles.decryptedFile) val decryptSuccess = cachedFiles.file.inputStream().use { inputStream -> - tmpFile.outputStream().buffered().use { outputStream -> + atomicFileCreator.partFile.outputStream().buffered().use { outputStream -> MXEncryptedAttachments.decryptAttachment( inputStream, elementToDecrypt, @@ -165,7 +160,7 @@ internal class DefaultFileService @Inject constructor( ) } } - tmpFile.renameTo(cachedFiles.decryptedFile) + atomicFileCreator.commit() if (!decryptSuccess) { throw IllegalStateException("Decryption error") } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/file/AtomicFileCreator.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/file/AtomicFileCreator.kt new file mode 100644 index 0000000000..e24473db6d --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/file/AtomicFileCreator.kt @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2021 The Matrix.org Foundation C.I.C. + * + * 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 org.matrix.android.sdk.internal.util.file + +import timber.log.Timber +import java.io.File + +internal class AtomicFileCreator(private val file: File) { + val partFile = File(file.parentFile, "${file.name}.part") + + init { + if (file.exists()) { + Timber.w("## AtomicFileCreator: target file ${file.path} exists, it should not happen.") + } + if (partFile.exists()) { + Timber.d("## AtomicFileCreator: discard aborted part file ${partFile.path}") + // No need to delete the file, we will overwrite it + } + } + + fun commit() { + partFile.renameTo(file) + } +}