mirror of
https://github.com/nextcloud/android.git
synced 2024-11-28 18:28:59 +03:00
Merge pull request #6917 from jmue/fixStaleFileContents
DocumentsStorageProvider: prevent opening stale file contents
This commit is contained in:
commit
d782da2fe9
2 changed files with 65 additions and 31 deletions
|
@ -6,6 +6,7 @@ import androidx.documentfile.provider.DocumentFile
|
|||
import com.owncloud.android.AbstractOnServerIT
|
||||
import com.owncloud.android.R
|
||||
import com.owncloud.android.datamodel.OCFile.ROOT_PATH
|
||||
import com.owncloud.android.lib.common.network.WebdavUtils
|
||||
import com.owncloud.android.providers.DocumentsProviderUtils.assertExistsOnServer
|
||||
import com.owncloud.android.providers.DocumentsProviderUtils.assertListFilesEquals
|
||||
import com.owncloud.android.providers.DocumentsProviderUtils.assertReadEquals
|
||||
|
@ -18,6 +19,9 @@ import com.owncloud.android.providers.DocumentsProviderUtils.listFilesBlocking
|
|||
import com.owncloud.android.providers.DocumentsStorageProvider.DOCUMENTID_SEPARATOR
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import net.bytebuddy.utility.RandomString
|
||||
import org.apache.commons.httpclient.HttpStatus
|
||||
import org.apache.commons.httpclient.methods.ByteArrayRequestEntity
|
||||
import org.apache.commons.httpclient.methods.PutMethod
|
||||
import org.junit.After
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertFalse
|
||||
|
@ -172,4 +176,32 @@ class DocumentsStorageProviderIT : AbstractOnServerIT() {
|
|||
assertFalse(file1.exists())
|
||||
assertExistsOnServer(client, ocFile1.remotePath, false)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testServerChangedFileContent() {
|
||||
// create random file
|
||||
val file1 = rootDir.createFile("text/plain", RandomString.make())!!
|
||||
file1.assertRegularFile(size = 0L)
|
||||
|
||||
val content1 = "initial content".toByteArray()
|
||||
|
||||
// write content bytes to file
|
||||
contentResolver.openOutputStream(file1.uri, "wt").use {
|
||||
it!!.write(content1)
|
||||
}
|
||||
|
||||
val remotePath = file1.getOCFile(storageManager)!!.remotePath
|
||||
|
||||
val content2 = "new content".toByteArray()
|
||||
|
||||
// modify content on server side
|
||||
val putMethod = PutMethod(client.webdavUri.toString() + WebdavUtils.encodePath(remotePath))
|
||||
putMethod.setRequestEntity(ByteArrayRequestEntity(content2))
|
||||
assertEquals(HttpStatus.SC_NO_CONTENT, client.executeMethod(putMethod))
|
||||
client.exhaustResponse(putMethod.responseBodyAsStream)
|
||||
putMethod.releaseConnection() // let the connection available for other methods
|
||||
|
||||
// read back content bytes
|
||||
assertReadEquals(content2, contentResolver.openInputStream(file1.uri))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -44,7 +44,6 @@ import android.provider.DocumentsProvider;
|
|||
import android.util.Log;
|
||||
import android.util.SparseArray;
|
||||
|
||||
import com.nextcloud.client.account.User;
|
||||
import com.nextcloud.client.account.UserAccountManager;
|
||||
import com.nextcloud.client.account.UserAccountManagerImpl;
|
||||
import com.nextcloud.client.files.downloader.DownloadTask;
|
||||
|
@ -64,6 +63,7 @@ import com.owncloud.android.lib.common.OwnCloudClientManagerFactory;
|
|||
import com.owncloud.android.lib.common.accounts.AccountUtils.AccountNotFoundException;
|
||||
import com.owncloud.android.lib.common.operations.RemoteOperationResult;
|
||||
import com.owncloud.android.lib.common.utils.Log_OC;
|
||||
import com.owncloud.android.lib.resources.files.CheckEtagRemoteOperation;
|
||||
import com.owncloud.android.lib.resources.files.UploadFileRemoteOperation;
|
||||
import com.owncloud.android.operations.CopyFileOperation;
|
||||
import com.owncloud.android.operations.CreateFolderOperation;
|
||||
|
@ -72,7 +72,6 @@ import com.owncloud.android.operations.MoveFileOperation;
|
|||
import com.owncloud.android.operations.RefreshFolderOperation;
|
||||
import com.owncloud.android.operations.RemoveFileOperation;
|
||||
import com.owncloud.android.operations.RenameFileOperation;
|
||||
import com.owncloud.android.operations.SynchronizeFileOperation;
|
||||
import com.owncloud.android.ui.activity.SettingsActivity;
|
||||
import com.owncloud.android.utils.FileStorageUtils;
|
||||
import com.owncloud.android.utils.MimeTypeUtil;
|
||||
|
@ -197,26 +196,12 @@ public class DocumentsStorageProvider extends DocumentsProvider {
|
|||
|
||||
OCFile ocFile = document.getFile();
|
||||
Account account = document.getAccount();
|
||||
final User user = accountManager.getUser(account.name).orElseThrow(RuntimeException::new); // should exist
|
||||
|
||||
if (ocFile.isDown()) {
|
||||
RemoteOperationResult result;
|
||||
try {
|
||||
result = new SynchronizeFileOperation(ocFile, null, user, true, context)
|
||||
.execute(document.getClient(), document.getStorageManager());
|
||||
} catch (Exception e) {
|
||||
throw getFileNotFoundExceptionWithCause("Error synchronizing file: " + ocFile.getFileName(), e);
|
||||
}
|
||||
if (result.getCode() == RemoteOperationResult.ResultCode.SYNC_CONFLICT) {
|
||||
boolean needsDownload = !ocFile.isDown() || hasServerChange(document);
|
||||
if (needsDownload) {
|
||||
if (ocFile.getLocalModificationTimestamp() > ocFile.getLastSyncDateForData()) {
|
||||
// TODO show a conflict notification with a pending intent that shows a ConflictResolveDialog
|
||||
Log_OC.w(TAG, "Conflict found: " + result);
|
||||
} else if (!result.isSuccess()) {
|
||||
Log_OC.e(TAG, result.toString());
|
||||
throw new FileNotFoundException("Error synchronizing file: " + ocFile.getFileName());
|
||||
}
|
||||
// TODO test if this needed here
|
||||
// block thread until file is saved
|
||||
FileStorageUtils.checkIfFileFinishedSaving(ocFile);
|
||||
Log_OC.w(TAG, "Conflict found!");
|
||||
} else {
|
||||
DownloadFileOperation downloadFileOperation = new DownloadFileOperation(account, ocFile, context);
|
||||
RemoteOperationResult result = downloadFileOperation.execute(document.getClient());
|
||||
|
@ -226,12 +211,12 @@ public class DocumentsStorageProvider extends DocumentsProvider {
|
|||
}
|
||||
saveDownloadedFile(document.getStorageManager(), downloadFileOperation, ocFile);
|
||||
}
|
||||
}
|
||||
|
||||
File file = new File(ocFile.getStoragePath());
|
||||
int accessMode = ParcelFileDescriptor.parseMode(mode);
|
||||
boolean isWrite = accessMode != ParcelFileDescriptor.MODE_READ_ONLY;
|
||||
|
||||
if (isWrite) {
|
||||
int accessMode = ParcelFileDescriptor.parseMode(mode);
|
||||
if (accessMode != ParcelFileDescriptor.MODE_READ_ONLY) {
|
||||
// The calling thread is not guaranteed to have a Looper, so we can't block it with the OnCloseListener.
|
||||
// Thus, we are unable to do a synchronous upload and have to start an asynchronous one.
|
||||
Handler handler = new Handler(context.getMainLooper());
|
||||
|
@ -264,6 +249,23 @@ public class DocumentsStorageProvider extends DocumentsProvider {
|
|||
}
|
||||
}
|
||||
|
||||
private boolean hasServerChange(Document document) throws FileNotFoundException {
|
||||
Context context = getNonNullContext();
|
||||
OCFile ocFile = document.getFile();
|
||||
RemoteOperationResult result = new CheckEtagRemoteOperation(ocFile.getRemotePath(), ocFile.getEtag())
|
||||
.execute(document.getAccount(), context);
|
||||
switch (result.getCode()) {
|
||||
case ETAG_CHANGED:
|
||||
return true;
|
||||
case ETAG_UNCHANGED:
|
||||
return false;
|
||||
case FILE_NOT_FOUND:
|
||||
default:
|
||||
Log_OC.e(TAG, result.toString());
|
||||
throw new FileNotFoundException("Error synchronizing file: " + ocFile.getFileName());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the OC File after a successful download.
|
||||
*
|
||||
|
|
Loading…
Reference in a new issue