Compare commits

..

15 Commits

Author SHA1 Message Date
Sebastian Kürten
b6fa7520e9 Try printing db table sizes on startup 2022-05-09 09:16:09 +02:00
Daniel Lublin
d3bffaadf3 Fetch and store mailbox's supported api versions when pairing 2022-04-16 13:49:53 +02:00
Daniel Lublin
12b887881d Allow storing int array in settings 2022-04-16 13:49:52 +02:00
akwizgran
74a3f54d28 Merge branch '2172-mailbox-status-ui' into 'master'
Implement status UI for mailbox connection

Closes #2172

See merge request briar/briar!1617
2022-04-14 12:46:28 +00:00
Torsten Grote
edcb234b93 Show OfflineFragment when TorPlugin becomes inactive in mailbox flow 2022-04-12 10:10:09 -03:00
Torsten Grote
dae00c7e4e Show different mailbox status in UI
and show failure status after unsuccessful attempt
2022-04-12 10:01:43 -03:00
Torsten Grote
29b16c4d74 Re-use OfflineFragment when offline in mailbox status screen 2022-04-12 09:35:39 -03:00
Torsten Grote
40d58a9359 Prevent memory leak and crash when refreshing MailboxStatusFragment 2022-04-07 11:00:41 -03:00
Torsten Grote
60a1a4d2d1 Make MailboxManager#checkConnection() blocking and let the UI manage the executor 2022-04-07 10:44:24 -03:00
Torsten Grote
238aeb3abd Merge branch 'extend-timeout-for-pre-release-tests' into 'master'
Extend timeout for pre-release tests

See merge request briar/briar!1618
2022-04-04 11:13:50 +00:00
akwizgran
62c16fad09 Merge branch '2191-reset-retransmission-times-when-contacts-mailbox-props-change' into 'master'
Reset retransmission times when contact's mailbox props change

Closes #2191

See merge request briar/briar!1619
2022-04-04 10:19:02 +00:00
Daniel Lublin
68e57bda0d Reset retransmission times when contact's mailbox props change 2022-04-04 12:01:19 +02:00
akwizgran
0df73dbf0a Extend timeout for pre-release tests. 2022-04-02 08:16:34 +01:00
Torsten Grote
5b648cbd35 Add connection check button to Mailbox status UI
and update the last connection timestamp accordingly
2022-04-01 13:55:11 -03:00
Torsten Grote
5e7891d78a Add checkConnection() to MailboxManager 2022-04-01 13:55:11 -03:00
32 changed files with 688 additions and 113 deletions

View File

@@ -123,5 +123,6 @@ pre_release_tests:
extends: .optional_tests extends: .optional_tests
script: script:
- OPTIONAL_TESTS=org.briarproject.bramble.plugin.tor.BridgeTest ./gradlew --info bramble-java:test --tests BridgeTest - OPTIONAL_TESTS=org.briarproject.bramble.plugin.tor.BridgeTest ./gradlew --info bramble-java:test --tests BridgeTest
timeout: 3h
only: only:
- tags - tags

View File

