mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-12 02:39:05 +01:00
Compare commits
29 Commits
feature-fl
...
removable-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d3692b2a97 | ||
|
|
bcbc96dc2d | ||
|
|
1ddcd6cfff | ||
|
|
fd810f5c16 | ||
|
|
3f5e131250 | ||
|
|
3ee516599d | ||
|
|
a5fb3bb4a4 | ||
|
|
eae329cdfa | ||
|
|
0ce0551f0d | ||
|
|
a198e7d08e | ||
|
|
bca6f1506e | ||
|
|
e420201b00 | ||
|
|
03248d04e5 | ||
|
|
2c39b02644 | ||
|
|
c9c6f3682c | ||
|
|
8f4a0ef030 | ||
|
|
5fe22bcd57 | ||
|
|
b4880af7e2 | ||
|
|
51d21bd669 | ||
|
|
b8f3728a0d | ||
|
|
bbfd4f137d | ||
|
|
7e3ca76dd1 | ||
|
|
524c8d26f8 | ||
|
|
7eccf7dac1 | ||
|
|
0bc06248ed | ||
|
|
c999f05cc7 | ||
|
|
428269b312 | ||
|
|
588e05ce83 | ||
|
|
f7875c99b6 |
@@ -47,7 +47,7 @@ public class AndroidBluetoothPluginFactory implements DuplexPluginFactory {
|
||||
private final BackoffFactory backoffFactory;
|
||||
|
||||
@Inject
|
||||
public AndroidBluetoothPluginFactory(@IoExecutor Executor ioExecutor,
|
||||
AndroidBluetoothPluginFactory(@IoExecutor Executor ioExecutor,
|
||||
@WakefulIoExecutor Executor wakefulIoExecutor,
|
||||
AndroidExecutor androidExecutor,
|
||||
AndroidWakeLockManager wakeLockManager,
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
package org.briarproject.bramble.plugin.file;
|
||||
|
||||
import android.app.Application;
|
||||
import android.net.Uri;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.properties.TransportProperties;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
|
||||
import static org.briarproject.bramble.api.plugin.file.RemovableDriveConstants.PROP_URI;
|
||||
import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty;
|
||||
|
||||
@Immutable
|
||||
@NotNullByDefault
|
||||
class AndroidRemovableDrivePlugin extends RemovableDrivePlugin {
|
||||
|
||||
private final Application app;
|
||||
|
||||
AndroidRemovableDrivePlugin(Application app, int maxLatency) {
|
||||
super(maxLatency);
|
||||
this.app = app;
|
||||
}
|
||||
|
||||
@Override
|
||||
InputStream openInputStream(TransportProperties p) throws IOException {
|
||||
String uri = p.get(PROP_URI);
|
||||
if (isNullOrEmpty(uri)) throw new IllegalArgumentException();
|
||||
return app.getContentResolver().openInputStream(Uri.parse(uri));
|
||||
}
|
||||
|
||||
@Override
|
||||
OutputStream openOutputStream(TransportProperties p) throws IOException {
|
||||
String uri = p.get(PROP_URI);
|
||||
if (isNullOrEmpty(uri)) throw new IllegalArgumentException();
|
||||
return app.getContentResolver().openOutputStream(Uri.parse(uri));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package org.briarproject.bramble.plugin.file;
|
||||
|
||||
import android.app.Application;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.plugin.PluginCallback;
|
||||
import org.briarproject.bramble.api.plugin.TransportId;
|
||||
import org.briarproject.bramble.api.plugin.simplex.SimplexPlugin;
|
||||
import org.briarproject.bramble.api.plugin.simplex.SimplexPluginFactory;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static java.util.concurrent.TimeUnit.DAYS;
|
||||
import static org.briarproject.bramble.api.plugin.file.RemovableDriveConstants.ID;
|
||||
|
||||
@Immutable
|
||||
@NotNullByDefault
|
||||
public class AndroidRemovableDrivePluginFactory implements
|
||||
SimplexPluginFactory {
|
||||
|
||||
private static final int MAX_LATENCY = (int) DAYS.toMillis(14);
|
||||
|
||||
private final Application app;
|
||||
|
||||
@Inject
|
||||
AndroidRemovableDrivePluginFactory(Application app) {
|
||||
this.app = app;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TransportId getId() {
|
||||
return ID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMaxLatency() {
|
||||
return MAX_LATENCY;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public SimplexPlugin createPlugin(PluginCallback callback) {
|
||||
return new AndroidRemovableDrivePlugin(app, MAX_LATENCY);
|
||||
}
|
||||
}
|
||||
@@ -37,7 +37,7 @@ public class AndroidLanTcpPluginFactory implements DuplexPluginFactory {
|
||||
private final Application app;
|
||||
|
||||
@Inject
|
||||
public AndroidLanTcpPluginFactory(@IoExecutor Executor ioExecutor,
|
||||
AndroidLanTcpPluginFactory(@IoExecutor Executor ioExecutor,
|
||||
@WakefulIoExecutor Executor wakefulIoExecutor,
|
||||
EventBus eventBus,
|
||||
BackoffFactory backoffFactory,
|
||||
|
||||
@@ -58,7 +58,7 @@ public class AndroidTorPluginFactory implements DuplexPluginFactory {
|
||||
private final File torDirectory;
|
||||
|
||||
@Inject
|
||||
public AndroidTorPluginFactory(@IoExecutor Executor ioExecutor,
|
||||
AndroidTorPluginFactory(@IoExecutor Executor ioExecutor,
|
||||
@WakefulIoExecutor Executor wakefulIoExecutor,
|
||||
Application app,
|
||||
NetworkManager networkManager,
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
package org.briarproject.bramble.api;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
|
||||
@NotNullByDefault
|
||||
public interface Consumer<T> {
|
||||
|
||||
void accept(T t);
|
||||
}
|
||||
@@ -292,6 +292,16 @@ public interface DatabaseComponent extends TransactionManager {
|
||||
*/
|
||||
Message getMessage(Transaction txn, MessageId m) throws DbException;
|
||||
|
||||
/**
|
||||
* Returns the total length, including headers, of any messages that are
|
||||
* eligible to be sent to the given contact via a transport with the given
|
||||
* max latency.
|
||||
* <p/>
|
||||
* Read-only.
|
||||
*/
|
||||
long getMessageBytesToSend(Transaction txn, ContactId c, int maxLatency)
|
||||
throws DbException;
|
||||
|
||||
/**
|
||||
* Returns the IDs of all delivered messages in the given group.
|
||||
* <p/>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package org.briarproject.bramble.api.plugin;
|
||||
package org.briarproject.bramble.api.plugin.file;
|
||||
|
||||
public interface FileConstants {
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
package org.briarproject.bramble.api.plugin.file;
|
||||
|
||||
import org.briarproject.bramble.api.plugin.TransportId;
|
||||
|
||||
public interface RemovableDriveConstants {
|
||||
|
||||
TransportId ID = new TransportId("org.briarproject.bramble.drive");
|
||||
|
||||
String PROP_PATH = "path";
|
||||
String PROP_URI = "uri";
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package org.briarproject.bramble.api.plugin.file;
|
||||
|
||||
import org.briarproject.bramble.api.contact.ContactId;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.properties.TransportProperties;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
@NotNullByDefault
|
||||
public interface RemovableDriveManager {
|
||||
|
||||
/**
|
||||
* Returns the currently running reader task for the given contact,
|
||||
* or null if no task is running.
|
||||
*/
|
||||
@Nullable
|
||||
RemovableDriveTask getCurrentReaderTask(ContactId c);
|
||||
|
||||
/**
|
||||
* Returns the currently running writer task for the given contact,
|
||||
* or null if no task is running.
|
||||
*/
|
||||
@Nullable
|
||||
RemovableDriveTask getCurrentWriterTask(ContactId c);
|
||||
|
||||
/**
|
||||
* Starts and returns a reader task for the given contact, reading from
|
||||
* a stream described by the given transport properties. If a reader task
|
||||
* for the contact is already running, it will be returned and the
|
||||
* transport properties will be ignored.
|
||||
*/
|
||||
RemovableDriveTask startReaderTask(ContactId c, TransportProperties p);
|
||||
|
||||
/**
|
||||
* Starts and returns a writer task for the given contact, writing to
|
||||
* a stream described by the given transport properties. If a writer task
|
||||
* for the contact is already running, it will be returned and the
|
||||
* transport properties will be ignored.
|
||||
*/
|
||||
RemovableDriveTask startWriterTask(ContactId c, TransportProperties p);
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
package org.briarproject.bramble.api.plugin.file;
|
||||
|
||||
import org.briarproject.bramble.api.Consumer;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.properties.TransportProperties;
|
||||
|
||||
@NotNullByDefault
|
||||
public interface RemovableDriveTask extends Runnable {
|
||||
|
||||
/**
|
||||
* Returns the {@link TransportProperties} that were used for creating
|
||||
* this task.
|
||||
*/
|
||||
TransportProperties getTransportProperties();
|
||||
|
||||
/**
|
||||
* Adds an observer to the task. The observer will be notified of state
|
||||
* changes on the event thread. If the task has already finished, the
|
||||
* observer will be notified of its final state.
|
||||
*/
|
||||
void addObserver(Consumer<State> observer);
|
||||
|
||||
/**
|
||||
* Removes an observer from the task.
|
||||
*/
|
||||
void removeObserver(Consumer<State> observer);
|
||||
|
||||
class State {
|
||||
|
||||
private final long done, total;
|
||||
private final boolean finished, success;
|
||||
|
||||
public State(long done, long total, boolean finished, boolean success) {
|
||||
this.done = done;
|
||||
this.total = total;
|
||||
this.finished = finished;
|
||||
this.success = success;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the total length in bytes of the messages read or written
|
||||
* so far.
|
||||
*/
|
||||
public long getDone() {
|
||||
return done;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the total length in bytes of the messages that will have
|
||||
* been read or written when the task is complete, or zero if the
|
||||
* total is unknown.
|
||||
*/
|
||||
public long getTotal() {
|
||||
return total;
|
||||
}
|
||||
|
||||
public boolean isFinished() {
|
||||
return finished;
|
||||
}
|
||||
|
||||
public boolean isSuccess() {
|
||||
return success;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -18,11 +18,13 @@ public class MessagesSentEvent extends Event {
|
||||
|
||||
private final ContactId contactId;
|
||||
private final Collection<MessageId> messageIds;
|
||||
private final long totalLength;
|
||||
|
||||
public MessagesSentEvent(ContactId contactId,
|
||||
Collection<MessageId> messageIds) {
|
||||
Collection<MessageId> messageIds, long totalLength) {
|
||||
this.contactId = contactId;
|
||||
this.messageIds = messageIds;
|
||||
this.totalLength = totalLength;
|
||||
}
|
||||
|
||||
public ContactId getContactId() {
|
||||
@@ -32,4 +34,8 @@ public class MessagesSentEvent extends Event {
|
||||
public Collection<MessageId> getMessageIds() {
|
||||
return messageIds;
|
||||
}
|
||||
|
||||
public long getTotalLength() {
|
||||
return totalLength;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -347,6 +347,16 @@ interface Database<T> {
|
||||
*/
|
||||
Message getMessage(T txn, MessageId m) throws DbException;
|
||||
|
||||
/**
|
||||
* Returns the total length, including headers, of any messages that are
|
||||
* eligible to be sent to the given contact via a transport with the given
|
||||
* max latency.
|
||||
* <p/>
|
||||
* Read-only.
|
||||
*/
|
||||
long getMessageBytesToSend(T txn, ContactId c, int maxLatency)
|
||||
throws DbException;
|
||||
|
||||
/**
|
||||
* Returns the IDs and states of all dependencies of the given message.
|
||||
* For missing dependencies and dependencies in other groups, the state
|
||||
|
||||
@@ -415,14 +415,17 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
||||
throw new NoSuchContactException();
|
||||
Collection<MessageId> ids =
|
||||
db.getMessagesToSend(txn, c, maxLength, maxLatency);
|
||||
long totalLength = 0;
|
||||
List<Message> messages = new ArrayList<>(ids.size());
|
||||
for (MessageId m : ids) {
|
||||
messages.add(db.getMessage(txn, m));
|
||||
Message message = db.getMessage(txn, m);
|
||||
totalLength += message.getRawLength();
|
||||
messages.add(message);
|
||||
db.updateExpiryTimeAndEta(txn, c, m, maxLatency);
|
||||
}
|
||||
if (ids.isEmpty()) return null;
|
||||
db.lowerRequestedFlag(txn, c, ids);
|
||||
transaction.attach(new MessagesSentEvent(c, ids));
|
||||
transaction.attach(new MessagesSentEvent(c, ids, totalLength));
|
||||
return messages;
|
||||
}
|
||||
|
||||
@@ -467,14 +470,17 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
||||
throw new NoSuchContactException();
|
||||
Collection<MessageId> ids =
|
||||
db.getRequestedMessagesToSend(txn, c, maxLength, maxLatency);
|
||||
long totalLength = 0;
|
||||
List<Message> messages = new ArrayList<>(ids.size());
|
||||
for (MessageId m : ids) {
|
||||
messages.add(db.getMessage(txn, m));
|
||||
Message message = db.getMessage(txn, m);
|
||||
totalLength += message.getRawLength();
|
||||
messages.add(message);
|
||||
db.updateExpiryTimeAndEta(txn, c, m, maxLatency);
|
||||
}
|
||||
if (ids.isEmpty()) return null;
|
||||
db.lowerRequestedFlag(txn, c, ids);
|
||||
transaction.attach(new MessagesSentEvent(c, ids));
|
||||
transaction.attach(new MessagesSentEvent(c, ids, totalLength));
|
||||
return messages;
|
||||
}
|
||||
|
||||
@@ -569,6 +575,15 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
||||
return db.getMessage(txn, m);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getMessageBytesToSend(Transaction transaction, ContactId c,
|
||||
int maxLatency) throws DbException {
|
||||
T txn = unbox(transaction);
|
||||
if (!db.containsContact(txn, c))
|
||||
throw new NoSuchContactException();
|
||||
return db.getMessageBytesToSend(txn, c, maxLatency);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<MessageId> getMessageIds(Transaction transaction,
|
||||
GroupId g) throws DbException {
|
||||
|
||||
@@ -1758,6 +1758,37 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getMessageBytesToSend(Connection txn, ContactId c,
|
||||
int maxLatency) throws DbException {
|
||||
long now = clock.currentTimeMillis();
|
||||
long eta = now + maxLatency;
|
||||
PreparedStatement ps = null;
|
||||
ResultSet rs = null;
|
||||
try {
|
||||
String sql = "SELECT SUM(length) FROM statuses"
|
||||
+ " WHERE contactId = ? AND state = ?"
|
||||
+ " AND groupShared = TRUE AND messageShared = TRUE"
|
||||
+ " AND deleted = FALSE AND seen = FALSE"
|
||||
+ " AND (expiry <= ? OR eta > ?)";
|
||||
ps = txn.prepareStatement(sql);
|
||||
ps.setInt(1, c.getInt());
|
||||
ps.setInt(2, DELIVERED.getValue());
|
||||
ps.setLong(3, now);
|
||||
ps.setLong(4, eta);
|
||||
rs = ps.executeQuery();
|
||||
rs.next();
|
||||
long total = rs.getInt(1);
|
||||
rs.close();
|
||||
ps.close();
|
||||
return total;
|
||||
} catch (SQLException e) {
|
||||
tryToClose(rs, LOG, WARNING);
|
||||
tryToClose(ps, LOG, WARNING);
|
||||
throw new DbException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<MessageId> getMessageIds(Connection txn, GroupId g)
|
||||
throws DbException {
|
||||
|
||||
@@ -0,0 +1,114 @@
|
||||
package org.briarproject.bramble.plugin.file;
|
||||
|
||||
import org.briarproject.bramble.api.Pair;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.plugin.ConnectionHandler;
|
||||
import org.briarproject.bramble.api.plugin.TransportConnectionReader;
|
||||
import org.briarproject.bramble.api.plugin.TransportConnectionWriter;
|
||||
import org.briarproject.bramble.api.plugin.TransportId;
|
||||
import org.briarproject.bramble.api.plugin.simplex.SimplexPlugin;
|
||||
import org.briarproject.bramble.api.properties.TransportProperties;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.Collection;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
|
||||
import static java.util.logging.Level.WARNING;
|
||||
import static java.util.logging.Logger.getLogger;
|
||||
import static org.briarproject.bramble.api.plugin.Plugin.State.ACTIVE;
|
||||
import static org.briarproject.bramble.api.plugin.file.RemovableDriveConstants.ID;
|
||||
import static org.briarproject.bramble.util.LogUtils.logException;
|
||||
|
||||
@Immutable
|
||||
@NotNullByDefault
|
||||
abstract class AbstractRemovableDrivePlugin implements SimplexPlugin {
|
||||
|
||||
private static final Logger LOG =
|
||||
getLogger(AbstractRemovableDrivePlugin.class.getName());
|
||||
|
||||
private final int maxLatency;
|
||||
|
||||
abstract InputStream openInputStream(TransportProperties p)
|
||||
throws IOException;
|
||||
|
||||
abstract OutputStream openOutputStream(TransportProperties p)
|
||||
throws IOException;
|
||||
|
||||
AbstractRemovableDrivePlugin(int maxLatency) {
|
||||
this.maxLatency = maxLatency;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TransportId getId() {
|
||||
return ID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMaxLatency() {
|
||||
return maxLatency;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMaxIdleTime() {
|
||||
// Unused for simplex transports
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public State getState() {
|
||||
return ACTIVE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getReasonsDisabled() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldPoll() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPollingInterval() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void poll(
|
||||
Collection<Pair<TransportProperties, ConnectionHandler>> properties) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public TransportConnectionReader createReader(TransportProperties p) {
|
||||
try {
|
||||
return new TransportInputStreamReader(openInputStream(p));
|
||||
} catch (IOException e) {
|
||||
logException(LOG, WARNING, e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public TransportConnectionWriter createWriter(TransportProperties p) {
|
||||
try {
|
||||
return new TransportOutputStreamWriter(this, openOutputStream(p));
|
||||
} catch (IOException e) {
|
||||
logException(LOG, WARNING, e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,8 +15,8 @@ import java.util.logging.Logger;
|
||||
|
||||
import static java.util.logging.Level.WARNING;
|
||||
import static java.util.logging.Logger.getLogger;
|
||||
import static org.briarproject.bramble.api.plugin.FileConstants.PROP_PATH;
|
||||
import static org.briarproject.bramble.api.plugin.Plugin.State.ACTIVE;
|
||||
import static org.briarproject.bramble.api.plugin.file.FileConstants.PROP_PATH;
|
||||
import static org.briarproject.bramble.util.LogUtils.logException;
|
||||
import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty;
|
||||
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
package org.briarproject.bramble.plugin.file;
|
||||
|
||||
import org.briarproject.bramble.api.contact.ContactId;
|
||||
import org.briarproject.bramble.api.lifecycle.IoExecutor;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.plugin.file.RemovableDriveManager;
|
||||
import org.briarproject.bramble.api.plugin.file.RemovableDriveTask;
|
||||
import org.briarproject.bramble.api.properties.TransportProperties;
|
||||
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.annotation.concurrent.ThreadSafe;
|
||||
import javax.inject.Inject;
|
||||
|
||||
@ThreadSafe
|
||||
@NotNullByDefault
|
||||
class RemovableDriveManagerImpl
|
||||
implements RemovableDriveManager, RemovableDriveTaskRegistry {
|
||||
|
||||
private final Executor ioExecutor;
|
||||
private final RemovableDriveTaskFactory taskFactory;
|
||||
private final ConcurrentHashMap<ContactId, RemovableDriveTask>
|
||||
readers = new ConcurrentHashMap<>();
|
||||
private final ConcurrentHashMap<ContactId, RemovableDriveTask>
|
||||
writers = new ConcurrentHashMap<>();
|
||||
|
||||
@Inject
|
||||
RemovableDriveManagerImpl(@IoExecutor Executor ioExecutor,
|
||||
RemovableDriveTaskFactory taskFactory) {
|
||||
this.ioExecutor = ioExecutor;
|
||||
this.taskFactory = taskFactory;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public RemovableDriveTask getCurrentReaderTask(ContactId c) {
|
||||
return readers.get(c);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public RemovableDriveTask getCurrentWriterTask(ContactId c) {
|
||||
return writers.get(c);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RemovableDriveTask startReaderTask(ContactId c,
|
||||
TransportProperties p) {
|
||||
RemovableDriveTask task = taskFactory.createReader(this, c, p);
|
||||
RemovableDriveTask old = readers.putIfAbsent(c, task);
|
||||
if (old == null) {
|
||||
ioExecutor.execute(task);
|
||||
return task;
|
||||
} else {
|
||||
return old;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public RemovableDriveTask startWriterTask(ContactId c,
|
||||
TransportProperties p) {
|
||||
RemovableDriveTask task = taskFactory.createWriter(this, c, p);
|
||||
RemovableDriveTask old = writers.putIfAbsent(c, task);
|
||||
if (old == null) {
|
||||
ioExecutor.execute(task);
|
||||
return task;
|
||||
} else {
|
||||
return old;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeReader(ContactId c, RemovableDriveTask task) {
|
||||
readers.remove(c, task);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeWriter(ContactId c, RemovableDriveTask task) {
|
||||
writers.remove(c, task);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package org.briarproject.bramble.plugin.file;
|
||||
|
||||
import org.briarproject.bramble.api.plugin.file.RemovableDriveManager;
|
||||
|
||||
import javax.inject.Singleton;
|
||||
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
|
||||
@Module
|
||||
public class RemovableDriveModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
RemovableDriveManager provideRemovableDriveManager(
|
||||
RemovableDriveManagerImpl removableDriveManager) {
|
||||
return removableDriveManager;
|
||||
}
|
||||
|
||||
@Provides
|
||||
RemovableDriveTaskFactory provideTaskFactory(
|
||||
RemovableDriveTaskFactoryImpl taskFactory) {
|
||||
return taskFactory;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package org.briarproject.bramble.plugin.file;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.properties.TransportProperties;
|
||||
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
|
||||
import static org.briarproject.bramble.api.plugin.file.RemovableDriveConstants.PROP_PATH;
|
||||
import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty;
|
||||
|
||||
@Immutable
|
||||
@NotNullByDefault
|
||||
class RemovableDrivePlugin extends AbstractRemovableDrivePlugin {
|
||||
|
||||
RemovableDrivePlugin(int maxLatency) {
|
||||
super(maxLatency);
|
||||
}
|
||||
|
||||
@Override
|
||||
InputStream openInputStream(TransportProperties p) throws IOException {
|
||||
String path = p.get(PROP_PATH);
|
||||
if (isNullOrEmpty(path)) throw new IllegalArgumentException();
|
||||
return new FileInputStream(path);
|
||||
}
|
||||
|
||||
@Override
|
||||
OutputStream openOutputStream(TransportProperties p) throws IOException {
|
||||
String path = p.get(PROP_PATH);
|
||||
if (isNullOrEmpty(path)) throw new IllegalArgumentException();
|
||||
return new FileOutputStream(path);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package org.briarproject.bramble.plugin.file;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.plugin.PluginCallback;
|
||||
import org.briarproject.bramble.api.plugin.TransportId;
|
||||
import org.briarproject.bramble.api.plugin.simplex.SimplexPlugin;
|
||||
import org.briarproject.bramble.api.plugin.simplex.SimplexPluginFactory;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static java.util.concurrent.TimeUnit.DAYS;
|
||||
import static org.briarproject.bramble.api.plugin.file.RemovableDriveConstants.ID;
|
||||
|
||||
@Immutable
|
||||
@NotNullByDefault
|
||||
public class RemovableDrivePluginFactory implements SimplexPluginFactory {
|
||||
|
||||
private static final int MAX_LATENCY = (int) DAYS.toMillis(14);
|
||||
|
||||
@Inject
|
||||
RemovableDrivePluginFactory() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public TransportId getId() {
|
||||
return ID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMaxLatency() {
|
||||
return MAX_LATENCY;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public SimplexPlugin createPlugin(PluginCallback callback) {
|
||||
return new RemovableDrivePlugin(MAX_LATENCY);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
package org.briarproject.bramble.plugin.file;
|
||||
|
||||
import org.briarproject.bramble.api.connection.ConnectionManager;
|
||||
import org.briarproject.bramble.api.contact.ContactId;
|
||||
import org.briarproject.bramble.api.event.Event;
|
||||
import org.briarproject.bramble.api.event.EventBus;
|
||||
import org.briarproject.bramble.api.event.EventListener;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.plugin.PluginManager;
|
||||
import org.briarproject.bramble.api.plugin.TransportConnectionReader;
|
||||
import org.briarproject.bramble.api.properties.TransportProperties;
|
||||
import org.briarproject.bramble.api.sync.event.MessageAddedEvent;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import static java.util.logging.Logger.getLogger;
|
||||
import static org.briarproject.bramble.api.plugin.file.RemovableDriveConstants.ID;
|
||||
|
||||
@NotNullByDefault
|
||||
class RemovableDriveReaderTask extends RemovableDriveTaskImpl
|
||||
implements EventListener {
|
||||
|
||||
private final static Logger LOG =
|
||||
getLogger(RemovableDriveReaderTask.class.getName());
|
||||
|
||||
RemovableDriveReaderTask(
|
||||
Executor eventExecutor,
|
||||
PluginManager pluginManager,
|
||||
ConnectionManager connectionManager,
|
||||
EventBus eventBus,
|
||||
RemovableDriveTaskRegistry registry,
|
||||
ContactId contactId,
|
||||
TransportProperties transportProperties) {
|
||||
super(eventExecutor, pluginManager, connectionManager, eventBus,
|
||||
registry, contactId, transportProperties);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
TransportConnectionReader r =
|
||||
getPlugin().createReader(transportProperties);
|
||||
if (r == null) {
|
||||
LOG.warning("Failed to create reader");
|
||||
registry.removeReader(contactId, this);
|
||||
setSuccess(false);
|
||||
return;
|
||||
}
|
||||
eventBus.addListener(this);
|
||||
connectionManager.manageIncomingConnection(ID, new DecoratedReader(r));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void eventOccurred(Event e) {
|
||||
if (e instanceof MessageAddedEvent) {
|
||||
MessageAddedEvent m = (MessageAddedEvent) e;
|
||||
if (contactId.equals(m.getContactId())) {
|
||||
LOG.info("Message received");
|
||||
addDone(m.getMessage().getRawLength());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class DecoratedReader implements TransportConnectionReader {
|
||||
|
||||
private final TransportConnectionReader delegate;
|
||||
|
||||
private DecoratedReader(TransportConnectionReader delegate) {
|
||||
this.delegate = delegate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream getInputStream() throws IOException {
|
||||
return delegate.getInputStream();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose(boolean exception, boolean recognised)
|
||||
throws IOException {
|
||||
delegate.dispose(exception, recognised);
|
||||
registry.removeReader(contactId, RemovableDriveReaderTask.this);
|
||||
eventBus.removeListener(RemovableDriveReaderTask.this);
|
||||
setSuccess(!exception && recognised);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package org.briarproject.bramble.plugin.file;
|
||||
|
||||
import org.briarproject.bramble.api.contact.ContactId;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.plugin.file.RemovableDriveTask;
|
||||
import org.briarproject.bramble.api.properties.TransportProperties;
|
||||
|
||||
@NotNullByDefault
|
||||
interface RemovableDriveTaskFactory {
|
||||
|
||||
RemovableDriveTask createReader(RemovableDriveTaskRegistry registry,
|
||||
ContactId c, TransportProperties p);
|
||||
|
||||
RemovableDriveTask createWriter(RemovableDriveTaskRegistry registry,
|
||||
ContactId c, TransportProperties p);
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
package org.briarproject.bramble.plugin.file;
|
||||
|
||||
import org.briarproject.bramble.api.connection.ConnectionManager;
|
||||
import org.briarproject.bramble.api.contact.ContactId;
|
||||
import org.briarproject.bramble.api.db.DatabaseComponent;
|
||||
import org.briarproject.bramble.api.event.EventBus;
|
||||
import org.briarproject.bramble.api.event.EventExecutor;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.plugin.PluginManager;
|
||||
import org.briarproject.bramble.api.plugin.file.RemovableDriveTask;
|
||||
import org.briarproject.bramble.api.properties.TransportProperties;
|
||||
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
import javax.inject.Inject;
|
||||
|
||||
@Immutable
|
||||
@NotNullByDefault
|
||||
class RemovableDriveTaskFactoryImpl implements RemovableDriveTaskFactory {
|
||||
|
||||
private final DatabaseComponent db;
|
||||
private final Executor eventExecutor;
|
||||
private final PluginManager pluginManager;
|
||||
private final ConnectionManager connectionManager;
|
||||
private final EventBus eventBus;
|
||||
|
||||
@Inject
|
||||
RemovableDriveTaskFactoryImpl(
|
||||
DatabaseComponent db,
|
||||
@EventExecutor Executor eventExecutor,
|
||||
PluginManager pluginManager,
|
||||
ConnectionManager connectionManager,
|
||||
EventBus eventBus) {
|
||||
this.db = db;
|
||||
this.eventExecutor = eventExecutor;
|
||||
this.pluginManager = pluginManager;
|
||||
this.connectionManager = connectionManager;
|
||||
this.eventBus = eventBus;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RemovableDriveTask createReader(RemovableDriveTaskRegistry registry,
|
||||
ContactId c, TransportProperties p) {
|
||||
return new RemovableDriveReaderTask(eventExecutor, pluginManager,
|
||||
connectionManager, eventBus, registry, c, p);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RemovableDriveTask createWriter(RemovableDriveTaskRegistry registry,
|
||||
ContactId c, TransportProperties p) {
|
||||
return new RemovableDriveWriterTask(db, eventExecutor, pluginManager,
|
||||
connectionManager, eventBus, registry, c, p);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
package org.briarproject.bramble.plugin.file;
|
||||
|
||||
import org.briarproject.bramble.api.Consumer;
|
||||
import org.briarproject.bramble.api.connection.ConnectionManager;
|
||||
import org.briarproject.bramble.api.contact.ContactId;
|
||||
import org.briarproject.bramble.api.event.EventBus;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.plugin.PluginManager;
|
||||
import org.briarproject.bramble.api.plugin.file.RemovableDriveTask;
|
||||
import org.briarproject.bramble.api.plugin.simplex.SimplexPlugin;
|
||||
import org.briarproject.bramble.api.properties.TransportProperties;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
import javax.annotation.concurrent.GuardedBy;
|
||||
import javax.annotation.concurrent.ThreadSafe;
|
||||
|
||||
import static java.lang.Math.min;
|
||||
import static org.briarproject.bramble.api.nullsafety.NullSafety.requireNonNull;
|
||||
import static org.briarproject.bramble.api.plugin.file.RemovableDriveConstants.ID;
|
||||
|
||||
@ThreadSafe
|
||||
@NotNullByDefault
|
||||
abstract class RemovableDriveTaskImpl implements RemovableDriveTask {
|
||||
|
||||
private final Executor eventExecutor;
|
||||
private final PluginManager pluginManager;
|
||||
final ConnectionManager connectionManager;
|
||||
final EventBus eventBus;
|
||||
final RemovableDriveTaskRegistry registry;
|
||||
final ContactId contactId;
|
||||
final TransportProperties transportProperties;
|
||||
|
||||
private final Object lock = new Object();
|
||||
@GuardedBy("lock")
|
||||
private final List<Consumer<State>> observers = new ArrayList<>();
|
||||
@GuardedBy("lock")
|
||||
private State state = new State(0, 0, false, false);
|
||||
|
||||
RemovableDriveTaskImpl(
|
||||
Executor eventExecutor,
|
||||
PluginManager pluginManager,
|
||||
ConnectionManager connectionManager,
|
||||
EventBus eventBus,
|
||||
RemovableDriveTaskRegistry registry,
|
||||
ContactId contactId,
|
||||
TransportProperties transportProperties) {
|
||||
this.eventExecutor = eventExecutor;
|
||||
this.pluginManager = pluginManager;
|
||||
this.connectionManager = connectionManager;
|
||||
this.eventBus = eventBus;
|
||||
this.registry = registry;
|
||||
this.contactId = contactId;
|
||||
this.transportProperties = transportProperties;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TransportProperties getTransportProperties() {
|
||||
return transportProperties;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addObserver(Consumer<State> o) {
|
||||
State state;
|
||||
synchronized (lock) {
|
||||
observers.add(o);
|
||||
state = this.state;
|
||||
}
|
||||
if (state.isFinished()) {
|
||||
eventExecutor.execute(() -> o.accept(state));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeObserver(Consumer<State> o) {
|
||||
synchronized (lock) {
|
||||
observers.remove(o);
|
||||
}
|
||||
}
|
||||
|
||||
SimplexPlugin getPlugin() {
|
||||
return (SimplexPlugin) requireNonNull(pluginManager.getPlugin(ID));
|
||||
}
|
||||
|
||||
void setTotal(long total) {
|
||||
synchronized (lock) {
|
||||
state = new State(state.getDone(), total, state.isFinished(),
|
||||
state.isSuccess());
|
||||
notifyObservers();
|
||||
}
|
||||
}
|
||||
|
||||
void addDone(long done) {
|
||||
synchronized (lock) {
|
||||
// Done and total come from different sources; make them consistent
|
||||
done = min(state.getDone() + done, state.getTotal());
|
||||
state = new State(done, state.getTotal(), state.isFinished(),
|
||||
state.isSuccess());
|
||||
}
|
||||
notifyObservers();
|
||||
}
|
||||
|
||||
void setSuccess(boolean success) {
|
||||
synchronized (lock) {
|
||||
state = new State(state.getDone(), state.getTotal(), true, success);
|
||||
}
|
||||
notifyObservers();
|
||||
}
|
||||
|
||||
@GuardedBy("lock")
|
||||
private void notifyObservers() {
|
||||
List<Consumer<State>> observers = new ArrayList<>(this.observers);
|
||||
State state = this.state;
|
||||
eventExecutor.execute(() -> {
|
||||
for (Consumer<State> o : observers) o.accept(state);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package org.briarproject.bramble.plugin.file;
|
||||
|
||||
import org.briarproject.bramble.api.contact.ContactId;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.plugin.file.RemovableDriveTask;
|
||||
|
||||
@NotNullByDefault
|
||||
interface RemovableDriveTaskRegistry {
|
||||
|
||||
void removeReader(ContactId c, RemovableDriveTask task);
|
||||
|
||||
void removeWriter(ContactId c, RemovableDriveTask task);
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
package org.briarproject.bramble.plugin.file;
|
||||
|
||||
import org.briarproject.bramble.api.connection.ConnectionManager;
|
||||
import org.briarproject.bramble.api.contact.ContactId;
|
||||
import org.briarproject.bramble.api.db.DatabaseComponent;
|
||||
import org.briarproject.bramble.api.db.DbException;
|
||||
import org.briarproject.bramble.api.event.Event;
|
||||
import org.briarproject.bramble.api.event.EventBus;
|
||||
import org.briarproject.bramble.api.event.EventListener;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.plugin.PluginManager;
|
||||
import org.briarproject.bramble.api.plugin.TransportConnectionWriter;
|
||||
import org.briarproject.bramble.api.plugin.simplex.SimplexPlugin;
|
||||
import org.briarproject.bramble.api.properties.TransportProperties;
|
||||
import org.briarproject.bramble.api.sync.event.MessagesSentEvent;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import static java.util.logging.Level.INFO;
|
||||
import static java.util.logging.Level.WARNING;
|
||||
import static java.util.logging.Logger.getLogger;
|
||||
import static org.briarproject.bramble.api.plugin.file.RemovableDriveConstants.ID;
|
||||
import static org.briarproject.bramble.util.LogUtils.logException;
|
||||
|
||||
@NotNullByDefault
|
||||
class RemovableDriveWriterTask extends RemovableDriveTaskImpl
|
||||
implements EventListener {
|
||||
|
||||
private static final Logger LOG =
|
||||
getLogger(RemovableDriveWriterTask.class.getName());
|
||||
|
||||
private final DatabaseComponent db;
|
||||
|
||||
RemovableDriveWriterTask(
|
||||
DatabaseComponent db,
|
||||
Executor eventExecutor,
|
||||
PluginManager pluginManager,
|
||||
ConnectionManager connectionManager,
|
||||
EventBus eventBus,
|
||||
RemovableDriveTaskRegistry registry,
|
||||
ContactId contactId,
|
||||
TransportProperties transportProperties) {
|
||||
super(eventExecutor, pluginManager, connectionManager, eventBus,
|
||||
registry, contactId, transportProperties);
|
||||
this.db = db;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
SimplexPlugin plugin = getPlugin();
|
||||
TransportConnectionWriter w = plugin.createWriter(transportProperties);
|
||||
if (w == null) {
|
||||
LOG.warning("Failed to create writer");
|
||||
registry.removeWriter(contactId, this);
|
||||
setSuccess(false);
|
||||
return;
|
||||
}
|
||||
int maxLatency = plugin.getMaxLatency();
|
||||
try {
|
||||
setTotal(db.transactionWithResult(true, txn ->
|
||||
db.getMessageBytesToSend(txn, contactId, maxLatency)));
|
||||
} catch (DbException e) {
|
||||
logException(LOG, WARNING, e);
|
||||
registry.removeWriter(contactId, this);
|
||||
setSuccess(false);
|
||||
return;
|
||||
}
|
||||
eventBus.addListener(this);
|
||||
connectionManager.manageOutgoingConnection(contactId, ID,
|
||||
new DecoratedWriter(w));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void eventOccurred(Event e) {
|
||||
if (e instanceof MessagesSentEvent) {
|
||||
MessagesSentEvent m = (MessagesSentEvent) e;
|
||||
if (contactId.equals(m.getContactId())) {
|
||||
if (LOG.isLoggable(INFO)) {
|
||||
LOG.info(m.getMessageIds().size() + " messages sent");
|
||||
}
|
||||
addDone(m.getTotalLength());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class DecoratedWriter implements TransportConnectionWriter {
|
||||
|
||||
private final TransportConnectionWriter delegate;
|
||||
|
||||
private DecoratedWriter(TransportConnectionWriter delegate) {
|
||||
this.delegate = delegate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMaxLatency() {
|
||||
return delegate.getMaxLatency();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMaxIdleTime() {
|
||||
return delegate.getMaxIdleTime();
|
||||
}
|
||||
|
||||
@Override
|
||||
public OutputStream getOutputStream() throws IOException {
|
||||
return delegate.getOutputStream();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose(boolean exception) throws IOException {
|
||||
delegate.dispose(exception);
|
||||
registry.removeWriter(contactId, RemovableDriveWriterTask.this);
|
||||
eventBus.removeListener(RemovableDriveWriterTask.this);
|
||||
setSuccess(!exception);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package org.briarproject.bramble.plugin.file;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.plugin.TransportConnectionReader;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import static java.util.logging.Level.WARNING;
|
||||
import static java.util.logging.Logger.getLogger;
|
||||
import static org.briarproject.bramble.util.IoUtils.tryToClose;
|
||||
|
||||
@NotNullByDefault
|
||||
class TransportInputStreamReader implements TransportConnectionReader {
|
||||
|
||||
private static final Logger LOG =
|
||||
getLogger(TransportInputStreamReader.class.getName());
|
||||
|
||||
private final InputStream in;
|
||||
|
||||
TransportInputStreamReader(InputStream in) {
|
||||
this.in = in;
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream getInputStream() {
|
||||
return in;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose(boolean exception, boolean recognised) {
|
||||
tryToClose(in, LOG, WARNING);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package org.briarproject.bramble.plugin.file;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.plugin.Plugin;
|
||||
import org.briarproject.bramble.api.plugin.TransportConnectionWriter;
|
||||
|
||||
import java.io.OutputStream;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import static java.util.logging.Level.WARNING;
|
||||
import static java.util.logging.Logger.getLogger;
|
||||
import static org.briarproject.bramble.util.IoUtils.tryToClose;
|
||||
|
||||
@NotNullByDefault
|
||||
class TransportOutputStreamWriter implements TransportConnectionWriter {
|
||||
|
||||
private static final Logger LOG =
|
||||
getLogger(TransportOutputStreamWriter.class.getName());
|
||||
|
||||
private final Plugin plugin;
|
||||
private final OutputStream out;
|
||||
|
||||
TransportOutputStreamWriter(Plugin plugin, OutputStream out) {
|
||||
this.plugin = plugin;
|
||||
this.out = out;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMaxLatency() {
|
||||
return plugin.getMaxLatency();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMaxIdleTime() {
|
||||
return plugin.getMaxIdleTime();
|
||||
}
|
||||
|
||||
@Override
|
||||
public OutputStream getOutputStream() {
|
||||
return out;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose(boolean exception) {
|
||||
tryToClose(out, LOG, WARNING);
|
||||
}
|
||||
}
|
||||
@@ -298,11 +298,11 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
throws Exception {
|
||||
context.checking(new Expectations() {{
|
||||
// Check whether the contact is in the DB (which it's not)
|
||||
exactly(18).of(database).startTransaction();
|
||||
exactly(19).of(database).startTransaction();
|
||||
will(returnValue(txn));
|
||||
exactly(18).of(database).containsContact(txn, contactId);
|
||||
exactly(19).of(database).containsContact(txn, contactId);
|
||||
will(returnValue(false));
|
||||
exactly(18).of(database).abortTransaction(txn);
|
||||
exactly(19).of(database).abortTransaction(txn);
|
||||
}});
|
||||
DatabaseComponent db = createDatabaseComponent(database, eventBus,
|
||||
eventExecutor, shutdownManager);
|
||||
@@ -349,7 +349,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
}
|
||||
|
||||
try {
|
||||
db.transaction(false, transaction ->
|
||||
db.transaction(true, transaction ->
|
||||
db.getContact(transaction, contactId));
|
||||
fail();
|
||||
} catch (NoSuchContactException expected) {
|
||||
@@ -357,7 +357,15 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
}
|
||||
|
||||
try {
|
||||
db.transaction(false, transaction ->
|
||||
db.transaction(true, transaction ->
|
||||
db.getMessageBytesToSend(transaction, contactId, 123));
|
||||
fail();
|
||||
} catch (NoSuchContactException expected) {
|
||||
// Expected
|
||||
}
|
||||
|
||||
try {
|
||||
db.transaction(true, transaction ->
|
||||
db.getMessageStatus(transaction, contactId, groupId));
|
||||
fail();
|
||||
} catch (NoSuchContactException expected) {
|
||||
@@ -365,7 +373,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
}
|
||||
|
||||
try {
|
||||
db.transaction(false, transaction ->
|
||||
db.transaction(true, transaction ->
|
||||
db.getMessageStatus(transaction, contactId, messageId));
|
||||
fail();
|
||||
} catch (NoSuchContactException expected) {
|
||||
@@ -373,7 +381,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
}
|
||||
|
||||
try {
|
||||
db.transaction(false, transaction ->
|
||||
db.transaction(true, transaction ->
|
||||
db.getGroupVisibility(transaction, contactId, groupId));
|
||||
fail();
|
||||
} catch (NoSuchContactException expected) {
|
||||
@@ -381,7 +389,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
}
|
||||
|
||||
try {
|
||||
db.transaction(false, transaction ->
|
||||
db.transaction(true, transaction ->
|
||||
db.getSyncVersions(transaction, contactId));
|
||||
fail();
|
||||
} catch (NoSuchContactException expected) {
|
||||
|
||||
@@ -227,6 +227,8 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
||||
assertEquals(singletonList(messageId), ids);
|
||||
ids = db.getMessagesToOffer(txn, contactId, 100, MAX_LATENCY);
|
||||
assertEquals(singletonList(messageId), ids);
|
||||
assertEquals(message.getRawLength(),
|
||||
db.getMessageBytesToSend(txn, contactId, MAX_LATENCY));
|
||||
|
||||
// Changing the status to seen = true should make the message unsendable
|
||||
db.raiseSeenFlag(txn, contactId, messageId);
|
||||
@@ -234,6 +236,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
||||
assertTrue(ids.isEmpty());
|
||||
ids = db.getMessagesToOffer(txn, contactId, 100, MAX_LATENCY);
|
||||
assertTrue(ids.isEmpty());
|
||||
assertEquals(0, db.getMessageBytesToSend(txn, contactId, MAX_LATENCY));
|
||||
|
||||
db.commitTransaction(txn);
|
||||
db.close();
|
||||
@@ -258,6 +261,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
||||
assertTrue(ids.isEmpty());
|
||||
ids = db.getMessagesToOffer(txn, contactId, 100, MAX_LATENCY);
|
||||
assertTrue(ids.isEmpty());
|
||||
assertEquals(0, db.getMessageBytesToSend(txn, contactId, MAX_LATENCY));
|
||||
|
||||
// Marking the message delivered should make it sendable
|
||||
db.setMessageState(txn, messageId, DELIVERED);
|
||||
@@ -265,6 +269,8 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
||||
assertEquals(singletonList(messageId), ids);
|
||||
ids = db.getMessagesToOffer(txn, contactId, 100, MAX_LATENCY);
|
||||
assertEquals(singletonList(messageId), ids);
|
||||
assertEquals(message.getRawLength(),
|
||||
db.getMessageBytesToSend(txn, contactId, MAX_LATENCY));
|
||||
|
||||
// Marking the message invalid should make it unsendable
|
||||
db.setMessageState(txn, messageId, INVALID);
|
||||
@@ -272,6 +278,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
||||
assertTrue(ids.isEmpty());
|
||||
ids = db.getMessagesToOffer(txn, contactId, 100, MAX_LATENCY);
|
||||
assertTrue(ids.isEmpty());
|
||||
assertEquals(0, db.getMessageBytesToSend(txn, contactId, MAX_LATENCY));
|
||||
|
||||
// Marking the message pending should make it unsendable
|
||||
db.setMessageState(txn, messageId, PENDING);
|
||||
@@ -279,6 +286,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
||||
assertTrue(ids.isEmpty());
|
||||
ids = db.getMessagesToOffer(txn, contactId, 100, MAX_LATENCY);
|
||||
assertTrue(ids.isEmpty());
|
||||
assertEquals(0, db.getMessageBytesToSend(txn, contactId, MAX_LATENCY));
|
||||
|
||||
db.commitTransaction(txn);
|
||||
db.close();
|
||||
@@ -302,6 +310,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
||||
assertTrue(ids.isEmpty());
|
||||
ids = db.getMessagesToOffer(txn, contactId, 100, MAX_LATENCY);
|
||||
assertTrue(ids.isEmpty());
|
||||
assertEquals(0, db.getMessageBytesToSend(txn, contactId, MAX_LATENCY));
|
||||
|
||||
// Making the group visible should not make the message sendable
|
||||
db.addGroupVisibility(txn, contactId, groupId, false);
|
||||
@@ -309,6 +318,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
||||
assertTrue(ids.isEmpty());
|
||||
ids = db.getMessagesToOffer(txn, contactId, 100, MAX_LATENCY);
|
||||
assertTrue(ids.isEmpty());
|
||||
assertEquals(0, db.getMessageBytesToSend(txn, contactId, MAX_LATENCY));
|
||||
|
||||
// Sharing the group should make the message sendable
|
||||
db.setGroupVisibility(txn, contactId, groupId, true);
|
||||
@@ -316,6 +326,8 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
||||
assertEquals(singletonList(messageId), ids);
|
||||
ids = db.getMessagesToOffer(txn, contactId, 100, MAX_LATENCY);
|
||||
assertEquals(singletonList(messageId), ids);
|
||||
assertEquals(message.getRawLength(),
|
||||
db.getMessageBytesToSend(txn, contactId, MAX_LATENCY));
|
||||
|
||||
// Unsharing the group should make the message unsendable
|
||||
db.setGroupVisibility(txn, contactId, groupId, false);
|
||||
@@ -323,6 +335,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
||||
assertTrue(ids.isEmpty());
|
||||
ids = db.getMessagesToOffer(txn, contactId, 100, MAX_LATENCY);
|
||||
assertTrue(ids.isEmpty());
|
||||
assertEquals(0, db.getMessageBytesToSend(txn, contactId, MAX_LATENCY));
|
||||
|
||||
// Making the group invisible should make the message unsendable
|
||||
db.removeGroupVisibility(txn, contactId, groupId);
|
||||
@@ -330,6 +343,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
||||
assertTrue(ids.isEmpty());
|
||||
ids = db.getMessagesToOffer(txn, contactId, 100, MAX_LATENCY);
|
||||
assertTrue(ids.isEmpty());
|
||||
assertEquals(0, db.getMessageBytesToSend(txn, contactId, MAX_LATENCY));
|
||||
|
||||
db.commitTransaction(txn);
|
||||
db.close();
|
||||
@@ -354,6 +368,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
||||
assertTrue(ids.isEmpty());
|
||||
ids = db.getMessagesToOffer(txn, contactId, 100, MAX_LATENCY);
|
||||
assertTrue(ids.isEmpty());
|
||||
assertEquals(0, db.getMessageBytesToSend(txn, contactId, MAX_LATENCY));
|
||||
|
||||
// Sharing the message should make it sendable
|
||||
db.setMessageShared(txn, messageId, true);
|
||||
@@ -361,6 +376,8 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
||||
assertEquals(singletonList(messageId), ids);
|
||||
ids = db.getMessagesToOffer(txn, contactId, 100, MAX_LATENCY);
|
||||
assertEquals(singletonList(messageId), ids);
|
||||
assertEquals(message.getRawLength(),
|
||||
db.getMessageBytesToSend(txn, contactId, MAX_LATENCY));
|
||||
|
||||
db.commitTransaction(txn);
|
||||
db.close();
|
||||
@@ -384,10 +401,15 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
||||
db.getMessagesToSend(txn, contactId, message.getRawLength() - 1,
|
||||
MAX_LATENCY);
|
||||
assertTrue(ids.isEmpty());
|
||||
assertEquals(message.getRawLength(),
|
||||
db.getMessageBytesToSend(txn, contactId, MAX_LATENCY));
|
||||
|
||||
// The message is just the right size to send
|
||||
ids = db.getMessagesToSend(txn, contactId, message.getRawLength(),
|
||||
MAX_LATENCY);
|
||||
assertEquals(singletonList(messageId), ids);
|
||||
assertEquals(message.getRawLength(),
|
||||
db.getMessageBytesToSend(txn, contactId, MAX_LATENCY));
|
||||
|
||||
db.commitTransaction(txn);
|
||||
db.close();
|
||||
|
||||
@@ -0,0 +1,168 @@
|
||||
package org.briarproject.bramble.plugin.file;
|
||||
|
||||
import org.briarproject.bramble.api.contact.ContactId;
|
||||
import org.briarproject.bramble.api.contact.ContactManager;
|
||||
import org.briarproject.bramble.api.crypto.SecretKey;
|
||||
import org.briarproject.bramble.api.event.Event;
|
||||
import org.briarproject.bramble.api.event.EventListener;
|
||||
import org.briarproject.bramble.api.identity.Author;
|
||||
import org.briarproject.bramble.api.identity.Identity;
|
||||
import org.briarproject.bramble.api.identity.IdentityManager;
|
||||
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.plugin.file.RemovableDriveTask;
|
||||
import org.briarproject.bramble.api.properties.TransportProperties;
|
||||
import org.briarproject.bramble.api.sync.event.MessageStateChangedEvent;
|
||||
import org.briarproject.bramble.test.BrambleTestCase;
|
||||
import org.briarproject.bramble.test.TestDatabaseConfigModule;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
|
||||
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
||||
import static org.briarproject.bramble.api.plugin.file.FileConstants.PROP_PATH;
|
||||
import static org.briarproject.bramble.api.sync.validation.MessageState.DELIVERED;
|
||||
import static org.briarproject.bramble.test.TestUtils.deleteTestDirectory;
|
||||
import static org.briarproject.bramble.test.TestUtils.getSecretKey;
|
||||
import static org.briarproject.bramble.test.TestUtils.getTestDirectory;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class RemovableDriveIntegrationTest extends BrambleTestCase {
|
||||
|
||||
private static final int TIMEOUT_MS = 5_000;
|
||||
|
||||
private final File testDir = getTestDirectory();
|
||||
private final File aliceDir = new File(testDir, "alice");
|
||||
private final File bobDir = new File(testDir, "bob");
|
||||
|
||||
private final SecretKey rootKey = getSecretKey();
|
||||
private final long timestamp = System.currentTimeMillis();
|
||||
|
||||
private RemovableDriveIntegrationTestComponent alice, bob;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
assertTrue(testDir.mkdirs());
|
||||
alice = DaggerRemovableDriveIntegrationTestComponent.builder()
|
||||
.testDatabaseConfigModule(
|
||||
new TestDatabaseConfigModule(aliceDir)).build();
|
||||
RemovableDriveIntegrationTestComponent.Helper
|
||||
.injectEagerSingletons(alice);
|
||||
bob = DaggerRemovableDriveIntegrationTestComponent.builder()
|
||||
.testDatabaseConfigModule(
|
||||
new TestDatabaseConfigModule(bobDir)).build();
|
||||
RemovableDriveIntegrationTestComponent.Helper
|
||||
.injectEagerSingletons(bob);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWriteAndRead() throws Exception {
|
||||
// Create the identities
|
||||
Identity aliceIdentity =
|
||||
alice.getIdentityManager().createIdentity("Alice");
|
||||
Identity bobIdentity = bob.getIdentityManager().createIdentity("Bob");
|
||||
// Set up the devices and get the contact IDs
|
||||
ContactId bobId = setUp(alice, aliceIdentity,
|
||||
bobIdentity.getLocalAuthor(), true);
|
||||
ContactId aliceId = setUp(bob, bobIdentity,
|
||||
aliceIdentity.getLocalAuthor(), false);
|
||||
// Sync Alice's client versions and transport properties
|
||||
read(bob, aliceId, write(alice, bobId), 2);
|
||||
// Sync Bob's client versions and transport properties
|
||||
read(alice, bobId, write(bob, aliceId), 2);
|
||||
}
|
||||
|
||||
private ContactId setUp(RemovableDriveIntegrationTestComponent device,
|
||||
Identity local, Author remote, boolean alice) throws Exception {
|
||||
// Add an identity for the user
|
||||
IdentityManager identityManager = device.getIdentityManager();
|
||||
identityManager.registerIdentity(local);
|
||||
// Start the lifecycle manager
|
||||
LifecycleManager lifecycleManager = device.getLifecycleManager();
|
||||
lifecycleManager.startServices(getSecretKey());
|
||||
lifecycleManager.waitForStartup();
|
||||
// Add the other user as a contact
|
||||
ContactManager contactManager = device.getContactManager();
|
||||
return contactManager.addContact(remote, local.getId(), rootKey,
|
||||
timestamp, alice, true, true);
|
||||
}
|
||||
|
||||
@SuppressWarnings("SameParameterValue")
|
||||
private void read(RemovableDriveIntegrationTestComponent device,
|
||||
ContactId contactId, File file, int deliveries) throws Exception {
|
||||
// Listen for message deliveries
|
||||
MessageDeliveryListener listener =
|
||||
new MessageDeliveryListener(deliveries);
|
||||
device.getEventBus().addListener(listener);
|
||||
// Read the incoming stream
|
||||
TransportProperties p = new TransportProperties();
|
||||
p.put(PROP_PATH, file.getAbsolutePath());
|
||||
RemovableDriveTask reader = device.getRemovableDriveManager()
|
||||
.startReaderTask(contactId, p);
|
||||
CountDownLatch disposedLatch = new CountDownLatch(1);
|
||||
reader.addObserver(state -> {
|
||||
if (state.isFinished()) disposedLatch.countDown();
|
||||
});
|
||||
// Wait for the messages to be delivered
|
||||
assertTrue(listener.delivered.await(TIMEOUT_MS, MILLISECONDS));
|
||||
// Clean up the listener
|
||||
device.getEventBus().removeListener(listener);
|
||||
// Wait for the reader to be disposed
|
||||
disposedLatch.await(TIMEOUT_MS, MILLISECONDS);
|
||||
}
|
||||
|
||||
private File write(RemovableDriveIntegrationTestComponent device,
|
||||
ContactId contactId) throws Exception {
|
||||
// Write the outgoing stream to a file
|
||||
File file = File.createTempFile("sync", ".tmp", testDir);
|
||||
TransportProperties p = new TransportProperties();
|
||||
p.put(PROP_PATH, file.getAbsolutePath());
|
||||
RemovableDriveTask writer = device.getRemovableDriveManager()
|
||||
.startWriterTask(contactId, p);
|
||||
CountDownLatch disposedLatch = new CountDownLatch(1);
|
||||
writer.addObserver(state -> {
|
||||
if (state.isFinished()) disposedLatch.countDown();
|
||||
});
|
||||
// Wait for the writer to be disposed
|
||||
disposedLatch.await(TIMEOUT_MS, MILLISECONDS);
|
||||
// Return the file containing the stream
|
||||
return file;
|
||||
}
|
||||
|
||||
private void tearDown(RemovableDriveIntegrationTestComponent device)
|
||||
throws Exception {
|
||||
// Stop the lifecycle manager
|
||||
LifecycleManager lifecycleManager = device.getLifecycleManager();
|
||||
lifecycleManager.stopServices();
|
||||
lifecycleManager.waitForShutdown();
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() throws Exception {
|
||||
// Tear down the devices
|
||||
tearDown(alice);
|
||||
tearDown(bob);
|
||||
deleteTestDirectory(testDir);
|
||||
}
|
||||
|
||||
@NotNullByDefault
|
||||
private static class MessageDeliveryListener implements EventListener {
|
||||
|
||||
private final CountDownLatch delivered;
|
||||
|
||||
private MessageDeliveryListener(int deliveries) {
|
||||
delivered = new CountDownLatch(deliveries);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void eventOccurred(Event e) {
|
||||
if (e instanceof MessageStateChangedEvent) {
|
||||
MessageStateChangedEvent m = (MessageStateChangedEvent) e;
|
||||
if (m.getState().equals(DELIVERED)) delivered.countDown();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package org.briarproject.bramble.plugin.file;
|
||||
|
||||
import org.briarproject.bramble.BrambleCoreEagerSingletons;
|
||||
import org.briarproject.bramble.BrambleCoreModule;
|
||||
import org.briarproject.bramble.api.contact.ContactManager;
|
||||
import org.briarproject.bramble.api.event.EventBus;
|
||||
import org.briarproject.bramble.api.identity.IdentityManager;
|
||||
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
|
||||
import org.briarproject.bramble.api.plugin.file.RemovableDriveManager;
|
||||
import org.briarproject.bramble.battery.DefaultBatteryManagerModule;
|
||||
import org.briarproject.bramble.event.DefaultEventExecutorModule;
|
||||
import org.briarproject.bramble.system.DefaultWakefulIoExecutorModule;
|
||||
import org.briarproject.bramble.system.TimeTravelModule;
|
||||
import org.briarproject.bramble.test.TestDatabaseConfigModule;
|
||||
import org.briarproject.bramble.test.TestSecureRandomModule;
|
||||
|
||||
import javax.inject.Singleton;
|
||||
|
||||
import dagger.Component;
|
||||
|
||||
@Singleton
|
||||
@Component(modules = {
|
||||
BrambleCoreModule.class,
|
||||
DefaultBatteryManagerModule.class,
|
||||
DefaultEventExecutorModule.class,
|
||||
DefaultWakefulIoExecutorModule.class,
|
||||
TestDatabaseConfigModule.class,
|
||||
RemovableDriveIntegrationTestModule.class,
|
||||
RemovableDriveModule.class,
|
||||
TestSecureRandomModule.class,
|
||||
TimeTravelModule.class
|
||||
})
|
||||
interface RemovableDriveIntegrationTestComponent
|
||||
extends BrambleCoreEagerSingletons {
|
||||
|
||||
ContactManager getContactManager();
|
||||
|
||||
EventBus getEventBus();
|
||||
|
||||
IdentityManager getIdentityManager();
|
||||
|
||||
LifecycleManager getLifecycleManager();
|
||||
|
||||
RemovableDriveManager getRemovableDriveManager();
|
||||
|
||||
class Helper {
|
||||
|
||||
public static void injectEagerSingletons(
|
||||
RemovableDriveIntegrationTestComponent c) {
|
||||
BrambleCoreEagerSingletons.Helper.injectEagerSingletons(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,61 +1,81 @@
|
||||
package org.briarproject.bramble.plugin;
|
||||
package org.briarproject.bramble.plugin.file;
|
||||
|
||||
import org.briarproject.bramble.api.FeatureFlags;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.plugin.BluetoothConstants;
|
||||
import org.briarproject.bramble.api.plugin.LanTcpConstants;
|
||||
import org.briarproject.bramble.api.plugin.PluginConfig;
|
||||
import org.briarproject.bramble.api.plugin.TransportId;
|
||||
import org.briarproject.bramble.api.plugin.duplex.DuplexPluginFactory;
|
||||
import org.briarproject.bramble.api.plugin.simplex.SimplexPluginFactory;
|
||||
import org.briarproject.bramble.plugin.bluetooth.JavaBluetoothPluginFactory;
|
||||
import org.briarproject.bramble.plugin.modem.ModemPluginFactory;
|
||||
import org.briarproject.bramble.plugin.tcp.LanTcpPluginFactory;
|
||||
import org.briarproject.bramble.plugin.tcp.WanTcpPluginFactory;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.inject.Singleton;
|
||||
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
|
||||
import static java.util.Arrays.asList;
|
||||
import static java.util.Collections.emptyList;
|
||||
import static java.util.Collections.emptyMap;
|
||||
import static java.util.Collections.singletonList;
|
||||
import static java.util.Collections.singletonMap;
|
||||
|
||||
@Module
|
||||
public class DesktopPluginModule extends PluginModule {
|
||||
class RemovableDriveIntegrationTestModule {
|
||||
|
||||
@Provides
|
||||
PluginConfig getPluginConfig(JavaBluetoothPluginFactory bluetooth,
|
||||
ModemPluginFactory modem, LanTcpPluginFactory lan,
|
||||
WanTcpPluginFactory wan) {
|
||||
@Singleton
|
||||
PluginConfig providePluginConfig(RemovableDrivePluginFactory drive) {
|
||||
@NotNullByDefault
|
||||
PluginConfig pluginConfig = new PluginConfig() {
|
||||
|
||||
@Override
|
||||
public Collection<DuplexPluginFactory> getDuplexFactories() {
|
||||
return asList(bluetooth, modem, lan, wan);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<SimplexPluginFactory> getSimplexFactories() {
|
||||
return emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<SimplexPluginFactory> getSimplexFactories() {
|
||||
return singletonList(drive);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldPoll() {
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<TransportId, List<TransportId>> getTransportPreferences() {
|
||||
// Prefer LAN to Bluetooth
|
||||
return singletonMap(BluetoothConstants.ID,
|
||||
singletonList(LanTcpConstants.ID));
|
||||
return emptyMap();
|
||||
}
|
||||
|
||||
};
|
||||
return pluginConfig;
|
||||
}
|
||||
|
||||
@Provides
|
||||
FeatureFlags provideFeatureFlags() {
|
||||
return new FeatureFlags() {
|
||||
|
||||
@Override
|
||||
public boolean shouldEnableImageAttachments() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldEnableProfilePictures() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldEnableDisappearingMessages() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldEnableConnectViaBluetooth() {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -55,7 +55,7 @@ public class UnixTorPluginFactory implements DuplexPluginFactory {
|
||||
private final File torDirectory;
|
||||
|
||||
@Inject
|
||||
public UnixTorPluginFactory(@IoExecutor Executor ioExecutor,
|
||||
UnixTorPluginFactory(@IoExecutor Executor ioExecutor,
|
||||
@IoExecutor Executor wakefulIoExecutor,
|
||||
NetworkManager networkManager,
|
||||
LocationUtils locationUtils,
|
||||
|
||||
@@ -437,6 +437,15 @@
|
||||
android:value="org.briarproject.briar.android.navdrawer.NavDrawerActivity" />
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name="org.briarproject.briar.android.removabledrive.RemovableDriveActivity"
|
||||
android:label="TODO Removable Drive"
|
||||
android:parentActivityName="org.briarproject.briar.android.conversation.ConversationActivity">
|
||||
<meta-data
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value="org.briarproject.briar.android.conversation.ConversationActivity" />
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name=".android.contact.add.remote.PendingContactListActivity"
|
||||
android:label="@string/pending_contact_requests"
|
||||
|
||||
@@ -28,6 +28,7 @@ import org.briarproject.bramble.api.system.AndroidExecutor;
|
||||
import org.briarproject.bramble.api.system.AndroidWakeLockManager;
|
||||
import org.briarproject.bramble.api.system.Clock;
|
||||
import org.briarproject.bramble.api.system.LocationUtils;
|
||||
import org.briarproject.bramble.plugin.file.RemovableDriveModule;
|
||||
import org.briarproject.bramble.plugin.tor.CircumventionProvider;
|
||||
import org.briarproject.bramble.system.ClockModule;
|
||||
import org.briarproject.briar.BriarCoreEagerSingletons;
|
||||
@@ -83,7 +84,8 @@ import dagger.Component;
|
||||
AppModule.class,
|
||||
AttachmentModule.class,
|
||||
ClockModule.class,
|
||||
MediaModule.class
|
||||
MediaModule.class,
|
||||
RemovableDriveModule.class
|
||||
})
|
||||
public interface AndroidComponent
|
||||
extends BrambleCoreEagerSingletons, BrambleAndroidEagerSingletons,
|
||||
|
||||
@@ -24,6 +24,8 @@ import org.briarproject.bramble.api.plugin.duplex.DuplexPluginFactory;
|
||||
import org.briarproject.bramble.api.plugin.simplex.SimplexPluginFactory;
|
||||
import org.briarproject.bramble.api.reporting.DevConfig;
|
||||
import org.briarproject.bramble.plugin.bluetooth.AndroidBluetoothPluginFactory;
|
||||
import org.briarproject.bramble.plugin.file.AndroidRemovableDrivePluginFactory;
|
||||
import org.briarproject.bramble.plugin.file.RemovableDriveModule;
|
||||
import org.briarproject.bramble.plugin.tcp.AndroidLanTcpPluginFactory;
|
||||
import org.briarproject.bramble.plugin.tor.AndroidTorPluginFactory;
|
||||
import org.briarproject.bramble.util.AndroidUtils;
|
||||
@@ -67,7 +69,6 @@ import dagger.Provides;
|
||||
import static android.content.Context.MODE_PRIVATE;
|
||||
import static android.os.Build.VERSION.SDK_INT;
|
||||
import static java.util.Arrays.asList;
|
||||
import static java.util.Collections.emptyList;
|
||||
import static java.util.Collections.singletonList;
|
||||
import static java.util.Collections.singletonMap;
|
||||
import static org.briarproject.bramble.api.reporting.ReportingConstants.DEV_ONION_ADDRESS;
|
||||
@@ -92,6 +93,7 @@ import static org.briarproject.briar.android.TestingConstants.IS_DEBUG_BUILD;
|
||||
GroupListModule.class,
|
||||
GroupConversationModule.class,
|
||||
SharingModule.class,
|
||||
RemovableDriveModule.class
|
||||
})
|
||||
public class AppModule {
|
||||
|
||||
@@ -149,8 +151,10 @@ public class AppModule {
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
PluginConfig providePluginConfig(AndroidBluetoothPluginFactory bluetooth,
|
||||
AndroidTorPluginFactory tor, AndroidLanTcpPluginFactory lan) {
|
||||
AndroidTorPluginFactory tor, AndroidLanTcpPluginFactory lan,
|
||||
AndroidRemovableDrivePluginFactory drive) {
|
||||
@NotNullByDefault
|
||||
PluginConfig pluginConfig = new PluginConfig() {
|
||||
|
||||
@@ -161,7 +165,7 @@ public class AppModule {
|
||||
|
||||
@Override
|
||||
public Collection<SimplexPluginFactory> getSimplexFactories() {
|
||||
return emptyList();
|
||||
return singletonList(drive);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -63,6 +63,7 @@ import org.briarproject.briar.android.privategroup.memberlist.GroupMemberModule;
|
||||
import org.briarproject.briar.android.privategroup.reveal.GroupRevealModule;
|
||||
import org.briarproject.briar.android.privategroup.reveal.RevealContactsActivity;
|
||||
import org.briarproject.briar.android.privategroup.reveal.RevealContactsFragment;
|
||||
import org.briarproject.briar.android.removabledrive.RemovableDriveActivity;
|
||||
import org.briarproject.briar.android.reporting.CrashFragment;
|
||||
import org.briarproject.briar.android.reporting.CrashReportActivity;
|
||||
import org.briarproject.briar.android.reporting.ReportFormFragment;
|
||||
@@ -176,6 +177,8 @@ public interface ActivityComponent {
|
||||
|
||||
void inject(CrashReportActivity crashReportActivity);
|
||||
|
||||
void inject(RemovableDriveActivity activity);
|
||||
|
||||
// Fragments
|
||||
|
||||
void inject(SetupFragment fragment);
|
||||
|
||||
@@ -14,5 +14,7 @@ public interface RequestCodes {
|
||||
int REQUEST_ATTACH_IMAGE = 13;
|
||||
int REQUEST_SAVE_ATTACHMENT = 14;
|
||||
int REQUEST_AVATAR_IMAGE = 15;
|
||||
int REQUEST_REMOVABLE_DRIVE_WRITE = 16;
|
||||
int REQUEST_REMOVABLE_DRIVE_READ = 17;
|
||||
|
||||
}
|
||||
|
||||
@@ -34,7 +34,6 @@ import org.briarproject.bramble.api.event.EventBus;
|
||||
import org.briarproject.bramble.api.event.EventListener;
|
||||
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
||||
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
||||
import org.briarproject.bramble.api.plugin.BluetoothConstants;
|
||||
import org.briarproject.bramble.api.plugin.event.ContactConnectedEvent;
|
||||
import org.briarproject.bramble.api.plugin.event.ContactDisconnectedEvent;
|
||||
import org.briarproject.bramble.api.sync.ClientId;
|
||||
@@ -54,6 +53,7 @@ import org.briarproject.briar.android.forum.ForumActivity;
|
||||
import org.briarproject.briar.android.fragment.BaseFragment.BaseFragmentListener;
|
||||
import org.briarproject.briar.android.introduction.IntroductionActivity;
|
||||
import org.briarproject.briar.android.privategroup.conversation.GroupActivity;
|
||||
import org.briarproject.briar.android.removabledrive.RemovableDriveActivity;
|
||||
import org.briarproject.briar.android.util.BriarSnackbarBuilder;
|
||||
import org.briarproject.briar.android.view.BriarRecyclerView;
|
||||
import org.briarproject.briar.android.view.ImagePreview;
|
||||
@@ -421,6 +421,11 @@ public class ConversationActivity extends BriarActivity
|
||||
} else if (itemId == R.id.action_social_remove_person) {
|
||||
askToRemoveContact();
|
||||
return true;
|
||||
} else if (itemId == R.id.action_removable_drive_write) {
|
||||
Intent intent = new Intent(this, RemovableDriveActivity.class);
|
||||
intent.putExtra(CONTACT_ID, contactId.getInt());
|
||||
startActivity(intent);
|
||||
return true;
|
||||
}
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,174 @@
|
||||
package org.briarproject.briar.android.removabledrive;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.view.MenuItem;
|
||||
import android.widget.Button;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.briarproject.bramble.api.contact.ContactId;
|
||||
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
||||
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
||||
import org.briarproject.bramble.api.plugin.file.RemovableDriveTask.State;
|
||||
import org.briarproject.briar.R;
|
||||
import org.briarproject.briar.android.activity.ActivityComponent;
|
||||
import org.briarproject.briar.android.activity.BriarActivity;
|
||||
import org.briarproject.briar.android.fragment.BaseFragment.BaseFragmentListener;
|
||||
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.Locale;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import androidx.activity.result.ActivityResultLauncher;
|
||||
import androidx.activity.result.contract.ActivityResultContracts;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.RequiresApi;
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
|
||||
import static org.briarproject.briar.android.conversation.ConversationActivity.CONTACT_ID;
|
||||
|
||||
// TODO 19 will be our requirement for sneakernet support, right. The file apis
|
||||
// used require this.
|
||||
@RequiresApi(api = 19)
|
||||
@MethodsNotNullByDefault
|
||||
@ParametersNotNullByDefault
|
||||
public class RemovableDriveActivity extends BriarActivity
|
||||
implements BaseFragmentListener {
|
||||
|
||||
@Inject
|
||||
ViewModelProvider.Factory viewModelFactory;
|
||||
private RemovableDriveViewModel viewModel;
|
||||
private TextView text;
|
||||
private Button writeButton;
|
||||
private Button readButton;
|
||||
|
||||
@Override
|
||||
public void injectActivity(ActivityComponent component) {
|
||||
component.inject(this);
|
||||
|
||||
viewModel = new ViewModelProvider(this, viewModelFactory)
|
||||
.get(RemovableDriveViewModel.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_removable_drive);
|
||||
text = findViewById(R.id.sneaker_text);
|
||||
writeButton = findViewById(R.id.sneaker_write);
|
||||
readButton = findViewById(R.id.sneaker_read);
|
||||
|
||||
Intent intent = getIntent();
|
||||
int contactId = intent.getIntExtra(CONTACT_ID, -1);
|
||||
if (contactId == -1) {
|
||||
writeButton.setEnabled(false);
|
||||
readButton.setEnabled(false);
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO we can pass an extra named DocumentsContract.EXTRA_INITIAL_URI
|
||||
// to have the filepicker start on the usb-stick -- if get hold of URI
|
||||
// of the same. USB manager API?
|
||||
// Overall, passing this extra requires extending the ready-made
|
||||
// contracts and overriding createIntent.
|
||||
|
||||
writeButton.setText("Write for contactId " + contactId);
|
||||
ActivityResultLauncher<String> createDocument =
|
||||
registerForActivityResult(
|
||||
new ActivityResultContracts.CreateDocument(),
|
||||
uri -> write(contactId, uri));
|
||||
writeButton.setOnClickListener(
|
||||
v -> createDocument.launch(viewModel.getFileName()));
|
||||
|
||||
readButton.setText("Read for contactId " + contactId);
|
||||
ActivityResultLauncher<String> getContent =
|
||||
registerForActivityResult(
|
||||
new ActivityResultContracts.GetContent(),
|
||||
uri -> read(contactId, uri));
|
||||
readButton.setOnClickListener(
|
||||
v -> getContent.launch("application/octet-stream"));
|
||||
|
||||
LiveData<State> state;
|
||||
state = viewModel.ongoingWrite(new ContactId(contactId));
|
||||
if (state == null) {
|
||||
writeButton.setEnabled(true);
|
||||
} else {
|
||||
say("\nOngoing write:");
|
||||
writeButton.setEnabled(false);
|
||||
state.observe(this, (taskState) -> handleState("write", taskState));
|
||||
}
|
||||
state = viewModel.ongoingRead(new ContactId(contactId));
|
||||
if (state == null) {
|
||||
readButton.setEnabled(true);
|
||||
} else {
|
||||
say("\nOngoing read:");
|
||||
readButton.setEnabled(false);
|
||||
state.observe(this, (taskState) -> handleState("read", taskState));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
|
||||
if (item.getItemId() == android.R.id.home) {
|
||||
onBackPressed();
|
||||
return true;
|
||||
}
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
private void write(int contactId, @Nullable Uri uri) {
|
||||
if (contactId == -1) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
if (uri == null) {
|
||||
say("no URI picked for write");
|
||||
return;
|
||||
}
|
||||
say("\nWriting to URI: " + uri);
|
||||
writeButton.setEnabled(false);
|
||||
LiveData<State> state = viewModel.write(new ContactId(contactId), uri);
|
||||
state.observe(this, (taskState) -> handleState("write", taskState));
|
||||
}
|
||||
|
||||
private void read(int contactId, @Nullable Uri uri) {
|
||||
if (contactId == -1) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
if (uri == null) {
|
||||
say("no URI picked for read");
|
||||
return;
|
||||
}
|
||||
say("\nReading from URI: " + uri);
|
||||
readButton.setEnabled(false);
|
||||
LiveData<State> state = viewModel.read(new ContactId(contactId), uri);
|
||||
state.observe(this, (taskState) -> handleState("read", taskState));
|
||||
}
|
||||
|
||||
private void handleState(String action, State taskState) {
|
||||
say(String.format(Locale.getDefault(),
|
||||
"%s: bytes done: %d of %d. %s. %s.",
|
||||
action, taskState.getDone(), taskState.getTotal(),
|
||||
taskState.isFinished() ? "Finished" : "Ongoing",
|
||||
taskState.isFinished() ?
|
||||
(taskState.isSuccess() ? "Success" : "Failed") : ".."));
|
||||
if (taskState.isFinished()) {
|
||||
if (action.equals("write")) {
|
||||
writeButton.setEnabled(true);
|
||||
} else if (action.equals("read")) {
|
||||
readButton.setEnabled(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void say(String txt) {
|
||||
String time = new SimpleDateFormat("HH:mm:ss", Locale.getDefault())
|
||||
.format(new Date());
|
||||
txt = String.format("%s %s\n", time, txt);
|
||||
text.setText(text.getText().toString().concat(txt));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package org.briarproject.briar.android.removabledrive;
|
||||
|
||||
import org.briarproject.briar.android.viewmodel.ViewModelKey;
|
||||
|
||||
import androidx.lifecycle.ViewModel;
|
||||
import dagger.Binds;
|
||||
import dagger.Module;
|
||||
import dagger.multibindings.IntoMap;
|
||||
|
||||
@Module
|
||||
public interface RemovableDriveModule {
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@ViewModelKey(RemovableDriveViewModel.class)
|
||||
ViewModel bindRemovableDriveViewModel(RemovableDriveViewModel removableDriveViewModel);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
package org.briarproject.briar.android.removabledrive;
|
||||
|
||||
import android.app.Application;
|
||||
import android.net.Uri;
|
||||
|
||||
import org.briarproject.bramble.api.Consumer;
|
||||
import org.briarproject.bramble.api.contact.ContactId;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.plugin.file.RemovableDriveManager;
|
||||
import org.briarproject.bramble.api.plugin.file.RemovableDriveTask;
|
||||
import org.briarproject.bramble.api.plugin.file.RemovableDriveTask.State;
|
||||
import org.briarproject.bramble.api.properties.TransportProperties;
|
||||
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.inject.Inject;
|
||||
|
||||
import androidx.lifecycle.AndroidViewModel;
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.MutableLiveData;
|
||||
|
||||
import static java.util.Locale.US;
|
||||
import static java.util.logging.Logger.getLogger;
|
||||
import static org.briarproject.bramble.api.plugin.file.RemovableDriveConstants.PROP_URI;
|
||||
|
||||
@NotNullByDefault
|
||||
class RemovableDriveViewModel extends AndroidViewModel {
|
||||
|
||||
private static final Logger LOG =
|
||||
getLogger(RemovableDriveViewModel.class.getName());
|
||||
|
||||
private final RemovableDriveManager manager;
|
||||
|
||||
private final ConcurrentHashMap<Consumer<State>, RemovableDriveTask>
|
||||
observers = new ConcurrentHashMap<>();
|
||||
|
||||
@Inject
|
||||
RemovableDriveViewModel(Application app,
|
||||
RemovableDriveManager removableDriveManager) {
|
||||
super(app);
|
||||
|
||||
this.manager = removableDriveManager;
|
||||
}
|
||||
|
||||
String getFileName() {
|
||||
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd_HHmmss_SSS", US);
|
||||
return sdf.format(new Date());
|
||||
}
|
||||
|
||||
|
||||
LiveData<State> write(ContactId contactId, Uri uri) {
|
||||
TransportProperties p = new TransportProperties();
|
||||
p.put(PROP_URI, uri.toString());
|
||||
return observe(manager.startWriterTask(contactId, p));
|
||||
}
|
||||
|
||||
LiveData<State> read(ContactId contactId, Uri uri) {
|
||||
TransportProperties p = new TransportProperties();
|
||||
p.put(PROP_URI, uri.toString());
|
||||
return observe(manager.startReaderTask(contactId, p));
|
||||
}
|
||||
|
||||
@Nullable
|
||||
LiveData<State> ongoingWrite(ContactId contactId) {
|
||||
RemovableDriveTask task = manager.getCurrentWriterTask(contactId);
|
||||
if (task == null) {
|
||||
return null;
|
||||
}
|
||||
return observe(task);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
LiveData<State> ongoingRead(ContactId contactId) {
|
||||
RemovableDriveTask task = manager.getCurrentReaderTask(contactId);
|
||||
if (task == null) {
|
||||
return null;
|
||||
}
|
||||
return observe(task);
|
||||
}
|
||||
|
||||
private LiveData<State> observe(RemovableDriveTask task) {
|
||||
MutableLiveData<State> state = new MutableLiveData<>();
|
||||
Consumer<State> observer = state::postValue;
|
||||
task.addObserver(observer);
|
||||
observers.put(observer, task);
|
||||
return state;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCleared() {
|
||||
for (Map.Entry<Consumer<State>, RemovableDriveTask> entry
|
||||
: observers.entrySet()) {
|
||||
entry.getValue().removeObserver(entry.getKey());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:context=".android.removabledrive.RemovableDriveActivity">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="@dimen/margin_large">
|
||||
|
||||
<Button
|
||||
android:id="@+id/sneaker_write"
|
||||
style="@style/BriarButton"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/margin_medium"
|
||||
android:enabled="false"
|
||||
android:text=""
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintRight_toRightOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:enabled="true" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/sneaker_read"
|
||||
style="@style/BriarButton"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/margin_medium"
|
||||
android:enabled="false"
|
||||
android:text=""
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintRight_toRightOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/sneaker_write"
|
||||
tools:enabled="true" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/sneaker_text"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="@dimen/margin_large"
|
||||
android:textSize="12sp"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintRight_toRightOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/sneaker_read" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</ScrollView>
|
||||
@@ -40,4 +40,9 @@
|
||||
android:title="@string/delete_contact"
|
||||
app:showAsAction="never" />
|
||||
|
||||
<item
|
||||
android:id="@+id/action_removable_drive_write"
|
||||
android:title="Transfer via removable drive"
|
||||
app:showAsAction="never" />
|
||||
|
||||
</menu>
|
||||
|
||||
@@ -74,6 +74,7 @@ internal class HeadlessModule(private val appDir: File) {
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
internal fun providePluginConfig(tor: UnixTorPluginFactory): PluginConfig {
|
||||
val duplex: List<DuplexPluginFactory> =
|
||||
if (isLinux() || isMac()) listOf(tor) else emptyList()
|
||||
|
||||
@@ -64,6 +64,7 @@ internal class HeadlessTestModule(private val appDir: File) {
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
internal fun providePluginConfig(): PluginConfig {
|
||||
return object : PluginConfig {
|
||||
override fun getDuplexFactories(): Collection<DuplexPluginFactory> = emptyList()
|
||||
|
||||
@@ -136,7 +136,7 @@ internal class MessagingControllerImplTest : ControllerTest() {
|
||||
val messageId1 = MessageId(getRandomId())
|
||||
val messageId2 = MessageId(getRandomId())
|
||||
val messageIds = listOf(messageId1, messageId2)
|
||||
val event = MessagesSentEvent(contact.id, messageIds)
|
||||
val event = MessagesSentEvent(contact.id, messageIds, 1234)
|
||||
|
||||
every {
|
||||
webSocketController.sendEvent(
|
||||
@@ -274,7 +274,7 @@ internal class MessagingControllerImplTest : ControllerTest() {
|
||||
val messageId1 = MessageId(getRandomId())
|
||||
val messageId2 = MessageId(getRandomId())
|
||||
val messageIds = listOf(messageId1, messageId2)
|
||||
val event = MessagesSentEvent(contact.id, messageIds)
|
||||
val event = MessagesSentEvent(contact.id, messageIds, 1234)
|
||||
|
||||
val json = """
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user