HttpDownloadConnection.java

  1package eu.siacs.conversations.http;
  2
  3import android.content.Intent;
  4import android.net.Uri;
  5import android.os.PowerManager;
  6import android.util.Log;
  7
  8import java.io.BufferedInputStream;
  9import java.io.IOException;
 10import java.io.InputStream;
 11import java.io.OutputStream;
 12import java.net.HttpURLConnection;
 13import java.net.MalformedURLException;
 14import java.net.URL;
 15import java.util.Arrays;
 16
 17import javax.net.ssl.HttpsURLConnection;
 18import javax.net.ssl.SSLHandshakeException;
 19
 20import eu.siacs.conversations.Config;
 21import eu.siacs.conversations.R;
 22import eu.siacs.conversations.entities.DownloadableFile;
 23import eu.siacs.conversations.entities.Message;
 24import eu.siacs.conversations.entities.Transferable;
 25import eu.siacs.conversations.persistance.FileBackend;
 26import eu.siacs.conversations.services.AbstractConnectionManager;
 27import eu.siacs.conversations.services.XmppConnectionService;
 28import eu.siacs.conversations.utils.CryptoHelper;
 29
 30public class HttpDownloadConnection implements Transferable {
 31
 32	private HttpConnectionManager mHttpConnectionManager;
 33	private XmppConnectionService mXmppConnectionService;
 34
 35	private URL mUrl;
 36	private Message message;
 37	private DownloadableFile file;
 38	private int mStatus = Transferable.STATUS_UNKNOWN;
 39	private boolean acceptedAutomatically = false;
 40	private int mProgress = 0;
 41
 42	public HttpDownloadConnection(HttpConnectionManager manager) {
 43		this.mHttpConnectionManager = manager;
 44		this.mXmppConnectionService = manager.getXmppConnectionService();
 45	}
 46
 47	@Override
 48	public boolean start() {
 49		if (mXmppConnectionService.hasInternetConnection()) {
 50			if (this.mStatus == STATUS_OFFER_CHECK_FILESIZE) {
 51				checkFileSize(true);
 52			} else {
 53				new Thread(new FileDownloader(true)).start();
 54			}
 55			return true;
 56		} else {
 57			return false;
 58		}
 59	}
 60
 61	public void init(Message message) {
 62		init(message, false);
 63	}
 64
 65	public void init(Message message, boolean interactive) {
 66		this.message = message;
 67		this.message.setTransferable(this);
 68		try {
 69			mUrl = new URL(message.getBody());
 70			String[] parts = mUrl.getPath().toLowerCase().split("\\.");
 71			String lastPart = parts.length >= 1 ? parts[parts.length - 1] : null;
 72			String secondToLast = parts.length >= 2 ? parts[parts.length -2] : null;
 73			if ("pgp".equals(lastPart) || "gpg".equals(lastPart)) {
 74				this.message.setEncryption(Message.ENCRYPTION_PGP);
 75			} else if (message.getEncryption() != Message.ENCRYPTION_OTR
 76					&& message.getEncryption() != Message.ENCRYPTION_AXOLOTL) {
 77				this.message.setEncryption(Message.ENCRYPTION_NONE);
 78			}
 79			String extension;
 80			if (Arrays.asList(VALID_CRYPTO_EXTENSIONS).contains(lastPart)) {
 81				extension = secondToLast;
 82			} else {
 83				extension = lastPart;
 84			}
 85			message.setRelativeFilePath(message.getUuid()+"."+extension);
 86			this.file = mXmppConnectionService.getFileBackend().getFile(message, false);
 87			String reference = mUrl.getRef();
 88			if (reference != null && reference.length() == 96) {
 89				this.file.setKeyAndIv(CryptoHelper.hexToBytes(reference));
 90			}
 91
 92			if ((this.message.getEncryption() == Message.ENCRYPTION_OTR
 93					|| this.message.getEncryption() == Message.ENCRYPTION_AXOLOTL)
 94					&& this.file.getKey() == null) {
 95				this.message.setEncryption(Message.ENCRYPTION_NONE);
 96					}
 97			checkFileSize(interactive);
 98		} catch (MalformedURLException e) {
 99			this.cancel();
100		}
101	}
102
103	private void checkFileSize(boolean interactive) {
104		new Thread(new FileSizeChecker(interactive)).start();
105	}
106
107	public void cancel() {
108		mHttpConnectionManager.finishConnection(this);
109		message.setTransferable(null);
110		mXmppConnectionService.updateConversationUi();
111	}
112
113	private void finish() {
114		Intent intent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
115		intent.setData(Uri.fromFile(file));
116		mXmppConnectionService.sendBroadcast(intent);
117		message.setTransferable(null);
118		mHttpConnectionManager.finishConnection(this);
119		mXmppConnectionService.updateConversationUi();
120		if (acceptedAutomatically) {
121			mXmppConnectionService.getNotificationService().push(message);
122		}
123	}
124
125	private void changeStatus(int status) {
126		this.mStatus = status;
127		mXmppConnectionService.updateConversationUi();
128	}
129
130	private void showToastForException(Exception e) {
131		e.printStackTrace();
132		if (e instanceof java.net.UnknownHostException) {
133			mXmppConnectionService.showErrorToastInUi(R.string.download_failed_server_not_found);
134		} else if (e instanceof java.net.ConnectException) {
135			mXmppConnectionService.showErrorToastInUi(R.string.download_failed_could_not_connect);
136		} else {
137			mXmppConnectionService.showErrorToastInUi(R.string.download_failed_file_not_found);
138		}
139	}
140
141	private class FileSizeChecker implements Runnable {
142
143		private boolean interactive = false;
144
145		public FileSizeChecker(boolean interactive) {
146			this.interactive = interactive;
147		}
148
149		@Override
150		public void run() {
151			long size;
152			try {
153				size = retrieveFileSize();
154			} catch (SSLHandshakeException e) {
155				changeStatus(STATUS_OFFER_CHECK_FILESIZE);
156				HttpDownloadConnection.this.acceptedAutomatically = false;
157				HttpDownloadConnection.this.mXmppConnectionService.getNotificationService().push(message);
158				return;
159			} catch (IOException e) {
160				Log.d(Config.LOGTAG, "io exception in http file size checker: " + e.getMessage());
161				if (interactive) {
162					showToastForException(e);
163				}
164				cancel();
165				return;
166			}
167			file.setExpectedSize(size);
168			if (size <= mHttpConnectionManager.getAutoAcceptFileSize()) {
169				HttpDownloadConnection.this.acceptedAutomatically = true;
170				new Thread(new FileDownloader(interactive)).start();
171			} else {
172				changeStatus(STATUS_OFFER);
173				HttpDownloadConnection.this.acceptedAutomatically = false;
174				HttpDownloadConnection.this.mXmppConnectionService.getNotificationService().push(message);
175			}
176		}
177
178		private long retrieveFileSize() throws IOException {
179			try {
180				Log.d(Config.LOGTAG, "retrieve file size. interactive:" + String.valueOf(interactive));
181				changeStatus(STATUS_CHECKING);
182				HttpURLConnection connection = (HttpURLConnection) mUrl.openConnection();
183				connection.setRequestMethod("HEAD");
184				if (connection instanceof HttpsURLConnection) {
185					mHttpConnectionManager.setupTrustManager((HttpsURLConnection) connection, interactive);
186				}
187				connection.connect();
188				String contentLength = connection.getHeaderField("Content-Length");
189				connection.disconnect();
190				if (contentLength == null) {
191					throw new IOException();
192				}
193				return Long.parseLong(contentLength, 10);
194			} catch (IOException e) {
195				throw e;
196			} catch (NumberFormatException e) {
197				throw new IOException();
198			}
199		}
200
201	}
202
203	private class FileDownloader implements Runnable {
204
205		private boolean interactive = false;
206
207		private OutputStream os;
208
209		public FileDownloader(boolean interactive) {
210			this.interactive = interactive;
211		}
212
213		@Override
214		public void run() {
215			try {
216				changeStatus(STATUS_DOWNLOADING);
217				download();
218				updateImageBounds();
219				finish();
220			} catch (SSLHandshakeException e) {
221				changeStatus(STATUS_OFFER);
222			} catch (IOException e) {
223				if (interactive) {
224					showToastForException(e);
225				}
226				cancel();
227			}
228		}
229
230		private void download()  throws IOException {
231			InputStream is = null;
232			PowerManager.WakeLock wakeLock = mHttpConnectionManager.createWakeLock("http_download_"+message.getUuid());
233			try {
234				wakeLock.acquire();
235				HttpURLConnection connection = (HttpURLConnection) mUrl.openConnection();
236				if (connection instanceof HttpsURLConnection) {
237					mHttpConnectionManager.setupTrustManager((HttpsURLConnection) connection, interactive);
238				}
239				connection.connect();
240				is = new BufferedInputStream(connection.getInputStream());
241				file.getParentFile().mkdirs();
242				file.createNewFile();
243				os = AbstractConnectionManager.createOutputStream(file, true);
244				long transmitted = 0;
245				long expected = file.getExpectedSize();
246				int count = -1;
247				byte[] buffer = new byte[1024];
248				while ((count = is.read(buffer)) != -1) {
249					transmitted += count;
250					os.write(buffer, 0, count);
251					updateProgress((int) ((((double) transmitted) / expected) * 100));
252				}
253				os.flush();
254			} catch (IOException e) {
255				throw e;
256			} finally {
257				FileBackend.close(os);
258				FileBackend.close(is);
259				wakeLock.release();
260			}
261		}
262
263		private void updateImageBounds() {
264			message.setType(Message.TYPE_FILE);
265			mXmppConnectionService.getFileBackend().updateFileParams(message, mUrl);
266			mXmppConnectionService.updateMessage(message);
267		}
268
269	}
270
271	public void updateProgress(int i) {
272		this.mProgress = i;
273		mXmppConnectionService.updateConversationUi();
274	}
275
276	@Override
277	public int getStatus() {
278		return this.mStatus;
279	}
280
281	@Override
282	public long getFileSize() {
283		if (this.file != null) {
284			return this.file.getExpectedSize();
285		} else {
286			return 0;
287		}
288	}
289
290	@Override
291	public int getProgress() {
292		return this.mProgress;
293	}
294}