Skip to content
Draft
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ import com.owncloud.android.datamodel.OCFileDepth.Root
import com.owncloud.android.ui.events.EncryptionEvent
import com.owncloud.android.utils.FileStorageUtils
import java.util.Calendar
import java.util.Date

fun List<OCFile>.filterFilenames(): List<OCFile> = distinctBy { it.fileName }

Expand Down Expand Up @@ -85,6 +84,19 @@ private fun transformToRows(list: List<OCFile>, layout: GalleryRowLayout): List<
.map { chunk -> GalleryRow(chunk, layout.measure(chunk)) }
}

fun OCFile?.isDownloadedFileChanged(serverFile: OCFile): Boolean {
if (this == null || !isDown || serverFile.isFolder) {
return false
}

return etag.eTagChanged(serverFile.etag)
}

fun OCFile.isLocalETagOutdated(): Boolean {
val serverETag = etagInConflict
return !serverETag.isNullOrEmpty() && etag.eTagChanged(serverETag)
}

fun OCFile.toEncryptionEvent(encrypt: Boolean): EncryptionEvent = EncryptionEvent(
localId,
remoteId,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,11 @@ import kotlinx.coroutines.delay
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import kotlin.time.Duration.Companion.milliseconds
import kotlin.time.Duration.Companion.seconds

class FolderRefreshScheduler(private val activity: FileDisplayActivity) {
companion object {
private const val ETAG_POLL_INTERVAL_MS = 30_000L
private val ETAG_POLL_INTERVAL = 10.seconds
private const val TAG = "FolderRefreshScheduler"
}

Expand All @@ -35,12 +35,12 @@ class FolderRefreshScheduler(private val activity: FileDisplayActivity) {

job = activity.lifecycleScope.launch {
while (isActive) {
delay(ETAG_POLL_INTERVAL_MS.milliseconds)
delay(ETAG_POLL_INTERVAL)
checkAndRefreshIfETagChanged()
}
}

Log_OC.d(TAG, "eTag polling started interval 30 seconds")
Log_OC.d(TAG, "eTag polling started interval $ETAG_POLL_INTERVAL")
}

fun stop() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import com.nextcloud.utils.ResultParser;
import com.nextcloud.utils.e2ee.E2EVersionHelper;
import com.nextcloud.utils.share.UnifiedShareSharees;
import com.nextcloud.utils.extensions.OCFileExtensionsKt;
import com.nextcloud.utils.extensions.StringExtensionsKt;
import com.owncloud.android.datamodel.ArbitraryDataProvider;
import com.owncloud.android.datamodel.ArbitraryDataProviderImpl;
Expand Down Expand Up @@ -55,6 +56,7 @@
import java.util.List;
import java.util.Map;
import java.util.Vector;
import java.util.concurrent.atomic.AtomicInteger;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
Expand Down Expand Up @@ -113,12 +115,12 @@ public class RefreshFolderOperation extends RemoteOperation {
/**
* Counter of conflicts found between local and remote files
*/
private int mConflictsFound;
private final AtomicInteger mConflictsFound = new AtomicInteger();

/**
* Counter of failed operations in synchronization of kept-in-sync files
*/
private int mFailsInKeptInSyncFound;
private final AtomicInteger mFailsInKeptInSyncFound = new AtomicInteger();

/**
* Map of remote and local paths to files that where locally stored in a location out of the ownCloud folder and
Expand All @@ -134,7 +136,7 @@ public class RefreshFolderOperation extends RemoteOperation {
/**
* 'True' means that the remote folder changed and should be fetched
*/
private boolean mRemoteFolderChanged;
private volatile boolean mRemoteFolderChanged;

/**
* 'True' means that the sharees of at least one child of the folder changed
Expand All @@ -154,6 +156,8 @@ public class RefreshFolderOperation extends RemoteOperation {
private final List<SynchronizeFileOperation> mFilesToSyncContents;
// this will be used for every file when 'folder synchronization' replaces 'folder download'

private final boolean syncChangedDownloadedFiles;


/**
* Creates a new instance of {@link RefreshFolderOperation}.
Expand All @@ -174,6 +178,17 @@ public RefreshFolderOperation(OCFile folder,
FileDataStorageManager dataStorageManager,
User user,
Context context) {
this(folder, currentSyncTime, syncFullAccount, ignoreETag, dataStorageManager, user, context, false);
}

public RefreshFolderOperation(OCFile folder,
long currentSyncTime,
boolean syncFullAccount,
boolean ignoreETag,
FileDataStorageManager dataStorageManager,
User user,
Context context,
boolean syncChangedDownloadedFiles) {
mLocalFolder = folder;
mCurrentSyncTime = currentSyncTime;
mSyncFullAccount = syncFullAccount;
Expand All @@ -185,6 +200,7 @@ public RefreshFolderOperation(OCFile folder,
mIgnoreETag = ignoreETag;
mOnlyFileMetadata = false;
mFilesToSyncContents = new Vector<>();
this.syncChangedDownloadedFiles = syncChangedDownloadedFiles;
}

/**
Expand All @@ -205,6 +221,7 @@ public RefreshFolderOperation(OCFile folder,
mIgnoreETag = false;
mOnlyFileMetadata = true;
mFilesToSyncContents = new Vector<>();
syncChangedDownloadedFiles = false;

// since metadata worker working in background for sub-folders no need send folder refresh event
isMetadataSyncWorkerRunning = true;
Expand All @@ -229,14 +246,15 @@ public RefreshFolderOperation(OCFile folder,
mIgnoreETag = ignoreETag;
mOnlyFileMetadata = onlyFileMetadata;
mFilesToSyncContents = new Vector<>();
syncChangedDownloadedFiles = false;
}

public int getConflictsFound() {
return mConflictsFound;
return mConflictsFound.get();
}

public int getFailsInKeptInSyncFound() {
return mFailsInKeptInSyncFound;
return mFailsInKeptInSyncFound.get();
}

public Map<String, String> getForgottenLocalFiles() {
Expand All @@ -261,8 +279,8 @@ public List<OCFile> getChildren() {
@Override
protected RemoteOperationResult run(OwnCloudClient client) {
RemoteOperationResult result;
mFailsInKeptInSyncFound = 0;
mConflictsFound = 0;
mFailsInKeptInSyncFound.set(0);
mConflictsFound.set(0);
mForgottenLocalFiles.clear();

if (mLocalFolder == null) {
Expand Down Expand Up @@ -470,7 +488,7 @@ private RemoteOperationResult fetchAndSyncRemoteFolder(OwnCloudClient client) {

if (result.isSuccess()) {
synchronizeData(result.getData());
if (mConflictsFound > 0 || mFailsInKeptInSyncFound > 0) {
if (mConflictsFound.get() > 0 || mFailsInKeptInSyncFound.get() > 0) {
result = new RemoteOperationResult(ResultCode.SYNC_CONFLICT);
// should be a different result code, but will do the job
}
Expand Down Expand Up @@ -619,6 +637,12 @@ private void synchronizeData(List<Object> folderAndFiles) {
updatedFile.setEncrypted(encrypted);
updatedFile.setReadOnly(localFile != null && localFile.isReadOnly());

if (syncChangedDownloadedFiles && !encrypted &&
OCFileExtensionsKt.isDownloadedFileChanged(localFile, remoteFile)) {
mFilesToSyncContents.add(new SynchronizeFileOperation(updatedFile, remoteFile, user, true, mContext,
fileDataStorageManager, true));
}

updatedFiles.add(updatedFile);
}

Expand Down Expand Up @@ -817,26 +841,37 @@ public static Map<String, OCFile> prefillLocalFilesMap(Object metadata, List<OCF
* @param filesToSyncContents Synchronization operations to execute.
*/
private void startContentSynchronizations(List<SynchronizeFileOperation> filesToSyncContents) {
RemoteOperationResult contentsResult;
for (SynchronizeFileOperation op : filesToSyncContents) {
contentsResult = op.execute(mContext); // async
if (!contentsResult.isSuccess()) {
if (contentsResult.getCode() == ResultCode.SYNC_CONFLICT) {
mConflictsFound++;
} else {
mFailsInKeptInSyncFound++;
if (contentsResult.getException() != null) {
Log_OC.e(TAG, "Error while synchronizing favourites : "
+ contentsResult.getLogMessage(), contentsResult.getException());
} else {
Log_OC.e(TAG, "Error while synchronizing favourites : "
+ contentsResult.getLogMessage());
}
}
} // won't let these fails break the synchronization process
// won't let these fails break the synchronization process
for (SynchronizeFileOperation operation : filesToSyncContents) {
final RemoteOperationResult<?> result = operation.execute(mContext);
if (result.isSuccess()) {
operation.printSuccess();
continue;
}

if (result.getCode() == ResultCode.SYNC_CONFLICT) {
operation.printConflict();
mConflictsFound.incrementAndGet();
continue;
}

operation.printFailure();
mFailsInKeptInSyncFound.incrementAndGet();
logContentSynchronizationFailure(result);
}
}

private void logContentSynchronizationFailure(RemoteOperationResult<?> result) {
final String message = "Error while synchronizing favourites : " + result.getLogMessage(mContext);
final Exception exception = result.getException();
if (exception == null) {
Log_OC.e(TAG, message);
return;
}

Log_OC.e(TAG, message, exception);
}

/**
* Sends a message to any application component interested in the progress of the synchronization.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,18 @@ class SynchronizeFileOperation : SyncOperation {
}
}

fun printSuccess() {
Log_OC.i(TAG, "content synchronized, remote path: $remotePath")
}

fun printConflict() {
Log_OC.w(TAG, "content has conflict, remote path: $remotePath")
}

fun printFailure() {
Log_OC.e(TAG, "content not synchronized, remote path: $remotePath")
}

companion object {
private val TAG: String = SynchronizeFileOperation::class.java.simpleName
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2014,7 +2014,7 @@ class FileDisplayActivity :
override fun onBrowsedDownTo(directory: OCFile?) {
file = directory
resetScrollingAndUpdateActionBar()
startSyncFolderOperation(directory, false)
startSyncFolderOperation(directory, ignoreETag = false, syncChangedDownloadedFiles = true)
startMetadataSyncForCurrentDir()
}

Expand Down Expand Up @@ -2598,14 +2598,19 @@ class FileDisplayActivity :
* @param ignoreFocus reloads file list even without focus, e.g. on tablet mode, focus can still be in detail view
*/
@JvmOverloads
fun startSyncFolderOperation(folder: OCFile?, ignoreETag: Boolean, ignoreFocus: Boolean = false) {
fun startSyncFolderOperation(
folder: OCFile?,
ignoreETag: Boolean,
ignoreFocus: Boolean = false,
syncChangedDownloadedFiles: Boolean = false
) {
Log_OC.d(TAG, "startSyncFolderOperation called, ignoreEtag: $ignoreETag, ignoreFocus: $ignoreFocus")

if (!searchQuery.isNullOrEmpty() || !user.isPresent) {
return
}

val syncFolder = Runnable { executeSyncFolderOperation(folder, ignoreETag) }
val syncFolder = Runnable { executeSyncFolderOperation(folder, ignoreETag, syncChangedDownloadedFiles) }

// The refresh must not run while another window floats over the activity, e.g. a dialog that is being
// dismissed or a rotation. Rather than waiting a fixed delay run right away when it already has focus
Expand All @@ -2631,7 +2636,7 @@ class FileDisplayActivity :
}
}

private fun executeSyncFolderOperation(folder: OCFile?, ignoreETag: Boolean) {
private fun executeSyncFolderOperation(folder: OCFile?, ignoreETag: Boolean, syncChangedDownloadedFiles: Boolean) {
val folder = folder ?: return

user.ifPresent { user ->
Expand All @@ -2644,7 +2649,8 @@ class FileDisplayActivity :
ignoreETag,
storageManager,
user,
applicationContext
applicationContext,
syncChangedDownloadedFiles
).execute(
account,
this,
Expand Down
Loading
Loading