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