HttpDownloadConnection.java

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