From b1ab7347b92329512bebe57f6624cae33c27036f Mon Sep 17 00:00:00 2001 From: steckbrief Date: Sun, 29 May 2016 20:33:36 +0200 Subject: FileTransfer reworked (first steps - functionality as is), HttpUpload separated, some bugfixes - HttpUpload moved into own package - FileTransfer managed by a central manager class, several FileTransferService implementation can be used - Security initializations moved to ConversationsPlusApplication - Access to PowerManager moved to ConversationsPlusApplication - Removed unused code fragments - Access to HttpConnectionManager is now static --- .../filetransfer/AbstractFileTransferService.java | 31 +++++ .../services/filetransfer/FileTransferEntity.java | 108 ++++++++++++++++++ .../services/filetransfer/FileTransferManager.java | 127 +++++++++++++++++++++ .../filetransfer/FileTransferStatusListener.java | 11 ++ .../httpupload/HttpFileTransferEntity.java | 52 +++++++++ .../filetransfer/httpupload/HttpFileUploader.java | 120 +++++++++++++++++++ .../httpupload/HttpUploadFileTransferService.java | 84 ++++++++++++++ .../httpupload/HttpUploadSlotRequestReceived.java | 34 ++++++ .../HttpUploadedFileEncryptionUiCallback.java | 33 ++++++ .../jingle/JingleFileTransferService.java | 51 +++++++++ 10 files changed, 651 insertions(+) create mode 100644 src/main/java/de/thedevstack/conversationsplus/services/filetransfer/AbstractFileTransferService.java create mode 100644 src/main/java/de/thedevstack/conversationsplus/services/filetransfer/FileTransferEntity.java create mode 100644 src/main/java/de/thedevstack/conversationsplus/services/filetransfer/FileTransferManager.java create mode 100644 src/main/java/de/thedevstack/conversationsplus/services/filetransfer/FileTransferStatusListener.java create mode 100644 src/main/java/de/thedevstack/conversationsplus/services/filetransfer/httpupload/HttpFileTransferEntity.java create mode 100644 src/main/java/de/thedevstack/conversationsplus/services/filetransfer/httpupload/HttpFileUploader.java create mode 100644 src/main/java/de/thedevstack/conversationsplus/services/filetransfer/httpupload/HttpUploadFileTransferService.java create mode 100644 src/main/java/de/thedevstack/conversationsplus/services/filetransfer/httpupload/HttpUploadSlotRequestReceived.java create mode 100644 src/main/java/de/thedevstack/conversationsplus/services/filetransfer/httpupload/HttpUploadedFileEncryptionUiCallback.java create mode 100644 src/main/java/de/thedevstack/conversationsplus/services/filetransfer/jingle/JingleFileTransferService.java (limited to 'src/main/java/de/thedevstack/conversationsplus/services/filetransfer') diff --git a/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/AbstractFileTransferService.java b/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/AbstractFileTransferService.java new file mode 100644 index 00000000..02bd04b9 --- /dev/null +++ b/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/AbstractFileTransferService.java @@ -0,0 +1,31 @@ +package de.thedevstack.conversationsplus.services.filetransfer; + +import java.util.ArrayList; +import java.util.List; + +import de.thedevstack.conversationsplus.entities.Message; +import de.thedevstack.conversationsplus.services.FileTransferService; + +/** + * + */ +public abstract class AbstractFileTransferService implements FileTransferService, FileTransferStatusListener { + protected List failedMessages = new ArrayList<>(); + + @Override + public void onFailure(Message message, boolean delay) { + this.failedMessages.add(message); + } + + @Override + public void onSuccess(Message message, boolean delay) { + if (this.failedMessages.contains(message)) { + this.failedMessages.remove(message); + } + } + + @Override + public boolean accept(Message message) { + return !this.failedMessages.contains(message); + } +} diff --git a/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/FileTransferEntity.java b/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/FileTransferEntity.java new file mode 100644 index 00000000..a44cf49d --- /dev/null +++ b/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/FileTransferEntity.java @@ -0,0 +1,108 @@ +package de.thedevstack.conversationsplus.services.filetransfer; + +import java.io.InputStream; + +import de.thedevstack.conversationsplus.entities.DownloadableFile; +import de.thedevstack.conversationsplus.entities.Message; +import de.thedevstack.conversationsplus.entities.Transferable; +import de.thedevstack.conversationsplus.persistance.FileBackend; +import de.thedevstack.conversationsplus.utils.MessageUtil; + +/** + * + */ +public class FileTransferEntity implements Transferable { + private final Message message; + private int tries = 0; + private boolean transferred = false; + private boolean canceled = false; + private boolean failed = false; + private long transmitted = 0; + private DownloadableFile file; + private InputStream fileInputStream; + + public FileTransferEntity(Message message) { + this.message = message; + this.message.setTransferable(this); + this.file = FileBackend.getFile(message, false); + } + + @Override + public boolean equals(Object o) { + FileTransferEntity other = (FileTransferEntity)o; + if ((this.message == null && other.message != null) + || (this.message != null && other.message == null)) { + return false; + } else if (this.message == null && other.message == null) { + return true; + } + return this.message.getUuid().equals(other.message.getUuid()); + } + + @Override + public boolean start() { + return false; + } + + @Override + public int getStatus() { + int status = (failed) ? STATUS_FAILED : STATUS_UPLOADING; + return status; + } + + @Override + public long getFileSize() { + return file == null ? 0 : file.getExpectedSize(); + } + + @Override + public int getProgress() { + if (file == null) { + return 0; + } + return (int) ((((double) transmitted) / file.getExpectedSize()) * 100); + } + + @Override + public void cancel() { + this.canceled = true; + } + + public void fail() { + this.failed = true; + this.getMessage().setTransferable(null); + MessageUtil.markMessage(this.getMessage(), Message.STATUS_SEND_FAILED); + } + + public Message getMessage() { + return message; + } + + public boolean isCanceled() { + return this.canceled; + } + + public boolean isFailed() { + return failed; + } + + public void updateProgress(long progress) { + this.transmitted += progress; + } + + public DownloadableFile getFile() { + return file; + } + + public void transferred() { + this.transferred = true; + } + + public void setFileInputStream(InputStream fileInputStream) { + this.fileInputStream = fileInputStream; + } + + public InputStream getFileInputStream() { + return fileInputStream; + } +} diff --git a/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/FileTransferManager.java b/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/FileTransferManager.java new file mode 100644 index 00000000..f7b3f4e2 --- /dev/null +++ b/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/FileTransferManager.java @@ -0,0 +1,127 @@ +package de.thedevstack.conversationsplus.services.filetransfer; + +import java.util.HashMap; +import java.util.Map; +import java.util.SortedSet; +import java.util.TreeSet; + +import de.thedevstack.android.logcat.Logging; +import de.thedevstack.conversationsplus.Config; +import de.thedevstack.conversationsplus.entities.Message; +import de.thedevstack.conversationsplus.services.FileTransferService; + +/** + * + */ +public class FileTransferManager implements FileTransferService { + private SortedSet transferServices; + private static final FileTransferManager INSTANCE = new FileTransferManager(); + + private FileTransferManager() { + this.transferServices = new TreeSet<>(); + } + + public static FileTransferManager getInstance() { + return INSTANCE; + } + + public static void init(FileTransferService... fileTransferServices) { + if (null != fileTransferServices && fileTransferServices.length > 0) { + for (FileTransferService fts : fileTransferServices) { + addFileTransferService(fts); + } + } + } + + public static void init(HashMap fileTransferServices) { + for (Map.Entry entry : fileTransferServices.entrySet()) { + addFileTransferService(entry.getKey(), entry.getValue()); + } + } + + public static void addFileTransferService(int weight, FileTransferService fts) { + INSTANCE.transferServices.add(new WeightedTransferService(weight, fts)); + } + + public static void addFileTransferService(FileTransferService fts) { + int weight = 1; + if (!INSTANCE.transferServices.isEmpty()) { + weight = INSTANCE.transferServices.last().weight + 1; + } + addFileTransferService(weight, fts); + } + + /** + * Transfers a file for the corresponding message. + * + * @param message the message containing the file to transfer + * @return true if the file transfer was successful, false otherwise + */ + public boolean transferFile(Message message) { + return this.transferFile(message, false); + } + + /** + * Transfers a file for the corresponding message. + * + * @param message the message containing the file to transfer + * @param delay whether the message is delayed or not + * @return true if the file transfer was successful, false otherwise + */ + @Override + public boolean transferFile(Message message, boolean delay) { + Logging.d(Config.LOGTAG, "send file message"); + boolean transferSuccessfullyStarted = false; + for (WeightedTransferService wts : this.transferServices) { + try { + if (wts.fileTransferService.accept(message)) { + transferSuccessfullyStarted = wts.fileTransferService.transferFile(message, delay); + if (transferSuccessfullyStarted) { + break; + } + } + } catch (Exception e) { + //TODO Do real exception handling!!!!! + } + } + return transferSuccessfullyStarted; + } + + /** + * Checks whether a message can be sent using this service or not. + * + * @param message the message to be checked + * @return true if the message can be processed, false otherwise + */ + @Override + public boolean accept(Message message) { + return message.needsUploading(); + } + + static class WeightedTransferService implements Comparable { + int weight; + FileTransferService fileTransferService; + + WeightedTransferService(int weight, FileTransferService service) { + this.weight = weight; + this.fileTransferService = service; + } + + /** + * Compares this object to the specified object to determine their relative + * order. + * + * @param another the object to compare to this instance. + * @return a negative integer if this instance is less than {@code another}; + * a positive integer if this instance is greater than + * {@code another}; 0 if this instance has the same order as + * {@code another}. + * @throws ClassCastException if {@code another} cannot be converted into something + * comparable to {@code this} instance. + */ + @Override + public int compareTo(WeightedTransferService another) { + return this.weight - another.weight; + } + } +} diff --git a/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/FileTransferStatusListener.java b/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/FileTransferStatusListener.java new file mode 100644 index 00000000..89af5b39 --- /dev/null +++ b/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/FileTransferStatusListener.java @@ -0,0 +1,11 @@ +package de.thedevstack.conversationsplus.services.filetransfer; + +import de.thedevstack.conversationsplus.entities.Message; + +/** + * + */ +public interface FileTransferStatusListener { + void onFailure(Message message, boolean delay); + void onSuccess(Message message, boolean delay); +} diff --git a/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/httpupload/HttpFileTransferEntity.java b/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/httpupload/HttpFileTransferEntity.java new file mode 100644 index 00000000..8efd498b --- /dev/null +++ b/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/httpupload/HttpFileTransferEntity.java @@ -0,0 +1,52 @@ +package de.thedevstack.conversationsplus.services.filetransfer.httpupload; + +import de.thedevstack.conversationsplus.Config; +import de.thedevstack.conversationsplus.ConversationsPlusApplication; +import de.thedevstack.conversationsplus.entities.Message; +import de.thedevstack.conversationsplus.services.filetransfer.FileTransferEntity; +import de.thedevstack.conversationsplus.xmpp.httpupload.HttpUploadSlot; + +/** + * + */ +public class HttpFileTransferEntity extends FileTransferEntity { + private HttpUploadSlot slot; + private final byte[] key; + private final boolean delayed; + + public HttpFileTransferEntity(Message message, boolean delayed) { + super(message); + this.getMessage().setHttpUploaded(true); + this.getMessage().setNoDownloadable(); + if (Config.ENCRYPT_ON_HTTP_UPLOADED + || message.getEncryption() == Message.ENCRYPTION_AXOLOTL + || message.getEncryption() == Message.ENCRYPTION_OTR) { + this.key = new byte[48]; + ConversationsPlusApplication.getSecureRandom().nextBytes(this.key); + this.getFile().setKeyAndIv(this.key); + } else { + this.key = null; + } + this.delayed = delayed; + } + + public void setSlot(HttpUploadSlot slot) { + this.slot = slot; + } + + public String getGetUrl() { + return this.slot.getGetUrl(); + } + + public String getPutUrl() { + return this.slot.getPutUrl(); + } + + public byte[] getKey() { + return key; + } + + public boolean isDelayed() { + return this.delayed; + } +} diff --git a/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/httpupload/HttpFileUploader.java b/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/httpupload/HttpFileUploader.java new file mode 100644 index 00000000..6352c7a7 --- /dev/null +++ b/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/httpupload/HttpFileUploader.java @@ -0,0 +1,120 @@ +package de.thedevstack.conversationsplus.services.filetransfer.httpupload; + +import android.os.PowerManager; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.HttpURLConnection; +import java.net.URL; +import java.util.Scanner; + +import javax.net.ssl.HttpsURLConnection; + +import de.thedevstack.android.logcat.Logging; +import de.thedevstack.conversationsplus.Config; +import de.thedevstack.conversationsplus.ConversationsPlusApplication; +import de.thedevstack.conversationsplus.entities.DownloadableFile; +import de.thedevstack.conversationsplus.entities.Message; +import de.thedevstack.conversationsplus.http.HttpConnectionManager; +import de.thedevstack.conversationsplus.persistance.FileBackend; +import de.thedevstack.conversationsplus.utils.CryptoHelper; +import de.thedevstack.conversationsplus.utils.MessageUtil; +import de.thedevstack.conversationsplus.utils.StreamUtil; +import de.thedevstack.conversationsplus.utils.UiUpdateHelper; +import de.thedevstack.conversationsplus.utils.XmppConnectionServiceAccessor; +import de.thedevstack.conversationsplus.xmpp.httpupload.HttpUpload; + +public class HttpFileUploader implements Runnable { + private static final String HTTP_METHOD = "PUT"; + private static final String MIME_REQUEST_PROPERTY_NAME = "Content-Type"; + private static final String USER_AGENT_REQUEST_PROPERTY_NAME = "User-Agent"; + private static final int BUFFER_LENGTH = 4096; + private final HttpFileTransferEntity entity; + + public HttpFileUploader(HttpFileTransferEntity entity) { + this.entity = entity; + } + + @Override + public void run() { + this.upload(); + } + + private void upload() { + OutputStream os = null; + InputStream errorStream = null; + HttpURLConnection connection = null; + InputStream fileInputStream = null; + DownloadableFile file = this.entity.getFile(); + PowerManager.WakeLock wakeLock = ConversationsPlusApplication.createPartialWakeLock("http_upload_" + entity.getMessage().getUuid()); + try { + wakeLock.acquire(); + Logging.d(Config.LOGTAG, "uploading to " + this.entity.getPutUrl()); + URL putUrl = new URL(this.entity.getPutUrl()); + fileInputStream = this.entity.getFileInputStream(); + connection = (HttpURLConnection) putUrl.openConnection(); + + if (connection instanceof HttpsURLConnection) { + HttpConnectionManager.setupTrustManager((HttpsURLConnection) connection, true); + } + connection.setRequestMethod(HTTP_METHOD); + connection.setFixedLengthStreamingMode((int) file.getExpectedSize()); + String mime = this.entity.getFile().getMimeType(); + connection.setRequestProperty(MIME_REQUEST_PROPERTY_NAME, mime == null ? HttpUpload.DEFAULT_MIME_TYPE : mime); + connection.setRequestProperty(USER_AGENT_REQUEST_PROPERTY_NAME, ConversationsPlusApplication.getNameAndVersion()); + connection.setDoOutput(true); + connection.connect(); + os = connection.getOutputStream(); + int count = -1; + byte[] buffer = new byte[BUFFER_LENGTH]; + while (((count = fileInputStream.read(buffer)) != -1) && !this.entity.isCanceled()) { + this.entity.updateProgress(count); + os.write(buffer, 0, count); + UiUpdateHelper.updateConversationUi(); + } + os.flush(); + os.close(); + fileInputStream.close(); + int code = connection.getResponseCode(); + if (code == 200 || code == 201) { + Logging.d(Config.LOGTAG, "finished uploading file"); + this.entity.transferred(); + URL getUrl = new URL(this.entity.getGetUrl()); + if (this.entity.getKey() != null) { + getUrl = new URL(getUrl.toString() + "#" + CryptoHelper.bytesToHex(this.entity.getKey())); + } + Message message = this.entity.getMessage(); + MessageUtil.updateFileParams(message, getUrl); + FileBackend.updateMediaScanner(file, XmppConnectionServiceAccessor.xmppConnectionService); + message.setTransferable(null); + message.setCounterpart(message.getConversation().getJid().toBareJid()); + if (message.getEncryption() == Message.ENCRYPTION_DECRYPTED) { + XmppConnectionServiceAccessor.xmppConnectionService.getPgpEngine().encrypt(message, new HttpUploadedFileEncryptionUiCallback(this.entity)); + } else { + XmppConnectionServiceAccessor.xmppConnectionService.resendMessage(message, this.entity.isDelayed()); + } + } else { + errorStream = connection.getErrorStream(); + Logging.e("httpupload", "file upload failed: http code (" + code + ") " + new Scanner(errorStream).useDelimiter("\\A").next()); + this.entity.fail(); + } + } catch (IOException e) { + errorStream = (null != connection) ? connection.getErrorStream() : null; + String httpResponseMessage = null; + if (null != errorStream) { + httpResponseMessage = new Scanner(errorStream).useDelimiter("\\A").next(); + } + Logging.e("httpupload", ((null != httpResponseMessage) ? ("http response: " + httpResponseMessage + ", ") : "") + "exception message: " + e.getMessage()); + this.entity.fail(); + } finally { + StreamUtil.close(os); + StreamUtil.close(errorStream); + StreamUtil.close(fileInputStream); + if (connection != null) { + connection.disconnect(); + } + wakeLock.release(); + } + } +} \ No newline at end of file diff --git a/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/httpupload/HttpUploadFileTransferService.java b/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/httpupload/HttpUploadFileTransferService.java new file mode 100644 index 00000000..59957d1e --- /dev/null +++ b/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/httpupload/HttpUploadFileTransferService.java @@ -0,0 +1,84 @@ +package de.thedevstack.conversationsplus.services.filetransfer.httpupload; + +import android.util.Pair; + +import java.io.FileNotFoundException; +import java.io.InputStream; + +import de.thedevstack.android.logcat.Logging; +import de.thedevstack.conversationsplus.entities.Account; +import de.thedevstack.conversationsplus.entities.DownloadableFile; +import de.thedevstack.conversationsplus.entities.Message; +import de.thedevstack.conversationsplus.persistance.FileBackend; +import de.thedevstack.conversationsplus.services.AbstractConnectionManager; +import de.thedevstack.conversationsplus.services.FileTransferService; +import de.thedevstack.conversationsplus.services.filetransfer.AbstractFileTransferService; +import de.thedevstack.conversationsplus.utils.MessageUtil; +import de.thedevstack.conversationsplus.utils.XmppSendUtil; +import de.thedevstack.conversationsplus.xmpp.httpupload.HttpUpload; +import de.thedevstack.conversationsplus.xmpp.httpupload.HttpUploadRequestSlotPacketGenerator; +import de.thedevstack.conversationsplus.xmpp.jid.Jid; +import de.thedevstack.conversationsplus.xmpp.stanzas.IqPacket; + +/** + * + */ +public class HttpUploadFileTransferService extends AbstractFileTransferService implements FileTransferService { + + public HttpUploadFileTransferService() { + } + + /** + * Transfers a file for the corresponding message. + * + * @param message the message containing the file to transfer + * @return true if the file transfer was started successfully, false otherwise + */ + @Override + public boolean transferFile(Message message) { + return this.transferFile(message, false); + } + + /** + * Transfers a file for the corresponding message. + * + * @param message the message containing the file to transfer + * @param delay whether the message is delayed or not + * @return true if the file transfer was started successfully, false otherwise + */ + @Override + public boolean transferFile(Message message, boolean delay) { + boolean started = false; + try { + final HttpFileTransferEntity entity = new HttpFileTransferEntity(message, delay); + Account account = message.getConversation().getAccount(); + DownloadableFile file = entity.getFile(); + Pair inputStreamAndExpectedSize = AbstractConnectionManager.createInputStream(file, true); + + entity.setFileInputStream(inputStreamAndExpectedSize.first); + file.setExpectedSize(inputStreamAndExpectedSize.second); + Jid host = account.getXmppConnection().findDiscoItemByFeature(HttpUpload.NAMESPACE); + IqPacket request = HttpUploadRequestSlotPacketGenerator.generate(host, file.getName(), file.getSize(), file.getMimeType()); + XmppSendUtil.sendIqPacket(account, request, new HttpUploadSlotRequestReceived(entity)); + MessageUtil.markMessage(message, Message.STATUS_UNSEND); + started = true; + } catch (FileNotFoundException e) { + Logging.e("httpupload", "Could not find file, exception message: " + e.getMessage()); + } + return started; + } + + /** + * Checks whether a message can be sent using this service or not. + * + * @param message the message to be checked + * @return true if the message can be processed, false otherwise + */ + @Override + public boolean accept(Message message) { + return null != message + && null != message.getConversation() + && null != message.getConversation().getAccount() + && message.getConversation().getAccount().httpUploadAvailable(FileBackend.getFile(message, false).getSize()); + } +} diff --git a/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/httpupload/HttpUploadSlotRequestReceived.java b/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/httpupload/HttpUploadSlotRequestReceived.java new file mode 100644 index 00000000..e0b2332a --- /dev/null +++ b/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/httpupload/HttpUploadSlotRequestReceived.java @@ -0,0 +1,34 @@ +package de.thedevstack.conversationsplus.services.filetransfer.httpupload; + +import de.thedevstack.android.logcat.Logging; +import de.thedevstack.conversationsplus.entities.Account; +import de.thedevstack.conversationsplus.xmpp.OnIqPacketReceived; +import de.thedevstack.conversationsplus.xmpp.exceptions.XmppException; +import de.thedevstack.conversationsplus.xmpp.httpupload.HttpUploadSlot; +import de.thedevstack.conversationsplus.xmpp.httpupload.SlotPacketParser; +import de.thedevstack.conversationsplus.xmpp.stanzas.IqPacket; + +/** + * + */ +public class HttpUploadSlotRequestReceived implements OnIqPacketReceived { + private final HttpFileTransferEntity entity; + + public HttpUploadSlotRequestReceived(HttpFileTransferEntity entity) { + this.entity = entity; + } + + @Override + public void onIqPacketReceived(Account account, IqPacket packet) { + try { + HttpUploadSlot slot = SlotPacketParser.parseGetAndPutUrl(packet); + this.entity.setSlot(slot); + if (!this.entity.isCanceled()) { + new Thread(new HttpFileUploader(this.entity)).start(); + } + } catch (XmppException e) { + Logging.e("httpupload", e.getMessage()); + this.entity.fail(); + } + } +} diff --git a/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/httpupload/HttpUploadedFileEncryptionUiCallback.java b/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/httpupload/HttpUploadedFileEncryptionUiCallback.java new file mode 100644 index 00000000..25a16d78 --- /dev/null +++ b/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/httpupload/HttpUploadedFileEncryptionUiCallback.java @@ -0,0 +1,33 @@ +package de.thedevstack.conversationsplus.services.filetransfer.httpupload; + +import android.app.PendingIntent; + +import de.thedevstack.conversationsplus.entities.Message; +import de.thedevstack.conversationsplus.ui.UiCallback; +import de.thedevstack.conversationsplus.utils.XmppConnectionServiceAccessor; + +/** + * + */ +public class HttpUploadedFileEncryptionUiCallback implements UiCallback { + private final HttpFileTransferEntity entity; + + public HttpUploadedFileEncryptionUiCallback(HttpFileTransferEntity entity) { + this.entity = entity; + } + + @Override + public void success(Message message) { + XmppConnectionServiceAccessor.xmppConnectionService.resendMessage(message, this.entity.isDelayed()); + } + + @Override + public void error(int errorCode, Message object) { + this.entity.fail(); + } + + @Override + public void userInputRequried(PendingIntent pi, Message object) { + this.entity.fail(); + } +} diff --git a/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/jingle/JingleFileTransferService.java b/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/jingle/JingleFileTransferService.java new file mode 100644 index 00000000..46ee7ce5 --- /dev/null +++ b/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/jingle/JingleFileTransferService.java @@ -0,0 +1,51 @@ +package de.thedevstack.conversationsplus.services.filetransfer.jingle; + +import de.thedevstack.conversationsplus.entities.Message; +import de.thedevstack.conversationsplus.services.FileTransferService; +import de.thedevstack.conversationsplus.xmpp.jingle.JingleConnection; +import de.thedevstack.conversationsplus.xmpp.jingle.JingleConnectionManager; + +/** + * + */ +public class JingleFileTransferService implements FileTransferService { + private final JingleConnectionManager jingleConnectionManager; + + public JingleFileTransferService() { + this.jingleConnectionManager = new JingleConnectionManager(); + } + /** + * Transfers a file for the corresponding message. + * + * @param message the message containing the file to transfer + * @return true if the file transfer was successful, false otherwise + */ + @Override + public boolean transferFile(Message message) { + return this.transferFile(message, false); + } + + /** + * Transfers a file for the corresponding message. + * + * @param message the message containing the file to transfer + * @param delay whether the message is delayed or not + * @return true if the file transfer was successful, false otherwise + */ + @Override + public boolean transferFile(Message message, boolean delay) { + JingleConnection jingleConnection = this.jingleConnectionManager.createNewConnection(message); + return null != jingleConnection; + } + + /** + * Checks whether a message can be sent using this service or not. + * + * @param message the message to be checked + * @return true if the message can be processed, false otherwise + */ + @Override + public boolean accept(Message message) { + return message.fixCounterpart(); // No clue why - but this seems to be the check for jingle file transfer + } +} -- cgit v1.2.3 From f45ad10b1baaf09fd4a40d6b63d1cd093623eedc Mon Sep 17 00:00:00 2001 From: steckbrief Date: Mon, 6 Jun 2016 09:05:50 +0200 Subject: Related to FS#131, FS#129, FS#220: - FileTransferFailureReason including types introduced. A failure can be recoverable, non-recoverable or limited recoverable - in case file transfer with the highest weight factor fails, the next file transfer method is used - improved logging - javadoc comments added --- .../filetransfer/AbstractFileTransferService.java | 22 +-- .../services/filetransfer/FileTransferEntity.java | 194 +++++++++++++++++---- .../filetransfer/FileTransferFailureReason.java | 91 ++++++++++ .../filetransfer/FileTransferFailureType.java | 24 +++ .../services/filetransfer/FileTransferManager.java | 58 +++++- .../filetransfer/FileTransferStatusEnum.java | 23 +++ .../filetransfer/FileTransferStatusListener.java | 5 +- .../httpupload/HttpFileTransferEntity.java | 35 ++++ .../filetransfer/httpupload/HttpFileUploader.java | 74 +++++--- .../httpupload/HttpUploadFileTransferService.java | 7 + .../httpupload/HttpUploadSlotRequestReceived.java | 3 +- .../HttpUploadedFileEncryptionUiCallback.java | 5 +- .../jingle/JingleFileTransferService.java | 3 +- 13 files changed, 464 insertions(+), 80 deletions(-) create mode 100644 src/main/java/de/thedevstack/conversationsplus/services/filetransfer/FileTransferFailureReason.java create mode 100644 src/main/java/de/thedevstack/conversationsplus/services/filetransfer/FileTransferFailureType.java create mode 100644 src/main/java/de/thedevstack/conversationsplus/services/filetransfer/FileTransferStatusEnum.java (limited to 'src/main/java/de/thedevstack/conversationsplus/services/filetransfer') diff --git a/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/AbstractFileTransferService.java b/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/AbstractFileTransferService.java index 02bd04b9..c24603b7 100644 --- a/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/AbstractFileTransferService.java +++ b/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/AbstractFileTransferService.java @@ -1,31 +1,23 @@ package de.thedevstack.conversationsplus.services.filetransfer; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; -import de.thedevstack.conversationsplus.entities.Message; import de.thedevstack.conversationsplus.services.FileTransferService; /** * */ -public abstract class AbstractFileTransferService implements FileTransferService, FileTransferStatusListener { - protected List failedMessages = new ArrayList<>(); +public abstract class AbstractFileTransferService implements FileTransferService { + private List statusListeners = new ArrayList<>(); @Override - public void onFailure(Message message, boolean delay) { - this.failedMessages.add(message); + public void addFileTransferStatusListener(FileTransferStatusListener... listeners) { + this.statusListeners.addAll(Arrays.asList(listeners)); } - @Override - public void onSuccess(Message message, boolean delay) { - if (this.failedMessages.contains(message)) { - this.failedMessages.remove(message); - } - } - - @Override - public boolean accept(Message message) { - return !this.failedMessages.contains(message); + protected void addStatusListenerToEntity(FileTransferEntity entity) { + entity.addFileTransferStatusListener(this.statusListeners.toArray(new FileTransferStatusListener[0])); } } diff --git a/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/FileTransferEntity.java b/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/FileTransferEntity.java index a44cf49d..e1b40fa6 100644 --- a/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/FileTransferEntity.java +++ b/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/FileTransferEntity.java @@ -1,60 +1,95 @@ package de.thedevstack.conversationsplus.services.filetransfer; import java.io.InputStream; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; import de.thedevstack.conversationsplus.entities.DownloadableFile; import de.thedevstack.conversationsplus.entities.Message; import de.thedevstack.conversationsplus.entities.Transferable; import de.thedevstack.conversationsplus.persistance.FileBackend; -import de.thedevstack.conversationsplus.utils.MessageUtil; +import de.thedevstack.conversationsplus.utils.StreamUtil; /** * */ public class FileTransferEntity implements Transferable { + /** + * Listeners to inform about a status change. + */ + private final List statusListeners = new ArrayList<>(); + /** + * The associated message. + */ private final Message message; - private int tries = 0; - private boolean transferred = false; - private boolean canceled = false; - private boolean failed = false; + /** + * Number of attempts. + */ + private int attempts = 0; + /** + * The status of the file transfer. + */ + private FileTransferStatusEnum transferStatus; + /** + * Number of bytes transmitted. + */ private long transmitted = 0; + /** + * The associated file. + */ private DownloadableFile file; + /** + * The associated file as stream. + */ private InputStream fileInputStream; + /** + * Initializes the FileTransferEntity based on the associated message. + * This initialization includes loading the file and associating this transferable to the message. + * @param message the message in which the file to transfer is contained. + */ public FileTransferEntity(Message message) { this.message = message; this.message.setTransferable(this); this.file = FileBackend.getFile(message, false); } - @Override - public boolean equals(Object o) { - FileTransferEntity other = (FileTransferEntity)o; - if ((this.message == null && other.message != null) - || (this.message != null && other.message == null)) { - return false; - } else if (this.message == null && other.message == null) { - return true; - } - return this.message.getUuid().equals(other.message.getUuid()); - } - + /** + * Start something. + * Empty implementation since documentation in interface is missing. + * @return false + */ @Override public boolean start() { return false; } + /** + * Returns the global transferable status. + * + * @return {@value STATUS_FAILED} if #isFailed returns true, {@value STATUS_UPLOADING} otherwise + */ @Override public int getStatus() { - int status = (failed) ? STATUS_FAILED : STATUS_UPLOADING; + int status = (isFailed()) ? STATUS_FAILED : STATUS_UPLOADING; return status; } + /** + * Returns the expected file size of the underlying file. + * @return the expected file size or 0 if no file is associated. + */ @Override public long getFileSize() { return file == null ? 0 : file.getExpectedSize(); } + /** + * Calculates the current progress in percent. + * + * @return the current progress in percent + */ @Override public int getProgress() { if (file == null) { @@ -63,46 +98,137 @@ public class FileTransferEntity implements Transferable { return (int) ((((double) transmitted) / file.getExpectedSize()) * 100); } + /** + * Cancels the file transfer and informs the listeners about cancellation. + */ @Override public void cancel() { - this.canceled = true; + this.transferStatus = FileTransferStatusEnum.CANCELED; + + this.close(); + + for (FileTransferStatusListener listener : this.statusListeners) { + listener.onCancel(this); + } + } + + /** + * Starts an transfer attempt. + */ + public void startAttempt() { + this.attempts++; + this.transferStatus = FileTransferStatusEnum.TRANSFERRING; + } + + /** + * Fails an file transfer and informs the listeners about failure. + * + * @param failureReason the reason of failure. + */ + public void fail(FileTransferFailureReason failureReason) { + this.transferStatus = FileTransferStatusEnum.FAILED; + + this.close(); + + failureReason.setAttempt(this.attempts); + for (FileTransferStatusListener listener : this.statusListeners) { + listener.onFailure(this, failureReason); + } + } + + /** + * Updates the progress by adding parameter value to current progress value. + * + * @param progress the number of newly transferred bytes + */ + public void updateProgress(long progress) { + if (0 == this.attempts) { + this.startAttempt(); + } + this.transmitted += progress; + } + + /** + * Set the status of the file transfer to FileTransferStatusEnum#TRANSFERRED and informs the listeners about success. + */ + public void transferred() { + this.transferStatus = FileTransferStatusEnum.TRANSFERRED; + + this.close(); + + for (FileTransferStatusListener listener : this.statusListeners) { + listener.onSuccess(this); + } } - public void fail() { - this.failed = true; + /** + * Closes the file input stream (if it is not yet closed) and removes association with message. + */ + private void close() { + StreamUtil.close(this.fileInputStream); this.getMessage().setTransferable(null); - MessageUtil.markMessage(this.getMessage(), Message.STATUS_SEND_FAILED); } - public Message getMessage() { - return message; + /** + * Whether the file is transferred or not. + * @return true if the file is successfully transferred, false otherwise + */ + public boolean isTransferred() { + return FileTransferStatusEnum.TRANSFERRED == this.transferStatus; } + /** + * Whether the file transfer is canceled or not. + * @return true if the file transfer was canceled, false otherwise + */ public boolean isCanceled() { - return this.canceled; + return FileTransferStatusEnum.CANCELED == this.transferStatus; } + /** + * Whether the file transfer failed or not. + * @return true if the file transfer failed, false otherwise + */ public boolean isFailed() { - return failed; + return FileTransferStatusEnum.FAILED == this.transferStatus; } - public void updateProgress(long progress) { - this.transmitted += progress; + public void setFileInputStream(InputStream fileInputStream) { + this.fileInputStream = fileInputStream; + } + + public InputStream getFileInputStream() { + return fileInputStream; + } + + public Message getMessage() { + return message; } public DownloadableFile getFile() { return file; } - public void transferred() { - this.transferred = true; + public void addFileTransferStatusListener(FileTransferStatusListener... listeners) { + this.statusListeners.addAll(Arrays.asList(listeners)); } - public void setFileInputStream(InputStream fileInputStream) { - this.fileInputStream = fileInputStream; + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + FileTransferEntity that = (FileTransferEntity) o; + + String uuid = message != null ? message.getUuid() : null; + String thatUuid = that.message != null ? that.message.getUuid() : null; + + return uuid != null ? uuid.equals(thatUuid) : thatUuid == null; + } - public InputStream getFileInputStream() { - return fileInputStream; + @Override + public int hashCode() { + return message != null && message.getUuid() != null ? message.getUuid().hashCode() : 0; } } diff --git a/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/FileTransferFailureReason.java b/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/FileTransferFailureReason.java new file mode 100644 index 00000000..353c34d8 --- /dev/null +++ b/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/FileTransferFailureReason.java @@ -0,0 +1,91 @@ +package de.thedevstack.conversationsplus.services.filetransfer; + +/** + * + */ +public final class FileTransferFailureReason { + private final FileTransferFailureType type; + private int attempt; + private Exception causedByException; + private String failureMessage; + + public static FileTransferFailureReason createRecoverableFailureReason(Exception e) { + return createFailureReason(FileTransferFailureType.RECOVERABLE, e.getMessage(), e); + } + + public static FileTransferFailureReason createRecoverableFailureReason(String reason) { + return createFailureReason(FileTransferFailureType.RECOVERABLE, reason, null); + } + + public static FileTransferFailureReason createRecoverableFailureReason(Exception e, String failureMessage) { + return createFailureReason(FileTransferFailureType.RECOVERABLE, failureMessage, e); + } + + public static FileTransferFailureReason createLimitedRecoverableFailureReason(Exception e) { + return createFailureReason(FileTransferFailureType.LIMITEDRECOVERABLE, e.getMessage(), e); + } + + public static FileTransferFailureReason createLimitedRecoverableFailureReason(String reason) { + return createFailureReason(FileTransferFailureType.LIMITEDRECOVERABLE, reason, null); + } + + public static FileTransferFailureReason createLimitedRecoverableFailureReason(Exception e, String failureMessage) { + return createFailureReason(FileTransferFailureType.LIMITEDRECOVERABLE, failureMessage, e); + } + + public static FileTransferFailureReason createNonRecoverableFailureReason(Exception e) { + return createFailureReason(FileTransferFailureType.NONRECOVERABLE, e.getMessage(), e); + } + + public static FileTransferFailureReason createNonRecoverableFailureReason(String reason) { + return createFailureReason(FileTransferFailureType.NONRECOVERABLE, reason, null); + } + + public static FileTransferFailureReason createNonRecoverableFailureReason(Exception e, String failureMessage) { + return createFailureReason(FileTransferFailureType.NONRECOVERABLE, failureMessage, e); + } + + private static FileTransferFailureReason createFailureReason(FileTransferFailureType type, String message, Exception e) { + FileTransferFailureReason ftfr = new FileTransferFailureReason(type); + ftfr.setCausedByException(e); + if (null != e && (null == message || message.isEmpty())) { + message = e.getMessage(); + } + ftfr.setFailureMessage(message); + + return ftfr; + + } + + private FileTransferFailureReason(FileTransferFailureType type) { + this.type = type; + } + + public void setFailureMessage(String failureMessage) { + this.failureMessage = failureMessage; + } + + public String getFailureMessage() { + return failureMessage; + } + + public FileTransferFailureType getType() { + return type; + } + + public void setCausedByException(Exception causedByException) { + this.causedByException = causedByException; + } + + public Exception getCausedByException() { + return causedByException; + } + + public void setAttempt(int attempt) { + this.attempt = attempt; + } + + public boolean isRecoverable() { + return this.type.isRetryPossible(this.attempt); + } +} diff --git a/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/FileTransferFailureType.java b/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/FileTransferFailureType.java new file mode 100644 index 00000000..3daca38e --- /dev/null +++ b/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/FileTransferFailureType.java @@ -0,0 +1,24 @@ +package de.thedevstack.conversationsplus.services.filetransfer; + +/** + * + */ +public enum FileTransferFailureType { + RECOVERABLE(Integer.MAX_VALUE), + LIMITEDRECOVERABLE(5), + NONRECOVERABLE(1); + + int maxAttempts; + + FileTransferFailureType(int maxAttempts) { + this.maxAttempts = maxAttempts; + } + + public boolean isRetryPossible(int attempt) { + return attempt < this.maxAttempts; + } + + public int getMaxAttempts() { + return this.maxAttempts; + } +} diff --git a/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/FileTransferManager.java b/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/FileTransferManager.java index f7b3f4e2..017b88ea 100644 --- a/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/FileTransferManager.java +++ b/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/FileTransferManager.java @@ -9,13 +9,16 @@ import de.thedevstack.android.logcat.Logging; import de.thedevstack.conversationsplus.Config; import de.thedevstack.conversationsplus.entities.Message; import de.thedevstack.conversationsplus.services.FileTransferService; +import de.thedevstack.conversationsplus.services.filetransfer.httpupload.HttpFileTransferEntity; +import de.thedevstack.conversationsplus.utils.MessageUtil; /** * */ -public class FileTransferManager implements FileTransferService { +public class FileTransferManager implements FileTransferStatusListener { private SortedSet transferServices; private static final FileTransferManager INSTANCE = new FileTransferManager(); + private final HashMap activeTransfers = new HashMap<>(); private FileTransferManager() { this.transferServices = new TreeSet<>(); @@ -40,6 +43,7 @@ public class FileTransferManager implements FileTransferService { } public static void addFileTransferService(int weight, FileTransferService fts) { + fts.addFileTransferStatusListener(INSTANCE); INSTANCE.transferServices.add(new WeightedTransferService(weight, fts)); } @@ -68,14 +72,13 @@ public class FileTransferManager implements FileTransferService { * @param delay whether the message is delayed or not * @return true if the file transfer was successful, false otherwise */ - @Override public boolean transferFile(Message message, boolean delay) { Logging.d(Config.LOGTAG, "send file message"); boolean transferSuccessfullyStarted = false; for (WeightedTransferService wts : this.transferServices) { try { if (wts.fileTransferService.accept(message)) { - transferSuccessfullyStarted = wts.fileTransferService.transferFile(message, delay); + transferSuccessfullyStarted = this.startFileTransfer(message, delay, wts); if (transferSuccessfullyStarted) { break; } @@ -93,11 +96,58 @@ public class FileTransferManager implements FileTransferService { * @param message the message to be checked * @return true if the message can be processed, false otherwise */ - @Override public boolean accept(Message message) { return message.needsUploading(); } + @Override + public void onFailure(FileTransferEntity entity, FileTransferFailureReason failureReason) { + WeightedTransferService wts = this.activeTransfers.get(entity.getMessage().getUuid()); + if (null == wts) { + return; + } + boolean delayed = (entity instanceof HttpFileTransferEntity) && ((HttpFileTransferEntity) entity).isDelayed(); + if (failureReason.isRecoverable()) { + wts.fileTransferService.transferFile(entity.getMessage(), delayed); + } else { + boolean retransferStarted = false; + this.activeTransfers.remove(entity.getMessage().getUuid()); + for (WeightedTransferService newWts : this.transferServices.tailSet(wts)) { + if (newWts == wts) { // Same Reference + continue; + } + if (newWts.fileTransferService.accept(entity.getMessage())) { + retransferStarted = startFileTransfer(entity.getMessage(), delayed, newWts); + if (retransferStarted) { + break; + } + } + } + if (!retransferStarted) { + MessageUtil.markMessage(entity.getMessage(), Message.STATUS_SEND_FAILED); + } + } + } + + @Override + public void onCancel(FileTransferEntity entity) { + this.activeTransfers.remove(entity.getMessage().getUuid()); + MessageUtil.markMessage(entity.getMessage(), Message.STATUS_SEND_FAILED); // TODO New Status CANCELED! + } + + @Override + public void onSuccess(FileTransferEntity entity) { + this.activeTransfers.remove(entity.getMessage().getUuid()); + } + + private boolean startFileTransfer(Message message, boolean delayed, WeightedTransferService wts) { + boolean transferSuccessfullyStarted = wts.fileTransferService.transferFile(message, delayed); + if (transferSuccessfullyStarted) { + this.activeTransfers.put(message.getUuid(), wts); + } + return transferSuccessfullyStarted; + } + static class WeightedTransferService implements Comparable { int weight; FileTransferService fileTransferService; diff --git a/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/FileTransferStatusEnum.java b/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/FileTransferStatusEnum.java new file mode 100644 index 00000000..e55d94d8 --- /dev/null +++ b/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/FileTransferStatusEnum.java @@ -0,0 +1,23 @@ +package de.thedevstack.conversationsplus.services.filetransfer; + +/** + * Status values of a file transfer. + */ +public enum FileTransferStatusEnum { + /** + * The file transfer is in progress. + */ + TRANSFERRING, + /** + * The file transfer was finished successfully. + */ + TRANSFERRED, + /** + * The file transfer failed. + */ + FAILED, + /** + * The file transfer was canceled. + */ + CANCELED; +} diff --git a/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/FileTransferStatusListener.java b/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/FileTransferStatusListener.java index 89af5b39..86d6fbfa 100644 --- a/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/FileTransferStatusListener.java +++ b/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/FileTransferStatusListener.java @@ -6,6 +6,7 @@ import de.thedevstack.conversationsplus.entities.Message; * */ public interface FileTransferStatusListener { - void onFailure(Message message, boolean delay); - void onSuccess(Message message, boolean delay); + void onFailure(FileTransferEntity entity, FileTransferFailureReason failureReason); + void onSuccess(FileTransferEntity entity); + void onCancel(FileTransferEntity entity); } diff --git a/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/httpupload/HttpFileTransferEntity.java b/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/httpupload/HttpFileTransferEntity.java index 8efd498b..a8e5734f 100644 --- a/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/httpupload/HttpFileTransferEntity.java +++ b/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/httpupload/HttpFileTransferEntity.java @@ -1,9 +1,16 @@ package de.thedevstack.conversationsplus.services.filetransfer.httpupload; +import java.net.MalformedURLException; +import java.net.URL; + +import de.thedevstack.android.logcat.Logging; import de.thedevstack.conversationsplus.Config; import de.thedevstack.conversationsplus.ConversationsPlusApplication; import de.thedevstack.conversationsplus.entities.Message; import de.thedevstack.conversationsplus.services.filetransfer.FileTransferEntity; +import de.thedevstack.conversationsplus.services.filetransfer.FileTransferFailureReason; +import de.thedevstack.conversationsplus.utils.CryptoHelper; +import de.thedevstack.conversationsplus.utils.MessageUtil; import de.thedevstack.conversationsplus.xmpp.httpupload.HttpUploadSlot; /** @@ -49,4 +56,32 @@ public class HttpFileTransferEntity extends FileTransferEntity { public boolean isDelayed() { return this.delayed; } + + @Override + public void fail(FileTransferFailureReason failureReason) { + this.getMessage().setHttpUploaded(false); + super.fail(failureReason); + } + + @Override + public void cancel() { + this.getMessage().setHttpUploaded(false); + super.cancel(); + } + + @Override + public void transferred() { + try { + URL getUrl = new URL(this.getGetUrl()); + if (this.getKey() != null) { + getUrl = new URL(getUrl.toString() + "#" + CryptoHelper.bytesToHex(this.getKey())); + } + + MessageUtil.updateFileParams(this.getMessage(), getUrl); + } catch (MalformedURLException e) { + Logging.e("httpupload", "Not a valid get url"); + } + + super.transferred(); + } } diff --git a/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/httpupload/HttpFileUploader.java b/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/httpupload/HttpFileUploader.java index 6352c7a7..8edc5be7 100644 --- a/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/httpupload/HttpFileUploader.java +++ b/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/httpupload/HttpFileUploader.java @@ -7,6 +7,7 @@ import java.io.InputStream; import java.io.OutputStream; import java.net.HttpURLConnection; import java.net.URL; +import java.net.UnknownHostException; import java.util.Scanner; import javax.net.ssl.HttpsURLConnection; @@ -18,6 +19,7 @@ import de.thedevstack.conversationsplus.entities.DownloadableFile; import de.thedevstack.conversationsplus.entities.Message; import de.thedevstack.conversationsplus.http.HttpConnectionManager; import de.thedevstack.conversationsplus.persistance.FileBackend; +import de.thedevstack.conversationsplus.services.filetransfer.FileTransferFailureReason; import de.thedevstack.conversationsplus.utils.CryptoHelper; import de.thedevstack.conversationsplus.utils.MessageUtil; import de.thedevstack.conversationsplus.utils.StreamUtil; @@ -43,14 +45,14 @@ public class HttpFileUploader implements Runnable { private void upload() { OutputStream os = null; - InputStream errorStream = null; + HttpURLConnection connection = null; InputStream fileInputStream = null; DownloadableFile file = this.entity.getFile(); PowerManager.WakeLock wakeLock = ConversationsPlusApplication.createPartialWakeLock("http_upload_" + entity.getMessage().getUuid()); try { wakeLock.acquire(); - Logging.d(Config.LOGTAG, "uploading to " + this.entity.getPutUrl()); + Logging.d("httpupload", "uploading file to " + this.entity.getPutUrl()); URL putUrl = new URL(this.entity.getPutUrl()); fileInputStream = this.entity.getFileInputStream(); connection = (HttpURLConnection) putUrl.openConnection(); @@ -60,7 +62,7 @@ public class HttpFileUploader implements Runnable { } connection.setRequestMethod(HTTP_METHOD); connection.setFixedLengthStreamingMode((int) file.getExpectedSize()); - String mime = this.entity.getFile().getMimeType(); + String mime = file.getMimeType(); connection.setRequestProperty(MIME_REQUEST_PROPERTY_NAME, mime == null ? HttpUpload.DEFAULT_MIME_TYPE : mime); connection.setRequestProperty(USER_AGENT_REQUEST_PROPERTY_NAME, ConversationsPlusApplication.getNameAndVersion()); connection.setDoOutput(true); @@ -78,16 +80,13 @@ public class HttpFileUploader implements Runnable { fileInputStream.close(); int code = connection.getResponseCode(); if (code == 200 || code == 201) { - Logging.d(Config.LOGTAG, "finished uploading file"); + Logging.d("httpupload", "finished uploading file"); this.entity.transferred(); - URL getUrl = new URL(this.entity.getGetUrl()); - if (this.entity.getKey() != null) { - getUrl = new URL(getUrl.toString() + "#" + CryptoHelper.bytesToHex(this.entity.getKey())); - } + + FileBackend.updateMediaScanner(file, XmppConnectionServiceAccessor.xmppConnectionService); // Why??? + + // Send the URL to the counterpart Message message = this.entity.getMessage(); - MessageUtil.updateFileParams(message, getUrl); - FileBackend.updateMediaScanner(file, XmppConnectionServiceAccessor.xmppConnectionService); - message.setTransferable(null); message.setCounterpart(message.getConversation().getJid().toBareJid()); if (message.getEncryption() == Message.ENCRYPTION_DECRYPTED) { XmppConnectionServiceAccessor.xmppConnectionService.getPgpEngine().encrypt(message, new HttpUploadedFileEncryptionUiCallback(this.entity)); @@ -95,21 +94,37 @@ public class HttpFileUploader implements Runnable { XmppConnectionServiceAccessor.xmppConnectionService.resendMessage(message, this.entity.isDelayed()); } } else { - errorStream = connection.getErrorStream(); - Logging.e("httpupload", "file upload failed: http code (" + code + ") " + new Scanner(errorStream).useDelimiter("\\A").next()); - this.entity.fail(); + String httpResponseMessage = this.getErrorStreamContent(connection); + String errorMessage = "file upload failed: http code (" + code + ") " + httpResponseMessage; + Logging.e("httpupload", errorMessage); + FileTransferFailureReason failureReason = null; + switch (code) { + case 403: + failureReason = FileTransferFailureReason.createLimitedRecoverableFailureReason(errorMessage); + break; + case 404: + failureReason = FileTransferFailureReason.createNonRecoverableFailureReason("Upload URL not found"); + break; + case 500: + failureReason = FileTransferFailureReason.createNonRecoverableFailureReason("Internal Server Error"); + break; + default: + failureReason = FileTransferFailureReason.createRecoverableFailureReason(errorMessage); + } + this.entity.fail(failureReason); } + } catch (UnknownHostException e) { + Logging.e("httpupload", "File upload failed due to unknown host. " + e.getMessage()); + //if (!HAS_INTERNET_CONNECTION) { + this.entity.fail(FileTransferFailureReason.createRecoverableFailureReason(e, "Missing internet connection")); + //} } catch (IOException e) { - errorStream = (null != connection) ? connection.getErrorStream() : null; - String httpResponseMessage = null; - if (null != errorStream) { - httpResponseMessage = new Scanner(errorStream).useDelimiter("\\A").next(); - } + String httpResponseMessage = this.getErrorStreamContent(connection); Logging.e("httpupload", ((null != httpResponseMessage) ? ("http response: " + httpResponseMessage + ", ") : "") + "exception message: " + e.getMessage()); - this.entity.fail(); + // TODO: Differentiate IOException while internet connection wasn't available and others + this.entity.fail(FileTransferFailureReason.createRecoverableFailureReason(e, httpResponseMessage)); } finally { StreamUtil.close(os); - StreamUtil.close(errorStream); StreamUtil.close(fileInputStream); if (connection != null) { connection.disconnect(); @@ -117,4 +132,21 @@ public class HttpFileUploader implements Runnable { wakeLock.release(); } } + + private String getErrorStreamContent(HttpURLConnection connection) { + InputStream errorStream = null; + String httpResponseMessage = null; + try { + errorStream = connection.getErrorStream(); + if (null != errorStream) { + Scanner scanner = new Scanner(errorStream).useDelimiter("\\A"); + if (scanner.hasNext()) { + httpResponseMessage = scanner.next(); + } + } + } finally { + StreamUtil.close(errorStream); + } + return httpResponseMessage; + } } \ No newline at end of file diff --git a/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/httpupload/HttpUploadFileTransferService.java b/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/httpupload/HttpUploadFileTransferService.java index 59957d1e..fb150a92 100644 --- a/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/httpupload/HttpUploadFileTransferService.java +++ b/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/httpupload/HttpUploadFileTransferService.java @@ -48,19 +48,26 @@ public class HttpUploadFileTransferService extends AbstractFileTransferService i */ @Override public boolean transferFile(Message message, boolean delay) { + Logging.d("httpupload", "Starting to upload file"); boolean started = false; try { final HttpFileTransferEntity entity = new HttpFileTransferEntity(message, delay); + this.addStatusListenerToEntity(entity); + entity.startAttempt(); Account account = message.getConversation().getAccount(); DownloadableFile file = entity.getFile(); Pair inputStreamAndExpectedSize = AbstractConnectionManager.createInputStream(file, true); entity.setFileInputStream(inputStreamAndExpectedSize.first); file.setExpectedSize(inputStreamAndExpectedSize.second); + + Logging.d("httpupload", "Requesting upload slot for file upload"); Jid host = account.getXmppConnection().findDiscoItemByFeature(HttpUpload.NAMESPACE); IqPacket request = HttpUploadRequestSlotPacketGenerator.generate(host, file.getName(), file.getSize(), file.getMimeType()); XmppSendUtil.sendIqPacket(account, request, new HttpUploadSlotRequestReceived(entity)); MessageUtil.markMessage(message, Message.STATUS_UNSEND); + + Logging.d("httpupload", "Upload slot for file upload requested"); started = true; } catch (FileNotFoundException e) { Logging.e("httpupload", "Could not find file, exception message: " + e.getMessage()); diff --git a/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/httpupload/HttpUploadSlotRequestReceived.java b/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/httpupload/HttpUploadSlotRequestReceived.java index e0b2332a..462a370c 100644 --- a/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/httpupload/HttpUploadSlotRequestReceived.java +++ b/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/httpupload/HttpUploadSlotRequestReceived.java @@ -2,6 +2,7 @@ package de.thedevstack.conversationsplus.services.filetransfer.httpupload; import de.thedevstack.android.logcat.Logging; import de.thedevstack.conversationsplus.entities.Account; +import de.thedevstack.conversationsplus.services.filetransfer.FileTransferFailureReason; import de.thedevstack.conversationsplus.xmpp.OnIqPacketReceived; import de.thedevstack.conversationsplus.xmpp.exceptions.XmppException; import de.thedevstack.conversationsplus.xmpp.httpupload.HttpUploadSlot; @@ -28,7 +29,7 @@ public class HttpUploadSlotRequestReceived implements OnIqPacketReceived { } } catch (XmppException e) { Logging.e("httpupload", e.getMessage()); - this.entity.fail(); + this.entity.fail(FileTransferFailureReason.createNonRecoverableFailureReason(e)); } } } diff --git a/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/httpupload/HttpUploadedFileEncryptionUiCallback.java b/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/httpupload/HttpUploadedFileEncryptionUiCallback.java index 25a16d78..e3935252 100644 --- a/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/httpupload/HttpUploadedFileEncryptionUiCallback.java +++ b/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/httpupload/HttpUploadedFileEncryptionUiCallback.java @@ -3,6 +3,7 @@ package de.thedevstack.conversationsplus.services.filetransfer.httpupload; import android.app.PendingIntent; import de.thedevstack.conversationsplus.entities.Message; +import de.thedevstack.conversationsplus.services.filetransfer.FileTransferFailureReason; import de.thedevstack.conversationsplus.ui.UiCallback; import de.thedevstack.conversationsplus.utils.XmppConnectionServiceAccessor; @@ -23,11 +24,11 @@ public class HttpUploadedFileEncryptionUiCallback implements UiCallback @Override public void error(int errorCode, Message object) { - this.entity.fail(); + this.entity.fail(FileTransferFailureReason.createLimitedRecoverableFailureReason("Failed to encrypt")); } @Override public void userInputRequried(PendingIntent pi, Message object) { - this.entity.fail(); + this.entity.fail(FileTransferFailureReason.createLimitedRecoverableFailureReason("Failed to encrypt, user input would have been required")); } } diff --git a/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/jingle/JingleFileTransferService.java b/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/jingle/JingleFileTransferService.java index 46ee7ce5..5d8ddd4e 100644 --- a/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/jingle/JingleFileTransferService.java +++ b/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/jingle/JingleFileTransferService.java @@ -2,13 +2,14 @@ package de.thedevstack.conversationsplus.services.filetransfer.jingle; import de.thedevstack.conversationsplus.entities.Message; import de.thedevstack.conversationsplus.services.FileTransferService; +import de.thedevstack.conversationsplus.services.filetransfer.AbstractFileTransferService; import de.thedevstack.conversationsplus.xmpp.jingle.JingleConnection; import de.thedevstack.conversationsplus.xmpp.jingle.JingleConnectionManager; /** * */ -public class JingleFileTransferService implements FileTransferService { +public class JingleFileTransferService extends AbstractFileTransferService implements FileTransferService { private final JingleConnectionManager jingleConnectionManager; public JingleFileTransferService() { -- cgit v1.2.3 From 3ed7cb54e5858afaadc3f7ec5bc01edb61e1428e Mon Sep 17 00:00:00 2001 From: steckbrief Date: Mon, 22 Aug 2016 21:30:36 +0200 Subject: Basic filetransfer http delete implementation; Exceptions for IqPacketError added --- .../filetransfer/http/DeleteRemoteFile.java | 22 ++++++ .../filetransfer/http/DeleteRemoteFileService.java | 30 ++++++++ .../filetransfer/http/DeleteTokenReceived.java | 83 ++++++++++++++++++++++ 3 files changed, 135 insertions(+) create mode 100644 src/main/java/de/thedevstack/conversationsplus/services/filetransfer/http/DeleteRemoteFile.java create mode 100644 src/main/java/de/thedevstack/conversationsplus/services/filetransfer/http/DeleteRemoteFileService.java create mode 100644 src/main/java/de/thedevstack/conversationsplus/services/filetransfer/http/DeleteTokenReceived.java (limited to 'src/main/java/de/thedevstack/conversationsplus/services/filetransfer') diff --git a/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/http/DeleteRemoteFile.java b/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/http/DeleteRemoteFile.java new file mode 100644 index 00000000..9f6b3bbd --- /dev/null +++ b/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/http/DeleteRemoteFile.java @@ -0,0 +1,22 @@ +package de.thedevstack.conversationsplus.services.filetransfer.http; + +import android.support.annotation.NonNull; + +import de.thedevstack.conversationsplus.entities.Message; +import de.thedevstack.conversationsplus.entities.RemoteFile; + +/** + * Created by steckbrief on 22.08.2016. + */ +public class DeleteRemoteFile extends RemoteFile { + private final Message message; + + public DeleteRemoteFile(@NonNull String path, @NonNull Message message) { + super(path); + this.message = message; + } + + public Message getMessage() { + return message; + } +} diff --git a/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/http/DeleteRemoteFileService.java b/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/http/DeleteRemoteFileService.java new file mode 100644 index 00000000..57f408cc --- /dev/null +++ b/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/http/DeleteRemoteFileService.java @@ -0,0 +1,30 @@ +package de.thedevstack.conversationsplus.services.filetransfer.http; + +import de.thedevstack.conversationsplus.entities.Account; +import de.thedevstack.conversationsplus.entities.Message; +import de.thedevstack.conversationsplus.utils.XmppSendUtil; +import de.thedevstack.conversationsplus.xmpp.filetransfer.http.FileTransferHttp; +import de.thedevstack.conversationsplus.xmpp.filetransfer.http.FileTransferHttpDeleteSlotRequestPacketGenerator; +import de.thedevstack.conversationsplus.xmpp.jid.Jid; +import de.thedevstack.conversationsplus.xmpp.stanzas.IqPacket; + +/** + * Created by steckbrief on 21.08.2016. + */ +public class DeleteRemoteFileService { + public void deleteRemoteFile(Message message) { + if (message.isHttpUploaded()) { + String path = message.getBody(); + if (message.hasFileOnRemoteHost()) { + path = message.getFileParams().url.toString(); + } + + DeleteRemoteFile remoteFile = new DeleteRemoteFile(path, message); + Account account = message.getConversation().getAccount(); + Jid host = account.getXmppConnection().findDiscoItemByFeature(FileTransferHttp.NAMESPACE); + IqPacket request = FileTransferHttpDeleteSlotRequestPacketGenerator.generate(host, path); + + XmppSendUtil.sendIqPacket(account, request, new DeleteTokenReceived(remoteFile)); + } + } +} diff --git a/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/http/DeleteTokenReceived.java b/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/http/DeleteTokenReceived.java new file mode 100644 index 00000000..fb83219b --- /dev/null +++ b/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/http/DeleteTokenReceived.java @@ -0,0 +1,83 @@ +package de.thedevstack.conversationsplus.services.filetransfer.http; + +import org.json.JSONException; +import org.json.JSONObject; + +import java.io.IOException; + +import de.thedevstack.android.logcat.Logging; +import de.thedevstack.conversationsplus.entities.Account; +import de.thedevstack.conversationsplus.entities.Message; +import de.thedevstack.conversationsplus.http.HttpClient; +import de.thedevstack.conversationsplus.utils.MessageUtil; +import de.thedevstack.conversationsplus.xmpp.OnIqPacketReceived; +import de.thedevstack.conversationsplus.xmpp.exceptions.XmppException; +import de.thedevstack.conversationsplus.xmpp.filetransfer.http.DeleteSlotPacketParser; +import de.thedevstack.conversationsplus.xmpp.stanzas.IqPacket; +import okhttp3.Call; +import okhttp3.Callback; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.Response; + +/** + * Created by steckbrief on 21.08.2016. + */ +public class DeleteTokenReceived implements OnIqPacketReceived { + private static final String HEADER_NAME_DELETE_TOKEN = "X-FILETRANSFER-HTTP-DELETE-TOKEN"; + private final DeleteRemoteFile remoteFile; + + public DeleteTokenReceived(DeleteRemoteFile remoteFile) { + this.remoteFile = remoteFile; + } + + @Override + public void onIqPacketReceived(Account account, IqPacket packet) { + try { + String url = this.remoteFile.getPath(); + String deleteToken = DeleteSlotPacketParser.parseDeleteToken(packet); + Logging.d("filetransfer.http.delete", "Got delete token '" + deleteToken + "' for remote file '" + remoteFile.getPath() + "'"); + OkHttpClient client = HttpClient.getClient(true); + Request request = new Request.Builder() + .url(url) + .addHeader(HEADER_NAME_DELETE_TOKEN, deleteToken) + .delete() + .build(); + client.newCall(request).enqueue(new Callback() { + @Override + public void onFailure(Call call, IOException e) { + Logging.e("filetransfer.http.delete", "Error while connecting to '" + call.request().url() + "': " + e.getMessage()); + MessageUtil.markMessage(remoteFile.getMessage(), Message.STATUS_REMOTE_FILE_DELETE_FAILED); + } + + @Override + public void onResponse(Call call, Response response) throws IOException { + if (response.isSuccessful()) { + MessageUtil.markMessage(remoteFile.getMessage(), Message.STATUS_REMOTE_FILE_DELETED); + Logging.i("filetransfer.http.delete", "Remote file successfully deleted '" + remoteFile.getPath() + "'"); + } else { + String detailedMessage = response.body().string(); + switch (response.code()) { + case 403: + case 404: + case 500: + try { + JSONObject jsonObject = new JSONObject(detailedMessage); + detailedMessage = jsonObject.getString("msg"); + } catch (JSONException e) { + Logging.e("filetransfer.http.delete", "Failed to get error message from expected json response: " + detailedMessage); + } + break; + } + Logging.e("filetransfer.http.delete", "Could not delete remote file '" + remoteFile.getPath() + "'. Response Code: " + response.code() + ", details: " + detailedMessage); + MessageUtil.markMessage(remoteFile.getMessage(), Message.STATUS_REMOTE_FILE_DELETE_FAILED); + } + } + }); + + } catch (XmppException e) { + Logging.e("filetransfer.http.delete", "Error while trying to get the delete token: " + e.getMessage()); + MessageUtil.markMessage(remoteFile.getMessage(), Message.STATUS_REMOTE_FILE_DELETE_FAILED); + } + } +} -- cgit v1.2.3 From 2d462a746ea6e733f9f2836b181795c80de5aae5 Mon Sep 17 00:00:00 2001 From: steckbrief Date: Tue, 23 Aug 2016 09:46:51 +0200 Subject: xmpp.httpupload moved to new namespace xmpp.filetransfer.http.upload; delete parts of xmpp.filetransfer.http moved to .delete package --- .../services/filetransfer/http/DeleteRemoteFileService.java | 2 +- .../services/filetransfer/http/DeleteTokenReceived.java | 2 +- .../services/filetransfer/httpupload/HttpFileTransferEntity.java | 2 +- .../services/filetransfer/httpupload/HttpFileUploader.java | 5 +---- .../filetransfer/httpupload/HttpUploadFileTransferService.java | 4 ++-- .../filetransfer/httpupload/HttpUploadSlotRequestReceived.java | 4 ++-- 6 files changed, 8 insertions(+), 11 deletions(-) (limited to 'src/main/java/de/thedevstack/conversationsplus/services/filetransfer') diff --git a/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/http/DeleteRemoteFileService.java b/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/http/DeleteRemoteFileService.java index 57f408cc..565143be 100644 --- a/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/http/DeleteRemoteFileService.java +++ b/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/http/DeleteRemoteFileService.java @@ -4,7 +4,7 @@ import de.thedevstack.conversationsplus.entities.Account; import de.thedevstack.conversationsplus.entities.Message; import de.thedevstack.conversationsplus.utils.XmppSendUtil; import de.thedevstack.conversationsplus.xmpp.filetransfer.http.FileTransferHttp; -import de.thedevstack.conversationsplus.xmpp.filetransfer.http.FileTransferHttpDeleteSlotRequestPacketGenerator; +import de.thedevstack.conversationsplus.xmpp.filetransfer.http.delete.FileTransferHttpDeleteSlotRequestPacketGenerator; import de.thedevstack.conversationsplus.xmpp.jid.Jid; import de.thedevstack.conversationsplus.xmpp.stanzas.IqPacket; diff --git a/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/http/DeleteTokenReceived.java b/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/http/DeleteTokenReceived.java index fb83219b..cce0b713 100644 --- a/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/http/DeleteTokenReceived.java +++ b/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/http/DeleteTokenReceived.java @@ -12,7 +12,7 @@ import de.thedevstack.conversationsplus.http.HttpClient; import de.thedevstack.conversationsplus.utils.MessageUtil; import de.thedevstack.conversationsplus.xmpp.OnIqPacketReceived; import de.thedevstack.conversationsplus.xmpp.exceptions.XmppException; -import de.thedevstack.conversationsplus.xmpp.filetransfer.http.DeleteSlotPacketParser; +import de.thedevstack.conversationsplus.xmpp.filetransfer.http.delete.DeleteSlotPacketParser; import de.thedevstack.conversationsplus.xmpp.stanzas.IqPacket; import okhttp3.Call; import okhttp3.Callback; diff --git a/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/httpupload/HttpFileTransferEntity.java b/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/httpupload/HttpFileTransferEntity.java index a8e5734f..f6596b0c 100644 --- a/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/httpupload/HttpFileTransferEntity.java +++ b/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/httpupload/HttpFileTransferEntity.java @@ -11,7 +11,7 @@ import de.thedevstack.conversationsplus.services.filetransfer.FileTransferEntity import de.thedevstack.conversationsplus.services.filetransfer.FileTransferFailureReason; import de.thedevstack.conversationsplus.utils.CryptoHelper; import de.thedevstack.conversationsplus.utils.MessageUtil; -import de.thedevstack.conversationsplus.xmpp.httpupload.HttpUploadSlot; +import de.thedevstack.conversationsplus.xmpp.filetransfer.http.upload.HttpUploadSlot; /** * diff --git a/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/httpupload/HttpFileUploader.java b/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/httpupload/HttpFileUploader.java index 8edc5be7..705dd23c 100644 --- a/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/httpupload/HttpFileUploader.java +++ b/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/httpupload/HttpFileUploader.java @@ -13,19 +13,16 @@ import java.util.Scanner; import javax.net.ssl.HttpsURLConnection; import de.thedevstack.android.logcat.Logging; -import de.thedevstack.conversationsplus.Config; import de.thedevstack.conversationsplus.ConversationsPlusApplication; import de.thedevstack.conversationsplus.entities.DownloadableFile; import de.thedevstack.conversationsplus.entities.Message; import de.thedevstack.conversationsplus.http.HttpConnectionManager; import de.thedevstack.conversationsplus.persistance.FileBackend; import de.thedevstack.conversationsplus.services.filetransfer.FileTransferFailureReason; -import de.thedevstack.conversationsplus.utils.CryptoHelper; -import de.thedevstack.conversationsplus.utils.MessageUtil; import de.thedevstack.conversationsplus.utils.StreamUtil; import de.thedevstack.conversationsplus.utils.UiUpdateHelper; import de.thedevstack.conversationsplus.utils.XmppConnectionServiceAccessor; -import de.thedevstack.conversationsplus.xmpp.httpupload.HttpUpload; +import de.thedevstack.conversationsplus.xmpp.filetransfer.http.upload.HttpUpload; public class HttpFileUploader implements Runnable { private static final String HTTP_METHOD = "PUT"; diff --git a/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/httpupload/HttpUploadFileTransferService.java b/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/httpupload/HttpUploadFileTransferService.java index fb150a92..6fd458a9 100644 --- a/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/httpupload/HttpUploadFileTransferService.java +++ b/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/httpupload/HttpUploadFileTransferService.java @@ -15,8 +15,8 @@ import de.thedevstack.conversationsplus.services.FileTransferService; import de.thedevstack.conversationsplus.services.filetransfer.AbstractFileTransferService; import de.thedevstack.conversationsplus.utils.MessageUtil; import de.thedevstack.conversationsplus.utils.XmppSendUtil; -import de.thedevstack.conversationsplus.xmpp.httpupload.HttpUpload; -import de.thedevstack.conversationsplus.xmpp.httpupload.HttpUploadRequestSlotPacketGenerator; +import de.thedevstack.conversationsplus.xmpp.filetransfer.http.upload.HttpUpload; +import de.thedevstack.conversationsplus.xmpp.filetransfer.http.upload.HttpUploadRequestSlotPacketGenerator; import de.thedevstack.conversationsplus.xmpp.jid.Jid; import de.thedevstack.conversationsplus.xmpp.stanzas.IqPacket; diff --git a/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/httpupload/HttpUploadSlotRequestReceived.java b/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/httpupload/HttpUploadSlotRequestReceived.java index 462a370c..75e793f1 100644 --- a/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/httpupload/HttpUploadSlotRequestReceived.java +++ b/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/httpupload/HttpUploadSlotRequestReceived.java @@ -5,8 +5,8 @@ import de.thedevstack.conversationsplus.entities.Account; import de.thedevstack.conversationsplus.services.filetransfer.FileTransferFailureReason; import de.thedevstack.conversationsplus.xmpp.OnIqPacketReceived; import de.thedevstack.conversationsplus.xmpp.exceptions.XmppException; -import de.thedevstack.conversationsplus.xmpp.httpupload.HttpUploadSlot; -import de.thedevstack.conversationsplus.xmpp.httpupload.SlotPacketParser; +import de.thedevstack.conversationsplus.xmpp.filetransfer.http.upload.HttpUploadSlot; +import de.thedevstack.conversationsplus.xmpp.filetransfer.http.upload.SlotPacketParser; import de.thedevstack.conversationsplus.xmpp.stanzas.IqPacket; /** -- cgit v1.2.3 From e91e8a30b1f4a806d77d74871df5af6cdb87ca0e Mon Sep 17 00:00:00 2001 From: steckbrief Date: Tue, 23 Aug 2016 09:49:06 +0200 Subject: services.filetransfer.httpupload moved to new namespace services.filetransfer.http.upload; delete parts of services.filetransfer.http moved to .delete package --- .../services/filetransfer/FileTransferManager.java | 2 +- .../filetransfer/http/DeleteRemoteFile.java | 22 --- .../filetransfer/http/DeleteRemoteFileService.java | 30 ----- .../filetransfer/http/DeleteTokenReceived.java | 83 ------------ .../filetransfer/http/delete/DeleteRemoteFile.java | 22 +++ .../http/delete/DeleteRemoteFileService.java | 30 +++++ .../http/delete/DeleteTokenReceived.java | 83 ++++++++++++ .../http/upload/HttpFileTransferEntity.java | 87 ++++++++++++ .../filetransfer/http/upload/HttpFileUploader.java | 149 +++++++++++++++++++++ .../http/upload/HttpUploadFileTransferService.java | 91 +++++++++++++ .../http/upload/HttpUploadSlotRequestReceived.java | 35 +++++ .../HttpUploadedFileEncryptionUiCallback.java | 34 +++++ .../httpupload/HttpFileTransferEntity.java | 87 ------------ .../filetransfer/httpupload/HttpFileUploader.java | 149 --------------------- .../httpupload/HttpUploadFileTransferService.java | 91 ------------- .../httpupload/HttpUploadSlotRequestReceived.java | 35 ----- .../HttpUploadedFileEncryptionUiCallback.java | 34 ----- 17 files changed, 532 insertions(+), 532 deletions(-) delete mode 100644 src/main/java/de/thedevstack/conversationsplus/services/filetransfer/http/DeleteRemoteFile.java delete mode 100644 src/main/java/de/thedevstack/conversationsplus/services/filetransfer/http/DeleteRemoteFileService.java delete mode 100644 src/main/java/de/thedevstack/conversationsplus/services/filetransfer/http/DeleteTokenReceived.java create mode 100644 src/main/java/de/thedevstack/conversationsplus/services/filetransfer/http/delete/DeleteRemoteFile.java create mode 100644 src/main/java/de/thedevstack/conversationsplus/services/filetransfer/http/delete/DeleteRemoteFileService.java create mode 100644 src/main/java/de/thedevstack/conversationsplus/services/filetransfer/http/delete/DeleteTokenReceived.java create mode 100644 src/main/java/de/thedevstack/conversationsplus/services/filetransfer/http/upload/HttpFileTransferEntity.java create mode 100644 src/main/java/de/thedevstack/conversationsplus/services/filetransfer/http/upload/HttpFileUploader.java create mode 100644 src/main/java/de/thedevstack/conversationsplus/services/filetransfer/http/upload/HttpUploadFileTransferService.java create mode 100644 src/main/java/de/thedevstack/conversationsplus/services/filetransfer/http/upload/HttpUploadSlotRequestReceived.java create mode 100644 src/main/java/de/thedevstack/conversationsplus/services/filetransfer/http/upload/HttpUploadedFileEncryptionUiCallback.java delete mode 100644 src/main/java/de/thedevstack/conversationsplus/services/filetransfer/httpupload/HttpFileTransferEntity.java delete mode 100644 src/main/java/de/thedevstack/conversationsplus/services/filetransfer/httpupload/HttpFileUploader.java delete mode 100644 src/main/java/de/thedevstack/conversationsplus/services/filetransfer/httpupload/HttpUploadFileTransferService.java delete mode 100644 src/main/java/de/thedevstack/conversationsplus/services/filetransfer/httpupload/HttpUploadSlotRequestReceived.java delete mode 100644 src/main/java/de/thedevstack/conversationsplus/services/filetransfer/httpupload/HttpUploadedFileEncryptionUiCallback.java (limited to 'src/main/java/de/thedevstack/conversationsplus/services/filetransfer') diff --git a/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/FileTransferManager.java b/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/FileTransferManager.java index 017b88ea..2f9a819b 100644 --- a/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/FileTransferManager.java +++ b/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/FileTransferManager.java @@ -9,7 +9,7 @@ import de.thedevstack.android.logcat.Logging; import de.thedevstack.conversationsplus.Config; import de.thedevstack.conversationsplus.entities.Message; import de.thedevstack.conversationsplus.services.FileTransferService; -import de.thedevstack.conversationsplus.services.filetransfer.httpupload.HttpFileTransferEntity; +import de.thedevstack.conversationsplus.services.filetransfer.http.upload.HttpFileTransferEntity; import de.thedevstack.conversationsplus.utils.MessageUtil; /** diff --git a/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/http/DeleteRemoteFile.java b/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/http/DeleteRemoteFile.java deleted file mode 100644 index 9f6b3bbd..00000000 --- a/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/http/DeleteRemoteFile.java +++ /dev/null @@ -1,22 +0,0 @@ -package de.thedevstack.conversationsplus.services.filetransfer.http; - -import android.support.annotation.NonNull; - -import de.thedevstack.conversationsplus.entities.Message; -import de.thedevstack.conversationsplus.entities.RemoteFile; - -/** - * Created by steckbrief on 22.08.2016. - */ -public class DeleteRemoteFile extends RemoteFile { - private final Message message; - - public DeleteRemoteFile(@NonNull String path, @NonNull Message message) { - super(path); - this.message = message; - } - - public Message getMessage() { - return message; - } -} diff --git a/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/http/DeleteRemoteFileService.java b/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/http/DeleteRemoteFileService.java deleted file mode 100644 index 565143be..00000000 --- a/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/http/DeleteRemoteFileService.java +++ /dev/null @@ -1,30 +0,0 @@ -package de.thedevstack.conversationsplus.services.filetransfer.http; - -import de.thedevstack.conversationsplus.entities.Account; -import de.thedevstack.conversationsplus.entities.Message; -import de.thedevstack.conversationsplus.utils.XmppSendUtil; -import de.thedevstack.conversationsplus.xmpp.filetransfer.http.FileTransferHttp; -import de.thedevstack.conversationsplus.xmpp.filetransfer.http.delete.FileTransferHttpDeleteSlotRequestPacketGenerator; -import de.thedevstack.conversationsplus.xmpp.jid.Jid; -import de.thedevstack.conversationsplus.xmpp.stanzas.IqPacket; - -/** - * Created by steckbrief on 21.08.2016. - */ -public class DeleteRemoteFileService { - public void deleteRemoteFile(Message message) { - if (message.isHttpUploaded()) { - String path = message.getBody(); - if (message.hasFileOnRemoteHost()) { - path = message.getFileParams().url.toString(); - } - - DeleteRemoteFile remoteFile = new DeleteRemoteFile(path, message); - Account account = message.getConversation().getAccount(); - Jid host = account.getXmppConnection().findDiscoItemByFeature(FileTransferHttp.NAMESPACE); - IqPacket request = FileTransferHttpDeleteSlotRequestPacketGenerator.generate(host, path); - - XmppSendUtil.sendIqPacket(account, request, new DeleteTokenReceived(remoteFile)); - } - } -} diff --git a/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/http/DeleteTokenReceived.java b/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/http/DeleteTokenReceived.java deleted file mode 100644 index cce0b713..00000000 --- a/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/http/DeleteTokenReceived.java +++ /dev/null @@ -1,83 +0,0 @@ -package de.thedevstack.conversationsplus.services.filetransfer.http; - -import org.json.JSONException; -import org.json.JSONObject; - -import java.io.IOException; - -import de.thedevstack.android.logcat.Logging; -import de.thedevstack.conversationsplus.entities.Account; -import de.thedevstack.conversationsplus.entities.Message; -import de.thedevstack.conversationsplus.http.HttpClient; -import de.thedevstack.conversationsplus.utils.MessageUtil; -import de.thedevstack.conversationsplus.xmpp.OnIqPacketReceived; -import de.thedevstack.conversationsplus.xmpp.exceptions.XmppException; -import de.thedevstack.conversationsplus.xmpp.filetransfer.http.delete.DeleteSlotPacketParser; -import de.thedevstack.conversationsplus.xmpp.stanzas.IqPacket; -import okhttp3.Call; -import okhttp3.Callback; -import okhttp3.OkHttpClient; -import okhttp3.Request; -import okhttp3.Response; - -/** - * Created by steckbrief on 21.08.2016. - */ -public class DeleteTokenReceived implements OnIqPacketReceived { - private static final String HEADER_NAME_DELETE_TOKEN = "X-FILETRANSFER-HTTP-DELETE-TOKEN"; - private final DeleteRemoteFile remoteFile; - - public DeleteTokenReceived(DeleteRemoteFile remoteFile) { - this.remoteFile = remoteFile; - } - - @Override - public void onIqPacketReceived(Account account, IqPacket packet) { - try { - String url = this.remoteFile.getPath(); - String deleteToken = DeleteSlotPacketParser.parseDeleteToken(packet); - Logging.d("filetransfer.http.delete", "Got delete token '" + deleteToken + "' for remote file '" + remoteFile.getPath() + "'"); - OkHttpClient client = HttpClient.getClient(true); - Request request = new Request.Builder() - .url(url) - .addHeader(HEADER_NAME_DELETE_TOKEN, deleteToken) - .delete() - .build(); - client.newCall(request).enqueue(new Callback() { - @Override - public void onFailure(Call call, IOException e) { - Logging.e("filetransfer.http.delete", "Error while connecting to '" + call.request().url() + "': " + e.getMessage()); - MessageUtil.markMessage(remoteFile.getMessage(), Message.STATUS_REMOTE_FILE_DELETE_FAILED); - } - - @Override - public void onResponse(Call call, Response response) throws IOException { - if (response.isSuccessful()) { - MessageUtil.markMessage(remoteFile.getMessage(), Message.STATUS_REMOTE_FILE_DELETED); - Logging.i("filetransfer.http.delete", "Remote file successfully deleted '" + remoteFile.getPath() + "'"); - } else { - String detailedMessage = response.body().string(); - switch (response.code()) { - case 403: - case 404: - case 500: - try { - JSONObject jsonObject = new JSONObject(detailedMessage); - detailedMessage = jsonObject.getString("msg"); - } catch (JSONException e) { - Logging.e("filetransfer.http.delete", "Failed to get error message from expected json response: " + detailedMessage); - } - break; - } - Logging.e("filetransfer.http.delete", "Could not delete remote file '" + remoteFile.getPath() + "'. Response Code: " + response.code() + ", details: " + detailedMessage); - MessageUtil.markMessage(remoteFile.getMessage(), Message.STATUS_REMOTE_FILE_DELETE_FAILED); - } - } - }); - - } catch (XmppException e) { - Logging.e("filetransfer.http.delete", "Error while trying to get the delete token: " + e.getMessage()); - MessageUtil.markMessage(remoteFile.getMessage(), Message.STATUS_REMOTE_FILE_DELETE_FAILED); - } - } -} diff --git a/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/http/delete/DeleteRemoteFile.java b/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/http/delete/DeleteRemoteFile.java new file mode 100644 index 00000000..39a369ef --- /dev/null +++ b/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/http/delete/DeleteRemoteFile.java @@ -0,0 +1,22 @@ +package de.thedevstack.conversationsplus.services.filetransfer.http.delete; + +import android.support.annotation.NonNull; + +import de.thedevstack.conversationsplus.entities.Message; +import de.thedevstack.conversationsplus.entities.RemoteFile; + +/** + * Created by steckbrief on 22.08.2016. + */ +public class DeleteRemoteFile extends RemoteFile { + private final Message message; + + public DeleteRemoteFile(@NonNull String path, @NonNull Message message) { + super(path); + this.message = message; + } + + public Message getMessage() { + return message; + } +} diff --git a/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/http/delete/DeleteRemoteFileService.java b/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/http/delete/DeleteRemoteFileService.java new file mode 100644 index 00000000..42410ed4 --- /dev/null +++ b/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/http/delete/DeleteRemoteFileService.java @@ -0,0 +1,30 @@ +package de.thedevstack.conversationsplus.services.filetransfer.http.delete; + +import de.thedevstack.conversationsplus.entities.Account; +import de.thedevstack.conversationsplus.entities.Message; +import de.thedevstack.conversationsplus.utils.XmppSendUtil; +import de.thedevstack.conversationsplus.xmpp.filetransfer.http.FileTransferHttp; +import de.thedevstack.conversationsplus.xmpp.filetransfer.http.delete.FileTransferHttpDeleteSlotRequestPacketGenerator; +import de.thedevstack.conversationsplus.xmpp.jid.Jid; +import de.thedevstack.conversationsplus.xmpp.stanzas.IqPacket; + +/** + * Created by steckbrief on 21.08.2016. + */ +public class DeleteRemoteFileService { + public void deleteRemoteFile(Message message) { + if (message.isHttpUploaded()) { + String path = message.getBody(); + if (message.hasFileOnRemoteHost()) { + path = message.getFileParams().url.toString(); + } + + DeleteRemoteFile remoteFile = new DeleteRemoteFile(path, message); + Account account = message.getConversation().getAccount(); + Jid host = account.getXmppConnection().findDiscoItemByFeature(FileTransferHttp.NAMESPACE); + IqPacket request = FileTransferHttpDeleteSlotRequestPacketGenerator.generate(host, path); + + XmppSendUtil.sendIqPacket(account, request, new DeleteTokenReceived(remoteFile)); + } + } +} diff --git a/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/http/delete/DeleteTokenReceived.java b/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/http/delete/DeleteTokenReceived.java new file mode 100644 index 00000000..a8bef0ed --- /dev/null +++ b/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/http/delete/DeleteTokenReceived.java @@ -0,0 +1,83 @@ +package de.thedevstack.conversationsplus.services.filetransfer.http.delete; + +import org.json.JSONException; +import org.json.JSONObject; + +import java.io.IOException; + +import de.thedevstack.android.logcat.Logging; +import de.thedevstack.conversationsplus.entities.Account; +import de.thedevstack.conversationsplus.entities.Message; +import de.thedevstack.conversationsplus.http.HttpClient; +import de.thedevstack.conversationsplus.utils.MessageUtil; +import de.thedevstack.conversationsplus.xmpp.OnIqPacketReceived; +import de.thedevstack.conversationsplus.xmpp.exceptions.XmppException; +import de.thedevstack.conversationsplus.xmpp.filetransfer.http.delete.DeleteSlotPacketParser; +import de.thedevstack.conversationsplus.xmpp.stanzas.IqPacket; +import okhttp3.Call; +import okhttp3.Callback; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.Response; + +/** + * Created by steckbrief on 21.08.2016. + */ +public class DeleteTokenReceived implements OnIqPacketReceived { + private static final String HEADER_NAME_DELETE_TOKEN = "X-FILETRANSFER-HTTP-DELETE-TOKEN"; + private final DeleteRemoteFile remoteFile; + + public DeleteTokenReceived(DeleteRemoteFile remoteFile) { + this.remoteFile = remoteFile; + } + + @Override + public void onIqPacketReceived(Account account, IqPacket packet) { + try { + String url = this.remoteFile.getPath(); + String deleteToken = DeleteSlotPacketParser.parseDeleteToken(packet); + Logging.d("filetransfer.http.delete", "Got delete token '" + deleteToken + "' for remote file '" + remoteFile.getPath() + "'"); + OkHttpClient client = HttpClient.getClient(true); + Request request = new Request.Builder() + .url(url) + .addHeader(HEADER_NAME_DELETE_TOKEN, deleteToken) + .delete() + .build(); + client.newCall(request).enqueue(new Callback() { + @Override + public void onFailure(Call call, IOException e) { + Logging.e("filetransfer.http.delete", "Error while connecting to '" + call.request().url() + "': " + e.getMessage()); + MessageUtil.markMessage(remoteFile.getMessage(), Message.STATUS_REMOTE_FILE_DELETE_FAILED); + } + + @Override + public void onResponse(Call call, Response response) throws IOException { + if (response.isSuccessful()) { + MessageUtil.markMessage(remoteFile.getMessage(), Message.STATUS_REMOTE_FILE_DELETED); + Logging.i("filetransfer.http.delete", "Remote file successfully deleted '" + remoteFile.getPath() + "'"); + } else { + String detailedMessage = response.body().string(); + switch (response.code()) { + case 403: + case 404: + case 500: + try { + JSONObject jsonObject = new JSONObject(detailedMessage); + detailedMessage = jsonObject.getString("msg"); + } catch (JSONException e) { + Logging.e("filetransfer.http.delete", "Failed to get error message from expected json response: " + detailedMessage); + } + break; + } + Logging.e("filetransfer.http.delete", "Could not delete remote file '" + remoteFile.getPath() + "'. Response Code: " + response.code() + ", details: " + detailedMessage); + MessageUtil.markMessage(remoteFile.getMessage(), Message.STATUS_REMOTE_FILE_DELETE_FAILED); + } + } + }); + + } catch (XmppException e) { + Logging.e("filetransfer.http.delete", "Error while trying to get the delete token: " + e.getMessage()); + MessageUtil.markMessage(remoteFile.getMessage(), Message.STATUS_REMOTE_FILE_DELETE_FAILED); + } + } +} diff --git a/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/http/upload/HttpFileTransferEntity.java b/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/http/upload/HttpFileTransferEntity.java new file mode 100644 index 00000000..fbe7186b --- /dev/null +++ b/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/http/upload/HttpFileTransferEntity.java @@ -0,0 +1,87 @@ +package de.thedevstack.conversationsplus.services.filetransfer.http.upload; + +import java.net.MalformedURLException; +import java.net.URL; + +import de.thedevstack.android.logcat.Logging; +import de.thedevstack.conversationsplus.Config; +import de.thedevstack.conversationsplus.ConversationsPlusApplication; +import de.thedevstack.conversationsplus.entities.Message; +import de.thedevstack.conversationsplus.services.filetransfer.FileTransferEntity; +import de.thedevstack.conversationsplus.services.filetransfer.FileTransferFailureReason; +import de.thedevstack.conversationsplus.utils.CryptoHelper; +import de.thedevstack.conversationsplus.utils.MessageUtil; +import de.thedevstack.conversationsplus.xmpp.filetransfer.http.upload.HttpUploadSlot; + +/** + * + */ +public class HttpFileTransferEntity extends FileTransferEntity { + private HttpUploadSlot slot; + private final byte[] key; + private final boolean delayed; + + public HttpFileTransferEntity(Message message, boolean delayed) { + super(message); + this.getMessage().setHttpUploaded(true); + this.getMessage().setNoDownloadable(); + if (Config.ENCRYPT_ON_HTTP_UPLOADED + || message.getEncryption() == Message.ENCRYPTION_AXOLOTL + || message.getEncryption() == Message.ENCRYPTION_OTR) { + this.key = new byte[48]; + ConversationsPlusApplication.getSecureRandom().nextBytes(this.key); + this.getFile().setKeyAndIv(this.key); + } else { + this.key = null; + } + this.delayed = delayed; + } + + public void setSlot(HttpUploadSlot slot) { + this.slot = slot; + } + + public String getGetUrl() { + return this.slot.getGetUrl(); + } + + public String getPutUrl() { + return this.slot.getPutUrl(); + } + + public byte[] getKey() { + return key; + } + + public boolean isDelayed() { + return this.delayed; + } + + @Override + public void fail(FileTransferFailureReason failureReason) { + this.getMessage().setHttpUploaded(false); + super.fail(failureReason); + } + + @Override + public void cancel() { + this.getMessage().setHttpUploaded(false); + super.cancel(); + } + + @Override + public void transferred() { + try { + URL getUrl = new URL(this.getGetUrl()); + if (this.getKey() != null) { + getUrl = new URL(getUrl.toString() + "#" + CryptoHelper.bytesToHex(this.getKey())); + } + + MessageUtil.updateFileParams(this.getMessage(), getUrl); + } catch (MalformedURLException e) { + Logging.e("httpupload", "Not a valid get url"); + } + + super.transferred(); + } +} diff --git a/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/http/upload/HttpFileUploader.java b/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/http/upload/HttpFileUploader.java new file mode 100644 index 00000000..318a8b8f --- /dev/null +++ b/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/http/upload/HttpFileUploader.java @@ -0,0 +1,149 @@ +package de.thedevstack.conversationsplus.services.filetransfer.http.upload; + +import android.os.PowerManager; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.HttpURLConnection; +import java.net.URL; +import java.net.UnknownHostException; +import java.util.Scanner; + +import javax.net.ssl.HttpsURLConnection; + +import de.thedevstack.android.logcat.Logging; +import de.thedevstack.conversationsplus.ConversationsPlusApplication; +import de.thedevstack.conversationsplus.entities.DownloadableFile; +import de.thedevstack.conversationsplus.entities.Message; +import de.thedevstack.conversationsplus.http.HttpConnectionManager; +import de.thedevstack.conversationsplus.persistance.FileBackend; +import de.thedevstack.conversationsplus.services.filetransfer.FileTransferFailureReason; +import de.thedevstack.conversationsplus.utils.StreamUtil; +import de.thedevstack.conversationsplus.utils.UiUpdateHelper; +import de.thedevstack.conversationsplus.utils.XmppConnectionServiceAccessor; +import de.thedevstack.conversationsplus.xmpp.filetransfer.http.upload.HttpUpload; + +public class HttpFileUploader implements Runnable { + private static final String HTTP_METHOD = "PUT"; + private static final String MIME_REQUEST_PROPERTY_NAME = "Content-Type"; + private static final String USER_AGENT_REQUEST_PROPERTY_NAME = "User-Agent"; + private static final int BUFFER_LENGTH = 4096; + private final HttpFileTransferEntity entity; + + public HttpFileUploader(HttpFileTransferEntity entity) { + this.entity = entity; + } + + @Override + public void run() { + this.upload(); + } + + private void upload() { + OutputStream os = null; + + HttpURLConnection connection = null; + InputStream fileInputStream = null; + DownloadableFile file = this.entity.getFile(); + PowerManager.WakeLock wakeLock = ConversationsPlusApplication.createPartialWakeLock("http_upload_" + entity.getMessage().getUuid()); + try { + wakeLock.acquire(); + Logging.d("httpupload", "uploading file to " + this.entity.getPutUrl()); + URL putUrl = new URL(this.entity.getPutUrl()); + fileInputStream = this.entity.getFileInputStream(); + connection = (HttpURLConnection) putUrl.openConnection(); + + if (connection instanceof HttpsURLConnection) { + HttpConnectionManager.setupTrustManager((HttpsURLConnection) connection, true); + } + connection.setRequestMethod(HTTP_METHOD); + connection.setFixedLengthStreamingMode((int) file.getExpectedSize()); + String mime = file.getMimeType(); + connection.setRequestProperty(MIME_REQUEST_PROPERTY_NAME, mime == null ? HttpUpload.DEFAULT_MIME_TYPE : mime); + connection.setRequestProperty(USER_AGENT_REQUEST_PROPERTY_NAME, ConversationsPlusApplication.getNameAndVersion()); + connection.setDoOutput(true); + connection.connect(); + os = connection.getOutputStream(); + int count = -1; + byte[] buffer = new byte[BUFFER_LENGTH]; + while (((count = fileInputStream.read(buffer)) != -1) && !this.entity.isCanceled()) { + this.entity.updateProgress(count); + os.write(buffer, 0, count); + UiUpdateHelper.updateConversationUi(); + } + os.flush(); + os.close(); + fileInputStream.close(); + int code = connection.getResponseCode(); + if (code == 200 || code == 201) { + Logging.d("httpupload", "finished uploading file"); + this.entity.transferred(); + + FileBackend.updateMediaScanner(file, XmppConnectionServiceAccessor.xmppConnectionService); // Why??? + + // Send the URL to the counterpart + Message message = this.entity.getMessage(); + message.setCounterpart(message.getConversation().getJid().toBareJid()); + if (message.getEncryption() == Message.ENCRYPTION_DECRYPTED) { + XmppConnectionServiceAccessor.xmppConnectionService.getPgpEngine().encrypt(message, new HttpUploadedFileEncryptionUiCallback(this.entity)); + } else { + XmppConnectionServiceAccessor.xmppConnectionService.resendMessage(message, this.entity.isDelayed()); + } + } else { + String httpResponseMessage = this.getErrorStreamContent(connection); + String errorMessage = "file upload failed: http code (" + code + ") " + httpResponseMessage; + Logging.e("httpupload", errorMessage); + FileTransferFailureReason failureReason = null; + switch (code) { + case 403: + failureReason = FileTransferFailureReason.createLimitedRecoverableFailureReason(errorMessage); + break; + case 404: + failureReason = FileTransferFailureReason.createNonRecoverableFailureReason("Upload URL not found"); + break; + case 500: + failureReason = FileTransferFailureReason.createNonRecoverableFailureReason("Internal Server Error"); + break; + default: + failureReason = FileTransferFailureReason.createRecoverableFailureReason(errorMessage); + } + this.entity.fail(failureReason); + } + } catch (UnknownHostException e) { + Logging.e("httpupload", "File upload failed due to unknown host. " + e.getMessage()); + //if (!HAS_INTERNET_CONNECTION) { + this.entity.fail(FileTransferFailureReason.createRecoverableFailureReason(e, "Missing internet connection")); + //} + } catch (IOException e) { + String httpResponseMessage = this.getErrorStreamContent(connection); + Logging.e("httpupload", ((null != httpResponseMessage) ? ("http response: " + httpResponseMessage + ", ") : "") + "exception message: " + e.getMessage()); + // TODO: Differentiate IOException while internet connection wasn't available and others + this.entity.fail(FileTransferFailureReason.createRecoverableFailureReason(e, httpResponseMessage)); + } finally { + StreamUtil.close(os); + StreamUtil.close(fileInputStream); + if (connection != null) { + connection.disconnect(); + } + wakeLock.release(); + } + } + + private String getErrorStreamContent(HttpURLConnection connection) { + InputStream errorStream = null; + String httpResponseMessage = null; + try { + errorStream = connection.getErrorStream(); + if (null != errorStream) { + Scanner scanner = new Scanner(errorStream).useDelimiter("\\A"); + if (scanner.hasNext()) { + httpResponseMessage = scanner.next(); + } + } + } finally { + StreamUtil.close(errorStream); + } + return httpResponseMessage; + } +} \ No newline at end of file diff --git a/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/http/upload/HttpUploadFileTransferService.java b/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/http/upload/HttpUploadFileTransferService.java new file mode 100644 index 00000000..f08e27f5 --- /dev/null +++ b/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/http/upload/HttpUploadFileTransferService.java @@ -0,0 +1,91 @@ +package de.thedevstack.conversationsplus.services.filetransfer.http.upload; + +import android.util.Pair; + +import java.io.FileNotFoundException; +import java.io.InputStream; + +import de.thedevstack.android.logcat.Logging; +import de.thedevstack.conversationsplus.entities.Account; +import de.thedevstack.conversationsplus.entities.DownloadableFile; +import de.thedevstack.conversationsplus.entities.Message; +import de.thedevstack.conversationsplus.persistance.FileBackend; +import de.thedevstack.conversationsplus.services.AbstractConnectionManager; +import de.thedevstack.conversationsplus.services.FileTransferService; +import de.thedevstack.conversationsplus.services.filetransfer.AbstractFileTransferService; +import de.thedevstack.conversationsplus.utils.MessageUtil; +import de.thedevstack.conversationsplus.utils.XmppSendUtil; +import de.thedevstack.conversationsplus.xmpp.filetransfer.http.upload.HttpUpload; +import de.thedevstack.conversationsplus.xmpp.filetransfer.http.upload.HttpUploadRequestSlotPacketGenerator; +import de.thedevstack.conversationsplus.xmpp.jid.Jid; +import de.thedevstack.conversationsplus.xmpp.stanzas.IqPacket; + +/** + * + */ +public class HttpUploadFileTransferService extends AbstractFileTransferService implements FileTransferService { + + public HttpUploadFileTransferService() { + } + + /** + * Transfers a file for the corresponding message. + * + * @param message the message containing the file to transfer + * @return true if the file transfer was started successfully, false otherwise + */ + @Override + public boolean transferFile(Message message) { + return this.transferFile(message, false); + } + + /** + * Transfers a file for the corresponding message. + * + * @param message the message containing the file to transfer + * @param delay whether the message is delayed or not + * @return true if the file transfer was started successfully, false otherwise + */ + @Override + public boolean transferFile(Message message, boolean delay) { + Logging.d("httpupload", "Starting to upload file"); + boolean started = false; + try { + final HttpFileTransferEntity entity = new HttpFileTransferEntity(message, delay); + this.addStatusListenerToEntity(entity); + entity.startAttempt(); + Account account = message.getConversation().getAccount(); + DownloadableFile file = entity.getFile(); + Pair inputStreamAndExpectedSize = AbstractConnectionManager.createInputStream(file, true); + + entity.setFileInputStream(inputStreamAndExpectedSize.first); + file.setExpectedSize(inputStreamAndExpectedSize.second); + + Logging.d("httpupload", "Requesting upload slot for file upload"); + Jid host = account.getXmppConnection().findDiscoItemByFeature(HttpUpload.NAMESPACE); + IqPacket request = HttpUploadRequestSlotPacketGenerator.generate(host, file.getName(), file.getSize(), file.getMimeType()); + XmppSendUtil.sendIqPacket(account, request, new HttpUploadSlotRequestReceived(entity)); + MessageUtil.markMessage(message, Message.STATUS_UNSEND); + + Logging.d("httpupload", "Upload slot for file upload requested"); + started = true; + } catch (FileNotFoundException e) { + Logging.e("httpupload", "Could not find file, exception message: " + e.getMessage()); + } + return started; + } + + /** + * Checks whether a message can be sent using this service or not. + * + * @param message the message to be checked + * @return true if the message can be processed, false otherwise + */ + @Override + public boolean accept(Message message) { + return null != message + && null != message.getConversation() + && null != message.getConversation().getAccount() + && message.getConversation().getAccount().httpUploadAvailable(FileBackend.getFile(message, false).getSize()); + } +} diff --git a/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/http/upload/HttpUploadSlotRequestReceived.java b/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/http/upload/HttpUploadSlotRequestReceived.java new file mode 100644 index 00000000..2687878d --- /dev/null +++ b/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/http/upload/HttpUploadSlotRequestReceived.java @@ -0,0 +1,35 @@ +package de.thedevstack.conversationsplus.services.filetransfer.http.upload; + +import de.thedevstack.android.logcat.Logging; +import de.thedevstack.conversationsplus.entities.Account; +import de.thedevstack.conversationsplus.services.filetransfer.FileTransferFailureReason; +import de.thedevstack.conversationsplus.xmpp.OnIqPacketReceived; +import de.thedevstack.conversationsplus.xmpp.exceptions.XmppException; +import de.thedevstack.conversationsplus.xmpp.filetransfer.http.upload.HttpUploadSlot; +import de.thedevstack.conversationsplus.xmpp.filetransfer.http.upload.SlotPacketParser; +import de.thedevstack.conversationsplus.xmpp.stanzas.IqPacket; + +/** + * + */ +public class HttpUploadSlotRequestReceived implements OnIqPacketReceived { + private final HttpFileTransferEntity entity; + + public HttpUploadSlotRequestReceived(HttpFileTransferEntity entity) { + this.entity = entity; + } + + @Override + public void onIqPacketReceived(Account account, IqPacket packet) { + try { + HttpUploadSlot slot = SlotPacketParser.parseGetAndPutUrl(packet); + this.entity.setSlot(slot); + if (!this.entity.isCanceled()) { + new Thread(new HttpFileUploader(this.entity)).start(); + } + } catch (XmppException e) { + Logging.e("httpupload", e.getMessage()); + this.entity.fail(FileTransferFailureReason.createNonRecoverableFailureReason(e)); + } + } +} diff --git a/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/http/upload/HttpUploadedFileEncryptionUiCallback.java b/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/http/upload/HttpUploadedFileEncryptionUiCallback.java new file mode 100644 index 00000000..f363a675 --- /dev/null +++ b/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/http/upload/HttpUploadedFileEncryptionUiCallback.java @@ -0,0 +1,34 @@ +package de.thedevstack.conversationsplus.services.filetransfer.http.upload; + +import android.app.PendingIntent; + +import de.thedevstack.conversationsplus.entities.Message; +import de.thedevstack.conversationsplus.services.filetransfer.FileTransferFailureReason; +import de.thedevstack.conversationsplus.ui.UiCallback; +import de.thedevstack.conversationsplus.utils.XmppConnectionServiceAccessor; + +/** + * + */ +public class HttpUploadedFileEncryptionUiCallback implements UiCallback { + private final HttpFileTransferEntity entity; + + public HttpUploadedFileEncryptionUiCallback(HttpFileTransferEntity entity) { + this.entity = entity; + } + + @Override + public void success(Message message) { + XmppConnectionServiceAccessor.xmppConnectionService.resendMessage(message, this.entity.isDelayed()); + } + + @Override + public void error(int errorCode, Message object) { + this.entity.fail(FileTransferFailureReason.createLimitedRecoverableFailureReason("Failed to encrypt")); + } + + @Override + public void userInputRequried(PendingIntent pi, Message object) { + this.entity.fail(FileTransferFailureReason.createLimitedRecoverableFailureReason("Failed to encrypt, user input would have been required")); + } +} diff --git a/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/httpupload/HttpFileTransferEntity.java b/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/httpupload/HttpFileTransferEntity.java deleted file mode 100644 index f6596b0c..00000000 --- a/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/httpupload/HttpFileTransferEntity.java +++ /dev/null @@ -1,87 +0,0 @@ -package de.thedevstack.conversationsplus.services.filetransfer.httpupload; - -import java.net.MalformedURLException; -import java.net.URL; - -import de.thedevstack.android.logcat.Logging; -import de.thedevstack.conversationsplus.Config; -import de.thedevstack.conversationsplus.ConversationsPlusApplication; -import de.thedevstack.conversationsplus.entities.Message; -import de.thedevstack.conversationsplus.services.filetransfer.FileTransferEntity; -import de.thedevstack.conversationsplus.services.filetransfer.FileTransferFailureReason; -import de.thedevstack.conversationsplus.utils.CryptoHelper; -import de.thedevstack.conversationsplus.utils.MessageUtil; -import de.thedevstack.conversationsplus.xmpp.filetransfer.http.upload.HttpUploadSlot; - -/** - * - */ -public class HttpFileTransferEntity extends FileTransferEntity { - private HttpUploadSlot slot; - private final byte[] key; - private final boolean delayed; - - public HttpFileTransferEntity(Message message, boolean delayed) { - super(message); - this.getMessage().setHttpUploaded(true); - this.getMessage().setNoDownloadable(); - if (Config.ENCRYPT_ON_HTTP_UPLOADED - || message.getEncryption() == Message.ENCRYPTION_AXOLOTL - || message.getEncryption() == Message.ENCRYPTION_OTR) { - this.key = new byte[48]; - ConversationsPlusApplication.getSecureRandom().nextBytes(this.key); - this.getFile().setKeyAndIv(this.key); - } else { - this.key = null; - } - this.delayed = delayed; - } - - public void setSlot(HttpUploadSlot slot) { - this.slot = slot; - } - - public String getGetUrl() { - return this.slot.getGetUrl(); - } - - public String getPutUrl() { - return this.slot.getPutUrl(); - } - - public byte[] getKey() { - return key; - } - - public boolean isDelayed() { - return this.delayed; - } - - @Override - public void fail(FileTransferFailureReason failureReason) { - this.getMessage().setHttpUploaded(false); - super.fail(failureReason); - } - - @Override - public void cancel() { - this.getMessage().setHttpUploaded(false); - super.cancel(); - } - - @Override - public void transferred() { - try { - URL getUrl = new URL(this.getGetUrl()); - if (this.getKey() != null) { - getUrl = new URL(getUrl.toString() + "#" + CryptoHelper.bytesToHex(this.getKey())); - } - - MessageUtil.updateFileParams(this.getMessage(), getUrl); - } catch (MalformedURLException e) { - Logging.e("httpupload", "Not a valid get url"); - } - - super.transferred(); - } -} diff --git a/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/httpupload/HttpFileUploader.java b/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/httpupload/HttpFileUploader.java deleted file mode 100644 index 705dd23c..00000000 --- a/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/httpupload/HttpFileUploader.java +++ /dev/null @@ -1,149 +0,0 @@ -package de.thedevstack.conversationsplus.services.filetransfer.httpupload; - -import android.os.PowerManager; - -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.net.HttpURLConnection; -import java.net.URL; -import java.net.UnknownHostException; -import java.util.Scanner; - -import javax.net.ssl.HttpsURLConnection; - -import de.thedevstack.android.logcat.Logging; -import de.thedevstack.conversationsplus.ConversationsPlusApplication; -import de.thedevstack.conversationsplus.entities.DownloadableFile; -import de.thedevstack.conversationsplus.entities.Message; -import de.thedevstack.conversationsplus.http.HttpConnectionManager; -import de.thedevstack.conversationsplus.persistance.FileBackend; -import de.thedevstack.conversationsplus.services.filetransfer.FileTransferFailureReason; -import de.thedevstack.conversationsplus.utils.StreamUtil; -import de.thedevstack.conversationsplus.utils.UiUpdateHelper; -import de.thedevstack.conversationsplus.utils.XmppConnectionServiceAccessor; -import de.thedevstack.conversationsplus.xmpp.filetransfer.http.upload.HttpUpload; - -public class HttpFileUploader implements Runnable { - private static final String HTTP_METHOD = "PUT"; - private static final String MIME_REQUEST_PROPERTY_NAME = "Content-Type"; - private static final String USER_AGENT_REQUEST_PROPERTY_NAME = "User-Agent"; - private static final int BUFFER_LENGTH = 4096; - private final HttpFileTransferEntity entity; - - public HttpFileUploader(HttpFileTransferEntity entity) { - this.entity = entity; - } - - @Override - public void run() { - this.upload(); - } - - private void upload() { - OutputStream os = null; - - HttpURLConnection connection = null; - InputStream fileInputStream = null; - DownloadableFile file = this.entity.getFile(); - PowerManager.WakeLock wakeLock = ConversationsPlusApplication.createPartialWakeLock("http_upload_" + entity.getMessage().getUuid()); - try { - wakeLock.acquire(); - Logging.d("httpupload", "uploading file to " + this.entity.getPutUrl()); - URL putUrl = new URL(this.entity.getPutUrl()); - fileInputStream = this.entity.getFileInputStream(); - connection = (HttpURLConnection) putUrl.openConnection(); - - if (connection instanceof HttpsURLConnection) { - HttpConnectionManager.setupTrustManager((HttpsURLConnection) connection, true); - } - connection.setRequestMethod(HTTP_METHOD); - connection.setFixedLengthStreamingMode((int) file.getExpectedSize()); - String mime = file.getMimeType(); - connection.setRequestProperty(MIME_REQUEST_PROPERTY_NAME, mime == null ? HttpUpload.DEFAULT_MIME_TYPE : mime); - connection.setRequestProperty(USER_AGENT_REQUEST_PROPERTY_NAME, ConversationsPlusApplication.getNameAndVersion()); - connection.setDoOutput(true); - connection.connect(); - os = connection.getOutputStream(); - int count = -1; - byte[] buffer = new byte[BUFFER_LENGTH]; - while (((count = fileInputStream.read(buffer)) != -1) && !this.entity.isCanceled()) { - this.entity.updateProgress(count); - os.write(buffer, 0, count); - UiUpdateHelper.updateConversationUi(); - } - os.flush(); - os.close(); - fileInputStream.close(); - int code = connection.getResponseCode(); - if (code == 200 || code == 201) { - Logging.d("httpupload", "finished uploading file"); - this.entity.transferred(); - - FileBackend.updateMediaScanner(file, XmppConnectionServiceAccessor.xmppConnectionService); // Why??? - - // Send the URL to the counterpart - Message message = this.entity.getMessage(); - message.setCounterpart(message.getConversation().getJid().toBareJid()); - if (message.getEncryption() == Message.ENCRYPTION_DECRYPTED) { - XmppConnectionServiceAccessor.xmppConnectionService.getPgpEngine().encrypt(message, new HttpUploadedFileEncryptionUiCallback(this.entity)); - } else { - XmppConnectionServiceAccessor.xmppConnectionService.resendMessage(message, this.entity.isDelayed()); - } - } else { - String httpResponseMessage = this.getErrorStreamContent(connection); - String errorMessage = "file upload failed: http code (" + code + ") " + httpResponseMessage; - Logging.e("httpupload", errorMessage); - FileTransferFailureReason failureReason = null; - switch (code) { - case 403: - failureReason = FileTransferFailureReason.createLimitedRecoverableFailureReason(errorMessage); - break; - case 404: - failureReason = FileTransferFailureReason.createNonRecoverableFailureReason("Upload URL not found"); - break; - case 500: - failureReason = FileTransferFailureReason.createNonRecoverableFailureReason("Internal Server Error"); - break; - default: - failureReason = FileTransferFailureReason.createRecoverableFailureReason(errorMessage); - } - this.entity.fail(failureReason); - } - } catch (UnknownHostException e) { - Logging.e("httpupload", "File upload failed due to unknown host. " + e.getMessage()); - //if (!HAS_INTERNET_CONNECTION) { - this.entity.fail(FileTransferFailureReason.createRecoverableFailureReason(e, "Missing internet connection")); - //} - } catch (IOException e) { - String httpResponseMessage = this.getErrorStreamContent(connection); - Logging.e("httpupload", ((null != httpResponseMessage) ? ("http response: " + httpResponseMessage + ", ") : "") + "exception message: " + e.getMessage()); - // TODO: Differentiate IOException while internet connection wasn't available and others - this.entity.fail(FileTransferFailureReason.createRecoverableFailureReason(e, httpResponseMessage)); - } finally { - StreamUtil.close(os); - StreamUtil.close(fileInputStream); - if (connection != null) { - connection.disconnect(); - } - wakeLock.release(); - } - } - - private String getErrorStreamContent(HttpURLConnection connection) { - InputStream errorStream = null; - String httpResponseMessage = null; - try { - errorStream = connection.getErrorStream(); - if (null != errorStream) { - Scanner scanner = new Scanner(errorStream).useDelimiter("\\A"); - if (scanner.hasNext()) { - httpResponseMessage = scanner.next(); - } - } - } finally { - StreamUtil.close(errorStream); - } - return httpResponseMessage; - } -} \ No newline at end of file diff --git a/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/httpupload/HttpUploadFileTransferService.java b/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/httpupload/HttpUploadFileTransferService.java deleted file mode 100644 index 6fd458a9..00000000 --- a/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/httpupload/HttpUploadFileTransferService.java +++ /dev/null @@ -1,91 +0,0 @@ -package de.thedevstack.conversationsplus.services.filetransfer.httpupload; - -import android.util.Pair; - -import java.io.FileNotFoundException; -import java.io.InputStream; - -import de.thedevstack.android.logcat.Logging; -import de.thedevstack.conversationsplus.entities.Account; -import de.thedevstack.conversationsplus.entities.DownloadableFile; -import de.thedevstack.conversationsplus.entities.Message; -import de.thedevstack.conversationsplus.persistance.FileBackend; -import de.thedevstack.conversationsplus.services.AbstractConnectionManager; -import de.thedevstack.conversationsplus.services.FileTransferService; -import de.thedevstack.conversationsplus.services.filetransfer.AbstractFileTransferService; -import de.thedevstack.conversationsplus.utils.MessageUtil; -import de.thedevstack.conversationsplus.utils.XmppSendUtil; -import de.thedevstack.conversationsplus.xmpp.filetransfer.http.upload.HttpUpload; -import de.thedevstack.conversationsplus.xmpp.filetransfer.http.upload.HttpUploadRequestSlotPacketGenerator; -import de.thedevstack.conversationsplus.xmpp.jid.Jid; -import de.thedevstack.conversationsplus.xmpp.stanzas.IqPacket; - -/** - * - */ -public class HttpUploadFileTransferService extends AbstractFileTransferService implements FileTransferService { - - public HttpUploadFileTransferService() { - } - - /** - * Transfers a file for the corresponding message. - * - * @param message the message containing the file to transfer - * @return true if the file transfer was started successfully, false otherwise - */ - @Override - public boolean transferFile(Message message) { - return this.transferFile(message, false); - } - - /** - * Transfers a file for the corresponding message. - * - * @param message the message containing the file to transfer - * @param delay whether the message is delayed or not - * @return true if the file transfer was started successfully, false otherwise - */ - @Override - public boolean transferFile(Message message, boolean delay) { - Logging.d("httpupload", "Starting to upload file"); - boolean started = false; - try { - final HttpFileTransferEntity entity = new HttpFileTransferEntity(message, delay); - this.addStatusListenerToEntity(entity); - entity.startAttempt(); - Account account = message.getConversation().getAccount(); - DownloadableFile file = entity.getFile(); - Pair inputStreamAndExpectedSize = AbstractConnectionManager.createInputStream(file, true); - - entity.setFileInputStream(inputStreamAndExpectedSize.first); - file.setExpectedSize(inputStreamAndExpectedSize.second); - - Logging.d("httpupload", "Requesting upload slot for file upload"); - Jid host = account.getXmppConnection().findDiscoItemByFeature(HttpUpload.NAMESPACE); - IqPacket request = HttpUploadRequestSlotPacketGenerator.generate(host, file.getName(), file.getSize(), file.getMimeType()); - XmppSendUtil.sendIqPacket(account, request, new HttpUploadSlotRequestReceived(entity)); - MessageUtil.markMessage(message, Message.STATUS_UNSEND); - - Logging.d("httpupload", "Upload slot for file upload requested"); - started = true; - } catch (FileNotFoundException e) { - Logging.e("httpupload", "Could not find file, exception message: " + e.getMessage()); - } - return started; - } - - /** - * Checks whether a message can be sent using this service or not. - * - * @param message the message to be checked - * @return true if the message can be processed, false otherwise - */ - @Override - public boolean accept(Message message) { - return null != message - && null != message.getConversation() - && null != message.getConversation().getAccount() - && message.getConversation().getAccount().httpUploadAvailable(FileBackend.getFile(message, false).getSize()); - } -} diff --git a/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/httpupload/HttpUploadSlotRequestReceived.java b/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/httpupload/HttpUploadSlotRequestReceived.java deleted file mode 100644 index 75e793f1..00000000 --- a/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/httpupload/HttpUploadSlotRequestReceived.java +++ /dev/null @@ -1,35 +0,0 @@ -package de.thedevstack.conversationsplus.services.filetransfer.httpupload; - -import de.thedevstack.android.logcat.Logging; -import de.thedevstack.conversationsplus.entities.Account; -import de.thedevstack.conversationsplus.services.filetransfer.FileTransferFailureReason; -import de.thedevstack.conversationsplus.xmpp.OnIqPacketReceived; -import de.thedevstack.conversationsplus.xmpp.exceptions.XmppException; -import de.thedevstack.conversationsplus.xmpp.filetransfer.http.upload.HttpUploadSlot; -import de.thedevstack.conversationsplus.xmpp.filetransfer.http.upload.SlotPacketParser; -import de.thedevstack.conversationsplus.xmpp.stanzas.IqPacket; - -/** - * - */ -public class HttpUploadSlotRequestReceived implements OnIqPacketReceived { - private final HttpFileTransferEntity entity; - - public HttpUploadSlotRequestReceived(HttpFileTransferEntity entity) { - this.entity = entity; - } - - @Override - public void onIqPacketReceived(Account account, IqPacket packet) { - try { - HttpUploadSlot slot = SlotPacketParser.parseGetAndPutUrl(packet); - this.entity.setSlot(slot); - if (!this.entity.isCanceled()) { - new Thread(new HttpFileUploader(this.entity)).start(); - } - } catch (XmppException e) { - Logging.e("httpupload", e.getMessage()); - this.entity.fail(FileTransferFailureReason.createNonRecoverableFailureReason(e)); - } - } -} diff --git a/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/httpupload/HttpUploadedFileEncryptionUiCallback.java b/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/httpupload/HttpUploadedFileEncryptionUiCallback.java deleted file mode 100644 index e3935252..00000000 --- a/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/httpupload/HttpUploadedFileEncryptionUiCallback.java +++ /dev/null @@ -1,34 +0,0 @@ -package de.thedevstack.conversationsplus.services.filetransfer.httpupload; - -import android.app.PendingIntent; - -import de.thedevstack.conversationsplus.entities.Message; -import de.thedevstack.conversationsplus.services.filetransfer.FileTransferFailureReason; -import de.thedevstack.conversationsplus.ui.UiCallback; -import de.thedevstack.conversationsplus.utils.XmppConnectionServiceAccessor; - -/** - * - */ -public class HttpUploadedFileEncryptionUiCallback implements UiCallback { - private final HttpFileTransferEntity entity; - - public HttpUploadedFileEncryptionUiCallback(HttpFileTransferEntity entity) { - this.entity = entity; - } - - @Override - public void success(Message message) { - XmppConnectionServiceAccessor.xmppConnectionService.resendMessage(message, this.entity.isDelayed()); - } - - @Override - public void error(int errorCode, Message object) { - this.entity.fail(FileTransferFailureReason.createLimitedRecoverableFailureReason("Failed to encrypt")); - } - - @Override - public void userInputRequried(PendingIntent pi, Message object) { - this.entity.fail(FileTransferFailureReason.createLimitedRecoverableFailureReason("Failed to encrypt, user input would have been required")); - } -} -- cgit v1.2.3 From ad096f4bfd220a106aab670105275397bfcbfd0e Mon Sep 17 00:00:00 2001 From: steckbrief Date: Tue, 23 Aug 2016 11:23:40 +0200 Subject: Confirmation dialog added before sending delete command for remote file --- .../http/delete/DeleteRemoteFileService.java | 33 ++++++++++++++++------ 1 file changed, 25 insertions(+), 8 deletions(-) (limited to 'src/main/java/de/thedevstack/conversationsplus/services/filetransfer') diff --git a/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/http/delete/DeleteRemoteFileService.java b/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/http/delete/DeleteRemoteFileService.java index 42410ed4..2b26fd85 100644 --- a/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/http/delete/DeleteRemoteFileService.java +++ b/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/http/delete/DeleteRemoteFileService.java @@ -2,6 +2,7 @@ package de.thedevstack.conversationsplus.services.filetransfer.http.delete; import de.thedevstack.conversationsplus.entities.Account; import de.thedevstack.conversationsplus.entities.Message; +import de.thedevstack.conversationsplus.ui.listeners.SimpleUserDecisionCallback; import de.thedevstack.conversationsplus.utils.XmppSendUtil; import de.thedevstack.conversationsplus.xmpp.filetransfer.http.FileTransferHttp; import de.thedevstack.conversationsplus.xmpp.filetransfer.http.delete.FileTransferHttpDeleteSlotRequestPacketGenerator; @@ -11,20 +12,36 @@ import de.thedevstack.conversationsplus.xmpp.stanzas.IqPacket; /** * Created by steckbrief on 21.08.2016. */ -public class DeleteRemoteFileService { - public void deleteRemoteFile(Message message) { - if (message.isHttpUploaded()) { - String path = message.getBody(); - if (message.hasFileOnRemoteHost()) { - path = message.getFileParams().url.toString(); +public class DeleteRemoteFileService implements SimpleUserDecisionCallback { + private Message message; + + public DeleteRemoteFileService(Message message) { + this.message = message; + } + + public void deleteRemoteFile() { + if (this.message.isHttpUploaded()) { + String path = this.message.getBody(); + if (this.message.hasFileOnRemoteHost()) { + path = this.message.getFileParams().url.toString(); } - DeleteRemoteFile remoteFile = new DeleteRemoteFile(path, message); - Account account = message.getConversation().getAccount(); + DeleteRemoteFile remoteFile = new DeleteRemoteFile(path, this.message); + Account account = this.message.getConversation().getAccount(); Jid host = account.getXmppConnection().findDiscoItemByFeature(FileTransferHttp.NAMESPACE); IqPacket request = FileTransferHttpDeleteSlotRequestPacketGenerator.generate(host, path); XmppSendUtil.sendIqPacket(account, request, new DeleteTokenReceived(remoteFile)); } } + + @Override + public void onYes() { + this.deleteRemoteFile(); + } + + @Override + public void onNo() { + // Nothing to do + } } -- cgit v1.2.3 From 34fcdda53fa8ae1174909b62860534d2d874eb72 Mon Sep 17 00:00:00 2001 From: steckbrief Date: Thu, 29 Sep 2016 11:57:16 +0200 Subject: Implements FS#235: Deletion of remote files uploaded via httpupload --- .../filetransfer/http/delete/DeleteRemoteFile.java | 4 ++-- .../http/delete/DeleteRemoteFileService.java | 11 ++++++++--- .../filetransfer/http/delete/DeleteTokenReceived.java | 18 +++++++++++------- .../http/upload/HttpFileTransferEntity.java | 11 ++++++++++- .../filetransfer/http/upload/HttpFileUploader.java | 1 + .../http/upload/HttpUploadFileTransferService.java | 3 ++- 6 files changed, 34 insertions(+), 14 deletions(-) (limited to 'src/main/java/de/thedevstack/conversationsplus/services/filetransfer') diff --git a/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/http/delete/DeleteRemoteFile.java b/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/http/delete/DeleteRemoteFile.java index 39a369ef..1549388e 100644 --- a/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/http/delete/DeleteRemoteFile.java +++ b/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/http/delete/DeleteRemoteFile.java @@ -3,10 +3,10 @@ package de.thedevstack.conversationsplus.services.filetransfer.http.delete; import android.support.annotation.NonNull; import de.thedevstack.conversationsplus.entities.Message; -import de.thedevstack.conversationsplus.entities.RemoteFile; +import de.thedevstack.conversationsplus.dto.RemoteFile; /** - * Created by steckbrief on 22.08.2016. + * */ public class DeleteRemoteFile extends RemoteFile { private final Message message; diff --git a/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/http/delete/DeleteRemoteFileService.java b/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/http/delete/DeleteRemoteFileService.java index 2b26fd85..eb9f1b04 100644 --- a/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/http/delete/DeleteRemoteFileService.java +++ b/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/http/delete/DeleteRemoteFileService.java @@ -1,8 +1,13 @@ package de.thedevstack.conversationsplus.services.filetransfer.http.delete; +import de.thedevstack.conversationsplus.ConversationsPlusApplication; import de.thedevstack.conversationsplus.entities.Account; import de.thedevstack.conversationsplus.entities.Message; +import de.thedevstack.conversationsplus.enums.FileStatus; +import de.thedevstack.conversationsplus.persistance.DatabaseBackend; import de.thedevstack.conversationsplus.ui.listeners.SimpleUserDecisionCallback; +import de.thedevstack.conversationsplus.utils.MessageUtil; +import de.thedevstack.conversationsplus.utils.UiUpdateHelper; import de.thedevstack.conversationsplus.utils.XmppSendUtil; import de.thedevstack.conversationsplus.xmpp.filetransfer.http.FileTransferHttp; import de.thedevstack.conversationsplus.xmpp.filetransfer.http.delete.FileTransferHttpDeleteSlotRequestPacketGenerator; @@ -10,7 +15,7 @@ import de.thedevstack.conversationsplus.xmpp.jid.Jid; import de.thedevstack.conversationsplus.xmpp.stanzas.IqPacket; /** - * Created by steckbrief on 21.08.2016. + * */ public class DeleteRemoteFileService implements SimpleUserDecisionCallback { private Message message; @@ -23,14 +28,14 @@ public class DeleteRemoteFileService implements SimpleUserDecisionCallback { if (this.message.isHttpUploaded()) { String path = this.message.getBody(); if (this.message.hasFileOnRemoteHost()) { - path = this.message.getFileParams().url.toString(); + path = this.message.getFileParams().getUrl(); } DeleteRemoteFile remoteFile = new DeleteRemoteFile(path, this.message); Account account = this.message.getConversation().getAccount(); Jid host = account.getXmppConnection().findDiscoItemByFeature(FileTransferHttp.NAMESPACE); IqPacket request = FileTransferHttpDeleteSlotRequestPacketGenerator.generate(host, path); - + MessageUtil.setAndSaveFileStatus(this.message, FileStatus.DELETING); XmppSendUtil.sendIqPacket(account, request, new DeleteTokenReceived(remoteFile)); } } diff --git a/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/http/delete/DeleteTokenReceived.java b/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/http/delete/DeleteTokenReceived.java index a8bef0ed..186454e4 100644 --- a/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/http/delete/DeleteTokenReceived.java +++ b/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/http/delete/DeleteTokenReceived.java @@ -7,7 +7,7 @@ import java.io.IOException; import de.thedevstack.android.logcat.Logging; import de.thedevstack.conversationsplus.entities.Account; -import de.thedevstack.conversationsplus.entities.Message; +import de.thedevstack.conversationsplus.enums.FileStatus; import de.thedevstack.conversationsplus.http.HttpClient; import de.thedevstack.conversationsplus.utils.MessageUtil; import de.thedevstack.conversationsplus.xmpp.OnIqPacketReceived; @@ -21,7 +21,7 @@ import okhttp3.Request; import okhttp3.Response; /** - * Created by steckbrief on 21.08.2016. + * */ public class DeleteTokenReceived implements OnIqPacketReceived { private static final String HEADER_NAME_DELETE_TOKEN = "X-FILETRANSFER-HTTP-DELETE-TOKEN"; @@ -47,19 +47,19 @@ public class DeleteTokenReceived implements OnIqPacketReceived { @Override public void onFailure(Call call, IOException e) { Logging.e("filetransfer.http.delete", "Error while connecting to '" + call.request().url() + "': " + e.getMessage()); - MessageUtil.markMessage(remoteFile.getMessage(), Message.STATUS_REMOTE_FILE_DELETE_FAILED); + MessageUtil.setAndSaveFileStatus(remoteFile.getMessage(), FileStatus.DELETE_FAILED); } @Override public void onResponse(Call call, Response response) throws IOException { if (response.isSuccessful()) { - MessageUtil.markMessage(remoteFile.getMessage(), Message.STATUS_REMOTE_FILE_DELETED); + MessageUtil.setAndSaveFileStatus(remoteFile.getMessage(), FileStatus.DELETED); Logging.i("filetransfer.http.delete", "Remote file successfully deleted '" + remoteFile.getPath() + "'"); } else { String detailedMessage = response.body().string(); + FileStatus fileStatus = FileStatus.DELETE_FAILED; switch (response.code()) { case 403: - case 404: case 500: try { JSONObject jsonObject = new JSONObject(detailedMessage); @@ -68,16 +68,20 @@ public class DeleteTokenReceived implements OnIqPacketReceived { Logging.e("filetransfer.http.delete", "Failed to get error message from expected json response: " + detailedMessage); } break; + case 404: + fileStatus = FileStatus.DELETED; + Logging.i("filetransfer.http.delete", "Failed to delete file - it was already deleted."); + break; } Logging.e("filetransfer.http.delete", "Could not delete remote file '" + remoteFile.getPath() + "'. Response Code: " + response.code() + ", details: " + detailedMessage); - MessageUtil.markMessage(remoteFile.getMessage(), Message.STATUS_REMOTE_FILE_DELETE_FAILED); + MessageUtil.setAndSaveFileStatus(remoteFile.getMessage(), fileStatus); } } }); } catch (XmppException e) { Logging.e("filetransfer.http.delete", "Error while trying to get the delete token: " + e.getMessage()); - MessageUtil.markMessage(remoteFile.getMessage(), Message.STATUS_REMOTE_FILE_DELETE_FAILED); + MessageUtil.setAndSaveFileStatus(remoteFile.getMessage(), FileStatus.DELETE_FAILED); } } } diff --git a/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/http/upload/HttpFileTransferEntity.java b/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/http/upload/HttpFileTransferEntity.java index fbe7186b..a373d584 100644 --- a/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/http/upload/HttpFileTransferEntity.java +++ b/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/http/upload/HttpFileTransferEntity.java @@ -6,7 +6,9 @@ import java.net.URL; import de.thedevstack.android.logcat.Logging; import de.thedevstack.conversationsplus.Config; import de.thedevstack.conversationsplus.ConversationsPlusApplication; +import de.thedevstack.conversationsplus.entities.FileParams; import de.thedevstack.conversationsplus.entities.Message; +import de.thedevstack.conversationsplus.enums.FileStatus; import de.thedevstack.conversationsplus.services.filetransfer.FileTransferEntity; import de.thedevstack.conversationsplus.services.filetransfer.FileTransferFailureReason; import de.thedevstack.conversationsplus.utils.CryptoHelper; @@ -24,7 +26,13 @@ public class HttpFileTransferEntity extends FileTransferEntity { public HttpFileTransferEntity(Message message, boolean delayed) { super(message); this.getMessage().setHttpUploaded(true); - this.getMessage().setNoDownloadable(); + this.getMessage().setNoDownloadable(); // TODO Set rmeote file status to uploaded + FileParams fileParams = this.getMessage().getFileParams(); + if (null == fileParams) { + fileParams = new FileParams(); + } + fileParams.setFileStatus(FileStatus.NEEDS_UPLOAD); + this.getMessage().setFileParams(fileParams); if (Config.ENCRYPT_ON_HTTP_UPLOADED || message.getEncryption() == Message.ENCRYPTION_AXOLOTL || message.getEncryption() == Message.ENCRYPTION_OTR) { @@ -77,6 +85,7 @@ public class HttpFileTransferEntity extends FileTransferEntity { getUrl = new URL(getUrl.toString() + "#" + CryptoHelper.bytesToHex(this.getKey())); } + this.getMessage().getFileParams().setFileStatus(FileStatus.UPLOADED); MessageUtil.updateFileParams(this.getMessage(), getUrl); } catch (MalformedURLException e) { Logging.e("httpupload", "Not a valid get url"); diff --git a/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/http/upload/HttpFileUploader.java b/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/http/upload/HttpFileUploader.java index 318a8b8f..8cae599c 100644 --- a/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/http/upload/HttpFileUploader.java +++ b/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/http/upload/HttpFileUploader.java @@ -84,6 +84,7 @@ public class HttpFileUploader implements Runnable { // Send the URL to the counterpart Message message = this.entity.getMessage(); + message.setBody(this.entity.getGetUrl()); message.setCounterpart(message.getConversation().getJid().toBareJid()); if (message.getEncryption() == Message.ENCRYPTION_DECRYPTED) { XmppConnectionServiceAccessor.xmppConnectionService.getPgpEngine().encrypt(message, new HttpUploadedFileEncryptionUiCallback(this.entity)); diff --git a/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/http/upload/HttpUploadFileTransferService.java b/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/http/upload/HttpUploadFileTransferService.java index f08e27f5..6b19cd5f 100644 --- a/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/http/upload/HttpUploadFileTransferService.java +++ b/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/http/upload/HttpUploadFileTransferService.java @@ -86,6 +86,7 @@ public class HttpUploadFileTransferService extends AbstractFileTransferService i return null != message && null != message.getConversation() && null != message.getConversation().getAccount() - && message.getConversation().getAccount().httpUploadAvailable(FileBackend.getFile(message, false).getSize()); + && null != message.getFileParams() + && message.getConversation().getAccount().httpUploadAvailable(message.getFileParams().getSize()); } } -- cgit v1.2.3