diff --git a/data/src/apiKey/java/org/cryptomator/data/cloud/dropbox/DropboxCloudNodeFactory.kt b/data/src/apiKey/java/org/cryptomator/data/cloud/dropbox/DropboxCloudNodeFactory.kt index e7414081b..05c97e749 100644 --- a/data/src/apiKey/java/org/cryptomator/data/cloud/dropbox/DropboxCloudNodeFactory.kt +++ b/data/src/apiKey/java/org/cryptomator/data/cloud/dropbox/DropboxCloudNodeFactory.kt @@ -7,7 +7,7 @@ import com.dropbox.core.v2.files.Metadata internal object DropboxCloudNodeFactory { fun from(parent: DropboxFolder, metadata: FileMetadata): DropboxFile { - return DropboxFile(parent, metadata.name, metadata.pathDisplay, metadata.size, metadata.serverModified) + return DropboxFile(parent, metadata.name, metadata.pathDisplay, metadata.size, metadata.clientModified) } @JvmStatic diff --git a/data/src/apiKey/java/org/cryptomator/data/cloud/dropbox/DropboxImpl.kt b/data/src/apiKey/java/org/cryptomator/data/cloud/dropbox/DropboxImpl.kt index 412b3b56e..42f6331ee 100644 --- a/data/src/apiKey/java/org/cryptomator/data/cloud/dropbox/DropboxImpl.kt +++ b/data/src/apiKey/java/org/cryptomator/data/cloud/dropbox/DropboxImpl.kt @@ -39,6 +39,7 @@ import org.cryptomator.util.file.LruFileCacheUtil.Companion.retrieveFromLruCache import java.io.File import java.io.IOException import java.io.OutputStream +import java.util.Date import timber.log.Timber internal class DropboxImpl(cloud: DropboxCloud, context: Context) { @@ -167,6 +168,7 @@ internal class DropboxImpl(cloud: DropboxCloud, context: Context) { client() // .files() // .uploadBuilder(file.path) // + .withClientModified(data.modifiedDate(context).orElse(Date())) // .withMode(writeMode) // .uploadAndFinish(it) } diff --git a/data/src/apiKey/java/org/cryptomator/data/cloud/onedrive/OnedriveCloudNodeFactory.kt b/data/src/apiKey/java/org/cryptomator/data/cloud/onedrive/OnedriveCloudNodeFactory.kt index 3d0036ee9..714a21984 100644 --- a/data/src/apiKey/java/org/cryptomator/data/cloud/onedrive/OnedriveCloudNodeFactory.kt +++ b/data/src/apiKey/java/org/cryptomator/data/cloud/onedrive/OnedriveCloudNodeFactory.kt @@ -74,8 +74,7 @@ internal object OnedriveCloudNodeFactory { } private fun lastModified(item: DriveItem): Date? { - return item.lastModifiedDateTime?.let { - return Date.from(it.toInstant()) - } + return item.fileSystemInfo?.lastModifiedDateTime?.let { clientDate -> Date.from(clientDate.toInstant()) } + ?: item.lastModifiedDateTime?.let { serverDate -> Date.from(serverDate.toInstant()) } } } diff --git a/data/src/apiKey/java/org/cryptomator/data/cloud/onedrive/OnedriveImpl.kt b/data/src/apiKey/java/org/cryptomator/data/cloud/onedrive/OnedriveImpl.kt index b9f747d93..a6ce6b09c 100644 --- a/data/src/apiKey/java/org/cryptomator/data/cloud/onedrive/OnedriveImpl.kt +++ b/data/src/apiKey/java/org/cryptomator/data/cloud/onedrive/OnedriveImpl.kt @@ -5,6 +5,7 @@ import com.microsoft.graph.http.GraphServiceException import com.microsoft.graph.models.DriveItem import com.microsoft.graph.models.DriveItemCreateUploadSessionParameterSet import com.microsoft.graph.models.DriveItemUploadableProperties +import com.microsoft.graph.models.FileSystemInfo import com.microsoft.graph.models.Folder import com.microsoft.graph.models.ItemReference import com.microsoft.graph.options.Option @@ -39,6 +40,8 @@ import org.cryptomator.util.file.LruFileCacheUtil.Companion.retrieveFromLruCache import java.io.File import java.io.IOException import java.io.OutputStream +import java.time.OffsetDateTime +import java.time.ZoneId import java.util.Date import java.util.concurrent.CompletableFuture import java.util.concurrent.ExecutionException @@ -202,7 +205,9 @@ internal class OnedriveImpl(private val cloud: OnedriveCloud, private val client } progressAware.onProgress(Progress.completed(UploadState.upload(file))) return try { - OnedriveCloudNodeFactory.file(file.parent, result.get(), Date()) + val driveItem: DriveItem = result.get() + val lastModifiedDate = getLastModifiedDateTime(driveItem) ?: Date() + OnedriveCloudNodeFactory.file(file.parent, driveItem, lastModifiedDate) } catch (e: ExecutionException) { throw FatalBackendException(e) } catch (e: InterruptedException) { @@ -210,6 +215,11 @@ internal class OnedriveImpl(private val cloud: OnedriveCloud, private val client } } + private fun getLastModifiedDateTime(driveItem: DriveItem): Date? { + return driveItem.fileSystemInfo?.lastModifiedDateTime?.let { clientDate -> Date.from(clientDate.toInstant()) } + ?: driveItem.lastModifiedDateTime?.let { serverDate -> Date.from(serverDate.toInstant()) } + } + @Throws(NoSuchCloudFileException::class) private fun uploadFile(file: OnedriveFile, data: DataSource, progressAware: ProgressAware, result: CompletableFuture, conflictBehaviorOption: Option, size: Long) { data.open(context)?.use { inputStream -> @@ -228,12 +238,26 @@ internal class OnedriveImpl(private val cloud: OnedriveCloud, private val client .putAsync(CopyStream.toByteArray(it)) // .whenComplete { driveItem, error -> run { - if (error == null) { + val modifiedDate = data.modifiedDate(context) + if (error != null) { + result.completeExceptionally(error) + return@whenComplete + } + if (modifiedDate.isPresent) { + patchAsyncLastModifiedDate(parentNodeInfo, driveItem, modifiedDate.get()) + .whenComplete { driveItem, error -> + if (error == null) { + progressAware.onProgress(Progress.completed(UploadState.upload(file))) + result.complete(driveItem) + cacheNodeInfo(file, driveItem) + } else { + result.completeExceptionally(error) + } + } + } else { // current date is the default, no need to patch() progressAware.onProgress(Progress.completed(UploadState.upload(file))) result.complete(driveItem) cacheNodeInfo(file, driveItem) - } else { - result.completeExceptionally(error) } } } @@ -244,13 +268,32 @@ internal class OnedriveImpl(private val cloud: OnedriveCloud, private val client } ?: throw FatalBackendException("InputStream shouldn't bee null") } + private fun patchAsyncLastModifiedDate(parentNodeInfo: OnedriveIdCache.NodeInfo, driveItem: DriveItem, modifiedDate: Date): CompletableFuture { + val diffItem = DriveItem() + diffItem.fileSystemInfo = FileSystemInfo() + diffItem.fileSystemInfo!!.lastModifiedDateTime = OffsetDateTime.ofInstant(modifiedDate.toInstant(), ZoneId.systemDefault()) + return drive(parentNodeInfo.driveId) // + .items(driveItem.id!!) // + .buildRequest() // + .patchAsync(diffItem) // + } + @Throws(IOException::class, NoSuchCloudFileException::class) private fun chunkedUploadFile(file: OnedriveFile, data: DataSource, progressAware: ProgressAware, result: CompletableFuture, conflictBehaviorOption: Option, size: Long) { val parentNodeInfo = requireNodeInfo(file.parent) + + val props = DriveItemUploadableProperties() + val modifiedDate = data.modifiedDate(context) + + if (modifiedDate.isPresent) { + props.fileSystemInfo = FileSystemInfo() + props.fileSystemInfo!!.lastModifiedDateTime = OffsetDateTime.ofInstant(modifiedDate.get().toInstant(), ZoneId.systemDefault()) + } + drive(parentNodeInfo.driveId) // .items(parentNodeInfo.id) // .itemWithPath(file.name) // - .createUploadSession(DriveItemCreateUploadSessionParameterSet.newBuilder().withItem(DriveItemUploadableProperties()).build()) // + .createUploadSession(DriveItemCreateUploadSessionParameterSet.newBuilder().withItem(props).build()) // .buildRequest() // .post()?.let { uploadSession -> data.open(context)?.use { inputStream -> diff --git a/data/src/apiKey/java/org/cryptomator/data/cloud/pcloud/PCloudImpl.kt b/data/src/apiKey/java/org/cryptomator/data/cloud/pcloud/PCloudImpl.kt index fba87a304..e8a262c42 100644 --- a/data/src/apiKey/java/org/cryptomator/data/cloud/pcloud/PCloudImpl.kt +++ b/data/src/apiKey/java/org/cryptomator/data/cloud/pcloud/PCloudImpl.kt @@ -208,7 +208,7 @@ internal class PCloudImpl(private val cloud: PCloud, private val client: ApiClie } return try { client // - .createFile(file.parent.path, file.name, pCloudDataSource, Date(), listener, uploadOptions) // + .createFile(file.parent.path, file.name, pCloudDataSource, data.modifiedDate(context).orElse(Date()), listener, uploadOptions) // .execute() } catch (ex: ApiError) { handleApiError(ex, file.name) diff --git a/data/src/apkStorePlaystore/java/org/cryptomator/data/cloud/googledrive/GoogleDriveImpl.kt b/data/src/apkStorePlaystore/java/org/cryptomator/data/cloud/googledrive/GoogleDriveImpl.kt index 75c06090d..32766b449 100644 --- a/data/src/apkStorePlaystore/java/org/cryptomator/data/cloud/googledrive/GoogleDriveImpl.kt +++ b/data/src/apkStorePlaystore/java/org/cryptomator/data/cloud/googledrive/GoogleDriveImpl.kt @@ -3,6 +3,7 @@ package org.cryptomator.data.cloud.googledrive import android.content.Context import com.google.api.client.googleapis.json.GoogleJsonResponseException import com.google.api.client.http.HttpResponseException +import com.google.api.client.util.DateTime import com.google.api.services.drive.Drive import com.google.api.services.drive.model.File import com.google.api.services.drive.model.Revision @@ -25,6 +26,7 @@ import org.cryptomator.util.file.LruFileCacheUtil import org.cryptomator.util.file.LruFileCacheUtil.Companion.retrieveFromLruCache import java.io.IOException import java.io.OutputStream +import java.util.Date import timber.log.Timber internal class GoogleDriveImpl(context: Context, googleDriveCloud: GoogleDriveCloud, idCache: GoogleDriveIdCache) { @@ -203,6 +205,7 @@ internal class GoogleDriveImpl(context: Context, googleDriveCloud: GoogleDriveCl val metadata = File() metadata.name = file.name progressAware.onProgress(Progress.started(UploadState.upload(file))) + metadata.setModifiedTime(DateTime(data.modifiedDate(context).orElse(Date()))) val uploadedFile = if (file.driveId != null && replace) { updateFile(file, data, progressAware, size, metadata) } else { diff --git a/data/src/main/java/org/cryptomator/data/cloud/crypto/CryptoImplDecorator.kt b/data/src/main/java/org/cryptomator/data/cloud/crypto/CryptoImplDecorator.kt index f32cd3e32..6e5c0ad83 100644 --- a/data/src/main/java/org/cryptomator/data/cloud/crypto/CryptoImplDecorator.kt +++ b/data/src/main/java/org/cryptomator/data/cloud/crypto/CryptoImplDecorator.kt @@ -449,6 +449,7 @@ abstract class CryptoImplDecorator( progressAware.onProgress(Progress.progress(UploadState.encryption(cryptoFile)).between(0).and(ciphertextSize).withValue(encrypted)) } encryptingWritableByteChannel.close() + data.modifiedDate(context).ifPresent { encryptedTmpFile.setLastModified(it.time) } progressAware.onProgress(Progress.completed(UploadState.encryption(cryptoFile))) return writeFromTmpFile(data, cryptoFile, encryptedTmpFile, progressAware, replace) } diff --git a/data/src/main/java/org/cryptomator/data/cloud/crypto/CryptoImplVaultFormat7.kt b/data/src/main/java/org/cryptomator/data/cloud/crypto/CryptoImplVaultFormat7.kt index f50d78645..4128ccc7a 100644 --- a/data/src/main/java/org/cryptomator/data/cloud/crypto/CryptoImplVaultFormat7.kt +++ b/data/src/main/java/org/cryptomator/data/cloud/crypto/CryptoImplVaultFormat7.kt @@ -486,6 +486,7 @@ open class CryptoImplVaultFormat7 : CryptoImplDecorator { progressAware.onProgress(Progress.progress(UploadState.encryption(cloudFile)).between(0).and(ciphertextSize).withValue(encrypted)) } encryptingWritableByteChannel.close() + data.modifiedDate(context).ifPresent { encryptedTmpFile.setLastModified(it.time) } progressAware.onProgress(Progress.completed(UploadState.encryption(cloudFile))) val targetFile = targetFile(cryptoFile, cloudFile, replace) return file( diff --git a/data/src/main/java/org/cryptomator/data/cloud/webdav/WebDavImpl.kt b/data/src/main/java/org/cryptomator/data/cloud/webdav/WebDavImpl.kt index fd28fcdc6..c8b415092 100644 --- a/data/src/main/java/org/cryptomator/data/cloud/webdav/WebDavImpl.kt +++ b/data/src/main/java/org/cryptomator/data/cloud/webdav/WebDavImpl.kt @@ -23,6 +23,7 @@ import org.cryptomator.domain.usecases.cloud.Progress import org.cryptomator.domain.usecases.cloud.UploadState import java.io.IOException import java.io.OutputStream +import java.util.Date import okhttp3.HttpUrl.Companion.toHttpUrlOrNull internal class WebDavImpl(private val cloud: WebDavCloud, private val connectionHandler: ConnectionHandlerHandlerImpl, private val context: Context) { @@ -133,7 +134,7 @@ internal class WebDavImpl(private val cloud: WebDavCloud, private val connection ) } }.use { - connectionHandler.writeFile(absoluteUriFrom(uploadFile.path), it) + connectionHandler.writeFile(absoluteUriFrom(uploadFile.path), it, data.modifiedDate(context).orElse(Date())) } } ?: throw FatalBackendException("InputStream shouldn't bee null") diff --git a/data/src/main/java/org/cryptomator/data/cloud/webdav/network/ConnectionHandlerHandlerImpl.kt b/data/src/main/java/org/cryptomator/data/cloud/webdav/network/ConnectionHandlerHandlerImpl.kt index 49bd6309a..6063efb54 100644 --- a/data/src/main/java/org/cryptomator/data/cloud/webdav/network/ConnectionHandlerHandlerImpl.kt +++ b/data/src/main/java/org/cryptomator/data/cloud/webdav/network/ConnectionHandlerHandlerImpl.kt @@ -5,6 +5,7 @@ import org.cryptomator.data.cloud.webdav.WebDavNode import org.cryptomator.domain.CloudFolder import org.cryptomator.domain.exception.BackendException import java.io.InputStream +import java.util.Date import javax.inject.Inject class ConnectionHandlerHandlerImpl @Inject internal constructor(httpClient: WebDavCompatibleHttpClient) { @@ -27,8 +28,8 @@ class ConnectionHandlerHandlerImpl @Inject internal constructor(httpClient: WebD } @Throws(BackendException::class) - fun writeFile(url: String, inputStream: InputStream) { - webDavClient.writeFile(url, inputStream) + fun writeFile(url: String, inputStream: InputStream, modifiedDate: Date) { + webDavClient.writeFile(url, inputStream, modifiedDate) } @Throws(BackendException::class) diff --git a/data/src/main/java/org/cryptomator/data/cloud/webdav/network/WebDavClient.kt b/data/src/main/java/org/cryptomator/data/cloud/webdav/network/WebDavClient.kt index 620412335..1bdf3249e 100644 --- a/data/src/main/java/org/cryptomator/data/cloud/webdav/network/WebDavClient.kt +++ b/data/src/main/java/org/cryptomator/data/cloud/webdav/network/WebDavClient.kt @@ -18,6 +18,7 @@ import java.io.IOException import java.io.InputStream import java.net.HttpURLConnection import java.util.Collections +import java.util.Date import okhttp3.MediaType.Companion.toMediaTypeOrNull import okhttp3.Request import okhttp3.RequestBody.Companion.toRequestBody @@ -150,8 +151,9 @@ internal class WebDavClient(private val httpClient: WebDavCompatibleHttpClient) } @Throws(BackendException::class) - fun writeFile(url: String, inputStream: InputStream) { + fun writeFile(url: String, inputStream: InputStream, modifiedDate: Date) { val builder = Request.Builder() // + .addHeader("X-OC-Mtime", modifiedDate.toInstant().toEpochMilli().div(1000).toString()) // .put(InputStreamSourceBasedRequestBody.from(inputStream)) // .url(url) try { diff --git a/domain/src/main/java/org/cryptomator/domain/usecases/cloud/ByteArrayDataSource.kt b/domain/src/main/java/org/cryptomator/domain/usecases/cloud/ByteArrayDataSource.kt index 7fb94639b..b3cd1b1d0 100644 --- a/domain/src/main/java/org/cryptomator/domain/usecases/cloud/ByteArrayDataSource.kt +++ b/domain/src/main/java/org/cryptomator/domain/usecases/cloud/ByteArrayDataSource.kt @@ -1,9 +1,11 @@ package org.cryptomator.domain.usecases.cloud import android.content.Context +import org.cryptomator.util.Optional import java.io.ByteArrayInputStream import java.io.IOException import java.io.InputStream +import java.util.Date class ByteArrayDataSource private constructor(private val bytes: ByteArray) : DataSource { @@ -25,6 +27,10 @@ class ByteArrayDataSource private constructor(private val bytes: ByteArray) : Da // do nothing because ByteArrayInputStream need no close } + override fun modifiedDate(context: Context): Optional { + return Optional.empty() + } + companion object { @JvmStatic diff --git a/domain/src/main/java/org/cryptomator/domain/usecases/cloud/CancelAwareDataSource.kt b/domain/src/main/java/org/cryptomator/domain/usecases/cloud/CancelAwareDataSource.kt index 5c1a82e25..c031b8c41 100644 --- a/domain/src/main/java/org/cryptomator/domain/usecases/cloud/CancelAwareDataSource.kt +++ b/domain/src/main/java/org/cryptomator/domain/usecases/cloud/CancelAwareDataSource.kt @@ -2,8 +2,10 @@ package org.cryptomator.domain.usecases.cloud import android.content.Context import org.cryptomator.domain.exception.CancellationException +import org.cryptomator.util.Optional import java.io.IOException import java.io.InputStream +import java.util.Date class CancelAwareDataSource private constructor(private val delegate: DataSource, private val cancelled: Flag) : DataSource { @@ -31,6 +33,13 @@ class CancelAwareDataSource private constructor(private val delegate: DataSource delegate.close() } + override fun modifiedDate(context: Context): Optional { + if (cancelled.get()) { + throw CancellationException() + } + return delegate.modifiedDate(context) + } + companion object { @JvmStatic diff --git a/domain/src/main/java/org/cryptomator/domain/usecases/cloud/DataSource.kt b/domain/src/main/java/org/cryptomator/domain/usecases/cloud/DataSource.kt index 9f965d2d9..ddc0e13b8 100644 --- a/domain/src/main/java/org/cryptomator/domain/usecases/cloud/DataSource.kt +++ b/domain/src/main/java/org/cryptomator/domain/usecases/cloud/DataSource.kt @@ -1,10 +1,12 @@ package org.cryptomator.domain.usecases.cloud import android.content.Context +import org.cryptomator.util.Optional import java.io.Closeable import java.io.IOException import java.io.InputStream import java.io.Serializable +import java.util.Date interface DataSource : Serializable, Closeable { @@ -15,4 +17,6 @@ interface DataSource : Serializable, Closeable { fun decorate(delegate: DataSource): DataSource + fun modifiedDate(context: Context): Optional + } diff --git a/domain/src/main/java/org/cryptomator/domain/usecases/cloud/FileBasedDataSource.kt b/domain/src/main/java/org/cryptomator/domain/usecases/cloud/FileBasedDataSource.kt index 499645682..1d373e1c8 100644 --- a/domain/src/main/java/org/cryptomator/domain/usecases/cloud/FileBasedDataSource.kt +++ b/domain/src/main/java/org/cryptomator/domain/usecases/cloud/FileBasedDataSource.kt @@ -1,10 +1,12 @@ package org.cryptomator.domain.usecases.cloud import android.content.Context +import org.cryptomator.util.Optional import java.io.File import java.io.FileInputStream import java.io.IOException import java.io.InputStream +import java.util.Date class FileBasedDataSource private constructor(private val file: File) : DataSource { @@ -26,6 +28,10 @@ class FileBasedDataSource private constructor(private val file: File) : DataSour // Do nothing } + override fun modifiedDate(context: Context): Optional { + return Optional.of(Date(file.lastModified())) + } + companion object { @JvmStatic diff --git a/domain/src/main/java/org/cryptomator/domain/usecases/cloud/UploadFiles.java b/domain/src/main/java/org/cryptomator/domain/usecases/cloud/UploadFiles.java index 284e2a88f..375d34393 100644 --- a/domain/src/main/java/org/cryptomator/domain/usecases/cloud/UploadFiles.java +++ b/domain/src/main/java/org/cryptomator/domain/usecases/cloud/UploadFiles.java @@ -105,6 +105,7 @@ private File copyDataToFile(DataSource dataSource) { InputStream in = CancelAwareDataSource.wrap(dataSource, cancelledFlag).open(context); OutputStream out = new FileOutputStream(target); copy(in, out); + dataSource.modifiedDate(context).ifPresent(value -> target.setLastModified(value.getTime())); return target; } catch (IOException e) { throw new FatalBackendException(e); diff --git a/domain/src/test/java/org/cryptomator/domain/usecases/cloud/UploadFileTest.kt b/domain/src/test/java/org/cryptomator/domain/usecases/cloud/UploadFileTest.kt index 3f4177fc3..6db6f4948 100644 --- a/domain/src/test/java/org/cryptomator/domain/usecases/cloud/UploadFileTest.kt +++ b/domain/src/test/java/org/cryptomator/domain/usecases/cloud/UploadFileTest.kt @@ -8,6 +8,7 @@ import org.cryptomator.domain.CloudNode import org.cryptomator.domain.exception.BackendException import org.cryptomator.domain.repository.CloudContentRepository import org.cryptomator.domain.usecases.ProgressAware +import org.cryptomator.util.Optional import org.hamcrest.CoreMatchers import org.hamcrest.MatcherAssert import org.junit.jupiter.params.ParameterizedTest @@ -21,6 +22,7 @@ import java.io.ByteArrayInputStream import java.io.IOException import java.io.InputStream import java.util.Arrays +import java.util.Date class UploadFileTest { @@ -105,6 +107,10 @@ class UploadFileTest { return delegate } + override fun modifiedDate(context: Context): Optional { + return Optional.of(Date()) + } + @Throws(IOException::class) override fun close() { // do nothing diff --git a/presentation/src/main/java/org/cryptomator/presentation/presenter/UriBasedDataSource.kt b/presentation/src/main/java/org/cryptomator/presentation/presenter/UriBasedDataSource.kt index 39280129e..79c6defaf 100644 --- a/presentation/src/main/java/org/cryptomator/presentation/presenter/UriBasedDataSource.kt +++ b/presentation/src/main/java/org/cryptomator/presentation/presenter/UriBasedDataSource.kt @@ -4,8 +4,10 @@ import android.content.Context import android.net.Uri import org.cryptomator.domain.usecases.cloud.DataSource import org.cryptomator.presentation.util.ContentResolverUtil +import org.cryptomator.util.Optional import java.io.IOException import java.io.InputStream +import java.util.Date class UriBasedDataSource private constructor(private val uri: Uri) : DataSource { @@ -27,6 +29,10 @@ class UriBasedDataSource private constructor(private val uri: Uri) : DataSource // do nothing } + override fun modifiedDate(context: Context): Optional { + return Optional.ofNullable(ContentResolverUtil(context).fileModifiedDate(uri)) + } + companion object { @JvmStatic diff --git a/presentation/src/main/java/org/cryptomator/presentation/util/ContentResolverUtil.kt b/presentation/src/main/java/org/cryptomator/presentation/util/ContentResolverUtil.kt index 6b91a42ad..f834a18eb 100644 --- a/presentation/src/main/java/org/cryptomator/presentation/util/ContentResolverUtil.kt +++ b/presentation/src/main/java/org/cryptomator/presentation/util/ContentResolverUtil.kt @@ -3,17 +3,55 @@ package org.cryptomator.presentation.util import android.content.ContentResolver import android.content.Context import android.net.Uri +import android.provider.DocumentsContract import android.provider.OpenableColumns import java.io.File import java.io.FileNotFoundException import java.io.InputStream import java.io.OutputStream +import java.util.Date import javax.inject.Inject class ContentResolverUtil @Inject constructor(context: Context) { private val contentResolver: ContentResolver = context.contentResolver + fun fileModifiedDate(uri: Uri): Date? { + return when { + isContentUri(uri) -> { + fileModifiedDateForContentUri(uri) + } + isFileUri(uri) -> { + fileModifiedDateForFileUri(uri) + } + else -> null + } + } + + private fun fileModifiedDateForContentUri(uri: Uri): Date? { + contentResolver.query(uri, null, null, null, null).use { cursor -> + if (cursor != null && cursor.moveToFirst()) { + val dateModifiedColumnIndex = cursor.getColumnIndex(DocumentsContract.Document.COLUMN_LAST_MODIFIED) + if (!cursor.isNull(dateModifiedColumnIndex)) { + val date = cursor.getLong(dateModifiedColumnIndex) + return Date(date); + } + } + return null + } + } + + private fun fileModifiedDateForFileUri(uri: Uri): Date? { + return uri.path?.let { + val file = File(it) + if (file.exists()) { + Date(file.lastModified()) + } else { + null + } + } + } + @Throws(FileNotFoundException::class) fun openInputStream(uri: Uri): InputStream? { return contentResolver.openInputStream(uri)