X-Git-Url: http://git.linex4red.de/pub/Android/ownCloud.git/blobdiff_plain/92080afefcab6de830d6a5c099b04e3c3a7db47f..00482b6dcb1e98da39df54374f7098b51d66bee5:/src/com/owncloud/android/operations/SynchronizeFileOperation.java diff --git a/src/com/owncloud/android/operations/SynchronizeFileOperation.java b/src/com/owncloud/android/operations/SynchronizeFileOperation.java index 62f61931..27b0b5ad 100644 --- a/src/com/owncloud/android/operations/SynchronizeFileOperation.java +++ b/src/com/owncloud/android/operations/SynchronizeFileOperation.java @@ -1,10 +1,14 @@ -/* ownCloud Android client application +/** + * ownCloud Android client application + * + * @author David A. Velasco + * @author masensio * Copyright (C) 2012 Bartek Przybylski + * 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 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 @@ -18,93 +22,300 @@ 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.util.Log; +import android.content.Intent; -import com.owncloud.android.datamodel.DataStorageManager; -import com.owncloud.android.datamodel.OCFile; - -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 OCFile mLocalFile; private String mRemotePath; - - private DataStorageManager mStorageManager; - + private OCFile mServerFile; private Account mAccount; + private boolean mSyncFileContents; + 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, - DataStorageManager dataStorageManager, + String remotePath, Account account, - Context context ) { + boolean syncFileContents, + Context context) { + mRemotePath = remotePath; - mStorageManager = dataStorageManager; + 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, + Account account, + boolean syncFileContents, + Context context) { + + mLocalFile = localFile; + mServerFile = serverFile; + 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; + 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; - 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 == 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 (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())); + } 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 { + // 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) { + 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 { + // 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); + + } + 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 file OCFile object representing the file to upload + */ + 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 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 download */ - 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 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; } }