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 --- .../services/FileTransferService.java | 11 +- .../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 +- 14 files changed, 472 insertions(+), 83 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 diff --git a/src/main/java/de/thedevstack/conversationsplus/services/FileTransferService.java b/src/main/java/de/thedevstack/conversationsplus/services/FileTransferService.java index d6ce4769..9fc35b2b 100644 --- a/src/main/java/de/thedevstack/conversationsplus/services/FileTransferService.java +++ b/src/main/java/de/thedevstack/conversationsplus/services/FileTransferService.java @@ -1,11 +1,10 @@ package de.thedevstack.conversationsplus.services; -import de.thedevstack.conversationsplus.entities.Account; import de.thedevstack.conversationsplus.entities.Message; -import de.thedevstack.conversationsplus.entities.Transferable; +import de.thedevstack.conversationsplus.services.filetransfer.FileTransferStatusListener; /** - * + * An implementation of this class transfers a file to another entity or server. */ public interface FileTransferService { /** @@ -28,4 +27,10 @@ public interface FileTransferService { * @return true if the message can be processed, false otherwise */ boolean accept(Message message); + + /** + * Adds one or more file transfer status listeners. + * @param listeners the listeners to add + */ + void addFileTransferStatusListener(FileTransferStatusListener... listeners); } 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