Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,313 changes: 1,313 additions & 0 deletions app/schemas/com.nextcloud.client.database.NextcloudDatabase/101.json

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,8 @@ class UploadDateTests {
isWhileChargingOnly = 1,
isWifiOnly = 1,
createdBy = 5,
folderUnlockToken = "token123"
folderUnlockToken = "token123",
etag = null
)

val upload = entity.toOCUpload()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,8 @@ class FileUploadHelperTest {
isWhileChargingOnly = isWhileChargingOnly,
isWifiOnly = isWifiOnly,
createdBy = createdBy,
folderUnlockToken = folderUnlockToken
folderUnlockToken = folderUnlockToken,
etag = null
)

private fun buildOCUpload(
Expand Down Expand Up @@ -406,7 +407,8 @@ class FileUploadHelperTest {
isWhileChargingOnly = null,
isWifiOnly = null,
createdBy = null,
folderUnlockToken = null
folderUnlockToken = null,
etag = null
)

// should not throw; optional fields simply stay at OCUpload defaults
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,8 +97,9 @@ import com.owncloud.android.db.ProviderMeta
AutoMigration(from = 95, to = 96),
AutoMigration(from = 96, to = 97, spec = DatabaseMigrationUtil.ResetCapabilitiesPostMigration::class),
// manual migration used for 97 to 98
AutoMigration(from = 98, to = 99)
AutoMigration(from = 98, to = 99),
// manual migration used for 99 to 100
AutoMigration(from = 100, to = 101)
],
exportSchema = true
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,9 @@ data class UploadEntity(
@ColumnInfo(name = ProviderTableMeta.UPLOADS_CREATED_BY)
val createdBy: Int?,
@ColumnInfo(name = ProviderTableMeta.UPLOADS_FOLDER_UNLOCK_TOKEN)
val folderUnlockToken: String?
val folderUnlockToken: String?,
@ColumnInfo(name = ProviderTableMeta.UPLOADS_ETAG)
val etag: String?
)

