From 34fcdda53fa8ae1174909b62860534d2d874eb72 Mon Sep 17 00:00:00 2001 From: steckbrief Date: Thu, 29 Sep 2016 11:57:16 +0200 Subject: Implements FS#235: Deletion of remote files uploaded via httpupload --- .../conversationsplus/crypto/PgpEngine.java | 11 +- .../crypto/axolotl/AxolotlServiceImpl.java | 7 +- .../conversationsplus/dto/RemoteFile.java | 37 ++++ .../conversationsplus/entities/Conversation.java | 7 +- .../conversationsplus/entities/FileParams.java | 129 +++++++++++ .../conversationsplus/entities/Message.java | 170 +++----------- .../conversationsplus/entities/RemoteFile.java | 37 ---- .../conversationsplus/enums/FileStatus.java | 17 ++ .../generator/MessageGenerator.java | 24 +- .../http/HttpConnectionManager.java | 5 - .../http/HttpDownloadConnection.java | 10 +- .../http/HttpUploadConnection.java | 243 --------------------- .../conversationsplus/parser/MessageParser.java | 7 +- .../persistance/CursorHelper.java | 203 ----------------- .../persistance/DatabaseBackend.java | 33 ++- .../conversationsplus/persistance/FileBackend.java | 81 ++++--- .../persistance/MessageDatabaseAccess.java | 59 ----- .../db/access/AbstractDatabaseAccess.java | 21 ++ .../persistance/db/access/CursorHelper.java | 203 +++++++++++++++++ .../db/access/MessageDatabaseAccess.java | 180 +++++++++++++++ .../FileParamsBodyToDatabaseFieldsMigration.java | 101 +++++++++ .../services/ExportLogsService.java | 2 +- .../services/NotificationService.java | 3 +- .../filetransfer/http/delete/DeleteRemoteFile.java | 4 +- .../http/delete/DeleteRemoteFileService.java | 11 +- .../http/delete/DeleteTokenReceived.java | 18 +- .../http/upload/HttpFileTransferEntity.java | 11 +- .../filetransfer/http/upload/HttpFileUploader.java | 1 + .../http/upload/HttpUploadFileTransferService.java | 3 +- .../conversationsplus/ui/ConversationFragment.java | 4 +- .../ui/adapter/ConversationAdapter.java | 2 +- .../ui/adapter/MessageAdapter.java | 65 ++++-- .../ui/dialogs/MessageDetailsDialog.java | 7 +- .../ResizePictureUserDecisionListener.java | 24 +- .../conversationsplus/utils/ConversationUtil.java | 7 +- .../conversationsplus/utils/FileUtils.java | 22 ++ .../conversationsplus/utils/ImageUtil.java | 22 +- .../conversationsplus/utils/MessageUtil.java | 61 +++--- src/main/res/layout/message_sent.xml | 12 + src/main/res/values/strings.xml | 3 + 40 files changed, 1013 insertions(+), 854 deletions(-) create mode 100644 src/main/java/de/thedevstack/conversationsplus/dto/RemoteFile.java create mode 100644 src/main/java/de/thedevstack/conversationsplus/entities/FileParams.java delete mode 100644 src/main/java/de/thedevstack/conversationsplus/entities/RemoteFile.java create mode 100644 src/main/java/de/thedevstack/conversationsplus/enums/FileStatus.java delete mode 100644 src/main/java/de/thedevstack/conversationsplus/http/HttpUploadConnection.java delete mode 100644 src/main/java/de/thedevstack/conversationsplus/persistance/CursorHelper.java delete mode 100644 src/main/java/de/thedevstack/conversationsplus/persistance/MessageDatabaseAccess.java create mode 100644 src/main/java/de/thedevstack/conversationsplus/persistance/db/access/AbstractDatabaseAccess.java create mode 100644 src/main/java/de/thedevstack/conversationsplus/persistance/db/access/CursorHelper.java create mode 100644 src/main/java/de/thedevstack/conversationsplus/persistance/db/access/MessageDatabaseAccess.java create mode 100644 src/main/java/de/thedevstack/conversationsplus/persistance/db/migrations/FileParamsBodyToDatabaseFieldsMigration.java diff --git a/src/main/java/de/thedevstack/conversationsplus/crypto/PgpEngine.java b/src/main/java/de/thedevstack/conversationsplus/crypto/PgpEngine.java index d0b30d6d..c97b8395 100644 --- a/src/main/java/de/thedevstack/conversationsplus/crypto/PgpEngine.java +++ b/src/main/java/de/thedevstack/conversationsplus/crypto/PgpEngine.java @@ -14,10 +14,8 @@ import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; -import java.net.URL; import de.thedevstack.conversationsplus.ConversationsPlusPreferences; -import de.thedevstack.conversationsplus.utils.MessageUtil; import de.thedevstack.conversationsplus.utils.StreamUtil; import de.thedevstack.conversationsplus.R; import de.thedevstack.conversationsplus.entities.Account; @@ -106,8 +104,6 @@ public class PgpEngine { switch (result.getIntExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR)) { case OpenPgpApi.RESULT_CODE_SUCCESS: - URL url = message.getFileParams().url; - MessageUtil.updateFileParams(message, url); message.setEncryption(Message.ENCRYPTION_DECRYPTED); PgpEngine.this.mXmppConnectionService .updateMessage(message); @@ -149,12 +145,7 @@ public class PgpEngine { if (!message.needsUploading()) { params.putExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, true); - String body; - if (message.hasFileOnRemoteHost()) { - body = message.getFileParams().url.toString(); - } else { - body = message.getBody(); - } + String body = message.getBody(); InputStream is = new ByteArrayInputStream(body.getBytes()); final OutputStream os = new ByteArrayOutputStream(); api.executeApiAsync(params, is, os, new IOpenPgpCallback() { diff --git a/src/main/java/de/thedevstack/conversationsplus/crypto/axolotl/AxolotlServiceImpl.java b/src/main/java/de/thedevstack/conversationsplus/crypto/axolotl/AxolotlServiceImpl.java index 5e302011..215e0995 100644 --- a/src/main/java/de/thedevstack/conversationsplus/crypto/axolotl/AxolotlServiceImpl.java +++ b/src/main/java/de/thedevstack/conversationsplus/crypto/axolotl/AxolotlServiceImpl.java @@ -925,12 +925,7 @@ public class AxolotlServiceImpl implements OnAdvancedStreamFeaturesLoaded, Axolo XmppAxolotlMessage axolotlMessage = buildHeader(message.getConversation()); if (axolotlMessage != null) { - final String content; - if (message.hasFileOnRemoteHost()) { - content = message.getFileParams().url.toString(); - } else { - content = message.getBody(); - } + final String content = message.getBody(); try { axolotlMessage.encrypt(content); } catch (CryptoFailedException e) { diff --git a/src/main/java/de/thedevstack/conversationsplus/dto/RemoteFile.java b/src/main/java/de/thedevstack/conversationsplus/dto/RemoteFile.java new file mode 100644 index 00000000..58b64b28 --- /dev/null +++ b/src/main/java/de/thedevstack/conversationsplus/dto/RemoteFile.java @@ -0,0 +1,37 @@ +package de.thedevstack.conversationsplus.dto; + +import android.support.annotation.NonNull; + +import java.io.Serializable; + +/** + * Created by steckbrief on 22.08.2016. + */ +public class RemoteFile implements Serializable { + private static final long serialVersionUID = 34564871234564L; + private final String path; + + public RemoteFile(@NonNull String path) { + this.path = path; + } + + public String getPath() { + return path; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + RemoteFile that = (RemoteFile) o; + + return path.equals(that.path); + + } + + @Override + public int hashCode() { + return path.hashCode(); + } +} diff --git a/src/main/java/de/thedevstack/conversationsplus/entities/Conversation.java b/src/main/java/de/thedevstack/conversationsplus/entities/Conversation.java index bfd00b5d..c0ae56fd 100644 --- a/src/main/java/de/thedevstack/conversationsplus/entities/Conversation.java +++ b/src/main/java/de/thedevstack/conversationsplus/entities/Conversation.java @@ -747,12 +747,7 @@ public class Conversation extends AbstractEntity implements Blockable { for (int i = this.messages.size() - 1; i >= 0; --i) { Message message = this.messages.get(i); if (message.getStatus() == Message.STATUS_UNSEND || message.getStatus() == Message.STATUS_SEND) { - String otherBody; - if (message.hasFileOnRemoteHost()) { - otherBody = message.getFileParams().url.toString(); - } else { - otherBody = message.getBody(); - } + String otherBody = message.getBody(); if (otherBody != null && otherBody.equals(body)) { return message; } diff --git a/src/main/java/de/thedevstack/conversationsplus/entities/FileParams.java b/src/main/java/de/thedevstack/conversationsplus/entities/FileParams.java new file mode 100644 index 00000000..bce8e571 --- /dev/null +++ b/src/main/java/de/thedevstack/conversationsplus/entities/FileParams.java @@ -0,0 +1,129 @@ +package de.thedevstack.conversationsplus.entities; + +import de.thedevstack.conversationsplus.enums.FileStatus; +import de.thedevstack.conversationsplus.utils.MimeUtils; + +/** + * + */ +public class FileParams { + private String name; + private String path; + private String url; + private String mimeType; + private long size = 0; + private int width = 0; + private int height = 0; + private FileStatus fileStatus; + + public FileParams() { + fileStatus = FileStatus.UNDEFINED; + } + + public FileParams(String url) { + this(); + this.url = url; + } + + public FileParams(String url, long size) { + this(url); + this.size = size; + } + + public FileParams(String url, long size, int width, int height) { + this(url, size); + this.width = width; + this.height = height; + } + + public String getUrl() { + return url; + } + + public void setUrl(String url) { + this.url = url; + } + + public long getSize() { + return size; + } + + public void setSize(long size) { + this.size = size; + } + + public int getWidth() { + return width; + } + + public void setWidth(int width) { + this.width = width; + } + + public int getHeight() { + return height; + } + + public void setHeight(int height) { + this.height = height; + } + + public String getMimeType() { + return mimeType; + } + + public void setMimeType(String mimeType) { + this.mimeType = mimeType; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getPath() { + return path; + } + + /** + * Sets the path to the file. + * If no file name is stored yet here - this method tries to extract the file name from the path. + * If the file name is stored here and the path does not end with the name - the name is appended to the path. + * @param path the path to be stored + */ + public void setPath(String path) { + if (null != path) { + if (null != this.name) { + path = (!path.endsWith(this.name)) ? path + "/" + this.name : path; + } else { + if (!path.endsWith("/")) { + this.setName(path.substring(path.lastIndexOf('/') + 1)); + } + } + if (null == this.mimeType) { + int start = path.lastIndexOf('.') + 1; + if (start < path.length()) { + String extension = path.substring(start); + this.mimeType = MimeUtils.guessMimeTypeFromExtension(extension); + } + } + } + + this.path = path; + } + + public boolean isRemoteAvailable() { + return null != this.url || FileStatus.UPLOADED == this.fileStatus || FileStatus.DELETE_FAILED == this.fileStatus; + } + + public void setFileStatus(FileStatus fileStatus) { + this.fileStatus = fileStatus; + } + + public FileStatus getFileStatus() { + return this.fileStatus; + } +} diff --git a/src/main/java/de/thedevstack/conversationsplus/entities/Message.java b/src/main/java/de/thedevstack/conversationsplus/entities/Message.java index f081e82a..a81ba404 100644 --- a/src/main/java/de/thedevstack/conversationsplus/entities/Message.java +++ b/src/main/java/de/thedevstack/conversationsplus/entities/Message.java @@ -7,6 +7,7 @@ import java.net.MalformedURLException; import java.net.URL; import de.thedevstack.conversationsplus.crypto.axolotl.XmppAxolotlSession; +import de.thedevstack.conversationsplus.enums.FileStatus; import de.thedevstack.conversationsplus.utils.FileUtils; import de.thedevstack.conversationsplus.utils.MimeUtils; import de.thedevstack.conversationsplus.xmpp.jid.InvalidJidException; @@ -27,9 +28,6 @@ public class Message extends AbstractEntity { public static final int STATUS_SEND_RECEIVED = 7; public static final int STATUS_SEND_DISPLAYED = 8; - public static final int STATUS_REMOTE_FILE_DELETE_FAILED = 101; - public static final int STATUS_REMOTE_FILE_DELETED = 102; - public static final int ENCRYPTION_NONE = 0; public static final int ENCRYPTION_PGP = 1; public static final int ENCRYPTION_OTR = 2; @@ -87,6 +85,7 @@ public class Message extends AbstractEntity { private Decision mTreatAsDownloadAble = Decision.NOT_DECIDED; private boolean httpUploaded; + private FileParams fileParams; private Message() { @@ -338,6 +337,9 @@ public class Message extends AbstractEntity { public void setType(int type) { this.type = type; + if (null != this.fileParams && (type == Message.TYPE_FILE || type == Message.TYPE_IMAGE)) { + this.setFileParams(new FileParams()); + } } public boolean isCarbon() { @@ -371,17 +373,8 @@ public class Message extends AbstractEntity { || message.getBody() == null || message.getCounterpart() == null) { return false; } else { - String body, otherBody; - if (this.hasFileOnRemoteHost()) { - body = this.getFileParams().url.toString(); - } else { - body = this.getBody(); - } - if (message.hasFileOnRemoteHost()) { - otherBody = message.getFileParams().url.toString(); - } else { - otherBody = message.getBody(); - } + String body = this.getBody(); + String otherBody = message.getBody(); if (message.getRemoteMsgId() != null && this.getRemoteMsgId() != null) { return (message.getRemoteMsgId().equals(this.getRemoteMsgId()) @@ -496,27 +489,10 @@ public class Message extends AbstractEntity { } private String extractRelevantExtension(String path) { - if (path == null || path.isEmpty()) { - return null; - } - - String filename = path.substring(path.lastIndexOf('/') + 1).toLowerCase(); - - final String lastPart = FileUtils.getLastExtension(filename); - - if (!lastPart.isEmpty()) { - // we want the real file extension, not the crypto one - final String secondToLastPart = FileUtils.getSecondToLastExtension(filename); - if (!secondToLastPart.isEmpty() && Transferable.VALID_CRYPTO_EXTENSIONS.contains(lastPart)) { - return secondToLastPart; - } else { - return lastPart; - } - } - return null; + return FileUtils.getRelevantExtension(path); } - public String getMimeType() { + public String getMimeType() { // TODO: Move to fileparams if (relativeFilePath != null) { int start = relativeFilePath.lastIndexOf('.') + 1; if (start < relativeFilePath.length()) { @@ -595,103 +571,6 @@ public class Message extends AbstractEntity { } } - public FileParams getFileParams() { - FileParams params = getLegacyFileParams(); - if (params != null) { - return params; - } - params = new FileParams(); - if (this.transferable != null) { - params.size = this.transferable.getFileSize(); - } - if (this.getBody() == null) { - return params; - } - String parts[] = this.getBody().split("\\|"); - switch (parts.length) { - case 1: - try { - params.size = Long.parseLong(parts[0]); - } catch (NumberFormatException e) { - try { - params.url = new URL(parts[0]); - } catch (MalformedURLException e1) { - params.url = null; - } - } - break; - case 2: - case 4: - try { - params.url = new URL(parts[0]); - } catch (MalformedURLException e1) { - params.url = null; - } - try { - params.size = Long.parseLong(parts[1]); - } catch (NumberFormatException e) { - params.size = 0; - } - try { - params.width = Integer.parseInt(parts[2]); - } catch (NumberFormatException | ArrayIndexOutOfBoundsException e) { - params.width = 0; - } - try { - params.height = Integer.parseInt(parts[3]); - } catch (NumberFormatException | ArrayIndexOutOfBoundsException e) { - params.height = 0; - } - break; - case 3: - try { - params.size = Long.parseLong(parts[0]); - } catch (NumberFormatException e) { - params.size = 0; - } - try { - params.width = Integer.parseInt(parts[1]); - } catch (NumberFormatException e) { - params.width = 0; - } - try { - params.height = Integer.parseInt(parts[2]); - } catch (NumberFormatException e) { - params.height = 0; - } - break; - } - return params; - } - - public FileParams getLegacyFileParams() { - FileParams params = new FileParams(); - if (this.getBody() == null) { - return params; - } - String parts[] = this.getBody().split(","); - if (parts.length == 3) { - try { - params.size = Long.parseLong(parts[0]); - } catch (NumberFormatException e) { - return null; - } - try { - params.width = Integer.parseInt(parts[1]); - } catch (NumberFormatException e) { - return null; - } - try { - params.height = Integer.parseInt(parts[2]); - } catch (NumberFormatException e) { - return null; - } - return params; - } else { - return null; - } - } - public void untie() { this.mNextMessage = null; this.mPreviousMessage = null; @@ -701,19 +580,22 @@ public class Message extends AbstractEntity { return type == TYPE_FILE || type == TYPE_IMAGE; } - public boolean hasFileOnRemoteHost() { - return isFileOrImage() && getFileParams().url != null; - } + public boolean hasFileAttached() { + return isFileOrImage() || isHttpUploaded() || (null != fileParams && null != fileParams.getPath()); + } - public boolean needsUploading() { - return isFileOrImage() && getFileParams().url == null; + /* + @TODO better + */ + public boolean hasFileOnRemoteHost() { + return hasFileAttached() && null != getFileParams() && getFileParams().isRemoteAvailable(); } - public class FileParams { - public URL url; - public long size = 0; - public int width = 0; - public int height = 0; + /* + @TODO better + */ + public boolean needsUploading() { + return hasFileAttached() && getFileParams().getFileStatus() == FileStatus.NEEDS_UPLOAD; } public void setFingerprint(String fingerprint) { @@ -768,6 +650,14 @@ public class Message extends AbstractEntity { this.httpUploaded = httpUploaded; } + public FileParams getFileParams() { + return this.fileParams; + } + + public void setFileParams(FileParams params) { + this.fileParams = params; + } + private static int getCleanedEncryption(int encryption) { if (encryption == ENCRYPTION_DECRYPTED || encryption == ENCRYPTION_DECRYPTION_FAILED) { return ENCRYPTION_PGP; diff --git a/src/main/java/de/thedevstack/conversationsplus/entities/RemoteFile.java b/src/main/java/de/thedevstack/conversationsplus/entities/RemoteFile.java deleted file mode 100644 index 76c1dbc9..00000000 --- a/src/main/java/de/thedevstack/conversationsplus/entities/RemoteFile.java +++ /dev/null @@ -1,37 +0,0 @@ -package de.thedevstack.conversationsplus.entities; - -import android.support.annotation.NonNull; - -import java.io.Serializable; - -/** - * Created by steckbrief on 22.08.2016. - */ -public class RemoteFile implements Serializable { - private static final long serialVersionUID = 34564871234564L; - private final String path; - - public RemoteFile(@NonNull String path) { - this.path = path; - } - - public String getPath() { - return path; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - - RemoteFile that = (RemoteFile) o; - - return path.equals(that.path); - - } - - @Override - public int hashCode() { - return path.hashCode(); - } -} diff --git a/src/main/java/de/thedevstack/conversationsplus/enums/FileStatus.java b/src/main/java/de/thedevstack/conversationsplus/enums/FileStatus.java new file mode 100644 index 00000000..b6a4ef9a --- /dev/null +++ b/src/main/java/de/thedevstack/conversationsplus/enums/FileStatus.java @@ -0,0 +1,17 @@ +package de.thedevstack.conversationsplus.enums; + +/** + * Created by steckbrief on 23.08.2016. + */ +public enum FileStatus { + NOT_DOWNLOADED, + DOWNLOADED, + DOWNLOAD_FAILED, + DELETED, + DELETE_FAILED, + UPLOADED, + NEEDS_UPLOAD, + UNDEFINED, + NEEDS_DOWNLOAD, + DELETING; +} diff --git a/src/main/java/de/thedevstack/conversationsplus/generator/MessageGenerator.java b/src/main/java/de/thedevstack/conversationsplus/generator/MessageGenerator.java index 5a1d6f3f..678f2c75 100644 --- a/src/main/java/de/thedevstack/conversationsplus/generator/MessageGenerator.java +++ b/src/main/java/de/thedevstack/conversationsplus/generator/MessageGenerator.java @@ -13,6 +13,7 @@ import de.thedevstack.conversationsplus.ConversationsPlusPreferences; import de.thedevstack.conversationsplus.crypto.axolotl.XmppAxolotlMessage; import de.thedevstack.conversationsplus.entities.Account; import de.thedevstack.conversationsplus.entities.Conversation; +import de.thedevstack.conversationsplus.entities.FileParams; import de.thedevstack.conversationsplus.entities.Message; import de.thedevstack.conversationsplus.xml.Element; import de.thedevstack.conversationsplus.xmpp.chatstate.ChatState; @@ -70,13 +71,13 @@ public class MessageGenerator extends AbstractGenerator { return packet; } - public static void addXhtmlImImage(MessagePacket packet, Message.FileParams params) { + public static void addXhtmlImImage(MessagePacket packet, FileParams params) { Element html = packet.addChild("html", "http://jabber.org/protocol/xhtml-im"); Element body = html.addChild("body", "http://www.w3.org/1999/xhtml"); Element img = body.addChild("img"); - img.setAttribute("src", params.url.toString()); - img.setAttribute("height", params.height); - img.setAttribute("width", params.width); + img.setAttribute("src", params.getUrl()); + img.setAttribute("height", params.getHeight()); + img.setAttribute("width", params.getWidth()); } public static void addMessageHints(MessagePacket packet) { @@ -94,12 +95,7 @@ public class MessageGenerator extends AbstractGenerator { MessagePacket packet = preparePacket(message); addMessageHints(packet); try { - String content; - if (message.hasFileOnRemoteHost()) { - content = message.getFileParams().url.toString(); - } else { - content = message.getBody(); - } + String content = message.getBody(); packet.setBody(otrSession.transformSending(content)[0]); return packet; } catch (OtrException e) { @@ -111,14 +107,14 @@ public class MessageGenerator extends AbstractGenerator { MessagePacket packet = preparePacket(message); String content; if (message.hasFileOnRemoteHost()) { - Message.FileParams fileParams = message.getFileParams(); - content = fileParams.url.toString(); + FileParams fileParams = message.getFileParams(); + content = message.getBody(); if (message.isHttpUploaded()) { packet.addChild(new HttpUploadHint()); } packet.addChild("x","jabber:x:oob").addChild("url").setContent(content); - if (fileParams.width > 0 && fileParams.height > 0) { - addXhtmlImImage(packet,fileParams); + if (fileParams.getWidth() > 0 && fileParams.getHeight() > 0) { + addXhtmlImImage(packet, fileParams); } } else { content = message.getBody(); diff --git a/src/main/java/de/thedevstack/conversationsplus/http/HttpConnectionManager.java b/src/main/java/de/thedevstack/conversationsplus/http/HttpConnectionManager.java index 529d12c4..686587c7 100644 --- a/src/main/java/de/thedevstack/conversationsplus/http/HttpConnectionManager.java +++ b/src/main/java/de/thedevstack/conversationsplus/http/HttpConnectionManager.java @@ -32,7 +32,6 @@ public class HttpConnectionManager extends AbstractConnectionManager { } private List downloadConnections = new CopyOnWriteArrayList<>(); - private List uploadConnections = new CopyOnWriteArrayList<>(); public static HttpDownloadConnection createNewDownloadConnection(Message message) { return createNewDownloadConnection(message, false); @@ -49,10 +48,6 @@ public class HttpConnectionManager extends AbstractConnectionManager { this.downloadConnections.remove(connection); } - public void finishUploadConnection(HttpUploadConnection httpUploadConnection) { - this.uploadConnections.remove(httpUploadConnection); - } - public static void setupTrustManager(final HttpsURLConnection connection, final boolean interactive) { final X509TrustManager trustManager; final HostnameVerifier hostnameVerifier; diff --git a/src/main/java/de/thedevstack/conversationsplus/http/HttpDownloadConnection.java b/src/main/java/de/thedevstack/conversationsplus/http/HttpDownloadConnection.java index 3facc14a..d9fc9584 100644 --- a/src/main/java/de/thedevstack/conversationsplus/http/HttpDownloadConnection.java +++ b/src/main/java/de/thedevstack/conversationsplus/http/HttpDownloadConnection.java @@ -18,6 +18,7 @@ import javax.net.ssl.SSLHandshakeException; import de.thedevstack.android.logcat.Logging; import de.thedevstack.conversationsplus.ConversationsPlusApplication; import de.thedevstack.conversationsplus.ConversationsPlusPreferences; +import de.thedevstack.conversationsplus.enums.FileStatus; import de.thedevstack.conversationsplus.exceptions.RemoteFileNotFoundException; import de.thedevstack.conversationsplus.utils.MessageUtil; import de.thedevstack.conversationsplus.utils.StreamUtil; @@ -74,11 +75,7 @@ public class HttpDownloadConnection implements Transferable { this.message = message; this.message.setTransferable(this); try { - if (message.hasFileOnRemoteHost()) { - mUrl = message.getFileParams().url; - } else { - mUrl = new URL(message.getBody()); - } + mUrl = new URL(message.getFileParams().getUrl()); final String sUrlFilename = mUrl.getPath().substring(mUrl.getPath().lastIndexOf('/')).toLowerCase(); final String lastPart = FileUtils.getLastExtension(sUrlFilename); @@ -131,6 +128,7 @@ public class HttpDownloadConnection implements Transferable { private void finish() { FileBackend.updateMediaScanner(file, mXmppConnectionService); message.setTransferable(null); + MessageUtil.setAndSaveFileStatus(this.message, FileStatus.DOWNLOADED); mHttpConnectionManager.finishConnection(this); if (message.getEncryption() == Message.ENCRYPTION_PGP) { message.getConversation().getAccount().getPgpDecryptionService().add(message); @@ -176,7 +174,7 @@ public class HttpDownloadConnection implements Transferable { HttpDownloadConnection.this.mXmppConnectionService.getNotificationService().push(message); return; } catch (RemoteFileNotFoundException e) { - message.setNoDownloadable(); + message.setNoDownloadable(); // TODO Set remote file status to not-available cancel(); return; } catch (IOException e) { diff --git a/src/main/java/de/thedevstack/conversationsplus/http/HttpUploadConnection.java b/src/main/java/de/thedevstack/conversationsplus/http/HttpUploadConnection.java deleted file mode 100644 index 3c49d083..00000000 --- a/src/main/java/de/thedevstack/conversationsplus/http/HttpUploadConnection.java +++ /dev/null @@ -1,243 +0,0 @@ -package de.thedevstack.conversationsplus.http; - -import android.app.PendingIntent; -import android.os.PowerManager; -import android.util.Pair; - -import java.io.FileNotFoundException; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.net.HttpURLConnection; -import java.net.MalformedURLException; -import java.net.URL; -import java.util.Scanner; - -import javax.net.ssl.HttpsURLConnection; - -import de.thedevstack.android.logcat.Logging; -import de.thedevstack.conversationsplus.ConversationsPlusApplication; -import de.thedevstack.conversationsplus.utils.MessageUtil; -import de.thedevstack.conversationsplus.utils.StreamUtil; -import de.thedevstack.conversationsplus.Config; -import de.thedevstack.conversationsplus.entities.Account; -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.services.AbstractConnectionManager; -import de.thedevstack.conversationsplus.services.XmppConnectionService; -import de.thedevstack.conversationsplus.ui.UiCallback; -import de.thedevstack.conversationsplus.utils.CryptoHelper; -import de.thedevstack.conversationsplus.utils.XmppConnectionServiceAccessor; -import de.thedevstack.conversationsplus.xmpp.OnIqPacketReceived; -import de.thedevstack.conversationsplus.xmpp.exceptions.XmppException; -import de.thedevstack.conversationsplus.xmpp.filetransfer.http.upload.HttpUpload; -import de.thedevstack.conversationsplus.xmpp.filetransfer.http.upload.HttpUploadRequestSlotPacketGenerator; -import de.thedevstack.conversationsplus.xmpp.filetransfer.http.upload.HttpUploadSlot; -import de.thedevstack.conversationsplus.xmpp.filetransfer.http.upload.SlotPacketParser; -import de.thedevstack.conversationsplus.xmpp.jid.Jid; -import de.thedevstack.conversationsplus.xmpp.stanzas.IqPacket; - -@Deprecated -public class HttpUploadConnection implements Transferable { - - private HttpConnectionManager mHttpConnectionManager; - private XmppConnectionService mXmppConnectionService; - - private boolean canceled = false; - private boolean delayed = false; - private Account account; - private DownloadableFile file; - private Message message; - private String mime; - private URL mGetUrl; - private URL mPutUrl; - - private byte[] key = null; - - private long transmitted = 0; - - private InputStream mFileInputStream; - - public HttpUploadConnection(HttpConnectionManager httpConnectionManager) { - this.mHttpConnectionManager = httpConnectionManager; - this.mXmppConnectionService = XmppConnectionServiceAccessor.xmppConnectionService; - } - - @Override - public boolean start() { - return false; - } - - @Override - public int getStatus() { - return STATUS_UPLOADING; - } - - @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; - } - - private void fail() { - mHttpConnectionManager.finishUploadConnection(this); - message.setTransferable(null); - MessageUtil.markMessage(message, Message.STATUS_SEND_FAILED); - StreamUtil.close(mFileInputStream); - } - - public void init(Message message, boolean delay) { - this.message = message; - this.message.setHttpUploaded(true); - this.message.setNoDownloadable(); - this.account = message.getConversation().getAccount(); - this.file = FileBackend.getFile(message, false); - this.mime = this.file.getMimeType(); - this.delayed = delay; - 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.file.setKeyAndIv(this.key); - } - Pair pair; - try { - pair = AbstractConnectionManager.createInputStream(file, true); - } catch (FileNotFoundException e) { - fail(); - return; - } - this.file.setExpectedSize(pair.second); - this.mFileInputStream = pair.first; - Jid host = account.getXmppConnection().findDiscoItemByFeature(HttpUpload.NAMESPACE); - IqPacket request = HttpUploadRequestSlotPacketGenerator.generate(host, file.getName(), file.getSize(), mime); - mXmppConnectionService.sendIqPacket(account, request, new OnIqPacketReceived() { - @Override - public void onIqPacketReceived(Account account, IqPacket packet) { - try { - HttpUploadSlot slot = SlotPacketParser.parseGetAndPutUrl(packet); - mGetUrl = new URL(slot.getGetUrl()); - mPutUrl = new URL(slot.getPutUrl()); - if (!canceled) { - new Thread(new FileUploader()).start(); - } - } catch (XmppException e) { - Logging.e("httpupload", e.getMessage()); - fail(); - } catch (MalformedURLException e) { - Logging.e("httpupload", "malformed url retrieved from slot", e); - fail(); - } - } - }); - message.setTransferable(this); - MessageUtil.markMessage(message, Message.STATUS_UNSEND); - } - - private class FileUploader implements Runnable { - - @Override - public void run() { - this.upload(); - } - - private void upload() { - OutputStream os = null; - InputStream errorStream = null; - HttpURLConnection connection = null; - PowerManager.WakeLock wakeLock = ConversationsPlusApplication.createPartialWakeLock("http_upload_"+message.getUuid()); - try { - wakeLock.acquire(); - Logging.d(Config.LOGTAG, "uploading to " + mPutUrl.toString()); - connection = (HttpURLConnection) mPutUrl.openConnection(); - - if (connection instanceof HttpsURLConnection) { - mHttpConnectionManager.setupTrustManager((HttpsURLConnection) connection, true); - } - connection.setRequestMethod("PUT"); - connection.setFixedLengthStreamingMode((int) file.getExpectedSize()); - connection.setRequestProperty("Content-Type", mime == null ? "application/octet-stream" : mime); - connection.setRequestProperty("User-Agent", ConversationsPlusApplication.getNameAndVersion()); - connection.setDoOutput(true); - connection.connect(); - os = connection.getOutputStream(); - transmitted = 0; - int count = -1; - byte[] buffer = new byte[4096]; - while (((count = mFileInputStream.read(buffer)) != -1) && !canceled) { - transmitted += count; - os.write(buffer, 0, count); - mXmppConnectionService.updateConversationUi(); - } - os.flush(); - os.close(); - mFileInputStream.close(); - int code = connection.getResponseCode(); - if (code == 200 || code == 201) { - Logging.d(Config.LOGTAG, "finished uploading file"); - if (key != null) { - mGetUrl = new URL(mGetUrl.toString() + "#" + CryptoHelper.bytesToHex(key)); - } - MessageUtil.updateFileParams(message, mGetUrl); - FileBackend.updateMediaScanner(file, mXmppConnectionService); - message.setTransferable(null); - message.setCounterpart(message.getConversation().getJid().toBareJid()); - if (message.getEncryption() == Message.ENCRYPTION_DECRYPTED) { - mXmppConnectionService.getPgpEngine().encrypt(message, new UiCallback() { - @Override - public void success(Message message) { - mXmppConnectionService.resendMessage(message,delayed); - } - - @Override - public void error(int errorCode, Message object) { - fail(); - } - - @Override - public void userInputRequried(PendingIntent pi, Message object) { - fail(); - } - }); - } else { - mXmppConnectionService.resendMessage(message, delayed); - } - } else { - errorStream = connection.getErrorStream(); - Logging.e("httpupload", "file upload failed: http code (" + code + ") " + new Scanner(errorStream).useDelimiter("\\A").next()); - 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()); - fail(); - } finally { - StreamUtil.close(os); - StreamUtil.close(errorStream); - if (connection != null) { - connection.disconnect(); - } - wakeLock.release(); - } - } - } -} diff --git a/src/main/java/de/thedevstack/conversationsplus/parser/MessageParser.java b/src/main/java/de/thedevstack/conversationsplus/parser/MessageParser.java index 2f1701df..b02677a8 100644 --- a/src/main/java/de/thedevstack/conversationsplus/parser/MessageParser.java +++ b/src/main/java/de/thedevstack/conversationsplus/parser/MessageParser.java @@ -3,6 +3,8 @@ package de.thedevstack.conversationsplus.parser; import android.util.Log; import android.util.Pair; +import de.thedevstack.conversationsplus.entities.FileParams; +import de.thedevstack.conversationsplus.enums.FileStatus; import de.thedevstack.conversationsplus.utils.MessageUtil; import de.thedevstack.conversationsplus.xmpp.httpuploadim.HttpUploadHint; import de.tzur.conversations.Settings; @@ -10,7 +12,6 @@ import de.tzur.conversations.Settings; import net.java.otr4j.session.Session; import net.java.otr4j.session.SessionStatus; -import java.net.URL; import java.util.ArrayList; import java.util.Set; @@ -472,6 +473,10 @@ public class MessageParser extends AbstractParser implements && message.treatAsDownloadable() != Message.Decision.NEVER && ConversationsPlusPreferences.autoAcceptFileSize() > 0 && (message.isHttpUploaded() || ConversationsPlusPreferences.autoDownloadFileLink())) { + FileParams fileParams = new FileParams(); + fileParams.setFileStatus(FileStatus.NEEDS_DOWNLOAD); + fileParams.setUrl(message.getBody()); + message.setFileParams(fileParams); HttpConnectionManager.createNewDownloadConnection(message); } else { if (query == null) { diff --git a/src/main/java/de/thedevstack/conversationsplus/persistance/CursorHelper.java b/src/main/java/de/thedevstack/conversationsplus/persistance/CursorHelper.java deleted file mode 100644 index 7e3fdab0..00000000 --- a/src/main/java/de/thedevstack/conversationsplus/persistance/CursorHelper.java +++ /dev/null @@ -1,203 +0,0 @@ -package de.thedevstack.conversationsplus.persistance; - -import android.database.Cursor; - -/** - * Created by steckbrief on 15.04.2016. - */ -public abstract class CursorHelper { - - static double getDouble(Cursor cursor, String columnName) { - if (null == cursor) { - return Double.MIN_VALUE; - } - int columnIndex = getColumnIndex(cursor, columnName); - if (columnIndex < 0) { - return Double.MIN_VALUE; - } - return getDouble(cursor, columnIndex); - } - - static String getString(Cursor cursor, String columnName) { - if (null == cursor) { - return null; - } - int columnIndex = getColumnIndex(cursor, columnName); - if (columnIndex < 0) { - return null; - } - return getString(cursor, columnIndex); - } - - static float getFloat(Cursor cursor, String columnName) { - if (null == cursor) { - return Float.MIN_VALUE; - } - int columnIndex = getColumnIndex(cursor, columnName); - if (columnIndex < 0) { - return Float.MIN_VALUE; - } - return getFloat(cursor, columnIndex); - } - - static int getInt(Cursor cursor, String columnName) { - if (null == cursor) { - return Integer.MIN_VALUE; - } - int columnIndex = getColumnIndex(cursor, columnName); - if (columnIndex < 0) { - return Integer.MIN_VALUE; - } - return getInt(cursor, columnIndex); - } - - static long getLong(Cursor cursor, String columnName) { - if (null == cursor) { - return Long.MIN_VALUE; - } - int columnIndex = getColumnIndex(cursor, columnName); - if (columnIndex < 0) { - return Long.MIN_VALUE; - } - return getLong(cursor, columnIndex); - } - - static int getShort(Cursor cursor, String columnName) { - if (null == cursor) { - return Short.MIN_VALUE; - } - int columnIndex = getColumnIndex(cursor, columnName); - if (columnIndex < 0) { - return Short.MIN_VALUE; - } - return getShort(cursor, columnIndex); - } - - static byte[] getBlob(Cursor cursor, String columnName) { - if (null == cursor) { - return null; - } - int columnIndex = getColumnIndex(cursor, columnName); - if (columnIndex < 0) { - return null; - } - return getBlob(cursor, columnIndex); - } - - /** - * Returns the zero-based index for the given column name, or -1 if the column doesn't exist. - * If you expect the column to exist use {@link Cursor#getColumnIndexOrThrow(String)} instead, which - * will make the error more clear. - * - * @param columnName the name of the target column. - * @return the zero-based column index for the given column name, or -1 if - * the column name does not exist. - * @see Cursor#getColumnIndexOrThrow(String) - */ - static int getColumnIndex(Cursor cursor, String columnName) { - return cursor.getColumnIndex(columnName); - } - - /** - * Returns the value of the requested column as a byte array. - * - *

