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
Original file line number Diff line number Diff line change
Expand Up @@ -73,34 +73,41 @@ class OfflineOperationsRepository(private val fileDataStorageManager: FileDataSt
fileDataStorageManager.getFileById(parentId)?.let { ocFile ->
ocFile.decryptedRemotePath?.let { updatedPath ->
val newPath = updatedPath + nextOperation.filename + pathSeparator

if (newPath != nextOperation.path) {
nextOperation.apply {
type = when (type) {
is OfflineOperationType.CreateFile ->
(type as OfflineOperationType.CreateFile).copy(
remotePath = newPath
)

is OfflineOperationType.CreateFolder ->
(type as OfflineOperationType.CreateFolder).copy(
path = newPath
)

else -> type
}
path = newPath
}
} else {
null
}
updateOperationPath(newPath, nextOperation)
}
}
}
}
.forEach { dao.update(it) }
}

private fun updateOperationPath(newPath: String, nextOperation: OfflineOperationEntity): OfflineOperationEntity? =
if (newPath != nextOperation.path) {
nextOperation.apply {
type = when (type) {
is OfflineOperationType.CreateFile ->
(type as OfflineOperationType.CreateFile).copy(
remotePath = newPath
)

is OfflineOperationType.CreateFolder ->
(type as OfflineOperationType.CreateFolder).copy(
path = newPath
)

else -> type
}
path = newPath
}
} else {
null
}

override fun updateOperationForKeepBoth(operation: OfflineOperationEntity, newPath: String) {
updateOperationPath(newPath, operation)
dao.update(operation)
}

