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 --- .../services/AbstractConnectionManager.java | 17 +-- .../services/FileTransferService.java | 31 +++++ .../services/MessageArchiveService.java | 3 +- .../services/XmppConnectionService.java | 118 ++++--------------- .../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 +++++++++ 14 files changed, 709 insertions(+), 111 deletions(-) create mode 100644 src/main/java/de/thedevstack/conversationsplus/services/FileTransferService.java 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') diff --git a/src/main/java/de/thedevstack/conversationsplus/services/AbstractConnectionManager.java b/src/main/java/de/thedevstack/conversationsplus/services/AbstractConnectionManager.java index 7c937475..5456430a 100644 --- a/src/main/java/de/thedevstack/conversationsplus/services/AbstractConnectionManager.java +++ b/src/main/java/de/thedevstack/conversationsplus/services/AbstractConnectionManager.java @@ -31,22 +31,14 @@ import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.SecretKeySpec; import de.thedevstack.conversationsplus.Config; +import de.thedevstack.conversationsplus.ConversationsPlusApplication; import de.thedevstack.conversationsplus.entities.DownloadableFile; public class AbstractConnectionManager { - protected XmppConnectionService mXmppConnectionService; - - public AbstractConnectionManager(XmppConnectionService service) { - this.mXmppConnectionService = service; - } - - public XmppConnectionService getXmppConnectionService() { - return this.mXmppConnectionService; - } public boolean hasStoragePermission() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - return mXmppConnectionService.checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED; + return ConversationsPlusApplication.getAppContext().checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED; } else { return true; } @@ -125,9 +117,4 @@ public class AbstractConnectionManager { return null; } } - - public PowerManager.WakeLock createWakeLock(String name) { - PowerManager powerManager = (PowerManager) mXmppConnectionService.getSystemService(Context.POWER_SERVICE); - return powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,name); - } } diff --git a/src/main/java/de/thedevstack/conversationsplus/services/FileTransferService.java b/src/main/java/de/thedevstack/conversationsplus/services/FileTransferService.java new file mode 100644 index 00000000..d6ce4769 --- /dev/null +++ b/src/main/java/de/thedevstack/conversationsplus/services/FileTransferService.java @@ -0,0 +1,31 @@ +package de.thedevstack.conversationsplus.services; + +import de.thedevstack.conversationsplus.entities.Account; +import de.thedevstack.conversationsplus.entities.Message; +import de.thedevstack.conversationsplus.entities.Transferable; + +/** + * + */ +public interface FileTransferService { + /** + * 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 + */ + boolean transferFile(Message message); + /** + * 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 + */ + boolean transferFile(Message message, boolean delay); + + /** + * 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 + */ + boolean accept(Message message); +} diff --git a/src/main/java/de/thedevstack/conversationsplus/services/MessageArchiveService.java b/src/main/java/de/thedevstack/conversationsplus/services/MessageArchiveService.java index cd55b42f..6402f4ed 100644 --- a/src/main/java/de/thedevstack/conversationsplus/services/MessageArchiveService.java +++ b/src/main/java/de/thedevstack/conversationsplus/services/MessageArchiveService.java @@ -10,6 +10,7 @@ import java.util.List; import de.thedevstack.android.logcat.Logging; import de.thedevstack.conversationsplus.Config; +import de.thedevstack.conversationsplus.ConversationsPlusApplication; import de.thedevstack.conversationsplus.R; import de.thedevstack.conversationsplus.entities.Account; import de.thedevstack.conversationsplus.entities.Conversation; @@ -290,7 +291,7 @@ public class MessageArchiveService implements OnAdvancedStreamFeaturesLoaded { this.account = account; this.start = start; this.end = end; - this.queryId = new BigInteger(50, mXmppConnectionService.getRNG()).toString(32); + this.queryId = new BigInteger(50, ConversationsPlusApplication.getSecureRandom()).toString(32); } private Query page(String reference) { diff --git a/src/main/java/de/thedevstack/conversationsplus/services/XmppConnectionService.java b/src/main/java/de/thedevstack/conversationsplus/services/XmppConnectionService.java index 9c0e55db..83cefc80 100644 --- a/src/main/java/de/thedevstack/conversationsplus/services/XmppConnectionService.java +++ b/src/main/java/de/thedevstack/conversationsplus/services/XmppConnectionService.java @@ -12,7 +12,6 @@ import android.graphics.Bitmap; import android.media.AudioManager; import android.net.ConnectivityManager; import android.net.NetworkInfo; -import android.net.Uri; import android.os.Binder; import android.os.Build; import android.os.Bundle; @@ -58,8 +57,7 @@ import de.duenndns.ssl.MemorizingTrustManager; import de.thedevstack.android.logcat.Logging; import de.thedevstack.conversationsplus.ConversationsPlusApplication; import de.thedevstack.conversationsplus.ConversationsPlusPreferences; -import de.thedevstack.conversationsplus.exceptions.FileCopyException; -import de.thedevstack.conversationsplus.utils.FileUtils; +import de.thedevstack.conversationsplus.services.filetransfer.FileTransferManager; import de.thedevstack.conversationsplus.utils.ImageUtil; import de.thedevstack.conversationsplus.utils.MessageUtil; import de.thedevstack.conversationsplus.utils.UiUpdateHelper; @@ -97,7 +95,6 @@ import de.thedevstack.conversationsplus.ui.UiCallback; import de.thedevstack.conversationsplus.utils.CryptoHelper; import de.thedevstack.conversationsplus.utils.ExceptionHelper; import de.thedevstack.conversationsplus.utils.OnPhoneContactsLoadedListener; -import de.thedevstack.conversationsplus.utils.PRNGFixes; import de.thedevstack.conversationsplus.utils.PhoneHelper; import de.thedevstack.conversationsplus.utils.Xmlns; import de.thedevstack.conversationsplus.xml.Element; @@ -148,7 +145,6 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa startService(intent); } }; - private MemorizingTrustManager mMemorizingTrustManager; private NotificationService mNotificationService = new NotificationService( this); private OnMessagePacketReceived mMessageParser = new MessageParser(this); @@ -169,8 +165,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa private MessageGenerator mMessageGenerator = new MessageGenerator(); private PresenceGenerator mPresenceGenerator = new PresenceGenerator(); private List accounts; - private JingleConnectionManager mJingleConnectionManager = new JingleConnectionManager( - this); + private JingleConnectionManager mJingleConnectionManager = new JingleConnectionManager(); public OnContactStatusChanged onContactStatusChanged = new OnContactStatusChanged() { @Override @@ -197,8 +192,6 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa } } }; - private HttpConnectionManager mHttpConnectionManager = new HttpConnectionManager( - this); private MessageArchiveService mMessageArchiveService = new MessageArchiveService(this); private PushManagementService mPushManagementService = new PushManagementService(this); private OnConversationUpdate mOnConversationUpdate = null; @@ -239,7 +232,6 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa private int mucRosterChangedListenerCount = 0; private OnKeyStatusUpdated mOnKeyStatusUpdated = null; private int keyStatusUpdatedListenerCount = 0; - private SecureRandom mRandom; private LruCache,ServiceDiscoveryResult> discoCache = new LruCache<>(20); private final OnBindListener mOnBindListener = new OnBindListener() { @@ -311,7 +303,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa && checkListeners(); Logging.d(Config.LOGTAG,account.getJid().toBareJid()+": push mode "+Boolean.toString(pushMode)); if (!disabled && !pushMode) { - int timeToReconnect = mRandom.nextInt(20) + 10; + int timeToReconnect = ConversationsPlusApplication.getSecureRandom().nextInt(20) + 10; scheduleWakeUpCall(timeToReconnect, account.getUuid().hashCode()); } } else if (account.getStatus() == Account.State.REGISTRATION_SUCCESSFUL) { @@ -334,8 +326,6 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa private OpenPgpServiceConnection pgpServiceConnection; private PgpEngine mPgpEngine = null; private WakeLock wakeLock; - private PowerManager pm; - private LruCache mBitmapCache; private Thread mPhoneContactMergerThread; private EventReceiver mEventReceiver = new EventReceiver(); @@ -577,17 +567,8 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa @Override public void onCreate() { ExceptionHelper.init(getApplicationContext()); - PRNGFixes.apply(); - this.mRandom = new SecureRandom(); - updateMemorizingTrustmanager(); final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024); final int cacheSize = maxMemory / 8; - this.mBitmapCache = new LruCache(cacheSize) { - @Override - protected int sizeOf(final String key, final Bitmap bitmap) { - return bitmap.getByteCount() / 1024; - } - }; this.databaseBackend = DatabaseBackend.getInstance(getApplicationContext()); this.accounts = databaseBackend.getAccounts(); @@ -614,8 +595,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa this.pgpServiceConnection.bindToService(); } - this.pm = (PowerManager) getSystemService(Context.POWER_SERVICE); - this.wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "XmppConnectionService"); + this.wakeLock = ConversationsPlusApplication.createPartialWakeLock("XmppConnectionService"); toggleForegroundService(); updateUnreadCountBadge(); UiUpdateHelper.initXmppConnectionService(this); @@ -628,7 +608,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa super.onTrimMemory(level); if (level >= TRIM_MEMORY_COMPLETE) { Log.d(Config.LOGTAG, "clear cache due to low memory"); - getBitmapCache().evictAll(); + ImageUtil.evictBitmapCache(); } } @@ -739,16 +719,6 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa } } - private void sendFileMessage(final Message message, final boolean delay) { - Logging.d(Config.LOGTAG, "send file message"); - final Account account = message.getConversation().getAccount(); - if (account.httpUploadAvailable(FileBackend.getFile(message,false).getSize())) { - mHttpConnectionManager.createNewUploadConnection(message, delay); - } else { - mJingleConnectionManager.createNewConnection(message); - } - } - public void sendMessage(final Message message) { sendMessage(message, false, false); } @@ -776,28 +746,23 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa } if (account.isOnlineAndConnected()) { + FileTransferManager fileTransferManager = FileTransferManager.getInstance(); switch (message.getEncryption()) { case Message.ENCRYPTION_NONE: - if (message.needsUploading()) { - if (account.httpUploadAvailable(FileBackend.getFile(message,false).getSize()) - || message.fixCounterpart()) { - this.sendFileMessage(message, delay); - } else { - break; - } + if (fileTransferManager.accept(message)) { + if (!fileTransferManager.transferFile(message, delay)) { + break; + } } else { packet = mMessageGenerator.generateChat(message); } break; case Message.ENCRYPTION_PGP: case Message.ENCRYPTION_DECRYPTED: - if (message.needsUploading()) { - if (account.httpUploadAvailable(FileBackend.getFile(message,false).getSize()) - || message.fixCounterpart()) { - this.sendFileMessage(message, delay); - } else { - break; - } + if (fileTransferManager.accept(message)) { + if (!fileTransferManager.transferFile(message, delay)) { + break; + } } else { packet = mMessageGenerator.generatePgpChat(message); } @@ -810,7 +775,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa } catch (InvalidJidException e) { break; } - if (message.needsUploading()) { + if (message.needsUploading()) { //TODO: Use FileTransferManager with a preselection of filetransfer method mJingleConnectionManager.createNewConnection(message); } else { packet = mMessageGenerator.generateOtrChat(message); @@ -828,13 +793,10 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa break; case Message.ENCRYPTION_AXOLOTL: message.setFingerprint(account.getAxolotlService().getOwnFingerprint()); - if (message.needsUploading()) { - if (account.httpUploadAvailable(FileBackend.getFile(message,false).getSize()) - || message.fixCounterpart()) { - this.sendFileMessage(message, delay); - } else { - break; - } + if (fileTransferManager.accept(message)) { + if (!fileTransferManager.transferFile(message, delay)) { + break; + } } else { XmppAxolotlMessage axolotlMessage = account.getAxolotlService().fetchAxolotlMessageFromCache(message); if (axolotlMessage == null) { @@ -1331,7 +1293,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa callback.onAccountCreated(account); if (Config.X509_VERIFICATION) { try { - getMemorizingTrustManager().getNonInteractive().checkClientTrusted(chain, "RSA"); + ConversationsPlusApplication.getMemorizingTrustManager().getNonInteractive().checkClientTrusted(chain, "RSA"); } catch (CertificateException e) { callback.informUser(R.string.certificate_chain_is_not_trusted); } @@ -1359,7 +1321,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa databaseBackend.updateAccount(account); if (Config.X509_VERIFICATION) { try { - getMemorizingTrustManager().getNonInteractive().checkClientTrusted(chain, "RSA"); + ConversationsPlusApplication.getMemorizingTrustManager().getNonInteractive().checkClientTrusted(chain, "RSA"); } catch (CertificateException e) { showErrorToastInUi(R.string.certificate_chain_is_not_trusted); } @@ -1901,7 +1863,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa } return; } - String name = new BigInteger(75, getRNG()).toString(32); + String name = new BigInteger(75, ConversationsPlusApplication.getSecureRandom()).toString(32); Jid jid = Jid.fromParts(name, server, null); final Conversation conversation = findOrCreateConversation(account, jid, true); joinMuc(conversation, new OnConferenceJoined() { @@ -2170,7 +2132,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa public boolean renewSymmetricKey(Conversation conversation) { Account account = conversation.getAccount(); byte[] symmetricKey = new byte[32]; - this.mRandom.nextBytes(symmetricKey); + ConversationsPlusApplication.getSecureRandom().nextBytes(symmetricKey); Session otrSession = conversation.getOtrSession(); if (otrSession != null) { MessagePacket packet = new MessagePacket(); @@ -2440,36 +2402,6 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa } } - public SecureRandom getRNG() { - return this.mRandom; - } - - public MemorizingTrustManager getMemorizingTrustManager() { - return this.mMemorizingTrustManager; - } - - public void setMemorizingTrustManager(MemorizingTrustManager trustManager) { - this.mMemorizingTrustManager = trustManager; - } - - public void updateMemorizingTrustmanager() { - final MemorizingTrustManager tm; - if (ConversationsPlusPreferences.dontTrustSystemCAs()) { - tm = new MemorizingTrustManager(getApplicationContext(), null); - } else { - tm = new MemorizingTrustManager(getApplicationContext()); - } - setMemorizingTrustManager(tm); - } - - public PowerManager getPowerManager() { - return this.pm; - } - - public LruCache getBitmapCache() { - return this.mBitmapCache; - } - public void syncRosterToDisk(final Account account) { Runnable runnable = new Runnable() { @@ -2599,10 +2531,6 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa return this.mNotificationService; } - public HttpConnectionManager getHttpConnectionManager() { - return this.mHttpConnectionManager; - } - public void resendFailedMessages(final Message message) { if (message.getStatus() == Message.STATUS_SEND_FAILED) { message.setTime(System.currentTimeMillis()); 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 --- .../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 (limited to 'src/main/java/de/thedevstack/conversationsplus/services') 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