/* ownCloud Android client application
* Copyright (C) 2012 Bartek Przybylski
+ * Copyright (C) 2012-2013 ownCloud Inc.
*
* This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
+ * it under the terms of the GNU General Public License version 2,
+ * as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
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.datamodel.FileDataStorageManager;
+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.RemoteOperation;
+import com.owncloud.android.lib.common.operations.RemoteOperationResult;
+import com.owncloud.android.lib.common.operations.RemoteOperationResult.ResultCode;
+import com.owncloud.android.lib.resources.files.ReadRemoteFileOperation;
+import com.owncloud.android.utils.FileStorageUtils;
+import com.owncloud.android.utils.Log_OC;
import android.accounts.Account;
import android.content.Context;
-import android.util.Log;
-
-import com.owncloud.android.datamodel.DataStorageManager;
-import com.owncloud.android.datamodel.OCFile;
+import android.content.Intent;
-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.
+ *
+ * @author David A. Velasco
+ * @author masensio
+ */
public class SynchronizeFileOperation extends RemoteOperation {
private String TAG = SynchronizeFileOperation.class.getSimpleName();
- private String mRemotePath;
-
- private DataStorageManager mStorageManager;
-
+ private OCFile mLocalFile;
+ private OCFile mServerFile;
+ private FileDataStorageManager mStorageManager;
private Account mAccount;
+ private boolean mSyncFileContents;
+ private Context mContext;
+
+ private boolean mTransferWasRequested = false;
public SynchronizeFileOperation(
- String remotePath,
- DataStorageManager dataStorageManager,
+ OCFile localFile,
+ OCFile serverFile, // make this null to let the operation checks the server; added to reuse info from SynchronizeFolderOperation
+ FileDataStorageManager storageManager,
Account account,
- Context context ) {
- mRemotePath = remotePath;
- mStorageManager = dataStorageManager;
+ boolean syncFileContents,
+ Context context) {
+
+ mLocalFile = localFile;
+ mServerFile = serverFile;
+ mStorageManager = storageManager;
mAccount = account;
+ mSyncFileContents = syncFileContents;
+ mContext = context;
}
+
@Override
- protected RemoteOperationResult run(WebdavClient client) {
- PropFindMethod propfind = null;
+ protected RemoteOperationResult run(OwnCloudClient client) {
+
RemoteOperationResult result = null;
- try {
- propfind = new PropFindMethod(client.getBaseUri() + WebdavUtils.encodePath(mRemotePath));
- int status = client.executeMethod(propfind);
- boolean isMultiStatus = status == HttpStatus.SC_MULTI_STATUS;
- Boolean isConflict = Boolean.FALSE;
- if (isMultiStatus) {
- MultiStatus resp = propfind.getResponseBodyAsMultiStatus();
- WebdavEntry we = new WebdavEntry(resp.getResponses()[0],
- client.getBaseUri().getPath());
- OCFile file = fillOCFile(we);
- OCFile oldFile = mStorageManager.getFileByPath(file.getRemotePath());
- if (oldFile.getFileLength() != file.getFileLength() ||
- oldFile.getModificationTimestamp() != file.getModificationTimestamp()) {
- isConflict = Boolean.TRUE;
- }
-
- } else {
- client.exhaustResponse(propfind.getResponseBodyAsStream());
- }
-
- result = new RemoteOperationResult(isMultiStatus, status);
- result.setExtraData(isConflict);
- Log.i(TAG, "Synchronizing " + mAccount.name + ", file " + mRemotePath + ": " + result.getLogMessage());
- } catch (Exception e) {
- result = new RemoteOperationResult(e);
- Log.e(TAG, "Synchronizing " + mAccount.name + ", file " + mRemotePath + ": " + result.getLogMessage(), result.getException());
-
- } finally {
- if (propfind != null)
- propfind.releaseConnection();
+ mTransferWasRequested = false;
+ 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) {
+ String remotePath = mLocalFile.getRemotePath();
+ ReadRemoteFileOperation operation = new ReadRemoteFileOperation(remotePath);
+ result = operation.execute(client);
+ if (result.isSuccess()){
+ mServerFile = FileStorageUtils.fillOCFile((RemoteFile) result.getData().get(0));
+ mServerFile.setLastSyncDateForProperties(System.currentTimeMillis());
+ }
+ }
+
+ 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?
+ } else { */
+ // server without etags
+ serverChanged = (mServerFile.getModificationTimestamp() != mLocalFile.getModificationTimestampAtLastSyncForData());
+ //}
+ boolean localChanged = (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 (!mLocalFile.getEtag().isEmpty() && localChanged && serverChanged) {
+ 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);
+
+ } 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;
}
+
/**
- * Creates and populates a new {@link OCFile} object with the data read from the server.
+ * Requests for an upload to the FileUploader service
*
- * @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.
+ * @param file OCFile object representing the file to upload
*/
- 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.modifiedTimesamp());
- file.setLastSyncDate(System.currentTimeMillis());
- return file;
+ private void requestForUpload(OCFile file) {
+ 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_LOCAL_FILE, localFile.getStoragePath());*/
+ i.putExtra(FileUploader.KEY_UPLOAD_TYPE, FileUploader.UPLOAD_SINGLE_FILE);
+ i.putExtra(FileUploader.KEY_FORCE_OVERWRITE, true);
+ mContext.startService(i);
+ mTransferWasRequested = true;
+ }
+
+
+ /**
+ * Requests for a download to the FileDownloader service
+ *
+ * @param file OCFile object representing the file to download
+ */
+ private void requestForDownload(OCFile file) {
+ Intent i = new Intent(mContext, FileDownloader.class);
+ i.putExtra(FileDownloader.EXTRA_ACCOUNT, mAccount);
+ i.putExtra(FileDownloader.EXTRA_FILE, file);
+ mContext.startService(i);
+ mTransferWasRequested = true;
+ }
+
+
+ public boolean transferWasRequested() {
+ return mTransferWasRequested;
+ }
+
+
+ public OCFile getLocalFile() {
+ return mLocalFile;
}
}