fun UploadEntity.toOCUpload(capability: OCCapability? = null): OCUpload? {
Expand Down Expand Up @@ -88,6 +90,7 @@ fun UploadEntity.toOCUpload(capability: OCCapability? = null): OCUpload? {
isWifiOnly?.let { upload.isUseWifiOnly = it == 1 }
isWhileChargingOnly?.let { upload.isWhileChargingOnly = it == 1 }
folderUnlockToken?.let { upload.folderUnlockToken = it }
etag?.let { upload.etag = it }

return upload
}
Expand Down Expand Up @@ -129,6 +132,7 @@ fun OCUpload.toUploadEntity(): UploadEntity {
isWifiOnly = if (isUseWifiOnly) 1 else 0,
isWhileChargingOnly = if (isWhileChargingOnly) 1 else 0,
folderUnlockToken = folderUnlockToken,
uploadTime = null
uploadTime = null,
etag = etag
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,14 @@ import com.nextcloud.client.jobs.BackgroundJobManager
import com.nextcloud.client.network.Connectivity
import com.nextcloud.client.network.ConnectivityService
import com.nextcloud.client.notifications.AppWideNotificationManager
import com.nextcloud.utils.TimeConstants
import com.nextcloud.utils.extensions.checkWCFRestrictions
import com.nextcloud.utils.extensions.getBitmapSize
import com.nextcloud.utils.extensions.getExifSize
import com.nextcloud.utils.extensions.getUploadIds
import com.nextcloud.utils.extensions.isAnonymous
import com.nextcloud.utils.extensions.isLastResultConflictError
import com.nextcloud.utils.extensions.isSame
import com.nextcloud.utils.extensions.toFile
import com.owncloud.android.MainApp
import com.owncloud.android.R
import com.owncloud.android.datamodel.FileDataStorageManager
Expand All @@ -40,7 +43,6 @@ import com.owncloud.android.files.services.NameCollisionPolicy
import com.owncloud.android.lib.common.OwnCloudClient
import com.owncloud.android.lib.common.OwnCloudClientFactory
import com.owncloud.android.lib.common.network.OnDatatransferProgressListener
import com.owncloud.android.lib.common.operations.RemoteOperationResult
import com.owncloud.android.lib.common.utils.Log_OC
import com.owncloud.android.lib.resources.files.ReadFileRemoteOperation
import com.owncloud.android.lib.resources.files.model.RemoteFile
Expand All @@ -51,6 +53,8 @@ import com.owncloud.android.operations.UploadFileOperation
import com.owncloud.android.ui.adapter.uploadList.helper.ConflictHandlingResult
import com.owncloud.android.ui.adapter.uploadList.helper.UploadListAdapterActionHandler
import com.owncloud.android.utils.DisplayUtils
import com.owncloud.android.utils.FileUtil
import com.owncloud.android.utils.MimeTypeUtil
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
Expand Down Expand Up @@ -185,21 +189,17 @@ class FileUploadHelper {
val batteryStatus = powerManagementService.battery

val uploadsToRetry = mutableListOf<Long>()

val currentAccount = accountManager.currentAccount
val context = MainApp.getAppContext()
var ownCloudClient: OwnCloudClient? = null
if (!currentAccount.isAnonymous(context)) {
ownCloudClient =
OwnCloudClientFactory.createOwnCloudClient(accountManager.currentAccount, MainApp.getAppContext())
}
val ownCloudClient = createClientForCurrentAccount(accountManager)
val uploadActionHandler = UploadListAdapterActionHandler()

for (upload in uploads) {
if (upload.isLastResultConflictError()) {
ownCloudClient?.let {
conflictHandlingResult =
uploadActionHandler.handleConflict(upload, ownCloudClient, uploadsStorageManager)
conflictHandlingResult = uploadActionHandler.handleConflict(
upload,
ownCloudClient,
uploadsStorageManager
)
}
continue
}
Expand Down Expand Up @@ -249,6 +249,16 @@ class FileUploadHelper {
return@withContext showNotExistMessage
}

private fun createClientForCurrentAccount(accountManager: UserAccountManager): OwnCloudClient? {
val context = MainApp.getAppContext()
val currentAccount = accountManager.currentAccount
return if (currentAccount.isAnonymous(context)) {
null
} else {
OwnCloudClientFactory.createOwnCloudClient(currentAccount, context)
}
}

@JvmOverloads
@Suppress("LongParameterList")
fun uploadNewFiles(
Expand Down Expand Up @@ -578,22 +588,77 @@ class FileUploadHelper {
}
}

@Suppress("MagicNumber", "ReturnCount", "ComplexCondition")
fun isSameFileOnRemote(user: User?, localPath: String?, remotePath: String?, context: Context?): Boolean {
if (user == null || localPath == null || remotePath == null || context == null) {
@Suppress("ReturnCount")
fun isSameFileOnRemote(localPath: String?, remotePath: String?): Boolean {
if (localPath == null || remotePath == null) {
Log_OC.e(TAG, "cannot compare remote and local file")
return false
}

Log_OC.d(TAG, "comparing local file against remote file")

val client = createClientForCurrentAccount(accountManager)
val operation = ReadFileRemoteOperation(remotePath)
val result: RemoteOperationResult<*> = operation.execute(user, context)
val result = operation.execute(client)
if (result.isSuccess) {
val remoteFile = result.data[0] as RemoteFile
return remoteFile.isSame(localPath)
val result = remoteFile.isSame(localPath) ||
isETagUnchangedSinceLastUpload(remoteFile, localPath, remotePath)
if (result) {
Log_OC.d(TAG, "local file and remote file are same")
} else {
Log_OC.d(TAG, "local file different than remote file")
}
return result
}

Log_OC.d(TAG, "local file different than remote file")

return false
}

private fun isETagUnchangedSinceLastUpload(remoteFile: RemoteFile, localPath: String, remotePath: String): Boolean {
val accountName = accountManager.user.accountName
val lastUploadedEtag = getUploadByPaths(accountName, localPath, remotePath)?.etag
val isUnchanged = !lastUploadedEtag.isNullOrEmpty() && lastUploadedEtag == remoteFile.etag
if (isUnchanged) {
Log_OC.d(TAG, "remote file is the version this client uploaded")
}
return isUnchanged
}

private fun RemoteFile.isSame(path: String?): Boolean {
val localFile = path?.toFile() ?: return false

// remote file timestamp in millisecond not microsecond
val localLastModifiedTimestamp = localFile.lastModified() / TimeConstants.MILLIS_PER_SECOND
val localCreationTimestamp = FileUtil.getCreationTimestamp(localFile)
val localSize: Long = localFile.length()

return size == localSize &&
localCreationTimestamp != null &&
localCreationTimestamp == creationTimestamp &&
modifiedTimestamp == localLastModifiedTimestamp * TimeConstants.MILLIS_PER_SECOND &&
this.areImageDimensionsSame(path)
}

@Suppress("ReturnCount")
private fun RemoteFile.areImageDimensionsSame(path: String): Boolean {
if (!MimeTypeUtil.isImage(mimeType)) {
// can't compare it's not image
return true
}

val localFileImageDimension = path.getExifSize() ?: path.getBitmapSize()
if (localFileImageDimension == null) {
// can't compare local file image dimension is not determined
return true
}

return localFileImageDimension.first.toFloat() == imageDimension?.width &&
localFileImageDimension.second.toFloat() == imageDimension?.height
}

fun showFileUploadLimitMessage(activity: Activity) {
val message = activity.resources.getQuantityString(
R.plurals.file_upload_limit_message,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,12 +80,7 @@ object UploadErrorNotificationManager {
// do not show an error notification when uploading the same file again
if (result.code.isConflict()) {
val isSameFile = withContext(Dispatchers.IO) {
FileUploadHelper.instance().isSameFileOnRemote(
operation.user,
operation.storagePath,
operation.remotePath,
context
)
FileUploadHelper.instance().isSameFileOnRemote(operation.storagePath, operation.remotePath)
}

if (isSameFile) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,27 +7,9 @@

package com.nextcloud.utils.extensions

import com.nextcloud.utils.TimeConstants
import com.owncloud.android.lib.resources.files.model.RemoteFile
import com.owncloud.android.lib.resources.shares.ShareeUser
import com.owncloud.android.lib.resources.tags.Tag
import com.owncloud.android.utils.FileUtil
import com.owncloud.android.utils.MimeTypeUtil

fun RemoteFile.isSame(path: String?): Boolean {
val localFile = path?.toFile() ?: return false

// remote file timestamp in millisecond not microsecond
val localLastModifiedTimestamp = localFile.lastModified() / TimeConstants.MILLIS_PER_SECOND
val localCreationTimestamp = FileUtil.getCreationTimestamp(localFile)
val localSize: Long = localFile.length()

return size == localSize &&
localCreationTimestamp != null &&
localCreationTimestamp == creationTimestamp &&
modifiedTimestamp == localLastModifiedTimestamp * TimeConstants.MILLIS_PER_SECOND &&
this.areImageDimensionsSame(path)
}

fun RemoteFile.sharedViaLink(): Boolean = sharees?.any { it.shareType?.isLink == true } ?: false

Expand All @@ -36,20 +18,3 @@ fun RemoteFile.sharedWithSharee(): Boolean = sharees?.isNotEmpty() ?: false
fun RemoteFile.getShareeList(): List<ShareeUser> = sharees?.toList() ?: emptyList()

fun RemoteFile.tags(): List<Tag> = tags?.mapNotNull { it } ?: emptyList()

@Suppress("ReturnCount")
private fun RemoteFile.areImageDimensionsSame(path: String): Boolean {
if (!MimeTypeUtil.isImage(mimeType)) {
// can't compare it's not image
return true
}

val localFileImageDimension = path.getExifSize() ?: path.getBitmapSize()
if (localFileImageDimension == null) {
// can't compare local file image dimension is not determined
return true
}

return localFileImageDimension.first.toFloat() == imageDimension?.width &&
localFileImageDimension.second.toFloat() == imageDimension?.height
}
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ class UploadsStorageManager(
put(ProviderTableMeta.UPLOADS_UPLOAD_END_TIMESTAMP_LONG, uploadEndTimestamp)
put(ProviderTableMeta.UPLOADS_FILE_SIZE, ocUpload.fileSize)
put(ProviderTableMeta.UPLOADS_FOLDER_UNLOCK_TOKEN, ocUpload.folderUnlockToken)
put(ProviderTableMeta.UPLOADS_ETAG, ocUpload.etag)
}

val result = contentResolver.update(
Expand All @@ -116,6 +117,26 @@ class UploadsStorageManager(
return result
}

fun updateUploadEtag(ocUpload: OCUpload) {
val etag = ocUpload.etag
if (etag.isNullOrEmpty()) {
return
}

val cv = ContentValues().apply {
put(ProviderTableMeta.UPLOADS_ETAG, etag)
}

val result = contentResolver.update(
ProviderTableMeta.CONTENT_URI_UPLOADS,
cv,
ProviderTableMeta._ID + "=? AND " + ProviderTableMeta.UPLOADS_ACCOUNT_NAME + "=?",
arrayOf(ocUpload.uploadId.toString(), ocUpload.accountName)
)

Log_OC.d(TAG, "updateUploadEtag returns with: $result for file: ${ocUpload.localPath}")
}

private fun updateUploadInternal(
c: Cursor,
status: UploadStatus?,
Expand Down Expand Up @@ -404,6 +425,11 @@ class UploadsStorageManager(
isUseWifiOnly = c.int(ProviderTableMeta.UPLOADS_IS_WIFI_ONLY) == 1
isWhileChargingOnly = c.int(ProviderTableMeta.UPLOADS_IS_WHILE_CHARGING_ONLY) == 1
folderUnlockToken = c.str(ProviderTableMeta.UPLOADS_FOLDER_UNLOCK_TOKEN)

val etagIndex = c.getColumnIndex(ProviderTableMeta.UPLOADS_ETAG)
if (etagIndex > -1) {
etag = c.getString(etagIndex)
}
}
}

Expand Down Expand Up @@ -495,12 +521,7 @@ class UploadsStorageManager(
status = UploadStatus.UPLOAD_SUCCEEDED
result = UploadResult.UPLOADED
} else if (code.isConflict()) {
val isSame = FileUploadHelper().isSameFileOnRemote(
upload.user,
upload.storagePath,
upload.remotePath,
upload.context
)
val isSame = FileUploadHelper().isSameFileOnRemote(upload.storagePath, upload.remotePath)

if (isSame) {
result = UploadResult.SAME_FILE_CONFLICT
Expand Down
Loading
Loading