New policy: uploaded files are copied to the local storage ownCloud directory bu...
[pub/Android/ownCloud.git] / src / com / owncloud / android / operations / UploadFileOperation.java
1 /* ownCloud Android client application
2 * Copyright (C) 2012 Bartek Przybylski
3 *
4 * This program is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, either version 3 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <http://www.gnu.org/licenses/>.
16 *
17 */
18
19 package com.owncloud.android.operations;
20
21 import java.io.File;
22 import java.io.FileInputStream;
23 import java.io.FileOutputStream;
24 import java.io.IOException;
25 import java.io.InputStream;
26 import java.io.OutputStream;
27 import java.util.HashSet;
28 import java.util.Set;
29 import java.util.concurrent.atomic.AtomicBoolean;
30
31 import org.apache.commons.httpclient.HttpException;
32 import org.apache.commons.httpclient.methods.PutMethod;
33 import org.apache.http.HttpStatus;
34
35 import com.owncloud.android.datamodel.OCFile;
36 import com.owncloud.android.operations.RemoteOperation;
37 import com.owncloud.android.operations.RemoteOperationResult;
38 import com.owncloud.android.operations.RemoteOperationResult.ResultCode;
39 import com.owncloud.android.utils.FileStorageUtils;
40
41 import eu.alefzero.webdav.FileRequestEntity;
42 import eu.alefzero.webdav.OnDatatransferProgressListener;
43 import eu.alefzero.webdav.WebdavClient;
44 import eu.alefzero.webdav.WebdavUtils;
45 import android.accounts.Account;
46 import android.util.Log;
47
48 /**
49 * Remote operation performing the upload of a file to an ownCloud server
50 *
51 * @author David A. Velasco
52 */
53 public class UploadFileOperation extends RemoteOperation {
54
55 private static final String TAG = UploadFileOperation.class.getSimpleName();
56
57 private Account mAccount;
58 private OCFile mFile;
59 private OCFile mOldFile;
60 private String mRemotePath = null;
61 private boolean mIsInstant = false;
62 private boolean mRemoteFolderToBeCreated = false;
63 private boolean mForceOverwrite = false;
64 private boolean mMoveLocalFile = false;
65 private boolean mWasRenamed = false;
66 PutMethod mPutMethod = null;
67 private Set<OnDatatransferProgressListener> mDataTransferListeners = new HashSet<OnDatatransferProgressListener>();
68 private final AtomicBoolean mCancellationRequested = new AtomicBoolean(false);
69
70
71 public UploadFileOperation( Account account,
72 OCFile file,
73 boolean isInstant,
74 boolean forceOverwrite,
75 boolean moveLocalFile) {
76 if (account == null)
77 throw new IllegalArgumentException("Illegal NULL account in UploadFileOperation creation");
78 if (file == null)
79 throw new IllegalArgumentException("Illegal NULL file in UploadFileOperation creation");
80 if (file.getStoragePath() == null || file.getStoragePath().length() <= 0 || !(new File(file.getStoragePath()).exists())) {
81 throw new IllegalArgumentException("Illegal file in UploadFileOperation; storage path invalid or file not found: " + file.getStoragePath());
82 }
83
84 mAccount = account;
85 mFile = file;
86 mRemotePath = file.getRemotePath();
87 mIsInstant = isInstant;
88 mForceOverwrite = forceOverwrite;
89 mMoveLocalFile = moveLocalFile;
90 }
91
92
93 public Account getAccount() {
94 return mAccount;
95 }
96
97 public OCFile getFile() {
98 return mFile;
99 }
100
101 public OCFile getOldFile() {
102 return mOldFile;
103 }
104
105 public String getStoragePath() {
106 return mFile.getStoragePath();
107 }
108
109 public String getRemotePath() {
110 return mFile.getRemotePath();
111 }
112
113 public String getMimeType() {
114 return mFile.getMimetype();
115 }
116
117 public boolean isInstant() {
118 return mIsInstant;
119 }
120
121 public boolean isRemoteFolderToBeCreated() {
122 return mRemoteFolderToBeCreated;
123 }
124
125 public void setRemoteFolderToBeCreated() {
126 mRemoteFolderToBeCreated = true;
127 }
128
129 public boolean getForceOverwrite() {
130 return mForceOverwrite;
131 }
132
133 public boolean wasRenamed() {
134 return mWasRenamed;
135 }
136
137 public Set<OnDatatransferProgressListener> getDataTransferListeners() {
138 return mDataTransferListeners;
139 }
140
141 public void addDatatransferProgressListener (OnDatatransferProgressListener listener) {
142 mDataTransferListeners.add(listener);
143 }
144
145
146 @Override
147 protected RemoteOperationResult run(WebdavClient client) {
148 RemoteOperationResult result = null;
149 boolean localCopyPassed = false, nameCheckPassed = false;
150 File temporalFile = null, originalFile = null;
151 String originalStoragePath = mFile.getStoragePath();
152 try {
153 /// rename the file to upload, if necessary
154 if (!mForceOverwrite) {
155 String remotePath = getAvailableRemotePath(client, mRemotePath);
156 mWasRenamed = !remotePath.equals(mRemotePath);
157 if (mWasRenamed) {
158 createNewOCFile(remotePath);
159 }
160 }
161 nameCheckPassed = true;
162
163 /// check location of local file, and copy to a temporal file to upload it if not in its corresponding directory
164 String targetLocalPath = FileStorageUtils.getDefaultSavePathFor(mAccount.name, mFile);
165 if (!originalStoragePath.equals(targetLocalPath)) {
166 File ocLocalFolder = new File(FileStorageUtils.getSavePath(mAccount.name));
167 originalFile = new File(originalStoragePath);
168 if (!mMoveLocalFile) {
169 // the file must be copied to the ownCloud local folder
170
171 if (ocLocalFolder.getUsableSpace() < originalFile.length()) {
172 result = new RemoteOperationResult(ResultCode.LOCAL_STORAGE_FULL);
173 return result;
174
175 } else {
176 String temporalPath = FileStorageUtils.getTemporalPath(mAccount.name) + mFile.getRemotePath();
177 mFile.setStoragePath(temporalPath);
178 temporalFile = new File(temporalPath);
179 if (!originalStoragePath.equals(temporalPath)) { // preventing weird but possible situation
180 InputStream in = new FileInputStream(originalFile);
181 OutputStream out = new FileOutputStream(temporalFile);
182 byte[] buf = new byte[1024];
183 int len;
184 while ((len = in.read(buf)) > 0){
185 out.write(buf, 0, len);
186 }
187 in.close();
188 out.close();
189 }
190 }
191 } // else - the file will be MOVED to the corresponding directory AFTER the upload finishes
192 }
193 localCopyPassed = true;
194
195 /// perform the upload
196 synchronized(mCancellationRequested) {
197 if (mCancellationRequested.get()) {
198 throw new OperationCancelledException();
199 } else {
200 mPutMethod = new PutMethod(client.getBaseUri() + WebdavUtils.encodePath(mFile.getRemotePath()));
201 }
202 }
203 int status = uploadFile(client);
204
205
206 /// move local temporal file or original file to its corresponding location in the ownCloud local folder
207 if (isSuccess(status)) {
208 File fileToMove = null;
209 if (temporalFile != null) {
210 fileToMove = temporalFile;
211 } else if (originalFile != null) {
212 fileToMove = originalFile;
213 }
214 if (fileToMove != null) {
215 mFile.setStoragePath(FileStorageUtils.getDefaultSavePathFor(mAccount.name, mFile));
216 File finalFile = new File(mFile.getStoragePath());
217 if (!fileToMove.renameTo(finalFile)) {
218 result = new RemoteOperationResult(ResultCode.LOCAL_STORAGE_NOT_MOVED);
219 }
220 }
221 }
222
223 if (result == null)
224 result = new RemoteOperationResult(isSuccess(status), status);
225
226
227 } catch (Exception e) {
228 if (mCancellationRequested.get()) {
229 result = new RemoteOperationResult(new OperationCancelledException());
230 } else {
231 result = new RemoteOperationResult(e);
232 }
233
234
235 } finally {
236 if (temporalFile != null && !originalFile.equals(temporalFile)) {
237 temporalFile.delete();
238 }
239 if (result.isSuccess()) {
240 Log.i(TAG, "Upload of " + originalStoragePath + " to " + mRemotePath + ": " + result.getLogMessage());
241
242 } else {
243 if (result.getException() != null) {
244 String complement = "";
245 if (!nameCheckPassed) {
246 complement = " (while checking file existence in server)";
247 } else if (!localCopyPassed) {
248 complement = " (while copying local file to " + FileStorageUtils.getSavePath(mAccount.name) + ")";
249 }
250 Log.e(TAG, "Upload of " + originalStoragePath + " to " + mRemotePath + ": " + result.getLogMessage() + complement, result.getException());
251 } else {
252 Log.e(TAG, "Upload of " + originalStoragePath + " to " + mRemotePath + ": " + result.getLogMessage());
253 }
254 }
255 }
256
257 return result;
258 }
259
260
261 private void createNewOCFile(String newRemotePath) {
262 // a new OCFile instance must be created for a new remote path
263 OCFile newFile = new OCFile(newRemotePath);
264 newFile.setCreationTimestamp(mFile.getCreationTimestamp());
265 newFile.setFileLength(mFile.getFileLength());
266 newFile.setMimetype(mFile.getMimetype());
267 newFile.setModificationTimestamp(mFile.getModificationTimestamp());
268 // newFile.setEtag(mFile.getEtag())
269 newFile.setKeepInSync(mFile.keepInSync());
270 newFile.setLastSyncDateForProperties(mFile.getLastSyncDateForProperties());
271 newFile.setLastSyncDateForData(mFile.getLastSyncDateForData());
272 newFile.setStoragePath(mFile.getStoragePath());
273 newFile.setParentId(mFile.getParentId());
274 mOldFile = mFile;
275 mFile = newFile;
276 }
277
278
279 public boolean isSuccess(int status) {
280 return ((status == HttpStatus.SC_OK || status == HttpStatus.SC_CREATED || status == HttpStatus.SC_NO_CONTENT));
281 }
282
283
284 protected int uploadFile(WebdavClient client) throws HttpException, IOException, OperationCancelledException {
285 int status = -1;
286 try {
287 File f = new File(mFile.getStoragePath());
288 FileRequestEntity entity = new FileRequestEntity(f, getMimeType());
289 entity.addOnDatatransferProgressListeners(mDataTransferListeners);
290 mPutMethod.setRequestEntity(entity);
291 status = client.executeMethod(mPutMethod);
292 client.exhaustResponse(mPutMethod.getResponseBodyAsStream());
293
294 } finally {
295 mPutMethod.releaseConnection(); // let the connection available for other methods
296 }
297 return status;
298 }
299
300 /**
301 * Checks if remotePath does not exist in the server and returns it, or adds a suffix to it in order to avoid the server
302 * file is overwritten.
303 *
304 * @param string
305 * @return
306 */
307 private String getAvailableRemotePath(WebdavClient wc, String remotePath) throws Exception {
308 boolean check = wc.existsFile(remotePath);
309 if (!check) {
310 return remotePath;
311 }
312
313 int pos = remotePath.lastIndexOf(".");
314 String suffix = "";
315 String extension = "";
316 if (pos >= 0) {
317 extension = remotePath.substring(pos+1);
318 remotePath = remotePath.substring(0, pos);
319 }
320 int count = 2;
321 do {
322 suffix = " (" + count + ")";
323 if (pos >= 0)
324 check = wc.existsFile(remotePath + suffix + "." + extension);
325 else
326 check = wc.existsFile(remotePath + suffix);
327 count++;
328 } while (check);
329
330 if (pos >=0) {
331 return remotePath + suffix + "." + extension;
332 } else {
333 return remotePath + suffix;
334 }
335 }
336
337
338 public void cancel() {
339 synchronized(mCancellationRequested) {
340 mCancellationRequested.set(true);
341 if (mPutMethod != null)
342 mPutMethod.abort();
343 }
344 }
345
346
347 }