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				connection.setRequestProperty("User-Agent", mXmppConnectionService.getIqGenerator().getIdentityName());
185				if (connection instanceof HttpsURLConnection) {
186					mHttpConnectionManager.setupTrustManager((HttpsURLConnection) connection, interactive);
187				}
188				connection.connect();
189				String contentLength = connection.getHeaderField("Content-Length");
190				connection.disconnect();
191				if (contentLength == null) {
192					throw new IOException();
193				}
194				return Long.parseLong(contentLength, 10);
195			} catch (IOException e) {
196				throw e;
197			} catch (NumberFormatException e) {
198				throw new IOException();
199			}
200		}
201
202	}
203
204	private class FileDownloader implements Runnable {
205
206		private boolean interactive = false;
207
208		private OutputStream os;
209
210		public FileDownloader(boolean interactive) {
211			this.interactive = interactive;
212		}
213
214		@Override
215		public void run() {
216			try {
217				changeStatus(STATUS_DOWNLOADING);
218				download();
219				updateImageBounds();
220				finish();
221			} catch (SSLHandshakeException e) {
222				changeStatus(STATUS_OFFER);
223			} catch (IOException e) {
224				if (interactive) {
225					showToastForException(e);
226				}
227				cancel();
228			}
229		}
230
231		private void download()  throws IOException {
232			InputStream is = null;
233			PowerManager.WakeLock wakeLock = mHttpConnectionManager.createWakeLock("http_download_"+message.getUuid());
234			try {
235				wakeLock.acquire();
236				HttpURLConnection connection = (HttpURLConnection) mUrl.openConnection();
237				if (connection instanceof HttpsURLConnection) {
238					mHttpConnectionManager.setupTrustManager((HttpsURLConnection) connection, interactive);
239				}
240				connection.setRequestProperty("User-Agent",mXmppConnectionService.getIqGenerator().getIdentityName());
241				connection.connect();
242				is = new BufferedInputStream(connection.getInputStream());
243				file.getParentFile().mkdirs();
244				file.createNewFile();
245				os = AbstractConnectionManager.createOutputStream(file, true);
246				long transmitted = 0;
247				long expected = file.getExpectedSize();
248				int count = -1;
249				byte[] buffer = new byte[1024];
250				while ((count = is.read(buffer)) != -1) {
251					transmitted += count;
252					os.write(buffer, 0, count);
253					updateProgress((int) ((((double) transmitted) / expected) * 100));
254				}
255				os.flush();
256			} catch (IOException e) {
257				throw e;
258			} finally {
259				FileBackend.close(os);
260				FileBackend.close(is);
261				wakeLock.release();
262			}
263		}
264
265		private void updateImageBounds() {
266			message.setType(Message.TYPE_FILE);
267			mXmppConnectionService.getFileBackend().updateFileParams(message, mUrl);
268			mXmppConnectionService.updateMessage(message);
269		}
270
271	}
272
273	public void updateProgress(int i) {
274		this.mProgress = i;
275		mXmppConnectionService.updateConversationUi();
276	}
277
278	@Override
279	public int getStatus() {
280		return this.mStatus;
281	}
282
283	@Override
284	public long getFileSize() {
285		if (this.file != null) {
286			return this.file.getExpectedSize();
287		} else {
288			return 0;
289		}
290	}
291
292	@Override
293	public int getProgress() {
294		return this.mProgress;
295	}
296}