Compare commits

..

5 Commits

Author SHA1 Message Date
Torsten Grote
464dcf8742 send attachments one after the other with delay 2019-06-20 12:18:43 -03:00
akwizgran
09af494d5c DO NOT MERGE: Delay sending of attachments. 2019-06-19 12:55:01 +01:00
akwizgran
c955466bda Load missing attachments when they arrive. 2019-06-19 12:47:18 +01:00
akwizgran
593a0c4632 Improve handling of missing and invalid attachments. 2019-06-19 11:23:57 +01:00
akwizgran
ed20b2d8d6 Use attachment header to retrieve attachment. 2019-06-19 10:57:13 +01:00
19 changed files with 429 additions and 366 deletions

View File

@@ -54,8 +54,8 @@ public class AttachmentRetrieverIntegrationTest {
public void testSmallJpegImage() throws Exception { public void testSmallJpegImage() throws Exception {
AttachmentHeader h = new AttachmentHeader(msgId, "image/jpeg"); AttachmentHeader h = new AttachmentHeader(msgId, "image/jpeg");
InputStream is = getUrlInputStream(smallKitten); InputStream is = getUrlInputStream(smallKitten);
Attachment a = new Attachment(is); Attachment a = new Attachment(h, is);
AttachmentItem item = retriever.getAttachmentItem(h, a, true); AttachmentItem item = retriever.getAttachmentItem(a, true);
assertEquals(msgId, item.getMessageId()); assertEquals(msgId, item.getMessageId());
assertEquals(160, item.getWidth()); assertEquals(160, item.getWidth());
assertEquals(240, item.getHeight()); assertEquals(240, item.getHeight());
@@ -70,8 +70,8 @@ public class AttachmentRetrieverIntegrationTest {
public void testBigJpegImage() throws Exception { public void testBigJpegImage() throws Exception {
AttachmentHeader h = new AttachmentHeader(msgId, "image/jpeg"); AttachmentHeader h = new AttachmentHeader(msgId, "image/jpeg");
InputStream is = getUrlInputStream(originalKitten); InputStream is = getUrlInputStream(originalKitten);
Attachment a = new Attachment(is); Attachment a = new Attachment(h, is);
AttachmentItem item = retriever.getAttachmentItem(h, a, true); AttachmentItem item = retriever.getAttachmentItem(a, true);
assertEquals(msgId, item.getMessageId()); assertEquals(msgId, item.getMessageId());
assertEquals(1728, item.getWidth()); assertEquals(1728, item.getWidth());
assertEquals(2592, item.getHeight()); assertEquals(2592, item.getHeight());
@@ -86,8 +86,8 @@ public class AttachmentRetrieverIntegrationTest {
public void testSmallPngImage() throws Exception { public void testSmallPngImage() throws Exception {
AttachmentHeader h = new AttachmentHeader(msgId, "image/png"); AttachmentHeader h = new AttachmentHeader(msgId, "image/png");
InputStream is = getUrlInputStream(pngKitten); InputStream is = getUrlInputStream(pngKitten);
Attachment a = new Attachment(is); Attachment a = new Attachment(h, is);
AttachmentItem item = retriever.getAttachmentItem(h, a, true); AttachmentItem item = retriever.getAttachmentItem(a, true);
assertEquals(msgId, item.getMessageId()); assertEquals(msgId, item.getMessageId());
assertEquals(737, item.getWidth()); assertEquals(737, item.getWidth());
assertEquals(510, item.getHeight()); assertEquals(510, item.getHeight());
@@ -102,8 +102,8 @@ public class AttachmentRetrieverIntegrationTest {
public void testUberGif() throws Exception { public void testUberGif() throws Exception {
AttachmentHeader h = new AttachmentHeader(msgId, "image/jpeg"); AttachmentHeader h = new AttachmentHeader(msgId, "image/jpeg");
InputStream is = getUrlInputStream(uberGif); InputStream is = getUrlInputStream(uberGif);
Attachment a = new Attachment(is); Attachment a = new Attachment(h, is);
AttachmentItem item = retriever.getAttachmentItem(h, a, true); AttachmentItem item = retriever.getAttachmentItem(a, true);
assertEquals(1, item.getWidth()); assertEquals(1, item.getWidth());
assertEquals(1, item.getHeight()); assertEquals(1, item.getHeight());
assertEquals(dimensions.minHeight, item.getThumbnailWidth()); assertEquals(dimensions.minHeight, item.getThumbnailWidth());
@@ -117,8 +117,8 @@ public class AttachmentRetrieverIntegrationTest {
public void testLottaPixels() throws Exception { public void testLottaPixels() throws Exception {
AttachmentHeader h = new AttachmentHeader(msgId, "image/jpeg"); AttachmentHeader h = new AttachmentHeader(msgId, "image/jpeg");
InputStream is = getUrlInputStream(lottaPixel); InputStream is = getUrlInputStream(lottaPixel);
Attachment a = new Attachment(is); Attachment a = new Attachment(h, is);
AttachmentItem item = retriever.getAttachmentItem(h, a, true); AttachmentItem item = retriever.getAttachmentItem(a, true);
assertEquals(64250, item.getWidth()); assertEquals(64250, item.getWidth());
assertEquals(64250, item.getHeight()); assertEquals(64250, item.getHeight());
assertEquals(dimensions.maxWidth, item.getThumbnailWidth()); assertEquals(dimensions.maxWidth, item.getThumbnailWidth());
@@ -132,8 +132,8 @@ public class AttachmentRetrieverIntegrationTest {
public void testImageIoCrash() throws Exception { public void testImageIoCrash() throws Exception {
AttachmentHeader h = new AttachmentHeader(msgId, "image/jpeg"); AttachmentHeader h = new AttachmentHeader(msgId, "image/jpeg");
InputStream is = getUrlInputStream(imageIoCrash); InputStream is = getUrlInputStream(imageIoCrash);
Attachment a = new Attachment(is); Attachment a = new Attachment(h, is);
AttachmentItem item = retriever.getAttachmentItem(h, a, true); AttachmentItem item = retriever.getAttachmentItem(a, true);
assertEquals(1184, item.getWidth()); assertEquals(1184, item.getWidth());
assertEquals(448, item.getHeight()); assertEquals(448, item.getHeight());
assertEquals(dimensions.maxWidth, item.getThumbnailWidth()); assertEquals(dimensions.maxWidth, item.getThumbnailWidth());
@@ -147,8 +147,8 @@ public class AttachmentRetrieverIntegrationTest {
public void testGimpCrash() throws Exception { public void testGimpCrash() throws Exception {
AttachmentHeader h = new AttachmentHeader(msgId, "image/jpeg"); AttachmentHeader h = new AttachmentHeader(msgId, "image/jpeg");
InputStream is = getUrlInputStream(gimpCrash); InputStream is = getUrlInputStream(gimpCrash);
Attachment a = new Attachment(is); Attachment a = new Attachment(h, is);
AttachmentItem item = retriever.getAttachmentItem(h, a, true); AttachmentItem item = retriever.getAttachmentItem(a, true);
assertEquals(1, item.getWidth()); assertEquals(1, item.getWidth());
assertEquals(1, item.getHeight()); assertEquals(1, item.getHeight());
assertEquals(dimensions.minHeight, item.getThumbnailWidth()); assertEquals(dimensions.minHeight, item.getThumbnailWidth());
@@ -162,8 +162,8 @@ public class AttachmentRetrieverIntegrationTest {
public void testOptiPngAfl() throws Exception { public void testOptiPngAfl() throws Exception {
AttachmentHeader h = new AttachmentHeader(msgId, "image/jpeg"); AttachmentHeader h = new AttachmentHeader(msgId, "image/jpeg");
InputStream is = getUrlInputStream(optiPngAfl); InputStream is = getUrlInputStream(optiPngAfl);
Attachment a = new Attachment(is); Attachment a = new Attachment(h, is);
AttachmentItem item = retriever.getAttachmentItem(h, a, true); AttachmentItem item = retriever.getAttachmentItem(a, true);
assertEquals(32, item.getWidth()); assertEquals(32, item.getWidth());
assertEquals(32, item.getHeight()); assertEquals(32, item.getHeight());
assertEquals(dimensions.minHeight, item.getThumbnailWidth()); assertEquals(dimensions.minHeight, item.getThumbnailWidth());
@@ -177,8 +177,8 @@ public class AttachmentRetrieverIntegrationTest {
public void testLibrawError() throws Exception { public void testLibrawError() throws Exception {
AttachmentHeader h = new AttachmentHeader(msgId, "image/jpeg"); AttachmentHeader h = new AttachmentHeader(msgId, "image/jpeg");
InputStream is = getUrlInputStream(librawError); InputStream is = getUrlInputStream(librawError);
Attachment a = new Attachment(is); Attachment a = new Attachment(h, is);
AttachmentItem item = retriever.getAttachmentItem(h, a, true); AttachmentItem item = retriever.getAttachmentItem(a, true);
assertTrue(item.hasError()); assertTrue(item.hasError());
} }
@@ -186,8 +186,8 @@ public class AttachmentRetrieverIntegrationTest {
public void testSmallAnimatedGifMaxDimensions() throws Exception { public void testSmallAnimatedGifMaxDimensions() throws Exception {
AttachmentHeader h = new AttachmentHeader(msgId, "image/gif"); AttachmentHeader h = new AttachmentHeader(msgId, "image/gif");
InputStream is = getAssetInputStream("animated.gif"); InputStream is = getAssetInputStream("animated.gif");
Attachment a = new Attachment(is); Attachment a = new Attachment(h, is);
AttachmentItem item = retriever.getAttachmentItem(h, a, true); AttachmentItem item = retriever.getAttachmentItem(a, true);
assertEquals(65535, item.getWidth()); assertEquals(65535, item.getWidth());
assertEquals(65535, item.getHeight()); assertEquals(65535, item.getHeight());
assertEquals(dimensions.maxWidth, item.getThumbnailWidth()); assertEquals(dimensions.maxWidth, item.getThumbnailWidth());
@@ -201,8 +201,8 @@ public class AttachmentRetrieverIntegrationTest {
public void testSmallAnimatedGifHugeDimensions() throws Exception { public void testSmallAnimatedGifHugeDimensions() throws Exception {
AttachmentHeader h = new AttachmentHeader(msgId, "image/gif"); AttachmentHeader h = new AttachmentHeader(msgId, "image/gif");
InputStream is = getAssetInputStream("animated2.gif"); InputStream is = getAssetInputStream("animated2.gif");
Attachment a = new Attachment(is); Attachment a = new Attachment(h, is);
AttachmentItem item = retriever.getAttachmentItem(h, a, true); AttachmentItem item = retriever.getAttachmentItem(a, true);
assertEquals(10000, item.getWidth()); assertEquals(10000, item.getWidth());
assertEquals(10000, item.getHeight()); assertEquals(10000, item.getHeight());
assertEquals(dimensions.maxWidth, item.getThumbnailWidth()); assertEquals(dimensions.maxWidth, item.getThumbnailWidth());
@@ -216,8 +216,8 @@ public class AttachmentRetrieverIntegrationTest {
public void testSmallGifLargeDimensions() throws Exception { public void testSmallGifLargeDimensions() throws Exception {
AttachmentHeader h = new AttachmentHeader(msgId, "image/gif"); AttachmentHeader h = new AttachmentHeader(msgId, "image/gif");
InputStream is = getAssetInputStream("error_large.gif"); InputStream is = getAssetInputStream("error_large.gif");
Attachment a = new Attachment(is); Attachment a = new Attachment(h, is);
AttachmentItem item = retriever.getAttachmentItem(h, a, true); AttachmentItem item = retriever.getAttachmentItem(a, true);
assertEquals(16384, item.getWidth()); assertEquals(16384, item.getWidth());
assertEquals(16384, item.getHeight()); assertEquals(16384, item.getHeight());
assertEquals(dimensions.maxWidth, item.getThumbnailWidth()); assertEquals(dimensions.maxWidth, item.getThumbnailWidth());
@@ -231,8 +231,8 @@ public class AttachmentRetrieverIntegrationTest {
public void testHighError() throws Exception { public void testHighError() throws Exception {
AttachmentHeader h = new AttachmentHeader(msgId, "image/jpeg"); AttachmentHeader h = new AttachmentHeader(msgId, "image/jpeg");
InputStream is = getAssetInputStream("error_high.jpg"); InputStream is = getAssetInputStream("error_high.jpg");
Attachment a = new Attachment(is); Attachment a = new Attachment(h, is);
AttachmentItem item = retriever.getAttachmentItem(h, a, true); AttachmentItem item = retriever.getAttachmentItem(a, true);
assertEquals(1, item.getWidth()); assertEquals(1, item.getWidth());
assertEquals(10000, item.getHeight()); assertEquals(10000, item.getHeight());
assertEquals(dimensions.minWidth, item.getThumbnailWidth()); assertEquals(dimensions.minWidth, item.getThumbnailWidth());
@@ -246,8 +246,8 @@ public class AttachmentRetrieverIntegrationTest {
public void testWideError() throws Exception { public void testWideError() throws Exception {
AttachmentHeader h = new AttachmentHeader(msgId, "image/jpeg"); AttachmentHeader h = new AttachmentHeader(msgId, "image/jpeg");
InputStream is = getAssetInputStream("error_wide.jpg"); InputStream is = getAssetInputStream("error_wide.jpg");
Attachment a = new Attachment(is); Attachment a = new Attachment(h, is);
AttachmentItem item = retriever.getAttachmentItem(h, a, true); AttachmentItem item = retriever.getAttachmentItem(a, true);
assertEquals(1920, item.getWidth()); assertEquals(1920, item.getWidth());
assertEquals(1, item.getHeight()); assertEquals(1, item.getHeight());
assertEquals(dimensions.maxWidth, item.getThumbnailWidth()); assertEquals(dimensions.maxWidth, item.getThumbnailWidth());

View File

@@ -1,33 +1,22 @@
package org.briarproject.briar.android.attachment; package org.briarproject.briar.android.attachment;
import android.arch.lifecycle.LiveData;
import android.arch.lifecycle.MutableLiveData;
import android.arch.lifecycle.Observer;
import android.content.ContentResolver; import android.content.ContentResolver;
import android.net.Uri; import android.net.Uri;
import android.support.annotation.Nullable;
import org.briarproject.bramble.api.db.DbException; import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.lifecycle.IoExecutor; import org.briarproject.bramble.api.lifecycle.IoExecutor;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.GroupId; import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.briar.api.messaging.Attachment;
import org.briarproject.briar.api.messaging.AttachmentHeader; import org.briarproject.briar.api.messaging.AttachmentHeader;
import org.briarproject.briar.api.messaging.MessagingManager; import org.briarproject.briar.api.messaging.MessagingManager;
import org.jsoup.UnsupportedMimeTypeException; import org.jsoup.UnsupportedMimeTypeException;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.List;
import java.util.concurrent.Executor;
import java.util.logging.Logger; import java.util.logging.Logger;
import javax.annotation.Nullable;
import javax.annotation.concurrent.ThreadSafe;
import static java.util.Objects.requireNonNull;
import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING; import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger; import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.util.IoUtils.tryToClose; import static org.briarproject.bramble.util.IoUtils.tryToClose;
@@ -36,97 +25,64 @@ import static org.briarproject.bramble.util.LogUtils.logException;
import static org.briarproject.bramble.util.LogUtils.now; import static org.briarproject.bramble.util.LogUtils.now;
import static org.briarproject.briar.api.messaging.MessagingConstants.IMAGE_MIME_TYPES; import static org.briarproject.briar.api.messaging.MessagingConstants.IMAGE_MIME_TYPES;
@ThreadSafe
@NotNullByDefault @NotNullByDefault
class AttachmentCreationTask { class AttachmentCreationTask {
private static Logger LOG = private static Logger LOG =
getLogger(AttachmentCreationTask.class.getName()); getLogger(AttachmentCreationTask.class.getName());
private final Executor ioExecutor;
private final MessagingManager messagingManager; private final MessagingManager messagingManager;
private final ContentResolver contentResolver; private final ContentResolver contentResolver;
private final AttachmentRetriever retriever;
private final GroupId groupId; private final GroupId groupId;
private final Collection<Uri> uris; private final Collection<Uri> uris;
private final boolean needsSize; private final boolean needsSize;
private final MutableLiveData<AttachmentResult> result; @Nullable
private volatile AttachmentCreator attachmentCreator;
private volatile boolean canceled = false; private volatile boolean canceled = false;
AttachmentCreationTask(Executor ioExecutor, AttachmentCreationTask(MessagingManager messagingManager,
MessagingManager messagingManager, ContentResolver contentResolver, ContentResolver contentResolver,
AttachmentRetriever retriever, GroupId groupId, AttachmentCreator attachmentCreator, GroupId groupId,
Collection<Uri> uris, boolean needsSize) { Collection<Uri> uris, boolean needsSize) {
this.ioExecutor = ioExecutor;
this.messagingManager = messagingManager; this.messagingManager = messagingManager;
this.contentResolver = contentResolver; this.contentResolver = contentResolver;
this.retriever = retriever;
this.groupId = groupId; this.groupId = groupId;
this.uris = uris; this.uris = uris;
this.needsSize = needsSize; this.needsSize = needsSize;
result = new MutableLiveData<>(); this.attachmentCreator = attachmentCreator;
} }
LiveData<AttachmentResult> getResult() { public void cancel() {
return result;
}
/**
* Cancels the task, asynchronously waits for it to finish, and deletes any
* created attachments.
*/
void cancel() {
canceled = true; canceled = true;
// Observe the task until it finishes (which may already have happened) attachmentCreator = null;
result.observeForever(new Observer<AttachmentResult>() {
@Override
public void onChanged(@Nullable AttachmentResult attachmentResult) {
requireNonNull(attachmentResult);
if (attachmentResult.isFinished()) {
deleteUnsentAttachments(attachmentResult.getItemResults());
result.removeObserver(this);
}
}
});
}
/**
* Asynchronously creates and stores the attachments.
*/
void storeAttachments() {
ioExecutor.execute(() -> {
if (LOG.isLoggable(INFO))
LOG.info("Storing " + uris.size() + " attachments");
List<AttachmentItemResult> results = new ArrayList<>();
for (Uri uri : uris) {
if (canceled) break;
results.add(processUri(uri));
result.postValue(new AttachmentResult(new ArrayList<>(results),
false, false));
}
result.postValue(new AttachmentResult(new ArrayList<>(results),
true, !canceled));
});
} }
@IoExecutor @IoExecutor
private AttachmentItemResult processUri(Uri uri) { public void storeAttachments() {
AttachmentHeader header = null; for (Uri uri: uris) processUri(uri);
AttachmentCreator attachmentCreator = this.attachmentCreator;
if (!canceled && attachmentCreator != null)
attachmentCreator.onAttachmentCreationFinished();
this.attachmentCreator = null;
}
@IoExecutor
private void processUri(Uri uri) {
if (canceled) return;
try { try {
header = storeAttachment(uri); AttachmentHeader h = storeAttachment(uri);
Attachment a = retriever.getMessageAttachment(header); AttachmentCreator attachmentCreator = this.attachmentCreator;
AttachmentItem item = if (attachmentCreator != null) {
retriever.getAttachmentItem(header, a, needsSize); attachmentCreator.onAttachmentHeaderReceived(uri, h, needsSize);
if (item.hasError()) throw new IOException(); }
if (needsSize) retriever.cachePut(item);
return new AttachmentItemResult(uri, item);
} catch (DbException | IOException e) { } catch (DbException | IOException e) {
logException(LOG, WARNING, e); logException(LOG, WARNING, e);
// If the attachment was already stored, delete it AttachmentCreator attachmentCreator = this.attachmentCreator;
tryToRemove(header); if (attachmentCreator != null) {
attachmentCreator.onAttachmentError(uri, e);
}
canceled = true; canceled = true;
return new AttachmentItemResult(uri, e);
} }
} }
@@ -157,31 +113,4 @@ class AttachmentCreationTask {
return false; return false;
} }
private void tryToRemove(@Nullable AttachmentHeader h) {
try {
if (h != null) messagingManager.removeAttachment(h);
} catch (DbException e1) {
logException(LOG, WARNING, e1);
}
}
private void deleteUnsentAttachments(
Collection<AttachmentItemResult> itemResults) {
List<AttachmentHeader> headers = new ArrayList<>(itemResults.size());
for (AttachmentItemResult itemResult : itemResults) {
AttachmentItem item = itemResult.getItem();
if (item != null) headers.add(item.getHeader());
}
if (LOG.isLoggable(INFO))
LOG.info("Deleting " + headers.size() + " unsent attachments");
ioExecutor.execute(() -> {
for (AttachmentHeader header : headers) {
try {
messagingManager.removeAttachment(header);
} catch (DbException e) {
logException(LOG, WARNING, e);
}
}
});
}
} }

View File

@@ -1,33 +1,56 @@
package org.briarproject.briar.android.attachment; package org.briarproject.briar.android.attachment;
import android.app.Application; import android.app.Application;
import android.arch.lifecycle.LiveData; import android.arch.lifecycle.LiveData;
import android.arch.lifecycle.MutableLiveData;
import android.net.Uri; import android.net.Uri;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import android.support.annotation.UiThread; import android.support.annotation.UiThread;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.lifecycle.IoExecutor; import org.briarproject.bramble.api.lifecycle.IoExecutor;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.GroupId; import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.briar.R;
import org.briarproject.briar.api.messaging.Attachment;
import org.briarproject.briar.api.messaging.AttachmentHeader; import org.briarproject.briar.api.messaging.AttachmentHeader;
import org.briarproject.briar.api.messaging.FileTooBigException;
import org.briarproject.briar.api.messaging.MessagingManager; import org.briarproject.briar.api.messaging.MessagingManager;
import org.jsoup.UnsupportedMimeTypeException;
import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
import java.util.logging.Logger;
import static java.util.Collections.emptyList; import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.util.LogUtils.logException;
import static org.briarproject.briar.android.util.UiUtils.observeForeverOnce;
import static org.briarproject.briar.api.messaging.MessagingConstants.MAX_IMAGE_SIZE;
@NotNullByDefault @NotNullByDefault
public class AttachmentCreator { public class AttachmentCreator {
private static Logger LOG = getLogger(AttachmentCreator.class.getName());
private final Application app; private final Application app;
@IoExecutor @IoExecutor
private final Executor ioExecutor; private final Executor ioExecutor;
private final MessagingManager messagingManager; private final MessagingManager messagingManager;
private final AttachmentRetriever retriever; private final AttachmentRetriever retriever;
private final CopyOnWriteArrayList<Uri> uris = new CopyOnWriteArrayList<>();
private final CopyOnWriteArrayList<AttachmentItemResult> itemResults =
new CopyOnWriteArrayList<>();
private final MutableLiveData<AttachmentResult> result =
new MutableLiveData<>();
@Nullable @Nullable
private AttachmentCreationTask task; private AttachmentCreationTask task;
@@ -39,19 +62,20 @@ public class AttachmentCreator {
this.retriever = retriever; this.retriever = retriever;
} }
/**
* Starts a background task to create attachments from the given URIs and
* returns a LiveData to monitor the progress of the task.
*/
@UiThread @UiThread
public LiveData<AttachmentResult> storeAttachments(GroupId groupId, public LiveData<AttachmentResult> storeAttachments(
Collection<Uri> uris) { LiveData<GroupId> groupId, Collection<Uri> newUris) {
if (task != null) throw new IllegalStateException(); if (task != null || !uris.isEmpty())
boolean needsSize = uris.size() == 1; throw new IllegalStateException();
task = new AttachmentCreationTask(ioExecutor, messagingManager, uris.addAll(newUris);
app.getContentResolver(), retriever, groupId, uris, needsSize); observeForeverOnce(groupId, id -> {
task.storeAttachments(); if (id == null) throw new IllegalStateException();
return task.getResult(); boolean needsSize = uris.size() == 1;
task = new AttachmentCreationTask(messagingManager,
app.getContentResolver(), this, id, uris, needsSize);
ioExecutor.execute(() -> task.storeAttachments());
});
return result;
} }
/** /**
@@ -61,49 +85,134 @@ public class AttachmentCreator {
*/ */
@UiThread @UiThread
public LiveData<AttachmentResult> getLiveAttachments() { public LiveData<AttachmentResult> getLiveAttachments() {
if (task == null) throw new IllegalStateException(); if (task == null || uris.isEmpty())
throw new IllegalStateException();
// A task is already running. It will update the result LiveData. // A task is already running. It will update the result LiveData.
// So nothing more to do here. // So nothing more to do here.
return task.getResult(); return result;
}
@IoExecutor
void onAttachmentHeaderReceived(Uri uri, AttachmentHeader h,
boolean needsSize) {
// get and cache AttachmentItem for ImagePreview
try {
Attachment a = retriever.getMessageAttachment(h);
AttachmentItem item = retriever.getAttachmentItem(a, needsSize);
if (item.hasError()) throw new IOException();
AttachmentItemResult itemResult =
new AttachmentItemResult(uri, item);
itemResults.add(itemResult);
result.postValue(getResult(false));
} catch (IOException | DbException e) {
logException(LOG, WARNING, e);
onAttachmentError(uri, e);
}
}
@IoExecutor
void onAttachmentError(Uri uri, Throwable t) {
// get error message
String errorMsg;
if (t instanceof UnsupportedMimeTypeException) {
String mimeType = ((UnsupportedMimeTypeException) t).getMimeType();
errorMsg = app.getString(
R.string.image_attach_error_invalid_mime_type, mimeType);
} else if (t instanceof FileTooBigException) {
int mb = MAX_IMAGE_SIZE / 1024 / 1024;
errorMsg = app.getString(R.string.image_attach_error_too_big, mb);
} else {
errorMsg = null; // generic error
}
AttachmentItemResult itemResult =
new AttachmentItemResult(uri, errorMsg);
itemResults.add(itemResult);
result.postValue(getResult(false));
// expect to receive a cancel from the UI
}
@IoExecutor
void onAttachmentCreationFinished() {
result.postValue(getResult(true));
} }
/**
* Returns the headers of any attachments created by
* {@link #storeAttachments(GroupId, Collection)}, unless
* {@link #onAttachmentsSent()} or {@link #cancel()} has been called.
*/
@UiThread @UiThread
public List<AttachmentHeader> getAttachmentHeadersForSending() { public List<AttachmentHeader> getAttachmentHeadersForSending() {
if (task == null) return emptyList(); List<AttachmentHeader> headers = new ArrayList<>(itemResults.size());
AttachmentResult result = task.getResult().getValue(); for (AttachmentItemResult itemResult : itemResults) {
if (result == null) return emptyList(); // check if we are trying to send attachment items with errors
List<AttachmentHeader> headers = new ArrayList<>(); if (itemResult.getItem() == null) throw new IllegalStateException();
for (AttachmentItemResult itemResult : result.getItemResults()) { headers.add(itemResult.getItem().getHeader());
AttachmentItem item = itemResult.getItem();
if (item != null) headers.add(item.getHeader());
} }
return headers; return headers;
} }
/** /**
* Informs the AttachmentCreator that the attachments created by * Marks the attachments as sent and adds the items to the cache for display
* {@link #storeAttachments(GroupId, Collection)} will be sent. *
* @param id The MessageId of the sent message.
*/ */
@UiThread @UiThread
public void onAttachmentsSent() { public void onAttachmentsSent(MessageId id) {
task = null; // Prevent cancel() from cancelling the task List<AttachmentItem> items = new ArrayList<>(itemResults.size());
for (AttachmentItemResult itemResult : itemResults) {
// check if we are trying to send attachment items with errors
if (itemResult.getItem() == null) throw new IllegalStateException();
items.add(itemResult.getItem());
}
retriever.cachePut(id, items);
resetState();
} }
/** /**
* Cancels the task started by * Needs to be called when created attachments will not be sent anymore.
* {@link #storeAttachments(GroupId, Collection)}, if any, unless
* {@link #onAttachmentsSent()} has been called.
*/ */
@UiThread @UiThread
public void cancel() { public void cancel() {
if (task != null) { if (task == null) throw new AssertionError();
task.cancel(); task.cancel();
task = null; deleteUnsentAttachments();
} resetState();
} }
@UiThread
private void resetState() {
task = null;
uris.clear();
itemResults.clear();
result.setValue(null);
}
@UiThread
public void deleteUnsentAttachments() {
// Make a copy for the IoExecutor as we clear the itemResults soon
List<AttachmentHeader> headers = new ArrayList<>(itemResults.size());
for (AttachmentItemResult itemResult : itemResults) {
// check if we are trying to send attachment items with errors
if (itemResult.getItem() != null)
headers.add(itemResult.getItem().getHeader());
}
ioExecutor.execute(() -> {
for (AttachmentHeader header : headers) {
try {
messagingManager.removeAttachment(header);
} catch (DbException e) {
logException(LOG, WARNING, e);
}
}
});
}
private AttachmentResult getResult(boolean finished) {
// Make a copy of the list,
// because our copy will continue to change in the background.
// (As it's a CopyOnWriteArrayList,
// the code that receives the result can safely do simple things
// like iterating over the list,
// but anything that involves calling more than one list method
// is still unsafe.)
Collection<AttachmentItemResult> items = new ArrayList<>(itemResults);
return new AttachmentResult(items, finished);
}
} }

View File

@@ -68,7 +68,7 @@ public class AttachmentItem implements Parcelable {
header = new AttachmentHeader(messageId, mimeType); header = new AttachmentHeader(messageId, mimeType);
} }
AttachmentHeader getHeader() { public AttachmentHeader getHeader() {
return header; return header;
} }

View File

@@ -15,18 +15,18 @@ public class AttachmentItemResult {
@Nullable @Nullable
private final AttachmentItem item; private final AttachmentItem item;
@Nullable @Nullable
private final Exception exception; private final String errorMsg;
AttachmentItemResult(Uri uri, AttachmentItem item) { AttachmentItemResult(Uri uri, AttachmentItem item) {
this.uri = uri; this.uri = uri;
this.item = item; this.item = item;
this.exception = null; this.errorMsg = null;
} }
AttachmentItemResult(Uri uri, Exception exception) { AttachmentItemResult(Uri uri, @Nullable String errorMsg) {
this.uri = uri; this.uri = uri;
this.item = null; this.item = null;
this.exception = exception; this.errorMsg = errorMsg;
} }
public Uri getUri() { public Uri getUri() {
@@ -43,8 +43,8 @@ public class AttachmentItemResult {
} }
@Nullable @Nullable
public Exception getException() { public String getErrorMsg() {
return exception; return errorMsg;
} }
} }

View File

@@ -12,13 +12,11 @@ public class AttachmentResult {
private final Collection<AttachmentItemResult> itemResults; private final Collection<AttachmentItemResult> itemResults;
private final boolean finished; private final boolean finished;
private final boolean success;
AttachmentResult(Collection<AttachmentItemResult> itemResults, public AttachmentResult(Collection<AttachmentItemResult> itemResults,
boolean finished, boolean success) { boolean finished) {
this.itemResults = itemResults; this.itemResults = itemResults;
this.finished = finished; this.finished = finished;
this.success = success;
} }
public Collection<AttachmentItemResult> getItemResults() { public Collection<AttachmentItemResult> getItemResults() {
@@ -29,7 +27,4 @@ public class AttachmentResult {
return finished; return finished;
} }
public boolean isSuccess() {
return success;
}
} }

View File

@@ -9,8 +9,6 @@ import android.webkit.MimeTypeMap;
import com.bumptech.glide.util.MarkEnforcingInputStream; import com.bumptech.glide.util.MarkEnforcingInputStream;
import org.briarproject.bramble.api.Pair;
import org.briarproject.bramble.api.db.DatabaseExecutor;
import org.briarproject.bramble.api.db.DbException; import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.MessageId; import org.briarproject.bramble.api.sync.MessageId;
@@ -22,7 +20,6 @@ import org.briarproject.briar.api.messaging.MessagingManager;
import java.io.BufferedInputStream; import java.io.BufferedInputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
@@ -38,16 +35,13 @@ import static android.support.media.ExifInterface.TAG_ORIENTATION;
import static java.util.logging.Level.WARNING; import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger; import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.util.IoUtils.tryToClose; import static org.briarproject.bramble.util.IoUtils.tryToClose;
import static org.briarproject.bramble.util.LogUtils.logDuration;
import static org.briarproject.bramble.util.LogUtils.logException; import static org.briarproject.bramble.util.LogUtils.logException;
import static org.briarproject.bramble.util.LogUtils.now;
@NotNullByDefault @NotNullByDefault
public class AttachmentRetriever { public class AttachmentRetriever {
private static final Logger LOG = private static final Logger LOG =
getLogger(AttachmentRetriever.class.getName()); getLogger(AttachmentRetriever.class.getName());
private static final int READ_LIMIT = 1024 * 8192; private static final int READ_LIMIT = 1024 * 8192;
private final MessagingManager messagingManager; private final MessagingManager messagingManager;
@@ -56,7 +50,7 @@ public class AttachmentRetriever {
private final int minWidth, maxWidth; private final int minWidth, maxWidth;
private final int minHeight, maxHeight; private final int minHeight, maxHeight;
private final Map<MessageId, AttachmentItem> attachmentCache = private final Map<MessageId, List<AttachmentItem>> attachmentCache =
new ConcurrentHashMap<>(); new ConcurrentHashMap<>();
@VisibleForTesting @VisibleForTesting
@@ -94,56 +88,27 @@ public class AttachmentRetriever {
}); });
} }
public void cachePut(AttachmentItem item) { public void cachePut(MessageId messageId,
attachmentCache.put(item.getMessageId(), item); List<AttachmentItem> attachments) {
attachmentCache.put(messageId, attachments);
} }
@Nullable @Nullable
public AttachmentItem cacheGet(MessageId attachmentId) { public List<AttachmentItem> cacheGet(MessageId messageId) {
return attachmentCache.get(attachmentId); return attachmentCache.get(messageId);
} }
@DatabaseExecutor public Attachment getMessageAttachment(AttachmentHeader h)
public List<Pair<AttachmentHeader, Attachment>> getMessageAttachments( throws DbException {
List<AttachmentHeader> headers) throws DbException { return messagingManager.getAttachment(h);
long start = now();
List<Pair<AttachmentHeader, Attachment>> attachments =
new ArrayList<>(headers.size());
for (AttachmentHeader h : headers) {
Attachment a = messagingManager.getAttachment(h.getMessageId());
attachments.add(new Pair<>(h, a));
}
logDuration(LOG, "Loading attachments", start);
return attachments;
}
Attachment getMessageAttachment(AttachmentHeader h) throws DbException {
return messagingManager.getAttachment(h.getMessageId());
}
/**
* Creates {@link AttachmentItem}s from the passed headers and Attachments.
* <p>
* Note: This closes the {@link Attachment}'s {@link InputStream}.
*/
public List<AttachmentItem> getAttachmentItems(
List<Pair<AttachmentHeader, Attachment>> attachments) {
boolean needsSize = attachments.size() == 1;
List<AttachmentItem> items = new ArrayList<>(attachments.size());
for (Pair<AttachmentHeader, Attachment> a : attachments) {
AttachmentItem item =
getAttachmentItem(a.getFirst(), a.getSecond(), needsSize);
items.add(item);
}
return items;
} }
/** /**
* Creates an {@link AttachmentItem} from the {@link Attachment}'s * Creates an {@link AttachmentItem} from the {@link Attachment}'s
* {@link InputStream} which will be closed when this method returns. * {@link InputStream} which will be closed when this method returns.
*/ */
AttachmentItem getAttachmentItem(AttachmentHeader h, Attachment a, public AttachmentItem getAttachmentItem(Attachment a, boolean needsSize) {
boolean needsSize) { AttachmentHeader h = a.getHeader();
if (!needsSize) { if (!needsSize) {
String extension = String extension =
imageHelper.getExtensionFromMimeType(h.getContentType()); imageHelper.getExtensionFromMimeType(h.getContentType());

View File

@@ -37,6 +37,7 @@ import org.briarproject.bramble.api.contact.event.ContactRemovedEvent;
import org.briarproject.bramble.api.db.DatabaseExecutor; import org.briarproject.bramble.api.db.DatabaseExecutor;
import org.briarproject.bramble.api.db.DbException; import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.NoSuchContactException; import org.briarproject.bramble.api.db.NoSuchContactException;
import org.briarproject.bramble.api.db.NoSuchMessageException;
import org.briarproject.bramble.api.event.Event; import org.briarproject.bramble.api.event.Event;
import org.briarproject.bramble.api.event.EventBus; import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.event.EventListener; import org.briarproject.bramble.api.event.EventListener;
@@ -81,6 +82,7 @@ import org.briarproject.briar.api.messaging.Attachment;
import org.briarproject.briar.api.messaging.AttachmentHeader; import org.briarproject.briar.api.messaging.AttachmentHeader;
import org.briarproject.briar.api.messaging.MessagingManager; import org.briarproject.briar.api.messaging.MessagingManager;
import org.briarproject.briar.api.messaging.PrivateMessageHeader; import org.briarproject.briar.api.messaging.PrivateMessageHeader;
import org.briarproject.briar.api.messaging.event.AttachmentReceivedEvent;
import org.briarproject.briar.api.privategroup.invitation.GroupInvitationManager; import org.briarproject.briar.api.privategroup.invitation.GroupInvitationManager;
import java.util.ArrayList; import java.util.ArrayList;
@@ -107,10 +109,12 @@ import static android.support.v7.util.SortedList.INVALID_POSITION;
import static android.view.Gravity.RIGHT; import static android.view.Gravity.RIGHT;
import static android.widget.Toast.LENGTH_SHORT; import static android.widget.Toast.LENGTH_SHORT;
import static java.util.Collections.emptyList; import static java.util.Collections.emptyList;
import static java.util.Collections.singletonList;
import static java.util.Collections.sort; import static java.util.Collections.sort;
import static java.util.Objects.requireNonNull; import static java.util.Objects.requireNonNull;
import static java.util.logging.Level.INFO; import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING; import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.util.LogUtils.logDuration; import static org.briarproject.bramble.util.LogUtils.logDuration;
import static org.briarproject.bramble.util.LogUtils.logException; import static org.briarproject.bramble.util.LogUtils.logException;
import static org.briarproject.bramble.util.LogUtils.now; import static org.briarproject.bramble.util.LogUtils.now;
@@ -138,7 +142,7 @@ public class ConversationActivity extends BriarActivity
public static final String CONTACT_ID = "briar.CONTACT_ID"; public static final String CONTACT_ID = "briar.CONTACT_ID";
private static final Logger LOG = private static final Logger LOG =
Logger.getLogger(ConversationActivity.class.getName()); getLogger(ConversationActivity.class.getName());
private static final int TRANSITION_DURATION_MS = 500; private static final int TRANSITION_DURATION_MS = 500;
private static final int ONBOARDING_DELAY_MS = 250; private static final int ONBOARDING_DELAY_MS = 250;
@@ -171,6 +175,8 @@ public class ConversationActivity extends BriarActivity
volatile GroupInvitationManager groupInvitationManager; volatile GroupInvitationManager groupInvitationManager;
private final Map<MessageId, String> textCache = new ConcurrentHashMap<>(); private final Map<MessageId, String> textCache = new ConcurrentHashMap<>();
private final Map<MessageId, PrivateMessageHeader> missingAttachments =
new ConcurrentHashMap<>();
private final Observer<String> contactNameObserver = name -> { private final Observer<String> contactNameObserver = name -> {
requireNonNull(name); requireNonNull(name);
loadMessages(); loadMessages();
@@ -434,31 +440,40 @@ public class ConversationActivity extends BriarActivity
}); });
} }
private void eagerlyLoadMessageSize(PrivateMessageHeader h) private void eagerlyLoadMessageSize(PrivateMessageHeader h) {
throws DbException { try {
MessageId id = h.getId(); MessageId id = h.getId();
// If the message has text, load it // If the message has text, load it
if (h.hasText()) { if (h.hasText()) {
String text = textCache.get(id); String text = textCache.get(id);
if (text == null) { if (text == null) {
LOG.info("Eagerly loading text for latest message"); LOG.info("Eagerly loading text for latest message");
text = messagingManager.getMessageText(id); text = messagingManager.getMessageText(id);
textCache.put(id, requireNonNull(text)); textCache.put(id, requireNonNull(text));
}
} }
} // If the message has a single image, load its size - for multiple
// If the message has a single image, load its size - for multiple // images we use a grid so the size is fixed
// images we use a grid so the size is fixed List<AttachmentHeader> headers = h.getAttachmentHeaders();
List<AttachmentHeader> headers = h.getAttachmentHeaders(); if (headers.size() == 1) {
if (headers.size() == 1) { List<AttachmentItem> items = attachmentRetriever.cacheGet(id);
MessageId attachmentId = headers.get(0).getMessageId(); if (items == null) {
AttachmentItem item = attachmentRetriever.cacheGet(attachmentId); LOG.info("Eagerly loading image size for latest message");
if (item == null) { AttachmentHeader header = headers.get(0);
LOG.info("Eagerly loading image size for latest message"); try {
item = attachmentRetriever.getAttachmentItems( Attachment a = attachmentRetriever
attachmentRetriever.getMessageAttachments(headers)) .getMessageAttachment(header);
.get(0); AttachmentItem item =
attachmentRetriever.cachePut(item); attachmentRetriever.getAttachmentItem(a, true);
attachmentRetriever.cachePut(id, singletonList(item));
} catch (NoSuchMessageException e) {
LOG.info("Attachment not received yet");
missingAttachments.put(header.getMessageId(), h);
}
}
} }
} catch (DbException e) {
logException(LOG, WARNING, e);
} }
} }
@@ -536,18 +551,30 @@ public class ConversationActivity extends BriarActivity
&& adapter.isScrolledToBottom(layoutManager); && adapter.isScrolledToBottom(layoutManager);
} }
private void loadMessageAttachments(MessageId messageId, private void loadMessageAttachments(PrivateMessageHeader h) {
List<AttachmentHeader> headers) { // TODO: Use placeholders for missing/invalid attachments
runOnDbThread(() -> { runOnDbThread(() -> {
try { try {
List<Pair<AttachmentHeader, Attachment>> attachments =
attachmentRetriever.getMessageAttachments(headers);
// TODO move getting the items off to IoExecutor, if size == 1 // TODO move getting the items off to IoExecutor, if size == 1
List<AttachmentItem> items = List<AttachmentHeader> headers = h.getAttachmentHeaders();
attachmentRetriever.getAttachmentItems(attachments); boolean needsSize = headers.size() == 1;
if (items.size() == 1) List<AttachmentItem> items = new ArrayList<>(headers.size());
attachmentRetriever.cachePut(items.get(0)); for (AttachmentHeader header : headers) {
displayMessageAttachments(messageId, items); try {
Attachment a = attachmentRetriever
.getMessageAttachment(header);
AttachmentItem item = attachmentRetriever
.getAttachmentItem(a, needsSize);
items.add(item);
} catch (NoSuchMessageException e) {
LOG.info("Attachment not received yet");
missingAttachments.put(header.getMessageId(), h);
return;
}
}
// Don't cache items unless all are present and valid
attachmentRetriever.cachePut(h.getId(), items);
displayMessageAttachments(h.getId(), items);
} catch (DbException e) { } catch (DbException e) {
logException(LOG, WARNING, e); logException(LOG, WARNING, e);
} }
@@ -570,6 +597,13 @@ public class ConversationActivity extends BriarActivity
@Override @Override
public void eventOccurred(Event e) { public void eventOccurred(Event e) {
if (e instanceof AttachmentReceivedEvent) {
AttachmentReceivedEvent a = (AttachmentReceivedEvent) e;
if (a.getContactId().equals(contactId)) {
LOG.info("Attachment received");
onAttachmentReceived(a.getMessageId());
}
}
if (e instanceof ContactRemovedEvent) { if (e instanceof ContactRemovedEvent) {
ContactRemovedEvent c = (ContactRemovedEvent) e; ContactRemovedEvent c = (ContactRemovedEvent) e;
if (c.getContactId().equals(contactId)) { if (c.getContactId().equals(contactId)) {
@@ -620,6 +654,15 @@ public class ConversationActivity extends BriarActivity
scrollToBottom(); scrollToBottom();
} }
@UiThread
private void onAttachmentReceived(MessageId attachmentId) {
PrivateMessageHeader h = missingAttachments.remove(attachmentId);
if (h != null) {
LOG.info("Missing attachment received");
loadMessageAttachments(h);
}
}
@UiThread @UiThread
private void onNewConversationMessage(ConversationMessageHeader h) { private void onNewConversationMessage(ConversationMessageHeader h) {
if (h instanceof ConversationRequest || if (h instanceof ConversationRequest ||
@@ -906,19 +949,14 @@ public class ConversationActivity extends BriarActivity
} }
@Override @Override
public List<AttachmentItem> getAttachmentItems(MessageId m, public List<AttachmentItem> getAttachmentItems(PrivateMessageHeader h) {
List<AttachmentHeader> headers) { List<AttachmentItem> attachments =
List<AttachmentItem> items = new ArrayList<>(headers.size()); attachmentRetriever.cacheGet(h.getId());
for (AttachmentHeader header : headers) { if (attachments == null) {
AttachmentItem item = loadMessageAttachments(h);
attachmentRetriever.cacheGet(header.getMessageId()); return emptyList();
if (item == null) {
loadMessageAttachments(m, headers);
return emptyList();
}
items.add(item);
} }
return items; return attachments;
} }
} }

View File

@@ -4,7 +4,6 @@ import android.app.Application;
import android.arch.lifecycle.AndroidViewModel; import android.arch.lifecycle.AndroidViewModel;
import android.arch.lifecycle.LiveData; import android.arch.lifecycle.LiveData;
import android.arch.lifecycle.MutableLiveData; import android.arch.lifecycle.MutableLiveData;
import android.arch.lifecycle.Observer;
import android.arch.lifecycle.Transformations; import android.arch.lifecycle.Transformations;
import android.net.Uri; import android.net.Uri;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
@@ -125,7 +124,7 @@ public class ConversationViewModel extends AndroidViewModel
@Override @Override
protected void onCleared() { protected void onCleared() {
super.onCleared(); super.onCleared();
attachmentCreator.cancel(); attachmentCreator.deleteUnsentAttachments();
} }
/** /**
@@ -201,23 +200,12 @@ public class ConversationViewModel extends AndroidViewModel
@UiThread @UiThread
public LiveData<AttachmentResult> storeAttachments(Collection<Uri> uris, public LiveData<AttachmentResult> storeAttachments(Collection<Uri> uris,
boolean restart) { boolean restart) {
MutableLiveData<AttachmentResult> delegate = new MutableLiveData<>(); if (restart) {
// messagingGroupId is loaded with the contact return attachmentCreator.getLiveAttachments();
observeForeverOnce(messagingGroupId, groupId -> { } else {
requireNonNull(groupId); // messagingGroupId is loaded with the contact
LiveData<AttachmentResult> result; return attachmentCreator.storeAttachments(messagingGroupId, uris);
if (restart) result = attachmentCreator.getLiveAttachments(); }
else result = attachmentCreator.storeAttachments(groupId, uris);
result.observeForever(new Observer<AttachmentResult>() {
@Override
public void onChanged(@Nullable AttachmentResult value) {
requireNonNull(value);
if (value.isFinished()) result.removeObserver(this);
delegate.setValue(value);
}
});
});
return delegate;
} }
@Override @Override
@@ -306,7 +294,7 @@ public class ConversationViewModel extends AndroidViewModel
} }
private void storeMessage(PrivateMessage m) { private void storeMessage(PrivateMessage m) {
attachmentCreator.onAttachmentsSent(); attachmentCreator.onAttachmentsSent(m.getMessage().getId());
dbExecutor.execute(() -> { dbExecutor.execute(() -> {
try { try {
long start = now(); long start = now();

View File

@@ -15,7 +15,6 @@ import org.briarproject.briar.api.forum.ForumInvitationRequest;
import org.briarproject.briar.api.forum.ForumInvitationResponse; import org.briarproject.briar.api.forum.ForumInvitationResponse;
import org.briarproject.briar.api.introduction.IntroductionRequest; import org.briarproject.briar.api.introduction.IntroductionRequest;
import org.briarproject.briar.api.introduction.IntroductionResponse; import org.briarproject.briar.api.introduction.IntroductionResponse;
import org.briarproject.briar.api.messaging.AttachmentHeader;
import org.briarproject.briar.api.messaging.PrivateMessageHeader; import org.briarproject.briar.api.messaging.PrivateMessageHeader;
import org.briarproject.briar.api.privategroup.invitation.GroupInvitationRequest; import org.briarproject.briar.api.privategroup.invitation.GroupInvitationRequest;
import org.briarproject.briar.api.privategroup.invitation.GroupInvitationResponse; import org.briarproject.briar.api.privategroup.invitation.GroupInvitationResponse;
@@ -56,8 +55,7 @@ class ConversationVisitor implements
if (h.getAttachmentHeaders().isEmpty()) { if (h.getAttachmentHeaders().isEmpty()) {
attachments = emptyList(); attachments = emptyList();
} else { } else {
attachments = attachmentCache attachments = attachmentCache.getAttachmentItems(h);
.getAttachmentItems(h.getId(), h.getAttachmentHeaders());
} }
if (h.isLocal()) { if (h.isLocal()) {
item = new ConversationMessageItem( item = new ConversationMessageItem(
@@ -295,7 +293,6 @@ class ConversationVisitor implements
} }
interface AttachmentCache { interface AttachmentCache {
List<AttachmentItem> getAttachmentItems(MessageId m, List<AttachmentItem> getAttachmentItems(PrivateMessageHeader h);
List<AttachmentHeader> headers);
} }
} }

View File

@@ -12,7 +12,6 @@ import org.briarproject.bramble.api.db.DatabaseExecutor;
import org.briarproject.bramble.api.db.DbException; import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.lifecycle.IoExecutor; import org.briarproject.bramble.api.lifecycle.IoExecutor;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.briar.android.attachment.AttachmentItem; import org.briarproject.briar.android.attachment.AttachmentItem;
import org.briarproject.briar.android.viewmodel.LiveEvent; import org.briarproject.briar.android.viewmodel.LiveEvent;
import org.briarproject.briar.android.viewmodel.MutableLiveEvent; import org.briarproject.briar.android.viewmodel.MutableLiveEvent;
@@ -135,10 +134,10 @@ public class ImageViewModel extends AndroidViewModel {
private void saveImage(AttachmentItem attachment, OutputStreamProvider osp, private void saveImage(AttachmentItem attachment, OutputStreamProvider osp,
@Nullable Runnable afterCopy) { @Nullable Runnable afterCopy) {
MessageId messageId = attachment.getMessageId();
dbExecutor.execute(() -> { dbExecutor.execute(() -> {
try { try {
Attachment a = messagingManager.getAttachment(messageId); Attachment a =
messagingManager.getAttachment(attachment.getHeader());
copyImageFromDb(a, osp, afterCopy); copyImageFromDb(a, osp, afterCopy);
} catch (DbException e) { } catch (DbException e) {
logException(LOG, WARNING, e); logException(LOG, WARNING, e);

View File

@@ -9,8 +9,8 @@ import com.bumptech.glide.load.data.DataFetcher;
import org.briarproject.bramble.api.db.DatabaseExecutor; import org.briarproject.bramble.api.db.DatabaseExecutor;
import org.briarproject.bramble.api.db.DbException; import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.briar.android.attachment.AttachmentItem; import org.briarproject.briar.android.attachment.AttachmentItem;
import org.briarproject.briar.api.messaging.Attachment;
import org.briarproject.briar.api.messaging.MessagingManager; import org.briarproject.briar.api.messaging.MessagingManager;
import java.io.InputStream; import java.io.InputStream;
@@ -50,11 +50,12 @@ class BriarDataFetcher implements DataFetcher<InputStream> {
@Override @Override
public void loadData(Priority priority, public void loadData(Priority priority,
DataCallback<? super InputStream> callback) { DataCallback<? super InputStream> callback) {
MessageId id = attachment.getMessageId();
dbExecutor.execute(() -> { dbExecutor.execute(() -> {
if (cancel) return; if (cancel) return;
try { try {
inputStream = messagingManager.getAttachment(id).getStream(); Attachment a =
messagingManager.getAttachment(attachment.getHeader());
inputStream = a.getStream();
callback.onDataReady(inputStream); callback.onDataReady(inputStream);
} catch (DbException e) { } catch (DbException e) {
callback.onLoadFailed(e); callback.onLoadFailed(e);

View File

@@ -22,8 +22,6 @@ import org.briarproject.briar.android.attachment.AttachmentItemResult;
import org.briarproject.briar.android.attachment.AttachmentManager; import org.briarproject.briar.android.attachment.AttachmentManager;
import org.briarproject.briar.android.attachment.AttachmentResult; import org.briarproject.briar.android.attachment.AttachmentResult;
import org.briarproject.briar.android.view.ImagePreview.ImagePreviewListener; import org.briarproject.briar.android.view.ImagePreview.ImagePreviewListener;
import org.briarproject.briar.api.messaging.FileTooBigException;
import org.jsoup.UnsupportedMimeTypeException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
@@ -43,11 +41,9 @@ import static android.support.v4.content.ContextCompat.getColor;
import static android.support.v4.view.AbsSavedState.EMPTY_STATE; import static android.support.v4.view.AbsSavedState.EMPTY_STATE;
import static android.view.View.GONE; import static android.view.View.GONE;
import static android.widget.Toast.LENGTH_LONG; import static android.widget.Toast.LENGTH_LONG;
import static java.util.Objects.requireNonNull;
import static org.briarproject.briar.android.util.UiUtils.resolveColorAttribute; import static org.briarproject.briar.android.util.UiUtils.resolveColorAttribute;
import static org.briarproject.briar.api.messaging.MessagingConstants.IMAGE_MIME_TYPES; import static org.briarproject.briar.api.messaging.MessagingConstants.IMAGE_MIME_TYPES;
import static org.briarproject.briar.api.messaging.MessagingConstants.MAX_ATTACHMENTS_PER_MESSAGE; import static org.briarproject.briar.api.messaging.MessagingConstants.MAX_ATTACHMENTS_PER_MESSAGE;
import static org.briarproject.briar.api.messaging.MessagingConstants.MAX_IMAGE_SIZE;
import static uk.co.samuelwall.materialtaptargetprompt.MaterialTapTargetPrompt.STATE_DISMISSED; import static uk.co.samuelwall.materialtaptargetprompt.MaterialTapTargetPrompt.STATE_DISMISSED;
import static uk.co.samuelwall.materialtaptargetprompt.MaterialTapTargetPrompt.STATE_FINISHED; import static uk.co.samuelwall.materialtaptargetprompt.MaterialTapTargetPrompt.STATE_FINISHED;
@@ -190,16 +186,18 @@ public class TextAttachmentController extends TextSendController
result.observe(attachmentListener, new Observer<AttachmentResult>() { result.observe(attachmentListener, new Observer<AttachmentResult>() {
@Override @Override
public void onChanged(@Nullable AttachmentResult attachmentResult) { public void onChanged(@Nullable AttachmentResult attachmentResult) {
requireNonNull(attachmentResult); if (attachmentResult == null) {
boolean finished = attachmentResult.isFinished(); // The fresh LiveData was deliberately set to null.
boolean success = attachmentResult.isSuccess(); // This means that we can stop observing it.
if (finished) {
result.removeObserver(this); result.removeObserver(this);
if (!success) return; } else {
boolean noError = onNewAttachmentItemResults(
attachmentResult.getItemResults());
if (noError && attachmentResult.isFinished()) {
onAllAttachmentsCreated();
result.removeObserver(this);
}
} }
boolean noError = onNewAttachmentItemResults(
attachmentResult.getItemResults());
if (noError && success) onAllAttachmentsCreated();
} }
}); });
} }
@@ -209,7 +207,7 @@ public class TextAttachmentController extends TextSendController
if (!loadingUris) throw new AssertionError(); if (!loadingUris) throw new AssertionError();
for (AttachmentItemResult result : itemResults) { for (AttachmentItemResult result : itemResults) {
if (result.hasError()) { if (result.hasError()) {
onError(requireNonNull(result.getException())); onError(result.getErrorMsg());
return false; return false;
} else { } else {
imagePreview.loadPreviewImage(result); imagePreview.loadPreviewImage(result);
@@ -255,20 +253,12 @@ public class TextAttachmentController extends TextSendController
} }
@UiThread @UiThread
private void onError(Exception e) { private void onError(@Nullable String errorMsg) {
String errorMsg; if (errorMsg == null) {
Context ctx = imagePreview.getContext(); errorMsg = imagePreview.getContext()
if (e instanceof UnsupportedMimeTypeException) { .getString(R.string.image_attach_error);
String mimeType = ((UnsupportedMimeTypeException) e).getMimeType();
errorMsg = ctx.getString(
R.string.image_attach_error_invalid_mime_type, mimeType);
} else if (e instanceof FileTooBigException) {
int mb = MAX_IMAGE_SIZE / 1024 / 1024;
errorMsg = ctx.getString(R.string.image_attach_error_too_big, mb);
} else {
errorMsg = ctx.getString(R.string.image_attach_error);
} }
Toast.makeText(ctx, errorMsg, LENGTH_LONG).show(); Toast.makeText(textInput.getContext(), errorMsg, LENGTH_LONG).show();
onCancel(); onCancel();
} }

View File

@@ -9,7 +9,6 @@ import org.briarproject.briar.api.messaging.MessagingManager;
import org.jmock.Expectations; import org.jmock.Expectations;
import org.junit.Test; import org.junit.Test;
import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.InputStream; import java.io.InputStream;
@@ -25,32 +24,26 @@ public class AttachmentRetrieverTest extends BrambleMockTestCase {
100, 50, 200, 75, 300 100, 50, 200, 75, 300
); );
private final MessageId msgId = new MessageId(getRandomId()); private final MessageId msgId = new MessageId(getRandomId());
private final Attachment attachment = new Attachment(
new BufferedInputStream(
new ByteArrayInputStream(getRandomBytes(42))));
private final MessagingManager messagingManager = private final MessagingManager messagingManager =
context.mock(MessagingManager.class); context.mock(MessagingManager.class);
private final ImageHelper imageHelper = context.mock(ImageHelper.class); private final ImageHelper imageHelper = context.mock(ImageHelper.class);
private final AttachmentRetriever controller = private final AttachmentRetriever retriever = new AttachmentRetriever(
new AttachmentRetriever( messagingManager,
messagingManager, dimensions,
dimensions, imageHelper
imageHelper );
);
@Test @Test
public void testNoSize() { public void testNoSize() {
String mimeType = "image/jpeg"; String mimeType = "image/jpeg";
AttachmentHeader h = getAttachmentHeader(mimeType); Attachment attachment = getAttachment(mimeType);
context.checking(new Expectations() {{ context.checking(new Expectations() {{
oneOf(imageHelper).getExtensionFromMimeType(mimeType); oneOf(imageHelper).getExtensionFromMimeType(mimeType);
will(returnValue("jpg")); will(returnValue("jpg"));
}}); }});
AttachmentItem item = AttachmentItem item = retriever.getAttachmentItem(attachment, false);
controller.getAttachmentItem(h, attachment, false);
assertEquals(mimeType, item.getMimeType()); assertEquals(mimeType, item.getMimeType());
assertEquals("jpg", item.getExtension()); assertEquals("jpg", item.getExtension());
assertFalse(item.hasError()); assertFalse(item.hasError());
@@ -59,22 +52,21 @@ public class AttachmentRetrieverTest extends BrambleMockTestCase {
@Test @Test
public void testNoSizeWrongMimeTypeProducesError() { public void testNoSizeWrongMimeTypeProducesError() {
String mimeType = "application/octet-stream"; String mimeType = "application/octet-stream";
AttachmentHeader h = getAttachmentHeader(mimeType); Attachment attachment = getAttachment(mimeType);
context.checking(new Expectations() {{ context.checking(new Expectations() {{
oneOf(imageHelper).getExtensionFromMimeType(mimeType); oneOf(imageHelper).getExtensionFromMimeType(mimeType);
will(returnValue(null)); will(returnValue(null));
}}); }});
AttachmentItem item = AttachmentItem item = retriever.getAttachmentItem(attachment, false);
controller.getAttachmentItem(h, attachment, false);
assertTrue(item.hasError()); assertTrue(item.hasError());
} }
@Test @Test
public void testSmallJpegImage() { public void testSmallJpegImage() {
String mimeType = "image/jpeg"; String mimeType = "image/jpeg";
AttachmentHeader h = getAttachmentHeader(mimeType); Attachment attachment = getAttachment(mimeType);
context.checking(new Expectations() {{ context.checking(new Expectations() {{
oneOf(imageHelper).decodeStream(with(any(InputStream.class))); oneOf(imageHelper).decodeStream(with(any(InputStream.class)));
@@ -83,7 +75,7 @@ public class AttachmentRetrieverTest extends BrambleMockTestCase {
will(returnValue("jpg")); will(returnValue("jpg"));
}}); }});
AttachmentItem item = controller.getAttachmentItem(h, attachment, true); AttachmentItem item = retriever.getAttachmentItem(attachment, true);
assertEquals(msgId, item.getMessageId()); assertEquals(msgId, item.getMessageId());
assertEquals(160, item.getWidth()); assertEquals(160, item.getWidth());
assertEquals(240, item.getHeight()); assertEquals(240, item.getHeight());
@@ -97,7 +89,7 @@ public class AttachmentRetrieverTest extends BrambleMockTestCase {
@Test @Test
public void testBigJpegImage() { public void testBigJpegImage() {
String mimeType = "image/jpeg"; String mimeType = "image/jpeg";
AttachmentHeader h = getAttachmentHeader(mimeType); Attachment attachment = getAttachment(mimeType);
context.checking(new Expectations() {{ context.checking(new Expectations() {{
oneOf(imageHelper).decodeStream(with(any(InputStream.class))); oneOf(imageHelper).decodeStream(with(any(InputStream.class)));
@@ -106,7 +98,7 @@ public class AttachmentRetrieverTest extends BrambleMockTestCase {
will(returnValue("jpg")); will(returnValue("jpg"));
}}); }});
AttachmentItem item = controller.getAttachmentItem(h, attachment, true); AttachmentItem item = retriever.getAttachmentItem(attachment, true);
assertEquals(1728, item.getWidth()); assertEquals(1728, item.getWidth());
assertEquals(2592, item.getHeight()); assertEquals(2592, item.getHeight());
assertEquals(dimensions.maxWidth, item.getThumbnailWidth()); assertEquals(dimensions.maxWidth, item.getThumbnailWidth());
@@ -116,7 +108,7 @@ public class AttachmentRetrieverTest extends BrambleMockTestCase {
@Test @Test
public void testMalformedError() { public void testMalformedError() {
AttachmentHeader h = getAttachmentHeader("image/jpeg"); Attachment attachment = getAttachment("image/jpeg");
context.checking(new Expectations() {{ context.checking(new Expectations() {{
oneOf(imageHelper).decodeStream(with(any(InputStream.class))); oneOf(imageHelper).decodeStream(with(any(InputStream.class)));
@@ -125,12 +117,13 @@ public class AttachmentRetrieverTest extends BrambleMockTestCase {
will(returnValue(null)); will(returnValue(null));
}}); }});
AttachmentItem item = controller.getAttachmentItem(h, attachment, true); AttachmentItem item = retriever.getAttachmentItem(attachment, true);
assertTrue(item.hasError()); assertTrue(item.hasError());
} }
private AttachmentHeader getAttachmentHeader(String contentType) { private Attachment getAttachment(String contentType) {
return new AttachmentHeader(msgId, contentType); AttachmentHeader header = new AttachmentHeader(msgId, contentType);
InputStream in = new ByteArrayInputStream(getRandomBytes(42));
return new Attachment(header, in);
} }
} }

View File

@@ -1,15 +1,27 @@
package org.briarproject.briar.api.messaging; package org.briarproject.briar.api.messaging;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import java.io.InputStream; import java.io.InputStream;
import javax.annotation.concurrent.Immutable;
@Immutable
@NotNullByDefault
public class Attachment { public class Attachment {
private final AttachmentHeader header;
private final InputStream stream; private final InputStream stream;
public Attachment(InputStream stream) { public Attachment(AttachmentHeader header, InputStream stream) {
this.header = header;
this.stream = stream; this.stream = stream;
} }
public AttachmentHeader getHeader() {
return header;
}
public InputStream getStream() { public InputStream getStream() {
return stream; return stream;
} }

View File

@@ -2,8 +2,5 @@ package org.briarproject.briar.api.messaging;
import java.io.IOException; import java.io.IOException;
/**
* An exception that is thrown when a file is too big to attach to a message.
*/
public class FileTooBigException extends IOException { public class FileTooBigException extends IOException {
} }

View File

@@ -0,0 +1,14 @@
package org.briarproject.briar.api.messaging;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
/**
* An exception that is thrown when an {@link AttachmentHeader} is used to
* load an {@link Attachment}, and the header refers to a message that is not
* an attachment, or to an attachment that does not have the expected content
* type.
*/
@NotNullByDefault
public class InvalidAttachmentException extends DbException {
}

View File

@@ -40,7 +40,7 @@ public interface MessagingManager extends ConversationClient {
/** /**
* Stores a local attachment message. * Stores a local attachment message.
* *
* @throws FileTooBigException * @throws FileTooBigException If the attachment is too big
*/ */
AttachmentHeader addLocalAttachment(GroupId groupId, long timestamp, AttachmentHeader addLocalAttachment(GroupId groupId, long timestamp,
String contentType, InputStream is) throws DbException, IOException; String contentType, InputStream is) throws DbException, IOException;
@@ -68,9 +68,13 @@ public interface MessagingManager extends ConversationClient {
String getMessageText(MessageId m) throws DbException; String getMessageText(MessageId m) throws DbException;
/** /**
* Returns the attachment with the given ID. * Returns the attachment with the given message ID and content type.
*
* @throws InvalidAttachmentException If the header refers to a message
* that is not an attachment, or to an attachment that does not have the
* expected content type
*/ */
Attachment getAttachment(MessageId m) throws DbException; Attachment getAttachment(AttachmentHeader h) throws DbException;
/** /**
* Returns true if the contact with the given {@link ContactId} does support * Returns true if the contact with the given {@link ContactId} does support

View File

@@ -23,6 +23,7 @@ import org.briarproject.bramble.api.sync.Message;
import org.briarproject.bramble.api.sync.MessageId; import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.bramble.api.sync.MessageStatus; import org.briarproject.bramble.api.sync.MessageStatus;
import org.briarproject.bramble.api.sync.validation.IncomingMessageHook; import org.briarproject.bramble.api.sync.validation.IncomingMessageHook;
import org.briarproject.bramble.api.system.Scheduler;
import org.briarproject.bramble.api.versioning.ClientVersioningManager; import org.briarproject.bramble.api.versioning.ClientVersioningManager;
import org.briarproject.bramble.api.versioning.ClientVersioningManager.ClientVersioningHook; import org.briarproject.bramble.api.versioning.ClientVersioningManager.ClientVersioningHook;
import org.briarproject.briar.api.client.MessageTracker; import org.briarproject.briar.api.client.MessageTracker;
@@ -32,6 +33,7 @@ import org.briarproject.briar.api.conversation.ConversationMessageHeader;
import org.briarproject.briar.api.messaging.Attachment; import org.briarproject.briar.api.messaging.Attachment;
import org.briarproject.briar.api.messaging.AttachmentHeader; import org.briarproject.briar.api.messaging.AttachmentHeader;
import org.briarproject.briar.api.messaging.FileTooBigException; import org.briarproject.briar.api.messaging.FileTooBigException;
import org.briarproject.briar.api.messaging.InvalidAttachmentException;
import org.briarproject.briar.api.messaging.MessagingManager; import org.briarproject.briar.api.messaging.MessagingManager;
import org.briarproject.briar.api.messaging.PrivateMessage; import org.briarproject.briar.api.messaging.PrivateMessage;
import org.briarproject.briar.api.messaging.PrivateMessageHeader; import org.briarproject.briar.api.messaging.PrivateMessageHeader;
@@ -46,13 +48,19 @@ import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.concurrent.ScheduledExecutorService;
import java.util.logging.Logger;
import javax.annotation.concurrent.Immutable; import javax.annotation.concurrent.Immutable;
import javax.inject.Inject; import javax.inject.Inject;
import static java.util.Collections.emptyList; import static java.util.Collections.emptyList;
import static java.util.concurrent.TimeUnit.SECONDS;
import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_BODY_LENGTH; import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_BODY_LENGTH;
import static org.briarproject.bramble.util.IoUtils.copyAndClose; import static org.briarproject.bramble.util.IoUtils.copyAndClose;
import static org.briarproject.bramble.util.LogUtils.logException;
import static org.briarproject.briar.client.MessageTrackerConstants.MSG_KEY_READ; import static org.briarproject.briar.client.MessageTrackerConstants.MSG_KEY_READ;
import static org.briarproject.briar.messaging.MessageTypes.ATTACHMENT; import static org.briarproject.briar.messaging.MessageTypes.ATTACHMENT;
import static org.briarproject.briar.messaging.MessageTypes.PRIVATE_MESSAGE; import static org.briarproject.briar.messaging.MessageTypes.PRIVATE_MESSAGE;
@@ -71,6 +79,10 @@ class MessagingManagerImpl implements MessagingManager, IncomingMessageHook,
ConversationClient, OpenDatabaseHook, ContactHook, ConversationClient, OpenDatabaseHook, ContactHook,
ClientVersioningHook { ClientVersioningHook {
private static final Logger LOG =
getLogger(MessagingManagerImpl.class.getName());
private final ScheduledExecutorService scheduler;
private final DatabaseComponent db; private final DatabaseComponent db;
private final ClientHelper clientHelper; private final ClientHelper clientHelper;
private final MetadataParser metadataParser; private final MetadataParser metadataParser;
@@ -79,10 +91,12 @@ class MessagingManagerImpl implements MessagingManager, IncomingMessageHook,
private final ContactGroupFactory contactGroupFactory; private final ContactGroupFactory contactGroupFactory;
@Inject @Inject
MessagingManagerImpl(DatabaseComponent db, ClientHelper clientHelper, MessagingManagerImpl(@Scheduler ScheduledExecutorService scheduler,
DatabaseComponent db, ClientHelper clientHelper,
ClientVersioningManager clientVersioningManager, ClientVersioningManager clientVersioningManager,
MetadataParser metadataParser, MessageTracker messageTracker, MetadataParser metadataParser, MessageTracker messageTracker,
ContactGroupFactory contactGroupFactory) { ContactGroupFactory contactGroupFactory) {
this.scheduler = scheduler;
this.db = db; this.db = db;
this.clientHelper = clientHelper; this.clientHelper = clientHelper;
this.metadataParser = metadataParser; this.metadataParser = metadataParser;
@@ -238,9 +252,20 @@ class MessagingManagerImpl implements MessagingManager, IncomingMessageHook,
meta.put(MSG_KEY_ATTACHMENT_HEADERS, headers); meta.put(MSG_KEY_ATTACHMENT_HEADERS, headers);
} }
// Mark attachments as shared and permanent now we're ready to send // Mark attachments as shared and permanent now we're ready to send
// FIXME: Revert
int i = 15;
for (AttachmentHeader a : m.getAttachmentHeaders()) { for (AttachmentHeader a : m.getAttachmentHeaders()) {
db.setMessageShared(txn, a.getMessageId()); scheduler.schedule(() -> {
db.setMessagePermanent(txn, a.getMessageId()); try {
db.transaction(false, txn1 -> {
db.setMessageShared(txn1, a.getMessageId());
db.setMessagePermanent(txn1, a.getMessageId());
});
} catch (DbException e) {
logException(LOG, WARNING, e);
}
}, i, SECONDS);
i *= 2;
} }
clientHelper.addLocalMessage(txn, m.getMessage(), meta, true, clientHelper.addLocalMessage(txn, m.getMessage(), meta, true,
false); false);
@@ -374,13 +399,20 @@ class MessagingManagerImpl implements MessagingManager, IncomingMessageHook,
} }
@Override @Override
public Attachment getAttachment(MessageId m) throws DbException { public Attachment getAttachment(AttachmentHeader h) throws DbException {
// TODO: Support large messages // TODO: Support large messages
MessageId m = h.getMessageId();
byte[] body = clientHelper.getMessage(m).getBody(); byte[] body = clientHelper.getMessage(m).getBody();
try { try {
BdfDictionary meta = clientHelper.getMessageMetadataAsDictionary(m); BdfDictionary meta = clientHelper.getMessageMetadataAsDictionary(m);
Long messageType = meta.getOptionalLong(MSG_KEY_MSG_TYPE);
if (messageType == null || messageType != ATTACHMENT)
throw new InvalidAttachmentException();
String contentType = meta.getString(MSG_KEY_CONTENT_TYPE);
if (!contentType.equals(h.getContentType()))
throw new InvalidAttachmentException();
int offset = meta.getLong(MSG_KEY_DESCRIPTOR_LENGTH).intValue(); int offset = meta.getLong(MSG_KEY_DESCRIPTOR_LENGTH).intValue();
return new Attachment(new ByteArrayInputStream(body, offset, return new Attachment(h, new ByteArrayInputStream(body, offset,
body.length - offset)); body.length - offset));
} catch (FormatException e) { } catch (FormatException e) {
throw new DbException(e); throw new DbException(e);