|
@@ -2,7 +2,7 @@
|
|
|
* ownCloud Android client application
|
|
|
*
|
|
|
* @author David A. Velasco
|
|
|
- * Copyright (C) 2015 ownCloud Inc.
|
|
|
+ * Copyright (C) 2016 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,
|
|
@@ -20,28 +20,14 @@
|
|
|
|
|
|
package com.owncloud.android.operations;
|
|
|
|
|
|
-import java.io.File;
|
|
|
-import java.io.FileInputStream;
|
|
|
-import java.io.FileOutputStream;
|
|
|
-import java.io.IOException;
|
|
|
-import java.io.InputStream;
|
|
|
-import java.io.OutputStream;
|
|
|
-import java.nio.channels.FileChannel;
|
|
|
-import java.util.HashSet;
|
|
|
-import java.util.Iterator;
|
|
|
-import java.util.Set;
|
|
|
-import java.util.concurrent.atomic.AtomicBoolean;
|
|
|
-
|
|
|
-import org.apache.commons.httpclient.HttpStatus;
|
|
|
-import org.apache.commons.httpclient.methods.RequestEntity;
|
|
|
-
|
|
|
import android.accounts.Account;
|
|
|
import android.content.Context;
|
|
|
import android.net.Uri;
|
|
|
|
|
|
-import com.owncloud.android.MainApp;
|
|
|
import com.owncloud.android.datamodel.FileDataStorageManager;
|
|
|
import com.owncloud.android.datamodel.OCFile;
|
|
|
+import com.owncloud.android.db.OCUpload;
|
|
|
+import com.owncloud.android.db.PreferenceReader;
|
|
|
import com.owncloud.android.files.services.FileUploader;
|
|
|
import com.owncloud.android.lib.common.OwnCloudClient;
|
|
|
import com.owncloud.android.lib.common.network.OnDatatransferProgressListener;
|
|
@@ -53,46 +39,140 @@ import com.owncloud.android.lib.common.operations.RemoteOperationResult.ResultCo
|
|
|
import com.owncloud.android.lib.common.utils.Log_OC;
|
|
|
import com.owncloud.android.lib.resources.files.ChunkedUploadRemoteFileOperation;
|
|
|
import com.owncloud.android.lib.resources.files.ExistenceCheckRemoteOperation;
|
|
|
+import com.owncloud.android.lib.resources.files.ReadRemoteFileOperation;
|
|
|
+import com.owncloud.android.lib.resources.files.RemoteFile;
|
|
|
import com.owncloud.android.lib.resources.files.UploadRemoteFileOperation;
|
|
|
+import com.owncloud.android.operations.common.SyncOperation;
|
|
|
+import com.owncloud.android.utils.ConnectivityUtils;
|
|
|
import com.owncloud.android.utils.FileStorageUtils;
|
|
|
+import com.owncloud.android.utils.MimetypeIconUtil;
|
|
|
import com.owncloud.android.utils.UriUtils;
|
|
|
|
|
|
+import org.apache.commons.httpclient.HttpStatus;
|
|
|
+import org.apache.commons.httpclient.methods.RequestEntity;
|
|
|
+
|
|
|
+import java.io.File;
|
|
|
+import java.io.FileInputStream;
|
|
|
+import java.io.FileOutputStream;
|
|
|
+import java.io.IOException;
|
|
|
+import java.io.InputStream;
|
|
|
+import java.io.OutputStream;
|
|
|
+import java.nio.channels.FileChannel;
|
|
|
+import java.util.HashSet;
|
|
|
+import java.util.Iterator;
|
|
|
+import java.util.Set;
|
|
|
+import java.util.concurrent.atomic.AtomicBoolean;
|
|
|
+
|
|
|
|
|
|
/**
|
|
|
- * Remote operation performing the upload of a file to an ownCloud server
|
|
|
+ * Operation performing the update in the ownCloud server
|
|
|
+ * of a file that was modified locally.
|
|
|
*/
|
|
|
-public class UploadFileOperation extends RemoteOperation {
|
|
|
+public class UploadFileOperation extends SyncOperation {
|
|
|
+
|
|
|
+
|
|
|
+ private static final String MIME_TYPE_PDF = "application/pdf";
|
|
|
+ private static final String FILE_EXTENSION_PDF = ".pdf";
|
|
|
+
|
|
|
+ public static final int CREATED_BY_USER = 0;
|
|
|
+ public static final int CREATED_AS_INSTANT_PICTURE = 1;
|
|
|
+ public static final int CREATED_AS_INSTANT_VIDEO = 2;
|
|
|
+
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Checks if content provider, using the content:// scheme, returns a file with mime-type
|
|
|
+ * 'application/pdf' but file has not extension
|
|
|
+ * @param localPath Full path to a file in the local file system.
|
|
|
+ * @param mimeType MIME type of the file.
|
|
|
+ * @return true if is needed to add the pdf file extension to the file
|
|
|
+ *
|
|
|
+ * TODO - move to OCFile or Utils class
|
|
|
+ */
|
|
|
+ private static boolean isPdfFileFromContentProviderWithoutExtension(String localPath,
|
|
|
+ String mimeType) {
|
|
|
+ return localPath.startsWith(UriUtils.URI_CONTENT_SCHEME) &&
|
|
|
+ mimeType.equals(MIME_TYPE_PDF) &&
|
|
|
+ !localPath.endsWith(FILE_EXTENSION_PDF);
|
|
|
+ }
|
|
|
+
|
|
|
+ public static OCFile obtainNewOCFileToUpload(String remotePath, String localPath, String mimeType) {
|
|
|
+
|
|
|
+ // MIME type
|
|
|
+ if (mimeType == null || mimeType.length() <= 0) {
|
|
|
+ mimeType = MimetypeIconUtil.getBestMimeTypeByFilename(localPath);
|
|
|
+ }
|
|
|
+
|
|
|
+ // TODO - this is a horrible special case that should not be handled this way
|
|
|
+ if (isPdfFileFromContentProviderWithoutExtension(localPath, mimeType)){
|
|
|
+ remotePath += FILE_EXTENSION_PDF;
|
|
|
+ }
|
|
|
+
|
|
|
+ OCFile newFile = new OCFile(remotePath);
|
|
|
+ newFile.setStoragePath(localPath);
|
|
|
+ newFile.setLastSyncDateForProperties(0);
|
|
|
+ newFile.setLastSyncDateForData(0);
|
|
|
+
|
|
|
+ // size
|
|
|
+ if (localPath != null && localPath.length() > 0) {
|
|
|
+ File localFile = new File(localPath);
|
|
|
+ newFile.setFileLength(localFile.length());
|
|
|
+ newFile.setLastSyncDateForData(localFile.lastModified());
|
|
|
+ } // don't worry about not assigning size, the problems with localPath
|
|
|
+ // are checked when the UploadFileOperation instance is created
|
|
|
+
|
|
|
+
|
|
|
+ newFile.setMimetype(mimeType);
|
|
|
+
|
|
|
+ return newFile;
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
|
|
|
private static final String TAG = UploadFileOperation.class.getSimpleName();
|
|
|
|
|
|
private Account mAccount;
|
|
|
+ /**
|
|
|
+ * OCFile which is to be uploaded.
|
|
|
+ */
|
|
|
private OCFile mFile;
|
|
|
+ /**
|
|
|
+ * Original OCFile which is to be uploaded in case file had to be renamed
|
|
|
+ * (if forceOverwrite==false and remote file already exists).
|
|
|
+ */
|
|
|
private OCFile mOldFile;
|
|
|
private String mRemotePath = null;
|
|
|
private boolean mChunked = false;
|
|
|
- private boolean mIsInstant = false;
|
|
|
private boolean mRemoteFolderToBeCreated = false;
|
|
|
private boolean mForceOverwrite = false;
|
|
|
private int mLocalBehaviour = FileUploader.LOCAL_BEHAVIOUR_COPY;
|
|
|
+ private int mCreatedBy = CREATED_BY_USER;
|
|
|
+
|
|
|
private boolean mWasRenamed = false;
|
|
|
private String mOriginalFileName = null;
|
|
|
+ private long mOCUploadId = -1;
|
|
|
+ /**
|
|
|
+ * Local path to file which is to be uploaded (before any possible renaming or moving).
|
|
|
+ */
|
|
|
private String mOriginalStoragePath = null;
|
|
|
private Set<OnDatatransferProgressListener> mDataTransferListeners = new HashSet<OnDatatransferProgressListener>();
|
|
|
- private AtomicBoolean mCancellationRequested = new AtomicBoolean(false);
|
|
|
+ private OnRenameListener mRenameUploadListener;
|
|
|
+
|
|
|
+ private final AtomicBoolean mCancellationRequested = new AtomicBoolean(false);
|
|
|
+ private final AtomicBoolean mUploadStarted = new AtomicBoolean(false);
|
|
|
+
|
|
|
private Context mContext;
|
|
|
-
|
|
|
+
|
|
|
private UploadRemoteFileOperation mUploadOperation;
|
|
|
|
|
|
protected RequestEntity mEntity = null;
|
|
|
|
|
|
-
|
|
|
- public UploadFileOperation( Account account,
|
|
|
- OCFile file,
|
|
|
- boolean chunked,
|
|
|
- boolean isInstant,
|
|
|
- boolean forceOverwrite,
|
|
|
- int localBehaviour,
|
|
|
- Context context) {
|
|
|
+ public UploadFileOperation(Account account,
|
|
|
+ OCFile file,
|
|
|
+ boolean chunked,
|
|
|
+ boolean forceOverwrite,
|
|
|
+ int localBehaviour,
|
|
|
+ Context context
|
|
|
+ ) {
|
|
|
if (account == null)
|
|
|
throw new IllegalArgumentException("Illegal NULL account in UploadFileOperation " +
|
|
|
"creation");
|
|
@@ -108,7 +188,6 @@ public class UploadFileOperation extends RemoteOperation {
|
|
|
mFile = file;
|
|
|
mRemotePath = file.getRemotePath();
|
|
|
mChunked = chunked;
|
|
|
- mIsInstant = isInstant;
|
|
|
mForceOverwrite = forceOverwrite;
|
|
|
mLocalBehaviour = localBehaviour;
|
|
|
mOriginalStoragePath = mFile.getStoragePath();
|
|
@@ -116,6 +195,42 @@ public class UploadFileOperation extends RemoteOperation {
|
|
|
mContext = context;
|
|
|
}
|
|
|
|
|
|
+ public UploadFileOperation(Account account,
|
|
|
+ OCUpload upload,
|
|
|
+ boolean chunked,
|
|
|
+ boolean forceOverwrite,
|
|
|
+ int localBehaviour,
|
|
|
+ Context context
|
|
|
+ ) {
|
|
|
+ if (account == null)
|
|
|
+ throw new IllegalArgumentException("Illegal NULL account in UploadFileOperation " +
|
|
|
+ "creation");
|
|
|
+ if (upload == null)
|
|
|
+ throw new IllegalArgumentException("Illegal NULL file in UploadFileOperation creation");
|
|
|
+ if (upload.getLocalPath() == null || upload.getLocalPath().length() <= 0) {
|
|
|
+ throw new IllegalArgumentException(
|
|
|
+ "Illegal file in UploadFileOperation; storage path invalid: "
|
|
|
+ + upload.getLocalPath());
|
|
|
+ }
|
|
|
+
|
|
|
+ mAccount = account;
|
|
|
+ mFile = obtainNewOCFileToUpload(
|
|
|
+ upload.getRemotePath(),
|
|
|
+ upload.getLocalPath(),
|
|
|
+ upload.getMimeType()
|
|
|
+ );
|
|
|
+ mRemotePath = upload.getRemotePath();
|
|
|
+ mChunked = chunked;
|
|
|
+ mForceOverwrite = forceOverwrite;
|
|
|
+ mLocalBehaviour = localBehaviour;
|
|
|
+ mOriginalStoragePath = mFile.getStoragePath();
|
|
|
+ mOriginalFileName = mFile.getFileName();
|
|
|
+ mContext = context;
|
|
|
+ mOCUploadId = upload.getUploadId();
|
|
|
+ mCreatedBy = upload.getCreadtedBy();
|
|
|
+ mRemoteFolderToBeCreated = upload.isCreateRemoteFolder();
|
|
|
+ }
|
|
|
+
|
|
|
public Account getAccount() {
|
|
|
return mAccount;
|
|
|
}
|
|
@@ -128,6 +243,10 @@ public class UploadFileOperation extends RemoteOperation {
|
|
|
return mFile;
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * If remote file was renamed, return original OCFile which was uploaded. Is
|
|
|
+ * null is file was not renamed.
|
|
|
+ */
|
|
|
public OCFile getOldFile() {
|
|
|
return mOldFile;
|
|
|
}
|
|
@@ -148,30 +267,48 @@ public class UploadFileOperation extends RemoteOperation {
|
|
|
return mFile.getMimetype();
|
|
|
}
|
|
|
|
|
|
- public boolean isInstant() {
|
|
|
- return mIsInstant;
|
|
|
- }
|
|
|
-
|
|
|
- public boolean isRemoteFolderToBeCreated() {
|
|
|
- return mRemoteFolderToBeCreated;
|
|
|
+ public int getLocalBehaviour() {
|
|
|
+ return mLocalBehaviour;
|
|
|
}
|
|
|
|
|
|
public void setRemoteFolderToBeCreated() {
|
|
|
mRemoteFolderToBeCreated = true;
|
|
|
}
|
|
|
|
|
|
- public boolean getForceOverwrite() {
|
|
|
- return mForceOverwrite;
|
|
|
- }
|
|
|
-
|
|
|
public boolean wasRenamed() {
|
|
|
return mWasRenamed;
|
|
|
}
|
|
|
|
|
|
+ public void setCreatedBy(int createdBy) {
|
|
|
+ mCreatedBy = createdBy;
|
|
|
+ if (createdBy < CREATED_BY_USER || CREATED_AS_INSTANT_VIDEO < createdBy) {
|
|
|
+ mCreatedBy = CREATED_BY_USER;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ public int getCreatedBy () {
|
|
|
+ return mCreatedBy;
|
|
|
+ }
|
|
|
+
|
|
|
+ public boolean isInstantPicture() {
|
|
|
+ return mCreatedBy == CREATED_AS_INSTANT_PICTURE;
|
|
|
+ }
|
|
|
+
|
|
|
+ public boolean isInstantVideo() {
|
|
|
+ return mCreatedBy == CREATED_AS_INSTANT_VIDEO;
|
|
|
+ }
|
|
|
+
|
|
|
+ public void setOCUploadId(long id){
|
|
|
+ mOCUploadId = id;
|
|
|
+ }
|
|
|
+ public long getOCUploadId() {
|
|
|
+ return mOCUploadId;
|
|
|
+ }
|
|
|
+
|
|
|
public Set<OnDatatransferProgressListener> getDataTransferListeners() {
|
|
|
return mDataTransferListeners;
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
public void addDatatransferProgressListener (OnDatatransferProgressListener listener) {
|
|
|
synchronized (mDataTransferListeners) {
|
|
|
mDataTransferListeners.add(listener);
|
|
@@ -179,8 +316,11 @@ public class UploadFileOperation extends RemoteOperation {
|
|
|
if (mEntity != null) {
|
|
|
((ProgressiveDataTransferer)mEntity).addDatatransferProgressListener(listener);
|
|
|
}
|
|
|
+ if(mUploadOperation != null){
|
|
|
+ mUploadOperation.addDatatransferProgressListener(listener);
|
|
|
+ }
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
public void removeDatatransferProgressListener(OnDatatransferProgressListener listener) {
|
|
|
synchronized (mDataTransferListeners) {
|
|
|
mDataTransferListeners.remove(listener);
|
|
@@ -188,125 +328,87 @@ public class UploadFileOperation extends RemoteOperation {
|
|
|
if (mEntity != null) {
|
|
|
((ProgressiveDataTransferer)mEntity).removeDatatransferProgressListener(listener);
|
|
|
}
|
|
|
+ if(mUploadOperation != null){
|
|
|
+ mUploadOperation.removeDatatransferProgressListener(listener);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ public void addRenameUploadListener (OnRenameListener listener) {
|
|
|
+ mRenameUploadListener = listener;
|
|
|
}
|
|
|
|
|
|
@Override
|
|
|
protected RemoteOperationResult run(OwnCloudClient client) {
|
|
|
+ mCancellationRequested.set(false);
|
|
|
+ mUploadStarted.set(true);
|
|
|
RemoteOperationResult result = null;
|
|
|
- boolean localCopyPassed = false, nameCheckPassed = false;
|
|
|
File temporalFile = null, originalFile = new File(mOriginalStoragePath), expectedFile = null;
|
|
|
+
|
|
|
try {
|
|
|
- // / rename the file to upload, if necessary
|
|
|
+
|
|
|
+ /// Check that connectivity conditions are met and delays the upload otherwise
|
|
|
+ if (delayForWifi()) {
|
|
|
+ Log_OC.d(TAG, "Upload delayed until WiFi is available: " + getRemotePath());
|
|
|
+ return new RemoteOperationResult(ResultCode.DELAYED_FOR_WIFI);
|
|
|
+ }
|
|
|
+
|
|
|
+ /// check if the file continues existing before schedule the operation
|
|
|
+ if (!originalFile.exists()) {
|
|
|
+ Log_OC.d(TAG, mOriginalStoragePath.toString() + " not exists anymore");
|
|
|
+ return new RemoteOperationResult(ResultCode.LOCAL_FILE_NOT_FOUND);
|
|
|
+ }
|
|
|
+
|
|
|
+ /// check the existence of the parent folder for the file to upload
|
|
|
+ String remoteParentPath = new File(getRemotePath()).getParent();
|
|
|
+ remoteParentPath = remoteParentPath.endsWith(OCFile.PATH_SEPARATOR) ?
|
|
|
+ remoteParentPath : remoteParentPath + OCFile.PATH_SEPARATOR;
|
|
|
+ result = grantFolderExistence(remoteParentPath, client);
|
|
|
+ if (!result.isSuccess()) {
|
|
|
+
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+
|
|
|
+ /// set parent local id in uploading file
|
|
|
+ OCFile parent = getStorageManager().getFileByPath(remoteParentPath);
|
|
|
+ mFile.setParentId(parent.getFileId());
|
|
|
+
|
|
|
+ /// automatic rename of file to upload in case of name collision in server
|
|
|
+ Log_OC.d(TAG, "Checking name collision in server");
|
|
|
if (!mForceOverwrite) {
|
|
|
String remotePath = getAvailableRemotePath(client, mRemotePath);
|
|
|
mWasRenamed = !remotePath.equals(mRemotePath);
|
|
|
if (mWasRenamed) {
|
|
|
createNewOCFile(remotePath);
|
|
|
+ Log_OC.d(TAG, "File renamed as " + remotePath);
|
|
|
}
|
|
|
+ mRemotePath = remotePath;
|
|
|
+ mRenameUploadListener.onRenameUpload();
|
|
|
}
|
|
|
- nameCheckPassed = true;
|
|
|
-
|
|
|
- String expectedPath = FileStorageUtils.getDefaultSavePathFor(mAccount.name, mFile); // /
|
|
|
- // not
|
|
|
- // before
|
|
|
- // getAvailableRemotePath()
|
|
|
- // !!!
|
|
|
- expectedFile = new File(expectedPath);
|
|
|
-
|
|
|
- // check location of local file; if not the expected, copy to a
|
|
|
- // temporal file before upload (if COPY is the expected behaviour)
|
|
|
- if (!mOriginalStoragePath.equals(expectedPath) &&
|
|
|
- mLocalBehaviour == FileUploader.LOCAL_BEHAVIOUR_COPY) {
|
|
|
-
|
|
|
- if (FileStorageUtils.getUsableSpace(mAccount.name) < originalFile.length()) {
|
|
|
- result = new RemoteOperationResult(ResultCode.LOCAL_STORAGE_FULL);
|
|
|
- return result; // error condition when the file should be
|
|
|
- // copied
|
|
|
-
|
|
|
- } else {
|
|
|
|
|
|
- String temporalPath = FileStorageUtils.getTemporalPath(mAccount.name) +
|
|
|
- mFile.getRemotePath();
|
|
|
- mFile.setStoragePath(temporalPath);
|
|
|
- temporalFile = new File(temporalPath);
|
|
|
+ if (mCancellationRequested.get()) {
|
|
|
+ throw new OperationCancelledException();
|
|
|
+ }
|
|
|
|
|
|
- File temporalParent = temporalFile.getParentFile();
|
|
|
- temporalParent.mkdirs();
|
|
|
- if (!temporalParent.isDirectory()) {
|
|
|
- throw new IOException("Unexpected error: parent directory could not be created");
|
|
|
- }
|
|
|
- temporalFile.createNewFile();
|
|
|
- if (!temporalFile.isFile()) {
|
|
|
- throw new IOException("Unexpected error: target file could not be created");
|
|
|
- }
|
|
|
+ String expectedPath = FileStorageUtils.getDefaultSavePathFor(mAccount.name, mFile);
|
|
|
+ expectedFile = new File(expectedPath);
|
|
|
|
|
|
- InputStream in = null;
|
|
|
- OutputStream out = null;
|
|
|
+ /// copy the file locally before uploading
|
|
|
+ if (mLocalBehaviour == FileUploader.LOCAL_BEHAVIOUR_COPY &&
|
|
|
+ !mOriginalStoragePath.equals(expectedPath)) {
|
|
|
|
|
|
- try {
|
|
|
+ String temporalPath = FileStorageUtils.getTemporalPath(mAccount.name) + mFile.getRemotePath();
|
|
|
+ mFile.setStoragePath(temporalPath);
|
|
|
+ temporalFile = new File(temporalPath);
|
|
|
|
|
|
- // In case document provider schema as 'content://'
|
|
|
- if (mOriginalStoragePath.startsWith(UriUtils.URI_CONTENT_SCHEME)) {
|
|
|
-
|
|
|
- Uri uri = Uri.parse(mOriginalStoragePath);
|
|
|
-
|
|
|
- in = MainApp.getAppContext().getContentResolver().openInputStream(uri);
|
|
|
- out = new FileOutputStream(temporalFile);
|
|
|
-
|
|
|
- int nRead;
|
|
|
- byte[] data = new byte[16384];
|
|
|
-
|
|
|
- while (!mCancellationRequested.get() &&
|
|
|
- (nRead = in.read(data, 0, data.length)) != -1) {
|
|
|
- out.write(data, 0, nRead);
|
|
|
- }
|
|
|
- out.flush();
|
|
|
-
|
|
|
- } else {
|
|
|
- if (!mOriginalStoragePath.equals(temporalPath)) { // preventing
|
|
|
- // weird
|
|
|
- // but
|
|
|
- // possible
|
|
|
- // situation
|
|
|
-
|
|
|
- in = new FileInputStream(originalFile);
|
|
|
- out = new FileOutputStream(temporalFile);
|
|
|
- byte[] buf = new byte[1024];
|
|
|
- int len;
|
|
|
- while (!mCancellationRequested.get() && (len = in.read(buf)) > 0) {
|
|
|
- out.write(buf, 0, len);
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- if (mCancellationRequested.get()) {
|
|
|
- result = new RemoteOperationResult(new OperationCancelledException());
|
|
|
- }
|
|
|
-
|
|
|
-
|
|
|
- } catch (Exception e) {
|
|
|
- result = new RemoteOperationResult(ResultCode.LOCAL_STORAGE_NOT_COPIED);
|
|
|
- return result;
|
|
|
-
|
|
|
- } finally {
|
|
|
- try {
|
|
|
- if (in != null)
|
|
|
- in.close();
|
|
|
- } catch (Exception e) {
|
|
|
- Log_OC.d(TAG, "Weird exception while closing input stream for " +
|
|
|
- mOriginalStoragePath + " (ignoring)", e);
|
|
|
- }
|
|
|
- try {
|
|
|
- if (out != null)
|
|
|
- out.close();
|
|
|
- } catch (Exception e) {
|
|
|
- Log_OC.d(TAG, "Weird exception while closing output stream for " +
|
|
|
- expectedPath + " (ignoring)", e);
|
|
|
- }
|
|
|
- }
|
|
|
+ result = copy(originalFile, temporalFile);
|
|
|
+ if (result != null) {
|
|
|
+ return result;
|
|
|
}
|
|
|
}
|
|
|
- localCopyPassed = (result == null);
|
|
|
+
|
|
|
+ if (mCancellationRequested.get()) {
|
|
|
+ throw new OperationCancelledException();
|
|
|
+ }
|
|
|
|
|
|
/// perform the upload
|
|
|
if ( mChunked &&
|
|
@@ -322,6 +424,7 @@ public class UploadFileOperation extends RemoteOperation {
|
|
|
while (listener.hasNext()) {
|
|
|
mUploadOperation.addDatatransferProgressListener(listener.next());
|
|
|
}
|
|
|
+
|
|
|
if (mCancellationRequested.get()) {
|
|
|
throw new OperationCancelledException();
|
|
|
}
|
|
@@ -335,53 +438,17 @@ public class UploadFileOperation extends RemoteOperation {
|
|
|
mFile.setStoragePath("");
|
|
|
} else {
|
|
|
mFile.setStoragePath(expectedPath);
|
|
|
- File fileToMove = null;
|
|
|
- if (temporalFile != null) { // FileUploader.LOCAL_BEHAVIOUR_COPY
|
|
|
- // ; see where temporalFile was
|
|
|
- // set
|
|
|
+ File fileToMove;
|
|
|
+ if (temporalFile != null) { // FileUploader.LOCAL_BEHAVIOUR_COPY
|
|
|
fileToMove = temporalFile;
|
|
|
- } else { // FileUploader.LOCAL_BEHAVIOUR_MOVE
|
|
|
+ } else { // FileUploader.LOCAL_BEHAVIOUR_MOVE
|
|
|
fileToMove = originalFile;
|
|
|
}
|
|
|
- if (!expectedFile.equals(fileToMove)) {
|
|
|
- File expectedFolder = expectedFile.getParentFile();
|
|
|
- expectedFolder.mkdirs();
|
|
|
-
|
|
|
- if (expectedFolder.isDirectory()){
|
|
|
- if (!fileToMove.renameTo(expectedFile)){
|
|
|
- // try to copy and then delete
|
|
|
- expectedFile.createNewFile();
|
|
|
- FileChannel inChannel = new FileInputStream(fileToMove).getChannel();
|
|
|
- FileChannel outChannel = new FileOutputStream(expectedFile).getChannel();
|
|
|
-
|
|
|
- try {
|
|
|
- inChannel.transferTo(0, inChannel.size(), outChannel);
|
|
|
- fileToMove.delete();
|
|
|
- } catch (Exception e){
|
|
|
- mFile.setStoragePath(null); // forget the local file
|
|
|
- // by now, treat this as a success; the file was
|
|
|
- // uploaded; the user won't like that the local file
|
|
|
- // is not linked, but this should be a very rare
|
|
|
- // fail;
|
|
|
- // the best option could be show a warning message
|
|
|
- // (but not a fail)
|
|
|
- // result = new
|
|
|
- // RemoteOperationResult(ResultCode.LOCAL_STORAGE_NOT_MOVED);
|
|
|
- // return result;
|
|
|
- }
|
|
|
- finally {
|
|
|
- if (inChannel != null) inChannel.close();
|
|
|
- if (outChannel != null) outChannel.close();
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- } else {
|
|
|
- mFile.setStoragePath(null);
|
|
|
- }
|
|
|
- }
|
|
|
+ move(fileToMove, expectedFile);
|
|
|
}
|
|
|
FileDataStorageManager.triggerMediaScan(originalFile.getAbsolutePath());
|
|
|
FileDataStorageManager.triggerMediaScan(expectedFile.getAbsolutePath());
|
|
|
+
|
|
|
} else if (result.getHttpCode() == HttpStatus.SC_PRECONDITION_FAILED ) {
|
|
|
result = new RemoteOperationResult(ResultCode.SYNC_CONFLICT);
|
|
|
}
|
|
@@ -390,6 +457,7 @@ public class UploadFileOperation extends RemoteOperation {
|
|
|
result = new RemoteOperationResult(e);
|
|
|
|
|
|
} finally {
|
|
|
+ mUploadStarted.set(false);
|
|
|
if (temporalFile != null && !originalFile.equals(temporalFile)) {
|
|
|
temporalFile.delete();
|
|
|
}
|
|
@@ -401,16 +469,14 @@ public class UploadFileOperation extends RemoteOperation {
|
|
|
result.getLogMessage());
|
|
|
} else {
|
|
|
if (result.getException() != null) {
|
|
|
- String complement = "";
|
|
|
- if (!nameCheckPassed) {
|
|
|
- complement = " (while checking file existence in server)";
|
|
|
- } else if (!localCopyPassed) {
|
|
|
- complement = " (while copying local file to " +
|
|
|
- FileStorageUtils.getSavePath(mAccount.name)
|
|
|
- + ")";
|
|
|
+ if(result.isCancelled()){
|
|
|
+ Log_OC.w(TAG, "Upload of " + mOriginalStoragePath + " to " + mRemotePath +
|
|
|
+ ": " + result.getLogMessage());
|
|
|
+ } else {
|
|
|
+ Log_OC.e(TAG, "Upload of " + mOriginalStoragePath + " to " + mRemotePath +
|
|
|
+ ": " + result.getLogMessage(), result.getException());
|
|
|
}
|
|
|
- Log_OC.e(TAG, "Upload of " + mOriginalStoragePath + " to " + mRemotePath +
|
|
|
- ": " + result.getLogMessage() + complement, result.getException());
|
|
|
+
|
|
|
} else {
|
|
|
Log_OC.e(TAG, "Upload of " + mOriginalStoragePath + " to " + mRemotePath +
|
|
|
": " + result.getLogMessage());
|
|
@@ -418,9 +484,93 @@ public class UploadFileOperation extends RemoteOperation {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ if (result.isSuccess()) {
|
|
|
+ saveUploadedFile(client);
|
|
|
+
|
|
|
+ } else if (result.getCode() == ResultCode.SYNC_CONFLICT) {
|
|
|
+ getStorageManager().saveConflict(mFile, mFile.getEtagInConflict());
|
|
|
+ }
|
|
|
+
|
|
|
return result;
|
|
|
}
|
|
|
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Checks origin of current upload and network type to decide if should be delayed, according to
|
|
|
+ * current user preferences.
|
|
|
+ *
|
|
|
+ * @return 'True' if the upload was delayed until WiFi connectivity is available, 'false' otherwise.
|
|
|
+ */
|
|
|
+ private boolean delayForWifi() {
|
|
|
+ boolean delayInstantPicture = (
|
|
|
+ isInstantPicture() && PreferenceReader.instantPictureUploadViaWiFiOnly(mContext)
|
|
|
+ );
|
|
|
+ boolean delayInstantVideo = (
|
|
|
+ isInstantVideo() && PreferenceReader.instantVideoUploadViaWiFiOnly(mContext)
|
|
|
+ );
|
|
|
+ return (
|
|
|
+ (delayInstantPicture || delayInstantVideo) &&
|
|
|
+ !ConnectivityUtils.isAppConnectedViaWiFi(mContext)
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Checks the existence of the folder where the current file will be uploaded both
|
|
|
+ * in the remote server and in the local database.
|
|
|
+ * <p/>
|
|
|
+ * If the upload is set to enforce the creation of the folder, the method tries to
|
|
|
+ * create it both remote and locally.
|
|
|
+ *
|
|
|
+ * @param pathToGrant Full remote path whose existence will be granted.
|
|
|
+ * @return An {@link OCFile} instance corresponding to the folder where the file
|
|
|
+ * will be uploaded.
|
|
|
+ */
|
|
|
+ private RemoteOperationResult grantFolderExistence(String pathToGrant, OwnCloudClient client) {
|
|
|
+ RemoteOperation operation = new ExistenceCheckRemoteOperation(pathToGrant, mContext, false);
|
|
|
+ RemoteOperationResult result = operation.execute(client);
|
|
|
+ if (!result.isSuccess() && result.getCode() == ResultCode.FILE_NOT_FOUND && mRemoteFolderToBeCreated) {
|
|
|
+ SyncOperation syncOp = new CreateFolderOperation(pathToGrant, true);
|
|
|
+ result = syncOp.execute(client, getStorageManager());
|
|
|
+ }
|
|
|
+ if (result.isSuccess()) {
|
|
|
+ OCFile parentDir = getStorageManager().getFileByPath(pathToGrant);
|
|
|
+ if (parentDir == null) {
|
|
|
+ parentDir = createLocalFolder(pathToGrant);
|
|
|
+ }
|
|
|
+ if (parentDir != null) {
|
|
|
+ result = new RemoteOperationResult(ResultCode.OK);
|
|
|
+ } else {
|
|
|
+ result = new RemoteOperationResult(ResultCode.UNKNOWN_ERROR);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+
|
|
|
+ private OCFile createLocalFolder(String remotePath) {
|
|
|
+ String parentPath = new File(remotePath).getParent();
|
|
|
+ parentPath = parentPath.endsWith(OCFile.PATH_SEPARATOR) ?
|
|
|
+ parentPath : parentPath + OCFile.PATH_SEPARATOR;
|
|
|
+ OCFile parent = getStorageManager().getFileByPath(parentPath);
|
|
|
+ if (parent == null) {
|
|
|
+ parent = createLocalFolder(parentPath);
|
|
|
+ }
|
|
|
+ if (parent != null) {
|
|
|
+ OCFile createdFolder = new OCFile(remotePath);
|
|
|
+ createdFolder.setMimetype("DIR");
|
|
|
+ createdFolder.setParentId(parent.getFileId());
|
|
|
+ getStorageManager().saveFile(createdFolder);
|
|
|
+ return createdFolder;
|
|
|
+ }
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Create a new OCFile mFile with new remote path. This is required if forceOverwrite==false.
|
|
|
+ * New file is stored as mFile, original as mOldFile.
|
|
|
+ * @param newRemotePath new remote path
|
|
|
+ */
|
|
|
private void createNewOCFile(String newRemotePath) {
|
|
|
// a new OCFile instance must be created for a new remote path
|
|
|
OCFile newFile = new OCFile(newRemotePath);
|
|
@@ -429,7 +579,8 @@ public class UploadFileOperation extends RemoteOperation {
|
|
|
newFile.setMimetype(mFile.getMimetype());
|
|
|
newFile.setModificationTimestamp(mFile.getModificationTimestamp());
|
|
|
newFile.setModificationTimestampAtLastSyncForData(
|
|
|
- mFile.getModificationTimestampAtLastSyncForData());
|
|
|
+ mFile.getModificationTimestampAtLastSyncForData()
|
|
|
+ );
|
|
|
newFile.setEtag(mFile.getEtag());
|
|
|
newFile.setFavorite(mFile.isFavorite());
|
|
|
newFile.setLastSyncDateForProperties(mFile.getLastSyncDateForProperties());
|
|
@@ -443,12 +594,12 @@ public class UploadFileOperation extends RemoteOperation {
|
|
|
/**
|
|
|
* Checks if remotePath does not exist in the server and returns it, or adds
|
|
|
* a suffix to it in order to avoid the server file is overwritten.
|
|
|
- *
|
|
|
+ *
|
|
|
* @param wc
|
|
|
* @param remotePath
|
|
|
* @return
|
|
|
*/
|
|
|
- private String getAvailableRemotePath(OwnCloudClient wc, String remotePath) throws Exception {
|
|
|
+ private String getAvailableRemotePath(OwnCloudClient wc, String remotePath) {
|
|
|
boolean check = existsFile(wc, remotePath);
|
|
|
if (!check) {
|
|
|
return remotePath;
|
|
@@ -486,11 +637,224 @@ public class UploadFileOperation extends RemoteOperation {
|
|
|
RemoteOperationResult result = existsOperation.execute(client);
|
|
|
return result.isSuccess();
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Allows to cancel the actual upload operation. If actual upload operating
|
|
|
+ * is in progress it is cancelled, if upload preparation is being performed
|
|
|
+ * upload will not take place.
|
|
|
+ */
|
|
|
public void cancel() {
|
|
|
- mCancellationRequested = new AtomicBoolean(true);
|
|
|
- if (mUploadOperation != null) {
|
|
|
+ if (mUploadOperation == null) {
|
|
|
+ if (mUploadStarted.get()) {
|
|
|
+ Log_OC.d(TAG, "Cancelling upload during upload preparations.");
|
|
|
+ mCancellationRequested.set(true);
|
|
|
+ } else {
|
|
|
+ Log_OC.e(TAG, "No upload in progress. This should not happen.");
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ Log_OC.d(TAG, "Cancelling upload during actual upload operation.");
|
|
|
mUploadOperation.cancel();
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
+ /**
|
|
|
+ * As soon as this method return true, upload can be cancel via cancel().
|
|
|
+ */
|
|
|
+ public boolean isUploadInProgress() {
|
|
|
+ return mUploadStarted.get();
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * TODO rewrite with homogeneous fail handling, remove dependency on {@link RemoteOperationResult},
|
|
|
+ * TODO use Exceptions instead
|
|
|
+ *
|
|
|
+ * @param sourceFile Source file to copy.
|
|
|
+ * @param targetFile Target location to copy the file.
|
|
|
+ * @return {@link RemoteOperationResult}
|
|
|
+ * @throws IOException
|
|
|
+ */
|
|
|
+ private RemoteOperationResult copy(File sourceFile, File targetFile) throws IOException {
|
|
|
+ Log_OC.d(TAG, "Copying local file");
|
|
|
+
|
|
|
+ RemoteOperationResult result = null;
|
|
|
+
|
|
|
+ if (FileStorageUtils.getUsableSpace(mAccount.name) < sourceFile.length()) {
|
|
|
+ result = new RemoteOperationResult(ResultCode.LOCAL_STORAGE_FULL);
|
|
|
+ return result; // error condition when the file should be copied
|
|
|
+
|
|
|
+ } else {
|
|
|
+ Log_OC.d(TAG, "Creating temporal folder");
|
|
|
+ File temporalParent = targetFile.getParentFile();
|
|
|
+ temporalParent.mkdirs();
|
|
|
+ if (!temporalParent.isDirectory()) {
|
|
|
+ throw new IOException(
|
|
|
+ "Unexpected error: parent directory could not be created");
|
|
|
+ }
|
|
|
+ Log_OC.d(TAG, "Creating temporal file");
|
|
|
+ targetFile.createNewFile();
|
|
|
+ if (!targetFile.isFile()) {
|
|
|
+ throw new IOException(
|
|
|
+ "Unexpected error: target file could not be created");
|
|
|
+ }
|
|
|
+
|
|
|
+ Log_OC.d(TAG, "Copying file contents");
|
|
|
+ InputStream in = null;
|
|
|
+ OutputStream out = null;
|
|
|
+
|
|
|
+ try {
|
|
|
+ if (!mOriginalStoragePath.equals(targetFile.getAbsolutePath())) {
|
|
|
+ // In case document provider schema as 'content://'
|
|
|
+ if (mOriginalStoragePath.startsWith(UriUtils.URI_CONTENT_SCHEME)) {
|
|
|
+ Uri uri = Uri.parse(mOriginalStoragePath);
|
|
|
+ in = mContext.getContentResolver().openInputStream(uri);
|
|
|
+ } else {
|
|
|
+ in = new FileInputStream(sourceFile);
|
|
|
+ }
|
|
|
+ out = new FileOutputStream(targetFile);
|
|
|
+ int nRead;
|
|
|
+ byte[] buf = new byte[4096];
|
|
|
+ while (!mCancellationRequested.get() &&
|
|
|
+ (nRead = in.read(buf)) > -1) {
|
|
|
+ out.write(buf, 0, nRead);
|
|
|
+ }
|
|
|
+ out.flush();
|
|
|
+
|
|
|
+ } // else: weird but possible situation, nothing to copy
|
|
|
+
|
|
|
+ if (mCancellationRequested.get()) {
|
|
|
+ result = new RemoteOperationResult(new OperationCancelledException());
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+
|
|
|
+ } catch (Exception e) {
|
|
|
+ result = new RemoteOperationResult(ResultCode.LOCAL_STORAGE_NOT_COPIED);
|
|
|
+ return result;
|
|
|
+
|
|
|
+ } finally {
|
|
|
+ try {
|
|
|
+ if (in != null)
|
|
|
+ in.close();
|
|
|
+ } catch (Exception e) {
|
|
|
+ Log_OC.d(TAG, "Weird exception while closing input stream for " +
|
|
|
+ mOriginalStoragePath + " (ignoring)", e);
|
|
|
+ }
|
|
|
+ try {
|
|
|
+ if (out != null)
|
|
|
+ out.close();
|
|
|
+ } catch (Exception e) {
|
|
|
+ Log_OC.d(TAG, "Weird exception while closing output stream for " +
|
|
|
+ targetFile.getAbsolutePath() + " (ignoring)", e);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ /**
|
|
|
+ * TODO rewrite with homogeneous fail handling, remove dependency on {@link RemoteOperationResult},
|
|
|
+ * TODO use Exceptions instead
|
|
|
+ *
|
|
|
+ * TODO refactor both this and 'copy' in a single method
|
|
|
+ *
|
|
|
+ * @param sourceFile Source file to move.
|
|
|
+ * @param targetFile Target location to move the file.
|
|
|
+ * @return {@link RemoteOperationResult}
|
|
|
+ * @throws IOException
|
|
|
+ */
|
|
|
+ private void move(File sourceFile, File targetFile) throws IOException {
|
|
|
+
|
|
|
+ if (!targetFile.equals(sourceFile)) {
|
|
|
+ File expectedFolder = targetFile.getParentFile();
|
|
|
+ expectedFolder.mkdirs();
|
|
|
+
|
|
|
+ if (expectedFolder.isDirectory()){
|
|
|
+ if (!sourceFile.renameTo(targetFile)){
|
|
|
+ // try to copy and then delete
|
|
|
+ targetFile.createNewFile();
|
|
|
+ FileChannel inChannel = new FileInputStream(sourceFile).getChannel();
|
|
|
+ FileChannel outChannel = new FileOutputStream(targetFile).getChannel();
|
|
|
+ try {
|
|
|
+ inChannel.transferTo(0, inChannel.size(), outChannel);
|
|
|
+ sourceFile.delete();
|
|
|
+ } catch (Exception e){
|
|
|
+ mFile.setStoragePath(""); // forget the local file
|
|
|
+ // by now, treat this as a success; the file was uploaded
|
|
|
+ // the best option could be show a warning message
|
|
|
+ }
|
|
|
+ finally {
|
|
|
+ if (inChannel != null) inChannel.close();
|
|
|
+ if (outChannel != null) outChannel.close();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ } else {
|
|
|
+ mFile.setStoragePath("");
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Saves a OC File after a successful upload.
|
|
|
+ * <p/>
|
|
|
+ * A PROPFIND is necessary to keep the props in the local database
|
|
|
+ * synchronized with the server, specially the modification time and Etag
|
|
|
+ * (where available)
|
|
|
+ * <p/>
|
|
|
+ */
|
|
|
+ private void saveUploadedFile(OwnCloudClient client) {
|
|
|
+ OCFile file = mFile;
|
|
|
+ if (file.fileExists()) {
|
|
|
+ file = getStorageManager().getFileById(file.getFileId());
|
|
|
+ }
|
|
|
+ long syncDate = System.currentTimeMillis();
|
|
|
+ file.setLastSyncDateForData(syncDate);
|
|
|
+
|
|
|
+ // new PROPFIND to keep data consistent with server
|
|
|
+ // in theory, should return the same we already have
|
|
|
+ // TODO from the appropriate OC server version, get data from last PUT response headers, instead
|
|
|
+ // TODO of a new PROPFIND; the latter may fail, specially for chunked uploads
|
|
|
+ ReadRemoteFileOperation operation = new ReadRemoteFileOperation(getRemotePath());
|
|
|
+ RemoteOperationResult result = operation.execute(client);
|
|
|
+ if (result.isSuccess()) {
|
|
|
+ updateOCFile(file, (RemoteFile) result.getData().get(0));
|
|
|
+ file.setLastSyncDateForProperties(syncDate);
|
|
|
+ } else {
|
|
|
+ Log_OC.e(TAG, "Error reading properties of file after successful upload; this is gonna hurt...");
|
|
|
+ }
|
|
|
+
|
|
|
+ if (mWasRenamed) {
|
|
|
+ OCFile oldFile = mOldFile;
|
|
|
+ if (oldFile.fileExists()) {
|
|
|
+ oldFile.setStoragePath(null);
|
|
|
+ getStorageManager().saveFile(oldFile);
|
|
|
+ getStorageManager().saveConflict(oldFile, null);
|
|
|
+ }
|
|
|
+ // else: it was just an automatic renaming due to a name
|
|
|
+ // coincidence; nothing else is needed, the storagePath is right
|
|
|
+ // in the instance returned by mCurrentUpload.getFile()
|
|
|
+ }
|
|
|
+ file.setNeedsUpdateThumbnail(true);
|
|
|
+ getStorageManager().saveFile(file);
|
|
|
+ getStorageManager().saveConflict(file, null);
|
|
|
+
|
|
|
+ FileDataStorageManager.triggerMediaScan(file.getStoragePath());
|
|
|
+ }
|
|
|
+
|
|
|
+ private void updateOCFile(OCFile file, RemoteFile remoteFile) {
|
|
|
+ file.setCreationTimestamp(remoteFile.getCreationTimestamp());
|
|
|
+ file.setFileLength(remoteFile.getLength());
|
|
|
+ file.setMimetype(remoteFile.getMimeType());
|
|
|
+ file.setModificationTimestamp(remoteFile.getModifiedTimestamp());
|
|
|
+ file.setModificationTimestampAtLastSyncForData(remoteFile.getModifiedTimestamp());
|
|
|
+ file.setEtag(remoteFile.getEtag());
|
|
|
+ file.setRemoteId(remoteFile.getRemoteId());
|
|
|
+ }
|
|
|
+
|
|
|
+ public interface OnRenameListener {
|
|
|
+
|
|
|
+ void onRenameUpload();
|
|
|
+ }
|
|
|
+
|
|
|
}
|