Improved cancellation of downloads: fast abort of operation and fixed thread synchron...
[pub/Android/ownCloud.git] / src / com / owncloud / android / operations / DownloadFileOperation.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.BufferedInputStream;
22 import java.io.File;
23 import java.io.FileOutputStream;
24 import java.io.IOException;
25 import java.util.HashSet;
26 import java.util.Iterator;
27 import java.util.Set;
28 import java.util.concurrent.atomic.AtomicBoolean;
29
30 import org.apache.commons.httpclient.HttpException;
31 import org.apache.commons.httpclient.methods.GetMethod;
32 import org.apache.http.HttpStatus;
33
34 import com.owncloud.android.files.services.FileDownloader;
35 import com.owncloud.android.operations.RemoteOperation;
36 import com.owncloud.android.operations.RemoteOperationResult;
37
38 import eu.alefzero.webdav.OnDatatransferProgressListener;
39 import eu.alefzero.webdav.WebdavClient;
40 import eu.alefzero.webdav.WebdavUtils;
41 import android.accounts.Account;
42 import android.util.Log;
43 import android.webkit.MimeTypeMap;
44
45 /**
46 * Remote operation performing the download of a file to an ownCloud server
47 *
48 * @author David A. Velasco
49 */
50 public class DownloadFileOperation extends RemoteOperation {
51
52 private static final String TAG = DownloadFileOperation.class.getCanonicalName();
53
54 private Account mAccount = null;
55 private String mLocalPath = null;
56 private String mRemotePath = null;
57 private String mMimeType = null;
58 private long mSize = -1;
59 private final AtomicBoolean mCancellationRequested = new AtomicBoolean(false);
60
61 private Set<OnDatatransferProgressListener> mDataTransferListeners = new HashSet<OnDatatransferProgressListener>();
62
63
64 public Account getAccount() {
65 return mAccount;
66 }
67
68 public String getLocalPath() {
69 return mLocalPath;
70 }
71
72 public String getRemotePath() {
73 return mRemotePath;
74 }
75
76 public String getMimeType() {
77 return mMimeType;
78 }
79
80 public long getSize() {
81 return mSize;
82 }
83
84
85 public DownloadFileOperation( Account account,
86 String localPath,
87 String remotePath,
88 String mimeType,
89 long size,
90 boolean forceOverwrite) {
91
92 if (account == null)
93 throw new IllegalArgumentException("Illegal null account in DownloadFileOperation creation");
94 if (localPath == null)
95 throw new IllegalArgumentException("Illegal null local path in DownloadFileOperation creation");
96 if (remotePath == null)
97 throw new IllegalArgumentException("Illegal null remote path in DownloadFileOperation creation");
98
99 mAccount = account;
100 mLocalPath = localPath;
101 mRemotePath = remotePath;
102 mMimeType = mimeType;
103 if (mMimeType == null) {
104 try {
105 mMimeType = MimeTypeMap.getSingleton()
106 .getMimeTypeFromExtension(
107 localPath.substring(localPath.lastIndexOf('.') + 1));
108 } catch (IndexOutOfBoundsException e) {
109 Log.e(TAG, "Trying to find out MIME type of a file without extension: " + localPath);
110 }
111 }
112 if (mMimeType == null) {
113 mMimeType = "application/octet-stream";
114 }
115 mSize = size;
116 }
117
118 public void addDatatransferProgressListener (OnDatatransferProgressListener listener) {
119 mDataTransferListeners.add(listener);
120 }
121
122
123
124 @Override
125 protected RemoteOperationResult run(WebdavClient client) {
126 RemoteOperationResult result = null;
127 File newFile = null;
128 boolean moved = false;
129
130 /// download will be in a temporal file
131 File tmpFile = new File(FileDownloader.getTemporalPath(mAccount.name) + mLocalPath);
132
133 /// perform the download
134 try {
135 tmpFile.getParentFile().mkdirs();
136 int status = downloadFile(client, tmpFile);
137 if (isSuccess(status)) {
138 newFile = new File(FileDownloader.getSavePath(mAccount.name) + mLocalPath);
139 newFile.getParentFile().mkdirs();
140 moved = tmpFile.renameTo(newFile);
141 }
142 if (!moved)
143 result = new RemoteOperationResult(RemoteOperationResult.ResultCode.STORAGE_ERROR_MOVING_FROM_TMP);
144 else
145 result = new RemoteOperationResult(isSuccess(status), status);
146 Log.i(TAG, "Download of " + mLocalPath + " to " + mRemotePath + ": " + result.getLogMessage());
147
148 } catch (Exception e) {
149 result = new RemoteOperationResult(e);
150 Log.e(TAG, "Download of " + mRemotePath + " to " + mLocalPath + ": " + result.getLogMessage(), e);
151 }
152
153 return result;
154 }
155
156
157 public boolean isSuccess(int status) {
158 return (status == HttpStatus.SC_OK);
159 }
160
161
162 protected int downloadFile(WebdavClient client, File targetFile) throws HttpException, IOException, OperationCancelledException {
163 int status = -1;
164 boolean savedFile = false;
165 GetMethod get = new GetMethod(client.getBaseUri() + WebdavUtils.encodePath(mRemotePath));
166 Iterator<OnDatatransferProgressListener> it = null;
167
168 FileOutputStream fos = null;
169 try {
170 status = client.executeMethod(get);
171 if (isSuccess(status)) {
172 targetFile.createNewFile();
173 BufferedInputStream bis = new BufferedInputStream(get.getResponseBodyAsStream());
174 fos = new FileOutputStream(targetFile);
175 long transferred = 0;
176
177 byte[] bytes = new byte[4096];
178 int readResult = 0;
179 while ((readResult = bis.read(bytes)) != -1) {
180 synchronized(mCancellationRequested) {
181 if (mCancellationRequested.get()) {
182 get.abort();
183 throw new OperationCancelledException();
184 }
185 }
186 fos.write(bytes, 0, readResult);
187 transferred += readResult;
188 it = mDataTransferListeners.iterator();
189 while (it.hasNext()) {
190 it.next().onTransferProgress(readResult, transferred, mSize, targetFile.getName());
191 }
192 }
193 savedFile = true;
194
195 } else {
196 client.exhaustResponse(get.getResponseBodyAsStream());
197 }
198
199 } finally {
200 if (fos != null) fos.close();
201 if (!savedFile && targetFile.exists()) {
202 targetFile.delete();
203 }
204 get.releaseConnection(); // let the connection available for other methods
205 }
206 return status;
207 }
208
209
210 public void cancel() {
211 mCancellationRequested.set(true); // atomic set; there is no need of synchronizing it
212 }
213
214 }