aboutsummaryrefslogtreecommitdiffstats
path: root/src/main/java/de/thedevstack/conversationsplus/services/filetransfer
diff options
context:
space:
mode:
authorsteckbrief <steckbrief@chefmail.de>2016-06-06 09:05:50 +0200
committersteckbrief <steckbrief@chefmail.de>2016-06-06 09:07:59 +0200
commitf45ad10b1baaf09fd4a40d6b63d1cd093623eedc (patch)
tree894e879ac98891879e9b97aa1df86b3b10c3d189 /src/main/java/de/thedevstack/conversationsplus/services/filetransfer
parentb1ab7347b92329512bebe57f6624cae33c27036f (diff)
Related to FS#131, FS#129, FS#220:filetransfer
- 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
Diffstat (limited to 'src/main/java/de/thedevstack/conversationsplus/services/filetransfer')
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/services/filetransfer/AbstractFileTransferService.java22
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/services/filetransfer/FileTransferEntity.java194
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/services/filetransfer/FileTransferFailureReason.java91
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/services/filetransfer/FileTransferFailureType.java24
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/services/filetransfer/FileTransferManager.java58
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/services/filetransfer/FileTransferStatusEnum.java23
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/services/filetransfer/FileTransferStatusListener.java5
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/services/filetransfer/httpupload/HttpFileTransferEntity.java35
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/services/filetransfer/httpupload/HttpFileUploader.java74
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/services/filetransfer/httpupload/HttpUploadFileTransferService.java7
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/services/filetransfer/httpupload/HttpUploadSlotRequestReceived.java3
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/services/filetransfer/httpupload/HttpUploadedFileEncryptionUiCallback.java5
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/services/filetransfer/jingle/JingleFileTransferService.java3
13 files changed, 464 insertions, 80 deletions
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<Message> failedMessages = new ArrayList<>();
+public abstract class AbstractFileTransferService implements FileTransferService {
+ private List<FileTransferStatusListener> 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<FileTransferStatusListener> 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 <code>false</code>
+ */
@Override
public boolean start() {
return false;
}
+ /**
+ * Returns the global transferable status.
+ *
+ * @return {@value STATUS_FAILED} if #isFailed returns <code>true</code>, {@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);
+ }
}
- public void fail() {
- this.failed = true;
+ /**
+ * 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);
+ }
+ }
+
+ /**
+ * 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 <code>true</code> if the file is successfully transferred, <code>false</code> otherwise
+ */
+ public boolean isTransferred() {
+ return FileTransferStatusEnum.TRANSFERRED == this.transferStatus;
}
+ /**
+ * Whether the file transfer is canceled or not.
+ * @return <code>true</code> if the file transfer was canceled, <code>false</code> otherwise
+ */
public boolean isCanceled() {
- return this.canceled;
+ return FileTransferStatusEnum.CANCELED == this.transferStatus;
}
+ /**
+ * Whether the file transfer failed or not.
+ * @return <code>true</code> if the file transfer failed, <code>false</code> 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<WeightedTransferService> transferServices;
private static final FileTransferManager INSTANCE = new FileTransferManager();
+ private final HashMap<String, WeightedTransferService> 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 <code>true</code> if the file transfer was successful, <code>false</code> 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 <code>true</code> if the message can be processed, <code>false</code> 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<WeightedTransferService> {
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);
}
- } catch (IOException e) {
- errorStream = (null != connection) ? connection.getErrorStream() : null;
- String httpResponseMessage = null;
- if (null != errorStream) {
- httpResponseMessage = new Scanner(errorStream).useDelimiter("\\A").next();
+ 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());
- 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<InputStream, Integer> 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<Message>
@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() {