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 @@ -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 @@ -2090,6 +2090,10 @@ class FileDisplayActivity :
fun canPreviewInMediaPager(file: OCFile?): Boolean =
PreviewImageFragment.canBePreviewed(file) || (file != null && MimeTypeUtil.isVideo(file))

/**
* Downloaded and non-downloaded images are handled later in
* [com.owncloud.android.ui.preview.PreviewMediaPagerAdapter.fragmentFor]
*/
fun previewImageWithSearchContext(
file: OCFile,
searchFragment: Boolean,
Expand All @@ -2112,8 +2116,7 @@ class FileDisplayActivity :
null
}

val showPreview = file.isDown || MimeTypeUtil.isVideo(file)
startImagePreview(file, showPreview, type, mediaState, sourceView)
startImagePreview(file, true, type, mediaState, sourceView)
}

fun previewFile(file: OCFile, setFabVisible: CompletionCallback?) {
Expand All @@ -2128,10 +2131,14 @@ class FileDisplayActivity :
startPdfPreview(file)
} else if (PreviewTextFileFragment.canBePreviewed(file)) {
setFabVisible?.onComplete(false)
startTextPreview(file, false)
fileOperationsHelper.startSyncForOutdatedFileAndIntent(file, textPreviewIntent(file))
} else if (canPreviewInAudioPlayer(file)) {
setFabVisible?.onComplete(false)
startAudioPreview(file, true, false)
if (file.isDownloading) {
fileOperationsHelper.startSyncForOutdatedFileAndIntent(file, audioPreviewIntent(file))
} else {
startAudioPlayer(file)
}
} else {
fileOperationsHelper.openFile(file)
}
Expand Down Expand Up @@ -2598,14 +2605,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 +2643,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 +2656,8 @@ class FileDisplayActivity :
ignoreETag,
storageManager,
user,
applicationContext
applicationContext,
syncChangedDownloadedFiles
).execute(
account,
this,
Expand Down Expand Up @@ -2773,15 +2786,17 @@ class FileDisplayActivity :
if ((showPreview && file.isDown && !file.isDownloading) || streamMedia) {
startAudioPlayer(file)
} else {
val previewIntent = Intent()
previewIntent.putExtra(EXTRA_FILE, file)
previewIntent.putExtra(AUDIO_PREVIEW, true)
val fileOperationsHelper =
FileOperationsHelper(this, userAccountManager, connectivityService, editorUtils)
fileOperationsHelper.startSyncForFileAndIntent(file, previewIntent)
fileOperationsHelper.startSyncForFileAndIntent(file, audioPreviewIntent(file))
}
}

private fun audioPreviewIntent(file: OCFile): Intent = Intent().apply {
putExtra(EXTRA_FILE, file)
putExtra(AUDIO_PREVIEW, true)
}

private fun startAudioPlayer(file: OCFile) {
val collection = listOfFilesFragment?.currentSearchType.toPlaybackCollection()
audioPlayerLauncher.launch(this, file, collection)
Expand Down Expand Up @@ -2810,15 +2825,17 @@ class FileDisplayActivity :
configureToolbarForPreview(file)
showBottomNavigationBar(false)
} else {
val previewIntent = Intent()
previewIntent.putExtra(EXTRA_FILE, file)
previewIntent.putExtra(TEXT_PREVIEW, true)
val fileOperationsHelper =
FileOperationsHelper(this, userAccountManager, connectivityService, editorUtils)
fileOperationsHelper.startSyncForFileAndIntent(file, previewIntent)
fileOperationsHelper.startSyncForFileAndIntent(file, textPreviewIntent(file))
}
}

private fun textPreviewIntent(file: OCFile?): Intent = Intent().apply {
putExtra(EXTRA_FILE, file)
putExtra(TEXT_PREVIEW, true)
}

/**
* Starts rich workspace preview for a folder.
*
Expand Down
Loading
Loading