The result and whether this method throws an exception when the - * column value is null or the column type is not a blob type is - * implementation-defined. - * - * @param columnIndex the zero-based index of the target column. - * @return the value of that column as a byte array. - */ - static byte[] getBlob(Cursor cursor, int columnIndex) { - return cursor.getBlob(columnIndex); - } - - /** - * Returns the value of the requested column as a String. - * - *

The result and whether this method throws an exception when the - * column value is null or the column type is not a string type is - * implementation-defined. - * - * @param columnIndex the zero-based index of the target column. - * @return the value of that column as a String. - */ - static String getString(Cursor cursor, int columnIndex) { - return cursor.getString(columnIndex); - } - - /** - * Returns the value of the requested column as a short. - * - *

The result and whether this method throws an exception when the - * column value is null, the column type is not an integral type, or the - * integer value is outside the range [Short.MIN_VALUE, - * Short.MAX_VALUE] is implementation-defined. - * - * @param columnIndex the zero-based index of the target column. - * @return the value of that column as a short. - */ - static short getShort(Cursor cursor, int columnIndex) { - return cursor.getShort(columnIndex); - } - - /** - * Returns the value of the requested column as an int. - * - *

The result and whether this method throws an exception when the - * column value is null, the column type is not an integral type, or the - * integer value is outside the range [Integer.MIN_VALUE, - * Integer.MAX_VALUE] is implementation-defined. - * - * @param columnIndex the zero-based index of the target column. - * @return the value of that column as an int. - */ - static int getInt(Cursor cursor, int columnIndex) { - return cursor.getInt(columnIndex); - } - - /** - * Returns the value of the requested column as a long. - * - *

The result and whether this method throws an exception when the - * column value is null, the column type is not an integral type, or the - * integer value is outside the range [Long.MIN_VALUE, - * Long.MAX_VALUE] is implementation-defined. - * - * @param columnIndex the zero-based index of the target column. - * @return the value of that column as a long. - */ - static long getLong(Cursor cursor, int columnIndex) { - return cursor.getLong(columnIndex); - } - - /** - * Returns the value of the requested column as a float. - * - *

The result and whether this method throws an exception when the - * column value is null, the column type is not a floating-point type, or the - * floating-point value is not representable as a float value is - * implementation-defined. - * - * @param columnIndex the zero-based index of the target column. - * @return the value of that column as a float. - */ - static float getFloat(Cursor cursor, int columnIndex) { - return cursor.getFloat(columnIndex); - } - - /** - * Returns the value of the requested column as a double. - * - *

The result and whether this method throws an exception when the - * column value is null, the column type is not a floating-point type, or the - * floating-point value is not representable as a double value is - * implementation-defined. - * - * @param columnIndex the zero-based index of the target column. - * @return the value of that column as a double. - */ - static double getDouble(Cursor cursor, int columnIndex) { - return cursor.getDouble(columnIndex); - } -} diff --git a/src/main/java/de/thedevstack/conversationsplus/persistance/DatabaseBackend.java b/src/main/java/de/thedevstack/conversationsplus/persistance/DatabaseBackend.java index 7c2583ae..e721afbb 100644 --- a/src/main/java/de/thedevstack/conversationsplus/persistance/DatabaseBackend.java +++ b/src/main/java/de/thedevstack/conversationsplus/persistance/DatabaseBackend.java @@ -35,6 +35,7 @@ import org.json.JSONException; import de.thedevstack.android.logcat.Logging; import de.thedevstack.conversationsplus.Config; +import de.thedevstack.conversationsplus.ConversationsPlusApplication; import de.thedevstack.conversationsplus.crypto.axolotl.AxolotlServiceImpl; import de.thedevstack.conversationsplus.crypto.axolotl.SQLiteAxolotlStore; import de.thedevstack.conversationsplus.crypto.axolotl.XmppAxolotlSession; @@ -44,6 +45,8 @@ import de.thedevstack.conversationsplus.entities.Conversation; import de.thedevstack.conversationsplus.entities.Message; import de.thedevstack.conversationsplus.entities.Roster; import de.thedevstack.conversationsplus.entities.ServiceDiscoveryResult; +import de.thedevstack.conversationsplus.persistance.db.access.CursorHelper; +import de.thedevstack.conversationsplus.persistance.db.access.MessageDatabaseAccess; import de.thedevstack.conversationsplus.utils.SimpleCryptoUtil; import de.thedevstack.conversationsplus.xmpp.jid.InvalidJidException; import de.thedevstack.conversationsplus.xmpp.jid.Jid; @@ -55,7 +58,7 @@ public class DatabaseBackend extends SQLiteOpenHelper { private static final String DATABASE_NAME = "history"; private static final int DATABASE_VERSION = 25; private static final int C_TO_CPLUS_VERSION_OFFSET = 1000; - private static final int CPLUS_DATABASE_VERSION = 2; + private static final int CPLUS_DATABASE_VERSION = 3; private static final int CPLUS_DATABASE_VERSION_MULTIPLIER = 100; private static final int PHYSICAL_DATABASE_VERSION = DATABASE_VERSION + C_TO_CPLUS_VERSION_OFFSET + (CPLUS_DATABASE_VERSION * CPLUS_DATABASE_VERSION_MULTIPLIER); @@ -191,18 +194,14 @@ public class DatabaseBackend extends SQLiteOpenHelper { db.execSQL(CREATE_SIGNED_PREKEYS_STATEMENT); db.execSQL(CREATE_IDENTITIES_STATEMENT); - // Create Conversations+ related tables - db.execSQL(MessageDatabaseAccess.TABLE_ADDITIONAL_PARAMETERS_CREATE_V0); + MessageDatabaseAccess.create(db); } protected void onUpgradeCPlusDatabase(SQLiteDatabase db, int oldVersion, int newVersion) { Logging.d("db.upgrade.cplus", "Updating Conversations+ database from version '" + oldVersion + "' to '" + newVersion + "'"); if (oldVersion < newVersion) { if (oldVersion < 1 && newVersion >= 1) { - Logging.d("db.upgrade.cplus", "Creating additional parameters table for messages."); - db.execSQL(MessageDatabaseAccess.TABLE_ADDITIONAL_PARAMETERS_CREATE_V0); - db.execSQL("INSERT INTO " + MessageDatabaseAccess.TABLE_NAME_ADDITIONAL_PARAMETERS + "(" + MessageDatabaseAccess.COLUMN_NAME_MSG_PARAMS_MSGUUID + ") " - + " SELECT " + Message.UUID + " FROM " + Message.TABLENAME); + MessageDatabaseAccess.upgrade(db, oldVersion, newVersion); } if (oldVersion < 2 && newVersion >= 2) { Logging.d("db.upgrade.cplus", "Encrypt all passwords for the first time"); @@ -217,6 +216,10 @@ public class DatabaseBackend extends SQLiteOpenHelper { } cursor.close(); } + + if (oldVersion < 3 && newVersion >= 3) { + MessageDatabaseAccess.upgrade(db, oldVersion, newVersion); + } } } @@ -434,6 +437,10 @@ public class DatabaseBackend extends SQLiteOpenHelper { } } + public static synchronized DatabaseBackend getInstance() { + return getInstance(ConversationsPlusApplication.getAppContext()); + } + public static synchronized DatabaseBackend getInstance(Context context) { if (instance == null) { instance = new DatabaseBackend(context); @@ -657,15 +664,7 @@ public class DatabaseBackend extends SQLiteOpenHelper { } public void updateMessage(Message message) { - Logging.d("db.msg.update", "Updating message with uuid '" + message.getUuid() + "', isRead: " + message.isRead()); - SQLiteDatabase db = this.getWritableDatabase(); - String[] args = {message.getUuid()}; - db.beginTransaction(); - db.update(Message.TABLENAME, message.getContentValues(), Message.UUID - + "=?", args); - db.update(MessageDatabaseAccess.TABLE_NAME_ADDITIONAL_PARAMETERS, MessageDatabaseAccess.getAdditionalParametersContentValues(message), MessageDatabaseAccess.COLUMN_NAME_MSG_PARAMS_MSGUUID + "=?", args); - db.setTransactionSuccessful(); - db.endTransaction(); + this.updateMessage(message, message.getUuid()); } public void updateMessage(Message message, String uuid) { @@ -676,7 +675,7 @@ public class DatabaseBackend extends SQLiteOpenHelper { db.beginTransaction(); db.update(Message.TABLENAME, message.getContentValues(), Message.UUID + "=?", args); - db.update(MessageDatabaseAccess.TABLE_NAME_ADDITIONAL_PARAMETERS, MessageDatabaseAccess.getAdditionalParametersContentValues(message), MessageDatabaseAccess.COLUMN_NAME_MSG_PARAMS_MSGUUID + "=?", args); + MessageDatabaseAccess.updateMessageParameters(db, message, uuid); db.setTransactionSuccessful(); db.endTransaction(); } diff --git a/src/main/java/de/thedevstack/conversationsplus/persistance/FileBackend.java b/src/main/java/de/thedevstack/conversationsplus/persistance/FileBackend.java index e5597c3c..a35ed043 100644 --- a/src/main/java/de/thedevstack/conversationsplus/persistance/FileBackend.java +++ b/src/main/java/de/thedevstack/conversationsplus/persistance/FileBackend.java @@ -20,6 +20,7 @@ import java.util.Locale; import de.thedevstack.android.logcat.Logging; import de.thedevstack.conversationsplus.ConversationsPlusApplication; import de.thedevstack.conversationsplus.ConversationsPlusPreferences; +import de.thedevstack.conversationsplus.entities.FileParams; import de.thedevstack.conversationsplus.exceptions.FileCopyException; import de.thedevstack.conversationsplus.persistance.observers.FileDeletionObserver; import de.thedevstack.conversationsplus.utils.StreamUtil; @@ -106,31 +107,55 @@ public class FileBackend { } public static DownloadableFile getFile(Message message, boolean decrypted) { - final boolean encrypted = !decrypted - && (message.getEncryption() == Message.ENCRYPTION_PGP - || message.getEncryption() == Message.ENCRYPTION_DECRYPTED); - final DownloadableFile file; - String path = message.getRelativeFilePath(); - if (path == null) { - path = message.getUuid(); - } - if (path.startsWith("/")) { - file = new DownloadableFile(path); - } else { - String mime = message.getMimeType(); - if (mime != null && mime.startsWith("image")) { - file = new DownloadableFile(getConversationsImageDirectory() + path); - } else { - file = new DownloadableFile(getConversationsFileDirectory() + path); - } - } - if (encrypted) { - return new DownloadableFile(getConversationsFileDirectory() + file.getName() + ".pgp"); - } else { - return file; - } + return new DownloadableFile(getFilePath(message, decrypted)); } + protected static String getFilePath(Message message, String extension) { + String path = FileBackend.getFilePath(message, true); + if (!path.endsWith(extension)) { + path += "." + extension; + } + + return path; + } + + protected static String getFilePath(Message message, Uri uri) { + String mime = ConversationsPlusApplication.getInstance().getContentResolver().getType(uri); + String extension = MimeTypeMap.getSingleton().getExtensionFromMimeType(mime); + + return getFilePath(message, extension); + } + + protected static String getFilePath(Message message, boolean decrypted) { + final boolean encrypted = !decrypted + && (message.getEncryption() == Message.ENCRYPTION_PGP + || message.getEncryption() == Message.ENCRYPTION_DECRYPTED); + FileParams fileParams = message.getFileParams(); + if (null == fileParams) { + fileParams = new FileParams(); + message.setFileParams(fileParams); + } + String path = fileParams.getPath(); + + if (null == path) { // File does not yet exist + path = message.getUuid(); + String mime = message.getMimeType(); + if (mime != null && mime.startsWith("image")) { // TODO: Check if this can be determined in a better way + path = getConversationsImageDirectory() + path; + } else { + path = getConversationsFileDirectory() + path; + } + String extension = MimeTypeMap.getSingleton().getExtensionFromMimeType(mime); + path += "." + extension; + + fileParams.setPath(path); + } + if (encrypted) { + path += ".pgp"; + } + return path; + } + public static String getConversationsFileDirectory() { return Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator + ConversationsPlusPreferences.fileTransferFolder() + File.separator; } @@ -175,14 +200,16 @@ public class FileBackend { public static void copyFileToPrivateStorage(Message message, Uri uri) throws FileCopyException { Log.d(Config.LOGTAG, "copy " + uri.toString() + " to private storage"); - String mime = ConversationsPlusApplication.getInstance().getContentResolver().getType(uri); - String extension = MimeTypeMap.getSingleton().getExtensionFromMimeType(mime); - message.setRelativeFilePath(message.getUuid() + "." + extension); + String path = getFilePath(message, uri); + message.getFileParams().setPath(path); + message.setRelativeFilePath(path); // TODO: Remove copyFileToPrivateStorage(getFile(message), uri); } public static DownloadableFile compressImageAndCopyToPrivateStorage(Message message, Bitmap scaledBitmap) throws FileCopyException { - message.setRelativeFilePath(FileBackend.getPrivateImageDirectoryPath() + message.getUuid() + ".jpg"); + String path = getFilePath(message, "jpg"); + message.getFileParams().setPath(path); + message.setRelativeFilePath(path); // TODO: Remove DownloadableFile file = getFile(message); file.getParentFile().mkdirs(); OutputStream os = null; diff --git a/src/main/java/de/thedevstack/conversationsplus/persistance/MessageDatabaseAccess.java b/src/main/java/de/thedevstack/conversationsplus/persistance/MessageDatabaseAccess.java deleted file mode 100644 index 7776174d..00000000 --- a/src/main/java/de/thedevstack/conversationsplus/persistance/MessageDatabaseAccess.java +++ /dev/null @@ -1,59 +0,0 @@ -package de.thedevstack.conversationsplus.persistance; - -import android.content.ContentValues; -import android.database.Cursor; -import android.database.sqlite.SQLiteDatabase; - -import de.thedevstack.android.logcat.Logging; -import de.thedevstack.conversationsplus.entities.Message; -import de.thedevstack.conversationsplus.xmpp.jid.InvalidJidException; -import de.thedevstack.conversationsplus.xmpp.jid.Jid; - -/** - * Created by steckbrief on 15.04.2016. - */ -public class MessageDatabaseAccess { - static final String TABLE_NAME_ADDITIONAL_PARAMETERS = "message_parameters"; - static final String COLUMN_NAME_MSG_PARAMS_HTTPUPLOAD = "httpupload"; - static final String COLUMN_NAME_MSG_PARAMS_MSGUUID = "message_uuid"; - static final String COLUMN_NAME_MSG_PARAMS_TREATASDOWNLOADABLE_DECISION = "treatasdownloadable_decision"; - - static final String TABLE_ADDITIONAL_PARAMETERS_CREATE_V0 = "CREATE TABLE " + MessageDatabaseAccess.TABLE_NAME_ADDITIONAL_PARAMETERS + " (" - + MessageDatabaseAccess.COLUMN_NAME_MSG_PARAMS_MSGUUID + " TEXT, " - + MessageDatabaseAccess.COLUMN_NAME_MSG_PARAMS_HTTPUPLOAD + " INTEGER DEFAULT 0, " - + MessageDatabaseAccess.COLUMN_NAME_MSG_PARAMS_TREATASDOWNLOADABLE_DECISION + " TEXT DEFAULT 'NOT_DECIDED', " - + "FOREIGN KEY(" + MessageDatabaseAccess.COLUMN_NAME_MSG_PARAMS_MSGUUID + ") REFERENCES " + Message.TABLENAME + "(" + Message.UUID + ") ON DELETE CASCADE)"; - - static ContentValues getAdditionalParametersContentValues(Message message) { - ContentValues additionalParameters = new ContentValues(); - additionalParameters.put(MessageDatabaseAccess.COLUMN_NAME_MSG_PARAMS_MSGUUID, message.getUuid()); - additionalParameters.put(MessageDatabaseAccess.COLUMN_NAME_MSG_PARAMS_HTTPUPLOAD, message.isHttpUploaded() ? 1 : 0); - additionalParameters.put(MessageDatabaseAccess.COLUMN_NAME_MSG_PARAMS_TREATASDOWNLOADABLE_DECISION, message.treatAsDownloadable().name()); - - return additionalParameters; - } - - static void populateMessageParametersFromCursor(Cursor cursor, Message message) { - boolean isHttpUploaded = CursorHelper.getInt(cursor, COLUMN_NAME_MSG_PARAMS_HTTPUPLOAD) == 1; - message.setHttpUploaded(isHttpUploaded); - String downloadable = CursorHelper.getString(cursor, COLUMN_NAME_MSG_PARAMS_TREATASDOWNLOADABLE_DECISION); - Message.Decision treatAsDownloadable = Message.Decision.NOT_DECIDED; - try { - treatAsDownloadable = Message.Decision.valueOf(downloadable); - } catch (IllegalArgumentException e) { - // Should only happen if the database is corrupted, but to be on the save side catch it here - Logging.e("db.msg", "Unknown Decision for treatAsDownloadable found: '" + downloadable + "'"); - } - message.setTreatAsDownloadable(treatAsDownloadable); - } - - static void populateMessageParameters(SQLiteDatabase db, Message message) { - Cursor paramsCursor = db.query(MessageDatabaseAccess.TABLE_NAME_ADDITIONAL_PARAMETERS, - null, MessageDatabaseAccess.COLUMN_NAME_MSG_PARAMS_MSGUUID + "=?", - new String[] {message.getUuid()}, - null, null, null, null); - paramsCursor.moveToNext(); - MessageDatabaseAccess.populateMessageParametersFromCursor(paramsCursor, message); - paramsCursor.close(); - } -} diff --git a/src/main/java/de/thedevstack/conversationsplus/persistance/db/access/AbstractDatabaseAccess.java b/src/main/java/de/thedevstack/conversationsplus/persistance/db/access/AbstractDatabaseAccess.java new file mode 100644 index 00000000..407859c7 --- /dev/null +++ b/src/main/java/de/thedevstack/conversationsplus/persistance/db/access/AbstractDatabaseAccess.java @@ -0,0 +1,21 @@ +package de.thedevstack.conversationsplus.persistance.db.access; + +import android.database.sqlite.SQLiteDatabase; + +/** + * Created by steckbrief on 24.08.2016. + */ +public abstract class AbstractDatabaseAccess { + static void executeAlterStatements(SQLiteDatabase db, String... alterStatements) { + for (String alterStatement : alterStatements) { + db.execSQL(alterStatement); + } + } + + static void addNewColumns(SQLiteDatabase db, String tableName, String... columnDefinitions) { + for (String columnDefinition : columnDefinitions) { + String alterStatement = "ALTER TABLE " + tableName + " ADD COLUMN " + columnDefinition; + db.execSQL(alterStatement); + } + } +} diff --git a/src/main/java/de/thedevstack/conversationsplus/persistance/db/access/CursorHelper.java b/src/main/java/de/thedevstack/conversationsplus/persistance/db/access/CursorHelper.java new file mode 100644 index 00000000..122e5160 --- /dev/null +++ b/src/main/java/de/thedevstack/conversationsplus/persistance/db/access/CursorHelper.java @@ -0,0 +1,203 @@ +package de.thedevstack.conversationsplus.persistance.db.access; + +import android.database.Cursor; + +/** + * Created by steckbrief on 15.04.2016. + */ +public abstract class CursorHelper { + + public static double getDouble(Cursor cursor, String columnName) { + if (null == cursor) { + return Double.MIN_VALUE; + } + int columnIndex = getColumnIndex(cursor, columnName); + if (columnIndex < 0) { + return Double.MIN_VALUE; + } + return getDouble(cursor, columnIndex); + } + + public static String getString(Cursor cursor, String columnName) { + if (null == cursor) { + return null; + } + int columnIndex = getColumnIndex(cursor, columnName); + if (columnIndex < 0) { + return null; + } + return getString(cursor, columnIndex); + } + + public static float getFloat(Cursor cursor, String columnName) { + if (null == cursor) { + return Float.MIN_VALUE; + } + int columnIndex = getColumnIndex(cursor, columnName); + if (columnIndex < 0) { + return Float.MIN_VALUE; + } + return getFloat(cursor, columnIndex); + } + + public static int getInt(Cursor cursor, String columnName) { + if (null == cursor) { + return Integer.MIN_VALUE; + } + int columnIndex = getColumnIndex(cursor, columnName); + if (columnIndex < 0) { + return Integer.MIN_VALUE; + } + return getInt(cursor, columnIndex); + } + + public static long getLong(Cursor cursor, String columnName) { + if (null == cursor) { + return Long.MIN_VALUE; + } + int columnIndex = getColumnIndex(cursor, columnName); + if (columnIndex < 0) { + return Long.MIN_VALUE; + } + return getLong(cursor, columnIndex); + } + + public static int getShort(Cursor cursor, String columnName) { + if (null == cursor) { + return Short.MIN_VALUE; + } + int columnIndex = getColumnIndex(cursor, columnName); + if (columnIndex < 0) { + return Short.MIN_VALUE; + } + return getShort(cursor, columnIndex); + } + + public static byte[] getBlob(Cursor cursor, String columnName) { + if (null == cursor) { + return null; + } + int columnIndex = getColumnIndex(cursor, columnName); + if (columnIndex < 0) { + return null; + } + return getBlob(cursor, columnIndex); + } + + /** + * Returns the zero-based index for the given column name, or -1 if the column doesn't exist. + * If you expect the column to exist use {@link Cursor#getColumnIndexOrThrow(String)} instead, which + * will make the error more clear. + * + * @param columnName the name of the target column. + * @return the zero-based column index for the given column name, or -1 if + * the column name does not exist. + * @see Cursor#getColumnIndexOrThrow(String) + */ + public static int getColumnIndex(Cursor cursor, String columnName) { + return cursor.getColumnIndex(columnName); + } + + /** + * Returns the value of the requested column as a byte array. + * + *

The result and whether this method throws an exception when the + * column value is null or the column type is not a blob type is + * implementation-defined. + * + * @param columnIndex the zero-based index of the target column. + * @return the value of that column as a byte array. + */ + public static byte[] getBlob(Cursor cursor, int columnIndex) { + return cursor.getBlob(columnIndex); + } + + /** + * Returns the value of the requested column as a String. + * + *

The result and whether this method throws an exception when the + * column value is null or the column type is not a string type is + * implementation-defined. + * + * @param columnIndex the zero-based index of the target column. + * @return the value of that column as a String. + */ + public static String getString(Cursor cursor, int columnIndex) { + return cursor.getString(columnIndex); + } + + /** + * Returns the value of the requested column as a short. + * + *

The result and whether this method throws an exception when the + * column value is null, the column type is not an integral type, or the + * integer value is outside the range [Short.MIN_VALUE, + * Short.MAX_VALUE] is implementation-defined. + * + * @param columnIndex the zero-based index of the target column. + * @return the value of that column as a short. + */ + public static short getShort(Cursor cursor, int columnIndex) { + return cursor.getShort(columnIndex); + } + + /** + * Returns the value of the requested column as an int. + * + *

The result and whether this method throws an exception when the + * column value is null, the column type is not an integral type, or the + * integer value is outside the range [Integer.MIN_VALUE, + * Integer.MAX_VALUE] is implementation-defined. + * + * @param columnIndex the zero-based index of the target column. + * @return the value of that column as an int. + */ + public static int getInt(Cursor cursor, int columnIndex) { + return cursor.getInt(columnIndex); + } + + /** + * Returns the value of the requested column as a long. + * + *

The result and whether this method throws an exception when the + * column value is null, the column type is not an integral type, or the + * integer value is outside the range [Long.MIN_VALUE, + * Long.MAX_VALUE] is implementation-defined. + * + * @param columnIndex the zero-based index of the target column. + * @return the value of that column as a long. + */ + public static long getLong(Cursor cursor, int columnIndex) { + return cursor.getLong(columnIndex); + } + + /** + * Returns the value of the requested column as a float. + * + *

The result and whether this method throws an exception when the + * column value is null, the column type is not a floating-point type, or the + * floating-point value is not representable as a float value is + * implementation-defined. + * + * @param columnIndex the zero-based index of the target column. + * @return the value of that column as a float. + */ + public static float getFloat(Cursor cursor, int columnIndex) { + return cursor.getFloat(columnIndex); + } + + /** + * Returns the value of the requested column as a double. + * + *

The result and whether this method throws an exception when the + * column value is null, the column type is not a floating-point type, or the + * floating-point value is not representable as a double value is + * implementation-defined. + * + * @param columnIndex the zero-based index of the target column. + * @return the value of that column as a double. + */ + public static double getDouble(Cursor cursor, int columnIndex) { + return cursor.getDouble(columnIndex); + } +} diff --git a/src/main/java/de/thedevstack/conversationsplus/persistance/db/access/MessageDatabaseAccess.java b/src/main/java/de/thedevstack/conversationsplus/persistance/db/access/MessageDatabaseAccess.java new file mode 100644 index 00000000..51ad99a4 --- /dev/null +++ b/src/main/java/de/thedevstack/conversationsplus/persistance/db/access/MessageDatabaseAccess.java @@ -0,0 +1,180 @@ +package de.thedevstack.conversationsplus.persistance.db.access; + +import android.content.ContentValues; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; + +import de.thedevstack.android.logcat.Logging; +import de.thedevstack.conversationsplus.entities.FileParams; +import de.thedevstack.conversationsplus.entities.Message; +import de.thedevstack.conversationsplus.enums.FileStatus; +import de.thedevstack.conversationsplus.persistance.db.migrations.FileParamsBodyToDatabaseFieldsMigration; + +/** + * + */ +public class MessageDatabaseAccess extends AbstractDatabaseAccess { + // since cplus db version 1 + public static final String TABLE_NAME_ADDITIONAL_PARAMETERS = "message_parameters"; + private static final String COLUMN_NAME_MSG_PARAMS_HTTPUPLOAD = "httpupload"; + private static final String COLUMN_NAME_MSG_PARAMS_MSGUUID = "message_uuid"; + private static final String COLUMN_NAME_MSG_PARAMS_TREATASDOWNLOADABLE_DECISION = "treatasdownloadable_decision"; + // since cplus db version 3 + private static final String COLUMN_NAME_MSG_PARAMS_FILE_SIZE = "file_size"; + private static final String COLUMN_NAME_MSG_PARAMS_FILE_TYPE = "file_type"; + private static final String COLUMN_NAME_MSG_PARAMS_FILE_WIDTH = "file_width"; + private static final String COLUMN_NAME_MSG_PARAMS_FILE_HEIGHT = "file_height"; + private static final String COLUMN_NAME_MSG_PARAMS_FILE_STATUS = "file_status"; + private static final String COLUMN_NAME_MSG_PARAMS_FILE_NAME = "file_name"; + private static final String COLUMN_NAME_MSG_PARAMS_FILE_PATH = "file_path"; + private static final String COLUMN_NAME_MSG_PARAMS_FILE_URL = "file_url"; + + private static final String TABLE_ADDITIONAL_PARAMETERS_CREATE_V1 = "CREATE TABLE " + MessageDatabaseAccess.TABLE_NAME_ADDITIONAL_PARAMETERS + " (" + + MessageDatabaseAccess.COLUMN_NAME_MSG_PARAMS_MSGUUID + " TEXT, " + + MessageDatabaseAccess.COLUMN_NAME_MSG_PARAMS_HTTPUPLOAD + " INTEGER DEFAULT 0, " + + MessageDatabaseAccess.COLUMN_NAME_MSG_PARAMS_TREATASDOWNLOADABLE_DECISION + " TEXT DEFAULT 'NOT_DECIDED', " + + "FOREIGN KEY(" + MessageDatabaseAccess.COLUMN_NAME_MSG_PARAMS_MSGUUID + ") REFERENCES " + Message.TABLENAME + "(" + Message.UUID + ") ON DELETE CASCADE)"; + + private static final String TABLE_ADDITIONAL_PARAMETERS_CREATE = "CREATE TABLE " + MessageDatabaseAccess.TABLE_NAME_ADDITIONAL_PARAMETERS + " (" + + MessageDatabaseAccess.COLUMN_NAME_MSG_PARAMS_MSGUUID + " TEXT, " + + MessageDatabaseAccess.COLUMN_NAME_MSG_PARAMS_HTTPUPLOAD + " INTEGER DEFAULT 0, " + + MessageDatabaseAccess.COLUMN_NAME_MSG_PARAMS_TREATASDOWNLOADABLE_DECISION + " TEXT DEFAULT 'NOT_DECIDED', " + + MessageDatabaseAccess.COLUMN_NAME_MSG_PARAMS_FILE_TYPE + " TEXT DEFAULT NULL, " + + MessageDatabaseAccess.COLUMN_NAME_MSG_PARAMS_FILE_SIZE + " INTEGER DEFAULT 0, " + + MessageDatabaseAccess.COLUMN_NAME_MSG_PARAMS_FILE_HEIGHT + " INTEGER DEFAULT 0, " + + MessageDatabaseAccess.COLUMN_NAME_MSG_PARAMS_FILE_WIDTH + " INTEGER DEFAULT 0, " + + MessageDatabaseAccess.COLUMN_NAME_MSG_PARAMS_FILE_STATUS + " TEXT DEFAULT 'UNDEFINED', " + + MessageDatabaseAccess.COLUMN_NAME_MSG_PARAMS_FILE_NAME + " TEXT DEFAULT NULL, " + + MessageDatabaseAccess.COLUMN_NAME_MSG_PARAMS_FILE_PATH + " TEXT DEFAULT NULL, " + + MessageDatabaseAccess.COLUMN_NAME_MSG_PARAMS_FILE_URL + " TEXT DEFAULT NULL, " + + "FOREIGN KEY(" + MessageDatabaseAccess.COLUMN_NAME_MSG_PARAMS_MSGUUID + ") REFERENCES " + Message.TABLENAME + "(" + Message.UUID + ") ON DELETE CASCADE)"; + + public static void updateMessageParameters(SQLiteDatabase db, Message message, String uuid) { + String[] args = {uuid}; + db.update(MessageDatabaseAccess.TABLE_NAME_ADDITIONAL_PARAMETERS, MessageDatabaseAccess.getAdditionalParametersContentValues(message), MessageDatabaseAccess.COLUMN_NAME_MSG_PARAMS_MSGUUID + "=?", args); + } + + public static ContentValues getAdditionalParametersContentValues(Message message) { + ContentValues additionalParameters = new ContentValues(); + additionalParameters.put(MessageDatabaseAccess.COLUMN_NAME_MSG_PARAMS_MSGUUID, message.getUuid()); + additionalParameters.put(MessageDatabaseAccess.COLUMN_NAME_MSG_PARAMS_HTTPUPLOAD, message.isHttpUploaded() ? 1 : 0); + additionalParameters.put(MessageDatabaseAccess.COLUMN_NAME_MSG_PARAMS_TREATASDOWNLOADABLE_DECISION, message.treatAsDownloadable().name()); + if (null != message.getFileParams()) { + FileParams fileParams = message.getFileParams(); + additionalParameters.put(MessageDatabaseAccess.COLUMN_NAME_MSG_PARAMS_FILE_TYPE, fileParams.getMimeType()); + additionalParameters.put(MessageDatabaseAccess.COLUMN_NAME_MSG_PARAMS_FILE_SIZE, fileParams.getSize()); + additionalParameters.put(MessageDatabaseAccess.COLUMN_NAME_MSG_PARAMS_FILE_HEIGHT, fileParams.getHeight()); + additionalParameters.put(MessageDatabaseAccess.COLUMN_NAME_MSG_PARAMS_FILE_WIDTH, fileParams.getWidth()); + additionalParameters.put(MessageDatabaseAccess.COLUMN_NAME_MSG_PARAMS_FILE_PATH, fileParams.getPath()); + additionalParameters.put(MessageDatabaseAccess.COLUMN_NAME_MSG_PARAMS_FILE_URL, fileParams.getUrl()); + if (null != fileParams.getFileStatus()) { + additionalParameters.put(MessageDatabaseAccess.COLUMN_NAME_MSG_PARAMS_FILE_STATUS, fileParams.getFileStatus().name()); + } + } + + return additionalParameters; + } + + private static void populateMessageParametersFromCursor(Cursor cursor, Message message) { + boolean isHttpUploaded = CursorHelper.getInt(cursor, COLUMN_NAME_MSG_PARAMS_HTTPUPLOAD) == 1; + message.setHttpUploaded(isHttpUploaded); + String downloadable = CursorHelper.getString(cursor, COLUMN_NAME_MSG_PARAMS_TREATASDOWNLOADABLE_DECISION); + Message.Decision treatAsDownloadable = Message.Decision.NOT_DECIDED; + try { + treatAsDownloadable = Message.Decision.valueOf(downloadable); + } catch (IllegalArgumentException e) { + // Should only happen if the database is corrupted, but to be on the save side catch it here + Logging.e("db.msg", "Unknown Decision for treatAsDownloadable found: '" + downloadable + "'"); + } + + message.setTreatAsDownloadable(treatAsDownloadable); + + if (message.hasFileAttached()) { + FileParams fileParams = new FileParams(message.getBody()); + String fileType = CursorHelper.getString(cursor, COLUMN_NAME_MSG_PARAMS_FILE_TYPE); + fileParams.setMimeType(fileType); + String name = CursorHelper.getString(cursor, COLUMN_NAME_MSG_PARAMS_FILE_NAME); + fileParams.setName(name); + String path = CursorHelper.getString(cursor, COLUMN_NAME_MSG_PARAMS_FILE_PATH); + fileParams.setPath(path); + String url = CursorHelper.getString(cursor, COLUMN_NAME_MSG_PARAMS_FILE_URL); + fileParams.setUrl(url); + long fileSize = CursorHelper.getLong(cursor, COLUMN_NAME_MSG_PARAMS_FILE_SIZE); + fileParams.setSize(fileSize); + int imageHeight = CursorHelper.getInt(cursor, COLUMN_NAME_MSG_PARAMS_FILE_HEIGHT); + fileParams.setHeight(imageHeight); + int imageWidth = CursorHelper.getInt(cursor, COLUMN_NAME_MSG_PARAMS_FILE_WIDTH); + fileParams.setWidth(imageWidth); + String status = CursorHelper.getString(cursor, COLUMN_NAME_MSG_PARAMS_FILE_STATUS); + if (null != status && !status.isEmpty()) { + FileStatus fileStatus = FileStatus.UNDEFINED; + try { + fileStatus = FileStatus.valueOf(status); + } catch (IllegalArgumentException e) { + // Should only happen if the database is corrupted, but to be on the save side catch it here + Logging.e("db.msg", "Unknown FileStatus for fileParams.fileStatus found: '" + status + "'"); + } + fileParams.setFileStatus(fileStatus); + } + message.setFileParams(fileParams); + } + } + + public static void populateMessageParameters(SQLiteDatabase db, Message message) { + Cursor paramsCursor = db.query(MessageDatabaseAccess.TABLE_NAME_ADDITIONAL_PARAMETERS, + null, MessageDatabaseAccess.COLUMN_NAME_MSG_PARAMS_MSGUUID + "=?", + new String[] {message.getUuid()}, + null, null, null, null); + paramsCursor.moveToNext(); + MessageDatabaseAccess.populateMessageParametersFromCursor(paramsCursor, message); + paramsCursor.close(); + } + + public static void upgrade(SQLiteDatabase db, int oldVersion, int newVersion) { + if (oldVersion < 1 && newVersion >= 1) { + Logging.d("db.upgrade.cplus", "Creating additional parameters table for messages."); + db.execSQL(MessageDatabaseAccess.TABLE_ADDITIONAL_PARAMETERS_CREATE_V1); + db.execSQL("INSERT INTO " + MessageDatabaseAccess.TABLE_NAME_ADDITIONAL_PARAMETERS + "(" + MessageDatabaseAccess.COLUMN_NAME_MSG_PARAMS_MSGUUID + ") " + + " SELECT " + Message.UUID + " FROM " + Message.TABLENAME); + } + if (oldVersion < 3 && newVersion >= 3) { + Logging.d("db.upgrade.cplus", "Upgrade " + MessageDatabaseAccess.TABLE_NAME_ADDITIONAL_PARAMETERS); + String[] columnDefinitions = { + MessageDatabaseAccess.COLUMN_NAME_MSG_PARAMS_FILE_TYPE + " TEXT DEFAULT NULL", + MessageDatabaseAccess.COLUMN_NAME_MSG_PARAMS_FILE_SIZE + " INTEGER DEFAULT 0", + MessageDatabaseAccess.COLUMN_NAME_MSG_PARAMS_FILE_HEIGHT + " INTEGER DEFAULT 0", + MessageDatabaseAccess.COLUMN_NAME_MSG_PARAMS_FILE_WIDTH + " INTEGER DEFAULT 0", + MessageDatabaseAccess.COLUMN_NAME_MSG_PARAMS_FILE_STATUS + " TEXT DEFAULT 'UNDEFINED'", + MessageDatabaseAccess.COLUMN_NAME_MSG_PARAMS_FILE_NAME + " TEXT DEFAULT NULL", + MessageDatabaseAccess.COLUMN_NAME_MSG_PARAMS_FILE_PATH + " TEXT DEFAULT NULL", + }; + + addNewColumns(db, MessageDatabaseAccess.TABLE_NAME_ADDITIONAL_PARAMETERS, columnDefinitions); + + Logging.d("db.upgrade.cplus", "Migrate file params from message body to database fields"); + Cursor cursor = db.rawQuery("SELECT " + Message.UUID + ", " + Message.BODY + " FROM " + Message.TABLENAME + " WHERE " + Message.TYPE + "=? OR " + Message.TYPE + "=?", new String[] {String.valueOf(Message.TYPE_FILE), String.valueOf(Message.TYPE_IMAGE)}); + while (cursor.moveToNext()) { + String uuid = CursorHelper.getString(cursor, Message.UUID); + String body = CursorHelper.getString(cursor, Message.BODY); + FileParams fileParams = FileParamsBodyToDatabaseFieldsMigration.getFileParams(body); + String newBody = fileParams.getUrl(); + ContentValues values = new ContentValues(); + values.put(Message.BODY, newBody); + db.update(Message.TABLENAME, values, Message.UUID + "=?", new String[] {uuid}); + + ContentValues parameterValues = new ContentValues(); + parameterValues.put(MessageDatabaseAccess.COLUMN_NAME_MSG_PARAMS_FILE_SIZE, fileParams.getSize()); + parameterValues.put(MessageDatabaseAccess.COLUMN_NAME_MSG_PARAMS_FILE_HEIGHT, fileParams.getHeight()); + parameterValues.put(MessageDatabaseAccess.COLUMN_NAME_MSG_PARAMS_FILE_WIDTH, fileParams.getWidth()); + + db.update(MessageDatabaseAccess.TABLE_NAME_ADDITIONAL_PARAMETERS, parameterValues, MessageDatabaseAccess.COLUMN_NAME_MSG_PARAMS_MSGUUID + "=?", new String[] {uuid}); + } + cursor.close(); + } + } + + public static void create(SQLiteDatabase db) { + // Create Conversations+ related tables + db.execSQL(MessageDatabaseAccess.TABLE_ADDITIONAL_PARAMETERS_CREATE); + } +} diff --git a/src/main/java/de/thedevstack/conversationsplus/persistance/db/migrations/FileParamsBodyToDatabaseFieldsMigration.java b/src/main/java/de/thedevstack/conversationsplus/persistance/db/migrations/FileParamsBodyToDatabaseFieldsMigration.java new file mode 100644 index 00000000..c0aa63c0 --- /dev/null +++ b/src/main/java/de/thedevstack/conversationsplus/persistance/db/migrations/FileParamsBodyToDatabaseFieldsMigration.java @@ -0,0 +1,101 @@ +package de.thedevstack.conversationsplus.persistance.db.migrations; + +import java.net.MalformedURLException; +import java.net.URL; + +import de.thedevstack.conversationsplus.entities.FileParams; + +/** + * Created by steckbrief on 24.08.2016. + */ +public class FileParamsBodyToDatabaseFieldsMigration { + + public static FileParams getFileParams(String body) { + FileParams params = getLegacyFileParams(body); + if (params != null) { + return params; + } + params = new FileParams(); + if (body == null) { + return params; + } + String parts[] = body.split("\\|"); + switch (parts.length) { + case 1: + try { + params.setSize(Long.parseLong(parts[0])); + } catch (NumberFormatException e) { + try { + URL url = new URL(parts[0]); + params.setUrl(url.toString()); + } catch (MalformedURLException e1) { + } + } + break; + case 2: + case 4: + try { + URL url = new URL(parts[0]); + params.setUrl(url.toString()); + } catch (MalformedURLException e1) { + } + try { + params.setSize(Long.parseLong(parts[1])); + } catch (NumberFormatException e) { + } + try { + params.setWidth(Integer.parseInt(parts[2])); + } catch (NumberFormatException | ArrayIndexOutOfBoundsException e) { + } + try { + params.setHeight(Integer.parseInt(parts[3])); + } catch (NumberFormatException | ArrayIndexOutOfBoundsException e) { + } + break; + case 3: + try { + params.setSize(Long.parseLong(parts[0])); + } catch (NumberFormatException e) { + } + try { + params.setWidth(Integer.parseInt(parts[1])); + } catch (NumberFormatException e) { + } + try { + params.setHeight(Integer.parseInt(parts[2])); + } catch (NumberFormatException e) { + } + break; + } + return params; + } + + private static FileParams getLegacyFileParams(String body) { + FileParams params = new FileParams(); + if (body == null) { + return params; + } + String parts[] = body.split(","); + if (parts.length == 3) { + try { + params.setSize(Long.parseLong(parts[0])); + } catch (NumberFormatException e) { + return null; + } + try { + params.setWidth(Integer.parseInt(parts[1])); + } catch (NumberFormatException e) { + return null; + } + try { + params.setHeight(Integer.parseInt(parts[2])); + } catch (NumberFormatException e) { + return null; + } + return params; + } else { + return null; + } + } + +} diff --git a/src/main/java/de/thedevstack/conversationsplus/services/ExportLogsService.java b/src/main/java/de/thedevstack/conversationsplus/services/ExportLogsService.java index 18930d1c..0dcfa05e 100644 --- a/src/main/java/de/thedevstack/conversationsplus/services/ExportLogsService.java +++ b/src/main/java/de/thedevstack/conversationsplus/services/ExportLogsService.java @@ -103,7 +103,7 @@ public class ExportLogsService extends Service { break; } if (jid != null) { - String body = message.hasFileOnRemoteHost() ? message.getFileParams().url.toString() : message.getBody(); + String body = message.hasFileOnRemoteHost() ? message.getFileParams().getUrl() : message.getBody(); bw.write(String.format(MESSAGE_STRING_FORMAT, date, jid, body.replace("\\\n", "\\ \n").replace("\n", "\\ \n"))); } diff --git a/src/main/java/de/thedevstack/conversationsplus/services/NotificationService.java b/src/main/java/de/thedevstack/conversationsplus/services/NotificationService.java index a3142df4..f4388b21 100644 --- a/src/main/java/de/thedevstack/conversationsplus/services/NotificationService.java +++ b/src/main/java/de/thedevstack/conversationsplus/services/NotificationService.java @@ -356,7 +356,8 @@ public class NotificationService { if (message.getType() != Message.TYPE_TEXT && message.getTransferable() == null && message.getEncryption() != Message.ENCRYPTION_PGP - && message.getFileParams().height > 0) { + && message.getFileParams() != null + && message.getFileParams().getHeight() > 0) { // TODO Use FileParams.getMimeType() return message; } } diff --git a/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/http/delete/DeleteRemoteFile.java b/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/http/delete/DeleteRemoteFile.java index 39a369ef..1549388e 100644 --- a/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/http/delete/DeleteRemoteFile.java +++ b/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/http/delete/DeleteRemoteFile.java @@ -3,10 +3,10 @@ package de.thedevstack.conversationsplus.services.filetransfer.http.delete; import android.support.annotation.NonNull; import de.thedevstack.conversationsplus.entities.Message; -import de.thedevstack.conversationsplus.entities.RemoteFile; +import de.thedevstack.conversationsplus.dto.RemoteFile; /** - * Created by steckbrief on 22.08.2016. + * */ public class DeleteRemoteFile extends RemoteFile { private final Message message; diff --git a/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/http/delete/DeleteRemoteFileService.java b/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/http/delete/DeleteRemoteFileService.java index 2b26fd85..eb9f1b04 100644 --- a/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/http/delete/DeleteRemoteFileService.java +++ b/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/http/delete/DeleteRemoteFileService.java @@ -1,8 +1,13 @@ package de.thedevstack.conversationsplus.services.filetransfer.http.delete; +import de.thedevstack.conversationsplus.ConversationsPlusApplication; import de.thedevstack.conversationsplus.entities.Account; import de.thedevstack.conversationsplus.entities.Message; +import de.thedevstack.conversationsplus.enums.FileStatus; +import de.thedevstack.conversationsplus.persistance.DatabaseBackend; import de.thedevstack.conversationsplus.ui.listeners.SimpleUserDecisionCallback; +import de.thedevstack.conversationsplus.utils.MessageUtil; +import de.thedevstack.conversationsplus.utils.UiUpdateHelper; import de.thedevstack.conversationsplus.utils.XmppSendUtil; import de.thedevstack.conversationsplus.xmpp.filetransfer.http.FileTransferHttp; import de.thedevstack.conversationsplus.xmpp.filetransfer.http.delete.FileTransferHttpDeleteSlotRequestPacketGenerator; @@ -10,7 +15,7 @@ import de.thedevstack.conversationsplus.xmpp.jid.Jid; import de.thedevstack.conversationsplus.xmpp.stanzas.IqPacket; /** - * Created by steckbrief on 21.08.2016. + * */ public class DeleteRemoteFileService implements SimpleUserDecisionCallback { private Message message; @@ -23,14 +28,14 @@ public class DeleteRemoteFileService implements SimpleUserDecisionCallback { if (this.message.isHttpUploaded()) { String path = this.message.getBody(); if (this.message.hasFileOnRemoteHost()) { - path = this.message.getFileParams().url.toString(); + path = this.message.getFileParams().getUrl(); } DeleteRemoteFile remoteFile = new DeleteRemoteFile(path, this.message); Account account = this.message.getConversation().getAccount(); Jid host = account.getXmppConnection().findDiscoItemByFeature(FileTransferHttp.NAMESPACE); IqPacket request = FileTransferHttpDeleteSlotRequestPacketGenerator.generate(host, path); - + MessageUtil.setAndSaveFileStatus(this.message, FileStatus.DELETING); XmppSendUtil.sendIqPacket(account, request, new DeleteTokenReceived(remoteFile)); } } diff --git a/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/http/delete/DeleteTokenReceived.java b/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/http/delete/DeleteTokenReceived.java index a8bef0ed..186454e4 100644 --- a/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/http/delete/DeleteTokenReceived.java +++ b/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/http/delete/DeleteTokenReceived.java @@ -7,7 +7,7 @@ import java.io.IOException; import de.thedevstack.android.logcat.Logging; import de.thedevstack.conversationsplus.entities.Account; -import de.thedevstack.conversationsplus.entities.Message; +import de.thedevstack.conversationsplus.enums.FileStatus; import de.thedevstack.conversationsplus.http.HttpClient; import de.thedevstack.conversationsplus.utils.MessageUtil; import de.thedevstack.conversationsplus.xmpp.OnIqPacketReceived; @@ -21,7 +21,7 @@ import okhttp3.Request; import okhttp3.Response; /** - * Created by steckbrief on 21.08.2016. + * */ public class DeleteTokenReceived implements OnIqPacketReceived { private static final String HEADER_NAME_DELETE_TOKEN = "X-FILETRANSFER-HTTP-DELETE-TOKEN"; @@ -47,19 +47,19 @@ public class DeleteTokenReceived implements OnIqPacketReceived { @Override public void onFailure(Call call, IOException e) { Logging.e("filetransfer.http.delete", "Error while connecting to '" + call.request().url() + "': " + e.getMessage()); - MessageUtil.markMessage(remoteFile.getMessage(), Message.STATUS_REMOTE_FILE_DELETE_FAILED); + MessageUtil.setAndSaveFileStatus(remoteFile.getMessage(), FileStatus.DELETE_FAILED); } @Override public void onResponse(Call call, Response response) throws IOException { if (response.isSuccessful()) { - MessageUtil.markMessage(remoteFile.getMessage(), Message.STATUS_REMOTE_FILE_DELETED); + MessageUtil.setAndSaveFileStatus(remoteFile.getMessage(), FileStatus.DELETED); Logging.i("filetransfer.http.delete", "Remote file successfully deleted '" + remoteFile.getPath() + "'"); } else { String detailedMessage = response.body().string(); + FileStatus fileStatus = FileStatus.DELETE_FAILED; switch (response.code()) { case 403: - case 404: case 500: try { JSONObject jsonObject = new JSONObject(detailedMessage); @@ -68,16 +68,20 @@ public class DeleteTokenReceived implements OnIqPacketReceived { Logging.e("filetransfer.http.delete", "Failed to get error message from expected json response: " + detailedMessage); } break; + case 404: + fileStatus = FileStatus.DELETED; + Logging.i("filetransfer.http.delete", "Failed to delete file - it was already deleted."); + break; } Logging.e("filetransfer.http.delete", "Could not delete remote file '" + remoteFile.getPath() + "'. Response Code: " + response.code() + ", details: " + detailedMessage); - MessageUtil.markMessage(remoteFile.getMessage(), Message.STATUS_REMOTE_FILE_DELETE_FAILED); + MessageUtil.setAndSaveFileStatus(remoteFile.getMessage(), fileStatus); } } }); } catch (XmppException e) { Logging.e("filetransfer.http.delete", "Error while trying to get the delete token: " + e.getMessage()); - MessageUtil.markMessage(remoteFile.getMessage(), Message.STATUS_REMOTE_FILE_DELETE_FAILED); + MessageUtil.setAndSaveFileStatus(remoteFile.getMessage(), FileStatus.DELETE_FAILED); } } } diff --git a/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/http/upload/HttpFileTransferEntity.java b/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/http/upload/HttpFileTransferEntity.java index fbe7186b..a373d584 100644 --- a/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/http/upload/HttpFileTransferEntity.java +++ b/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/http/upload/HttpFileTransferEntity.java @@ -6,7 +6,9 @@ import java.net.URL; import de.thedevstack.android.logcat.Logging; import de.thedevstack.conversationsplus.Config; import de.thedevstack.conversationsplus.ConversationsPlusApplication; +import de.thedevstack.conversationsplus.entities.FileParams; import de.thedevstack.conversationsplus.entities.Message; +import de.thedevstack.conversationsplus.enums.FileStatus; import de.thedevstack.conversationsplus.services.filetransfer.FileTransferEntity; import de.thedevstack.conversationsplus.services.filetransfer.FileTransferFailureReason; import de.thedevstack.conversationsplus.utils.CryptoHelper; @@ -24,7 +26,13 @@ public class HttpFileTransferEntity extends FileTransferEntity { public HttpFileTransferEntity(Message message, boolean delayed) { super(message); this.getMessage().setHttpUploaded(true); - this.getMessage().setNoDownloadable(); + this.getMessage().setNoDownloadable(); // TODO Set rmeote file status to uploaded + FileParams fileParams = this.getMessage().getFileParams(); + if (null == fileParams) { + fileParams = new FileParams(); + } + fileParams.setFileStatus(FileStatus.NEEDS_UPLOAD); + this.getMessage().setFileParams(fileParams); if (Config.ENCRYPT_ON_HTTP_UPLOADED || message.getEncryption() == Message.ENCRYPTION_AXOLOTL || message.getEncryption() == Message.ENCRYPTION_OTR) { @@ -77,6 +85,7 @@ public class HttpFileTransferEntity extends FileTransferEntity { getUrl = new URL(getUrl.toString() + "#" + CryptoHelper.bytesToHex(this.getKey())); } + this.getMessage().getFileParams().setFileStatus(FileStatus.UPLOADED); MessageUtil.updateFileParams(this.getMessage(), getUrl); } catch (MalformedURLException e) { Logging.e("httpupload", "Not a valid get url"); diff --git a/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/http/upload/HttpFileUploader.java b/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/http/upload/HttpFileUploader.java index 318a8b8f..8cae599c 100644 --- a/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/http/upload/HttpFileUploader.java +++ b/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/http/upload/HttpFileUploader.java @@ -84,6 +84,7 @@ public class HttpFileUploader implements Runnable { // Send the URL to the counterpart Message message = this.entity.getMessage(); + message.setBody(this.entity.getGetUrl()); message.setCounterpart(message.getConversation().getJid().toBareJid()); if (message.getEncryption() == Message.ENCRYPTION_DECRYPTED) { XmppConnectionServiceAccessor.xmppConnectionService.getPgpEngine().encrypt(message, new HttpUploadedFileEncryptionUiCallback(this.entity)); diff --git a/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/http/upload/HttpUploadFileTransferService.java b/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/http/upload/HttpUploadFileTransferService.java index f08e27f5..6b19cd5f 100644 --- a/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/http/upload/HttpUploadFileTransferService.java +++ b/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/http/upload/HttpUploadFileTransferService.java @@ -86,6 +86,7 @@ public class HttpUploadFileTransferService extends AbstractFileTransferService i return null != message && null != message.getConversation() && null != message.getConversation().getAccount() - && message.getConversation().getAccount().httpUploadAvailable(FileBackend.getFile(message, false).getSize()); + && null != message.getFileParams() + && message.getConversation().getAccount().httpUploadAvailable(message.getFileParams().getSize()); } } diff --git a/src/main/java/de/thedevstack/conversationsplus/ui/ConversationFragment.java b/src/main/java/de/thedevstack/conversationsplus/ui/ConversationFragment.java index 56065ea4..0c2f2960 100644 --- a/src/main/java/de/thedevstack/conversationsplus/ui/ConversationFragment.java +++ b/src/main/java/de/thedevstack/conversationsplus/ui/ConversationFragment.java @@ -578,7 +578,7 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa deleteFile.setVisible(true); deleteFile.setTitle(activity.getString(R.string.delete_x_file,UIHelper.getFileDescriptionString(activity, m))); } - if (m.isHttpUploaded() && (MessageUtil.isMessageSent(m) || m.getStatus() == Message.STATUS_REMOTE_FILE_DELETE_FAILED)) { + if (m.isHttpUploaded() && MessageUtil.isMessageSent(m)) { MenuItem deleteRemoteFile = menu.findItem(R.id.msg_ctx_menu_delete_remote_file); deleteRemoteFile.setVisible(true); } @@ -676,7 +676,7 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa url = message.getBody(); } else if (message.hasFileOnRemoteHost()) { resId = R.string.file_url; - url = message.getFileParams().url.toString(); + url = message.getFileParams().getUrl(); } else { url = message.getBody(); resId = R.string.file_url; diff --git a/src/main/java/de/thedevstack/conversationsplus/ui/adapter/ConversationAdapter.java b/src/main/java/de/thedevstack/conversationsplus/ui/adapter/ConversationAdapter.java index e28774f2..98a9a96c 100644 --- a/src/main/java/de/thedevstack/conversationsplus/ui/adapter/ConversationAdapter.java +++ b/src/main/java/de/thedevstack/conversationsplus/ui/adapter/ConversationAdapter.java @@ -90,7 +90,7 @@ public class ConversationAdapter extends ArrayAdapter { convName.setTypeface(null, Typeface.NORMAL); } - if (message.getFileParams().width > 0 + if ((null != message.getFileParams() && message.getFileParams().getWidth() > 0) // TODO: Use FileParams.getMimeType() && (message.getTransferable() == null || message.getTransferable().getStatus() != Transferable.STATUS_DELETED)) { mLastMessage.setVisibility(View.GONE); diff --git a/src/main/java/de/thedevstack/conversationsplus/ui/adapter/MessageAdapter.java b/src/main/java/de/thedevstack/conversationsplus/ui/adapter/MessageAdapter.java index b2dca6b9..e93ddbfa 100644 --- a/src/main/java/de/thedevstack/conversationsplus/ui/adapter/MessageAdapter.java +++ b/src/main/java/de/thedevstack/conversationsplus/ui/adapter/MessageAdapter.java @@ -20,6 +20,7 @@ import android.text.style.StyleSpan; import android.text.util.Linkify; import android.util.DisplayMetrics; import android.util.Patterns; +import android.view.Gravity; import android.view.View; import android.view.View.OnClickListener; import android.view.View.OnLongClickListener; @@ -46,15 +47,17 @@ import de.thedevstack.conversationsplus.crypto.axolotl.XmppAxolotlSession; import de.thedevstack.conversationsplus.entities.Account; import de.thedevstack.conversationsplus.entities.Conversation; import de.thedevstack.conversationsplus.entities.DownloadableFile; +import de.thedevstack.conversationsplus.entities.FileParams; import de.thedevstack.conversationsplus.entities.Message; -import de.thedevstack.conversationsplus.entities.Message.FileParams; import de.thedevstack.conversationsplus.entities.Transferable; +import de.thedevstack.conversationsplus.enums.FileStatus; import de.thedevstack.conversationsplus.persistance.FileBackend; import de.thedevstack.conversationsplus.providers.ConversationsPlusFileProvider; import de.thedevstack.conversationsplus.services.AvatarService; import de.thedevstack.conversationsplus.ui.ConversationActivity; import de.thedevstack.conversationsplus.utils.CryptoHelper; import de.thedevstack.conversationsplus.utils.GeoHelper; +import de.thedevstack.conversationsplus.utils.MessageUtil; import de.thedevstack.conversationsplus.utils.UIHelper; public class MessageAdapter extends ArrayAdapter { @@ -138,13 +141,17 @@ public class MessageAdapter extends ArrayAdapter { boolean multiReceived = message.getConversation().getMode() == Conversation.MODE_MULTI && message.getStatus() <= Message.STATUS_RECEIVED; - if (message.getType() == Message.TYPE_IMAGE || message.getType() == Message.TYPE_FILE || message.getTransferable() != null) { + if (message.hasFileAttached() || message.getTransferable() != null) { FileParams params = message.getFileParams(); - if (params.size > (1.5 * 1024 * 1024)) { - filesize = params.size / (1024 * 1024)+ " MiB"; - } else if (params.size > 0) { - filesize = params.size / 1024 + " KiB"; - } + if (null != params) { + long size = params.getSize(); + if (size > (1.5 * 1024 * 1024)) { + filesize = size / (1024 * 1024)+ " MiB"; + } else if (size > 0) { + filesize = size / 1024 + " KiB"; + } + } + if (message.getTransferable() != null && message.getTransferable().getStatus() == Transferable.STATUS_FAILED) { error = true; } @@ -247,6 +254,25 @@ public class MessageAdapter extends ArrayAdapter { viewHolder.time.setText(formatedTime); } } + + if (message.hasFileAttached() && null != message.getFileParams() && null != viewHolder.remoteFileStatus) { + FileStatus fileStatus = message.getFileParams().getFileStatus(); + if (fileStatus == FileStatus.DELETE_FAILED || fileStatus == FileStatus.DELETED || fileStatus == FileStatus.DELETING) { + viewHolder.remoteFileStatus.setVisibility(View.VISIBLE); + viewHolder.remoteFileStatus.setTypeface(null, Typeface.ITALIC); + switch (fileStatus) { + case DELETE_FAILED: + viewHolder.remoteFileStatus.setText(R.string.remote_filestatus_delete_failed); + break; + case DELETED: + viewHolder.remoteFileStatus.setText(R.string.remote_filestatus_delete_success); + break; + case DELETING: + viewHolder.remoteFileStatus.setText(R.string.remote_filestatus_delete_inprogress); + break; + } + } + } } private void displayInfoMessage(ViewHolder viewHolder, String text, boolean darkBackground) { @@ -434,9 +460,10 @@ public class MessageAdapter extends ArrayAdapter { } viewHolder.messageBody.setVisibility(View.GONE); viewHolder.image.setVisibility(View.VISIBLE); - FileParams params = message.getFileParams(); //TODO: Check what value add the following lines have (compared with setting height/width in XmppActivity.loadBitmap from thumbnail after thumbnail is created) - /*double target = metrics.density * 288; + /* + FileParams params = message.getFileParams(); + double target = metrics.density * 288; int scalledW; int scalledH; if (params.width <= params.height) { @@ -491,6 +518,7 @@ public class MessageAdapter extends ArrayAdapter { .findViewById(R.id.message_time); viewHolder.indicatorReceived = (ImageView) view .findViewById(R.id.indicator_received); + viewHolder.remoteFileStatus = (TextView) view.findViewById(R.id.remote_file_status); break; case RECEIVED: view = activity.getLayoutInflater().inflate( @@ -585,7 +613,7 @@ public class MessageAdapter extends ArrayAdapter { } else if (message.getType() == Message.TYPE_IMAGE && message.getEncryption() != Message.ENCRYPTION_PGP && message.getEncryption() != Message.ENCRYPTION_DECRYPTION_FAILED) { displayImageMessage(viewHolder, message); } else if (message.getType() == Message.TYPE_FILE && message.getEncryption() != Message.ENCRYPTION_PGP && message.getEncryption() != Message.ENCRYPTION_DECRYPTION_FAILED) { - if (message.getFileParams().width > 0) { + if (message.getFileParams() != null && message.getFileParams().getWidth() > 0) { // TODO Use FileParams.getMimeType() displayImageMessage(viewHolder,message); } else { displayOpenableMessage(viewHolder, message); @@ -612,12 +640,20 @@ public class MessageAdapter extends ArrayAdapter { } } else if (message.getEncryption() == Message.ENCRYPTION_DECRYPTION_FAILED) { displayDecryptionFailed(viewHolder,darkBackground); - } else { + } else if (message.hasFileAttached()) { // TODO: Move to a single block with Message.TYPE_FILE OR Message.TYPE_IMAGE + FileParams fileParams = message.getFileParams(); + String mimeType = (null != fileParams) ? fileParams.getMimeType() : null; + if (null != mimeType && mimeType.startsWith("image/")) { + displayImageMessage(viewHolder, message); + } else { + displayOpenableMessage(viewHolder, message); + } + } else { if (GeoHelper.isGeoUri(message.getBody())) { displayLocationMessage(viewHolder,message); - } else if (message.treatAsDownloadable() == Message.Decision.MUST) { + } else if (MessageUtil.needsDownload(message)) { try { - URL url = new URL(message.getBody()); + URL url = new URL(message.getFileParams().getUrl()); displayDownloadableMessage(viewHolder, message, activity.getString(R.string.check_x_filesize_on_host, @@ -724,7 +760,8 @@ public class MessageAdapter extends ArrayAdapter { protected ImageView contact_picture; protected TextView status_message; protected TextView encryption; - } + public TextView remoteFileStatus; + } class BitmapWorkerTask extends AsyncTask { private final WeakReference imageViewReference; diff --git a/src/main/java/de/thedevstack/conversationsplus/ui/dialogs/MessageDetailsDialog.java b/src/main/java/de/thedevstack/conversationsplus/ui/dialogs/MessageDetailsDialog.java index e1c30a56..4ec8e3eb 100644 --- a/src/main/java/de/thedevstack/conversationsplus/ui/dialogs/MessageDetailsDialog.java +++ b/src/main/java/de/thedevstack/conversationsplus/ui/dialogs/MessageDetailsDialog.java @@ -11,6 +11,7 @@ import de.thedevstack.android.logcat.Logging; import de.thedevstack.conversationsplus.ConversationsPlusColors; import de.thedevstack.conversationsplus.R; import de.thedevstack.conversationsplus.entities.Conversation; +import de.thedevstack.conversationsplus.entities.FileParams; import de.thedevstack.conversationsplus.entities.Message; import de.thedevstack.conversationsplus.utils.UIHelper; import de.thedevstack.conversationsplus.utils.ui.TextViewUtil; @@ -59,10 +60,10 @@ public class MessageDetailsDialog extends AbstractAlertDialog { Logging.d("messagedetailsfile", "File is stored in path: " + message.getRelativeFilePath()); view.findViewById(R.id.dlgMsgDetFileTable).setVisibility(View.VISIBLE); if (null != message.getFileParams()) { - Message.FileParams params = message.getFileParams(); - TextViewUtil.setText(view, R.id.dlgMsgDetFileSize, UIHelper.getHumanReadableFileSize(params.size)); + FileParams params = message.getFileParams(); + TextViewUtil.setText(view, R.id.dlgMsgDetFileSize, UIHelper.getHumanReadableFileSize(params.getSize())); + TextViewUtil.setText(view, R.id.dlgMsgDetFileMimeType, params.getMimeType()); } - TextViewUtil.setText(view, R.id.dlgMsgDetFileMimeType, message.getMimeType()); TextViewUtil.setText(view, R.id.dlgMsgDetFileHttpUploaded, message.isHttpUploaded() ? R.string.cplus_yes : R.string.cplus_no); } diff --git a/src/main/java/de/thedevstack/conversationsplus/ui/listeners/ResizePictureUserDecisionListener.java b/src/main/java/de/thedevstack/conversationsplus/ui/listeners/ResizePictureUserDecisionListener.java index b7340ce0..1574bb85 100644 --- a/src/main/java/de/thedevstack/conversationsplus/ui/listeners/ResizePictureUserDecisionListener.java +++ b/src/main/java/de/thedevstack/conversationsplus/ui/listeners/ResizePictureUserDecisionListener.java @@ -14,6 +14,8 @@ import de.thedevstack.android.logcat.Logging; import de.thedevstack.conversationsplus.ConversationsPlusApplication; import de.thedevstack.conversationsplus.ConversationsPlusPreferences; import de.thedevstack.conversationsplus.crypto.PgpEngine; +import de.thedevstack.conversationsplus.entities.FileParams; +import de.thedevstack.conversationsplus.enums.FileStatus; import de.thedevstack.conversationsplus.enums.UserDecision; import de.thedevstack.conversationsplus.exceptions.UiException; import de.thedevstack.conversationsplus.utils.FileUtils; @@ -27,6 +29,7 @@ import de.thedevstack.conversationsplus.persistance.FileBackend; import de.thedevstack.conversationsplus.services.XmppConnectionService; import de.thedevstack.conversationsplus.ui.UiCallback; import de.thedevstack.conversationsplus.ui.XmppActivity; +import de.thedevstack.conversationsplus.utils.MimeUtils; import de.thedevstack.conversationsplus.utils.StreamUtil; /** @@ -96,20 +99,18 @@ public class ResizePictureUserDecisionListener implements UserDecisionListener { @Override public void onYes() { this.showPrepareFileToast(); - final Message message; - if (conversation.getNextEncryption() == Message.ENCRYPTION_PGP) { - message = new Message(conversation, "", Message.ENCRYPTION_DECRYPTED); - } else { - message = new Message(conversation, "", conversation.getNextEncryption()); - } - message.setCounterpart(conversation.getNextCounterpart()); - message.setType(Message.TYPE_IMAGE); + final Message message = createMessage(); ConversationsPlusApplication.executeFileAdding(new OnYesRunnable(message, uri)); } @Override public void onNo() { this.showPrepareFileToast(); + final Message message = createMessage(); + ConversationsPlusApplication.executeFileAdding(new OnNoRunnable(message, uri)); + } + + protected Message createMessage() { final Message message; if (conversation.getNextEncryption() == Message.ENCRYPTION_PGP) { message = new Message(conversation, "", Message.ENCRYPTION_DECRYPTED); @@ -117,8 +118,11 @@ public class ResizePictureUserDecisionListener implements UserDecisionListener { message = new Message(conversation, "", conversation.getNextEncryption()); } message.setCounterpart(conversation.getNextCounterpart()); - message.setType(Message.TYPE_IMAGE); - ConversationsPlusApplication.executeFileAdding(new OnNoRunnable(message, uri)); + //message.setType(Message.TYPE_IMAGE); + message.setFileParams(new FileParams()); + message.getFileParams().setFileStatus(FileStatus.NEEDS_UPLOAD); + + return message; } @Override diff --git a/src/main/java/de/thedevstack/conversationsplus/utils/ConversationUtil.java b/src/main/java/de/thedevstack/conversationsplus/utils/ConversationUtil.java index 77c2c728..55fd1b8e 100644 --- a/src/main/java/de/thedevstack/conversationsplus/utils/ConversationUtil.java +++ b/src/main/java/de/thedevstack/conversationsplus/utils/ConversationUtil.java @@ -6,6 +6,7 @@ import de.thedevstack.conversationsplus.ConversationsPlusApplication; import de.thedevstack.conversationsplus.crypto.PgpEngine; import de.thedevstack.conversationsplus.entities.Conversation; import de.thedevstack.conversationsplus.entities.Message; +import de.thedevstack.conversationsplus.enums.FileStatus; import de.thedevstack.conversationsplus.exceptions.FileCopyException; import de.thedevstack.conversationsplus.persistance.FileBackend; import de.thedevstack.conversationsplus.ui.UiCallback; @@ -43,10 +44,12 @@ public class ConversationUtil { message = new Message(conversation, "", conversation.getNextEncryption()); } message.setCounterpart(conversation.getNextCounterpart()); - message.setType(Message.TYPE_FILE); + //message.setType(Message.TYPE_FILE); + message.getFileParams().setFileStatus(FileStatus.NEEDS_UPLOAD); String path = FileUtils.getPath(uri); if (path != null) { - message.setRelativeFilePath(path); + message.getFileParams().setPath(path); + message.setRelativeFilePath(path); // TODO: Remove when everything is moved to fileparams MessageUtil.updateFileParams(message); if (message.getEncryption() == Message.ENCRYPTION_DECRYPTED) { PgpEngine.getInstance().encrypt(message, callback); diff --git a/src/main/java/de/thedevstack/conversationsplus/utils/FileUtils.java b/src/main/java/de/thedevstack/conversationsplus/utils/FileUtils.java index 3ee15f70..ce289968 100644 --- a/src/main/java/de/thedevstack/conversationsplus/utils/FileUtils.java +++ b/src/main/java/de/thedevstack/conversationsplus/utils/FileUtils.java @@ -16,6 +16,7 @@ import java.io.File; import java.util.List; import de.thedevstack.conversationsplus.ConversationsPlusApplication; +import de.thedevstack.conversationsplus.entities.Transferable; public final class FileUtils { @@ -160,6 +161,27 @@ public final class FileUtils { return "com.android.providers.media.documents".equals(uri.getAuthority()); } + public static String getRelevantExtension(String path) { + if (path == null || path.isEmpty()) { + return null; + } + + String filename = path.substring(path.lastIndexOf('/') + 1).toLowerCase(); + + final String lastPart = FileUtils.getLastExtension(filename); + + if (!lastPart.isEmpty()) { + // we want the real file extension, not the crypto one + final String secondToLastPart = FileUtils.getSecondToLastExtension(filename); + if (!secondToLastPart.isEmpty() && Transferable.VALID_CRYPTO_EXTENSIONS.contains(lastPart)) { + return secondToLastPart; + } else { + return lastPart; + } + } + return null; + } + /** * @param filename The filename to extract extension from * @return last extension or empty string diff --git a/src/main/java/de/thedevstack/conversationsplus/utils/ImageUtil.java b/src/main/java/de/thedevstack/conversationsplus/utils/ImageUtil.java index bf79d7e0..0b8ace95 100644 --- a/src/main/java/de/thedevstack/conversationsplus/utils/ImageUtil.java +++ b/src/main/java/de/thedevstack/conversationsplus/utils/ImageUtil.java @@ -337,20 +337,28 @@ public final class ImageUtil { public static int calcSampleSize(Uri image, int size) throws FileNotFoundException { BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = true; - BitmapFactory.decodeStream(StreamUtil.openInputStreamFromContentResolver(image), null, options); - return calcSampleSize(options, size); + Bitmap bmp = BitmapFactory.decodeStream(StreamUtil.openInputStreamFromContentResolver(image), null, options); + int height = options.outHeight; + int width = options.outWidth; + if (null != bmp) { + bmp.recycle(); + } + return calcSampleSize(width, height, size); } public static int calcSampleSize(File image, int size) { BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = true; - BitmapFactory.decodeFile(image.getAbsolutePath(), options); - return calcSampleSize(options, size); - } - - public static int calcSampleSize(BitmapFactory.Options options, int size) { + Bitmap bmp = BitmapFactory.decodeFile(image.getAbsolutePath(), options); int height = options.outHeight; int width = options.outWidth; + if (null != bmp) { + bmp.recycle(); + } + return calcSampleSize(width, height, size); + } + + private static int calcSampleSize(int width, int height, int size) { int inSampleSize = 1; if (height > size || width > size) { diff --git a/src/main/java/de/thedevstack/conversationsplus/utils/MessageUtil.java b/src/main/java/de/thedevstack/conversationsplus/utils/MessageUtil.java index 626b1bf3..dc9607f0 100644 --- a/src/main/java/de/thedevstack/conversationsplus/utils/MessageUtil.java +++ b/src/main/java/de/thedevstack/conversationsplus/utils/MessageUtil.java @@ -1,5 +1,6 @@ package de.thedevstack.conversationsplus.utils; +import android.graphics.Bitmap; import android.graphics.BitmapFactory; import java.net.URL; @@ -9,7 +10,9 @@ import java.util.regex.Pattern; import de.thedevstack.conversationsplus.ConversationsPlusApplication; import de.thedevstack.conversationsplus.entities.Conversation; import de.thedevstack.conversationsplus.entities.DownloadableFile; +import de.thedevstack.conversationsplus.entities.FileParams; import de.thedevstack.conversationsplus.entities.Message; +import de.thedevstack.conversationsplus.enums.FileStatus; import de.thedevstack.conversationsplus.persistance.DatabaseBackend; import de.thedevstack.conversationsplus.persistance.FileBackend; @@ -18,6 +21,13 @@ import de.thedevstack.conversationsplus.persistance.FileBackend; */ public final class MessageUtil { + public static boolean needsDownload(Message message) { + FileStatus fileStatus = (null != message.getFileParams()) ? message.getFileParams().getFileStatus() : null; + return (null != fileStatus && (fileStatus == FileStatus.NEEDS_DOWNLOAD + || fileStatus == FileStatus.UNDEFINED)) + && message.treatAsDownloadable() != Message.Decision.NEVER; + } + public static boolean isMessageSent(Message message) { switch (message.getStatus()) { case Message.STATUS_SEND: @@ -29,6 +39,12 @@ public final class MessageUtil { } } + public static void setAndSaveFileStatus(Message message, FileStatus fileStatus) { + message.getFileParams().setFileStatus(fileStatus); + DatabaseBackend.getInstance().updateMessage(message); + UiUpdateHelper.updateConversationUi(); + } + public static boolean markMessage(Conversation conversation, String uuid, int status) { if (uuid == null) { return false; @@ -76,7 +92,7 @@ public final class MessageUtil { public static void updateMessageWithImageDetails(Message message, String filePath, long size, int imageWidth, int imageHeight) { message.setRelativeFilePath(filePath); - MessageUtil.updateMessageBodyWithImageParams(message, size, imageWidth, imageHeight); + MessageUtil.updateMessageWithFileParams(message, null, size, imageWidth, imageHeight); } public static void updateFileParams(Message message) { @@ -90,42 +106,37 @@ public final class MessageUtil { if (message.getType() == Message.TYPE_IMAGE || file.getMimeType().startsWith("image/")) { BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = true; - BitmapFactory.decodeFile(file.getAbsolutePath(), options); + Bitmap bmp = BitmapFactory.decodeFile(file.getAbsolutePath(), options); imageHeight = options.outHeight; imageWidth = options.outWidth; + if (null != bmp) { + bmp.recycle(); + } } - MessageUtil.updateMessageBodyWithFileParams(message, url, file.getSize(), imageWidth, imageHeight); - } - - private static void updateMessageBodyWithFileParams(Message message, URL url, long fileSize, int imageWidth, int imageHeight) { - message.setBody(MessageUtil.getMessageBodyWithImageParams(url, fileSize, imageWidth, imageHeight)); - } - - private static void updateMessageBodyWithImageParams(Message message, long size, int imageWidth, int imageHeight) { - MessageUtil.updateMessageBodyWithImageParams(message, null, size, imageWidth, imageHeight); + MessageUtil.updateMessageWithFileParams(message, url, file.getSize(), imageWidth, imageHeight); } - private static void updateMessageBodyWithImageParams(Message message, URL url, long size, int imageWidth, int imageHeight) { - message.setBody(MessageUtil.getMessageBodyWithImageParams(url, size, imageWidth, imageHeight)); - } - - private static String getMessageBodyWithImageParams(URL url, long size, int imageWidth, int imageHeight) { - StringBuilder sb = new StringBuilder(); + private static void updateMessageWithFileParams(Message message, URL url, long size, int imageWidth, int imageHeight) { + FileParams fileParams = message.getFileParams(); + if (null == fileParams) { + fileParams = new FileParams(); + } + fileParams.setSize(size); if (null != url) { - sb.append(url.toString()); - sb.append('|'); + fileParams.setUrl(url.toString()); } - sb.append(size); if (-1 < imageWidth) { - sb.append('|'); - sb.append(imageWidth); + fileParams.setWidth(imageWidth); } if (-1 < imageHeight) { - sb.append('|'); - sb.append(imageHeight); + fileParams.setHeight(imageHeight); + } + String relativeFilePathFromMessage = message.getRelativeFilePath(); + if (null != relativeFilePathFromMessage && relativeFilePathFromMessage.startsWith("/")) { + fileParams.setPath(relativeFilePathFromMessage); } - return sb.toString(); + message.setFileParams(fileParams); } private MessageUtil() { diff --git a/src/main/res/layout/message_sent.xml b/src/main/res/layout/message_sent.xml index 0edbab51..30003309 100644 --- a/src/main/res/layout/message_sent.xml +++ b/src/main/res/layout/message_sent.xml @@ -103,6 +103,18 @@ android:gravity="center_vertical" android:src="@drawable/ic_received_indicator" /> + + diff --git a/src/main/res/values/strings.xml b/src/main/res/values/strings.xml index 8136a84f..e356a947 100644 --- a/src/main/res/values/strings.xml +++ b/src/main/res/values/strings.xml @@ -676,4 +676,7 @@ Share URI with… Delete remote file Are you sure? + Failed to delete remote file + Remote file deleted + Deleting remote file... -- cgit v1.2.3