override fun convertToOCFiles(fileId: Long): List<OCFile> =
dao.getSubEntitiesByParentOCFileId(fileId).map { entity ->
OCFile(entity.path).apply {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,6 @@ interface OfflineOperationsRepositoryType {
fun getAllSubEntities(fileId: Long): List<OfflineOperationEntity>
fun deleteOperation(file: OCFile)
fun updateNextOperations(operation: OfflineOperationEntity)
fun updateOperationForKeepBoth(operation: OfflineOperationEntity, newPath: String)
fun convertToOCFiles(fileId: Long): List<OCFile>
}
28 changes: 28 additions & 0 deletions app/src/main/java/com/nextcloud/utils/extensions/FileExtensions.kt
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,22 @@
package com.nextcloud.utils.extensions

import android.graphics.Bitmap
import android.util.Log
import androidx.exifinterface.media.ExifInterface
import com.owncloud.android.datamodel.FileDataStorageManager
import com.owncloud.android.datamodel.OCFile
import com.owncloud.android.datamodel.ThumbnailsCacheManager
import com.owncloud.android.lib.common.utils.Log_OC
import com.owncloud.android.lib.resources.files.model.ServerFileInterface
import com.owncloud.android.utils.DisplayUtils
import java.io.File
import java.io.IOException
import java.nio.file.Files
import java.nio.file.Path
import java.nio.file.attribute.BasicFileAttributes

private const val TAG = "FileExtensions"
private const val MS_IN_SECOND = 1000

fun OCFile?.logFileSize(tag: String) {
val size = DisplayUtils.bytesToHumanReadable(this?.fileLength ?: -1)
Expand Down Expand Up @@ -111,3 +117,25 @@ fun String.getBitmapSize(): Pair<Int, Int>? = try {
} catch (_: Exception) {
null
}

fun OCFile?.isTheSameAs(localFile: File?): Boolean = try {
this ?: return false
localFile ?: return false

val attr = Files.readAttributes(localFile.toPath(), BasicFileAttributes::class.java)
val localName = localFile.getName()
val remoteName = this.fileName
val localSize = localFile.length()
val remoteSize = this.fileLength
val localCreated = attr.creationTime().toMillis() / MS_IN_SECOND // Unix time in milliseconds
val localModified = attr.lastModifiedTime().toMillis() / MS_IN_SECOND // Unix time in milliseconds
val remoteCreated = this.creationTimestamp // Unix time in seconds!
val remoteModified = this.modificationTimestamp / MS_IN_SECOND // Unix time in milliseconds
remoteName == localName &&
remoteSize == localSize &&
remoteCreated == localCreated &&
remoteModified == localModified
} catch (e: IOException) {
Log.e(FileDataStorageManager.TAG, "fileIsTheSame: unable to obtain local file attributes for comparing: $e")
false
}
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@
import com.owncloud.android.MainApp;
import com.owncloud.android.datamodel.e2e.v2.decrypted.DecryptedFolderMetadataFile;
import com.owncloud.android.db.ProviderMeta.ProviderTableMeta;
import com.owncloud.android.lib.common.OwnCloudClient;
import com.owncloud.android.lib.common.network.WebdavEntry;
import com.owncloud.android.lib.common.utils.Log_OC;
import com.owncloud.android.lib.resources.files.ReadFileRemoteOperation;
Expand All @@ -70,6 +71,7 @@
import com.owncloud.android.lib.resources.status.OCCapability;
import com.owncloud.android.lib.resources.tags.Tag;
import com.owncloud.android.operations.RemoteOperationFailedException;
import com.owncloud.android.operations.UploadFileOperation;
import com.owncloud.android.utils.FileStorageUtils;
import com.owncloud.android.utils.MimeType;
import com.owncloud.android.utils.MimeTypeUtil;
Expand Down Expand Up @@ -200,7 +202,7 @@ public void addCreateFileOfflineOperation(String[] localPaths, String[] remotePa
}

offlineOperationDao.insert(entity);
createPendingFile(remotePath, mimeType, createdAt, modificationTimestamp);
createPendingFile(remotePath, mimeType, createdAt, modificationTimestamp, localPath);
}
}

Expand Down Expand Up @@ -230,8 +232,22 @@ public OfflineOperationEntity addCreateFolderOfflineOperation(String path, Strin
return entity;
}

public void createPendingFile(String path, String mimeType, long createdAt, long modificationTimestamp) {
OCFile file = new OCFile(path);
public void createPendingFile(
String remotePath,
String mimeType,
long createdAt,
long modificationTimestamp,
String localPath
) {
final OCFile existingFile = getFileByRemotePath(remotePath);
final File localFile = FileExtensionsKt.toFile(localPath);
if (FileExtensionsKt.isTheSameAs(existingFile, localFile)) {
// In case the same file was already uploaded, do not overwrite it to avoid triggering a conflict
Log_OC.i(TAG, "Creating pendingFile for an already uploaded file: keeping metadata");
return;
}

OCFile file = new OCFile(remotePath);
file.setMimeType(mimeType);
file.setCreationTimestamp(createdAt);
file.setModificationTimestamp(modificationTimestamp);
Expand Down Expand Up @@ -340,10 +356,7 @@ public void renameOfflineOperation(OCFile file, String newFolderName) {
moveLocalFile(file, newPath, parentFolder.getDecryptedRemotePath());
}

@SuppressLint("SimpleDateFormat")
public void keepOfflineOperationAndServerFile(OfflineOperationEntity entity, OCFile file) {
if (file == null) return;

public void keepOfflineOperationAndServerFile(OfflineOperationEntity entity, OCFile file, OwnCloudClient client) {
String oldFileName = entity.getFilename();
if (oldFileName == null) return;

Expand All @@ -353,13 +366,13 @@ public void keepOfflineOperationAndServerFile(OfflineOperationEntity entity, OCF
OCFile parentFolder = getFileById(parentOCFileId);
if (parentFolder == null) return;

DateFormatPattern formatPattern = DateFormatPattern.FullDateWithHours;
String currentDateTime = DateExtensionsKt.currentDateRepresentation(new Date(), formatPattern);

String newFolderName = oldFileName + " - " + currentDateTime;
String newPath = parentFolder.getDecryptedRemotePath() + newFolderName + OCFile.PATH_SEPARATOR;
moveLocalFile(file, newPath, parentFolder.getDecryptedRemotePath());
offlineOperationsRepository.updateNextOperations(entity);
final String newPath = UploadFileOperation.getNewAvailableRemotePath(
client,
(entity.getPath() != null) ? entity.getPath() : file.getDecryptedRemotePath(),
List.of(oldFileName),
file.isEncrypted()
);
offlineOperationsRepository.updateOperationForKeepBoth(entity, newPath);
}

@Nullable
Expand Down Expand Up @@ -541,7 +554,7 @@ public List<OCFile> getFolderImagesAndVideos(OCFile folder, boolean onlyOnDevice
}

public boolean saveFile(OCFile ocFile) {
Log_OC.d(TAG, "saving file: " + ocFile.getRemotePath());
Log_OC.d(TAG, "saving file " + ocFile.getFileName() + " into " + ocFile.getRemotePath());

boolean overridden = false;
final ContentValues cv = createContentValuesForFile(ocFile);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1786,6 +1786,7 @@ private void updateOCFile(OCFile file, RemoteFile remoteFile) {
file.setModificationTimestamp(remoteFile.getModifiedTimestamp());
file.setModificationTimestampAtLastSyncForData(remoteFile.getModifiedTimestamp());
file.setEtag(remoteFile.getEtag());
file.setEtagOnServer(remoteFile.getEtag());
file.setRemoteId(remoteFile.getRemoteId());
file.setPermissions(remoteFile.getPermissions());
file.setUploadTimestamp(remoteFile.getUploadTimestamp());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,9 @@ import com.owncloud.android.files.services.NameCollisionPolicy
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
import com.owncloud.android.ui.dialog.conflict.ConflictResolveDialogFactory
import com.owncloud.android.ui.dialog.conflict.ConflictsResolveDialog.Decision
import com.owncloud.android.ui.dialog.conflict.ConflictsResolveDialog.OnConflictDecisionMadeListener
import com.owncloud.android.ui.dialog.conflict.ConflictResolveDialogFactory
import com.owncloud.android.utils.DisplayUtils
import com.owncloud.android.utils.FileStorageUtils
import kotlinx.coroutines.Dispatchers
Expand Down Expand Up @@ -201,7 +201,11 @@ class ConflictsResolveActivity :

private suspend fun keepBothFolder(offlineOperation: OfflineOperationEntity?, serverFile: OCFile?) {
offlineOperation ?: return
fileDataStorageManager.keepOfflineOperationAndServerFile(offlineOperation, serverFile)
fileDataStorageManager.keepOfflineOperationAndServerFile(
offlineOperation,
serverFile,
clientRepository.getOwncloudClient()
)
backgroundJobManager.startOfflineOperations()
withContext(Dispatchers.Main) {
offlineOperationNotificationManager.dismissNotification(offlineOperation.id)
Expand Down Expand Up @@ -272,11 +276,12 @@ class ConflictsResolveActivity :
return
}

val (ft, _) = prepareDialogTransaction()
val (ft, user) = prepareDialogTransaction()
ConflictResolveDialogFactory.forOffline(
context = this,
leftFile = offlineOperation,
rightFile = newFile!!
rightFile = newFile!!,
user = user
).show(ft, "conflictDialog")
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import android.os.Bundle
import androidx.annotation.StringRes
import com.nextcloud.client.account.User
import com.nextcloud.client.database.entity.OfflineOperationEntity
import com.nextcloud.model.OfflineOperationType
import com.nextcloud.utils.extensions.toFile
import com.owncloud.android.R
import com.owncloud.android.datamodel.OCFile
import com.owncloud.android.ui.dialog.conflict.model.ConflictDialogData
Expand All @@ -22,23 +24,34 @@ import java.io.File

object ConflictResolveDialogFactory {

private const val SECONDS_TO_MILLIS = 1000L
private const val UNKNOWN_FOLDER_SIZE = 0L
fun forOffline(
context: Context,
leftFile: OfflineOperationEntity,
rightFile: OCFile,
user: User?
): ConflictsResolveDialog {
val localFile =
if (leftFile.type is OfflineOperationType.CreateFile) {
(leftFile.type as OfflineOperationType.CreateFile).localPath.toFile()
} else {
null
}

fun forOffline(context: Context, leftFile: OfflineOperationEntity, rightFile: OCFile): ConflictsResolveDialog {
val data = ConflictDialogData(
headline = context.getString(R.string.conflict_folder_headline),
description = context.getString(R.string.conflict_message_description_for_folder),
headline = context.getString(R.string.choose_which_file),
description = context.getString(R.string.conflict_message_description),
localFile = context.conflictFileData(
titleId = R.string.prefs_synced_folders_local_path_title,
timestamp = (leftFile.createdAt ?: 0L) * SECONDS_TO_MILLIS,
fileLength = UNKNOWN_FOLDER_SIZE
titleId = R.string.conflict_local_file,
timestamp = (localFile?.lastModified() ?: 0L),
fileLength = (localFile?.length() ?: 0L)
),
serverFile = context.conflictFileData(R.string.prefs_synced_folders_remote_path_title, rightFile)
)

return createDialog(ConflictDialogType.Offline(data)) {
putSerializable(ConflictsResolveDialog.ARG_LEFT_FILE, localFile)
putParcelable(ConflictsResolveDialog.ARG_RIGHT_FILE, rightFile)
putParcelable(ConflictsResolveDialog.ARG_USER, user)
}
}

Expand Down
2 changes: 0 additions & 2 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -1267,8 +1267,6 @@
<string name="sync_not_enough_space_dialog_action_free_space">Free up space</string>
<string name="sync_not_enough_space_dialog_placeholder">%1$s is %2$s, but there is only %3$s available on device.</string>
<string name="sync_not_enough_space_dialog_title">Not enough space</string>
<string name="conflict_folder_headline">Folder conflict</string>
<string name="conflict_message_description_for_folder">If you select both versions, the local folder will have a number appended to its name.</string>
<string name="conflict_message_description">If you select both versions, the local file will have a number appended to its name.</string>
<string name="conflict_local_file">Local file</string>
<string name="conflict_server_file">Server file</string>
Expand Down
Loading