-/* ownCloud Android client application
+/**
+ * ownCloud Android client application
+ *
+ * @author David A. Velasco
+ * @author masensio
* Copyright (C) 2012 Bartek Przybylski
- * Copyright (C) 2012-2013 ownCloud Inc.
+ * Copyright (C) 2015 ownCloud Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2,
package com.owncloud.android.operations;
-import org.apache.http.HttpStatus;
-import org.apache.jackrabbit.webdav.MultiStatus;
-import org.apache.jackrabbit.webdav.client.methods.PropFindMethod;
+import com.owncloud.android.MainApp;
+import com.owncloud.android.datamodel.OCFile;
+import com.owncloud.android.files.services.FileDownloader;
+import com.owncloud.android.files.services.FileUploader;
+import com.owncloud.android.lib.common.OwnCloudClient;
+import com.owncloud.android.lib.resources.files.RemoteFile;
+import com.owncloud.android.lib.common.operations.RemoteOperationResult;
+import com.owncloud.android.lib.common.operations.RemoteOperationResult.ResultCode;
+import com.owncloud.android.lib.common.utils.Log_OC;
+import com.owncloud.android.lib.resources.files.ReadRemoteFileOperation;
+import com.owncloud.android.operations.common.SyncOperation;
+import com.owncloud.android.utils.FileStorageUtils;
import android.accounts.Account;
import android.content.Context;
import android.content.Intent;
-import com.owncloud.android.Log_OC;
-import com.owncloud.android.datamodel.DataStorageManager;
-import com.owncloud.android.datamodel.OCFile;
-import com.owncloud.android.files.services.FileDownloader;
-import com.owncloud.android.files.services.FileUploader;
-import com.owncloud.android.operations.RemoteOperationResult.ResultCode;
-
-import eu.alefzero.webdav.WebdavClient;
-import eu.alefzero.webdav.WebdavEntry;
-import eu.alefzero.webdav.WebdavUtils;
+/**
+ * Remote operation performing the read of remote file in the ownCloud server.
+ */
-public class SynchronizeFileOperation extends RemoteOperation {
+public class SynchronizeFileOperation extends SyncOperation {
private String TAG = SynchronizeFileOperation.class.getSimpleName();
- private static final int SYNC_READ_TIMEOUT = 10000;
- private static final int SYNC_CONNECTION_TIMEOUT = 5000;
private OCFile mLocalFile;
+ private String mRemotePath;
private OCFile mServerFile;
- private DataStorageManager mStorageManager;
private Account mAccount;
private boolean mSyncFileContents;
- private boolean mLocalChangeAlreadyKnown;
private Context mContext;
private boolean mTransferWasRequested = false;
+
+ /**
+ * When 'false', uploads to the server are not done; only downloads or conflict detection.
+ * This is a temporal field.
+ * TODO Remove when 'folder synchronization' replaces 'folder download'.
+ */
+ private boolean mAllowUploads;
+
+ /**
+ * Constructor for "full synchronization mode".
+ *
+ * Uses remotePath to retrieve all the data both in local cache and in the remote OC server
+ * when the operation is executed, instead of reusing {@link OCFile} instances.
+ *
+ * Useful for direct synchronization of a single file.
+ *
+ * @param
+ * @param account ownCloud account holding the file.
+ * @param syncFileContents When 'true', transference of data will be started by the
+ * operation if needed and no conflict is detected.
+ * @param context Android context; needed to start transfers.
+ */
+ public SynchronizeFileOperation(
+ String remotePath,
+ Account account,
+ boolean syncFileContents,
+ Context context) {
+
+ mRemotePath = remotePath;
+ mLocalFile = null;
+ mServerFile = null;
+ mAccount = account;
+ mSyncFileContents = syncFileContents;
+ mContext = context;
+ mAllowUploads = true;
+ }
+
+
+ /**
+ * Constructor allowing to reuse {@link OCFile} instances just queried from local cache or
+ * from remote OC server.
+ *
+ * Useful to include this operation as part of the synchronization of a folder
+ * (or a full account), avoiding the repetition of fetch operations (both in local database
+ * or remote server).
+ *
+ * At least one of localFile or serverFile MUST NOT BE NULL. If you don't have none of them,
+ * use the other constructor.
+ *
+ * @param localFile Data of file (just) retrieved from local cache/database.
+ * @param serverFile Data of file (just) retrieved from a remote server. If null,
+ * will be retrieved from network by the operation when executed.
+ * @param account ownCloud account holding the file.
+ * @param syncFileContents When 'true', transference of data will be started by the
+ * operation if needed and no conflict is detected.
+ * @param context Android context; needed to start transfers.
+ */
public SynchronizeFileOperation(
OCFile localFile,
- OCFile serverFile, // make this null to let the operation checks the server; added to reuse info from SynchronizeFolderOperation
- DataStorageManager storageManager,
+ OCFile serverFile,
Account account,
boolean syncFileContents,
- boolean localChangeAlreadyKnown,
Context context) {
mLocalFile = localFile;
mServerFile = serverFile;
- mStorageManager = storageManager;
+ if (mLocalFile != null) {
+ mRemotePath = mLocalFile.getRemotePath();
+ if (mServerFile != null && !mServerFile.getRemotePath().equals(mRemotePath)) {
+ throw new IllegalArgumentException("serverFile and localFile do not correspond" +
+ " to the same OC file");
+ }
+ } else if (mServerFile != null) {
+ mRemotePath = mServerFile.getRemotePath();
+ } else {
+ throw new IllegalArgumentException("Both serverFile and localFile are NULL");
+ }
mAccount = account;
mSyncFileContents = syncFileContents;
- mLocalChangeAlreadyKnown = localChangeAlreadyKnown;
mContext = context;
+ mAllowUploads = true;
}
+
+ /**
+ * Temporal constructor.
+ *
+ * Extends the previous one to allow constrained synchronizations where uploads are never
+ * performed - only downloads or conflict detection.
+ *
+ * Do not use unless you are involved in 'folder synchronization' or 'folder download' work
+ * in progress.
+ *
+ * TODO Remove when 'folder synchronization' replaces 'folder download'.
+ *
+ * @param localFile Data of file (just) retrieved from local cache/database.
+ * MUSTN't be null.
+ * @param serverFile Data of file (just) retrieved from a remote server.
+ * If null, will be retrieved from network by the operation
+ * when executed.
+ * @param account ownCloud account holding the file.
+ * @param syncFileContents When 'true', transference of data will be started by the
+ * operation if needed and no conflict is detected.
+ * @param allowUploads When 'false', uploads to the server are not done;
+ * only downloads or conflict detection.
+ * @param context Android context; needed to start transfers.
+ */
+ public SynchronizeFileOperation(
+ OCFile localFile,
+ OCFile serverFile,
+ Account account,
+ boolean syncFileContents,
+ boolean allowUploads,
+ Context context) {
+
+ this(localFile, serverFile, account, syncFileContents, context);
+ mAllowUploads = allowUploads;
+ }
+
@Override
- protected RemoteOperationResult run(WebdavClient client) {
-
- PropFindMethod propfind = null;
+ protected RemoteOperationResult run(OwnCloudClient client) {
+
RemoteOperationResult result = null;
mTransferWasRequested = false;
- try {
- if (!mLocalFile.isDown()) {
- /// easy decision
- requestForDownload(mLocalFile);
- result = new RemoteOperationResult(ResultCode.OK);
-
- } else {
- /// local copy in the device -> need to think a bit more before do anything
-
- if (mServerFile == null) {
- /// take the duty of check the server for the current state of the file there
- propfind = new PropFindMethod(client.getBaseUri() + WebdavUtils.encodePath(mLocalFile.getRemotePath()));
- int status = client.executeMethod(propfind, SYNC_READ_TIMEOUT, SYNC_CONNECTION_TIMEOUT);
- boolean isMultiStatus = status == HttpStatus.SC_MULTI_STATUS;
- if (isMultiStatus) {
- MultiStatus resp = propfind.getResponseBodyAsMultiStatus();
- WebdavEntry we = new WebdavEntry(resp.getResponses()[0],
- client.getBaseUri().getPath());
- mServerFile = fillOCFile(we);
- mServerFile.setLastSyncDateForProperties(System.currentTimeMillis());
-
- } else {
- client.exhaustResponse(propfind.getResponseBodyAsStream());
- result = new RemoteOperationResult(false, status, propfind.getResponseHeaders());
- }
+
+ if (mLocalFile == null) {
+ // Get local file from the DB
+ mLocalFile = getStorageManager().getFileByPath(mRemotePath);
+ }
+
+ if (!mLocalFile.isDown()) {
+ /// easy decision
+ requestForDownload(mLocalFile);
+ result = new RemoteOperationResult(ResultCode.OK);
+
+ } else {
+ /// local copy in the device -> need to think a bit more before do anything
+
+ if (mServerFile == null) {
+ ReadRemoteFileOperation operation = new ReadRemoteFileOperation(mRemotePath);
+ result = operation.execute(client);
+ if (result.isSuccess()){
+ mServerFile = FileStorageUtils.fillOCFile((RemoteFile) result.getData().get(0));
+ mServerFile.setLastSyncDateForProperties(System.currentTimeMillis());
}
-
- if (result == null) { // true if the server was not checked. nothing was wrong with the remote request
-
- /// check changes in server and local file
- boolean serverChanged = false;
+ }
+
+ if (mServerFile != null) {
+
+ /// check changes in server and local file
+ boolean serverChanged = false;
+ /* time for eTag is coming, but not yet
if (mServerFile.getEtag() != null) {
- serverChanged = (!mServerFile.getEtag().equals(mLocalFile.getEtag())); // TODO could this be dangerous when the user upgrades the server from non-tagged to tagged?
+ serverChanged = (!mServerFile.getEtag().equals(mLocalFile.getEtag()));
+ } else { */
+ serverChanged = (
+ mServerFile.getModificationTimestamp() !=
+ mLocalFile.getModificationTimestampAtLastSyncForData()
+ );
+ //}
+ boolean localChanged = (
+ mLocalFile.getLocalModificationTimestamp() > mLocalFile.getLastSyncDateForData()
+ );
+
+ /// decide action to perform depending upon changes
+ //if (!mLocalFile.getEtag().isEmpty() && localChanged && serverChanged) {
+ if (localChanged && serverChanged) {
+ result = new RemoteOperationResult(ResultCode.SYNC_CONFLICT);
+
+ } else if (localChanged) {
+ if (mSyncFileContents && mAllowUploads) {
+ requestForUpload(mLocalFile);
+ // the local update of file properties will be done by the FileUploader
+ // service when the upload finishes
} else {
- // server without etags
- serverChanged = (mServerFile.getModificationTimestamp() > mLocalFile.getModificationTimestampAtLastSyncForData());
+ // NOTHING TO DO HERE: updating the properties of the file in the server
+ // without uploading the contents would be stupid;
+ // So, an instance of SynchronizeFileOperation created with
+ // syncFileContents == false is completely useless when we suspect
+ // that an upload is necessary (for instance, in FileObserverService).
}
- boolean localChanged = (mLocalChangeAlreadyKnown || mLocalFile.getLocalModificationTimestamp() > mLocalFile.getLastSyncDateForData());
- // TODO this will be always true after the app is upgraded to database version 2; will result in unnecessary uploads
-
- /// decide action to perform depending upon changes
- if (localChanged && serverChanged) {
- result = new RemoteOperationResult(ResultCode.SYNC_CONFLICT);
-
- } else if (localChanged) {
- if (mSyncFileContents) {
- requestForUpload(mLocalFile);
- // the local update of file properties will be done by the FileUploader service when the upload finishes
- } else {
- // NOTHING TO DO HERE: updating the properties of the file in the server without uploading the contents would be stupid;
- // So, an instance of SynchronizeFileOperation created with syncFileContents == false is completely useless when we suspect
- // that an upload is necessary (for instance, in FileObserverService).
- }
- result = new RemoteOperationResult(ResultCode.OK);
-
- } else if (serverChanged) {
- if (mSyncFileContents) {
- requestForDownload(mLocalFile); // local, not server; we won't to keep the value of keepInSync!
- // the update of local data will be done later by the FileUploader service when the upload finishes
- } else {
- // TODO CHECK: is this really useful in some point in the code?
- mServerFile.setKeepInSync(mLocalFile.keepInSync());
- mServerFile.setLastSyncDateForData(mLocalFile.getLastSyncDateForData());
- mServerFile.setStoragePath(mLocalFile.getStoragePath());
- mServerFile.setParentId(mLocalFile.getParentId());
- mStorageManager.saveFile(mServerFile);
-
- }
- result = new RemoteOperationResult(ResultCode.OK);
-
+ result = new RemoteOperationResult(ResultCode.OK);
+
+ } else if (serverChanged) {
+ mLocalFile.setRemoteId(mServerFile.getRemoteId());
+
+ if (mSyncFileContents) {
+ requestForDownload(mLocalFile); // local, not server; we won't to keep
+ // the value of keepInSync!
+ // the update of local data will be done later by the FileUploader
+ // service when the upload finishes
} else {
- // nothing changed, nothing to do
- result = new RemoteOperationResult(ResultCode.OK);
+ // TODO CHECK: is this really useful in some point in the code?
+ mServerFile.setKeepInSync(mLocalFile.keepInSync());
+ mServerFile.setLastSyncDateForData(mLocalFile.getLastSyncDateForData());
+ mServerFile.setStoragePath(mLocalFile.getStoragePath());
+ mServerFile.setParentId(mLocalFile.getParentId());
+ getStorageManager().saveFile(mServerFile);
+
}
-
- }
-
- }
-
- Log_OC.i(TAG, "Synchronizing " + mAccount.name + ", file " + mLocalFile.getRemotePath() + ": " + result.getLogMessage());
-
- } catch (Exception e) {
- result = new RemoteOperationResult(e);
- Log_OC.e(TAG, "Synchronizing " + mAccount.name + ", file " + (mLocalFile != null ? mLocalFile.getRemotePath() : "NULL") + ": " + result.getLogMessage(), result.getException());
-
- } finally {
- if (propfind != null)
- propfind.releaseConnection();
+ result = new RemoteOperationResult(ResultCode.OK);
+
+ } else {
+ // nothing changed, nothing to do
+ result = new RemoteOperationResult(ResultCode.OK);
+ }
+
+ }
+
}
+
+ Log_OC.i(TAG, "Synchronizing " + mAccount.name + ", file " + mLocalFile.getRemotePath() +
+ ": " + result.getLogMessage());
+
return result;
}
Intent i = new Intent(mContext, FileUploader.class);
i.putExtra(FileUploader.KEY_ACCOUNT, mAccount);
i.putExtra(FileUploader.KEY_FILE, file);
- /*i.putExtra(FileUploader.KEY_REMOTE_FILE, mRemotePath); // doing this we would lose the value of keepInSync in the road, and maybe it's not updated in the database when the FileUploader service gets it!
+ /*i.putExtra(FileUploader.KEY_REMOTE_FILE, mRemotePath);
+ // doing this we would lose the value of keepInSync in the road, and maybe
+ // it's not updated in the database when the FileUploader service gets it!
i.putExtra(FileUploader.KEY_LOCAL_FILE, localFile.getStoragePath());*/
i.putExtra(FileUploader.KEY_UPLOAD_TYPE, FileUploader.UPLOAD_SINGLE_FILE);
i.putExtra(FileUploader.KEY_FORCE_OVERWRITE, true);
}
- /**
- * Creates and populates a new {@link OCFile} object with the data read from the server.
- *
- * @param we WebDAV entry read from the server for a WebDAV resource (remote file or folder).
- * @return New OCFile instance representing the remote resource described by we.
- */
- private OCFile fillOCFile(WebdavEntry we) {
- OCFile file = new OCFile(we.decodedPath());
- file.setCreationTimestamp(we.createTimestamp());
- file.setFileLength(we.contentLength());
- file.setMimetype(we.contentType());
- file.setModificationTimestamp(we.modifiedTimestamp());
- return file;
- }
-
-
public boolean transferWasRequested() {
return mTransferWasRequested;
}