mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-12 18:59:06 +01:00
Compare commits
4 Commits
beta-1.5.3
...
attachment
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b6b15fe657 | ||
|
|
f3bbc7179e | ||
|
|
9abe32ab4b | ||
|
|
d07b98eae1 |
@@ -1,22 +1,33 @@
|
|||||||
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;
|
||||||
@@ -25,64 +36,97 @@ 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;
|
||||||
@Nullable
|
private final MutableLiveData<AttachmentResult> result;
|
||||||
private volatile AttachmentCreator attachmentCreator;
|
|
||||||
|
|
||||||
private volatile boolean canceled = false;
|
private volatile boolean canceled = false;
|
||||||
|
|
||||||
AttachmentCreationTask(MessagingManager messagingManager,
|
AttachmentCreationTask(Executor ioExecutor,
|
||||||
ContentResolver contentResolver,
|
MessagingManager messagingManager, ContentResolver contentResolver,
|
||||||
AttachmentCreator attachmentCreator, GroupId groupId,
|
AttachmentRetriever retriever, 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;
|
||||||
this.attachmentCreator = attachmentCreator;
|
result = new MutableLiveData<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void cancel() {
|
LiveData<AttachmentResult> getResult() {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cancels the task, asynchronously waits for it to finish, and deletes any
|
||||||
|
* created attachments.
|
||||||
|
*/
|
||||||
|
void cancel() {
|
||||||
canceled = true;
|
canceled = true;
|
||||||
attachmentCreator = null;
|
// Observe the task until it finishes (which may already have happened)
|
||||||
}
|
result.observeForever(new Observer<AttachmentResult>() {
|
||||||
|
@Override
|
||||||
@IoExecutor
|
public void onChanged(@Nullable AttachmentResult attachmentResult) {
|
||||||
public void storeAttachments() {
|
requireNonNull(attachmentResult);
|
||||||
for (Uri uri: uris) processUri(uri);
|
if (attachmentResult.isFinished()) {
|
||||||
AttachmentCreator attachmentCreator = this.attachmentCreator;
|
deleteUnsentAttachments(attachmentResult.getItemResults());
|
||||||
if (!canceled && attachmentCreator != null)
|
result.removeObserver(this);
|
||||||
attachmentCreator.onAttachmentCreationFinished();
|
}
|
||||||
this.attachmentCreator = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@IoExecutor
|
|
||||||
private void processUri(Uri uri) {
|
|
||||||
if (canceled) return;
|
|
||||||
try {
|
|
||||||
AttachmentHeader h = storeAttachment(uri);
|
|
||||||
AttachmentCreator attachmentCreator = this.attachmentCreator;
|
|
||||||
if (attachmentCreator != null) {
|
|
||||||
attachmentCreator.onAttachmentHeaderReceived(uri, h, needsSize);
|
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
|
private AttachmentItemResult processUri(Uri uri) {
|
||||||
|
AttachmentHeader header = null;
|
||||||
|
try {
|
||||||
|
header = storeAttachment(uri);
|
||||||
|
Attachment a = retriever.getMessageAttachment(header);
|
||||||
|
AttachmentItem item =
|
||||||
|
retriever.getAttachmentItem(header, a, 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);
|
||||||
AttachmentCreator attachmentCreator = this.attachmentCreator;
|
// If the attachment was already stored, delete it
|
||||||
if (attachmentCreator != null) {
|
tryToRemove(header);
|
||||||
attachmentCreator.onAttachmentError(uri, e);
|
|
||||||
}
|
|
||||||
canceled = true;
|
canceled = true;
|
||||||
|
return new AttachmentItemResult(uri, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -113,4 +157,31 @@ 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,56 +1,33 @@
|
|||||||
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.logging.Level.WARNING;
|
import static java.util.Collections.emptyList;
|
||||||
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;
|
||||||
|
|
||||||
@@ -62,20 +39,19 @@ 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(
|
public LiveData<AttachmentResult> storeAttachments(GroupId groupId,
|
||||||
LiveData<GroupId> groupId, Collection<Uri> newUris) {
|
Collection<Uri> uris) {
|
||||||
if (task != null || !uris.isEmpty())
|
if (task != null) throw new IllegalStateException();
|
||||||
throw new IllegalStateException();
|
boolean needsSize = uris.size() == 1;
|
||||||
uris.addAll(newUris);
|
task = new AttachmentCreationTask(ioExecutor, messagingManager,
|
||||||
observeForeverOnce(groupId, id -> {
|
app.getContentResolver(), retriever, groupId, uris, needsSize);
|
||||||
if (id == null) throw new IllegalStateException();
|
task.storeAttachments();
|
||||||
boolean needsSize = uris.size() == 1;
|
return task.getResult();
|
||||||
task = new AttachmentCreationTask(messagingManager,
|
|
||||||
app.getContentResolver(), this, id, uris, needsSize);
|
|
||||||
ioExecutor.execute(() -> task.storeAttachments());
|
|
||||||
});
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -85,134 +61,49 @@ public class AttachmentCreator {
|
|||||||
*/
|
*/
|
||||||
@UiThread
|
@UiThread
|
||||||
public LiveData<AttachmentResult> getLiveAttachments() {
|
public LiveData<AttachmentResult> getLiveAttachments() {
|
||||||
if (task == null || uris.isEmpty())
|
if (task == null) throw new IllegalStateException();
|
||||||
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 result;
|
return task.getResult();
|
||||||
}
|
|
||||||
|
|
||||||
@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(h, 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() {
|
||||||
List<AttachmentHeader> headers = new ArrayList<>(itemResults.size());
|
if (task == null) return emptyList();
|
||||||
for (AttachmentItemResult itemResult : itemResults) {
|
AttachmentResult result = task.getResult().getValue();
|
||||||
// check if we are trying to send attachment items with errors
|
if (result == null) return emptyList();
|
||||||
if (itemResult.getItem() == null) throw new IllegalStateException();
|
List<AttachmentHeader> headers = new ArrayList<>();
|
||||||
headers.add(itemResult.getItem().getHeader());
|
for (AttachmentItemResult itemResult : result.getItemResults()) {
|
||||||
|
AttachmentItem item = itemResult.getItem();
|
||||||
|
if (item != null) headers.add(item.getHeader());
|
||||||
}
|
}
|
||||||
return headers;
|
return headers;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Marks the attachments as sent and adds the items to the cache for display
|
* Informs the AttachmentCreator that the attachments created by
|
||||||
*
|
* {@link #storeAttachments(GroupId, Collection)} will be sent.
|
||||||
* @param id The MessageId of the sent message.
|
|
||||||
*/
|
*/
|
||||||
@UiThread
|
@UiThread
|
||||||
public void onAttachmentsSent(MessageId id) {
|
public void onAttachmentsSent() {
|
||||||
List<AttachmentItem> items = new ArrayList<>(itemResults.size());
|
task = null; // Prevent cancel() from cancelling the task
|
||||||
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();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Needs to be called when created attachments will not be sent anymore.
|
* Cancels the task started by
|
||||||
|
* {@link #storeAttachments(GroupId, Collection)}, if any, unless
|
||||||
|
* {@link #onAttachmentsSent()} has been called.
|
||||||
*/
|
*/
|
||||||
@UiThread
|
@UiThread
|
||||||
public void cancel() {
|
public void cancel() {
|
||||||
if (task == null) throw new AssertionError();
|
if (task != null) {
|
||||||
task.cancel();
|
task.cancel();
|
||||||
deleteUnsentAttachments();
|
task = null;
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,18 +15,18 @@ public class AttachmentItemResult {
|
|||||||
@Nullable
|
@Nullable
|
||||||
private final AttachmentItem item;
|
private final AttachmentItem item;
|
||||||
@Nullable
|
@Nullable
|
||||||
private final String errorMsg;
|
private final Exception exception;
|
||||||
|
|
||||||
AttachmentItemResult(Uri uri, AttachmentItem item) {
|
AttachmentItemResult(Uri uri, AttachmentItem item) {
|
||||||
this.uri = uri;
|
this.uri = uri;
|
||||||
this.item = item;
|
this.item = item;
|
||||||
this.errorMsg = null;
|
this.exception = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
AttachmentItemResult(Uri uri, @Nullable String errorMsg) {
|
AttachmentItemResult(Uri uri, Exception exception) {
|
||||||
this.uri = uri;
|
this.uri = uri;
|
||||||
this.item = null;
|
this.item = null;
|
||||||
this.errorMsg = errorMsg;
|
this.exception = exception;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Uri getUri() {
|
public Uri getUri() {
|
||||||
@@ -43,8 +43,8 @@ public class AttachmentItemResult {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
public String getErrorMsg() {
|
public Exception getException() {
|
||||||
return errorMsg;
|
return exception;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,11 +12,13 @@ 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;
|
||||||
|
|
||||||
public AttachmentResult(Collection<AttachmentItemResult> itemResults,
|
AttachmentResult(Collection<AttachmentItemResult> itemResults,
|
||||||
boolean finished) {
|
boolean finished, boolean success) {
|
||||||
this.itemResults = itemResults;
|
this.itemResults = itemResults;
|
||||||
this.finished = finished;
|
this.finished = finished;
|
||||||
|
this.success = success;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Collection<AttachmentItemResult> getItemResults() {
|
public Collection<AttachmentItemResult> getItemResults() {
|
||||||
@@ -27,4 +29,7 @@ public class AttachmentResult {
|
|||||||
return finished;
|
return finished;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isSuccess() {
|
||||||
|
return success;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -47,6 +47,7 @@ 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;
|
||||||
@@ -55,7 +56,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, List<AttachmentItem>> attachmentCache =
|
private final Map<MessageId, AttachmentItem> attachmentCache =
|
||||||
new ConcurrentHashMap<>();
|
new ConcurrentHashMap<>();
|
||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
@@ -93,13 +94,13 @@ public class AttachmentRetriever {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public void cachePut(MessageId messageId, List<AttachmentItem> attachments) {
|
public void cachePut(AttachmentItem item) {
|
||||||
attachmentCache.put(messageId, attachments);
|
attachmentCache.put(item.getMessageId(), item);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
public List<AttachmentItem> cacheGet(MessageId messageId) {
|
public AttachmentItem cacheGet(MessageId attachmentId) {
|
||||||
return attachmentCache.get(messageId);
|
return attachmentCache.get(attachmentId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@DatabaseExecutor
|
@DatabaseExecutor
|
||||||
@@ -116,7 +117,6 @@ public class AttachmentRetriever {
|
|||||||
return attachments;
|
return attachments;
|
||||||
}
|
}
|
||||||
|
|
||||||
@DatabaseExecutor
|
|
||||||
Attachment getMessageAttachment(AttachmentHeader h) throws DbException {
|
Attachment getMessageAttachment(AttachmentHeader h) throws DbException {
|
||||||
return messagingManager.getAttachment(h.getMessageId());
|
return messagingManager.getAttachment(h.getMessageId());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -448,14 +448,16 @@ public class ConversationActivity extends BriarActivity
|
|||||||
}
|
}
|
||||||
// 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
|
||||||
if (h.getAttachmentHeaders().size() == 1) {
|
List<AttachmentHeader> headers = h.getAttachmentHeaders();
|
||||||
List<AttachmentItem> items = attachmentRetriever.cacheGet(id);
|
if (headers.size() == 1) {
|
||||||
if (items == null) {
|
MessageId attachmentId = headers.get(0).getMessageId();
|
||||||
|
AttachmentItem item = attachmentRetriever.cacheGet(attachmentId);
|
||||||
|
if (item == null) {
|
||||||
LOG.info("Eagerly loading image size for latest message");
|
LOG.info("Eagerly loading image size for latest message");
|
||||||
items = attachmentRetriever.getAttachmentItems(
|
item = attachmentRetriever.getAttachmentItems(
|
||||||
attachmentRetriever.getMessageAttachments(
|
attachmentRetriever.getMessageAttachments(headers))
|
||||||
h.getAttachmentHeaders()));
|
.get(0);
|
||||||
attachmentRetriever.cachePut(id, items);
|
attachmentRetriever.cachePut(item);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -543,6 +545,8 @@ public class ConversationActivity extends BriarActivity
|
|||||||
// 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<AttachmentItem> items =
|
||||||
attachmentRetriever.getAttachmentItems(attachments);
|
attachmentRetriever.getAttachmentItems(attachments);
|
||||||
|
if (items.size() == 1)
|
||||||
|
attachmentRetriever.cachePut(items.get(0));
|
||||||
displayMessageAttachments(messageId, items);
|
displayMessageAttachments(messageId, items);
|
||||||
} catch (DbException e) {
|
} catch (DbException e) {
|
||||||
logException(LOG, WARNING, e);
|
logException(LOG, WARNING, e);
|
||||||
@@ -553,7 +557,6 @@ public class ConversationActivity extends BriarActivity
|
|||||||
private void displayMessageAttachments(MessageId m,
|
private void displayMessageAttachments(MessageId m,
|
||||||
List<AttachmentItem> items) {
|
List<AttachmentItem> items) {
|
||||||
runOnUiThreadUnlessDestroyed(() -> {
|
runOnUiThreadUnlessDestroyed(() -> {
|
||||||
attachmentRetriever.cachePut(m, items);
|
|
||||||
Pair<Integer, ConversationMessageItem> pair =
|
Pair<Integer, ConversationMessageItem> pair =
|
||||||
adapter.getMessageItem(m);
|
adapter.getMessageItem(m);
|
||||||
if (pair != null) {
|
if (pair != null) {
|
||||||
@@ -905,12 +908,17 @@ public class ConversationActivity extends BriarActivity
|
|||||||
@Override
|
@Override
|
||||||
public List<AttachmentItem> getAttachmentItems(MessageId m,
|
public List<AttachmentItem> getAttachmentItems(MessageId m,
|
||||||
List<AttachmentHeader> headers) {
|
List<AttachmentHeader> headers) {
|
||||||
List<AttachmentItem> attachments = attachmentRetriever.cacheGet(m);
|
List<AttachmentItem> items = new ArrayList<>(headers.size());
|
||||||
if (attachments == null) {
|
for (AttachmentHeader header : headers) {
|
||||||
loadMessageAttachments(m, headers);
|
AttachmentItem item =
|
||||||
return emptyList();
|
attachmentRetriever.cacheGet(header.getMessageId());
|
||||||
|
if (item == null) {
|
||||||
|
loadMessageAttachments(m, headers);
|
||||||
|
return emptyList();
|
||||||
|
}
|
||||||
|
items.add(item);
|
||||||
}
|
}
|
||||||
return attachments;
|
return items;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ 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;
|
||||||
@@ -124,7 +125,7 @@ public class ConversationViewModel extends AndroidViewModel
|
|||||||
@Override
|
@Override
|
||||||
protected void onCleared() {
|
protected void onCleared() {
|
||||||
super.onCleared();
|
super.onCleared();
|
||||||
attachmentCreator.deleteUnsentAttachments();
|
attachmentCreator.cancel();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -200,12 +201,23 @@ 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) {
|
||||||
if (restart) {
|
MutableLiveData<AttachmentResult> delegate = new MutableLiveData<>();
|
||||||
return attachmentCreator.getLiveAttachments();
|
// messagingGroupId is loaded with the contact
|
||||||
} else {
|
observeForeverOnce(messagingGroupId, groupId -> {
|
||||||
// messagingGroupId is loaded with the contact
|
requireNonNull(groupId);
|
||||||
return attachmentCreator.storeAttachments(messagingGroupId, uris);
|
LiveData<AttachmentResult> result;
|
||||||
}
|
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
|
||||||
@@ -294,7 +306,7 @@ public class ConversationViewModel extends AndroidViewModel
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void storeMessage(PrivateMessage m) {
|
private void storeMessage(PrivateMessage m) {
|
||||||
attachmentCreator.onAttachmentsSent(m.getMessage().getId());
|
attachmentCreator.onAttachmentsSent();
|
||||||
dbExecutor.execute(() -> {
|
dbExecutor.execute(() -> {
|
||||||
try {
|
try {
|
||||||
long start = now();
|
long start = now();
|
||||||
|
|||||||
@@ -22,6 +22,8 @@ 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;
|
||||||
@@ -41,9 +43,11 @@ 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;
|
||||||
|
|
||||||
@@ -186,18 +190,16 @@ 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) {
|
||||||
if (attachmentResult == null) {
|
requireNonNull(attachmentResult);
|
||||||
// The fresh LiveData was deliberately set to null.
|
boolean finished = attachmentResult.isFinished();
|
||||||
// This means that we can stop observing it.
|
boolean success = attachmentResult.isSuccess();
|
||||||
|
if (finished) {
|
||||||
result.removeObserver(this);
|
result.removeObserver(this);
|
||||||
} else {
|
if (!success) return;
|
||||||
boolean noError = onNewAttachmentItemResults(
|
|
||||||
attachmentResult.getItemResults());
|
|
||||||
if (noError && attachmentResult.isFinished()) {
|
|
||||||
onAllAttachmentsCreated();
|
|
||||||
result.removeObserver(this);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
boolean noError = onNewAttachmentItemResults(
|
||||||
|
attachmentResult.getItemResults());
|
||||||
|
if (noError && success) onAllAttachmentsCreated();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -207,7 +209,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(result.getErrorMsg());
|
onError(requireNonNull(result.getException()));
|
||||||
return false;
|
return false;
|
||||||
} else {
|
} else {
|
||||||
imagePreview.loadPreviewImage(result);
|
imagePreview.loadPreviewImage(result);
|
||||||
@@ -253,12 +255,20 @@ public class TextAttachmentController extends TextSendController
|
|||||||
}
|
}
|
||||||
|
|
||||||
@UiThread
|
@UiThread
|
||||||
private void onError(@Nullable String errorMsg) {
|
private void onError(Exception e) {
|
||||||
if (errorMsg == null) {
|
String errorMsg;
|
||||||
errorMsg = imagePreview.getContext()
|
Context ctx = imagePreview.getContext();
|
||||||
.getString(R.string.image_attach_error);
|
if (e instanceof UnsupportedMimeTypeException) {
|
||||||
|
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(textInput.getContext(), errorMsg, LENGTH_LONG).show();
|
Toast.makeText(ctx, errorMsg, LENGTH_LONG).show();
|
||||||
onCancel();
|
onCancel();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,5 +2,8 @@ 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 {
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user