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		if (message.getEncryption() == Message.ENCRYPTION_PGP) {
129			message.getConversation().getAccount().getPgpDecryptionService().add(message);
130		}
131		mXmppConnectionService.updateConversationUi();
132		if (acceptedAutomatically) {
133			mXmppConnectionService.getNotificationService().push(message);
134		}
135	}
136
137	private void changeStatus(int status) {
138		this.mStatus = status;
139		mXmppConnectionService.updateConversationUi();
140	}
141
142	private void showToastForException(Exception e) {
143		e.printStackTrace();
144		if (e instanceof java.net.UnknownHostException) {
145			mXmppConnectionService.showErrorToastInUi(R.string.download_failed_server_not_found);
146		} else if (e instanceof java.net.ConnectException) {
147			mXmppConnectionService.showErrorToastInUi(R.string.download_failed_could_not_connect);
148		} else {
149			mXmppConnectionService.showErrorToastInUi(R.string.download_failed_file_not_found);
150		}
151	}
152
153	private class FileSizeChecker implements Runnable {
154
155		private boolean interactive = false;
156
157		public FileSizeChecker(boolean interactive) {
158			this.interactive = interactive;
159		}
160
161		@Override
162		public void run() {
163			long size;
164			try {
165				size = retrieveFileSize();
166			} catch (SSLHandshakeException e) {
167				changeStatus(STATUS_OFFER_CHECK_FILESIZE);
168				HttpDownloadConnection.this.acceptedAutomatically = false;
169				HttpDownloadConnection.this.mXmppConnectionService.getNotificationService().push(message);
170				return;
171			} catch (IOException e) {
172				Log.d(Config.LOGTAG, "io exception in http file size checker: " + e.getMessage());
173				if (interactive) {
174					showToastForException(e);
175				}
176				cancel();
177				return;
178			}
179			file.setExpectedSize(size);
180			if (size <= mHttpConnectionManager.getAutoAcceptFileSize()) {
181				HttpDownloadConnection.this.acceptedAutomatically = true;
182				new Thread(new FileDownloader(interactive)).start();
183			} else {
184				changeStatus(STATUS_OFFER);
185				HttpDownloadConnection.this.acceptedAutomatically = false;
186				HttpDownloadConnection.this.mXmppConnectionService.getNotificationService().push(message);
187			}
188		}
189
190		private long retrieveFileSize() throws IOException {
191			try {
192				Log.d(Config.LOGTAG, "retrieve file size. interactive:" + String.valueOf(interactive));
193				changeStatus(STATUS_CHECKING);
194				HttpURLConnection connection = (HttpURLConnection) mUrl.openConnection();
195				connection.setRequestMethod("HEAD");
196				connection.setRequestProperty("User-Agent", mXmppConnectionService.getIqGenerator().getIdentityName());
197				if (connection instanceof HttpsURLConnection) {
198					mHttpConnectionManager.setupTrustManager((HttpsURLConnection) connection, interactive);
199				}
200				connection.connect();
201				String contentLength = connection.getHeaderField("Content-Length");
202				connection.disconnect();
203				if (contentLength == null) {
204					throw new IOException();
205				}
206				return Long.parseLong(contentLength, 10);
207			} catch (IOException e) {
208				throw e;
209			} catch (NumberFormatException e) {
210				throw new IOException();
211			}
212		}
213
214	}
215
216	private class FileDownloader implements Runnable {
217
218		private boolean interactive = false;
219
220		private OutputStream os;
221
222		public FileDownloader(boolean interactive) {
223			this.interactive = interactive;
224		}
225
226		@Override
227		public void run() {
228			try {
229				changeStatus(STATUS_DOWNLOADING);
230				download();
231				updateImageBounds();
232				finish();
233			} catch (SSLHandshakeException e) {
234				changeStatus(STATUS_OFFER);
235			} catch (IOException e) {
236				if (interactive) {
237					showToastForException(e);
238				}
239				cancel();
240			}
241		}
242
243		private void download()  throws IOException {
244			InputStream is = null;
245			PowerManager.WakeLock wakeLock = mHttpConnectionManager.createWakeLock("http_download_"+message.getUuid());
246			try {
247				wakeLock.acquire();
248				HttpURLConnection connection = (HttpURLConnection) mUrl.openConnection();
249				if (connection instanceof HttpsURLConnection) {
250					mHttpConnectionManager.setupTrustManager((HttpsURLConnection) connection, interactive);
251				}
252				connection.setRequestProperty("User-Agent",mXmppConnectionService.getIqGenerator().getIdentityName());
253				connection.connect();
254				is = new BufferedInputStream(connection.getInputStream());
255				file.getParentFile().mkdirs();
256				file.createNewFile();
257				os = AbstractConnectionManager.createOutputStream(file, true);
258				long transmitted = 0;
259				long expected = file.getExpectedSize();
260				int count = -1;
261				byte[] buffer = new byte[1024];
262				while ((count = is.read(buffer)) != -1) {
263					transmitted += count;
264					os.write(buffer, 0, count);
265					updateProgress((int) ((((double) transmitted) / expected) * 100));
266				}
267				os.flush();
268			} catch (IOException e) {
269				throw e;
270			} finally {
271				FileBackend.close(os);
272				FileBackend.close(is);
273				wakeLock.release();
274			}
275		}
276
277		private void updateImageBounds() {
278			message.setType(Message.TYPE_FILE);
279			mXmppConnectionService.getFileBackend().updateFileParams(message, mUrl);
280			mXmppConnectionService.updateMessage(message);
281		}
282
283	}
284
285	public void updateProgress(int i) {
286		this.mProgress = i;
287		mXmppConnectionService.updateConversationUi();
288	}
289
290	@Override
291	public int getStatus() {
292		return this.mStatus;
293	}
294
295	@Override
296	public long getFileSize() {
297		if (this.file != null) {
298			return this.file.getExpectedSize();
299		} else {
300			return 0;
301		}
302	}
303
304	@Override
305	public int getProgress() {
306		return this.mProgress;
307	}
308}