@@ -1,8 +1,14 @@
package org.briarproject.bramble.api; package org.briarproject.bramble.api;
import org.briarproject.bramble.util.StringUtils;
import java.util.ArrayList;
import java.util.Hashtable; import java.util.Hashtable;
import java.util.List;
import java.util.Map; import java.util.Map;
import javax.annotation.Nullable;
public abstract class StringMap extends Hashtable<String, String> { public abstract class StringMap extends Hashtable<String, String> {
protected StringMap(Map<String, String> m) { protected StringMap(Map<String, String> m) {
@@ -52,4 +58,31 @@ public abstract class StringMap extends Hashtable<String, String> {
public void putLong(String key, long value) { public void putLong(String key, long value) {
put(key, String.valueOf(value)); put(key, String.valueOf(value));
} }
@Nullable
public int[] getIntArray(String key) {
String s = get(key);
if (s == null) return null;
// Handle empty string because "".split(",") returns {""}
if (s.length() == 0) return new int[0];
String[] intStrings = s.split(",");
int[] ints = new int[intStrings.length];
try {
for (int i = 0; i < ints.length; i++) {
ints[i] = Integer.parseInt(intStrings[i]);
}
} catch (NumberFormatException e) {
return null;
}
return ints;
}
public void putIntArray(String key, int[] value) {
List<String> intStrings = new ArrayList<>();
for (int integer : value) {
intStrings.add(String.valueOf(integer));
}
// Puts empty string if input array value is empty
put(key, StringUtils.join(intStrings, ","));
}
} }

View File

@@ -746,4 +746,6 @@ public interface DatabaseComponent extends TransactionManager {
*/ */
void updateTransportKeys(Transaction txn, Collection<TransportKeySet> keys) void updateTransportKeys(Transaction txn, Collection<TransportKeySet> keys)
throws DbException; throws DbException;
void printStats(Transaction txn) throws DbException;
} }

View File

@@ -32,4 +32,13 @@ public interface MailboxManager {
*/ */
MailboxPairingTask startPairingTask(String qrCodePayload); MailboxPairingTask startPairingTask(String qrCodePayload);
/**
* Can be used by the UI to test the mailbox connection.
*
* @return true (success) or false (error).
* A {@link OwnMailboxConnectionStatusEvent} might be broadcast with a new
* {@link MailboxStatus}.
*/
boolean checkConnection();
} }

View File

@@ -2,6 +2,8 @@ package org.briarproject.bramble.api.mailbox;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import java.util.List;
import javax.annotation.concurrent.Immutable; import javax.annotation.concurrent.Immutable;
@Immutable @Immutable
@@ -11,12 +13,14 @@ public class MailboxProperties {
private final String baseUrl; private final String baseUrl;
private final MailboxAuthToken authToken; private final MailboxAuthToken authToken;
private final boolean owner; private final boolean owner;
private final List<MailboxVersion> serverSupports;
public MailboxProperties(String baseUrl, MailboxAuthToken authToken, public MailboxProperties(String baseUrl, MailboxAuthToken authToken,
boolean owner) { boolean owner, List<MailboxVersion> serverSupports) {
this.baseUrl = baseUrl; this.baseUrl = baseUrl;
this.authToken = authToken; this.authToken = authToken;
this.owner = owner; this.owner = owner;
this.serverSupports = serverSupports;
} }
public String getBaseUrl() { public String getBaseUrl() {
@@ -35,4 +39,8 @@ public class MailboxProperties {
public boolean isOwner() { public boolean isOwner() {
return owner; return owner;
} }
public List<MailboxVersion> getServerSupports() {
return serverSupports;
}
} }

View File

@@ -0,0 +1,44 @@
package org.briarproject.bramble.api.mailbox;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import javax.annotation.concurrent.Immutable;
@Immutable
@NotNullByDefault
public class MailboxVersion implements Comparable<MailboxVersion> {
private final int major;
private final int minor;
public MailboxVersion(int major, int minor) {
this.major = major;
this.minor = minor;
}
public int getMajor() {
return major;
}
public int getMinor() {
return minor;
}
@Override
public boolean equals(Object o) {
if (o instanceof MailboxVersion) {
MailboxVersion v = (MailboxVersion) o;
return major == v.major && minor == v.minor;
}
return false;
}
@Override
public int compareTo(MailboxVersion v) {
int c = major - v.major;
if (c != 0) {
return c;
}
return minor - v.minor;
}
}

View File

@@ -20,6 +20,7 @@ import org.briarproject.bramble.api.identity.Author;
import org.briarproject.bramble.api.identity.AuthorId; import org.briarproject.bramble.api.identity.AuthorId;
import org.briarproject.bramble.api.identity.Identity; import org.briarproject.bramble.api.identity.Identity;
import org.briarproject.bramble.api.identity.LocalAuthor; import org.briarproject.bramble.api.identity.LocalAuthor;
import org.briarproject.bramble.api.mailbox.MailboxProperties;
import org.briarproject.bramble.api.mailbox.MailboxPropertiesUpdate; import org.briarproject.bramble.api.mailbox.MailboxPropertiesUpdate;
import org.briarproject.bramble.api.plugin.TransportId; import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.properties.TransportProperties; import org.briarproject.bramble.api.properties.TransportProperties;
@@ -291,6 +292,18 @@ public class TestUtils {
a.getOutboxId().equals(b.getOutboxId()); a.getOutboxId().equals(b.getOutboxId());
} }
public static boolean mailboxPropertiesEqual(
@Nullable MailboxProperties a,
@Nullable MailboxProperties b) {
if (a == null || b == null) {
return a == b;
}
return a.getOnion().equals(b.getOnion()) &&
a.getAuthToken().equals(b.getAuthToken()) &&
a.isOwner() == b.isOwner() &&
a.getServerSupports().equals(b.getServerSupports());
}
public static boolean hasEvent(Transaction txn, public static boolean hasEvent(Transaction txn,
Class<? extends Event> eventClass) { Class<? extends Event> eventClass) {
for (CommitAction action : txn.getActions()) { for (CommitAction action : txn.getActions()) {

View File

@@ -862,4 +862,6 @@ interface Database<T> {
* Stores the given transport keys, deleting any keys they have replaced. * Stores the given transport keys, deleting any keys they have replaced.
*/ */
void updateTransportKeys(T txn, TransportKeySet ks) throws DbException; void updateTransportKeys(T txn, TransportKeySet ks) throws DbException;
void printStats(T txn) throws DbException;
} }

View File

@@ -1256,6 +1256,12 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
} }
} }
@Override
public void printStats(Transaction transaction) throws DbException {
T txn = unbox(transaction);
db.printStats(txn);
}
private class CommitActionVisitor implements Visitor { private class CommitActionVisitor implements Visitor {
@Override @Override

View File

@@ -13,8 +13,12 @@ import org.briarproject.bramble.util.StringUtils;
import java.io.File; import java.io.File;
import java.sql.Connection; import java.sql.Connection;
import java.sql.DriverManager; import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException; import java.sql.SQLException;
import java.sql.Statement; import java.sql.Statement;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties; import java.util.Properties;
import java.util.logging.Logger; import java.util.logging.Logger;
@@ -26,6 +30,7 @@ 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.db.JdbcUtils.tryToClose; import static org.briarproject.bramble.db.JdbcUtils.tryToClose;
import static org.briarproject.bramble.util.IoUtils.isNonEmptyDirectory; import static org.briarproject.bramble.util.IoUtils.isNonEmptyDirectory;
import static org.briarproject.bramble.util.LogUtils.logException;
import static org.briarproject.bramble.util.LogUtils.logFileOrDir; import static org.briarproject.bramble.util.LogUtils.logFileOrDir;
/** /**
@@ -101,6 +106,73 @@ class H2Database extends JdbcDatabase {
} }
} }
@Override
public void printStats(Connection txn) throws DbException {
List<String> names = printNames(txn);
for (String table : names) {
tryPrintStats(txn, table);
}
}
private List<String> printNames(Connection txn) throws DbException {
List<String> names = new ArrayList<>();
PreparedStatement ps = null;
ResultSet rs = null;
try {
String sql =
"SELECT table_catalog, table_schema, table_name FROM INFORMATION_SCHEMA.TABLES";
ps = txn.prepareStatement(sql);
rs = ps.executeQuery();
while (rs.next()) {
String catalog = rs.getString(1);
String schema = rs.getString(2);
String name = rs.getString(3);
LOG.info(
String.format("Table %s %s %s", catalog, schema, name));
names.add(schema + "." + name);
}
rs.close();
ps.close();
} catch (SQLException e) {
tryToClose(rs, LOG, WARNING);
tryToClose(ps, LOG, WARNING);
throw new DbException(e);
}
return names;
}
private void tryPrintStats(Connection txn, String table) {
try {
printStats(txn, table);
} catch (DbException e) {
logException(LOG, WARNING, e);
}
}
private void printStats(Connection txn, String table) throws DbException {
PreparedStatement ps = null;
ResultSet rs = null;
try {
String sql = "CALL DISK_SPACE_USED(?)";
ps = txn.prepareStatement(sql);
ps.setString(1, table);
rs = ps.executeQuery();
if (!rs.next()) {
rs.close();
ps.close();
}
long size = rs.getLong(1);
if (rs.next()) throw new DbStateException();
rs.close();
ps.close();
LOG.info(String.format("Size of table %s: %d", table, size));
} catch (SQLException e) {
tryToClose(rs, LOG, WARNING);
tryToClose(ps, LOG, WARNING);
throw new DbException(e);
}
}
@Override @Override
protected Connection createConnection() throws DbException, SQLException { protected Connection createConnection() throws DbException, SQLException {
SecretKey key = this.key; SecretKey key = this.key;

View File

@@ -93,6 +93,11 @@ class HyperSqlDatabase extends JdbcDatabase {
} }
} }
@Override
public void printStats(Connection txn) throws DbException {
// Not implemented
}
@Override @Override
protected Connection createConnection() throws DbException, SQLException { protected Connection createConnection() throws DbException, SQLException {
SecretKey key = this.key; SecretKey key = this.key;

View File

@@ -25,7 +25,7 @@ interface MailboxApi {
* @return the owner token * @return the owner token
* @throws ApiException for 401 response. * @throws ApiException for 401 response.
*/ */
MailboxAuthToken setup(MailboxProperties properties) MailboxProperties setup(MailboxProperties properties)
throws IOException, ApiException; throws IOException, ApiException;
/** /**

View File

@@ -14,6 +14,7 @@ import org.briarproject.bramble.api.mailbox.MailboxFileId;
import org.briarproject.bramble.api.mailbox.MailboxFolderId; import org.briarproject.bramble.api.mailbox.MailboxFolderId;
import org.briarproject.bramble.api.mailbox.MailboxId; import org.briarproject.bramble.api.mailbox.MailboxId;
import org.briarproject.bramble.api.mailbox.MailboxProperties; import org.briarproject.bramble.api.mailbox.MailboxProperties;
import org.briarproject.bramble.api.mailbox.MailboxVersion;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import java.io.File; import java.io.File;
@@ -56,7 +57,7 @@ class MailboxApiImpl implements MailboxApi {
} }
@Override @Override
public MailboxAuthToken setup(MailboxProperties properties) public MailboxProperties setup(MailboxProperties properties)
throws IOException, ApiException { throws IOException, ApiException {
if (!properties.isOwner()) throw new IllegalArgumentException(); if (!properties.isOwner()) throw new IllegalArgumentException();
Request request = getRequestBuilder(properties.getAuthToken()) Request request = getRequestBuilder(properties.getAuthToken())
@@ -75,8 +76,27 @@ class MailboxApiImpl implements MailboxApi {
if (tokenNode == null) { if (tokenNode == null) {
throw new ApiException(); throw new ApiException();
} }
String ownerToken = tokenNode.textValue(); List<MailboxVersion> serverSupports = new ArrayList<>();
return MailboxAuthToken.fromString(ownerToken); ArrayNode serverSupportsNode = getArray(node, "serverSupports");
for (JsonNode versionNode : serverSupportsNode) {
if (!versionNode.isObject()) throw new ApiException();
ObjectNode objectNode = (ObjectNode) versionNode;
JsonNode majorNode = objectNode.get("major");
JsonNode minorNode = objectNode.get("minor");
if (majorNode == null || !majorNode.isNumber()) {
throw new ApiException();
}
if (minorNode == null || !minorNode.isNumber()) {
throw new ApiException();
}
int major = majorNode.asInt();
int minor = minorNode.asInt();
if (major < 0 || minor < 0) throw new ApiException();
serverSupports.add(new MailboxVersion(major, minor));
}
return new MailboxProperties(properties.getBaseUrl(),
MailboxAuthToken.fromString(tokenNode.textValue()),
true, serverSupports);
} catch (JacksonException | InvalidMailboxIdException e) { } catch (JacksonException | InvalidMailboxIdException e) {
throw new ApiException(); throw new ApiException();
} }

View File

@@ -2,27 +2,42 @@ package org.briarproject.bramble.mailbox;
import org.briarproject.bramble.api.db.DbException; import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.Transaction; import org.briarproject.bramble.api.db.Transaction;
import org.briarproject.bramble.api.db.TransactionManager;
import org.briarproject.bramble.api.lifecycle.IoExecutor; import org.briarproject.bramble.api.lifecycle.IoExecutor;
import org.briarproject.bramble.api.mailbox.MailboxManager; import org.briarproject.bramble.api.mailbox.MailboxManager;
import org.briarproject.bramble.api.mailbox.MailboxPairingTask; import org.briarproject.bramble.api.mailbox.MailboxPairingTask;
import org.briarproject.bramble.api.mailbox.MailboxProperties;
import org.briarproject.bramble.api.mailbox.MailboxSettingsManager; import org.briarproject.bramble.api.mailbox.MailboxSettingsManager;
import org.briarproject.bramble.api.mailbox.MailboxStatus; import org.briarproject.bramble.api.mailbox.MailboxStatus;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.system.Clock;
import java.io.IOException;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
import java.util.logging.Logger;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import javax.annotation.concurrent.GuardedBy; import javax.annotation.concurrent.GuardedBy;
import javax.annotation.concurrent.Immutable; import javax.annotation.concurrent.Immutable;
import javax.inject.Inject; import javax.inject.Inject;
import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.util.LogUtils.logException;
@Immutable @Immutable
@NotNullByDefault @NotNullByDefault
class MailboxManagerImpl implements MailboxManager { class MailboxManagerImpl implements MailboxManager {
private static final String TAG = MailboxManagerImpl.class.getName();
private final static Logger LOG = getLogger(TAG);
private final Executor ioExecutor; private final Executor ioExecutor;
private final MailboxApi api;
private final TransactionManager db;
private final MailboxSettingsManager mailboxSettingsManager; private final MailboxSettingsManager mailboxSettingsManager;
private final MailboxPairingTaskFactory pairingTaskFactory; private final MailboxPairingTaskFactory pairingTaskFactory;
private final Clock clock;
private final Object lock = new Object(); private final Object lock = new Object();
@Nullable @Nullable
@@ -32,11 +47,17 @@ class MailboxManagerImpl implements MailboxManager {
@Inject @Inject
MailboxManagerImpl( MailboxManagerImpl(
@IoExecutor Executor ioExecutor, @IoExecutor Executor ioExecutor,
MailboxApi api,
TransactionManager db,
MailboxSettingsManager mailboxSettingsManager, MailboxSettingsManager mailboxSettingsManager,
MailboxPairingTaskFactory pairingTaskFactory) { MailboxPairingTaskFactory pairingTaskFactory,
Clock clock) {
this.ioExecutor = ioExecutor; this.ioExecutor = ioExecutor;
this.api = api;
this.db = db;
this.mailboxSettingsManager = mailboxSettingsManager; this.mailboxSettingsManager = mailboxSettingsManager;
this.pairingTaskFactory = pairingTaskFactory; this.pairingTaskFactory = pairingTaskFactory;
this.clock = clock;
} }
@Override @Override
@@ -75,4 +96,29 @@ class MailboxManagerImpl implements MailboxManager {
return created; return created;
} }
@Override
public boolean checkConnection() {
boolean success;
try {
MailboxProperties props = db.transactionWithNullableResult(true,
mailboxSettingsManager::getOwnMailboxProperties);
success = api.checkStatus(props);
} catch (DbException | IOException | MailboxApi.ApiException e) {
success = false;
logException(LOG, WARNING, e);
}
if (success) {
try {
// we are only recording successful connections here
// as those update the UI and failures might be false negatives
db.transaction(false, txn ->
mailboxSettingsManager.recordSuccessfulConnection(txn,
clock.currentTimeMillis()));
} catch (DbException e) {
logException(LOG, WARNING, e);
}
}
return success;
}
} }

View File

@@ -115,9 +115,7 @@ class MailboxPairingTaskImpl implements MailboxPairingTask {
private void pairMailbox() throws IOException, ApiException, DbException { private void pairMailbox() throws IOException, ApiException, DbException {
MailboxProperties mailboxProperties = decodeQrCodePayload(payload); MailboxProperties mailboxProperties = decodeQrCodePayload(payload);
setState(new MailboxPairingState.Pairing()); setState(new MailboxPairingState.Pairing());
MailboxAuthToken ownerToken = api.setup(mailboxProperties); MailboxProperties ownerProperties = api.setup(mailboxProperties);
MailboxProperties ownerProperties = new MailboxProperties(
mailboxProperties.getBaseUrl(), ownerToken, true);
long time = clock.currentTimeMillis(); long time = clock.currentTimeMillis();
db.transaction(false, txn -> { db.transaction(false, txn -> {
mailboxSettingsManager mailboxSettingsManager
@@ -182,7 +180,8 @@ class MailboxPairingTaskImpl implements MailboxPairingTask {
String baseUrl = "http://" + onion + ".onion"; String baseUrl = "http://" + onion + ".onion";
byte[] tokenBytes = Arrays.copyOfRange(bytes, 33, 65); byte[] tokenBytes = Arrays.copyOfRange(bytes, 33, 65);
MailboxAuthToken setupToken = new MailboxAuthToken(tokenBytes); MailboxAuthToken setupToken = new MailboxAuthToken(tokenBytes);
return new MailboxProperties(baseUrl, setupToken, true); return new MailboxProperties(baseUrl, setupToken, true,
new ArrayList<>());
} }
} }

View File

@@ -158,6 +158,12 @@ class MailboxPropertyManagerImpl implements MailboxPropertyManager,
BdfList body = clientHelper.getMessageAsList(txn, m.getId()); BdfList body = clientHelper.getMessageAsList(txn, m.getId());
MailboxPropertiesUpdate p = parseProperties(body); MailboxPropertiesUpdate p = parseProperties(body);
txn.attach(new RemoteMailboxPropertiesUpdateEvent(c, p)); txn.attach(new RemoteMailboxPropertiesUpdateEvent(c, p));
// Reset message retransmission timers for the contact. Avoiding
// messages getting stranded:
// - on our mailbox, if they now have a mailbox but didn't before
// - on the contact's old mailbox, if they removed their mailbox
// - on the contact's old mailbox, if they replaced their mailbox
db.resetUnackedMessagesToSend(txn, c);
} catch (FormatException e) { } catch (FormatException e) {
throw new InvalidMessageException(e); throw new InvalidMessageException(e);
} }

View File

@@ -8,11 +8,13 @@ import org.briarproject.bramble.api.mailbox.MailboxAuthToken;
import org.briarproject.bramble.api.mailbox.MailboxProperties; import org.briarproject.bramble.api.mailbox.MailboxProperties;
import org.briarproject.bramble.api.mailbox.MailboxSettingsManager; import org.briarproject.bramble.api.mailbox.MailboxSettingsManager;
import org.briarproject.bramble.api.mailbox.MailboxStatus; import org.briarproject.bramble.api.mailbox.MailboxStatus;
import org.briarproject.bramble.api.mailbox.MailboxVersion;
import org.briarproject.bramble.api.mailbox.OwnMailboxConnectionStatusEvent; import org.briarproject.bramble.api.mailbox.OwnMailboxConnectionStatusEvent;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.settings.Settings; import org.briarproject.bramble.api.settings.Settings;
import org.briarproject.bramble.api.settings.SettingsManager; import org.briarproject.bramble.api.settings.SettingsManager;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.CopyOnWriteArrayList;
@@ -30,6 +32,7 @@ class MailboxSettingsManagerImpl implements MailboxSettingsManager {
static final String SETTINGS_NAMESPACE = "mailbox"; static final String SETTINGS_NAMESPACE = "mailbox";
static final String SETTINGS_KEY_ONION = "onion"; static final String SETTINGS_KEY_ONION = "onion";
static final String SETTINGS_KEY_TOKEN = "token"; static final String SETTINGS_KEY_TOKEN = "token";
static final String SETTINGS_KEY_SERVER_SUPPORTS = "serverSupports";
static final String SETTINGS_KEY_LAST_ATTEMPT = "lastAttempt"; static final String SETTINGS_KEY_LAST_ATTEMPT = "lastAttempt";
static final String SETTINGS_KEY_LAST_SUCCESS = "lastSuccess"; static final String SETTINGS_KEY_LAST_SUCCESS = "lastSuccess";
static final String SETTINGS_KEY_ATTEMPTS = "attempts"; static final String SETTINGS_KEY_ATTEMPTS = "attempts";
@@ -55,9 +58,19 @@ class MailboxSettingsManagerImpl implements MailboxSettingsManager {
String onion = s.get(SETTINGS_KEY_ONION); String onion = s.get(SETTINGS_KEY_ONION);
String token = s.get(SETTINGS_KEY_TOKEN); String token = s.get(SETTINGS_KEY_TOKEN);
if (isNullOrEmpty(onion) || isNullOrEmpty(token)) return null; if (isNullOrEmpty(onion) || isNullOrEmpty(token)) return null;
int[] ints = s.getIntArray(SETTINGS_KEY_SERVER_SUPPORTS);
// We know we were paired, so we must have proper serverSupports
// TODO is throwing sensible? But it's done like that below on "parse error"
if (ints == null || ints.length == 0 || ints.length % 2 != 0) {
throw new DbException();
}
List<MailboxVersion> serverSupports = new ArrayList<>();
for (int i = 0; i < ints.length - 1; i += 2) {
serverSupports.add(new MailboxVersion(ints[i], ints[i + 1]));
}
try { try {
MailboxAuthToken tokenId = MailboxAuthToken.fromString(token); MailboxAuthToken tokenId = MailboxAuthToken.fromString(token);
return new MailboxProperties(onion, tokenId, true); return new MailboxProperties(onion, tokenId, true, serverSupports);
} catch (InvalidMailboxIdException e) { } catch (InvalidMailboxIdException e) {
throw new DbException(e); throw new DbException(e);
} }
@@ -69,6 +82,14 @@ class MailboxSettingsManagerImpl implements MailboxSettingsManager {
Settings s = new Settings(); Settings s = new Settings();
s.put(SETTINGS_KEY_ONION, p.getBaseUrl()); s.put(SETTINGS_KEY_ONION, p.getBaseUrl());
s.put(SETTINGS_KEY_TOKEN, p.getAuthToken().toString()); s.put(SETTINGS_KEY_TOKEN, p.getAuthToken().toString());
List<MailboxVersion> serverSupports = p.getServerSupports();
int[] ints = new int[serverSupports.size() * 2];
int i = 0;
for (MailboxVersion v : serverSupports) {
ints[i++] = v.getMajor();
ints[i++] = v.getMinor();
}
s.putIntArray(SETTINGS_KEY_SERVER_SUPPORTS, ints);
settingsManager.mergeSettings(txn, s, SETTINGS_NAMESPACE); settingsManager.mergeSettings(txn, s, SETTINGS_NAMESPACE);
for (MailboxHook hook : hooks) { for (MailboxHook hook : hooks) {
hook.mailboxPaired(txn, p.getOnion()); hook.mailboxPaired(txn, p.getOnion());

View File

@@ -37,6 +37,7 @@ import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static org.briarproject.bramble.test.TestUtils.getContactId; import static org.briarproject.bramble.test.TestUtils.getContactId;
import static org.briarproject.bramble.test.TestUtils.getRandomBytes; import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
import static org.briarproject.bramble.test.TestUtils.getRandomId; import static org.briarproject.bramble.test.TestUtils.getRandomId;
import static org.briarproject.bramble.test.TestUtils.mailboxPropertiesEqual;
import static org.briarproject.bramble.test.TestUtils.readBytes; import static org.briarproject.bramble.test.TestUtils.readBytes;
import static org.briarproject.bramble.test.TestUtils.writeBytes; import static org.briarproject.bramble.test.TestUtils.writeBytes;
import static org.briarproject.bramble.util.StringUtils.getRandomString; import static org.briarproject.bramble.util.StringUtils.getRandomString;
@@ -80,11 +81,20 @@ public class MailboxApiTest extends BrambleTestCase {
@Test @Test
public void testSetup() throws Exception { public void testSetup() throws Exception {
String validResponse = "{\"token\":\"" + token2 + "\"}"; String validVersions = "[ {\"major\":1,\"minor\":0} ]";
String validResponse = makeSetupResponse(
"\"" + token2 + "\"", validVersions);
String invalidResponse = "{\"foo\":\"bar\"}"; String invalidResponse = "{\"foo\":\"bar\"}";
String invalidTokenResponse = "{\"token\":{\"foo\":\"bar\"}}"; String invalidTokenResponse = makeSetupResponse(
String invalidTokenResponse2 = "{\"foo\":\"bar\"}", validVersions);
"{\"token\":\"" + getRandomString(64) + "\"}"; String invalidTokenResponse2 = makeSetupResponse(
"\"" + getRandomString(64) + "\"", validVersions);
String invalidVersionsResponse = makeSetupResponse(
"\"" + token2 + "\"", "42");
String invalidVersionsResponse2 = makeSetupResponse(
"\"" + token2 + "\"", "[ 1,0 ]");
String invalidVersionsResponse3 = makeSetupResponse(
"\"" + token2 + "\"", "[ {\"major\":1, \"minor\":-1} ]");
MockWebServer server = new MockWebServer(); MockWebServer server = new MockWebServer();
server.enqueue(new MockResponse().setBody(validResponse)); server.enqueue(new MockResponse().setBody(validResponse));
@@ -94,15 +104,18 @@ public class MailboxApiTest extends BrambleTestCase {
server.enqueue(new MockResponse().setResponseCode(500)); server.enqueue(new MockResponse().setResponseCode(500));
server.enqueue(new MockResponse().setBody(invalidTokenResponse)); server.enqueue(new MockResponse().setBody(invalidTokenResponse));
server.enqueue(new MockResponse().setBody(invalidTokenResponse2)); server.enqueue(new MockResponse().setBody(invalidTokenResponse2));
server.enqueue(new MockResponse().setBody(invalidVersionsResponse));
server.enqueue(new MockResponse().setBody(invalidVersionsResponse2));
server.enqueue(new MockResponse().setBody(invalidVersionsResponse3));
server.start(); server.start();
String baseUrl = getBaseUrl(server); String baseUrl = getBaseUrl(server);
MailboxProperties properties = MailboxProperties properties =
new MailboxProperties(baseUrl, token, true); new MailboxProperties(baseUrl, token, true, new ArrayList<>());
MailboxProperties properties2 = MailboxProperties properties2 =
new MailboxProperties(baseUrl, token2, true); new MailboxProperties(baseUrl, token2, true, new ArrayList<>());
// valid response with valid token // valid response with valid token
assertEquals(token2, api.setup(properties)); mailboxPropertiesEqual(properties2, api.setup(properties));
RecordedRequest request1 = server.takeRequest(); RecordedRequest request1 = server.takeRequest();
assertEquals("/setup", request1.getPath()); assertEquals("/setup", request1.getPath());
assertEquals("PUT", request1.getMethod()); assertEquals("PUT", request1.getMethod());
@@ -149,12 +162,38 @@ public class MailboxApiTest extends BrambleTestCase {
assertEquals("/setup", request7.getPath()); assertEquals("/setup", request7.getPath());
assertEquals("PUT", request7.getMethod()); assertEquals("PUT", request7.getMethod());
assertToken(request7, token); assertToken(request7, token);
// invalid non-array serverSupports response
assertThrows(ApiException.class, () -> api.setup(properties));
RecordedRequest request8 = server.takeRequest();
assertEquals("/setup", request8.getPath());
assertEquals("PUT", request8.getMethod());
assertToken(request8, token);
// invalid non-object in serverSupports array response
assertThrows(ApiException.class, () -> api.setup(properties));
RecordedRequest request9 = server.takeRequest();
assertEquals("/setup", request9.getPath());
assertEquals("PUT", request9.getMethod());
assertToken(request9, token);
// invalid negative minor version in serverSupports response
assertThrows(ApiException.class, () -> api.setup(properties));
RecordedRequest request10 = server.takeRequest();
assertEquals("/setup", request10.getPath());
assertEquals("PUT", request10.getMethod());
assertToken(request10, token);
}
private String makeSetupResponse(String token, String versions) {
return "{\"token\":" + token + "," +
"\"serverSupports\":" + versions + "}";
} }
@Test @Test
public void testSetupOnlyForOwner() { public void testSetupOnlyForOwner() {
MailboxProperties properties = MailboxProperties properties =
new MailboxProperties("", token, false); new MailboxProperties("", token, false, new ArrayList<>());
assertThrows( assertThrows(
IllegalArgumentException.class, IllegalArgumentException.class,
() -> api.setup(properties) () -> api.setup(properties)
@@ -170,9 +209,9 @@ public class MailboxApiTest extends BrambleTestCase {
server.start(); server.start();
String baseUrl = getBaseUrl(server); String baseUrl = getBaseUrl(server);
MailboxProperties properties = MailboxProperties properties =
new MailboxProperties(baseUrl, token, true); new MailboxProperties(baseUrl, token, true, new ArrayList<>());
MailboxProperties properties2 = MailboxProperties properties2 =
new MailboxProperties(baseUrl, token2, true); new MailboxProperties(baseUrl, token2, true, new ArrayList<>());
assertTrue(api.checkStatus(properties)); assertTrue(api.checkStatus(properties));
RecordedRequest request1 = server.takeRequest(); RecordedRequest request1 = server.takeRequest();
@@ -193,7 +232,7 @@ public class MailboxApiTest extends BrambleTestCase {
@Test @Test
public void testStatusOnlyForOwner() { public void testStatusOnlyForOwner() {
MailboxProperties properties = MailboxProperties properties =
new MailboxProperties("", token, false); new MailboxProperties("", token, false, new ArrayList<>());
assertThrows( assertThrows(
IllegalArgumentException.class, IllegalArgumentException.class,
() -> api.checkStatus(properties) () -> api.checkStatus(properties)
@@ -210,9 +249,9 @@ public class MailboxApiTest extends BrambleTestCase {
server.start(); server.start();
String baseUrl = getBaseUrl(server); String baseUrl = getBaseUrl(server);
MailboxProperties properties = MailboxProperties properties =
new MailboxProperties(baseUrl, token, true); new MailboxProperties(baseUrl, token, true, new ArrayList<>());
MailboxProperties properties2 = MailboxProperties properties2 =
new MailboxProperties(baseUrl, token2, true); new MailboxProperties(baseUrl, token2, true, new ArrayList<>());
api.wipeMailbox(properties); api.wipeMailbox(properties);
RecordedRequest request1 = server.takeRequest(); RecordedRequest request1 = server.takeRequest();
@@ -242,7 +281,7 @@ public class MailboxApiTest extends BrambleTestCase {
@Test @Test
public void testWipeOnlyForOwner() { public void testWipeOnlyForOwner() {
MailboxProperties properties = MailboxProperties properties =
new MailboxProperties("", token, false); new MailboxProperties("", token, false, new ArrayList<>());
assertThrows(IllegalArgumentException.class, () -> assertThrows(IllegalArgumentException.class, () ->
api.wipeMailbox(properties)); api.wipeMailbox(properties));
} }
@@ -256,7 +295,7 @@ public class MailboxApiTest extends BrambleTestCase {
server.start(); server.start();
String baseUrl = getBaseUrl(server); String baseUrl = getBaseUrl(server);
MailboxProperties properties = MailboxProperties properties =
new MailboxProperties(baseUrl, token, true); new MailboxProperties(baseUrl, token, true, new ArrayList<>());
// contact gets added as expected // contact gets added as expected
api.addContact(properties, mailboxContact); api.addContact(properties, mailboxContact);
@@ -288,7 +327,7 @@ public class MailboxApiTest extends BrambleTestCase {
@Test @Test
public void testAddContactOnlyForOwner() { public void testAddContactOnlyForOwner() {
MailboxProperties properties = MailboxProperties properties =
new MailboxProperties("", token, false); new MailboxProperties("", token, false, new ArrayList<>());
assertThrows(IllegalArgumentException.class, () -> assertThrows(IllegalArgumentException.class, () ->
api.addContact(properties, mailboxContact)); api.addContact(properties, mailboxContact));
} }
@@ -303,7 +342,7 @@ public class MailboxApiTest extends BrambleTestCase {
server.start(); server.start();
String baseUrl = getBaseUrl(server); String baseUrl = getBaseUrl(server);
MailboxProperties properties = MailboxProperties properties =
new MailboxProperties(baseUrl, token, true); new MailboxProperties(baseUrl, token, true, new ArrayList<>());
// contact gets deleted as expected // contact gets deleted as expected
api.deleteContact(properties, contactId); api.deleteContact(properties, contactId);
@@ -340,7 +379,7 @@ public class MailboxApiTest extends BrambleTestCase {
@Test @Test
public void testDeleteContactOnlyForOwner() { public void testDeleteContactOnlyForOwner() {
MailboxProperties properties = MailboxProperties properties =
new MailboxProperties("", token, false); new MailboxProperties("", token, false, new ArrayList<>());
assertThrows(IllegalArgumentException.class, () -> assertThrows(IllegalArgumentException.class, () ->
api.deleteContact(properties, contactId)); api.deleteContact(properties, contactId));
} }
@@ -367,7 +406,7 @@ public class MailboxApiTest extends BrambleTestCase {
server.start(); server.start();
String baseUrl = getBaseUrl(server); String baseUrl = getBaseUrl(server);
MailboxProperties properties = MailboxProperties properties =
new MailboxProperties(baseUrl, token, true); new MailboxProperties(baseUrl, token, true, new ArrayList<>());
// valid response with two contacts // valid response with two contacts
assertEquals(singletonList(contactId), api.getContacts(properties)); assertEquals(singletonList(contactId), api.getContacts(properties));
@@ -432,7 +471,7 @@ public class MailboxApiTest extends BrambleTestCase {
@Test @Test
public void testGetContactsOnlyForOwner() { public void testGetContactsOnlyForOwner() {
MailboxProperties properties = MailboxProperties properties =
new MailboxProperties("", token, false); new MailboxProperties("", token, false, new ArrayList<>());
assertThrows( assertThrows(
IllegalArgumentException.class, IllegalArgumentException.class,
() -> api.getContacts(properties) () -> api.getContacts(properties)
@@ -452,7 +491,7 @@ public class MailboxApiTest extends BrambleTestCase {
server.start(); server.start();
String baseUrl = getBaseUrl(server); String baseUrl = getBaseUrl(server);
MailboxProperties properties = MailboxProperties properties =
new MailboxProperties(baseUrl, token, true); new MailboxProperties(baseUrl, token, true, new ArrayList<>());
// file gets uploaded as expected // file gets uploaded as expected
api.addFile(properties, contactInboxId, file); api.addFile(properties, contactInboxId, file);
@@ -511,7 +550,7 @@ public class MailboxApiTest extends BrambleTestCase {
server.start(); server.start();
String baseUrl = getBaseUrl(server); String baseUrl = getBaseUrl(server);
MailboxProperties properties = MailboxProperties properties =
new MailboxProperties(baseUrl, token, true); new MailboxProperties(baseUrl, token, true, new ArrayList<>());
// valid response with one file // valid response with one file
List<MailboxFile> received1 = api.getFiles(properties, contactInboxId); List<MailboxFile> received1 = api.getFiles(properties, contactInboxId);
@@ -607,7 +646,7 @@ public class MailboxApiTest extends BrambleTestCase {
server.start(); server.start();
String baseUrl = getBaseUrl(server); String baseUrl = getBaseUrl(server);
MailboxProperties properties = MailboxProperties properties =
new MailboxProperties(baseUrl, token, true); new MailboxProperties(baseUrl, token, true, new ArrayList<>());
// file gets downloaded as expected // file gets downloaded as expected
api.getFile(properties, contactOutboxId, name, file1); api.getFile(properties, contactOutboxId, name, file1);
@@ -651,7 +690,7 @@ public class MailboxApiTest extends BrambleTestCase {
server.start(); server.start();
String baseUrl = getBaseUrl(server); String baseUrl = getBaseUrl(server);
MailboxProperties properties = MailboxProperties properties =
new MailboxProperties(baseUrl, token, true); new MailboxProperties(baseUrl, token, true, new ArrayList<>());
// file gets deleted as expected // file gets deleted as expected
api.deleteFile(properties, contactInboxId, name); api.deleteFile(properties, contactInboxId, name);
@@ -715,7 +754,7 @@ public class MailboxApiTest extends BrambleTestCase {
server.start(); server.start();
String baseUrl = getBaseUrl(server); String baseUrl = getBaseUrl(server);
MailboxProperties properties = MailboxProperties properties =
new MailboxProperties(baseUrl, token, true); new MailboxProperties(baseUrl, token, true, new ArrayList<>());
// valid response with one folders // valid response with one folders
assertEquals(singletonList(id1), api.getFolders(properties)); assertEquals(singletonList(id1), api.getFolders(properties));
@@ -784,7 +823,7 @@ public class MailboxApiTest extends BrambleTestCase {
@Test @Test
public void testGetFoldersOnlyForOwner() { public void testGetFoldersOnlyForOwner() {
MailboxProperties properties = MailboxProperties properties =
new MailboxProperties("", token, false); new MailboxProperties("", token, false, new ArrayList<>());
assertThrows(IllegalArgumentException.class, () -> assertThrows(IllegalArgumentException.class, () ->
api.getFolders(properties)); api.getFolders(properties));
} }

View File

@@ -21,6 +21,7 @@ import org.junit.rules.TemporaryFolder;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
@@ -89,17 +90,16 @@ public class MailboxIntegrationTest extends BrambleTestCase {
assumeTrue(isOptionalTestEnabled(MailboxIntegrationTest.class)); assumeTrue(isOptionalTestEnabled(MailboxIntegrationTest.class));
if (ownerProperties != null) return; if (ownerProperties != null) return;
MailboxProperties setupProperties = MailboxProperties setupProperties = new MailboxProperties(
new MailboxProperties(URL_BASE, SETUP_TOKEN, true); URL_BASE, SETUP_TOKEN, true, new ArrayList<>());
MailboxAuthToken ownerToken = api.setup(setupProperties); ownerProperties = api.setup(setupProperties);
ownerProperties = new MailboxProperties(URL_BASE, ownerToken, true);
} }
@AfterClass @AfterClass
// we can't test wiping as a regular test as it stops the mailbox // we can't test wiping as a regular test as it stops the mailbox
public static void wipe() throws IOException, ApiException { public static void wipe() throws IOException, ApiException {
if (!isOptionalTestEnabled(MailboxIntegrationTest.class)) return; if (!isOptionalTestEnabled(MailboxIntegrationTest.class)) return;
api.wipeMailbox(ownerProperties); api.wipeMailbox(ownerProperties);
// check doesn't work anymore // check doesn't work anymore
@@ -107,8 +107,8 @@ public class MailboxIntegrationTest extends BrambleTestCase {
api.checkStatus(ownerProperties)); api.checkStatus(ownerProperties));
// new setup doesn't work as mailbox is stopping // new setup doesn't work as mailbox is stopping
MailboxProperties setupProperties = MailboxProperties setupProperties = new MailboxProperties(
new MailboxProperties(URL_BASE, SETUP_TOKEN, true); URL_BASE, SETUP_TOKEN, true, new ArrayList<>());
assertThrows(ApiException.class, () -> api.setup(setupProperties)); assertThrows(ApiException.class, () -> api.setup(setupProperties));
} }
@@ -151,7 +151,8 @@ public class MailboxIntegrationTest extends BrambleTestCase {
ContactId contactId = new ContactId(1); ContactId contactId = new ContactId(1);
MailboxContact contact = getMailboxContact(contactId); MailboxContact contact = getMailboxContact(contactId);
MailboxProperties contactProperties = new MailboxProperties( MailboxProperties contactProperties = new MailboxProperties(
ownerProperties.getBaseUrl(), contact.token, false); ownerProperties.getBaseUrl(), contact.token, false,
new ArrayList<>());
api.addContact(ownerProperties, contact); api.addContact(ownerProperties, contact);
// upload a file for our contact // upload a file for our contact

View File

@@ -23,6 +23,7 @@ import org.junit.Test;
import java.io.IOException; import java.io.IOException;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.charset.Charset; import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
@@ -61,10 +62,10 @@ public class MailboxPairingTaskImplTest extends BrambleMockTestCase {
new MailboxAuthToken(getRandomId()); new MailboxAuthToken(getRandomId());
private final String validPayload = getValidPayload(); private final String validPayload = getValidPayload();
private final long time = System.currentTimeMillis(); private final long time = System.currentTimeMillis();
private final MailboxProperties setupProperties = private final MailboxProperties setupProperties = new MailboxProperties(
new MailboxProperties(onionAddress, setupToken, true); onionAddress, setupToken, true, new ArrayList<>());
private final MailboxProperties ownerProperties = private final MailboxProperties ownerProperties = new MailboxProperties(
new MailboxProperties(onionAddress, ownerToken, true); onionAddress, ownerToken, true, new ArrayList<>());
@Test @Test
public void testInitialQrCodeReceivedState() { public void testInitialQrCodeReceivedState() {
@@ -98,7 +99,7 @@ public class MailboxPairingTaskImplTest extends BrambleMockTestCase {
oneOf(crypto).encodeOnion(onionBytes); oneOf(crypto).encodeOnion(onionBytes);
will(returnValue(onion)); will(returnValue(onion));
oneOf(api).setup(with(matches(setupProperties))); oneOf(api).setup(with(matches(setupProperties)));
will(returnValue(ownerToken)); will(returnValue(ownerProperties));
oneOf(clock).currentTimeMillis(); oneOf(clock).currentTimeMillis();
will(returnValue(time)); will(returnValue(time));
}}); }});
@@ -174,7 +175,7 @@ public class MailboxPairingTaskImplTest extends BrambleMockTestCase {
oneOf(crypto).encodeOnion(onionBytes); oneOf(crypto).encodeOnion(onionBytes);
will(returnValue(onion)); will(returnValue(onion));
oneOf(api).setup(with(matches(setupProperties))); oneOf(api).setup(with(matches(setupProperties)));
will(returnValue(ownerToken)); will(returnValue(ownerProperties));
oneOf(clock).currentTimeMillis(); oneOf(clock).currentTimeMillis();
will(returnValue(time)); will(returnValue(time));
}}); }});
@@ -206,7 +207,8 @@ public class MailboxPairingTaskImplTest extends BrambleMockTestCase {
return new PredicateMatcher<>(MailboxProperties.class, p1 -> return new PredicateMatcher<>(MailboxProperties.class, p1 ->
p1.getAuthToken().equals(p2.getAuthToken()) && p1.getAuthToken().equals(p2.getAuthToken()) &&
p1.getBaseUrl().equals(p2.getBaseUrl()) && p1.getBaseUrl().equals(p2.getBaseUrl()) &&
p1.isOwner() == p2.isOwner()); p1.isOwner() == p2.isOwner() &&
p1.getServerSupports().equals(p2.getServerSupports()));
} }
} }

View File

@@ -27,6 +27,7 @@ import org.briarproject.bramble.test.BrambleMockTestCase;
import org.jmock.Expectations; import org.jmock.Expectations;
import org.junit.Test; import org.junit.Test;
import java.util.ArrayList;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@@ -76,7 +77,7 @@ public class MailboxPropertyManagerImplTest extends BrambleMockTestCase {
public MailboxPropertyManagerImplTest() { public MailboxPropertyManagerImplTest() {
ownProps = new MailboxProperties("http://bar.onion", ownProps = new MailboxProperties("http://bar.onion",
new MailboxAuthToken(getRandomId()), true); new MailboxAuthToken(getRandomId()), true, new ArrayList<>());
props = new MailboxPropertiesUpdate(ownProps.getOnion(), props = new MailboxPropertiesUpdate(ownProps.getOnion(),
new MailboxAuthToken(getRandomId()), new MailboxAuthToken(getRandomId()),
new MailboxFolderId(getRandomId()), new MailboxFolderId(getRandomId()),
@@ -281,6 +282,7 @@ public class MailboxPropertyManagerImplTest extends BrambleMockTestCase {
public void testDoesNotDeleteAnythingWhenFirstUpdateIsDelivered() public void testDoesNotDeleteAnythingWhenFirstUpdateIsDelivered()
throws Exception { throws Exception {
Transaction txn = new Transaction(null, false); Transaction txn = new Transaction(null, false);
Contact contact = getContact();
GroupId contactGroupId = new GroupId(getRandomId()); GroupId contactGroupId = new GroupId(getRandomId());
Message message = getMessage(contactGroupId); Message message = getMessage(contactGroupId);
BdfList body = BdfList.of(1, propsDict); BdfList body = BdfList.of(1, propsDict);
@@ -304,11 +306,13 @@ public class MailboxPropertyManagerImplTest extends BrambleMockTestCase {
contactGroupId); contactGroupId);
will(returnValue(messageMetadata)); will(returnValue(messageMetadata));
oneOf(clientHelper).getContactId(txn, contactGroupId); oneOf(clientHelper).getContactId(txn, contactGroupId);
will(returnValue(contact.getId()));
oneOf(clientHelper).getMessageAsList(txn, message.getId()); oneOf(clientHelper).getMessageAsList(txn, message.getId());
will(returnValue(body)); will(returnValue(body));
oneOf(clientHelper).parseAndValidateMailboxPropertiesUpdate( oneOf(clientHelper).parseAndValidateMailboxPropertiesUpdate(
propsDict); propsDict);
will(returnValue(props)); will(returnValue(props));
oneOf(db).resetUnackedMessagesToSend(txn, contact.getId());
}}); }});
MailboxPropertyManagerImpl t = createInstance(); MailboxPropertyManagerImpl t = createInstance();
@@ -321,6 +325,7 @@ public class MailboxPropertyManagerImplTest extends BrambleMockTestCase {
public void testDeletesOlderUpdateWhenUpdateIsDelivered() public void testDeletesOlderUpdateWhenUpdateIsDelivered()
throws Exception { throws Exception {
Transaction txn = new Transaction(null, false); Transaction txn = new Transaction(null, false);
Contact contact = getContact();
GroupId contactGroupId = new GroupId(getRandomId()); GroupId contactGroupId = new GroupId(getRandomId());
Message message = getMessage(contactGroupId); Message message = getMessage(contactGroupId);
BdfList body = BdfList.of(1, propsDict); BdfList body = BdfList.of(1, propsDict);
@@ -352,11 +357,13 @@ public class MailboxPropertyManagerImplTest extends BrambleMockTestCase {
oneOf(db).deleteMessage(txn, updateId); oneOf(db).deleteMessage(txn, updateId);
oneOf(db).deleteMessageMetadata(txn, updateId); oneOf(db).deleteMessageMetadata(txn, updateId);
oneOf(clientHelper).getContactId(txn, contactGroupId); oneOf(clientHelper).getContactId(txn, contactGroupId);
will(returnValue(contact.getId()));
oneOf(clientHelper).getMessageAsList(txn, message.getId()); oneOf(clientHelper).getMessageAsList(txn, message.getId());
will(returnValue(body)); will(returnValue(body));
oneOf(clientHelper).parseAndValidateMailboxPropertiesUpdate( oneOf(clientHelper).parseAndValidateMailboxPropertiesUpdate(
propsDict); propsDict);
will(returnValue(props)); will(returnValue(props));
oneOf(db).resetUnackedMessagesToSend(txn, contact.getId());
}}); }});
MailboxPropertyManagerImpl t = createInstance(); MailboxPropertyManagerImpl t = createInstance();

View File

@@ -6,6 +6,7 @@ import org.briarproject.bramble.api.mailbox.MailboxAuthToken;
import org.briarproject.bramble.api.mailbox.MailboxProperties; import org.briarproject.bramble.api.mailbox.MailboxProperties;
import org.briarproject.bramble.api.mailbox.MailboxSettingsManager; import org.briarproject.bramble.api.mailbox.MailboxSettingsManager;
import org.briarproject.bramble.api.mailbox.MailboxStatus; import org.briarproject.bramble.api.mailbox.MailboxStatus;
import org.briarproject.bramble.api.mailbox.MailboxVersion;
import org.briarproject.bramble.api.mailbox.OwnMailboxConnectionStatusEvent; import org.briarproject.bramble.api.mailbox.OwnMailboxConnectionStatusEvent;
import org.briarproject.bramble.api.settings.Settings; import org.briarproject.bramble.api.settings.Settings;
import org.briarproject.bramble.api.settings.SettingsManager; import org.briarproject.bramble.api.settings.SettingsManager;
@@ -13,12 +14,15 @@ import org.briarproject.bramble.test.BrambleMockTestCase;
import org.jmock.Expectations; import org.jmock.Expectations;
import org.junit.Test; import org.junit.Test;
import java.util.List;
import java.util.Random; import java.util.Random;
import static java.util.Arrays.asList;
import static org.briarproject.bramble.mailbox.MailboxSettingsManagerImpl.SETTINGS_KEY_ATTEMPTS; import static org.briarproject.bramble.mailbox.MailboxSettingsManagerImpl.SETTINGS_KEY_ATTEMPTS;
import static org.briarproject.bramble.mailbox.MailboxSettingsManagerImpl.SETTINGS_KEY_LAST_ATTEMPT; import static org.briarproject.bramble.mailbox.MailboxSettingsManagerImpl.SETTINGS_KEY_LAST_ATTEMPT;
import static org.briarproject.bramble.mailbox.MailboxSettingsManagerImpl.SETTINGS_KEY_LAST_SUCCESS; import static org.briarproject.bramble.mailbox.MailboxSettingsManagerImpl.SETTINGS_KEY_LAST_SUCCESS;
import static org.briarproject.bramble.mailbox.MailboxSettingsManagerImpl.SETTINGS_KEY_ONION; import static org.briarproject.bramble.mailbox.MailboxSettingsManagerImpl.SETTINGS_KEY_ONION;
import static org.briarproject.bramble.mailbox.MailboxSettingsManagerImpl.SETTINGS_KEY_SERVER_SUPPORTS;
import static org.briarproject.bramble.mailbox.MailboxSettingsManagerImpl.SETTINGS_KEY_TOKEN; import static org.briarproject.bramble.mailbox.MailboxSettingsManagerImpl.SETTINGS_KEY_TOKEN;
import static org.briarproject.bramble.mailbox.MailboxSettingsManagerImpl.SETTINGS_NAMESPACE; import static org.briarproject.bramble.mailbox.MailboxSettingsManagerImpl.SETTINGS_NAMESPACE;
import static org.briarproject.bramble.mailbox.MailboxSettingsManagerImpl.SETTINGS_UPLOADS_NAMESPACE; import static org.briarproject.bramble.mailbox.MailboxSettingsManagerImpl.SETTINGS_UPLOADS_NAMESPACE;
@@ -40,6 +44,9 @@ public class MailboxSettingsManagerImplTest extends BrambleMockTestCase {
private final Random random = new Random(); private final Random random = new Random();
private final String onion = getRandomString(64); private final String onion = getRandomString(64);
private final MailboxAuthToken token = new MailboxAuthToken(getRandomId()); private final MailboxAuthToken token = new MailboxAuthToken(getRandomId());
private final List<MailboxVersion> serverSupports =
asList(new MailboxVersion(1, 0), new MailboxVersion(1, 1));
private final int[] serverSupportsInts = {1, 0, 1, 1};
private final ContactId contactId1 = new ContactId(random.nextInt()); private final ContactId contactId1 = new ContactId(random.nextInt());
private final ContactId contactId2 = new ContactId(random.nextInt()); private final ContactId contactId2 = new ContactId(random.nextInt());
private final ContactId contactId3 = new ContactId(random.nextInt()); private final ContactId contactId3 = new ContactId(random.nextInt());
@@ -67,6 +74,7 @@ public class MailboxSettingsManagerImplTest extends BrambleMockTestCase {
Settings settings = new Settings(); Settings settings = new Settings();
settings.put(SETTINGS_KEY_ONION, onion); settings.put(SETTINGS_KEY_ONION, onion);
settings.put(SETTINGS_KEY_TOKEN, token.toString()); settings.put(SETTINGS_KEY_TOKEN, token.toString());
settings.putIntArray(SETTINGS_KEY_SERVER_SUPPORTS, serverSupportsInts);
context.checking(new Expectations() {{ context.checking(new Expectations() {{
oneOf(settingsManager).getSettings(txn, SETTINGS_NAMESPACE); oneOf(settingsManager).getSettings(txn, SETTINGS_NAMESPACE);
@@ -77,6 +85,7 @@ public class MailboxSettingsManagerImplTest extends BrambleMockTestCase {
assertNotNull(properties); assertNotNull(properties);
assertEquals(onion, properties.getBaseUrl()); assertEquals(onion, properties.getBaseUrl());
assertEquals(token, properties.getAuthToken()); assertEquals(token, properties.getAuthToken());
assertEquals(serverSupports, properties.getServerSupports());
assertTrue(properties.isOwner()); assertTrue(properties.isOwner());
} }
@@ -86,8 +95,10 @@ public class MailboxSettingsManagerImplTest extends BrambleMockTestCase {
Settings expectedSettings = new Settings(); Settings expectedSettings = new Settings();
expectedSettings.put(SETTINGS_KEY_ONION, onion); expectedSettings.put(SETTINGS_KEY_ONION, onion);
expectedSettings.put(SETTINGS_KEY_TOKEN, token.toString()); expectedSettings.put(SETTINGS_KEY_TOKEN, token.toString());
MailboxProperties properties = expectedSettings.putIntArray(SETTINGS_KEY_SERVER_SUPPORTS,
new MailboxProperties(onion, token, true); serverSupportsInts);
MailboxProperties properties = new MailboxProperties(onion, token, true,
serverSupports);
context.checking(new Expectations() {{ context.checking(new Expectations() {{
oneOf(settingsManager).mergeSettings(txn, expectedSettings, oneOf(settingsManager).mergeSettings(txn, expectedSettings,

View File

@@ -47,11 +47,11 @@ public class MailboxActivity extends BriarActivity {
setContentView(R.layout.activity_mailbox); setContentView(R.layout.activity_mailbox);
progressBar = findViewById(R.id.progressBar); progressBar = findViewById(R.id.progressBar);
if (viewModel.getState().getValue() == null) { if (viewModel.getPairingState().getValue() == null) {
progressBar.setVisibility(VISIBLE); progressBar.setVisibility(VISIBLE);
} }
viewModel.getState().observeEvent(this, state -> { viewModel.getPairingState().observeEvent(this, state -> {
if (state instanceof MailboxState.NotSetup) { if (state instanceof MailboxState.NotSetup) {
onNotSetup(); onNotSetup();
} else if (state instanceof MailboxState.ShowDownload) { } else if (state instanceof MailboxState.ShowDownload) {
@@ -67,7 +67,7 @@ public class MailboxActivity extends BriarActivity {
} else if (state instanceof MailboxState.CameraError) { } else if (state instanceof MailboxState.CameraError) {
onCameraError(); onCameraError();
} else if (state instanceof MailboxState.IsPaired) { } else if (state instanceof MailboxState.IsPaired) {
onIsPaired(); onIsPaired(((MailboxState.IsPaired) state).isOnline);
} else { } else {
throw new AssertionError("Unknown state: " + state); throw new AssertionError("Unknown state: " + state);
} }
@@ -85,7 +85,7 @@ public class MailboxActivity extends BriarActivity {
@Override @Override
public void onBackPressed() { public void onBackPressed() {
MailboxState s = viewModel.getState().getLastValue(); MailboxState s = viewModel.getPairingState().getLastValue();
if (s instanceof MailboxState.Pairing) { if (s instanceof MailboxState.Pairing) {
// don't go back in the flow if we are already pairing // don't go back in the flow if we are already pairing
// with the mailbox. We provide a try-again button instead. // with the mailbox. We provide a try-again button instead.
@@ -181,10 +181,13 @@ public class MailboxActivity extends BriarActivity {
showFragment(getSupportFragmentManager(), f, ErrorFragment.TAG); showFragment(getSupportFragmentManager(), f, ErrorFragment.TAG);
} }
private void onIsPaired() { private void onIsPaired(boolean isOnline) {
progressBar.setVisibility(INVISIBLE); progressBar.setVisibility(INVISIBLE);
showFragment(getSupportFragmentManager(), new MailboxStatusFragment(), Fragment f = isOnline ?
MailboxStatusFragment.TAG, false); new MailboxStatusFragment() : new OfflineStatusFragment();
String tag = isOnline ?
MailboxStatusFragment.TAG : OfflineStatusFragment.TAG;
showFragment(getSupportFragmentManager(), f, tag, false);
} }
} }

View File

@@ -1,7 +1,6 @@
package org.briarproject.briar.android.mailbox; package org.briarproject.briar.android.mailbox;
import org.briarproject.bramble.api.mailbox.MailboxPairingState; import org.briarproject.bramble.api.mailbox.MailboxPairingState;
import org.briarproject.bramble.api.mailbox.MailboxStatus;
class MailboxState { class MailboxState {
@@ -29,10 +28,10 @@ class MailboxState {
} }
static class IsPaired extends MailboxState { static class IsPaired extends MailboxState {
final MailboxStatus mailboxStatus; final boolean isOnline;
IsPaired(MailboxStatus mailboxStatus) { IsPaired(boolean isOnline) {
this.mailboxStatus = mailboxStatus; this.isOnline = isOnline;
} }
} }

View File

@@ -1,37 +1,62 @@
package org.briarproject.briar.android.mailbox; package org.briarproject.briar.android.mailbox;
import android.content.Context; import android.content.Context;
import android.content.res.ColorStateList;
import android.os.Bundle; import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.ProgressBar;
import android.widget.TextView; import android.widget.TextView;
import android.widget.Toast;
import org.briarproject.bramble.api.mailbox.MailboxStatus;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault; import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault; import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.briar.R; import org.briarproject.briar.R;
import javax.inject.Inject; import javax.inject.Inject;
import androidx.annotation.ColorRes;
import androidx.annotation.DrawableRes;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.annotation.UiThread;
import androidx.fragment.app.Fragment; import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentActivity; import androidx.fragment.app.FragmentActivity;
import androidx.lifecycle.ViewModelProvider; import androidx.lifecycle.ViewModelProvider;
import static java.util.Objects.requireNonNull; import static android.view.View.INVISIBLE;
import static android.view.View.VISIBLE;
import static androidx.core.content.ContextCompat.getColor;
import static androidx.core.widget.ImageViewCompat.setImageTintList;
import static androidx.transition.TransitionManager.beginDelayedTransition;
import static org.briarproject.briar.android.AppModule.getAndroidComponent; import static org.briarproject.briar.android.AppModule.getAndroidComponent;
import static org.briarproject.briar.android.util.UiUtils.MIN_DATE_RESOLUTION;
import static org.briarproject.briar.android.util.UiUtils.formatDate; import static org.briarproject.briar.android.util.UiUtils.formatDate;
import static org.briarproject.briar.android.util.UiUtils.observeOnce;
@MethodsNotNullByDefault @MethodsNotNullByDefault
@ParametersNotNullByDefault @ParametersNotNullByDefault
public class MailboxStatusFragment extends Fragment { public class MailboxStatusFragment extends Fragment {
static final String TAG = MailboxStatusFragment.class.getName(); static final String TAG = MailboxStatusFragment.class.getName();
private static final int NUM_FAILURES = 4;
@Inject @Inject
ViewModelProvider.Factory viewModelFactory; ViewModelProvider.Factory viewModelFactory;
private MailboxViewModel viewModel; private MailboxViewModel viewModel;
private final Handler handler = new Handler(Looper.getMainLooper());
@Nullable // UiThread
private Runnable refresher = null;
private ImageView imageView;
private TextView statusTitleView;
private TextView statusInfoView;
@Override @Override
public void onAttach(Context context) { public void onAttach(Context context) {
@@ -54,11 +79,72 @@ public class MailboxStatusFragment extends Fragment {
@Override @Override
public void onViewCreated(View v, @Nullable Bundle savedInstanceState) { public void onViewCreated(View v, @Nullable Bundle savedInstanceState) {
super.onViewCreated(v, savedInstanceState); super.onViewCreated(v, savedInstanceState);
MailboxState.IsPaired state =
(MailboxState.IsPaired) viewModel.getState().getLastValue(); Button checkButton = v.findViewById(R.id.checkButton);
requireNonNull(state); // TODO check assumption ProgressBar checkProgress = v.findViewById(R.id.checkProgress);
TextView statusInfoView = v.findViewById(R.id.statusInfoView); checkButton.setOnClickListener(view -> {
long lastSuccess = state.mailboxStatus.getTimeOfLastSuccess(); beginDelayedTransition((ViewGroup) v);
checkButton.setVisibility(INVISIBLE);
checkProgress.setVisibility(VISIBLE);
observeOnce(viewModel.checkConnection(), this, result -> {
beginDelayedTransition((ViewGroup) v);
checkButton.setVisibility(VISIBLE);
checkProgress.setVisibility(INVISIBLE);
});
});
imageView = v.findViewById(R.id.imageView);
statusTitleView = v.findViewById(R.id.statusTitleView);
statusInfoView = v.findViewById(R.id.statusInfoView);
viewModel.getStatus()
.observe(getViewLifecycleOwner(), this::onMailboxStateChanged);
// TODO
// * detect problems and show them #2175
// * add "Unlink" button confirmation dialog and functionality #2173
Button unlinkButton = v.findViewById(R.id.unlinkButton);
unlinkButton.setOnClickListener(view -> Toast.makeText(requireContext(),
"NOT IMPLEMENTED", Toast.LENGTH_SHORT).show());
}
@Override
public void onStart() {
super.onStart();
requireActivity().setTitle(R.string.mailbox_status_title);
refresher = this::refreshLastConnection;
handler.postDelayed(refresher, MIN_DATE_RESOLUTION);
}
@Override
public void onStop() {
super.onStop();
handler.removeCallbacks(refresher);
refresher = null;
}
private void onMailboxStateChanged(MailboxStatus status) {
@ColorRes int tintRes;
@DrawableRes int iconRes;
String title;
if (status.getAttemptsSinceSuccess() == 0) {
iconRes = R.drawable.ic_check_circle_outline;
title = getString(R.string.mailbox_status_connected_title);
tintRes = R.color.briar_brand_green;
} else if (status.getAttemptsSinceSuccess() < NUM_FAILURES) {
iconRes = R.drawable.ic_help_outline_white;
title = getString(R.string.mailbox_status_problem_title);
tintRes = R.color.briar_orange_500;
} else {
tintRes = R.color.briar_red_500;
title = getString(R.string.mailbox_status_failure_title);
iconRes = R.drawable.alerts_and_states_error;
}
imageView.setImageResource(iconRes);
int color = getColor(requireContext(), tintRes);
setImageTintList(imageView, ColorStateList.valueOf(color));
statusTitleView.setText(title);
long lastSuccess = status.getTimeOfLastSuccess();
String lastConnectionText; String lastConnectionText;
if (lastSuccess < 0) { if (lastSuccess < 0) {
lastConnectionText = lastConnectionText =
@@ -66,21 +152,19 @@ public class MailboxStatusFragment extends Fragment {
} else { } else {
lastConnectionText = formatDate(requireContext(), lastSuccess); lastConnectionText = formatDate(requireContext(), lastSuccess);
} }
String statusInfoText = getString( String statusInfoText =
R.string.mailbox_status_connected_info, lastConnectionText); getString(R.string.mailbox_status_connected_info,
lastConnectionText);
statusInfoView.setText(statusInfoText); statusInfoView.setText(statusInfoText);
// TODO
// * react to status changes
// * detect problems and show them
// * update connection time periodically like conversation timestamps
// * add "Check connection" button
// * add "Unlink" button with confirmation dialog
} }
@Override @UiThread
public void onStart() { private void refreshLastConnection() {
super.onStart(); MailboxStatus status = viewModel.getStatus().getValue();
requireActivity().setTitle(R.string.mailbox_status_title); if (status != null) onMailboxStateChanged(status);
if (refresher != null) {
handler.postDelayed(refresher, MIN_DATE_RESOLUTION);
}
} }
} }

View File

@@ -7,16 +7,22 @@ import com.google.zxing.Result;
import org.briarproject.bramble.api.Consumer; import org.briarproject.bramble.api.Consumer;
import org.briarproject.bramble.api.db.DatabaseExecutor; import org.briarproject.bramble.api.db.DatabaseExecutor;
import org.briarproject.bramble.api.db.TransactionManager; import org.briarproject.bramble.api.db.TransactionManager;
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.lifecycle.IoExecutor; import org.briarproject.bramble.api.lifecycle.IoExecutor;
import org.briarproject.bramble.api.lifecycle.LifecycleManager; import org.briarproject.bramble.api.lifecycle.LifecycleManager;
import org.briarproject.bramble.api.mailbox.MailboxManager; import org.briarproject.bramble.api.mailbox.MailboxManager;
import org.briarproject.bramble.api.mailbox.MailboxPairingState; import org.briarproject.bramble.api.mailbox.MailboxPairingState;
import org.briarproject.bramble.api.mailbox.MailboxPairingTask; import org.briarproject.bramble.api.mailbox.MailboxPairingTask;
import org.briarproject.bramble.api.mailbox.MailboxStatus; import org.briarproject.bramble.api.mailbox.MailboxStatus;
import org.briarproject.bramble.api.mailbox.OwnMailboxConnectionStatusEvent;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.Plugin; import org.briarproject.bramble.api.plugin.Plugin;
import org.briarproject.bramble.api.plugin.PluginManager; import org.briarproject.bramble.api.plugin.PluginManager;
import org.briarproject.bramble.api.plugin.TorConstants; import org.briarproject.bramble.api.plugin.TorConstants;
import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.plugin.event.TransportInactiveEvent;
import org.briarproject.bramble.api.system.AndroidExecutor; import org.briarproject.bramble.api.system.AndroidExecutor;
import org.briarproject.briar.android.mailbox.MailboxState.NotSetup; import org.briarproject.briar.android.mailbox.MailboxState.NotSetup;
import org.briarproject.briar.android.qrcode.QrCodeDecoder; import org.briarproject.briar.android.qrcode.QrCodeDecoder;
@@ -32,6 +38,8 @@ import javax.inject.Inject;
import androidx.annotation.AnyThread; import androidx.annotation.AnyThread;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.annotation.UiThread; import androidx.annotation.UiThread;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import static java.util.logging.Level.INFO; import static java.util.logging.Level.INFO;
import static java.util.logging.Logger.getLogger; import static java.util.logging.Logger.getLogger;
@@ -39,17 +47,22 @@ import static org.briarproject.bramble.api.plugin.Plugin.State.ACTIVE;
@NotNullByDefault @NotNullByDefault
class MailboxViewModel extends DbViewModel class MailboxViewModel extends DbViewModel
implements QrCodeDecoder.ResultCallback, Consumer<MailboxPairingState> { implements QrCodeDecoder.ResultCallback, Consumer<MailboxPairingState>,
EventListener {
private static final Logger LOG = private static final Logger LOG =
getLogger(MailboxViewModel.class.getName()); getLogger(MailboxViewModel.class.getName());
private final EventBus eventBus;
private final Executor ioExecutor;
private final QrCodeDecoder qrCodeDecoder; private final QrCodeDecoder qrCodeDecoder;
private final PluginManager pluginManager; private final PluginManager pluginManager;
private final MailboxManager mailboxManager; private final MailboxManager mailboxManager;
private final MutableLiveEvent<MailboxState> state = private final MutableLiveEvent<MailboxState> pairingState =
new MutableLiveEvent<>(); new MutableLiveEvent<>();
private final MutableLiveData<MailboxStatus> status =
new MutableLiveData<>();
@Nullable @Nullable
private MailboxPairingTask pairingTask = null; private MailboxPairingTask pairingTask = null;
@@ -60,19 +73,24 @@ class MailboxViewModel extends DbViewModel
LifecycleManager lifecycleManager, LifecycleManager lifecycleManager,
TransactionManager db, TransactionManager db,
AndroidExecutor androidExecutor, AndroidExecutor androidExecutor,
EventBus eventBus,
@IoExecutor Executor ioExecutor, @IoExecutor Executor ioExecutor,
PluginManager pluginManager, PluginManager pluginManager,
MailboxManager mailboxManager) { MailboxManager mailboxManager) {
super(app, dbExecutor, lifecycleManager, db, androidExecutor); super(app, dbExecutor, lifecycleManager, db, androidExecutor);
this.eventBus = eventBus;
this.ioExecutor = ioExecutor;
this.pluginManager = pluginManager; this.pluginManager = pluginManager;
this.mailboxManager = mailboxManager; this.mailboxManager = mailboxManager;
qrCodeDecoder = new QrCodeDecoder(androidExecutor, ioExecutor, this); qrCodeDecoder = new QrCodeDecoder(androidExecutor, ioExecutor, this);
eventBus.addListener(this);
checkIfSetup(); checkIfSetup();
} }
@Override @Override
protected void onCleared() { protected void onCleared() {
super.onCleared(); super.onCleared();
eventBus.removeListener(this);
MailboxPairingTask task = pairingTask; MailboxPairingTask task = pairingTask;
if (task != null) { if (task != null) {
task.removeObserver(this); task.removeObserver(this);
@@ -89,9 +107,11 @@ class MailboxViewModel extends DbViewModel
if (isPaired) { if (isPaired) {
MailboxStatus mailboxStatus = MailboxStatus mailboxStatus =
mailboxManager.getMailboxStatus(txn); mailboxManager.getMailboxStatus(txn);
state.postEvent(new MailboxState.IsPaired(mailboxStatus)); boolean isOnline = isTorActive();
pairingState.postEvent(new MailboxState.IsPaired(isOnline));
status.postValue(mailboxStatus);
} else { } else {
state.postEvent(new NotSetup()); pairingState.postEvent(new NotSetup());
} }
}, this::handleException); }, this::handleException);
} else { } else {
@@ -100,18 +120,37 @@ class MailboxViewModel extends DbViewModel
} }
} }
@UiThread
@Override
public void eventOccurred(Event e) {
if (e instanceof OwnMailboxConnectionStatusEvent) {
MailboxStatus status =
((OwnMailboxConnectionStatusEvent) e).getStatus();
this.status.setValue(status);
} else if (e instanceof TransportInactiveEvent) {
TransportId id = ((TransportInactiveEvent) e).getTransportId();
if (!TorConstants.ID.equals(id)) return;
MailboxState lastState = pairingState.getLastValue();
if (lastState instanceof MailboxState.IsPaired) {
pairingState.setEvent(new MailboxState.IsPaired(false));
} else if (lastState != null) {
pairingState.setEvent(new MailboxState.OfflineWhenPairing());
}
}
}
@UiThread @UiThread
void onScanButtonClicked() { void onScanButtonClicked() {
if (isTorActive()) { if (isTorActive()) {
state.setEvent(new MailboxState.ScanningQrCode()); pairingState.setEvent(new MailboxState.ScanningQrCode());
} else { } else {
state.setEvent(new MailboxState.OfflineWhenPairing()); pairingState.setEvent(new MailboxState.OfflineWhenPairing());
} }
} }
@UiThread @UiThread
void onCameraError() { void onCameraError() {
state.setEvent(new MailboxState.CameraError()); pairingState.setEvent(new MailboxState.CameraError());
} }
@Override @Override
@@ -127,7 +166,7 @@ class MailboxViewModel extends DbViewModel
pairingTask = mailboxManager.startPairingTask(qrCodePayload); pairingTask = mailboxManager.startPairingTask(qrCodePayload);
pairingTask.addObserver(this); pairingTask.addObserver(this);
} else { } else {
state.postEvent(new MailboxState.OfflineWhenPairing()); pairingState.postEvent(new MailboxState.OfflineWhenPairing());
} }
} }
@@ -138,7 +177,7 @@ class MailboxViewModel extends DbViewModel
LOG.info("New pairing state: " + LOG.info("New pairing state: " +
mailboxPairingState.getClass().getSimpleName()); mailboxPairingState.getClass().getSimpleName());
} }
state.setEvent(new MailboxState.Pairing(mailboxPairingState)); pairingState.setEvent(new MailboxState.Pairing(mailboxPairingState));
} }
private boolean isTorActive() { private boolean isTorActive() {
@@ -148,7 +187,7 @@ class MailboxViewModel extends DbViewModel
@UiThread @UiThread
void showDownloadFragment() { void showDownloadFragment() {
state.setEvent(new MailboxState.ShowDownload()); pairingState.setEvent(new MailboxState.ShowDownload());
} }
@UiThread @UiThread
@@ -157,7 +196,37 @@ class MailboxViewModel extends DbViewModel
} }
@UiThread @UiThread
LiveEvent<MailboxState> getState() { void checkIfOnlineWhenPaired() {
return state; boolean isOnline = isTorActive();
pairingState.setEvent(new MailboxState.IsPaired(isOnline));
}
LiveData<Boolean> checkConnection() {
MutableLiveData<Boolean> liveData = new MutableLiveData<>();
ioExecutor.execute(() -> {
boolean success = mailboxManager.checkConnection();
if (LOG.isLoggable(INFO)) {
LOG.info("Got result from connection check: " + success);
}
liveData.postValue(success);
if (!success) { // force failure screen
MailboxStatus lastStatus = status.getValue();
long lastSuccess = lastStatus == null ?
-1 : lastStatus.getTimeOfLastSuccess();
long now = System.currentTimeMillis();
status.postValue(new MailboxStatus(now, lastSuccess, 999));
}
});
return liveData;
}
@UiThread
LiveEvent<MailboxState> getPairingState() {
return pairingState;
}
@UiThread
LiveData<MailboxStatus> getStatus() {
return status;
} }
} }

View File

@@ -33,10 +33,9 @@ public class OfflineFragment extends Fragment {
@Inject @Inject
ViewModelProvider.Factory viewModelFactory; ViewModelProvider.Factory viewModelFactory;
private MailboxViewModel viewModel; protected MailboxViewModel viewModel;
private NestedScrollView scrollView; private NestedScrollView scrollView;
protected Button buttonView;
@Override @Override
public void onAttach(Context context) { public void onAttach(Context context) {
@@ -61,8 +60,8 @@ public class OfflineFragment extends Fragment {
Intent i = new Intent(requireContext(), TransportsActivity.class); Intent i = new Intent(requireContext(), TransportsActivity.class);
startActivity(i); startActivity(i);
}); });
buttonView = v.findViewById(R.id.button); Button buttonView = v.findViewById(R.id.button);
buttonView.setOnClickListener(view -> viewModel.showDownloadFragment()); buttonView.setOnClickListener(view -> onTryAgainClicked());
return v; return v;
} }
@@ -74,4 +73,8 @@ public class OfflineFragment extends Fragment {
scrollView.post(() -> scrollView.fullScroll(FOCUS_DOWN)); scrollView.post(() -> scrollView.fullScroll(FOCUS_DOWN));
} }
protected void onTryAgainClicked() {
viewModel.showDownloadFragment();
}
} }

View File

@@ -0,0 +1,17 @@
package org.briarproject.briar.android.mailbox;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
@MethodsNotNullByDefault
@ParametersNotNullByDefault
public class OfflineStatusFragment extends OfflineFragment {
public static final String TAG = OfflineStatusFragment.class.getName();
@Override
protected void onTryAgainClicked() {
viewModel.checkIfOnlineWhenPaired();
}
}

View File

@@ -215,6 +215,7 @@ public class NavDrawerActivity extends BriarActivity implements
if (IS_DEBUG_BUILD || shouldWarnOldAndroidExpiry()) { if (IS_DEBUG_BUILD || shouldWarnOldAndroidExpiry()) {
navDrawerViewModel.checkExpiryWarning(); navDrawerViewModel.checkExpiryWarning();
} }
navDrawerViewModel.printStats();
} }
@Override @Override

View File

@@ -2,9 +2,9 @@ package org.briarproject.briar.android.navdrawer;
import android.app.Application; import android.app.Application;
import org.briarproject.bramble.api.db.DatabaseComponent;
import org.briarproject.bramble.api.db.DatabaseExecutor; import org.briarproject.bramble.api.db.DatabaseExecutor;
import org.briarproject.bramble.api.db.DbException; import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.TransactionManager;
import org.briarproject.bramble.api.lifecycle.LifecycleManager; import org.briarproject.bramble.api.lifecycle.LifecycleManager;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.settings.Settings; import org.briarproject.bramble.api.settings.Settings;
@@ -41,6 +41,7 @@ public class NavDrawerViewModel extends DbViewModel {
private static final String SHOW_TRANSPORTS_ONBOARDING = private static final String SHOW_TRANSPORTS_ONBOARDING =
"showTransportsOnboarding"; "showTransportsOnboarding";
private final DatabaseComponent db;
private final SettingsManager settingsManager; private final SettingsManager settingsManager;
private final MutableLiveData<Boolean> showExpiryWarning = private final MutableLiveData<Boolean> showExpiryWarning =
@@ -54,10 +55,11 @@ public class NavDrawerViewModel extends DbViewModel {
NavDrawerViewModel(Application app, NavDrawerViewModel(Application app,
@DatabaseExecutor Executor dbExecutor, @DatabaseExecutor Executor dbExecutor,
LifecycleManager lifecycleManager, LifecycleManager lifecycleManager,
TransactionManager db, DatabaseComponent db,
AndroidExecutor androidExecutor, AndroidExecutor androidExecutor,
SettingsManager settingsManager) { SettingsManager settingsManager) {
super(app, dbExecutor, lifecycleManager, db, androidExecutor); super(app, dbExecutor, lifecycleManager, db, androidExecutor);
this.db = db;
this.settingsManager = settingsManager; this.settingsManager = settingsManager;
} }
@@ -174,4 +176,16 @@ public class NavDrawerViewModel extends DbViewModel {
} }
}); });
} }
public void printStats() {
runOnDbThread(() -> {
try {
db.transaction(false, txn -> {
db.printStats(txn);
});
} catch (DbException e) {
handleException(e);
}
});
}
} }

View File

@@ -10,13 +10,14 @@
android:id="@+id/imageView" android:id="@+id/imageView"
android:layout_width="32dp" android:layout_width="32dp"
android:layout_height="32dp" android:layout_height="32dp"
android:layout_marginStart="16dp" android:layout_marginHorizontal="16dp"
android:layout_marginLeft="16dp" app:layout_constraintBottom_toTopOf="@+id/statusTitleView"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintEnd_toStartOf="@+id/statusTitleView" app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintHorizontal_chainStyle="packed" app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.25"
app:layout_constraintVertical_chainStyle="packed" app:layout_constraintVertical_chainStyle="packed"
app:srcCompat="@drawable/ic_check_circle_outline" app:srcCompat="@drawable/ic_check_circle_outline"
app:tint="@color/briar_brand_green" app:tint="@color/briar_brand_green"
@@ -26,14 +27,37 @@
android:id="@+id/statusTitleView" android:id="@+id/statusTitleView"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginHorizontal="16dp" android:layout_margin="16dp"
android:text="@string/mailbox_status_connected_title" android:text="@string/mailbox_status_connected_title"
android:textAppearance="@style/TextAppearance.MaterialComponents.Headline6" android:textAppearance="@style/TextAppearance.MaterialComponents.Headline6"
app:layout_constrainedWidth="true" app:layout_constrainedWidth="true"
app:layout_constraintBottom_toBottomOf="@+id/imageView" app:layout_constraintBottom_toTopOf="@+id/checkButton"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/imageView" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@+id/imageView" /> app:layout_constraintTop_toBottomOf="@+id/imageView" />
<Button
android:id="@+id/checkButton"
style="@style/BriarButtonFlat.Neutral"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginHorizontal="16dp"
android:text="@string/mailbox_status_check_button"
app:layout_constraintBottom_toTopOf="@+id/statusInfoView"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/statusTitleView" />
<ProgressBar
android:id="@+id/checkProgress"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="invisible"
app:layout_constraintBottom_toBottomOf="@+id/checkButton"
app:layout_constraintEnd_toEndOf="@+id/checkButton"
app:layout_constraintStart_toStartOf="@+id/checkButton"
app:layout_constraintTop_toTopOf="@+id/checkButton"
tools:visibility="visible" />
<TextView <TextView
android:id="@+id/statusInfoView" android:id="@+id/statusInfoView"
@@ -41,11 +65,21 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_margin="16dp" android:layout_margin="16dp"
android:gravity="center" android:gravity="center"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toTopOf="@+id/unlinkButton"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/statusTitleView" app:layout_constraintTop_toBottomOf="@+id/checkButton"
app:layout_constraintVertical_bias="0.0"
tools:text="@string/mailbox_status_connected_info" /> tools:text="@string/mailbox_status_connected_info" />
<Button
android:id="@+id/unlinkButton"
style="@style/BriarButtonFlat.Negative"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="16dp"
android:text="@string/mailbox_status_unlink_button"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -643,10 +643,14 @@
<string name="tor_offline_button_check">Check connection settings</string> <string name="tor_offline_button_check">Check connection settings</string>
<string name="mailbox_status_title">Mailbox status</string> <string name="mailbox_status_title">Mailbox status</string>
<string name="mailbox_status_connected_title">Mailbox is running</string> <string name="mailbox_status_connected_title">Mailbox is running</string>
<string name="mailbox_status_problem_title">We are having trouble connecting to the mailbox</string>
<string name="mailbox_status_failure_title">Mailbox is unavailable</string>
<string name="mailbox_status_check_button">Check Connection</string>
<!-- Example for string substitution: Last connection: 3min ago--> <!-- Example for string substitution: Last connection: 3min ago-->
<string name="mailbox_status_connected_info">Last connection: %s</string> <string name="mailbox_status_connected_info">Last connection: %s</string>
<!-- Indicates that there never was a connection to the mailbox. Last connection: Never --> <!-- Indicates that there never was a connection to the mailbox. Last connection: Never -->
<string name="mailbox_status_connected_never">Never</string> <string name="mailbox_status_connected_never">Never</string>
<string name="mailbox_status_unlink_button">Unlink</string>
<!-- Conversation Settings --> <!-- Conversation Settings -->
<string name="disappearing_messages_title">Disappearing messages</string> <string name="disappearing_messages_title">Disappearing messages</string>