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.Transferable;
 22import eu.siacs.conversations.entities.DownloadableFile;
 23import eu.siacs.conversations.entities.Message;
 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				this.message.setEncryption(Message.ENCRYPTION_NONE);
 75			}
 76			String extension;
 77			if (Arrays.asList(VALID_CRYPTO_EXTENSIONS).contains(lastPart)) {
 78				extension = secondToLast;
 79			} else {
 80				extension = lastPart;
 81			}
 82			message.setRelativeFilePath(message.getUuid()+"."+extension);
 83			this.file = mXmppConnectionService.getFileBackend().getFile(message, false);
 84			String reference = mUrl.getRef();
 85			if (reference != null && reference.length() == 96) {
 86				this.file.setKey(CryptoHelper.hexToBytes(reference));
 87			}
 88
 89			if (this.message.getEncryption() == Message.ENCRYPTION_OTR
 90					&& this.file.getKey() == null) {
 91				this.message.setEncryption(Message.ENCRYPTION_NONE);
 92					}
 93			checkFileSize(true);
 94		} catch (MalformedURLException e) {
 95			this.cancel();
 96		}
 97	}
 98
 99	private void checkFileSize(boolean interactive) {
100		new Thread(new FileSizeChecker(interactive)).start();
101	}
102
103	public void cancel() {
104		mHttpConnectionManager.finishConnection(this);
105		message.setTransferable(null);
106		mXmppConnectionService.updateConversationUi();
107	}
108
109	private void finish() {
110		Intent intent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
111		intent.setData(Uri.fromFile(file));
112		mXmppConnectionService.sendBroadcast(intent);
113		message.setTransferable(null);
114		mHttpConnectionManager.finishConnection(this);
115		mXmppConnectionService.updateConversationUi();
116		if (acceptedAutomatically) {
117			mXmppConnectionService.getNotificationService().push(message);
118		}
119	}
120
121	private void changeStatus(int status) {
122		this.mStatus = status;
123		mXmppConnectionService.updateConversationUi();
124	}
125
126	private class FileSizeChecker implements Runnable {
127
128		private boolean interactive = false;
129
130		public FileSizeChecker(boolean interactive) {
131			this.interactive = interactive;
132		}
133
134		@Override
135		public void run() {
136			long size;
137			try {
138				size = retrieveFileSize();
139			} catch (SSLHandshakeException e) {
140				changeStatus(STATUS_OFFER_CHECK_FILESIZE);
141				HttpDownloadConnection.this.acceptedAutomatically = false;
142				HttpDownloadConnection.this.mXmppConnectionService.getNotificationService().push(message);
143				return;
144			} catch (IOException e) {
145				Log.d(Config.LOGTAG, "io exception in http file size checker: " + e.getMessage());
146				if (interactive) {
147					mXmppConnectionService.showErrorToastInUi(R.string.file_not_found_on_remote_host);
148				}
149				cancel();
150				return;
151			}
152			file.setExpectedSize(size);
153			if (size <= mHttpConnectionManager.getAutoAcceptFileSize()) {
154				HttpDownloadConnection.this.acceptedAutomatically = true;
155				new Thread(new FileDownloader(interactive)).start();
156			} else {
157				changeStatus(STATUS_OFFER);
158				HttpDownloadConnection.this.acceptedAutomatically = false;
159				HttpDownloadConnection.this.mXmppConnectionService.getNotificationService().push(message);
160			}
161		}
162
163		private long retrieveFileSize() throws IOException {
164			Log.d(Config.LOGTAG,"retrieve file size. interactive:"+String.valueOf(interactive));
165			changeStatus(STATUS_CHECKING);
166			HttpURLConnection connection = (HttpURLConnection) mUrl.openConnection();
167			connection.setRequestMethod("HEAD");
168			if (connection instanceof HttpsURLConnection) {
169				mHttpConnectionManager.setupTrustManager((HttpsURLConnection) connection, interactive);
170			}
171			connection.connect();
172			String contentLength = connection.getHeaderField("Content-Length");
173			if (contentLength == null) {
174				throw new IOException();
175			}
176			try {
177				return Long.parseLong(contentLength, 10);
178			} catch (NumberFormatException e) {
179				throw new IOException();
180			}
181		}
182
183	}
184
185	private class FileDownloader implements Runnable {
186
187		private boolean interactive = false;
188
189		public FileDownloader(boolean interactive) {
190			this.interactive = interactive;
191		}
192
193		@Override
194		public void run() {
195			try {
196				changeStatus(STATUS_DOWNLOADING);
197				download();
198				updateImageBounds();
199				finish();
200			} catch (SSLHandshakeException e) {
201				changeStatus(STATUS_OFFER);
202			} catch (IOException e) {
203				mXmppConnectionService.showErrorToastInUi(R.string.file_not_found_on_remote_host);
204				cancel();
205			}
206		}
207
208		private void download() throws SSLHandshakeException, IOException {
209			HttpURLConnection connection = (HttpURLConnection) mUrl.openConnection();
210			if (connection instanceof HttpsURLConnection) {
211				mHttpConnectionManager.setupTrustManager((HttpsURLConnection) connection, interactive);
212			}
213			connection.connect();
214			BufferedInputStream is = new BufferedInputStream(connection.getInputStream());
215			file.getParentFile().mkdirs();
216			file.createNewFile();
217			OutputStream os = file.createOutputStream();
218			if (os == null) {
219				throw new IOException();
220			}
221			long transmitted = 0;
222			long expected = file.getExpectedSize();
223			int count = -1;
224			byte[] buffer = new byte[1024];
225			while ((count = is.read(buffer)) != -1) {
226				transmitted += count;
227				os.write(buffer, 0, count);
228				updateProgress((int) ((((double) transmitted) / expected) * 100));
229			}
230			os.flush();
231			os.close();
232			is.close();
233		}
234
235		private void updateImageBounds() {
236			message.setType(Message.TYPE_FILE);
237			mXmppConnectionService.getFileBackend().updateFileParams(message, mUrl);
238			mXmppConnectionService.updateMessage(message);
239		}
240
241	}
242
243	public void updateProgress(int i) {
244		this.mProgress = i;
245		if (SystemClock.elapsedRealtime() - this.mLastGuiRefresh > Config.PROGRESS_UI_UPDATE_INTERVAL) {
246			this.mLastGuiRefresh = SystemClock.elapsedRealtime();
247			mXmppConnectionService.updateConversationUi();
248		}
249	}
250
251	@Override
252	public int getStatus() {
253		return this.mStatus;
254	}
255
256	@Override
257	public long getFileSize() {
258		if (this.file != null) {
259			return this.file.getExpectedSize();
260		} else {
261			return 0;
262		}
263	}
264
265	@Override
266	public int getProgress() {
267		return this.mProgress;
268	}
269}